ice_setup_tc() and dcbnl ops are called with rtnl held and then take
pf->tc_mutex. LLDP MIB change handler and ice_dcb_rebuild() (the latter
only on its error path, via ice_pf_dcb_cfg(..., false)) do the opposite:
take pf->tc_mutex and then rtnl. The MIB change handler runs from the
service task. ice_dcb_rebuild() runs from ice_rebuild() (reset, also PCI
AER resume), and from ice_link_event() in the service task, on a link
change while DCB is enabled. So a MIB change from the link partner, a reset
or a link change, concurrent with "tc qdisc add ... mqprio" or "dcb ets
set" may deadlock.
Take rtnl first in both functions. ice_dcb_rebuild() now holds rtnl also
on its success path, which only sends a few AQ commands. None of its
callers hold rtnl.
Lockdep splat, on the first MIB change that needs a DCB reconfiguration
after "dcb ets set" was used:
CPU0 CPU1
---- ----
lock(&pf->tc_mutex);
lock(rtnl_mutex);
lock(&pf->tc_mutex);
lock(rtnl_mutex);
Reproducer, with the FW LLDP agent in its default (willing) mode:
PF=eth2
ethtool --set-priv-flags $PF fw-lldp-agent off
dcb ets set dev $PF tc-tsa all:ets tc-bw all:0 0:100 prio-tc all:0
ethtool --set-priv-flags $PF fw-lldp-agent on
and then, on the link partner, send an ETS recommendation with a different
UP2TC mapping (python3 lldp-ets.py <partner-ifname>):
import socket, struct, sys
ifc = sys.argv[1]
mac = open(f"/sys/class/net/{ifc}/address").read().strip()
mac = bytes.fromhex(mac.replace(":", ""))
tlv = lambda t, v: struct.pack("!H", t << 9 | len(v)) + v
# not willing, prio 4 -> TC1, TC0/TC1 ETS 50/50
ets = bytes([0, 0, 0, 0x10, 0, 50, 50] + [0] * 6 + [2, 2] + [0] * 6)
lldp = (tlv(1, b"\x04" + mac) + tlv(2, b"\x03" + mac) +
tlv(3, b"\x00\x78") +
tlv(127, b"\x00\x80\xc2\x09" + ets) + # ETS Configuration
tlv(127, b"\x00\x80\xc2\x0a" + ets) + # ETS Recommendation
tlv(0, b""))
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW)
s.bind((ifc, 0))
s.send(bytes.fromhex("0180c200000e") + mac + b"\x88\xcc" + lldp)
Fixes: 242b5e068b25 ("ice: Fix DCB rebuild after reset")
Signed-off-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
---
drivers/net/ethernet/intel/ice/ice_dcb_lib.c | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_dcb_lib.c b/drivers/net/ethernet/intel/ice/ice_dcb_lib.c
index 0bc6dd375687..3034f461f513 100644
--- a/drivers/net/ethernet/intel/ice/ice_dcb_lib.c
+++ b/drivers/net/ethernet/intel/ice/ice_dcb_lib.c
@@ -537,6 +537,7 @@ void ice_dcb_rebuild(struct ice_pf *pf)
struct ice_dcbx_cfg *err_cfg;
int ret;
+ rtnl_lock();
mutex_lock(&pf->tc_mutex);
ret = ice_query_port_ets(pf->hw.port_info, &buf, sizeof(buf), NULL);
@@ -570,14 +571,16 @@ void ice_dcb_rebuild(struct ice_pf *pf)
}
mutex_unlock(&pf->tc_mutex);
+ rtnl_unlock();
return;
dcb_error:
dev_err(dev, "Disabling DCB until new settings occur\n");
err_cfg = kzalloc_obj(*err_cfg);
if (!err_cfg) {
mutex_unlock(&pf->tc_mutex);
+ rtnl_unlock();
return;
}
@@ -591,10 +594,11 @@ void ice_dcb_rebuild(struct ice_pf *pf)
* Suppress the Coverity warning with the following comment...
*/
/* coverity[check_return] */
- ice_pf_dcb_cfg(pf, err_cfg, false);
+ ice_pf_dcb_cfg(pf, err_cfg, true);
kfree(err_cfg);
mutex_unlock(&pf->tc_mutex);
+ rtnl_unlock();
}
/**
@@ -1055,6 +1059,7 @@ ice_dcb_process_lldp_set_mib_change(struct ice_pf *pf,
}
/* That a DCB change has happened is now determined */
+ rtnl_lock();
mutex_lock(&pf->tc_mutex);
/* store the old configuration */
@@ -1105,25 +1110,23 @@ ice_dcb_process_lldp_set_mib_change(struct ice_pf *pf,
pending_handled = true;
}
- rtnl_lock();
/* disable VSIs affected by DCB changes */
ice_dcb_ena_dis_vsi(pf, false, true);
ret = ice_query_port_ets(pi, &buf, sizeof(buf), NULL);
if (ret) {
dev_err(dev, "Query Port ETS failed\n");
- goto unlock_rtnl;
+ goto out;
}
/* changes in configuration update VSI */
ice_pf_dcb_recfg(pf, false);
/* enable previously downed VSIs */
ice_dcb_ena_dis_vsi(pf, true, true);
-unlock_rtnl:
- rtnl_unlock();
out:
mutex_unlock(&pf->tc_mutex);
+ rtnl_unlock();
/* Send Execute Pending MIB Change event if it is a Pending event */
if (!pending_handled)--
2.54.0