When a netdev is unregistered, pending netdev_work events are not explicitly canceled. Only events cleared by ndo_stop() (e.g. VLAN_WORK_LINK_STATE) are handled; core events like NETDEV_WORK_RX_MODE and driver events like VLAN_WORK_MTU / VLAN_WORK_FEATURES remain pending, keeping a tracked reference (dev->work_tracker) on the device. During netdev_wait_allrefs_any(), RTNL is released to let the system workqueue run. netdev_work_proc() may then attempt to process the stale work_tracker, triggering ref_tracker warnings at boot: WARNING: lib/ref_tracker.c:322 at ref_tracker_free WARNING: lib/ref_tracker.c:246 at ref_tracker_dir_exit Add netdev_work_cancel_all() to cancel all pending core and driver work, releasing the work_tracker before the device enters netdev_run_todo(). Call it from unregister_netdevice_many_notify() after rx_mode cleanup. This is safe because netdev_work_cancel_all() runs under RTNL in unregister_netdevice_many_notify(), and netdev_work_proc() also requires RTNL, so the two cannot execute concurrently. If netdev_work_proc() has already dequeued the device, the cancel is a no-op; otherwise it releases the tracker before the device enters netdev_run_todo(). Fixes: 12c765be84d2 ("net: turn the rx_mode work into a generic netdev_work facility") Cc: stable@vger.kernel.org Signed-off-by: Jiangshan Yi --- net/core/dev.c | 2 +- net/core/dev.h | 2 ++ net/core/netdev_work.c | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/net/core/dev.c b/net/core/dev.c index 5933c5d..b461b01 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -12456,7 +12456,7 @@ void unregister_netdevice_many_notify(struct list_head *head, dev_uc_flush(dev); dev_mc_flush(dev); - + netdev_work_cancel_all(dev); netdev_rss_contexts_free(dev); call_netdevice_notifiers(NETDEV_PRE_UNINIT, dev); diff --git a/net/core/dev.h b/net/core/dev.h index 5d0b030..d3ac3ba 100644 --- a/net/core/dev.h +++ b/net/core/dev.h @@ -180,6 +180,8 @@ 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 netdev_work_cancel_all(struct net_device *dev); + 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/netdev_work.c b/net/core/netdev_work.c index 3109fae..d69363d 100644 --- a/net/core/netdev_work.c +++ b/net/core/netdev_work.c @@ -99,6 +99,16 @@ __netdev_work_core_cancel(struct net_device *dev, unsigned long mask) return netdev_work_dequeue(dev, &dev->work_core_pending, mask); } +/* Cancel all pending core and driver work and release the work_tracker + * reference. Must be called under RTNL, which mutually excludes + * netdev_work_proc(). + */ +void netdev_work_cancel_all(struct net_device *dev) +{ + __netdev_work_core_cancel(dev, ~0UL); + netdev_work_cancel(dev, ~0UL); +} + static void netdev_work_run(struct net_device *dev, unsigned long events, unsigned long core) { -- 2.25.1