@@ -3705,80 +3705,75 @@ static void bond_fold_stats(struct rtnl_link_stats64 *_res,
}
}
-#ifdef CONFIG_LOCKDEP
-static int bond_get_lowest_level_rcu(struct net_device *dev)
-{
- struct net_device *ldev, *next, *now, *dev_stack[MAX_NEST_DEV + 1];
- struct list_head *niter, *iter, *iter_stack[MAX_NEST_DEV + 1];
- int cur = 0, max = 0;
-
- now = dev;
- iter = &dev->adj_list.lower;
-
- while (1) {
- next = NULL;
- while (1) {
- ldev = netdev_next_lower_dev_rcu(now, &iter);
- if (!ldev)
- break;
-
- next = ldev;
- niter = &ldev->adj_list.lower;
- dev_stack[cur] = now;
- iter_stack[cur++] = iter;
- if (max <= cur)
- max = cur;
- break;
- }
-
- if (!next) {
- if (!cur)
- return max;
- next = dev_stack[--cur];
- niter = iter_stack[cur];
- }
-
- now = next;
- iter = niter;
- }
-
- return max;
-}
-#endif
-
static int bond_get_stats(struct net_device *bond_dev,
struct rtnl_link_stats64 *stats)
{
struct bonding *bond = netdev_priv(bond_dev);
- struct rtnl_link_stats64 temp;
- struct list_head *iter;
- struct slave *slave;
- int nest_level = 0;
- int res = 0;
+ struct rtnl_link_stats64 *dev_stats;
+ struct bonding_slave_dev *s;
+ struct list_head slaves;
+ int res, num_slaves;
+ int i = 0;
- rcu_read_lock();
-#ifdef CONFIG_LOCKDEP
- nest_level = bond_get_lowest_level_rcu(bond_dev);
-#endif
+ res = bond_get_slaves(bond, &slaves, &num_slaves);
+ if (res)
+ return res;
- spin_lock_nested(&bond->stats_lock, nest_level);
- memcpy(stats, &bond->bond_stats, sizeof(*stats));
+ dev_stats = kcalloc(num_slaves, sizeof(*dev_stats), GFP_KERNEL);
+ if (!dev_stats) {
+ bond_put_slaves(&slaves);
+ return -ENOMEM;
+ }
- bond_for_each_slave_rcu(bond, slave, iter) {
- res = dev_get_stats(slave->dev, &temp);
+ /* Recurse with no locks taken */
+ list_for_each_entry(s, &slaves, list) {
+ res = dev_get_stats(s->ndev, &dev_stats[i]);
if (res)
goto out;
+ i++;
+ }
+
+ spin_lock(&bond->stats_lock);
+
+ memcpy(stats, &bond->bond_stats, sizeof(*stats));
+
+ /* Because we released the RCU lock in bond_get_slaves, the new slave
+ * array might be different from the original one, so we need to take
+ * it again and only update the stats of the slaves that still exist.
+ */
+ rcu_read_lock();
+
+ i = 0;
+
+ list_for_each_entry(s, &slaves, list) {
+ struct list_head *iter;
+ struct slave *slave;
- bond_fold_stats(stats, &temp, &slave->slave_stats);
+ bond_for_each_slave_rcu(bond, slave, iter) {
+ if (slave->dev != s->ndev)
+ continue;
- /* save off the slave stats for the next run */
- memcpy(&slave->slave_stats, &temp, sizeof(temp));
+ bond_fold_stats(stats, &dev_stats[i],
+ &slave->slave_stats);
+
+ /* save off the slave stats for the next run */
+ memcpy(&slave->slave_stats, &dev_stats[i],
+ sizeof(dev_stats[i]));
+ break;
+ }
+
+ i++;
}
+ rcu_read_unlock();
+
memcpy(&bond->bond_stats, stats, sizeof(*stats));
-out:
+
spin_unlock(&bond->stats_lock);
- rcu_read_unlock();
+
+out:
+ kfree(dev_stats);
+ bond_put_slaves(&slaves);
return res;
}
@@ -449,6 +449,60 @@ static inline void bond_hw_addr_copy(u8 *dst, const u8 *src, unsigned int len)
memcpy(dst, src, len);
}
+/* Helpers for reference counting the struct net_device behind the bond slaves.
+ * These can be used to propagate the net_device_ops from the bond to the
+ * slaves while not holding rcu_read_lock() or the rtnl_mutex.
+ */
+struct bonding_slave_dev {
+ struct net_device *ndev;
+ struct list_head list;
+};
+
+static inline void bond_put_slaves(struct list_head *slaves)
+{
+ struct bonding_slave_dev *s, *tmp;
+
+ list_for_each_entry_safe(s, tmp, slaves, list) {
+ dev_put(s->ndev);
+ list_del(&s->list);
+ kfree(s);
+ }
+}
+
+static inline int bond_get_slaves(struct bonding *bond,
+ struct list_head *slaves,
+ int *num_slaves)
+{
+ struct list_head *iter;
+ struct slave *slave;
+ int err = 0;
+
+ INIT_LIST_HEAD(slaves);
+ *num_slaves = 0;
+
+ rcu_read_lock();
+
+ bond_for_each_slave_rcu(bond, slave, iter) {
+ struct bonding_slave_dev *s;
+
+ s = kzalloc(sizeof(*s), GFP_ATOMIC);
+ if (!s) {
+ rcu_read_unlock();
+ bond_put_slaves(slaves);
+ break;
+ }
+
+ s->ndev = slave->dev;
+ dev_hold(s->ndev);
+ list_add_tail(&s->list, slaves);
+ (*num_slaves)++;
+ }
+
+ rcu_read_unlock();
+
+ return err;
+}
+
#define BOND_PRI_RESELECT_ALWAYS 0
#define BOND_PRI_RESELECT_BETTER 1
#define BOND_PRI_RESELECT_FAILURE 2