Subject: [PATCH net v3 05/11] team: use dynamic lockdep key instead of static key

From: Taehee Yoo <[email-masked]>
Date: Mon, 16 Sep 2019 22:47:56 +0900

In the current code, all team devices have same static lockdep key
and team devices could be nested so that it makes unnecessary
lockdep warning.

Test commands:
    ip link add team0 type team
    for i in {1..7}
    do
	    let A=$i-1
	    ip link add team$i type team
	    ip link set team$i master team$A
    done
    ip link del team0

Splat looks like:
[   52.518420] WARNING: possible recursive locking detected
[   52.518955] 5.3.0-rc8+ #179 Not tainted
[   52.519373] --------------------------------------------
[   52.519925] ip/1005 is trying to acquire lock:
[   52.520893] 00000000c84e69ac (&dev_addr_list_lock_key/1){+...}, at: dev_uc_sync_multiple+0xfa/0x1a0
[   52.522194]
[   52.522194] but task is already holding lock:
[   52.523038] 0000000025864f52 (&dev_addr_list_lock_key/1){+...}, at: dev_uc_unsync+0x10c/0x1b0
[   52.524609]
[   52.524609] other info that might help us debug this:
[   52.525435]  Possible unsafe locking scenario:
[   52.525435]
[   52.526154]        CPU0
[   52.526460]        ----
[   52.526766]   lock(&dev_addr_list_lock_key/1);
[   52.527311]   lock(&dev_addr_list_lock_key/1);
[   52.527854]
[   52.527854]  *** DEADLOCK ***
[   52.527854]
[   52.528573]  May be due to missing lock nesting notation
[   52.528573]
[   52.529527] 5 locks held by ip/1005:
[   52.529968]  #0: 0000000080a68bb2 (rtnl_mutex){+.+.}, at: rtnetlink_rcv_msg+0x466/0x8a0
[   52.530940]  #1: 0000000035d90450 (&team->lock){+.+.}, at: team_uninit+0x3a/0x1a0 [team]
[   52.531933]  #2: 00000000c75d8f70 (&dev_addr_list_lock_key){+...}, at: dev_uc_unsync+0x98/0x1b0
[   52.532991]  #3: 0000000025864f52 (&dev_addr_list_lock_key/1){+...}, at: dev_uc_unsync+0x10c/0x1b0
[   52.534142]  #4: 00000000efa4d642 (rcu_read_lock){....}, at: team_set_rx_mode+0x5/0x1d0 [team]
[   52.535195]
[   52.535195] stack backtrace:
[   52.535727] CPU: 0 PID: 1005 Comm: ip Not tainted 5.3.0-rc8+ #179
[   52.536376] Hardware name: innotek GmbH VirtualBox/VirtualBox, BIOS VirtualBox 12/01/2006
[   52.537254] Call Trace:
[   52.537549]  dump_stack+0x7c/0xbb
[   52.537917]  __lock_acquire+0x26a9/0x3de0
[   52.538340]  ? register_lock_class+0x14d0/0x14d0
[   52.538872]  ? register_lock_class+0x14d0/0x14d0
[   52.539559]  lock_acquire+0x164/0x3b0
[   52.540043]  ? dev_uc_sync_multiple+0xfa/0x1a0
[   52.540663]  _raw_spin_lock_nested+0x2e/0x60
[   52.541241]  ? dev_uc_sync_multiple+0xfa/0x1a0
[   52.541867]  dev_uc_sync_multiple+0xfa/0x1a0
[   52.542435]  team_set_rx_mode+0xa9/0x1d0 [team]
[   52.543033]  dev_uc_unsync+0x151/0x1b0
[   52.543534]  team_port_del+0x304/0x790 [team]
[   52.544110]  team_uninit+0xb0/0x1a0 [team]
[   52.544653]  rollback_registered_many+0x728/0xda0
[   52.545271]  ? generic_xdp_install+0x310/0x310
[   52.545865]  ? check_chain_key+0x236/0x5d0
[   52.546425]  ? __nla_validate_parse+0x98/0x1ad0
[   52.547023]  unregister_netdevice_many.part.124+0x13/0x1b0
[   52.547741]  rtnl_delete_link+0xbc/0x100
[   52.548262]  ? rtnl_af_register+0xc0/0xc0
[   52.548793]  rtnl_dellink+0x30e/0x8a0
[ ... ]

Fixes: 3d249d4ca7d0 ("net: introduce ethernet teaming device")
Signed-off-by: Taehee Yoo <[email-masked]>
---

v2 -> v3 :
 - This patch is not changed
v1 -> v2 :
 - This patch is not changed

 drivers/net/team/team.c | 61 ++++++++++++++++++++++++++++++++++++++---
 include/linux/if_team.h |  5 ++++
 2 files changed, 62 insertions(+), 4 deletions(-)

diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c
index e8089def5a46..bfcd6ed57493 100644
--- a/drivers/net/team/team.c
+++ b/drivers/net/team/team.c
@@ -1607,6 +1607,34 @@ static const struct team_option team_options[] = {
 	},
 };
 
+static void team_dev_set_lockdep_one(struct net_device *dev,
+				     struct netdev_queue *txq,
+				     void *_unused)
+{
+	struct team *team = netdev_priv(dev);
+
+	lockdep_set_class(&txq->_xmit_lock, &team->xmit_lock_key);
+}
+
+static struct lock_class_key qdisc_tx_busylock_key;
+static struct lock_class_key qdisc_running_key;
+
+static void team_dev_set_lockdep_class(struct net_device *dev)
+{
+	struct team *team = netdev_priv(dev);
+
+	dev->qdisc_tx_busylock = &qdisc_tx_busylock_key;
+	dev->qdisc_running_key = &qdisc_running_key;
+
+	lockdep_register_key(&team->team_lock_key);
+	__mutex_init(&team->lock, "team->team_lock_key", &team->team_lock_key);
+
+	lockdep_register_key(&team->addr_lock_key);
+	lockdep_set_class(&dev->addr_list_lock, &team->addr_lock_key);
+
+	lockdep_register_key(&team->xmit_lock_key);
+	netdev_for_each_tx_queue(dev, team_dev_set_lockdep_one, NULL);
+}
 
 static int team_init(struct net_device *dev)
 {
@@ -1615,7 +1643,6 @@ static int team_init(struct net_device *dev)
 	int err;
 
 	team->dev = dev;
-	mutex_init(&team->lock);
 	team_set_no_mode(team);
 
 	team->pcpu_stats = netdev_alloc_pcpu_stats(struct team_pcpu_stats);
@@ -1642,7 +1669,7 @@ static int team_init(struct net_device *dev)
 		goto err_options_register;
 	netif_carrier_off(dev);
 
-	netdev_lockdep_set_classes(dev);
+	team_dev_set_lockdep_class(dev);
 
 	return 0;
 
@@ -1673,6 +1700,11 @@ static void team_uninit(struct net_device *dev)
 	team_queue_override_fini(team);
 	mutex_unlock(&team->lock);
 	netdev_change_features(dev);
+
+	lockdep_unregister_key(&team->team_lock_key);
+	lockdep_unregister_key(&team->addr_lock_key);
+	lockdep_unregister_key(&team->xmit_lock_key);
+
 }
 
 static void team_destructor(struct net_device *dev)
@@ -1967,6 +1999,23 @@ static int team_add_slave(struct net_device *dev, struct net_device *port_dev,
 	return err;
 }
 
+static void team_update_lock_key(struct net_device *dev)
+{
+	struct team *team = netdev_priv(dev);
+
+	lockdep_unregister_key(&team->team_lock_key);
+	lockdep_unregister_key(&team->addr_lock_key);
+	lockdep_unregister_key(&team->xmit_lock_key);
+
+	lockdep_register_key(&team->team_lock_key);
+	lockdep_register_key(&team->addr_lock_key);
+	lockdep_register_key(&team->xmit_lock_key);
+
+	lockdep_set_class(&team->lock, &team->team_lock_key);
+	lockdep_set_class(&dev->addr_list_lock, &team->addr_lock_key);
+	netdev_for_each_tx_queue(dev, team_dev_set_lockdep_one, NULL);
+}
+
 static int team_del_slave(struct net_device *dev, struct net_device *port_dev)
 {
 	struct team *team = netdev_priv(dev);
@@ -1976,8 +2025,12 @@ static int team_del_slave(struct net_device *dev, struct net_device *port_dev)
 	err = team_port_del(team, port_dev);
 	mutex_unlock(&team->lock);
 
-	if (!err)
-		netdev_change_features(dev);
+	if (err)
+		return err;
+
+	if (netif_is_team_master(port_dev))
+		team_update_lock_key(port_dev);
+	netdev_change_features(dev);
 
 	return err;
 }
diff --git a/include/linux/if_team.h b/include/linux/if_team.h
index 06faa066496f..9c97bb19ed34 100644
--- a/include/linux/if_team.h
+++ b/include/linux/if_team.h
@@ -223,6 +223,11 @@ struct team {
 		atomic_t count_pending;
 		struct delayed_work dw;
 	} mcast_rejoin;
+
+	struct lock_class_key team_lock_key;
+	struct lock_class_key xmit_lock_key;
+	struct lock_class_key addr_lock_key;
+
 	long mode_priv[TEAM_MODE_PRIV_LONGS];
 };
 
-- 
2.17.1