|
@@ -4600,6 +4600,232 @@ static int __init dev_proc_init(void)
|
|
|
#endif /* CONFIG_PROC_FS */
|
|
|
|
|
|
|
|
|
+struct netdev_upper {
|
|
|
+ struct net_device *dev;
|
|
|
+ bool master;
|
|
|
+ struct list_head list;
|
|
|
+ struct rcu_head rcu;
|
|
|
+ struct list_head search_list;
|
|
|
+};
|
|
|
+
|
|
|
+static void __append_search_uppers(struct list_head *search_list,
|
|
|
+ struct net_device *dev)
|
|
|
+{
|
|
|
+ struct netdev_upper *upper;
|
|
|
+
|
|
|
+ list_for_each_entry(upper, &dev->upper_dev_list, list) {
|
|
|
+ /* check if this upper is not already in search list */
|
|
|
+ if (list_empty(&upper->search_list))
|
|
|
+ list_add_tail(&upper->search_list, search_list);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static bool __netdev_search_upper_dev(struct net_device *dev,
|
|
|
+ struct net_device *upper_dev)
|
|
|
+{
|
|
|
+ LIST_HEAD(search_list);
|
|
|
+ struct netdev_upper *upper;
|
|
|
+ struct netdev_upper *tmp;
|
|
|
+ bool ret = false;
|
|
|
+
|
|
|
+ __append_search_uppers(&search_list, dev);
|
|
|
+ list_for_each_entry(upper, &search_list, search_list) {
|
|
|
+ if (upper->dev == upper_dev) {
|
|
|
+ ret = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ __append_search_uppers(&search_list, upper->dev);
|
|
|
+ }
|
|
|
+ list_for_each_entry_safe(upper, tmp, &search_list, search_list)
|
|
|
+ INIT_LIST_HEAD(&upper->search_list);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static struct netdev_upper *__netdev_find_upper(struct net_device *dev,
|
|
|
+ struct net_device *upper_dev)
|
|
|
+{
|
|
|
+ struct netdev_upper *upper;
|
|
|
+
|
|
|
+ list_for_each_entry(upper, &dev->upper_dev_list, list) {
|
|
|
+ if (upper->dev == upper_dev)
|
|
|
+ return upper;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * netdev_has_upper_dev - Check if device is linked to an upper device
|
|
|
+ * @dev: device
|
|
|
+ * @upper_dev: upper device to check
|
|
|
+ *
|
|
|
+ * Find out if a device is linked to specified upper device and return true
|
|
|
+ * in case it is. Note that this checks only immediate upper device,
|
|
|
+ * not through a complete stack of devices. The caller must hold the RTNL lock.
|
|
|
+ */
|
|
|
+bool netdev_has_upper_dev(struct net_device *dev,
|
|
|
+ struct net_device *upper_dev)
|
|
|
+{
|
|
|
+ ASSERT_RTNL();
|
|
|
+
|
|
|
+ return __netdev_find_upper(dev, upper_dev);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(netdev_has_upper_dev);
|
|
|
+
|
|
|
+/**
|
|
|
+ * netdev_has_any_upper_dev - Check if device is linked to some device
|
|
|
+ * @dev: device
|
|
|
+ *
|
|
|
+ * Find out if a device is linked to an upper device and return true in case
|
|
|
+ * it is. The caller must hold the RTNL lock.
|
|
|
+ */
|
|
|
+bool netdev_has_any_upper_dev(struct net_device *dev)
|
|
|
+{
|
|
|
+ ASSERT_RTNL();
|
|
|
+
|
|
|
+ return !list_empty(&dev->upper_dev_list);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(netdev_has_any_upper_dev);
|
|
|
+
|
|
|
+/**
|
|
|
+ * netdev_master_upper_dev_get - Get master upper device
|
|
|
+ * @dev: device
|
|
|
+ *
|
|
|
+ * Find a master upper device and return pointer to it or NULL in case
|
|
|
+ * it's not there. The caller must hold the RTNL lock.
|
|
|
+ */
|
|
|
+struct net_device *netdev_master_upper_dev_get(struct net_device *dev)
|
|
|
+{
|
|
|
+ struct netdev_upper *upper;
|
|
|
+
|
|
|
+ ASSERT_RTNL();
|
|
|
+
|
|
|
+ if (list_empty(&dev->upper_dev_list))
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ upper = list_first_entry(&dev->upper_dev_list,
|
|
|
+ struct netdev_upper, list);
|
|
|
+ if (likely(upper->master))
|
|
|
+ return upper->dev;
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(netdev_master_upper_dev_get);
|
|
|
+
|
|
|
+/**
|
|
|
+ * netdev_master_upper_dev_get_rcu - Get master upper device
|
|
|
+ * @dev: device
|
|
|
+ *
|
|
|
+ * Find a master upper device and return pointer to it or NULL in case
|
|
|
+ * it's not there. The caller must hold the RCU read lock.
|
|
|
+ */
|
|
|
+struct net_device *netdev_master_upper_dev_get_rcu(struct net_device *dev)
|
|
|
+{
|
|
|
+ struct netdev_upper *upper;
|
|
|
+
|
|
|
+ upper = list_first_or_null_rcu(&dev->upper_dev_list,
|
|
|
+ struct netdev_upper, list);
|
|
|
+ if (upper && likely(upper->master))
|
|
|
+ return upper->dev;
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(netdev_master_upper_dev_get_rcu);
|
|
|
+
|
|
|
+static int __netdev_upper_dev_link(struct net_device *dev,
|
|
|
+ struct net_device *upper_dev, bool master)
|
|
|
+{
|
|
|
+ struct netdev_upper *upper;
|
|
|
+
|
|
|
+ ASSERT_RTNL();
|
|
|
+
|
|
|
+ if (dev == upper_dev)
|
|
|
+ return -EBUSY;
|
|
|
+
|
|
|
+ /* To prevent loops, check if dev is not upper device to upper_dev. */
|
|
|
+ if (__netdev_search_upper_dev(upper_dev, dev))
|
|
|
+ return -EBUSY;
|
|
|
+
|
|
|
+ if (__netdev_find_upper(dev, upper_dev))
|
|
|
+ return -EEXIST;
|
|
|
+
|
|
|
+ if (master && netdev_master_upper_dev_get(dev))
|
|
|
+ return -EBUSY;
|
|
|
+
|
|
|
+ upper = kmalloc(sizeof(*upper), GFP_KERNEL);
|
|
|
+ if (!upper)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ upper->dev = upper_dev;
|
|
|
+ upper->master = master;
|
|
|
+ INIT_LIST_HEAD(&upper->search_list);
|
|
|
+
|
|
|
+ /* Ensure that master upper link is always the first item in list. */
|
|
|
+ if (master)
|
|
|
+ list_add_rcu(&upper->list, &dev->upper_dev_list);
|
|
|
+ else
|
|
|
+ list_add_tail_rcu(&upper->list, &dev->upper_dev_list);
|
|
|
+ dev_hold(upper_dev);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * netdev_upper_dev_link - Add a link to the upper device
|
|
|
+ * @dev: device
|
|
|
+ * @upper_dev: new upper device
|
|
|
+ *
|
|
|
+ * Adds a link to device which is upper to this one. The caller must hold
|
|
|
+ * the RTNL lock. On a failure a negative errno code is returned.
|
|
|
+ * On success the reference counts are adjusted and the function
|
|
|
+ * returns zero.
|
|
|
+ */
|
|
|
+int netdev_upper_dev_link(struct net_device *dev,
|
|
|
+ struct net_device *upper_dev)
|
|
|
+{
|
|
|
+ return __netdev_upper_dev_link(dev, upper_dev, false);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(netdev_upper_dev_link);
|
|
|
+
|
|
|
+/**
|
|
|
+ * netdev_master_upper_dev_link - Add a master link to the upper device
|
|
|
+ * @dev: device
|
|
|
+ * @upper_dev: new upper device
|
|
|
+ *
|
|
|
+ * Adds a link to device which is upper to this one. In this case, only
|
|
|
+ * one master upper device can be linked, although other non-master devices
|
|
|
+ * might be linked as well. The caller must hold the RTNL lock.
|
|
|
+ * On a failure a negative errno code is returned. On success the reference
|
|
|
+ * counts are adjusted and the function returns zero.
|
|
|
+ */
|
|
|
+int netdev_master_upper_dev_link(struct net_device *dev,
|
|
|
+ struct net_device *upper_dev)
|
|
|
+{
|
|
|
+ return __netdev_upper_dev_link(dev, upper_dev, true);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(netdev_master_upper_dev_link);
|
|
|
+
|
|
|
+/**
|
|
|
+ * netdev_upper_dev_unlink - Removes a link to upper device
|
|
|
+ * @dev: device
|
|
|
+ * @upper_dev: new upper device
|
|
|
+ *
|
|
|
+ * Removes a link to device which is upper to this one. The caller must hold
|
|
|
+ * the RTNL lock.
|
|
|
+ */
|
|
|
+void netdev_upper_dev_unlink(struct net_device *dev,
|
|
|
+ struct net_device *upper_dev)
|
|
|
+{
|
|
|
+ struct netdev_upper *upper;
|
|
|
+
|
|
|
+ ASSERT_RTNL();
|
|
|
+
|
|
|
+ upper = __netdev_find_upper(dev, upper_dev);
|
|
|
+ if (!upper)
|
|
|
+ return;
|
|
|
+ list_del_rcu(&upper->list);
|
|
|
+ dev_put(upper_dev);
|
|
|
+ kfree_rcu(upper, rcu);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(netdev_upper_dev_unlink);
|
|
|
+
|
|
|
/**
|
|
|
* netdev_set_master - set up master pointer
|
|
|
* @slave: slave device
|
|
@@ -4613,19 +4839,23 @@ static int __init dev_proc_init(void)
|
|
|
int netdev_set_master(struct net_device *slave, struct net_device *master)
|
|
|
{
|
|
|
struct net_device *old = slave->master;
|
|
|
+ int err;
|
|
|
|
|
|
ASSERT_RTNL();
|
|
|
|
|
|
if (master) {
|
|
|
if (old)
|
|
|
return -EBUSY;
|
|
|
- dev_hold(master);
|
|
|
+ err = netdev_master_upper_dev_link(slave, master);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
}
|
|
|
|
|
|
slave->master = master;
|
|
|
|
|
|
if (old)
|
|
|
- dev_put(old);
|
|
|
+ netdev_upper_dev_unlink(slave, master);
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
EXPORT_SYMBOL(netdev_set_master);
|
|
@@ -5503,8 +5733,8 @@ static void rollback_registered_many(struct list_head *head)
|
|
|
if (dev->netdev_ops->ndo_uninit)
|
|
|
dev->netdev_ops->ndo_uninit(dev);
|
|
|
|
|
|
- /* Notifier chain MUST detach us from master device. */
|
|
|
- WARN_ON(dev->master);
|
|
|
+ /* Notifier chain MUST detach us all upper devices. */
|
|
|
+ WARN_ON(netdev_has_any_upper_dev(dev));
|
|
|
|
|
|
/* Remove entries from kobject tree */
|
|
|
netdev_unregister_kobject(dev);
|
|
@@ -6212,6 +6442,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
|
|
|
INIT_LIST_HEAD(&dev->napi_list);
|
|
|
INIT_LIST_HEAD(&dev->unreg_list);
|
|
|
INIT_LIST_HEAD(&dev->link_watch_list);
|
|
|
+ INIT_LIST_HEAD(&dev->upper_dev_list);
|
|
|
dev->priv_flags = IFF_XMIT_DST_RELEASE;
|
|
|
setup(dev);
|
|
|
|