ice_tear_down_devlink_rate_tree() is called with RTNL held (dcbnl, mqprio),
and then blocks on the PF devlink lock and pf->vfs.table_lock, both of which
are taken the other way around elsewhere:
- ice_start_vfs() and ice_free_vfs() hold table_lock and call
ice_eswitch_{attach,detach}_vf(), which take the PF devlink lock and
register/unregister representor netdevs (so RTNL).
- ice_probe() holds the devlink lock across ice_load(), which registers
the netdev (so RTNL).
Lockdep reports a possible ABBA deadlock:
CPU0 CPU1
---- ----
lock(&pf->vfs.table_lock);
lock(&devlink->lock_key);
lock(&pf->vfs.table_lock);
lock(&devlink->lock_key);
Swapping the two locks would only fix this particular pair, as the RTNL
inversion would remain. Instead, only try both locks and fail with -EAGAIN
when either is contended, unlike the -EBUSY of an enabled custom Tx
scheduler.
Return early, without taking any lock, when no tree is exported: always
when switchdev is not running, and also after DCB got enabled, as the tree
is not exported again then. The latter is checked by rate_node pointers of
the Tx scheduler nodes, under the scheduler lock, unless the scheduler is
not ready (during a reset). This way DCB and mqprio configuration fails on
contention only when there is a tree to tear down. It also covers
ice_pf_dcb_cfg() with locked == false, called from probe or devlink reload,
where the devlink lock is already held and RTNL is not.
Propagate the error to the callers that can report it: dcbnl ops and
mqprio. For that, stop ice_dcbnl_setapp() from folding all errors into
ICE_DCB_NO_HW_CHG. ice_dcbnl_setapp() updates the app list, and may switch
the FW to DSCP mode, before it calls ice_pf_dcb_cfg(). To fail before any
change in the usual case, move the custom Tx scheduler check and the
teardown into a helper, and call it from ice_dcbnl_setapp() first.
ice_dcbnl_delapp() does not add TCs, so it has no tree to tear down.
ice_dcbnl_cee_set_all() returns a u8 status, not an errno, so it can't
report -EAGAIN, but the desired config is kept and a retry applies it.
DCB init, called from ethtool priv flags, already ignores ice_pf_dcb_cfg()
failures.
Found during development, reproducer:
PF=ens801f7np7
BDF=$(basename $(readlink /sys/class/net/$PF/device))
ethtool --set-priv-flags $PF fw-lldp-agent off
devlink dev eswitch set pci/$BDF mode switchdev
echo 2 > /sys/class/net/$PF/device/sriov_numvfs
dcb ets set dev $PF tc-tsa all:ets tc-bw all:0 0:50 1:50 prio-tc all:0 4:1
Fixes: fff292b47ac1 ("ice: add VF representors one by one")
Signed-off-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
---
.../net/ethernet/intel/ice/devlink/devlink.h | 2 +-
drivers/net/ethernet/intel/ice/ice_dcb_lib.h | 1 +
.../net/ethernet/intel/ice/devlink/devlink.c | 47 +++++++++++++++++--
drivers/net/ethernet/intel/ice/ice_dcb_lib.c | 27 +++++++++--
drivers/net/ethernet/intel/ice/ice_dcb_nl.c | 15 +++++-
drivers/net/ethernet/intel/ice/ice_main.c | 4 +-
6 files changed, 83 insertions(+), 13 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/devlink/devlink.h b/drivers/net/ethernet/intel/ice/devlink/devlink.h
index 1af3b0763fbb..4139171ca57c 100644
--- a/drivers/net/ethernet/intel/ice/devlink/devlink.h
+++ b/drivers/net/ethernet/intel/ice/devlink/devlink.h
@@ -20,7 +20,7 @@ void ice_devlink_init_regions(struct ice_pf *pf);
void ice_devlink_destroy_regions(struct ice_pf *pf);
int ice_devlink_rate_init_tx_topology(struct devlink *devlink, struct ice_vsi *vsi);
-void ice_tear_down_devlink_rate_tree(struct ice_pf *pf);
+int ice_tear_down_devlink_rate_tree(struct ice_pf *pf);
void ice_devlink_rate_clear_tx_topology(struct ice_vsi *vsi);
#endif /* _ICE_DEVLINK_H_ */
diff --git a/drivers/net/ethernet/intel/ice/ice_dcb_lib.h b/drivers/net/ethernet/intel/ice/ice_dcb_lib.h
index da9ba814b4e8..dd00cb459212 100644
--- a/drivers/net/ethernet/intel/ice/ice_dcb_lib.h
+++ b/drivers/net/ethernet/intel/ice/ice_dcb_lib.h
@@ -20,6 +20,7 @@ u8 ice_dcb_get_num_tc(struct ice_dcbx_cfg *dcbcfg);
void ice_vsi_set_dcb_tc_cfg(struct ice_vsi *vsi);
bool ice_is_pfc_causing_hung_q(struct ice_pf *pf, unsigned int txqueue);
u8 ice_dcb_get_tc(struct ice_vsi *vsi, int queue_index);
+int ice_dcb_prep_multi_tc(struct ice_pf *pf);
int
ice_pf_dcb_cfg(struct ice_pf *pf, struct ice_dcbx_cfg *new_cfg, bool locked);
int ice_dcb_bwchk(struct ice_pf *pf, struct ice_dcbx_cfg *dcbcfg);
diff --git a/drivers/net/ethernet/intel/ice/devlink/devlink.c b/drivers/net/ethernet/intel/ice/devlink/devlink.c
index 044aa62be473..c94bc75e448e 100644
--- a/drivers/net/ethernet/intel/ice/devlink/devlink.c
+++ b/drivers/net/ethernet/intel/ice/devlink/devlink.c
@@ -683,31 +683,68 @@ static int ice_devlink_tx_sched_layers_validate(struct devlink *devlink, u32 id,
return 0;
}
+static bool ice_devlink_rate_tree_is_exported(struct ice_port_info *pi)
+{
+ struct ice_sched_node *tc_node;
+ bool exported = false;
+
+ if (pi->port_state != ICE_SCHED_PORT_STATE_READY)
+ return false;
+
+ mutex_lock(&pi->sched_lock);
+ tc_node = pi->root->children[0];
+ for (int i = 0; i < tc_node->num_children; i++) {
+ if (tc_node->children[i]->rate_node) {
+ exported = true;
+ break;
+ }
+ }
+ mutex_unlock(&pi->sched_lock);
+
+ return exported;
+}
+
/**
* ice_tear_down_devlink_rate_tree - removes devlink-rate exported tree
* @pf: pf struct
*
* This function tears down tree exported during VF's creation.
+ *
+ * Return: 0 on success or when no tree is exported, -EAGAIN if a lock could
+ * not be taken.
*/
-void ice_tear_down_devlink_rate_tree(struct ice_pf *pf)
+int ice_tear_down_devlink_rate_tree(struct ice_pf *pf)
{
struct devlink *devlink;
struct ice_vf *vf;
unsigned int bkt;
+ /* The tree is exported only when switchdev is running */
+ if (!ice_is_switchdev_running(pf) ||
+ !ice_devlink_rate_tree_is_exported(pf->hw.port_info))
+ return 0;
+
+ ASSERT_RTNL();
+
devlink = priv_to_devlink(pf);
- devl_lock(devlink);
- mutex_lock(&pf->vfs.table_lock);
+ /* Both locks are taken before RTNL elsewhere, so only try them here */
+ if (!mutex_trylock(&pf->vfs.table_lock))
+ return -EAGAIN;
+ if (!devl_trylock(devlink)) {
+ mutex_unlock(&pf->vfs.table_lock);
+ return -EAGAIN;
+ }
ice_for_each_vf(pf, bkt, vf) {
if (vf->devlink_port.devlink_rate)
devl_rate_leaf_destroy(&vf->devlink_port);
}
- mutex_unlock(&pf->vfs.table_lock);
-
ice_devlink_rate_clear_tx_topology(ice_get_main_vsi(pf));
devl_rate_nodes_destroy(devlink);
devl_unlock(devlink);
+ mutex_unlock(&pf->vfs.table_lock);
+
+ return 0;
}
/**diff --git a/drivers/net/ethernet/intel/ice/ice_dcb_lib.c b/drivers/net/ethernet/intel/ice/ice_dcb_lib.c
index 3034f461f513..59fafd296f1c 100644
--- a/drivers/net/ethernet/intel/ice/ice_dcb_lib.c
+++ b/drivers/net/ethernet/intel/ice/ice_dcb_lib.c
@@ -341,6 +341,23 @@ int ice_dcb_bwchk(struct ice_pf *pf, struct ice_dcbx_cfg *dcbcfg)
return 0;
}
+/**
+ * ice_dcb_prep_multi_tc - prepare the Tx scheduler for more than one TC
+ * @pf: pointer to the PF struct
+ *
+ * Return: 0 on success, -EBUSY if the Tx scheduler is customized via
+ * devlink-rate, -EAGAIN if its devlink-rate export could not be torn down.
+ */
+int ice_dcb_prep_multi_tc(struct ice_pf *pf)
+{
+ if (pf->hw.port_info->is_custom_tx_enabled) {
+ dev_err(ice_pf_to_dev(pf), "Custom Tx scheduler feature enabled, can't configure DCB\n");
+ return -EBUSY;
+ }
+
+ return ice_tear_down_devlink_rate_tree(pf);
+}
+
/**
* ice_pf_dcb_cfg - Apply new DCB configuration
* @pf: pointer to the PF struct@@ -364,12 +381,12 @@ int ice_pf_dcb_cfg(struct ice_pf *pf, struct ice_dcbx_cfg *new_cfg, bool locked)
/* Enable DCB tagging only when more than one TC */
if (ice_dcb_get_num_tc(new_cfg) > 1) {
+ int err;
+
dev_dbg(dev, "DCB tagging enabled (num TC > 1)\n");
- if (pf->hw.port_info->is_custom_tx_enabled) {
- dev_err(dev, "Custom Tx scheduler feature enabled, can't configure DCB\n");
- return -EBUSY;
- }
- ice_tear_down_devlink_rate_tree(pf);
+ err = ice_dcb_prep_multi_tc(pf);
+ if (err)
+ return err;
set_bit(ICE_FLAG_DCB_ENA, pf->flags);
} else {diff --git a/drivers/net/ethernet/intel/ice/ice_dcb_nl.c b/drivers/net/ethernet/intel/ice/ice_dcb_nl.c
index 9cdb9581fdc3..2b9f5e6c1bbe 100644
--- a/drivers/net/ethernet/intel/ice/ice_dcb_nl.c
+++ b/drivers/net/ethernet/intel/ice/ice_dcb_nl.c
@@ -736,6 +736,7 @@ static int ice_dcbnl_setapp(struct net_device *netdev, struct dcb_app *app)
struct ice_pf *pf = ice_netdev_to_pf(netdev);
struct ice_dcb_app_priority_table new_app;
struct ice_dcbx_cfg *old_cfg, *new_cfg;
+ int tc_to_prep;
u8 max_tc;
int ret;
@@ -779,6 +780,18 @@ static int ice_dcbnl_setapp(struct net_device *netdev, struct dcb_app *app)
new_cfg = &pf->hw.port_info->qos_cfg.desired_dcbx_cfg;
old_cfg = &pf->hw.port_info->qos_cfg.local_dcbx_cfg;
+ /* fail before any change; switching to DSCP mode enables max_tc TCs */
+ if (old_cfg->pfc_mode == ICE_QOS_MODE_VLAN)
+ tc_to_prep = max_tc;
+ else
+ tc_to_prep = ice_dcb_get_num_tc(new_cfg);
+
+ if (tc_to_prep > 1) {
+ ret = ice_dcb_prep_multi_tc(pf);
+ if (ret)
+ goto setapp_out;
+ }
+
ret = dcb_ieee_setapp(netdev, app);
if (ret)
goto setapp_out;@@ -850,7 +863,7 @@ static int ice_dcbnl_setapp(struct net_device *netdev, struct dcb_app *app)
/* return of zero indicates new cfg applied */
if (ret == ICE_DCB_HW_CHG_RST)
ice_dcbnl_devreset(netdev);
- else
+ else if (ret > 0)
ret = ICE_DCB_NO_HW_CHG;
setapp_out:
diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
index 454fba07d4ec..30de690ecf7c 100644
--- a/drivers/net/ethernet/intel/ice/ice_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_main.c
@@ -9180,7 +9180,9 @@ static int ice_setup_tc_mqprio_qdisc(struct net_device *netdev, void *type_data)
dev_err(dev, "Custom Tx scheduler feature enabled, can't configure ADQ\n");
return -EBUSY;
}
- ice_tear_down_devlink_rate_tree(pf);
+ ret = ice_tear_down_devlink_rate_tree(pf);
+ if (ret)
+ return ret;
ret = ice_validate_mqprio_qopt(vsi, mqprio_qopt);
if (ret) {--
2.54.0