Merge branch 'net-hold-instance-lock-around-netdev_down-and-netdev_going_down'

Stanislav Fomichev says:

====================
net: hold instance lock around NETDEV_DOWN and NETDEV_GOING_DOWN

NETDEV_UP and NETDEV_REGISTER already run under the per-device
instance lock. The teardown side does not. Make it symmetric so
ops-locked drivers can rely on the lock being held in both
directions.
====================

Link: https://patch.msgid.link/20260702224150.3730033-1-sdf@fomichev.me
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Paolo Abeni 2026-07-07 15:31:38 +02:00
commit 4a13f31a92
7 changed files with 63 additions and 14 deletions

View File

@ -421,10 +421,17 @@ running under the lock:
* ``NETDEV_CHANGENAME``
* ``NETDEV_REGISTER``
* ``NETDEV_UP``
* ``NETDEV_DOWN``
* ``NETDEV_GOING_DOWN``
The following notifiers are running without the lock:
* ``NETDEV_UNREGISTER``
Many SW devices (uppers) catch their lower's ``NETDEV_UNREGISTER``
events and may interact with them via ``dev_*()`` handlers, which take
the instance lock. Until we convert these devices to ``netif_*()`` variants,
``NETDEV_UNREGISTER`` stays unlocked.
There are no clear expectations for the remaining notifiers. Notifiers not on
the list may run with or without the instance lock, potentially even invoking
the same notifier type with and without the lock from different code paths.

View File

@ -26,6 +26,7 @@
#include <linux/bitfield.h>
#include <net/dsa.h>
#include <net/dst_metadata.h>
#include <net/netdev_lock.h>
#include <net/page_pool/helpers.h>
#include <linux/genalloc.h>
@ -5030,10 +5031,14 @@ void mtk_eth_set_dma_device(struct mtk_eth *eth, struct device *dma_dev)
continue;
list_add_tail(&dev->close_list, &dev_list);
netdev_lock_ops(dev);
}
netif_close_many(&dev_list, false);
list_for_each_entry(dev, &dev_list, close_list)
netdev_unlock_ops(dev);
eth->dma_dev = dma_dev;
list_for_each_entry_safe(dev, tmp, &dev_list, close_list) {

View File

@ -1802,6 +1802,7 @@ void netif_close_many(struct list_head *head, bool unlink)
__dev_close_many(head);
list_for_each_entry_safe(dev, tmp, head, close_list) {
netdev_assert_locked_ops_compat(dev);
rtmsg_ifinfo(RTM_NEWLINK, dev, IFF_UP | IFF_RUNNING, GFP_KERNEL, 0, NULL);
call_netdevice_notifiers(NETDEV_DOWN, dev);
if (unlink)
@ -1912,9 +1913,11 @@ static void call_netdevice_unregister_notifiers(struct notifier_block *nb,
struct net_device *dev)
{
if (dev->flags & IFF_UP) {
netdev_lock_ops(dev);
call_netdevice_notifier(nb, NETDEV_GOING_DOWN,
dev);
call_netdevice_notifier(nb, NETDEV_DOWN, dev);
netdev_unlock_ops(dev);
}
call_netdevice_notifier(nb, NETDEV_UNREGISTER, dev);
}
@ -9785,6 +9788,8 @@ void __dev_notify_flags(struct net_device *dev, unsigned int old_flags,
{
unsigned int changes = dev->flags ^ old_flags;
netdev_assert_locked_ops_compat(dev);
if (gchanges)
rtmsg_ifinfo(RTM_NEWLINK, dev, gchanges, GFP_ATOMIC, portid, nlh);

View File

@ -24,15 +24,15 @@ int netdev_debug_event(struct notifier_block *nb, unsigned long event,
case NETDEV_CHANGE:
case NETDEV_REGISTER:
case NETDEV_UP:
case NETDEV_DOWN:
case NETDEV_GOING_DOWN:
netdev_assert_locked_ops_compat(dev);
fallthrough;
case NETDEV_DOWN:
case NETDEV_REBOOT:
case NETDEV_UNREGISTER:
case NETDEV_CHANGEMTU:
case NETDEV_CHANGEADDR:
case NETDEV_PRE_CHANGEADDR:
case NETDEV_GOING_DOWN:
case NETDEV_FEAT_CHANGE:
case NETDEV_BONDING_FAILOVER:
case NETDEV_PRE_UP:

View File

@ -3660,14 +3660,16 @@ int rtnl_configure_link(struct net_device *dev, const struct ifinfomsg *ifm,
u32 portid, const struct nlmsghdr *nlh)
{
unsigned int old_flags, changed;
int err;
int err = 0;
netdev_lock_ops(dev);
old_flags = dev->flags;
if (ifm && (ifm->ifi_flags || ifm->ifi_change)) {
err = __dev_change_flags(dev, rtnl_dev_combine_flags(dev, ifm),
NULL);
if (err < 0)
return err;
goto out;
}
changed = old_flags ^ dev->flags;
@ -3677,7 +3679,10 @@ int rtnl_configure_link(struct net_device *dev, const struct ifinfomsg *ifm,
}
__dev_notify_flags(dev, old_flags, changed, portid, nlh);
return 0;
out:
netdev_unlock_ops(dev);
return err;
}
EXPORT_SYMBOL(rtnl_configure_link);
@ -3918,22 +3923,20 @@ static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm,
goto out;
}
netdev_lock_ops(dev);
err = rtnl_configure_link(dev, ifm, portid, nlh);
if (err < 0)
goto out_unregister;
if (tb[IFLA_MASTER]) {
netdev_lock_ops(dev);
err = do_set_master(dev, nla_get_u32(tb[IFLA_MASTER]), extack);
netdev_unlock_ops(dev);
if (err)
goto out_unregister;
}
netdev_unlock_ops(dev);
out:
return err;
out_unregister:
netdev_unlock_ops(dev);
if (ops->newlink) {
LIST_HEAD(list_kill);

View File

@ -18,6 +18,7 @@
#include <linux/of.h>
#include <linux/of_net.h>
#include <net/dsa_stubs.h>
#include <net/netdev_lock.h>
#include <net/sch_generic.h>
#include "conduit.h"
@ -1620,10 +1621,23 @@ void dsa_switch_shutdown(struct dsa_switch *ds)
rtnl_lock();
dsa_switch_for_each_cpu_port(dp, ds)
list_add(&dp->conduit->close_list, &close_list);
dsa_switch_for_each_cpu_port(dp, ds) {
if (!(dp->conduit->flags & IFF_UP))
continue;
list_add_tail(&dp->conduit->close_list, &close_list);
netdev_lock_ops(dp->conduit);
}
netif_close_many(&close_list, true);
netif_close_many(&close_list, false);
while (!list_empty(&close_list)) {
struct net_device *conduit;
conduit = list_first_entry(&close_list, struct net_device,
close_list);
netdev_unlock_ops(conduit);
list_del_init(&conduit->close_list);
}
dsa_switch_for_each_user_port(dp, ds) {
conduit = dsa_port_to_conduit(dp);

View File

@ -13,6 +13,7 @@
#include <linux/of_net.h>
#include <linux/of_mdio.h>
#include <linux/mdio.h>
#include <net/netdev_lock.h>
#include <net/rtnetlink.h>
#include <net/pkt_cls.h>
#include <net/selftests.h>
@ -3599,10 +3600,24 @@ static int dsa_user_netdevice_event(struct notifier_block *nb,
if (dp->cpu_dp != cpu_dp)
continue;
list_add(&dp->user->close_list, &close_list);
if (!(dp->user->flags & IFF_UP))
continue;
list_add_tail(&dp->user->close_list, &close_list);
netdev_lock_ops(dp->user);
}
netif_close_many(&close_list, true);
netif_close_many(&close_list, false);
while (!list_empty(&close_list)) {
struct net_device *user_dev;
user_dev = list_first_entry(&close_list,
struct net_device,
close_list);
netdev_unlock_ops(user_dev);
list_del_init(&user_dev->close_list);
}
return NOTIFY_OK;
}