From a9ea9d273f655bb5f84ee7fde0a4d160549ce954 Mon Sep 17 00:00:00 2001 From: "Ycarus (Yannick Chabanois)" Date: Thu, 30 Sep 2021 09:31:47 +0200 Subject: [PATCH] Update OpenWrt and fix patches --- build.sh | 90 ++--- ...w_table-fix-offloaded-connection-tim.patch | 115 ++++++ ...dd-support-for-threaded-NAPI-polling.patch | 344 ++++++++++++++++++ 3 files changed, 504 insertions(+), 45 deletions(-) create mode 100644 root/target/linux/generic/backport-5.4/370-netfilter-nf_flow_table-fix-offloaded-connection-tim.patch create mode 100644 root/target/linux/generic/pending-5.4/690-net-add-support-for-threaded-NAPI-polling.patch diff --git a/build.sh b/build.sh index 08ef375c..554a5dce 100755 --- a/build.sh +++ b/build.sh @@ -90,9 +90,9 @@ fi #_get_repo source https://github.com/ysurac/openmptcprouter-source "master" if [ "$OMR_OPENWRT" = "default" ]; then - _get_repo "$OMR_TARGET/source" https://github.com/openwrt/openwrt "830c2e53781ade1817b03bbb8ece6291ae34df5d" - _get_repo feeds/packages https://github.com/openwrt/packages "a94ef655ff2d4544083f79cd8cef9599865f0005" - _get_repo feeds/luci https://github.com/openwrt/luci "0b49ed45c6e9f4bc10abdcea392125aec3794e58" + _get_repo "$OMR_TARGET/source" https://github.com/openwrt/openwrt "585cef5f1a9c1c3aecd7d231364618e96d03ab65" + _get_repo feeds/packages https://github.com/openwrt/packages "e2055b5433da245e6ff8fb060d018d036499cf38" + _get_repo feeds/luci https://github.com/openwrt/luci "7c943a1d6bcf449019ca8a43e800e51f269bb8f6" elif [ "$OMR_OPENWRT" = "master" ]; then _get_repo "$OMR_TARGET/source" https://github.com/openwrt/openwrt "master" _get_repo feeds/packages https://github.com/openwrt/packages "master" @@ -437,48 +437,48 @@ fi #fi #echo "Done" -if [ -f target/linux/generic/backport-5.4/370-netfilter-nf_flow_table-fix-offloaded-connection-tim.patch ]; then - rm -f target/linux/generic/backport-5.4/370-netfilter-nf_flow_table-fix-offloaded-connection-tim.patch -fi -if [ -f target/linux/generic/pending-5.4/640-netfilter-nf_flow_table-add-hardware-offload-support.patch ]; then - rm -f target/linux/generic/pending-5.4/640-netfilter-nf_flow_table-add-hardware-offload-support.patch -fi -if [ -f target/linux/generic/pending-5.4/641-netfilter-nf_flow_table-support-hw-offload-through-v.patch ]; then - rm -f target/linux/generic/pending-5.4/641-netfilter-nf_flow_table-support-hw-offload-through-v.patch -fi -if [ -f target/linux/generic/pending-5.4/642-net-8021q-support-hardware-flow-table-offload.patch ]; then - rm -f target/linux/generic/pending-5.4/642-net-8021q-support-hardware-flow-table-offload.patch -fi -if [ -f target/linux/generic/pending-5.4/643-net-bridge-support-hardware-flow-table-offload.patch ]; then - rm -f target/linux/generic/pending-5.4/643-net-bridge-support-hardware-flow-table-offload.patch -fi -if [ -f target/linux/generic/pending-5.4/644-net-pppoe-support-hardware-flow-table-offload.patch ]; then - rm -f target/linux/generic/pending-5.4/644-net-pppoe-support-hardware-flow-table-offload.patch -fi -if [ -f target/linux/generic/pending-5.4/645-netfilter-nf_flow_table-rework-hardware-offload-time.patch ]; then - rm -f target/linux/generic/pending-5.4/645-netfilter-nf_flow_table-rework-hardware-offload-time.patch -fi -if [ -f target/linux/generic/pending-5.4/647-net-dsa-support-hardware-flow-table-offload.patch ]; then - rm -f target/linux/generic/pending-5.4/647-net-dsa-support-hardware-flow-table-offload.patch -fi -if [ -f target/linux/generic/hack-5.4/650-netfilter-add-xt_OFFLOAD-target.patch ]; then - rm -f target/linux/generic/hack-5.4/650-netfilter-add-xt_OFFLOAD-target.patch -fi -if [ -f target/linux/generic/pending-5.4/690-net-add-support-for-threaded-NAPI-polling.patch ]; then - rm -f target/linux/generic/pending-5.4/690-net-add-support-for-threaded-NAPI-polling.patch -fi -if [ -f target/linux/generic/hack-5.4/647-netfilter-flow-acct.patch ]; then - rm -f target/linux/generic/hack-5.4/647-netfilter-flow-acct.patch -fi -if [ -f target/linux/generic/hack-5.4/953-net-patch-linux-kernel-to-support-shortcut-fe.patch ]; then - rm -f target/linux/generic/hack-5.4/953-net-patch-linux-kernel-to-support-shortcut-fe.patch -fi -if [ -f target/linux/bcm27xx/patches-5.4/950-1031-net-lan78xx-Ack-pending-PHY-ints-when-resetting.patch ]; then - rm -f target/linux/bcm27xx/patches-5.4/950-1031-net-lan78xx-Ack-pending-PHY-ints-when-resetting.patch -fi -if [ -f target/linux/generic/pending-5.4/770-16-net-ethernet-mediatek-mtk_eth_soc-add-flow-offloadin.patch ]; then - rm -f target/linux/generic/pending-5.4/770-16-net-ethernet-mediatek-mtk_eth_soc-add-flow-offloadin.patch -fi +#if [ -f target/linux/generic/backport-5.4/370-netfilter-nf_flow_table-fix-offloaded-connection-tim.patch ]; then +# rm -f target/linux/generic/backport-5.4/370-netfilter-nf_flow_table-fix-offloaded-connection-tim.patch +#fi +#if [ -f target/linux/generic/pending-5.4/640-netfilter-nf_flow_table-add-hardware-offload-support.patch ]; then +# rm -f target/linux/generic/pending-5.4/640-netfilter-nf_flow_table-add-hardware-offload-support.patch +#fi +#if [ -f target/linux/generic/pending-5.4/641-netfilter-nf_flow_table-support-hw-offload-through-v.patch ]; then +# rm -f target/linux/generic/pending-5.4/641-netfilter-nf_flow_table-support-hw-offload-through-v.patch +#fi +#if [ -f target/linux/generic/pending-5.4/642-net-8021q-support-hardware-flow-table-offload.patch ]; then +# rm -f target/linux/generic/pending-5.4/642-net-8021q-support-hardware-flow-table-offload.patch +#fi +#if [ -f target/linux/generic/pending-5.4/643-net-bridge-support-hardware-flow-table-offload.patch ]; then +# rm -f target/linux/generic/pending-5.4/643-net-bridge-support-hardware-flow-table-offload.patch +#fi +#if [ -f target/linux/generic/pending-5.4/644-net-pppoe-support-hardware-flow-table-offload.patch ]; then +# rm -f target/linux/generic/pending-5.4/644-net-pppoe-support-hardware-flow-table-offload.patch +#fi +#if [ -f target/linux/generic/pending-5.4/645-netfilter-nf_flow_table-rework-hardware-offload-time.patch ]; then +# rm -f target/linux/generic/pending-5.4/645-netfilter-nf_flow_table-rework-hardware-offload-time.patch +#fi +#if [ -f target/linux/generic/pending-5.4/647-net-dsa-support-hardware-flow-table-offload.patch ]; then +# rm -f target/linux/generic/pending-5.4/647-net-dsa-support-hardware-flow-table-offload.patch +#fi +#if [ -f target/linux/generic/hack-5.4/650-netfilter-add-xt_OFFLOAD-target.patch ]; then +# rm -f target/linux/generic/hack-5.4/650-netfilter-add-xt_OFFLOAD-target.patch +#fi +#if [ -f target/linux/generic/pending-5.4/690-net-add-support-for-threaded-NAPI-polling.patch ]; then +# rm -f target/linux/generic/pending-5.4/690-net-add-support-for-threaded-NAPI-polling.patch +#fi +#if [ -f target/linux/generic/hack-5.4/647-netfilter-flow-acct.patch ]; then +# rm -f target/linux/generic/hack-5.4/647-netfilter-flow-acct.patch +#fi +#if [ -f target/linux/generic/hack-5.4/953-net-patch-linux-kernel-to-support-shortcut-fe.patch ]; then +# rm -f target/linux/generic/hack-5.4/953-net-patch-linux-kernel-to-support-shortcut-fe.patch +#fi +#if [ -f target/linux/bcm27xx/patches-5.4/950-1031-net-lan78xx-Ack-pending-PHY-ints-when-resetting.patch ]; then +# rm -f target/linux/bcm27xx/patches-5.4/950-1031-net-lan78xx-Ack-pending-PHY-ints-when-resetting.patch +#fi +#if [ -f target/linux/generic/pending-5.4/770-16-net-ethernet-mediatek-mtk_eth_soc-add-flow-offloadin.patch ]; then +# rm -f target/linux/generic/pending-5.4/770-16-net-ethernet-mediatek-mtk_eth_soc-add-flow-offloadin.patch +#fi if [ "$OMR_KERNEL" = "5.4" ]; then echo "Set to kernel 5.4 for rpi arch" diff --git a/root/target/linux/generic/backport-5.4/370-netfilter-nf_flow_table-fix-offloaded-connection-tim.patch b/root/target/linux/generic/backport-5.4/370-netfilter-nf_flow_table-fix-offloaded-connection-tim.patch new file mode 100644 index 00000000..5dac7f5e --- /dev/null +++ b/root/target/linux/generic/backport-5.4/370-netfilter-nf_flow_table-fix-offloaded-connection-tim.patch @@ -0,0 +1,115 @@ +From: Felix Fietkau +Date: Wed, 13 Jun 2018 12:33:39 +0200 +Subject: [PATCH] netfilter: nf_flow_table: fix offloaded connection timeout + corner case + +The full teardown of offloaded flows is deferred to a gc work item, +however processing of packets by netfilter needs to happen immediately +after a teardown is requested, because the conntrack state needs to be +fixed up. + +Since the IPS_OFFLOAD_BIT is still kept until the teardown is complete, +the netfilter conntrack gc can accidentally bump the timeout of a +connection where offload was just stopped, causing a conntrack entry +leak. + +Fix this by moving the conntrack timeout bumping from conntrack core to +the nf_flow_offload and add a check to prevent bogus timeout bumps. + +Signed-off-by: Felix Fietkau +--- + +--- a/net/netfilter/nf_conntrack_core.c ++++ b/net/netfilter/nf_conntrack_core.c +@@ -1207,18 +1207,6 @@ static bool gc_worker_can_early_drop(con + return false; + } + +-#define DAY (86400 * HZ) +- +-/* Set an arbitrary timeout large enough not to ever expire, this save +- * us a check for the IPS_OFFLOAD_BIT from the packet path via +- * nf_ct_is_expired(). +- */ +-static void nf_ct_offload_timeout(struct nf_conn *ct) +-{ +- if (nf_ct_expires(ct) < DAY / 2) +- ct->timeout = nfct_time_stamp + DAY; +-} +- + static void gc_worker(struct work_struct *work) + { + unsigned long end_time = jiffies + GC_SCAN_MAX_DURATION; +@@ -1250,11 +1238,9 @@ static void gc_worker(struct work_struct + + tmp = nf_ct_tuplehash_to_ctrack(h); + + scanned++; +- if (test_bit(IPS_OFFLOAD_BIT, &tmp->status)) { +- nf_ct_offload_timeout(tmp); ++ if (test_bit(IPS_OFFLOAD_BIT, &tmp->status)) + continue; +- } + + if (nf_ct_is_expired(tmp)) { + nf_ct_gc_expired(tmp); +--- a/net/netfilter/nf_flow_table_core.c ++++ b/net/netfilter/nf_flow_table_core.c +@@ -198,10 +198,29 @@ static const struct rhashtable_params nf + .automatic_shrinking = true, + }; + ++#define DAY (86400 * HZ) ++ ++/* Set an arbitrary timeout large enough not to ever expire, this save ++ * us a check for the IPS_OFFLOAD_BIT from the packet path via ++ * nf_ct_is_expired(). ++ */ ++static void nf_ct_offload_timeout(struct flow_offload *flow) ++{ ++ struct flow_offload_entry *entry; ++ struct nf_conn *ct; ++ ++ entry = container_of(flow, struct flow_offload_entry, flow); ++ ct = entry->ct; ++ ++ if (nf_ct_expires(ct) < DAY / 2) ++ ct->timeout = nfct_time_stamp + DAY; ++} ++ + int flow_offload_add(struct nf_flowtable *flow_table, struct flow_offload *flow) + { + int err; + ++ nf_ct_offload_timeout(flow); + flow->timeout = (u32)jiffies + NF_FLOW_TIMEOUT; + + err = rhashtable_insert_fast(&flow_table->rhashtable, +@@ -304,6 +323,7 @@ nf_flow_table_iterate(struct nf_flowtabl + rhashtable_walk_start(&hti); + + while ((tuplehash = rhashtable_walk_next(&hti))) { ++ + if (IS_ERR(tuplehash)) { + if (PTR_ERR(tuplehash) != -EAGAIN) { + err = PTR_ERR(tuplehash); +@@ -328,10 +348,17 @@ static void nf_flow_offload_gc_step(stru + { + struct nf_flowtable *flow_table = data; + struct flow_offload_entry *e; ++ bool teardown; + + e = container_of(flow, struct flow_offload_entry, flow); +- if (nf_flow_has_expired(flow) || nf_ct_is_dying(e->ct) || +- (flow->flags & (FLOW_OFFLOAD_DYING | FLOW_OFFLOAD_TEARDOWN))) ++ ++ teardown = flow->flags & (FLOW_OFFLOAD_DYING | ++ FLOW_OFFLOAD_TEARDOWN); ++ ++ if (!teardown) ++ nf_ct_offload_timeout(flow); ++ ++ if (nf_flow_has_expired(flow) || teardown) + flow_offload_del(flow_table, flow); + } + diff --git a/root/target/linux/generic/pending-5.4/690-net-add-support-for-threaded-NAPI-polling.patch b/root/target/linux/generic/pending-5.4/690-net-add-support-for-threaded-NAPI-polling.patch new file mode 100644 index 00000000..88c995f2 --- /dev/null +++ b/root/target/linux/generic/pending-5.4/690-net-add-support-for-threaded-NAPI-polling.patch @@ -0,0 +1,344 @@ +From: Felix Fietkau +Date: Sun, 26 Jul 2020 14:03:21 +0200 +Subject: [PATCH] net: add support for threaded NAPI polling + +For some drivers (especially 802.11 drivers), doing a lot of work in the NAPI +poll function does not perform well. Since NAPI poll is bound to the CPU it +was scheduled from, we can easily end up with a few very busy CPUs spending +most of their time in softirq/ksoftirqd and some idle ones. + +Introduce threaded NAPI for such drivers based on a workqueue. The API is the +same except for using netif_threaded_napi_add instead of netif_napi_add. + +In my tests with mt76 on MT7621 using threaded NAPI + a thread for tx scheduling +improves LAN->WLAN bridging throughput by 10-50%. Throughput without threaded +NAPI is wildly inconsistent, depending on the CPU that runs the tx scheduling +thread. + +With threaded NAPI it seems stable and consistent (and higher than the best +results I got without it). + +Based on a patch by Hillf Danton + +Cc: Hillf Danton +Signed-off-by: Felix Fietkau +--- + +--- a/include/linux/netdevice.h ++++ b/include/linux/netdevice.h +@@ -340,6 +340,7 @@ struct napi_struct { + struct list_head dev_list; + struct hlist_node napi_hash_node; + unsigned int napi_id; ++ struct work_struct work; + }; + + enum { +@@ -350,6 +351,7 @@ enum { + NAPI_STATE_HASHED, /* In NAPI hash (busy polling possible) */ + NAPI_STATE_NO_BUSY_POLL,/* Do not add in napi_hash, no busy polling */ + NAPI_STATE_IN_BUSY_POLL,/* sk_busy_loop() owns this NAPI */ ++ NAPI_STATE_THREADED, /* Use threaded NAPI */ + }; + + enum { +@@ -360,6 +362,7 @@ enum { + NAPIF_STATE_HASHED = BIT(NAPI_STATE_HASHED), + NAPIF_STATE_NO_BUSY_POLL = BIT(NAPI_STATE_NO_BUSY_POLL), + NAPIF_STATE_IN_BUSY_POLL = BIT(NAPI_STATE_IN_BUSY_POLL), ++ NAPIF_STATE_THREADED = BIT(NAPI_STATE_THREADED), + }; + + enum gro_result { +@@ -2101,6 +2104,7 @@ struct net_device { + struct lock_class_key addr_list_lock_key; + bool proto_down; + unsigned wol_enabled:1; ++ unsigned threaded:1; + }; + #define to_net_dev(d) container_of(d, struct net_device, dev) + +@@ -2281,6 +2285,26 @@ void netif_napi_add(struct net_device *d + int (*poll)(struct napi_struct *, int), int weight); + + /** ++ * netif_threaded_napi_add - initialize a NAPI context ++ * @dev: network device ++ * @napi: NAPI context ++ * @poll: polling function ++ * @weight: default weight ++ * ++ * This variant of netif_napi_add() should be used from drivers using NAPI ++ * with CPU intensive poll functions. ++ * This will schedule polling from a high priority workqueue ++ */ ++static inline void netif_threaded_napi_add(struct net_device *dev, ++ struct napi_struct *napi, ++ int (*poll)(struct napi_struct *, int), ++ int weight) ++{ ++ set_bit(NAPI_STATE_THREADED, &napi->state); ++ netif_napi_add(dev, napi, poll, weight); ++} ++ ++/** + * netif_tx_napi_add - initialize a NAPI context + * @dev: network device + * @napi: NAPI context +--- a/net/core/dev.c ++++ b/net/core/dev.c +@@ -156,6 +156,7 @@ static DEFINE_SPINLOCK(offload_lock); + struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly; + struct list_head ptype_all __read_mostly; /* Taps */ + static struct list_head offload_base __read_mostly; ++static struct workqueue_struct *napi_workq __read_mostly; + + static int netif_rx_internal(struct sk_buff *skb); + static int call_netdevice_notifiers_info(unsigned long val, +@@ -5931,6 +5932,11 @@ void __napi_schedule(struct napi_struct + { + unsigned long flags; + ++ if (test_bit(NAPI_STATE_THREADED, &n->state)) { ++ queue_work(napi_workq, &n->work); ++ return; ++ } ++ + local_irq_save(flags); + ____napi_schedule(this_cpu_ptr(&softnet_data), n); + local_irq_restore(flags); +@@ -6246,9 +6256,89 @@ static void init_gro_hash(struct napi_st + napi->gro_bitmask = 0; + } + ++static int __napi_poll(struct napi_struct *n, bool *repoll) ++{ ++ int work, weight; ++ ++ weight = n->weight; ++ ++ /* This NAPI_STATE_SCHED test is for avoiding a race ++ * with netpoll's poll_napi(). Only the entity which ++ * obtains the lock and sees NAPI_STATE_SCHED set will ++ * actually make the ->poll() call. Therefore we avoid ++ * accidentally calling ->poll() when NAPI is not scheduled. ++ */ ++ work = 0; ++ if (test_bit(NAPI_STATE_SCHED, &n->state)) { ++ work = n->poll(n, weight); ++ trace_napi_poll(n, work, weight); ++ } ++ ++ WARN_ON_ONCE(work > weight); ++ ++ if (likely(work < weight)) ++ return work; ++ ++ /* Drivers must not modify the NAPI state if they ++ * consume the entire weight. In such cases this code ++ * still "owns" the NAPI instance and therefore can ++ * move the instance around on the list at-will. ++ */ ++ if (unlikely(napi_disable_pending(n))) { ++ napi_complete(n); ++ return work; ++ } ++ ++ if (n->gro_bitmask) { ++ /* flush too old packets ++ * If HZ < 1000, flush all packets. ++ */ ++ napi_gro_flush(n, HZ >= 1000); ++ } ++ ++ gro_normal_list(n); ++ ++ *repoll = true; ++ ++ return work; ++} ++ ++static void napi_workfn(struct work_struct *work) ++{ ++ struct napi_struct *n = container_of(work, struct napi_struct, work); ++ void *have; ++ ++ for (;;) { ++ bool repoll = false; ++ ++ local_bh_disable(); ++ ++ have = netpoll_poll_lock(n); ++ __napi_poll(n, &repoll); ++ netpoll_poll_unlock(have); ++ ++ local_bh_enable(); ++ ++ if (!repoll) ++ return; ++ ++ if (!need_resched()) ++ continue; ++ ++ /* ++ * have to pay for the latency of task switch even if ++ * napi is scheduled ++ */ ++ queue_work(napi_workq, work); ++ return; ++ } ++} ++ + void netif_napi_add(struct net_device *dev, struct napi_struct *napi, + int (*poll)(struct napi_struct *, int), int weight) + { ++ if (dev->threaded) ++ set_bit(NAPI_STATE_THREADED, &napi->state); + INIT_LIST_HEAD(&napi->poll_list); + hrtimer_init(&napi->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_PINNED); + napi->timer.function = napi_watchdog; +@@ -6265,6 +6355,7 @@ void netif_napi_add(struct net_device *d + #ifdef CONFIG_NETPOLL + napi->poll_owner = -1; + #endif ++ INIT_WORK(&napi->work, napi_workfn); + set_bit(NAPI_STATE_SCHED, &napi->state); + set_bit(NAPI_STATE_NPSVC, &napi->state); + list_add_rcu(&napi->dev_list, &dev->napi_list); +@@ -6305,6 +6396,7 @@ static void flush_gro_hash(struct napi_s + void netif_napi_del(struct napi_struct *napi) + { + might_sleep(); ++ cancel_work_sync(&napi->work); + if (napi_hash_del(napi)) + synchronize_net(); + list_del_init(&napi->dev_list); +@@ -6317,50 +6409,18 @@ EXPORT_SYMBOL(netif_napi_del); + + static int napi_poll(struct napi_struct *n, struct list_head *repoll) + { ++ bool do_repoll = false; + void *have; +- int work, weight; ++ int work; + + list_del_init(&n->poll_list); + + have = netpoll_poll_lock(n); + +- weight = n->weight; +- +- /* This NAPI_STATE_SCHED test is for avoiding a race +- * with netpoll's poll_napi(). Only the entity which +- * obtains the lock and sees NAPI_STATE_SCHED set will +- * actually make the ->poll() call. Therefore we avoid +- * accidentally calling ->poll() when NAPI is not scheduled. +- */ +- work = 0; +- if (test_bit(NAPI_STATE_SCHED, &n->state)) { +- work = n->poll(n, weight); +- trace_napi_poll(n, work, weight); +- } +- +- WARN_ON_ONCE(work > weight); ++ work = __napi_poll(n, &do_repoll); + +- if (likely(work < weight)) +- goto out_unlock; +- +- /* Drivers must not modify the NAPI state if they +- * consume the entire weight. In such cases this code +- * still "owns" the NAPI instance and therefore can +- * move the instance around on the list at-will. +- */ +- if (unlikely(napi_disable_pending(n))) { +- napi_complete(n); ++ if (!do_repoll) + goto out_unlock; +- } +- +- if (n->gro_bitmask) { +- /* flush too old packets +- * If HZ < 1000, flush all packets. +- */ +- napi_gro_flush(n, HZ >= 1000); +- } +- +- gro_normal_list(n); + + /* Some drivers may have called napi_schedule + * prior to exhausting their budget. +@@ -10340,6 +10400,10 @@ static int __init net_dev_init(void) + sd->backlog.weight = weight_p; + } + ++ napi_workq = alloc_workqueue("napi_workq", WQ_UNBOUND | WQ_HIGHPRI, ++ WQ_UNBOUND_MAX_ACTIVE | WQ_SYSFS); ++ BUG_ON(!napi_workq); ++ + dev_boot_phase = 0; + + /* The loopback device is special if any other network devices +--- a/net/core/net-sysfs.c ++++ b/net/core/net-sysfs.c +@@ -442,6 +442,52 @@ static ssize_t proto_down_store(struct d + } + NETDEVICE_SHOW_RW(proto_down, fmt_dec); + ++static int change_napi_threaded(struct net_device *dev, unsigned long val) ++{ ++ struct napi_struct *napi; ++ ++ if (list_empty(&dev->napi_list)) ++ return -EOPNOTSUPP; ++ ++ list_for_each_entry(napi, &dev->napi_list, dev_list) { ++ if (val) ++ set_bit(NAPI_STATE_THREADED, &napi->state); ++ else ++ clear_bit(NAPI_STATE_THREADED, &napi->state); ++ } ++ ++ return 0; ++} ++ ++static ssize_t napi_threaded_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ return netdev_store(dev, attr, buf, len, change_napi_threaded); ++} ++ ++static ssize_t napi_threaded_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct net_device *netdev = to_net_dev(dev); ++ struct napi_struct *napi; ++ bool enabled = false; ++ ++ if (!rtnl_trylock()) ++ return restart_syscall(); ++ ++ list_for_each_entry(napi, &netdev->napi_list, dev_list) { ++ if (test_bit(NAPI_STATE_THREADED, &napi->state)) ++ enabled = true; ++ } ++ ++ rtnl_unlock(); ++ ++ return sprintf(buf, fmt_dec, enabled); ++} ++static DEVICE_ATTR_RW(napi_threaded); ++ + static ssize_t phys_port_id_show(struct device *dev, + struct device_attribute *attr, char *buf) + { +@@ -532,6 +578,7 @@ static struct attribute *net_class_attrs + &dev_attr_flags.attr, + &dev_attr_tx_queue_len.attr, + &dev_attr_gro_flush_timeout.attr, ++ &dev_attr_napi_threaded.attr, + &dev_attr_phys_port_id.attr, + &dev_attr_phys_port_name.attr, + &dev_attr_phys_switch_id.attr,