hulk inclusion category: feature bugzilla: https://atomgit.com/openeuler/kernel/issues/10109 ---------------------------------------- Add a new loadable module net/dynthread, which will be used to switch the NAPI processing of the configured net devices between softirq context and kernel threads at runtime, based on the load of a target application. As a first step add the Kconfig entry (restricted to ARM64 and to be built as a module only), the MAINTAINERS entry and the module skeleton. On load the module takes a reference on the net devices given with the ifname module parameter ('#' separated list of names), walks the IRQ descriptor table and records the number and the current affinity CPU of every RX queue IRQ of those devices. IRQs are matched by name: an IRQ belongs to a device when its name contains the device name or the name of its parent device and one of the common RX queue IRQ name patterns, which can be extended via the irqname module parameter. The recorded affinity allows the later patches to restore the original IRQ placement. The net device reference is dropped when the module is unloaded. Co-developed-by: Yue Haibing <yuehaibing@huawei.com> Signed-off-by: Yue Haibing <yuehaibing@huawei.com> Assisted-by: opencode:glm-5.3 Signed-off-by: Cai Xinchen <caixinchen1@huawei.com> --- net/dynthread/Kconfig | 13 ++ net/dynthread/Makefile | 2 + net/dynthread/net_dynthread.c | 273 ++++++++++++++++++++++++++++++++++ 3 files changed, 288 insertions(+) create mode 100644 net/dynthread/Kconfig create mode 100644 net/dynthread/Makefile create mode 100644 net/dynthread/net_dynthread.c diff --git a/net/dynthread/Kconfig b/net/dynthread/Kconfig new file mode 100644 index 000000000000..b7c19723937c --- /dev/null +++ b/net/dynthread/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only +config NET_DYNTHREAD + tristate "Dynamic threaded NAPI scheduler" + depends on m && ARM64 + default m + help + Monitor the scheduling behaviour of a target application and, + when it is interrupted too often by network processing, switch + the NAPI processing of the configured net devices from softirq + context to dedicated kernel threads, whose CPU affinity is + adapted dynamically. + + This module can only be built as a loadable module. diff --git a/net/dynthread/Makefile b/net/dynthread/Makefile new file mode 100644 index 000000000000..9c7ab5f5c2e9 --- /dev/null +++ b/net/dynthread/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_NET_DYNTHREAD) += net_dynthread.o diff --git a/net/dynthread/net_dynthread.c b/net/dynthread/net_dynthread.c new file mode 100644 index 000000000000..1aa7b35cfd94 --- /dev/null +++ b/net/dynthread/net_dynthread.c @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Dynamic threaded NAPI scheduler + * + * Record the RX queue IRQ information of the net devices configured + * via the ifname module parameter, so that the IRQ placement can be + * reworked at runtime. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/irq.h> +#include <linux/netdevice.h> +#include <linux/topology.h> + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Dynamic threaded NAPI scheduler"); + +#define SCHED_MAX_NETDEV_NUM 8 +#define MAX_DEV_RXQ_NUM 256 + +static char ifname[64]; +module_param_string(ifname, ifname, sizeof(ifname), 0644); +MODULE_PARM_DESC(ifname, "net device names to monitor, separated by '#'"); + +static char irqname[64] = "comp"; +module_param_string(irqname, irqname, sizeof(irqname), 0644); +MODULE_PARM_DESC(irqname, "substring of the NIC RX queue IRQ names, default comp"); + +/** + * struct sched_netdev_queue_info - IRQ information of one RX queue + * @irq: Linux IRQ number of the queue + * @affinity_cpu: CPU the IRQ was affined to when the module was loaded + */ +struct sched_netdev_queue_info { + int irq; + int affinity_cpu; +}; + +/** + * struct sched_netdev - net device to monitor + * @dev_name: interface name + * @netdev: net device with a reference held by this module + * @rxq_num: number of RX queue IRQs recorded + * @node_num: number of NUMA nodes the RX queue IRQs span + * @rxq: IRQ information of the RX queues + */ +struct sched_netdev { + char dev_name[IFNAMSIZ]; + struct net_device *netdev; + int rxq_num; + int node_num; + struct sched_netdev_queue_info rxq[MAX_DEV_RXQ_NUM]; +}; + +static int sched_netdev_num; +static struct sched_netdev sched_netdev_table[SCHED_MAX_NETDEV_NUM]; + +static struct sched_netdev *get_sched_netdev(int index) +{ + if (index >= SCHED_MAX_NETDEV_NUM) + return NULL; + + return &sched_netdev_table[index]; +} + +static struct sched_netdev *alloc_sched_netdev(void) +{ + if (sched_netdev_num >= SCHED_MAX_NETDEV_NUM) + return NULL; + + return &sched_netdev_table[sched_netdev_num++]; +} + +/* Does @irq_name belong to an RX queue of @sched_dev? The default + * patterns match the common NIC completion and RX queue IRQ names, the + * user provided pattern is used as an additional match when not empty. + */ +static bool check_irq_name(const char *irq_name, struct sched_netdev *sched_dev) +{ + if (!strstr(irq_name, "TxRx") && !strstr(irq_name, "comp") && + !strstr(irq_name, "rx") && strlen(irqname) > 0 && + !strstr(irq_name, irqname)) + return false; + + if (strstr(irq_name, sched_dev->dev_name)) + return true; + + if (sched_dev->netdev->dev.parent && + strstr(irq_name, dev_name(sched_dev->netdev->dev.parent))) + return true; + + return false; +} + +static void get_netdev_queue_info(struct sched_netdev *sched_dev) +{ + struct sched_netdev_queue_info *rxq_info; + struct irq_desc *desc; + struct irq_data *data; + unsigned long flags; + char name[64]; + int irq, cpu; + + /* irq_to_desc(), and thus for_each_irq_desc(), is not exported to + * modules when SPARSE_IRQ is enabled; walk the IRQ numbers through + * the exported irq_get_irq_data() instead + */ + for (irq = 0; irq < nr_irqs; irq++) { + data = irq_get_irq_data(irq); + if (!data) + continue; + + desc = irq_data_to_desc(data); + + /* desc->action and the effective affinity are protected by + * desc->lock; snapshot them under the lock so that the + * check runs on stable data and the name cannot be freed + * by a concurrent free_irq() + */ + cpu = nr_cpu_ids; + name[0] = '\0'; + raw_spin_lock_irqsave(&desc->lock, flags); + if (desc->action && desc->action->name && + check_irq_name(desc->action->name, sched_dev)) { + strscpy(name, desc->action->name, sizeof(name)); + cpu = cpumask_first(irq_data_get_effective_affinity_mask( + &desc->irq_data)); + } + raw_spin_unlock_irqrestore(&desc->lock, flags); + + /* unknown affinity, or the rxq array is full: skip */ + if (!name[0] || cpu >= nr_cpu_ids) + continue; + if (sched_dev->rxq_num >= MAX_DEV_RXQ_NUM) + break; + + rxq_info = &sched_dev->rxq[sched_dev->rxq_num++]; + rxq_info->irq = irq; + rxq_info->affinity_cpu = cpu; + } +} + +/* Drop the references taken on the devices and forget them. Used + * both on module unload and to unwind a partially failed init. + */ +static void deinit_netdevinfo(void) +{ + struct sched_netdev *sched_dev; + int id; + + for (id = 0; id < sched_netdev_num; id++) { + sched_dev = get_sched_netdev(id); + if (sched_dev && sched_dev->netdev) + dev_put(sched_dev->netdev); + } + sched_netdev_num = 0; +} + +/* count_sched_netdev_nodes - count the NUMA nodes of the RX queues */ +static void count_sched_netdev_nodes(struct sched_netdev *sched_dev) +{ + nodemask_t nodes; + int rxq_idx, node; + + nodes_clear(nodes); + for (rxq_idx = 0; rxq_idx < sched_dev->rxq_num; rxq_idx++) { + node = cpu_to_node(sched_dev->rxq[rxq_idx].affinity_cpu); + if (node >= 0) + node_set(node, nodes); + } + sched_dev->node_num = nodes_weight(nodes); +} + +static int init_dev(const char *if_name, size_t length) +{ + struct sched_netdev *sched_dev; + struct net_device *netdev; + char dev_name[IFNAMSIZ]; + int ret; + + strscpy(dev_name, if_name, min_t(size_t, length + 1, IFNAMSIZ)); + netdev = dev_get_by_name(&init_net, dev_name); + if (!netdev) + return -ENODEV; + + if (!(netdev->flags & IFF_UP)) { + ret = -ENETDOWN; + goto err_dev_put; + } + + if (netdev->flags & IFF_LOOPBACK) { + ret = -EOPNOTSUPP; + goto err_dev_put; + } + + sched_dev = alloc_sched_netdev(); + if (!sched_dev) { + ret = -ENOMEM; + goto err_dev_put; + } + + memcpy(sched_dev->dev_name, dev_name, IFNAMSIZ); + sched_dev->netdev = netdev; + get_netdev_queue_info(sched_dev); + count_sched_netdev_nodes(sched_dev); + + return 0; + +err_dev_put: + dev_put(netdev); + return ret; +} + +/* Parse the '#' separated list of net device names and record the RX + * queue IRQ information of each device. The reference taken on each + * net device is dropped on module unload. + */ +static int init_netdevinfo(const char *netdev_str) +{ + const char *start = netdev_str, *end; + int err = -ENODEV; + + while (*start != '\0') { + /* skip empty names */ + end = strchr(start, '#'); + if (end == start) { + start++; + continue; + } + + /* the last name has no separator following it */ + if (!end) { + err = init_dev(start, strlen(start)); + break; + } + + err = init_dev(start, end - start); + if (err) + break; + + start = end + 1; + } + + if (err) + deinit_netdevinfo(); + + return err; +} + +static int __init dynthread_init(void) +{ + int ret; + + ret = init_netdevinfo(ifname); + if (ret) + return ret; + + + return 0; +} + +static void __exit dynthread_exit(void) +{ + deinit_netdevinfo(); + +} + +module_init(dynthread_init); +module_exit(dynthread_exit); -- 2.18.0.huawei.25