@@ -3122,14 +3122,22 @@ extern struct nd_lock fallback_nd_lock;
void nd_lock_transfer_devices(struct nd_lock **p_lock, struct nd_lock **p_lock2);
+int __register_netdevice(struct net_device *dev);
int register_netdevice(struct net_device *dev);
void unregister_netdevice_queue(struct net_device *dev, struct list_head *head);
void unregister_netdevice_many(struct list_head *head);
+/* XXX: This will be converted to take nd_lock after drivers are ready */
static inline void unregister_netdevice(struct net_device *dev)
{
unregister_netdevice_queue(dev, NULL);
}
+/* XXX: This will be used in places, where nd_lock is already taken */
+static inline void __unregister_netdevice(struct net_device *dev)
+{
+ unregister_netdevice_queue(dev, NULL);
+}
+
int netdev_refcnt_read(const struct net_device *dev);
void free_netdev(struct net_device *dev);
void init_dummy_netdev(struct net_device *dev);
@@ -10651,7 +10651,7 @@ struct nd_lock *attach_new_nd_lock(struct net_device *dev)
EXPORT_SYMBOL(attach_new_nd_lock);
/**
- * register_netdevice() - register a network device
+ * __register_netdevice() - register a network device
* @dev: device to register
*
* Take a prepared network device structure and make it externally accessible.
@@ -10659,7 +10659,7 @@ EXPORT_SYMBOL(attach_new_nd_lock);
* Callers must hold the rtnl lock - you may want register_netdev()
* instead of this.
*/
-int register_netdevice(struct net_device *dev)
+int __register_netdevice(struct net_device *dev)
{
int ret;
struct net *net = dev_net(dev);
@@ -10675,6 +10675,9 @@ int register_netdevice(struct net_device *dev)
BUG_ON(dev->reg_state != NETREG_UNINITIALIZED);
BUG_ON(!net);
+ if (WARN_ON(!rcu_access_pointer(dev->nd_lock)))
+ return -ENOLCK;
+
ret = ethtool_check_ops(dev->ethtool_ops);
if (ret)
return ret;
@@ -10837,6 +10840,40 @@ int register_netdevice(struct net_device *dev)
netdev_name_node_free(dev->name_node);
goto out;
}
+EXPORT_SYMBOL(__register_netdevice);
+
+int register_netdevice(struct net_device *dev)
+{
+ struct nd_lock *nd_lock;
+ int err;
+
+ /* XXX: This "if" is to start one by one convertation
+ * to use __register_netdevice() in devices, that
+ * want to attach nd_lock themself (e.g., having newlink).
+ * After all of them are converted, we remove this.
+ */
+ if (rcu_access_pointer(dev->nd_lock))
+ return __register_netdevice(dev);
+
+ nd_lock = alloc_nd_lock();
+ if (!nd_lock)
+ return -ENOMEM;
+
+ /* This may be called from netdevice notifier, which is not converted
+ * yet. The context is unknown: either some nd_lock is locked or not.
+ * Sometimes here is nested mutex and sometimes is not. We use trylock
+ * to silence lockdep assert about that.
+ * It will be replaced by mutex_lock(), see next patches.
+ */
+ BUG_ON(!mutex_trylock(&nd_lock->mutex));
+ attach_nd_lock(dev, nd_lock);
+ err = __register_netdevice(dev);
+ if (err)
+ detach_nd_lock(dev);
+ mutex_unlock(&nd_lock->mutex);
+ put_nd_lock(nd_lock);
+ return err;
+}
EXPORT_SYMBOL(register_netdevice);
/* Initialize the core of a dummy net device.
@@ -10907,7 +10944,23 @@ int register_netdev(struct net_device *dev)
if (rtnl_lock_killable())
return -EINTR;
- err = register_netdevice(dev);
+
+ /* Since this function is called without rtnl_lock(),
+ * nested registration is not possible here (compare
+ * to .newlink). So it's not interesting for us as
+ * much as register_netdevice(). Here are possible some
+ * real cross-device links between devices related
+ * to specific driver family, and they are handled by
+ * using fallback_nd_lock for all devices.
+ * Also, see comment in nd_lock_transfer_devices().
+ */
+ mutex_lock(&fallback_nd_lock.mutex);
+ attach_nd_lock(dev, &fallback_nd_lock);
+ err = __register_netdevice(dev);
+ if (err)
+ detach_nd_lock(dev);
+ mutex_unlock(&fallback_nd_lock.mutex);
+
rtnl_unlock();
return err;
}
@@ -11474,6 +11527,54 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
}
EXPORT_SYMBOL(alloc_netdev_mqs);
+static DEFINE_SPINLOCK(put_lock);
+static LIST_HEAD(put_list);
+
+static void put_work_func(struct work_struct *unused)
+{
+ struct nd_lock *nd_lock;
+ struct net_device *dev;
+ LIST_HEAD(list);
+
+ spin_lock(&put_lock);
+ list_replace_init(&put_list, &list);
+ spin_unlock(&put_lock);
+
+ while (!list_empty(&list)) {
+ dev = list_first_entry(&list,
+ struct net_device,
+ todo_list);
+ list_del_init(&dev->todo_list);
+
+ /* XXX: this nd_lock finaly should be held during
+ * the whole unregistering. Since not all of devices
+ * are converted yet, we place the detach_nd_lock here
+ * to be able to start attaching nd_lock to every device
+ * one by one in separate patches of this series.
+ * Then, it will be moved to callers (unregister_netdevice()
+ * and others).
+ *
+ * Note, we can't place the below to free_netdev(), because
+ * of free_netdev() currently may be called locked and unlocked
+ * from different callers.
+ *
+ * Also note, that lock may be detached here in case of
+ * this is cleanup after failed __register_netdevice().
+ */
+ if (lock_netdev(dev, &nd_lock)) {
+ detach_nd_lock(dev);
+ unlock_netdev(nd_lock);
+ }
+
+ if (dev->reg_state == NETREG_RELEASED)
+ put_device(&dev->dev); /* free via device release */
+ else /* Compatibility with error handling in drivers */
+ kvfree(dev);
+ }
+}
+
+static DECLARE_WORK(put_work, put_work_func);
+
/**
* free_netdev - free network device
* @dev: device
@@ -11486,6 +11587,7 @@ EXPORT_SYMBOL(alloc_netdev_mqs);
void free_netdev(struct net_device *dev)
{
struct napi_struct *p, *n;
+ bool work;
might_sleep();
@@ -11521,18 +11623,19 @@ void free_netdev(struct net_device *dev)
free_percpu(dev->xdp_bulkq);
dev->xdp_bulkq = NULL;
- /* Compatibility with error handling in drivers */
- if (dev->reg_state == NETREG_UNINITIALIZED ||
- dev->reg_state == NETREG_DUMMY) {
- kvfree(dev);
- return;
+ if (dev->reg_state != NETREG_UNINITIALIZED &&
+ dev->reg_state != NETREG_DUMMY) {
+ BUG_ON(dev->reg_state != NETREG_UNREGISTERED);
+ WRITE_ONCE(dev->reg_state, NETREG_RELEASED);
}
- BUG_ON(dev->reg_state != NETREG_UNREGISTERED);
- WRITE_ONCE(dev->reg_state, NETREG_RELEASED);
+ spin_lock(&put_lock);
+ list_add_tail(&dev->todo_list, &put_list);
+ work = list_is_singular(&put_list);
+ spin_unlock(&put_lock);
- /* will free via device release */
- put_device(&dev->dev);
+ if (work)
+ schedule_work(&put_work);
}
EXPORT_SYMBOL(free_netdev);
To start convertation devices one by one, we need defaults assigned to rest of devices. Here we add default lock assignment and a branch for already converted drivers in register_netdevice. Signed-off-by: Kirill Tkhai <tkhai@ya.ru> --- include/linux/netdevice.h | 8 +++ net/core/dev.c | 127 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 123 insertions(+), 12 deletions(-)