From 22f9dbed18bcc865d750ed109c6ae2dd4cf2af55 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 13 Jun 2026 22:25:24 -0700 Subject: netfilter: x_tables.h: fix all kernel-doc warnings - use correct names in kernel-doc comments - add missing struct members to kernel-doc comments Warning: include/linux/netfilter/x_tables.h:41 struct member 'targinfo' not described in 'xt_action_param' Warning: include/linux/netfilter/x_tables.h:41 Excess struct member 'targetinfo' description in 'xt_action_param' Warning: include/linux/netfilter/x_tables.h:90 struct member 'family' not described in 'xt_mtchk_param' Warning: include/linux/netfilter/x_tables.h:90 struct member 'nft_compat' not described in 'xt_mtchk_param' Warning: include/linux/netfilter/x_tables.h:101 expecting prototype for struct xt_mdtor_param. Prototype was for struct xt_mtdtor_param instead Warning: include/linux/netfilter/x_tables.h:121 struct member 'net' not described in 'xt_tgchk_param' Warning: include/linux/netfilter/x_tables.h:121 struct member 'table' not described in 'xt_tgchk_param' Warning: include/linux/netfilter/x_tables.h:121 struct member 'target' not described in 'xt_tgchk_param' Warning: include/linux/netfilter/x_tables.h:121 struct member 'targinfo' not described in 'xt_tgchk_param' Warning: include/linux/netfilter/x_tables.h:121 struct member 'hook_mask' not described in 'xt_tgchk_param' Warning: include/linux/netfilter/x_tables.h:121 struct member 'family' not described in 'xt_tgchk_param' Warning: include/linux/netfilter/x_tables.h:121 struct member 'nft_compat' not described in 'xt_tgchk_param' Warning: include/linux/netfilter/x_tables.h:345 expecting prototype for xt_recseq(). Prototype was for DECLARE_PER_CPU() instead Signed-off-by: Randy Dunlap Signed-off-by: Pablo Neira Ayuso --- include/linux/netfilter/x_tables.h | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) (limited to 'include/linux') diff --git a/include/linux/netfilter/x_tables.h b/include/linux/netfilter/x_tables.h index 20d70dddbe50..25062f4a0dd5 100644 --- a/include/linux/netfilter/x_tables.h +++ b/include/linux/netfilter/x_tables.h @@ -18,7 +18,7 @@ * @match: the match extension * @target: the target extension * @matchinfo: per-match data - * @targetinfo: per-target data + * @targinfo: per-target data * @state: pointer to hook state this packet came from * @fragoff: packet is a fragment, this is the data offset * @thoff: position of transport header relative to skb->data @@ -77,7 +77,9 @@ static inline u_int8_t xt_family(const struct xt_action_param *par) * @match: struct xt_match through which this function was invoked * @matchinfo: per-match data * @hook_mask: via which hooks the new rule is reachable - * Other fields as above. + * @family: actual NFPROTO_* through which the function is invoked + * (helpful when match->family == NFPROTO_UNSPEC) + * @nft_compat: running from the nft compat layer if true */ struct xt_mtchk_param { struct net *net; @@ -91,8 +93,13 @@ struct xt_mtchk_param { }; /** - * struct xt_mdtor_param - match destructor parameters - * Fields as above. + * struct xt_mtdtor_param - match destructor parameters + * + * @net: network namespace through which the check was invoked + * @match: struct xt_match through which this function was invoked + * @matchinfo: per-match data + * @family: actual NFPROTO_* through which the function is invoked + * (helpful when match->family == NFPROTO_UNSPEC) */ struct xt_mtdtor_param { struct net *net; @@ -105,10 +112,16 @@ struct xt_mtdtor_param { * struct xt_tgchk_param - parameters for target extensions' * checkentry functions * + * @net: network namespace through which the check was invoked + * @table: table the rule is tried to be inserted into * @entryinfo: the family-specific rule data * (struct ipt_entry, ip6t_entry, arpt_entry, ebt_entry) - * - * Other fields see above. + * @target: the target extension + * @targinfo: per-target data + * @hook_mask: via which hooks the new rule is reachable + * @family: actual NFPROTO_* through which the function is invoked + * (helpful when match->family == NFPROTO_UNSPEC) + * @nft_compat: running from the nft compat layer if true */ struct xt_tgchk_param { struct net *net; @@ -336,9 +349,9 @@ struct xt_table_info *xt_alloc_table_info(unsigned int size); void xt_free_table_info(struct xt_table_info *info); /** - * xt_recseq - recursive seqcount for netfilter use + * var xt_recseq - recursive seqcount for netfilter use * - * Packet processing changes the seqcount only if no recursion happened + * Packet processing changes the seqcount only if no recursion happened. * get_counters() can use read_seqcount_begin()/read_seqcount_retry(), * because we use the normal seqcount convention : * Low order bit set to 1 if a writer is active. -- cgit v1.2.3 From 1105ef941c1a28e115d1b97f17e1c85576884100 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 24 Jun 2026 12:04:39 -0700 Subject: net: ethtool: keep rtnl_lock for ops using ethtool_op_get_link() Breno reports following splats on mlx5: RTNL: assertion failed at net/core/dev.c (2241) WARNING: net/core/dev.c:2241 at netif_state_change+0xed/0x130, CPU#5: ethtool/1335 RIP: 0010:netif_state_change+0xf9/0x130 Call Trace: __linkwatch_sync_dev+0xea/0x120 ethtool_op_get_link+0xe/0x20 __ethtool_get_link+0x26/0x40 linkstate_prepare_data+0x51/0x200 ethnl_default_doit+0x213/0x470 genl_family_rcv_msg_doit+0xdd/0x110 Looks like I missed ethtool_op_get_link() trying to sync linkwatch, which needs rtnl_lock. Not all drivers do this - bnxt doesn't, it just returns the link state, so add an opt-in bit. Reported-by: Breno Leitao Fixes: 45079e00133e ("net: ethtool: optionally skip rtnl_lock on Netlink path for GET ops") Acked-by: Stanislav Fomichev Reviewed-by: Breno Leitao Acked-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260624190439.2521219-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_ethtool.c | 3 ++- drivers/net/ethernet/intel/iavf/iavf_ethtool.c | 1 + drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c | 3 ++- drivers/net/ethernet/mellanox/mlx5/core/en_rep.c | 3 ++- drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c | 4 +++- drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c | 3 ++- drivers/net/ethernet/microsoft/mana/mana_ethtool.c | 3 ++- include/linux/ethtool.h | 2 ++ net/ethtool/common.h | 4 ++++ 9 files changed, 20 insertions(+), 6 deletions(-) (limited to 'include/linux') diff --git a/drivers/net/ethernet/google/gve/gve_ethtool.c b/drivers/net/ethernet/google/gve/gve_ethtool.c index 7cc22916852f..8199738ba979 100644 --- a/drivers/net/ethernet/google/gve/gve_ethtool.c +++ b/drivers/net/ethernet/google/gve/gve_ethtool.c @@ -984,7 +984,8 @@ const struct ethtool_ops gve_ethtool_ops = { .supported_ring_params = ETHTOOL_RING_USE_TCP_DATA_SPLIT | ETHTOOL_RING_USE_RX_BUF_LEN, .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | - ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM, + ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM | + ETHTOOL_OP_NEEDS_RTNL_GLINK, .get_drvinfo = gve_get_drvinfo, .get_strings = gve_get_strings, .get_sset_count = gve_get_sset_count, diff --git a/drivers/net/ethernet/intel/iavf/iavf_ethtool.c b/drivers/net/ethernet/intel/iavf/iavf_ethtool.c index a615d599b88e..e7cf12eaa268 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_ethtool.c +++ b/drivers/net/ethernet/intel/iavf/iavf_ethtool.c @@ -1855,6 +1855,7 @@ static const struct ethtool_ops iavf_ethtool_ops = { .supported_coalesce_params = ETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_USE_ADAPTIVE, .supported_input_xfrm = RXH_XFRM_SYM_XOR, + .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_GLINK, .get_drvinfo = iavf_get_drvinfo, .get_link = ethtool_op_get_link, .get_ringparam = iavf_get_ringparam, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c index 2f5b626ba33f..112926d07634 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c @@ -2721,7 +2721,8 @@ const struct ethtool_ops mlx5e_ethtool_ops = { .rxfh_max_num_contexts = MLX5E_MAX_NUM_RSS, .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM | - ETHTOOL_OP_NEEDS_RTNL_SPFLAGS, + ETHTOOL_OP_NEEDS_RTNL_SPFLAGS | + ETHTOOL_OP_NEEDS_RTNL_GLINK, .supported_coalesce_params = ETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_MAX_FRAMES | ETHTOOL_COALESCE_USE_ADAPTIVE | diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c index 1a8a19f980d3..c8b76d301c92 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c @@ -419,7 +419,8 @@ static const struct ethtool_ops mlx5e_rep_ethtool_ops = { ETHTOOL_COALESCE_MAX_FRAMES | ETHTOOL_COALESCE_USE_ADAPTIVE, .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | - ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM, + ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM | + ETHTOOL_OP_NEEDS_RTNL_GLINK, .get_drvinfo = mlx5e_rep_get_drvinfo, .get_link = ethtool_op_get_link, .get_strings = mlx5e_rep_get_strings, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c index 9b3b32408c64..01ddc3def9ac 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c @@ -286,7 +286,8 @@ const struct ethtool_ops mlx5i_ethtool_ops = { ETHTOOL_COALESCE_MAX_FRAMES | ETHTOOL_COALESCE_USE_ADAPTIVE, .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | - ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM, + ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM | + ETHTOOL_OP_NEEDS_RTNL_GLINK, .get_drvinfo = mlx5i_get_drvinfo, .get_strings = mlx5i_get_strings, .get_sset_count = mlx5i_get_sset_count, @@ -309,6 +310,7 @@ const struct ethtool_ops mlx5i_ethtool_ops = { }; const struct ethtool_ops mlx5i_pkey_ethtool_ops = { + .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_GLINK, .get_drvinfo = mlx5i_get_drvinfo, .get_link = ethtool_op_get_link, .get_ts_info = mlx5i_get_ts_info, diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c index cb34fc166ef9..0e47088ec44b 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c @@ -2024,7 +2024,8 @@ static const struct ethtool_ops fbnic_ethtool_ops = { ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM | ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM | ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | - ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM, + ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM | + ETHTOOL_OP_NEEDS_RTNL_GLINK, .get_drvinfo = fbnic_get_drvinfo, .get_regs_len = fbnic_get_regs_len, .get_regs = fbnic_get_regs, diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c index 94e658d07a27..881df597d7f9 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c @@ -597,7 +597,8 @@ static int mana_get_link_ksettings(struct net_device *ndev, const struct ethtool_ops mana_ethtool_ops = { .supported_coalesce_params = ETHTOOL_COALESCE_RX_CQE_FRAMES, .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | - ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM, + ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM | + ETHTOOL_OP_NEEDS_RTNL_GLINK, .get_ethtool_stats = mana_get_ethtool_stats, .get_sset_count = mana_get_sset_count, .get_strings = mana_get_strings, diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index 1b834e2a522e..5d491a98265e 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -942,6 +942,7 @@ struct kernel_ethtool_ts_info { #define ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM BIT(5) #define ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM BIT(6) #define ETHTOOL_OP_NEEDS_RTNL_RSS BIT(7) +#define ETHTOOL_OP_NEEDS_RTNL_GLINK BIT(8) /** * struct ethtool_ops - optional netdev operations @@ -978,6 +979,7 @@ struct kernel_ethtool_ts_info { * - phylink helpers (note that phydev is currently unsupported!) * - netdev_update_features() * - netif_set_real_num_tx_queues() + * - ethtool_op_get_link() (syncs link watch under rtnl_lock) * * @get_drvinfo: Report driver/device information. Modern drivers no * longer have to implement this callback. Most fields are diff --git a/net/ethtool/common.h b/net/ethtool/common.h index 2b3847f00801..4e5356e26f40 100644 --- a/net/ethtool/common.h +++ b/net/ethtool/common.h @@ -113,6 +113,8 @@ ethtool_nl_msg_needs_rtnl(const struct net_device *dev, u8 cmd) return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM; case ETHTOOL_MSG_RSS_SET: return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_RSS; + case ETHTOOL_MSG_LINKSTATE_GET: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_GLINK; case ETHTOOL_MSG_TSCONFIG_GET: case ETHTOOL_MSG_TSCONFIG_SET: /* tsconfig calls ndos (ndo_hwtstamp_set/get), not ethtool ops. @@ -159,6 +161,8 @@ ethtool_ioctl_needs_rtnl(const struct net_device *dev, u32 ethcmd) case ETHTOOL_SRXFH: case ETHTOOL_SRXFHINDIR: return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_RSS; + case ETHTOOL_GLINK: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_GLINK; } return false; } -- cgit v1.2.3 From 12c765be84d28f22deca10e775889f54bd571a85 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 24 Jun 2026 11:20:15 -0700 Subject: net: turn the rx_mode work into a generic netdev_work facility The rx_mode update runs from a workqueue: drivers have their ndo_set_rx_mode_async() callback executed by a single global work item under RTNL and ops lock. This is a useful pattern. Support multiple "events" that need to be serviced and make RX_MODE sync the first one. Call the events "core" because later on we will let drivers define and schedule their own. Reviewed-by: Kuniyuki Iwashima Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20260624182018.2445732-2-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/linux/netdevice.h | 10 ++-- net/core/Makefile | 2 +- net/core/dev.c | 1 + net/core/dev.h | 11 ++++- net/core/dev_addr_lists.c | 77 ++---------------------------- net/core/netdev_work.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 138 insertions(+), 80 deletions(-) create mode 100644 net/core/netdev_work.c (limited to 'include/linux') diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index b67a12541eac..732506787db3 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -1930,8 +1930,9 @@ enum netdev_reg_state { * has been enabled due to the need to listen to * additional unicast addresses in a device that * does not implement ndo_set_rx_mode() - * @rx_mode_node: List entry for rx_mode work processing - * @rx_mode_tracker: Refcount tracker for rx_mode work + * @work_node: List entry for async netdev_work processing + * @work_tracker: Refcount tracker for async netdev_work + * @work_core_pending: Core-defined pending netdev_work (NETDEV_WORK_*) * @rx_mode_addr_cache: Recycled snapshot entries for rx_mode work * @rx_mode_retry_timer: Timer that re-queues rx_mode work after failure * @rx_mode_retry_count: Number of consecutive retries already scheduled @@ -2326,8 +2327,9 @@ struct net_device { unsigned int promiscuity; unsigned int allmulti; bool uc_promisc; - struct list_head rx_mode_node; - netdevice_tracker rx_mode_tracker; + struct list_head work_node; + netdevice_tracker work_tracker; + unsigned long work_core_pending; struct netdev_hw_addr_list rx_mode_addr_cache; struct timer_list rx_mode_retry_timer; unsigned int rx_mode_retry_count; diff --git a/net/core/Makefile b/net/core/Makefile index dc17c5a61e9a..b3fdcb4e355f 100644 --- a/net/core/Makefile +++ b/net/core/Makefile @@ -13,7 +13,7 @@ obj-y += dev.o dev_api.o dev_addr_lists.o dst.o netevent.o \ neighbour.o rtnetlink.o utils.o link_watch.o filter.o \ sock_diag.o dev_ioctl.o tso.o sock_reuseport.o \ fib_notifier.o xdp.o flow_offload.o gro.o \ - netdev-genl.o netdev-genl-gen.o gso.o + netdev-genl.o netdev-genl-gen.o netdev_work.o gso.o obj-$(CONFIG_NETDEV_ADDR_LIST_TEST) += dev_addr_lists_test.o diff --git a/net/core/dev.c b/net/core/dev.c index 5c01dfaa6c44..e1d8af0ef6ab 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -12093,6 +12093,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name, INIT_LIST_HEAD(&dev->ptype_all); INIT_LIST_HEAD(&dev->ptype_specific); INIT_LIST_HEAD(&dev->net_notifier_list); + INIT_LIST_HEAD(&dev->work_node); #ifdef CONFIG_NET_SCHED hash_init(dev->qdisc_hash); #endif diff --git a/net/core/dev.h b/net/core/dev.h index 4121c50e7c88..5d0b0305d3ba 100644 --- a/net/core/dev.h +++ b/net/core/dev.h @@ -167,10 +167,19 @@ int dev_change_carrier(struct net_device *dev, bool new_carrier); void __dev_set_rx_mode(struct net_device *dev); int __dev_set_promiscuity(struct net_device *dev, int inc, bool notify); void netif_rx_mode_init(struct net_device *dev); -bool netif_rx_mode_clean(struct net_device *dev); +void netif_rx_mode_run(struct net_device *dev); void netif_rx_mode_sync(struct net_device *dev); void netif_rx_mode_cancel_retry(struct net_device *dev); +/* Events for the async netdev work, tracked in netdev->work_core_pending. */ +enum netdev_work_core { + NETDEV_WORK_RX_MODE = BIT(0), /* run the rx_mode update */ +}; + +void __netdev_work_core_sched(struct net_device *dev, unsigned long event); +unsigned long +__netdev_work_core_cancel(struct net_device *dev, unsigned long mask); + void __dev_notify_flags(struct net_device *dev, unsigned int old_flags, unsigned int gchanges, u32 portid, const struct nlmsghdr *nlh); diff --git a/net/core/dev_addr_lists.c b/net/core/dev_addr_lists.c index e17f64a65e17..08528ca0a8b3 100644 --- a/net/core/dev_addr_lists.c +++ b/net/core/dev_addr_lists.c @@ -12,17 +12,10 @@ #include #include #include -#include #include #include "dev.h" -static void netdev_rx_mode_work(struct work_struct *work); - -static LIST_HEAD(rx_mode_list); -static DEFINE_SPINLOCK(rx_mode_lock); -static DECLARE_WORK(rx_mode_work, netdev_rx_mode_work); - /* * General list handling functions */ @@ -1281,7 +1274,7 @@ void netif_rx_mode_cancel_retry(struct net_device *dev) dev->rx_mode_retry_count = 0; } -static void netif_rx_mode_run(struct net_device *dev) +void netif_rx_mode_run(struct net_device *dev) { struct netdev_hw_addr_list uc_snap, mc_snap, uc_ref, mc_ref; const struct net_device_ops *ops = dev->netdev_ops; @@ -1339,49 +1332,9 @@ static void netif_rx_mode_run(struct net_device *dev) } } -static void netdev_rx_mode_work(struct work_struct *work) -{ - struct net_device *dev; - - rtnl_lock(); - - while (true) { - spin_lock_bh(&rx_mode_lock); - if (list_empty(&rx_mode_list)) { - spin_unlock_bh(&rx_mode_lock); - break; - } - dev = list_first_entry(&rx_mode_list, struct net_device, - rx_mode_node); - list_del_init(&dev->rx_mode_node); - /* We must free netdev tracker under - * the spinlock protection. - */ - netdev_tracker_free(dev, &dev->rx_mode_tracker); - spin_unlock_bh(&rx_mode_lock); - - netdev_lock_ops(dev); - netif_rx_mode_run(dev); - netdev_unlock_ops(dev); - /* Use __dev_put() because netdev_tracker_free() was already - * called above. Must be after netdev_unlock_ops() to prevent - * netdev_run_todo() from freeing the device while still in use. - */ - __dev_put(dev); - } - - rtnl_unlock(); -} - static void netif_rx_mode_queue(struct net_device *dev) { - spin_lock_bh(&rx_mode_lock); - if (list_empty(&dev->rx_mode_node)) { - list_add_tail(&dev->rx_mode_node, &rx_mode_list); - netdev_hold(dev, &dev->rx_mode_tracker, GFP_ATOMIC); - } - spin_unlock_bh(&rx_mode_lock); - schedule_work(&rx_mode_work); + __netdev_work_core_sched(dev, NETDEV_WORK_RX_MODE); } static void netif_rx_mode_retry(struct timer_list *t) @@ -1394,7 +1347,6 @@ static void netif_rx_mode_retry(struct timer_list *t) void netif_rx_mode_init(struct net_device *dev) { - INIT_LIST_HEAD(&dev->rx_mode_node); __hw_addr_init(&dev->rx_mode_addr_cache); timer_setup(&dev->rx_mode_retry_timer, netif_rx_mode_retry, 0); } @@ -1442,24 +1394,6 @@ void dev_set_rx_mode(struct net_device *dev) netif_addr_unlock_bh(dev); } -bool netif_rx_mode_clean(struct net_device *dev) -{ - bool clean = false; - - spin_lock_bh(&rx_mode_lock); - if (!list_empty(&dev->rx_mode_node)) { - list_del_init(&dev->rx_mode_node); - clean = true; - /* We must release netdev tracker under - * the spinlock protection. - */ - netdev_tracker_free(dev, &dev->rx_mode_tracker); - } - spin_unlock_bh(&rx_mode_lock); - - return clean; -} - /** * netif_rx_mode_sync() - sync rx mode inline * @dev: network device @@ -1473,11 +1407,6 @@ bool netif_rx_mode_clean(struct net_device *dev) */ void netif_rx_mode_sync(struct net_device *dev) { - if (netif_rx_mode_clean(dev)) { + if (__netdev_work_core_cancel(dev, NETDEV_WORK_RX_MODE)) netif_rx_mode_run(dev); - /* Use __dev_put() because netdev_tracker_free() was already - * called inside netif_rx_mode_clean(). - */ - __dev_put(dev); - } } diff --git a/net/core/netdev_work.c b/net/core/netdev_work.c new file mode 100644 index 000000000000..c121c24dc493 --- /dev/null +++ b/net/core/netdev_work.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include + +#include "dev.h" + +static void netdev_work_proc(struct work_struct *work); + +/* @netdev_work_lock protects: + * - @netdev_work_list + * - within the list entries (struct net_device fields): + * - work_node + * - work_tracker + * - work_core_pending + */ +static LIST_HEAD(netdev_work_list); +static DEFINE_SPINLOCK(netdev_work_lock); +static DECLARE_WORK(netdev_work, netdev_work_proc); + +void __netdev_work_core_sched(struct net_device *dev, unsigned long event) +{ + spin_lock_bh(&netdev_work_lock); + if (list_empty(&dev->work_node)) { + list_add_tail(&dev->work_node, &netdev_work_list); + netdev_hold(dev, &dev->work_tracker, GFP_ATOMIC); + } + dev->work_core_pending |= event; + spin_unlock_bh(&netdev_work_lock); + + schedule_work(&netdev_work); +} + +/** + * __netdev_work_core_cancel() - cancel selected core work for a netdev + * @dev: net_device + * @mask: events to cancel + * + * Clear @mask from the device's work pending mask. If no work is left pending + * the device is dequeued. + * + * No expectations on locking, but also no guarantees provided. If the caller + * wants to touch @dev afterwards (e.g. call the work that got canceled) + * they have to ensure @dev does not get freed. + * + * Returns: the subset of @mask that was actually pending, so the caller can run + * those events inline. + */ +unsigned long +__netdev_work_core_cancel(struct net_device *dev, unsigned long mask) +{ + unsigned long event; + + spin_lock_bh(&netdev_work_lock); + event = dev->work_core_pending & mask; + dev->work_core_pending &= ~mask; + if (!list_empty(&dev->work_node) && !dev->work_core_pending) { + list_del_init(&dev->work_node); + netdev_put(dev, &dev->work_tracker); + } + spin_unlock_bh(&netdev_work_lock); + + return event; +} + +static void netdev_work_proc(struct work_struct *work) +{ + rtnl_lock(); + + while (true) { + netdevice_tracker tracker; + struct net_device *dev; + unsigned long core = 0; + + spin_lock_bh(&netdev_work_lock); + if (list_empty(&netdev_work_list)) { + spin_unlock_bh(&netdev_work_lock); + break; + } + dev = list_first_entry(&netdev_work_list, struct net_device, + work_node); + /* Take a temporary reference so @dev can't be freed while we + * drop the lock to grab its ops lock; the work reference is + * only released once we claim the work below. + * The re-locking dance is to ensure that ops lock is enough + * to ensure canceling work is not racy with dequeue. + */ + netdev_hold(dev, &tracker, GFP_ATOMIC); + spin_unlock_bh(&netdev_work_lock); + + netdev_lock_ops(dev); + spin_lock_bh(&netdev_work_lock); + if (!list_empty(&dev->work_node)) { + list_del_init(&dev->work_node); + core = dev->work_core_pending; + dev->work_core_pending = 0; + /* We took another ref above */ + netdev_put(dev, &dev->work_tracker); + + if (!dev_isalive(dev)) + core = 0; + } + spin_unlock_bh(&netdev_work_lock); + + if (core & NETDEV_WORK_RX_MODE) + netif_rx_mode_run(dev); + netdev_unlock_ops(dev); + + netdev_put(dev, &tracker); + } + + rtnl_unlock(); +} -- cgit v1.2.3 From 129cdce9da9e44c52d38889e0411be9817bca114 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 24 Jun 2026 11:20:16 -0700 Subject: net: add the driver-facing netdev_work scheduling API With an extra event mask we can easily extend the netdev work to also service driver-defined events. For advanced drivers this is probably not a perfect match, but it makes running deferred work easier in simple cases. Expose the netdev_work facility to drivers. Add helpers to schedule work and a dedicated ndo to perform the driver- -scheduled actions. Reviewed-by: Kuniyuki Iwashima Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20260624182018.2445732-3-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/linux/netdevice.h | 11 +++++++ net/core/netdev_work.c | 81 ++++++++++++++++++++++++++++++++++++----------- 2 files changed, 74 insertions(+), 18 deletions(-) (limited to 'include/linux') diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 732506787db3..9981d637f8b5 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -1131,6 +1131,9 @@ struct netdev_net_notifier { * netdev_hw_addr_list_for_each(ha, uc). Return 0 on success or a * negative errno to request a retry via the core backoff. * + * void (*ndo_work)(struct net_device *dev, unsigned long events); + * Run deferred work scheduled with netdev_work_sched(@events). + * * int (*ndo_set_mac_address)(struct net_device *dev, void *addr); * This function is called when the Media Access Control address * needs to be changed. If this interface is not defined, the @@ -1460,6 +1463,8 @@ struct net_device_ops { struct net_device *dev, struct netdev_hw_addr_list *uc, struct netdev_hw_addr_list *mc); + void (*ndo_work)(struct net_device *dev, + unsigned long events); int (*ndo_set_mac_address)(struct net_device *dev, void *addr); int (*ndo_validate_addr)(struct net_device *dev); @@ -1932,6 +1937,8 @@ enum netdev_reg_state { * does not implement ndo_set_rx_mode() * @work_node: List entry for async netdev_work processing * @work_tracker: Refcount tracker for async netdev_work + * @work_pending: Driver-defined pending netdev_work, passed to + * ndo_work() (see netdev_work_sched()) * @work_core_pending: Core-defined pending netdev_work (NETDEV_WORK_*) * @rx_mode_addr_cache: Recycled snapshot entries for rx_mode work * @rx_mode_retry_timer: Timer that re-queues rx_mode work after failure @@ -2329,6 +2336,7 @@ struct net_device { bool uc_promisc; struct list_head work_node; netdevice_tracker work_tracker; + unsigned long work_pending; unsigned long work_core_pending; struct netdev_hw_addr_list rx_mode_addr_cache; struct timer_list rx_mode_retry_timer; @@ -5178,6 +5186,9 @@ void dev_fetch_sw_netstats(struct rtnl_link_stats64 *s, const struct pcpu_sw_netstats __percpu *netstats); void dev_get_tstats64(struct net_device *dev, struct rtnl_link_stats64 *s); +void netdev_work_sched(struct net_device *dev, unsigned long events); +unsigned long netdev_work_cancel(struct net_device *dev, unsigned long mask); + enum { NESTED_SYNC_IMM_BIT, NESTED_SYNC_TODO_BIT, diff --git a/net/core/netdev_work.c b/net/core/netdev_work.c index c121c24dc493..3109fae132ad 100644 --- a/net/core/netdev_work.c +++ b/net/core/netdev_work.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include #include @@ -16,32 +17,63 @@ static void netdev_work_proc(struct work_struct *work); * - within the list entries (struct net_device fields): * - work_node * - work_tracker + * - work_pending * - work_core_pending */ static LIST_HEAD(netdev_work_list); static DEFINE_SPINLOCK(netdev_work_lock); static DECLARE_WORK(netdev_work, netdev_work_proc); -void __netdev_work_core_sched(struct net_device *dev, unsigned long event) +static void netdev_work_enqueue(struct net_device *dev, unsigned long events, + unsigned long core) { + if (!events && !core) + return; + spin_lock_bh(&netdev_work_lock); if (list_empty(&dev->work_node)) { list_add_tail(&dev->work_node, &netdev_work_list); netdev_hold(dev, &dev->work_tracker, GFP_ATOMIC); } - dev->work_core_pending |= event; + dev->work_pending |= events; + dev->work_core_pending |= core; spin_unlock_bh(&netdev_work_lock); schedule_work(&netdev_work); } +static unsigned long +netdev_work_dequeue(struct net_device *dev, unsigned long *pending, + unsigned long mask) +{ + unsigned long events; + + spin_lock_bh(&netdev_work_lock); + events = *pending & mask; + *pending &= ~events; + if (!list_empty(&dev->work_node) && + !dev->work_pending && !dev->work_core_pending) { + list_del_init(&dev->work_node); + netdev_put(dev, &dev->work_tracker); + } + spin_unlock_bh(&netdev_work_lock); + + return events; +} + +void netdev_work_sched(struct net_device *dev, unsigned long events) +{ + netdev_work_enqueue(dev, events, 0); +} +EXPORT_SYMBOL(netdev_work_sched); + /** - * __netdev_work_core_cancel() - cancel selected core work for a netdev + * netdev_work_cancel() - cancel selected work for a netdev * @dev: net_device * @mask: events to cancel * * Clear @mask from the device's work pending mask. If no work is left pending - * the device is dequeued. + * the device is dequeued and its ndo_work won't be called. * * No expectations on locking, but also no guarantees provided. If the caller * wants to touch @dev afterwards (e.g. call the work that got canceled) @@ -50,21 +82,33 @@ void __netdev_work_core_sched(struct net_device *dev, unsigned long event) * Returns: the subset of @mask that was actually pending, so the caller can run * those events inline. */ +unsigned long netdev_work_cancel(struct net_device *dev, unsigned long mask) +{ + return netdev_work_dequeue(dev, &dev->work_pending, mask); +} +EXPORT_SYMBOL(netdev_work_cancel); + +void __netdev_work_core_sched(struct net_device *dev, unsigned long events) +{ + netdev_work_enqueue(dev, 0, events); +} + unsigned long __netdev_work_core_cancel(struct net_device *dev, unsigned long mask) { - unsigned long event; + return netdev_work_dequeue(dev, &dev->work_core_pending, mask); +} - spin_lock_bh(&netdev_work_lock); - event = dev->work_core_pending & mask; - dev->work_core_pending &= ~mask; - if (!list_empty(&dev->work_node) && !dev->work_core_pending) { - list_del_init(&dev->work_node); - netdev_put(dev, &dev->work_tracker); - } - spin_unlock_bh(&netdev_work_lock); +static void netdev_work_run(struct net_device *dev, unsigned long events, + unsigned long core) +{ + if (!netif_device_present(dev)) + return; - return event; + if (core & NETDEV_WORK_RX_MODE) + netif_rx_mode_run(dev); + if (events && dev->netdev_ops->ndo_work) + dev->netdev_ops->ndo_work(dev, events); } static void netdev_work_proc(struct work_struct *work) @@ -72,9 +116,9 @@ static void netdev_work_proc(struct work_struct *work) rtnl_lock(); while (true) { + unsigned long events = 0, core = 0; netdevice_tracker tracker; struct net_device *dev; - unsigned long core = 0; spin_lock_bh(&netdev_work_lock); if (list_empty(&netdev_work_list)) { @@ -98,16 +142,17 @@ static void netdev_work_proc(struct work_struct *work) list_del_init(&dev->work_node); core = dev->work_core_pending; dev->work_core_pending = 0; + events = dev->work_pending; + dev->work_pending = 0; /* We took another ref above */ netdev_put(dev, &dev->work_tracker); if (!dev_isalive(dev)) - core = 0; + core = events = 0; } spin_unlock_bh(&netdev_work_lock); - if (core & NETDEV_WORK_RX_MODE) - netif_rx_mode_run(dev); + netdev_work_run(dev, events, core); netdev_unlock_ops(dev); netdev_put(dev, &tracker); -- cgit v1.2.3