From nobody Mon May 25 05:54:13 2026 Received: from mail-qv1-f50.google.com (mail-qv1-f50.google.com [209.85.219.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 015E03C5853 for ; Mon, 18 May 2026 05:57:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779083830; cv=none; b=GXsiX5UEzYA3lJJ0rFrxr6inL5iGZPjQ+JggXaOyR5jxuca6q1sUE/XiO5mRgsFvJjfyONkdILIDIZ6AeBE1bmS/qI/rPVB+tn4qVenRI5DOMwJOmNPtr5ZGJny23XgAGEpjbmi1EICPT7ZIloEJImm4akgrK3UPL1K8wdTRgOE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779083830; c=relaxed/simple; bh=dZgsj3svlkH/KZ6CkOn0pPVe9UUBaOaXE8i5DZxoDi0=; h=Date:From:To:Cc:Subject:Message-ID:MIME-Version:Content-Type: Content-Disposition; b=RXBPO6pe0AO7p9DLP0v3liqURcFm5peeBjhDTCFUKDLAkfnzmYLsVe/WDSl7QdxICJfEDcAT+2mYczBpaBSnhni8qAE4//9BbrTpstnxrhzN0JZINKtSbFHDk3WvX6tJchbjRd+HYPUNqfCQAWLV2TJUB+kvX83mMwt3gkdO/xo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=juliahub.com; spf=pass smtp.mailfrom=juliahub.com; dkim=pass (2048-bit key) header.d=juliahub.com header.i=@juliahub.com header.b=B8y1ajx7; arc=none smtp.client-ip=209.85.219.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=juliahub.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=juliahub.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=juliahub.com header.i=@juliahub.com header.b="B8y1ajx7" Received: by mail-qv1-f50.google.com with SMTP id 6a1803df08f44-8b3fe2f19a4so22872396d6.2 for ; Sun, 17 May 2026 22:57:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=juliahub.com; s=google; t=1779083821; x=1779688621; darn=vger.kernel.org; h=content-disposition:mime-version:message-id:subject:cc:to:from:date :from:to:cc:subject:date:message-id:reply-to; bh=+ulqQPlVV/y7BfD0xL+liiVg74f2HlR/Ki2I9yWqZnA=; b=B8y1ajx7BcjMcnQT7nxrqPDWLgmIox/VUycZcnAX9D3EXjZnh8hGysreEYxa+0OmbB S8AEa+faINs5l0jVpPzFoXrxOgzlJ81TolWRnPFWeBc91EoY+c+SpUCm8W6JpSUsdyre eSH4G/lXgf6Lcup2H/5GJj5hXo1zQ2jamjts+VspSoU5e/BtJ35AlEfUqc+lSc4HVdwG MzP2udak7wpQAGwoJH1PM24BXNc7jnM2/c00LIU8C9vEMVAAjitMYki1nycqegFL4dFM j5+4ADNwfWtYmRuFqsge3SpthNts2VqlFa1q7yExbx7aUYzpjo1R7J3EhjAJfSG9rqhb gjHg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779083821; x=1779688621; h=content-disposition:mime-version:message-id:subject:cc:to:from:date :x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=+ulqQPlVV/y7BfD0xL+liiVg74f2HlR/Ki2I9yWqZnA=; b=rU1HNf0fFpTc8rPW2/pq27Eh3y6XW6nRtzwj5OJ1MiagluGxrDUgVXToyUEdgVI4ZZ xEGAq0EdUl+FwOsCY4fel+bgHuEjZwtvXjQRww7tC4e/ffu7D20jezJDw1iwroOY+aMg q1UDWEzk/5YQGG3TcKlXZ1aWhFjP86U9yqbx5ADFA9aqww6Vw1Rx2s0qkuhAn2JDeIpH f0hdI72q9okmvXLPYBvp3lKhxKmy7rj9hIZJDSMIcP2MPJzjhBFBf+SF/wHQlUb0Ikw5 386WcPCo+ZTvlmJLLQvV3BuT1ISPTkFPPc7wZQ/ByX6JIcyzqmKygXOrJoG62tnESLH2 mLyg== X-Forwarded-Encrypted: i=1; AFNElJ90MjdJ8lIPewIMetWspCQwRiCIE4VqSjdPOZgilgkvSxXabC402r4HIGfACQ3gXsGH5Xe5H1wGVmOU1j0=@vger.kernel.org X-Gm-Message-State: AOJu0YyplNuFStDviefykCEEZqOvCozrNU1fSPTKWkdpdFjTaya52Snq l2EgmElwSU451FTE+128C6itWkwRNvuKGak1OroJUjS+HXuMBwD10o2vv/v67B3xLjw= X-Gm-Gg: Acq92OEO3ecvT4w6Xg8cg4DrS0BjgZTEP/ZWSPAS8Ut/ju3+gzCUGhKGgjibqd/4Iz3 MqFhNuMCkueCVbzx64hTwWIKa17HH5AbO5u58XjKAeVBR/TZ0f3XnKVAnZg9SPVH2aP+x4u7Fgk uSTrNB8Ra8Ql02sHFaa3Ko0VAnHRt0I9OgPthIbLW2dC8cQfsEP+hAt1RZY09PMSHG73k3id1Iv xGI/zgDsqCxRaaLJ975Q9CenkUdtqKGyPpFWvHWY83pwXsgjy9ZeJ2nbsbQxoOpqeFJQaZv0yWH Bvj4ExbQQZrC/MvfMor+kPA87R+xjBB+8lkRemkEXJxvS8IxGUh5UTvmkYMbEp425kB80feVQ/8 9pkpK+R0pghv6f/hkBHQkGUtelj7X2hRI3df01TkT58Yxf88w2YXkHkOSZb5DvRgWqGgH77/gox zYzyEb0TjqQoZdTTLuPJWhbd8FxfTjgwRZo7irScsph6XI+PfrJj5+ X-Received: by 2002:a05:6214:242b:b0:8bf:87c5:fa21 with SMTP id 6a1803df08f44-8ca0f706530mr255919346d6.49.1779083821544; Sun, 17 May 2026 22:57:01 -0700 (PDT) Received: from juliahub.com ([66.31.114.203]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8ca36086a61sm45866836d6.4.2026.05.17.22.57.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 17 May 2026 22:57:01 -0700 (PDT) Date: Mon, 18 May 2026 01:56:59 -0400 From: Keno Fischer To: netdev@vger.kernel.org Cc: Ido Schimmel , Petr Machata , linux-kernel@vger.kernel.org Subject: [PATCH net-next] mlxsw: spectrum_dcb: Reject writes owned by an offloaded qdisc Message-ID: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Some bits of state in the mlxsw driver can be controlled by two mechanisms. The first is the DCB netlink command set (RTM_SETDCB and e.g. DCB_ATTR_IEEE_ETS/DCB_ATTR_IEEE_MAXRATE), reachable e.g. from the `dcb` userspace utility. The second is via the qdisc framework (reachable e.g. from the `tc` userspace utility). Because they touch the same bit of state, it is currently possible for the ETS config to get out of sync with what the qdisc expects. One hard-to-debug consequence is that a config script becomes non-idempotent. E.g., consider ``` dcb ets set dev "${port}" prio-tc 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7 tc qdisc replace dev "${port}" root handle 1: prio bands 8 \ priomap 0 1 2 3 4 5 6 7 ``` Here the user made a mistake and put the priomap in the wrong order. The first time this code is run, the `qdisc` specification overwrites the `prio-tc` specification. However, the second time this code is run, the subsystem sees that the qdisc is identical and does nothing, causing the `prio-tc` setting to stick and the final mapping to be different. This can be very confusing, since the same config script creates two different states. I think there's two reasonable ways to fix this. One would be by reflecting the ets state changes back into the qdisc, so that the qdisc subsystem notices that it's been changed. However, I think it is clearer to just forbid the non-qdisc API from trying to make changes while the qdisc is offloaded. This is similar to the existing check in mlxsw_sp_dcbnl_setbuffer, which does essentially the same thing in reverse (buffers are only explicitly managed when there is an offloaded qdisc, otherwise they're managed explicitly). I hope this saves the next person a few hours of debugging wondering why their tc_no_buffer_discard_uc_tc_ counters are going up. Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Keno Fischer --- .../net/ethernet/mellanox/mlxsw/spectrum.h | 2 + .../ethernet/mellanox/mlxsw/spectrum_dcb.c | 10 +++++ .../ethernet/mellanox/mlxsw/spectrum_qdisc.c | 43 +++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum.h b/drivers/net/e= thernet/mellanox/mlxsw/spectrum.h index b03ff9e044f9..ecf4525c42a4 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum.h +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum.h @@ -1260,6 +1260,8 @@ int mlxsw_sp_setup_tc_block_qevent_early_drop(struct = mlxsw_sp_port *mlxsw_sp_por struct flow_block_offload *f); int mlxsw_sp_setup_tc_block_qevent_mark(struct mlxsw_sp_port *mlxsw_sp_por= t, struct flow_block_offload *f); +bool mlxsw_sp_qdisc_has_prio_ets(struct mlxsw_sp_port *mlxsw_sp_port); +bool mlxsw_sp_qdisc_has_tbf_subgroup(struct mlxsw_sp_port *mlxsw_sp_port); =20 /* spectrum_fid.c */ struct mlxsw_sp_fid *mlxsw_sp_fid_lookup_by_index(struct mlxsw_sp *mlxsw_s= p, diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_dcb.c b/drivers/n= et/ethernet/mellanox/mlxsw/spectrum_dcb.c index f1ad937405a3..6e689d5de6fa 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_dcb.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_dcb.c @@ -151,6 +151,11 @@ static int mlxsw_sp_dcbnl_ieee_setets(struct net_devic= e *dev, struct mlxsw_sp_port *mlxsw_sp_port =3D netdev_priv(dev); int err; =20 + if (mlxsw_sp_qdisc_has_prio_ets(mlxsw_sp_port)) { + netdev_err(dev, "ETS configuration is controlled by offloaded qdisc\n"); + return -EBUSY; + } + err =3D mlxsw_sp_port_ets_validate(mlxsw_sp_port, ets); if (err) return err; @@ -450,6 +455,11 @@ static int mlxsw_sp_dcbnl_ieee_setmaxrate(struct net_d= evice *dev, struct ieee_maxrate *my_maxrate =3D mlxsw_sp_port->dcb.maxrate; int err, i; =20 + if (mlxsw_sp_qdisc_has_tbf_subgroup(mlxsw_sp_port)) { + netdev_err(dev, "maxrate is controlled by offloaded qdisc\n"); + return -EBUSY; + } + for (i =3D 0; i < IEEE_8021QAZ_MAX_TCS; i++) { err =3D mlxsw_sp_port_ets_maxrate_set(mlxsw_sp_port, MLXSW_REG_QEEC_HR_SUBGROUP, diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_qdisc.c b/drivers= /net/ethernet/mellanox/mlxsw/spectrum_qdisc.c index 73519301b744..c2ce4f3f562e 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_qdisc.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_qdisc.c @@ -2316,6 +2316,49 @@ int mlxsw_sp_setup_tc_block_qevent_mark(struct mlxsw= _sp_port *mlxsw_sp_port, action_mask); } =20 +bool mlxsw_sp_qdisc_has_prio_ets(struct mlxsw_sp_port *mlxsw_sp_port) +{ + struct mlxsw_sp_qdisc_state *qdisc_state =3D mlxsw_sp_port->qdisc; + struct mlxsw_sp_qdisc *root_qdisc; + bool found; + + mutex_lock(&qdisc_state->lock); + root_qdisc =3D &qdisc_state->root_qdisc; + found =3D root_qdisc->ops && + (root_qdisc->ops->type =3D=3D MLXSW_SP_QDISC_PRIO || + root_qdisc->ops->type =3D=3D MLXSW_SP_QDISC_ETS); + mutex_unlock(&qdisc_state->lock); + + return found; +} + +static struct mlxsw_sp_qdisc * +mlxsw_sp_qdisc_walk_cb_find_subgroup_tbf(struct mlxsw_sp_qdisc *qdisc, + void *data) +{ + struct mlxsw_sp_port *mlxsw_sp_port =3D (struct mlxsw_sp_port *)data; + + if (qdisc->ops && qdisc->ops->type =3D=3D MLXSW_SP_QDISC_TBF && + mlxsw_sp_qdisc_tbf_hr(mlxsw_sp_port, qdisc) =3D=3D + MLXSW_REG_QEEC_HR_SUBGROUP) + return qdisc; + return NULL; +} + +bool mlxsw_sp_qdisc_has_tbf_subgroup(struct mlxsw_sp_port *mlxsw_sp_port) +{ + struct mlxsw_sp_qdisc_state *qdisc_state =3D mlxsw_sp_port->qdisc; + bool found; + + mutex_lock(&qdisc_state->lock); + found =3D mlxsw_sp_qdisc_walk(&qdisc_state->root_qdisc, + mlxsw_sp_qdisc_walk_cb_find_subgroup_tbf, + mlxsw_sp_port) !=3D NULL; + mutex_unlock(&qdisc_state->lock); + + return found; +} + int mlxsw_sp_tc_qdisc_init(struct mlxsw_sp_port *mlxsw_sp_port) { struct mlxsw_sp_qdisc_state *qdisc_state; --=20 2.54.0