* Re: [PATCH v2 net-next] ipv4: fib: fix route re-dump in inet_dump_fib() on multi-batch dump
From: patchwork-bot+netdevbpf @ 2026-07-03 6:50 UTC (permalink / raw)
To: Pengfei Zhang
Cc: dsahern, idosch, davem, edumazet, kuba, pabeni, horms, netdev,
linux-kernel, chenzhangqi, baohua, zhangpengfei16
In-Reply-To: <20260630084220.2711025-1-zhangfeionline@gmail.com>
Hello:
This patch was applied to netdev/net-next.git (main)
by Paolo Abeni <pabeni@redhat.com>:
On Tue, 30 Jun 2026 16:42:20 +0800 you wrote:
> inet_dump_fib() saves its progress in cb->args[1] as a positional
> index within the current hash chain. Between batches, a concurrent
> fib_new_table() can insert a new table at the chain head, shifting
> all existing entries. On resume the saved index lands on a different
> table, causing already-dumped tables to be re-dumped and the
> originally suspended table to restart from the beginning.
>
> [...]
Here is the summary with links:
- [v2,net-next] ipv4: fib: fix route re-dump in inet_dump_fib() on multi-batch dump
https://git.kernel.org/netdev/net-next/c/2ed8d5c72488
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* [PATCH net-next 2/2] net: dsa: mv88e6xxx: add support for DCB apptrust app
From: Luke Howard @ 2026-07-03 6:46 UTC (permalink / raw)
To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn
Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
Luke Howard
In-Reply-To: <20260703-mv88e6xxx-dscp-prio-map-v1-0-250e0ebcce83@padl.com>
Implement port_{get,set}_apptrust for Marvell switches that support it.
This maps to the per-port USE_TAG / USE_IP / TAG_IF_BOTH bits in Port
Control 0, which the driver already initialises for every port (trust
both, IP taking precedence), so apptrust is available across the shared
mv88e6352_dcb_ops families, not only the 6352/6390/6393x.
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Luke Howard <lukeh@padl.com>
---
drivers/net/dsa/mv88e6xxx/chip.c | 2 +
drivers/net/dsa/mv88e6xxx/chip.h | 6 +++
drivers/net/dsa/mv88e6xxx/dcb.c | 112 +++++++++++++++++++++++++++++++++++++++
drivers/net/dsa/mv88e6xxx/dcb.h | 5 ++
4 files changed, 125 insertions(+)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 3bdf8c7b36612..6b85b2d0a982f 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -7437,6 +7437,8 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
.port_get_dscp_prio = mv88e6xxx_port_get_dscp_prio,
.port_add_dscp_prio = mv88e6xxx_port_add_dscp_prio,
.port_del_dscp_prio = mv88e6xxx_port_del_dscp_prio,
+ .port_get_apptrust = mv88e6xxx_port_get_apptrust,
+ .port_set_apptrust = mv88e6xxx_port_set_apptrust,
.port_setup_tc = mv88e6xxx_port_setup_tc,
.cls_flower_add = mv88e6xxx_cls_flower_add,
.cls_flower_del = mv88e6xxx_cls_flower_del,
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index d6265e925574f..e95be110dfa7b 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -808,6 +808,12 @@ struct mv88e6xxx_dcb_ops {
u8 dscp, u8 prio);
int (*port_del_dscp_prio)(struct mv88e6xxx_chip *chip, int port,
u8 dscp);
+
+ /* Get/set a port's trusted application priority sources */
+ int (*port_get_apptrust)(struct mv88e6xxx_chip *chip, int port,
+ u8 *sel, int *nsel);
+ int (*port_set_apptrust)(struct mv88e6xxx_chip *chip, int port,
+ const u8 *sel, int nsel);
};
struct mv88e6xxx_ptp_ops {
diff --git a/drivers/net/dsa/mv88e6xxx/dcb.c b/drivers/net/dsa/mv88e6xxx/dcb.c
index c26f43b27f09e..08a08acb9bfad 100644
--- a/drivers/net/dsa/mv88e6xxx/dcb.c
+++ b/drivers/net/dsa/mv88e6xxx/dcb.c
@@ -311,12 +311,86 @@ static int mv88e6390_port_del_dscp_prio(struct mv88e6xxx_chip *chip, int port,
dscp));
}
+/* Application trust (apptrust).
+ *
+ * InitialPri (the UseTag/UseIP bits) selects which ingress values a port
+ * trusts for priority assignment; TagIfBoth breaks the tie when a frame
+ * matches both. DCB lists selectors in increasing precedence, so the
+ * last-named selector is the most trusted.
+ */
+
+static int mv88e6xxx_set_apptrust(struct mv88e6xxx_chip *chip, int port,
+ const u8 *sel, int nsel)
+{
+ bool use_tag = false, use_ip = false, tag_wins = false;
+ u16 reg;
+ int i, err;
+
+ for (i = 0; i < nsel; i++) {
+ switch (sel[i]) {
+ case DCB_APP_SEL_PCP:
+ use_tag = true;
+ tag_wins = true;
+ break;
+ case IEEE_8021QAZ_APP_SEL_DSCP:
+ use_ip = true;
+ tag_wins = false;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ }
+
+ err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_CTL0, ®);
+ if (err)
+ return err;
+
+ reg &= ~(MV88E6185_PORT_CTL0_USE_TAG | MV88E6185_PORT_CTL0_USE_IP |
+ MV88E6XXX_PORT_CTL0_TAG_IF_BOTH);
+ if (use_tag)
+ reg |= MV88E6185_PORT_CTL0_USE_TAG;
+ if (use_ip)
+ reg |= MV88E6185_PORT_CTL0_USE_IP;
+ if (use_tag && use_ip && tag_wins)
+ reg |= MV88E6XXX_PORT_CTL0_TAG_IF_BOTH;
+
+ return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_CTL0, reg);
+}
+
+static int mv88e6xxx_get_apptrust(struct mv88e6xxx_chip *chip, int port,
+ u8 *sel, int *nsel)
+{
+ bool tag_wins;
+ u16 reg;
+ int err, n = 0;
+
+ err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_CTL0, ®);
+ if (err)
+ return err;
+
+ /* Report in increasing precedence: the winner of TagIfBoth comes last */
+ tag_wins = reg & MV88E6XXX_PORT_CTL0_TAG_IF_BOTH;
+
+ if (!tag_wins && (reg & MV88E6185_PORT_CTL0_USE_TAG))
+ sel[n++] = DCB_APP_SEL_PCP;
+ if (reg & MV88E6185_PORT_CTL0_USE_IP)
+ sel[n++] = IEEE_8021QAZ_APP_SEL_DSCP;
+ if (tag_wins && (reg & MV88E6185_PORT_CTL0_USE_TAG))
+ sel[n++] = DCB_APP_SEL_PCP;
+
+ *nsel = n;
+
+ return 0;
+}
+
const struct mv88e6xxx_dcb_ops mv88e6352_dcb_ops = {
.global_get_pcp_prio = mv88e6352_get_pcp_prio,
.global_set_pcp_prio = mv88e6352_set_pcp_prio,
.global_get_dscp_prio = mv88e6352_get_dscp_prio,
.global_set_dscp_prio = mv88e6352_set_dscp_prio,
.global_del_dscp_prio = mv88e6352_del_dscp_prio,
+ .port_get_apptrust = mv88e6xxx_get_apptrust,
+ .port_set_apptrust = mv88e6xxx_set_apptrust,
};
const struct mv88e6xxx_dcb_ops mv88e6390_dcb_ops = {
@@ -325,6 +399,8 @@ const struct mv88e6xxx_dcb_ops mv88e6390_dcb_ops = {
.port_get_dscp_prio = mv88e6390_port_get_dscp_prio,
.port_set_dscp_prio = mv88e6390_port_set_dscp_prio,
.port_del_dscp_prio = mv88e6390_port_del_dscp_prio,
+ .port_get_apptrust = mv88e6xxx_get_apptrust,
+ .port_set_apptrust = mv88e6xxx_set_apptrust,
};
const struct mv88e6xxx_dcb_ops mv88e6393x_dcb_ops = {
@@ -333,6 +409,8 @@ const struct mv88e6xxx_dcb_ops mv88e6393x_dcb_ops = {
.port_get_dscp_prio = mv88e6390_port_get_dscp_prio,
.port_set_dscp_prio = mv88e6390_port_set_dscp_prio,
.port_del_dscp_prio = mv88e6390_port_del_dscp_prio,
+ .port_get_apptrust = mv88e6xxx_get_apptrust,
+ .port_set_apptrust = mv88e6xxx_set_apptrust,
};
static int mv88e6xxx_dcb_get_pcp_prio(struct mv88e6xxx_chip *chip, int port,
@@ -521,3 +599,37 @@ int mv88e6xxx_port_del_dscp_prio(struct dsa_switch *ds, int port, u8 dscp,
mv88e6xxx_reg_unlock(chip);
return err;
}
+
+int mv88e6xxx_port_get_apptrust(struct dsa_switch *ds, int port, u8 *sel,
+ int *nsel)
+{
+ struct mv88e6xxx_chip *chip = ds->priv;
+ const struct mv88e6xxx_dcb_ops *dcb_ops = chip->info->ops->dcb_ops;
+ int err;
+
+ if (!dcb_ops || !dcb_ops->port_get_apptrust)
+ return -EOPNOTSUPP;
+
+ mv88e6xxx_reg_lock(chip);
+ err = dcb_ops->port_get_apptrust(chip, port, sel, nsel);
+ mv88e6xxx_reg_unlock(chip);
+
+ return err;
+}
+
+int mv88e6xxx_port_set_apptrust(struct dsa_switch *ds, int port, const u8 *sel,
+ int nsel)
+{
+ struct mv88e6xxx_chip *chip = ds->priv;
+ const struct mv88e6xxx_dcb_ops *dcb_ops = chip->info->ops->dcb_ops;
+ int err;
+
+ if (!dcb_ops || !dcb_ops->port_set_apptrust)
+ return -EOPNOTSUPP;
+
+ mv88e6xxx_reg_lock(chip);
+ err = dcb_ops->port_set_apptrust(chip, port, sel, nsel);
+ mv88e6xxx_reg_unlock(chip);
+
+ return err;
+}
diff --git a/drivers/net/dsa/mv88e6xxx/dcb.h b/drivers/net/dsa/mv88e6xxx/dcb.h
index e1cf92bbd8729..256d25d650014 100644
--- a/drivers/net/dsa/mv88e6xxx/dcb.h
+++ b/drivers/net/dsa/mv88e6xxx/dcb.h
@@ -24,4 +24,9 @@ int mv88e6xxx_port_add_dscp_prio(struct dsa_switch *ds, int port, u8 dscp,
int mv88e6xxx_port_del_dscp_prio(struct dsa_switch *ds, int port, u8 dscp,
u8 prio);
+int mv88e6xxx_port_get_apptrust(struct dsa_switch *ds, int port, u8 *sel,
+ int *nsel);
+int mv88e6xxx_port_set_apptrust(struct dsa_switch *ds, int port, const u8 *sel,
+ int nsel);
+
#endif /* _MV88E6XXX_DCB_H_ */
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 1/2] net: dsa: mv88e6xxx: add support for DCB DSCP app
From: Luke Howard @ 2026-07-03 6:46 UTC (permalink / raw)
To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn
Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
Luke Howard
In-Reply-To: <20260703-mv88e6xxx-dscp-prio-map-v1-0-250e0ebcce83@padl.com>
Implement {get,add,del}_dscp_prio for Marvell switches that support it.
On switches that do not support setting the QPri independently of FPri,
FPri is set to the default for the DSCP value.
On 6352-class switches the DSCP-to-QPri mapping is a global table with no
per-entry "unmapped" state: every DSCP is always mapped, so the DCB app
table comes up fully populated and deleting an entry restores its
reset-default QPri rather than removing it.
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Luke Howard <lukeh@padl.com>
---
drivers/net/dsa/mv88e6xxx/chip.c | 6 +
drivers/net/dsa/mv88e6xxx/chip.h | 14 ++
drivers/net/dsa/mv88e6xxx/dcb.c | 294 ++++++++++++++++++++++++++++++++++++
drivers/net/dsa/mv88e6xxx/dcb.h | 6 +
drivers/net/dsa/mv88e6xxx/global1.h | 8 +
drivers/net/dsa/mv88e6xxx/port.h | 10 ++
6 files changed, 338 insertions(+)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index cefe6be2f0ce9..3bdf8c7b36612 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -7434,6 +7434,9 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
.port_get_pcp_prio = mv88e6xxx_port_get_pcp_prio,
.port_add_pcp_prio = mv88e6xxx_port_add_pcp_prio,
.port_del_pcp_prio = mv88e6xxx_port_del_pcp_prio,
+ .port_get_dscp_prio = mv88e6xxx_port_get_dscp_prio,
+ .port_add_dscp_prio = mv88e6xxx_port_add_dscp_prio,
+ .port_del_dscp_prio = mv88e6xxx_port_del_dscp_prio,
.port_setup_tc = mv88e6xxx_port_setup_tc,
.cls_flower_add = mv88e6xxx_cls_flower_add,
.cls_flower_del = mv88e6xxx_cls_flower_del,
@@ -7471,6 +7474,9 @@ static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)
ds->pcp_prio_mapping_is_global = dcb_ops &&
dcb_ops->global_get_pcp_prio;
+ ds->dscp_prio_mapping_is_global = dcb_ops &&
+ dcb_ops->global_get_dscp_prio;
+
/* Some chips support up to 32, but that requires enabling the
* 5-bit port mode, which we do not support. 640k^W16 ought to
* be enough for anyone.
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index 8c4e8b4890907..d6265e925574f 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -794,6 +794,20 @@ struct mv88e6xxx_dcb_ops {
u8 pcp);
int (*port_set_pcp_prio)(struct mv88e6xxx_chip *chip, int port,
u8 pcp, u8 prio);
+
+ /* Get/set the chip's global DSCP to queue priority (QPri) mapping */
+ int (*global_get_dscp_prio)(struct mv88e6xxx_chip *chip, u8 dscp);
+ int (*global_set_dscp_prio)(struct mv88e6xxx_chip *chip, u8 dscp,
+ u8 prio);
+ int (*global_del_dscp_prio)(struct mv88e6xxx_chip *chip, u8 dscp);
+
+ /* Get/set a port's DSCP to queue priority (QPri) mapping */
+ int (*port_get_dscp_prio)(struct mv88e6xxx_chip *chip, int port,
+ u8 dscp);
+ int (*port_set_dscp_prio)(struct mv88e6xxx_chip *chip, int port,
+ u8 dscp, u8 prio);
+ int (*port_del_dscp_prio)(struct mv88e6xxx_chip *chip, int port,
+ u8 dscp);
};
struct mv88e6xxx_ptp_ops {
diff --git a/drivers/net/dsa/mv88e6xxx/dcb.c b/drivers/net/dsa/mv88e6xxx/dcb.c
index 0a3099cdf3882..c26f43b27f09e 100644
--- a/drivers/net/dsa/mv88e6xxx/dcb.c
+++ b/drivers/net/dsa/mv88e6xxx/dcb.c
@@ -1,6 +1,10 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2026 Luminex Network Intelligence
+#include <linux/bitfield.h>
+
+#include <net/dscp.h>
+
#include "chip.h"
#include "dcb.h"
#include "global1.h"
@@ -134,19 +138,201 @@ static int mv88e6393x_port_set_pcp_prio(struct mv88e6xxx_chip *chip, int port,
return mv88e639x_port_set_pcp_prio(chip, port, pcp, prio, true);
}
+/* The DCB priority maps the DSCP to an egress queue priority (QPri); the frame
+ * priority (FPri) is left untouched. These chips always map every DSCP, so
+ * there is no per-entry "unmapped" state on the global (6352) table: deleting
+ * an entry restores its reset-default QPri rather than removing it.
+ */
+
+/* G1 offset 0x19 is the IP Mapping Table only on these families; elsewhere it is
+ * the Core Tag Type register, so report DSCP mapping as unsupported there.
+ */
+static bool mv88e6352_has_ip_pri_map(struct mv88e6xxx_chip *chip)
+{
+ switch (chip->info->family) {
+ case MV88E6XXX_FAMILY_6320:
+ case MV88E6XXX_FAMILY_6341:
+ case MV88E6XXX_FAMILY_6352:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int mv88e6352_get_dscp_prio(struct mv88e6xxx_chip *chip, u8 dscp)
+{
+ u16 val;
+ int err;
+
+ if (!mv88e6352_has_ip_pri_map(chip))
+ return -EOPNOTSUPP;
+
+ if (dscp >= DSCP_MAX)
+ return -EINVAL;
+
+ /* load the table pointer (Update clear), then read the entry back */
+ err = mv88e6xxx_g1_write(chip, MV88E6352_G1_IP_PRI_MAP,
+ FIELD_PREP(MV88E6352_G1_IP_PRI_MAP_PTR_MASK,
+ dscp));
+ if (err)
+ return err;
+
+ err = mv88e6xxx_g1_read(chip, MV88E6352_G1_IP_PRI_MAP, &val);
+ if (err)
+ return err;
+
+ return FIELD_GET(MV88E6352_G1_IP_PRI_MAP_QPRI_MASK, val);
+}
+
+/* Update only the QPri of a DSCP entry, preserving the global UseIpFPri bit and
+ * the per-entry FPri so the frame priority handling is left untouched.
+ */
+static int mv88e6352_write_dscp_qpri(struct mv88e6xxx_chip *chip, u8 dscp,
+ u8 prio)
+{
+ u16 val;
+ int err;
+
+ /* load the table pointer (Update clear), then read the entry back */
+ err = mv88e6xxx_g1_write(chip, MV88E6352_G1_IP_PRI_MAP,
+ FIELD_PREP(MV88E6352_G1_IP_PRI_MAP_PTR_MASK,
+ dscp));
+ if (err)
+ return err;
+
+ err = mv88e6xxx_g1_read(chip, MV88E6352_G1_IP_PRI_MAP, &val);
+ if (err)
+ return err;
+
+ val &= ~(MV88E6352_G1_IP_PRI_MAP_PTR_MASK |
+ MV88E6352_G1_IP_PRI_MAP_QPRI_MASK);
+ val |= MV88E6352_G1_IP_PRI_MAP_UPDATE |
+ FIELD_PREP(MV88E6352_G1_IP_PRI_MAP_PTR_MASK, dscp) |
+ FIELD_PREP(MV88E6352_G1_IP_PRI_MAP_QPRI_MASK, prio);
+
+ return mv88e6xxx_g1_write(chip, MV88E6352_G1_IP_PRI_MAP, val);
+}
+
+static int mv88e6352_set_dscp_prio(struct mv88e6xxx_chip *chip, u8 dscp,
+ u8 prio)
+{
+ if (!mv88e6352_has_ip_pri_map(chip))
+ return -EOPNOTSUPP;
+
+ if (dscp >= DSCP_MAX)
+ return -EINVAL;
+
+ /* QPri range as for the PCP-to-priority mapping */
+ if (prio >= 4)
+ return -EINVAL;
+
+ return mv88e6352_write_dscp_qpri(chip, dscp, prio);
+}
+
+static int mv88e6352_del_dscp_prio(struct mv88e6xxx_chip *chip, u8 dscp)
+{
+ if (!mv88e6352_has_ip_pri_map(chip))
+ return -EOPNOTSUPP;
+
+ if (dscp >= DSCP_MAX)
+ return -EINVAL;
+
+ /* No per-entry disable exists, so restore the reset-default QPri, which
+ * is the top two DSCP bits (DSCP >> 4: 0x00-0x0f->0, 0x10-0x1f->1,
+ * 0x20-0x2f->2, 0x30-0x3f->3).
+ */
+ return mv88e6352_write_dscp_qpri(chip, dscp, dscp >> 4);
+}
+
+static int mv88e6390_port_get_dscp_prio(struct mv88e6xxx_chip *chip, int port,
+ u8 dscp)
+{
+ u16 val;
+ int err;
+
+ if (dscp >= DSCP_MAX)
+ return -EINVAL;
+
+ /* load the table pointer (Update clear), then read the entry back */
+ err = mv88e6xxx_port_write(chip, port, MV88E6390_PORT_IP_PRI_MAP,
+ FIELD_PREP(MV88E6390_PORT_IP_PRI_MAP_PTR_MASK,
+ dscp));
+ if (err)
+ return err;
+
+ err = mv88e6xxx_port_read(chip, port, MV88E6390_PORT_IP_PRI_MAP, &val);
+ if (err)
+ return err;
+
+ if (val & MV88E6390_PORT_IP_PRI_MAP_DIS_IP_QPRI)
+ return -EOPNOTSUPP;
+
+ return FIELD_GET(MV88E6390_PORT_IP_PRI_MAP_QPRI_MASK, val);
+}
+
+static int mv88e6390_port_set_dscp_prio(struct mv88e6xxx_chip *chip, int port,
+ u8 dscp, u8 prio)
+{
+ if (dscp >= DSCP_MAX)
+ return -EINVAL;
+
+ /* QPri range as for the PCP-to-priority mapping */
+ if (prio >= 8)
+ return -EINVAL;
+
+ /* This write defines the whole entry: enable and set the QPri, disable
+ * the FPri override so frame priority is left unchanged, and leave
+ * IP_YELLOW clear. The entry is owned solely by this API, so a blind
+ * write is sufficient and no read-modify-write is needed.
+ */
+ return mv88e6xxx_port_write(chip, port, MV88E6390_PORT_IP_PRI_MAP,
+ MV88E6390_PORT_IP_PRI_MAP_UPDATE |
+ MV88E6390_PORT_IP_PRI_MAP_DIS_IP_FPRI |
+ FIELD_PREP(MV88E6390_PORT_IP_PRI_MAP_PTR_MASK,
+ dscp) |
+ FIELD_PREP(MV88E6390_PORT_IP_PRI_MAP_QPRI_MASK,
+ prio));
+}
+
+static int mv88e6390_port_del_dscp_prio(struct mv88e6xxx_chip *chip, int port,
+ u8 dscp)
+{
+ if (dscp >= DSCP_MAX)
+ return -EINVAL;
+
+ /* Disable both the QPri and FPri overrides so the DSCP falls back to the
+ * port default; get_dscp_prio then reports the entry as removed.
+ */
+ return mv88e6xxx_port_write(chip, port, MV88E6390_PORT_IP_PRI_MAP,
+ MV88E6390_PORT_IP_PRI_MAP_UPDATE |
+ MV88E6390_PORT_IP_PRI_MAP_DIS_IP_QPRI |
+ MV88E6390_PORT_IP_PRI_MAP_DIS_IP_FPRI |
+ FIELD_PREP(MV88E6390_PORT_IP_PRI_MAP_PTR_MASK,
+ dscp));
+}
+
const struct mv88e6xxx_dcb_ops mv88e6352_dcb_ops = {
.global_get_pcp_prio = mv88e6352_get_pcp_prio,
.global_set_pcp_prio = mv88e6352_set_pcp_prio,
+ .global_get_dscp_prio = mv88e6352_get_dscp_prio,
+ .global_set_dscp_prio = mv88e6352_set_dscp_prio,
+ .global_del_dscp_prio = mv88e6352_del_dscp_prio,
};
const struct mv88e6xxx_dcb_ops mv88e6390_dcb_ops = {
.port_get_pcp_prio = mv88e6390_port_get_pcp_prio,
.port_set_pcp_prio = mv88e6390_port_set_pcp_prio,
+ .port_get_dscp_prio = mv88e6390_port_get_dscp_prio,
+ .port_set_dscp_prio = mv88e6390_port_set_dscp_prio,
+ .port_del_dscp_prio = mv88e6390_port_del_dscp_prio,
};
const struct mv88e6xxx_dcb_ops mv88e6393x_dcb_ops = {
.port_get_pcp_prio = mv88e6393x_port_get_pcp_prio,
.port_set_pcp_prio = mv88e6393x_port_set_pcp_prio,
+ .port_get_dscp_prio = mv88e6390_port_get_dscp_prio,
+ .port_set_dscp_prio = mv88e6390_port_set_dscp_prio,
+ .port_del_dscp_prio = mv88e6390_port_del_dscp_prio,
};
static int mv88e6xxx_dcb_get_pcp_prio(struct mv88e6xxx_chip *chip, int port,
@@ -227,3 +413,111 @@ int mv88e6xxx_port_del_pcp_prio(struct dsa_switch *ds, int port, u8 pcp,
mv88e6xxx_reg_unlock(chip);
return err;
}
+
+static int mv88e6xxx_dcb_get_dscp_prio(struct mv88e6xxx_chip *chip, int port,
+ u8 dscp)
+{
+ const struct mv88e6xxx_dcb_ops *dcb_ops = chip->info->ops->dcb_ops;
+
+ if (!dcb_ops)
+ return -EOPNOTSUPP;
+
+ if (dcb_ops->port_get_dscp_prio)
+ return dcb_ops->port_get_dscp_prio(chip, port, dscp);
+
+ if (dcb_ops->global_get_dscp_prio)
+ return dcb_ops->global_get_dscp_prio(chip, dscp);
+
+ return -EOPNOTSUPP;
+}
+
+static int mv88e6xxx_dcb_set_dscp_prio(struct mv88e6xxx_chip *chip, int port,
+ u8 dscp, u8 prio)
+{
+ const struct mv88e6xxx_dcb_ops *dcb_ops = chip->info->ops->dcb_ops;
+
+ if (!dcb_ops)
+ return -EOPNOTSUPP;
+
+ if (dcb_ops->port_set_dscp_prio)
+ return dcb_ops->port_set_dscp_prio(chip, port, dscp, prio);
+
+ if (dcb_ops->global_set_dscp_prio)
+ return dcb_ops->global_set_dscp_prio(chip, dscp, prio);
+
+ return -EOPNOTSUPP;
+}
+
+static int mv88e6xxx_dcb_del_dscp_prio(struct mv88e6xxx_chip *chip, int port,
+ u8 dscp)
+{
+ const struct mv88e6xxx_dcb_ops *dcb_ops = chip->info->ops->dcb_ops;
+
+ if (!dcb_ops)
+ return -EOPNOTSUPP;
+
+ if (dcb_ops->port_del_dscp_prio)
+ return dcb_ops->port_del_dscp_prio(chip, port, dscp);
+
+ if (dcb_ops->global_del_dscp_prio)
+ return dcb_ops->global_del_dscp_prio(chip, dscp);
+
+ return -EOPNOTSUPP;
+}
+
+int mv88e6xxx_port_get_dscp_prio(struct dsa_switch *ds, int port, u8 dscp)
+{
+ struct mv88e6xxx_chip *chip = ds->priv;
+ int err;
+
+ mv88e6xxx_reg_lock(chip);
+ err = mv88e6xxx_dcb_get_dscp_prio(chip, port, dscp);
+ mv88e6xxx_reg_unlock(chip);
+
+ return err;
+}
+
+int mv88e6xxx_port_add_dscp_prio(struct dsa_switch *ds, int port, u8 dscp,
+ u8 prio)
+{
+ struct mv88e6xxx_chip *chip = ds->priv;
+ int err;
+
+ if (prio >= IEEE_8021Q_MAX_PRIORITIES)
+ return -EINVAL;
+
+ mv88e6xxx_reg_lock(chip);
+ err = mv88e6xxx_dcb_set_dscp_prio(chip, port, dscp, prio);
+ mv88e6xxx_reg_unlock(chip);
+
+ return err;
+}
+
+int mv88e6xxx_port_del_dscp_prio(struct dsa_switch *ds, int port, u8 dscp,
+ u8 prio)
+{
+ struct mv88e6xxx_chip *chip = ds->priv;
+ int err;
+
+ mv88e6xxx_reg_lock(chip);
+
+ err = mv88e6xxx_dcb_get_dscp_prio(chip, port, dscp);
+ if (err == -EOPNOTSUPP) {
+ /* No explicit mapping for this DSCP, so nothing to remove */
+ err = 0;
+ goto unlock;
+ }
+ if (err < 0)
+ goto unlock;
+ if (err != prio) {
+ /* Mapping was changed in the meantime; leave it alone */
+ err = 0;
+ goto unlock;
+ }
+
+ err = mv88e6xxx_dcb_del_dscp_prio(chip, port, dscp);
+
+unlock:
+ mv88e6xxx_reg_unlock(chip);
+ return err;
+}
diff --git a/drivers/net/dsa/mv88e6xxx/dcb.h b/drivers/net/dsa/mv88e6xxx/dcb.h
index 7a2124ec23ba9..e1cf92bbd8729 100644
--- a/drivers/net/dsa/mv88e6xxx/dcb.h
+++ b/drivers/net/dsa/mv88e6xxx/dcb.h
@@ -18,4 +18,10 @@ int mv88e6xxx_port_add_pcp_prio(struct dsa_switch *ds, int port, u8 pcp,
int mv88e6xxx_port_del_pcp_prio(struct dsa_switch *ds, int port, u8 pcp,
u8 prio);
+int mv88e6xxx_port_get_dscp_prio(struct dsa_switch *ds, int port, u8 dscp);
+int mv88e6xxx_port_add_dscp_prio(struct dsa_switch *ds, int port, u8 dscp,
+ u8 prio);
+int mv88e6xxx_port_del_dscp_prio(struct dsa_switch *ds, int port, u8 dscp,
+ u8 prio);
+
#endif /* _MV88E6XXX_DCB_H_ */
diff --git a/drivers/net/dsa/mv88e6xxx/global1.h b/drivers/net/dsa/mv88e6xxx/global1.h
index 3dbb7a1b8fe11..fdc9795fa5c6d 100644
--- a/drivers/net/dsa/mv88e6xxx/global1.h
+++ b/drivers/net/dsa/mv88e6xxx/global1.h
@@ -195,6 +195,14 @@
/* Offset 0x19: Core Tag Type */
#define MV88E6185_G1_CORE_TAG_TYPE 0x19
+/* Offset 0x19: IP Mapping Table (6172/6176/6240/6320/6341/6352) */
+#define MV88E6352_G1_IP_PRI_MAP 0x19
+#define MV88E6352_G1_IP_PRI_MAP_UPDATE 0x8000
+#define MV88E6352_G1_IP_PRI_MAP_USE_IP_FPRI 0x4000
+#define MV88E6352_G1_IP_PRI_MAP_PTR_MASK 0x3f00
+#define MV88E6352_G1_IP_PRI_MAP_FPRI_MASK 0x0070
+#define MV88E6352_G1_IP_PRI_MAP_QPRI_MASK 0x0003
+
/* Offset 0x1A: Monitor Control */
#define MV88E6185_G1_MONITOR_CTL 0x1a
#define MV88E6185_G1_MONITOR_CTL_INGRESS_DEST_MASK 0xf000
diff --git a/drivers/net/dsa/mv88e6xxx/port.h b/drivers/net/dsa/mv88e6xxx/port.h
index 56143fa534c16..230a99f404606 100644
--- a/drivers/net/dsa/mv88e6xxx/port.h
+++ b/drivers/net/dsa/mv88e6xxx/port.h
@@ -448,6 +448,16 @@
/* Control for Special LED (Index 0x7 of LED Control on Port 2) */
#define MV88E6XXX_PORT_LED_CONTROL_0x07_P2_PTP_ACT 0 /* bits 6:0 PTP Activity */
+/* Offset 0x17: IP Priority Mapping Table */
+#define MV88E6390_PORT_IP_PRI_MAP 0x17
+#define MV88E6390_PORT_IP_PRI_MAP_UPDATE BIT(15)
+#define MV88E6390_PORT_IP_PRI_MAP_PTR_MASK GENMASK(14, 9)
+#define MV88E6390_PORT_IP_PRI_MAP_IP_YELLOW BIT(8)
+#define MV88E6390_PORT_IP_PRI_MAP_DIS_IP_QPRI BIT(7)
+#define MV88E6390_PORT_IP_PRI_MAP_QPRI_MASK GENMASK(6, 4)
+#define MV88E6390_PORT_IP_PRI_MAP_DIS_IP_FPRI BIT(3)
+#define MV88E6390_PORT_IP_PRI_MAP_FPRI_MASK GENMASK(2, 0)
+
/* Offset 0x18: IEEE Priority Mapping Table */
#define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE 0x18
#define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE_UPDATE 0x8000
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 0/2] net: dsa: mv88e6xxx: add support for DCB DSCP config
From: Luke Howard @ 2026-07-03 6:46 UTC (permalink / raw)
To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn
Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
Luke Howard
Add the DCB DSCP and apptrust applications for configuring DSCP to
queue mappings.
Signed-off-by: Luke Howard <lukeh@padl.com>
---
Luke Howard (2):
net: dsa: mv88e6xxx: add support for DCB DSCP app
net: dsa: mv88e6xxx: add support for DCB apptrust app
drivers/net/dsa/mv88e6xxx/chip.c | 8 +
drivers/net/dsa/mv88e6xxx/chip.h | 20 ++
drivers/net/dsa/mv88e6xxx/dcb.c | 406 ++++++++++++++++++++++++++++++++++++
drivers/net/dsa/mv88e6xxx/dcb.h | 11 +
drivers/net/dsa/mv88e6xxx/global1.h | 8 +
drivers/net/dsa/mv88e6xxx/port.h | 10 +
6 files changed, 463 insertions(+)
---
base-commit: 3abbe30231441f1fbb3305e9854c56a34650af53
change-id: 20260607-mv88e6xxx-dscp-prio-map-2fe4d8b0a19c
prerequisite-change-id: 20260430-net-next-mv88e6xxx-cbs-2121169caa68:v7
prerequisite-patch-id: 8ad59c43368d4639e0cabcc59a7f6e487560d3f7
prerequisite-patch-id: 22f1cda311b1826bd4151fc7d4cdf3d87ca519ed
prerequisite-change-id: 20260603-net-next-mv88e6xxx-pcp-prio-5752caa7926b:v1
prerequisite-patch-id: f324a0334ef91256d6f09562d978e9d6c32b7f3c
prerequisite-patch-id: 79c2b056ae68846b5e2950b9738cb83e73d2710e
Best regards,
--
Luke Howard <lukeh@padl.com>
^ permalink raw reply
* [PATCH RFC net-next v3] net: dsa: mv88e6xxx: MQPRIO support
From: Luke Howard @ 2026-07-03 6:44 UTC (permalink / raw)
To: Jiri Pirko, Ivan Vecera, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Nikolay Aleksandrov,
Ido Schimmel, Andrew Lunn, David Ahern, Shuah Khan, Andrew Lunn,
Vladimir Oltean
Cc: netdev, linux-kernel, bridge, linux-kselftest, Cedric Jehasse,
Max Hunter, Kieran Tyrrell, Simon Gapp, Max Holtmann,
Christoph Mellauner, Luke Howard
Add MQPRIO traffic class offload for the Marvell 6352 and 6390 families
of switches.
Three traffic classes are supported: legacy (TC0), low (TC1) and high
(TC2), corresponding to non-AVB, AVB Class B and AVB Class A traffic.
A single Ethernet frame priority may be mapped to each AVB class.
As the policy is per-switch, HW offload can only be enabled across
multiple ports if the policy matches. This is similar to configuration
of DCB ingress priority mappings on switches that only support global
configuration. Attempts to configure a different policy on an additional
port will return -EEXIST.
Signed-off-by: Luke Howard <lukeh@padl.com>
---
This patch series introduces support for using MQPRIO to configure
egress queues, using the AVB frame priority to queue mapping which
is supported by some Marvell switches.
It does configure the isochronous pointer reservation and enable
the standard AVB mode on all ports. The former potentially belongs
in a local patch: discussion is welcome. For this reason, and also
Jakub's comments about technical debt [1], I have demoted this to
a RFC.
This is the only way to configure priority to queue mappings on
egress on Marvell switches (their configuration of _ingress_ mappings
is more flexible and, on the 6390, is supported per-port). Egress
queues can be set in TCAM entries, but my understanding is that
tc-flower offloading can only specify priority, not queue, mappings.
We are using it in conjunction with the since rejected support for
802.1Q Dynamic Reservation Entries to implement hardware offloaded
AVB (802.1BA) bridging. The Dynamic Reservation Entry extension to
the FDB could potentially be implemented using tc-flower in a manner
acceptable to upstream; I do not have the resources to investigate
this at this time.
[1] https://lore.kernel.org/all/20260615095112.1dca0977@kernel.org/
---
Changes in v3:
- dropped support for Dynamic Resevation Entries in the MDB. Although
these are defined in the 802.1Q specification, it seemed unlikely that
the Linux bridge would accept this change. I maintain the old patches
at https://github.com/PADL/linux/tree/b4/mv88e6xxx-8021qat-mqprio.
- Link to v2: https://patch.msgid.link/20260602-mv88e6xxx-8021qat-mqprio-v2-0-72be14522e7c@padl.com
Changes in v2:
- dropped CBS implementation (this is provided separately by Cedric
Jehasse's patch series, and is required for its definition of
num_tx_queues and qav_info)
- added MQPRIO channel support
- admission control is configured using a bridge port flag rather than
a device tree entry
- software bridge support for admission control
- Link to v1: https://lore.kernel.org/all/cover.1779841530.git.lukeh@padl.com/
To: Andrew Lunn <andrew@lunn.ch>
To: Vladimir Oltean <olteanv@gmail.com>
To: "David S. Miller" <davem@davemloft.net>
To: Eric Dumazet <edumazet@google.com>
To: Jakub Kicinski <kuba@kernel.org>
To: Paolo Abeni <pabeni@redhat.com>
Cc: linux-kernel@vger.kernel.org
Cc: netdev@vger.kernel.org
---
drivers/net/dsa/mv88e6xxx/Makefile | 3 +-
drivers/net/dsa/mv88e6xxx/avb.c | 189 +++++++++++++++++++++++++++++
drivers/net/dsa/mv88e6xxx/avb.h | 67 ++++++++++
drivers/net/dsa/mv88e6xxx/chip.c | 208 ++++++++++++++++++++++++++++++++
drivers/net/dsa/mv88e6xxx/chip.h | 45 +++++++
drivers/net/dsa/mv88e6xxx/global1.h | 1 +
drivers/net/dsa/mv88e6xxx/global2.h | 2 +
drivers/net/dsa/mv88e6xxx/global2_avb.c | 121 +++++++++++++++++++
8 files changed, 635 insertions(+), 1 deletion(-)
diff --git a/drivers/net/dsa/mv88e6xxx/Makefile b/drivers/net/dsa/mv88e6xxx/Makefile
index b0b08c6f159c6..6123b431e255e 100644
--- a/drivers/net/dsa/mv88e6xxx/Makefile
+++ b/drivers/net/dsa/mv88e6xxx/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_NET_DSA_MV88E6XXX) += mv88e6xxx.o
-mv88e6xxx-objs := chip.o
+mv88e6xxx-objs := avb.o
+mv88e6xxx-objs += chip.o
mv88e6xxx-objs += devlink.o
mv88e6xxx-objs += global1.o
mv88e6xxx-objs += global1_atu.o
diff --git a/drivers/net/dsa/mv88e6xxx/avb.c b/drivers/net/dsa/mv88e6xxx/avb.c
new file mode 100644
index 0000000000000..f02aa8a4ad458
--- /dev/null
+++ b/drivers/net/dsa/mv88e6xxx/avb.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Marvell 88E6xxx Switch AVB support
+ *
+ * Copyright (c) 2024-2026 PADL Software Pty Ltd
+ */
+
+#include "avb.h"
+#include "chip.h"
+#include "global1.h"
+#include "global2.h"
+#include "port.h"
+
+static int mv88e6xxx_qav_read(struct mv88e6xxx_chip *chip, int addr,
+ u16 *data, int len)
+{
+ if (!chip->info->ops->avb_ops->qav_read)
+ return -EOPNOTSUPP;
+
+ return chip->info->ops->avb_ops->qav_read(chip, addr, data, len);
+}
+
+static int mv88e6xxx_qav_write(struct mv88e6xxx_chip *chip, int addr, u16 data)
+{
+ if (!chip->info->ops->avb_ops->qav_write)
+ return -EOPNOTSUPP;
+
+ return chip->info->ops->avb_ops->qav_write(chip, addr, data);
+}
+
+static int mv88e6xxx_avb_write(struct mv88e6xxx_chip *chip, int addr, u16 data)
+{
+ if (!chip->info->ops->avb_ops->avb_write)
+ return -EOPNOTSUPP;
+
+ return chip->info->ops->avb_ops->avb_write(chip, addr, data);
+}
+
+static int mv88e6xxx_port_avb_read(struct mv88e6xxx_chip *chip, int port,
+ int addr, u16 *data, int len)
+{
+ if (!chip->info->ops->avb_ops->port_avb_read)
+ return -EOPNOTSUPP;
+
+ return chip->info->ops->avb_ops->port_avb_read(chip, port, addr,
+ data, len);
+}
+
+static int mv88e6xxx_port_avb_write(struct mv88e6xxx_chip *chip, int port,
+ int addr, u16 data)
+{
+ if (!chip->info->ops->avb_ops->port_avb_write)
+ return -EOPNOTSUPP;
+
+ return chip->info->ops->avb_ops->port_avb_write(chip, port, addr, data);
+}
+
+static int mv88e6xxx_qav_set_iso_ptr(struct mv88e6xxx_chip *chip, u16 threshold)
+{
+ u16 data;
+ int err;
+
+ err = mv88e6xxx_qav_read(chip, MV88E6XXX_QAV_CFG, &data, 1);
+ if (err)
+ return err;
+
+ data &= ~(MV88E6XXX_QAV_CFG_GLOBAL_ISO_PTR_MASK);
+ data |= MV88E6XXX_QAV_CFG_GLOBAL_ISO_PTR_SET(threshold);
+
+ return mv88e6xxx_qav_write(chip, MV88E6XXX_QAV_CFG, data);
+}
+
+static int mv88e6xxx_avb_set_port_avb_mode(struct mv88e6xxx_chip *chip,
+ int port, u16 avb_mode)
+{
+ u16 data;
+ int err;
+
+ err = mv88e6xxx_port_avb_read(chip, port, MV88E6XXX_PORT_AVB_CFG,
+ &data, 1);
+ if (err)
+ return err;
+
+ data &= ~(MV88E6XXX_PORT_AVB_CFG_AVB_MODE |
+ MV88E6XXX_PORT_AVB_CFG_AVB_OVERRIDE |
+ MV88E6XXX_PORT_AVB_CFG_AVB_TUNNEL);
+
+ data |= avb_mode & MV88E6XXX_PORT_AVB_CFG_AVB_MODE;
+ data |= MV88E6XXX_PORT_AVB_CFG_AVB_FILTER_BAD_AVB;
+
+ return mv88e6xxx_port_avb_write(chip, port, MV88E6XXX_PORT_AVB_CFG, data);
+}
+
+static u8 mv88e6xxx_mqprio_tc_fpri(const struct tc_mqprio_qopt *qopt, int tc)
+{
+ u8 fpri;
+
+ for (fpri = 0; fpri < IEEE_8021Q_MAX_PRIORITIES; fpri++)
+ if (qopt->prio_tc_map[fpri] == tc)
+ return fpri;
+
+ return 0;
+}
+
+u16 mv88e6xxx_avb_pri_map_to_reg(const struct tc_mqprio_qopt *qopt)
+{
+ u8 hi_fpri = mv88e6xxx_mqprio_tc_fpri(qopt, MV88E6XXX_AVB_TC_HI);
+ u8 lo_fpri = mv88e6xxx_mqprio_tc_fpri(qopt, MV88E6XXX_AVB_TC_LO);
+ u8 hi_qpri = qopt->offset[MV88E6XXX_AVB_TC_HI];
+ u8 lo_qpri = qopt->offset[MV88E6XXX_AVB_TC_LO];
+
+ return MV88E6XXX_AVB_CFG_AVB_HI_FPRI_SET(hi_fpri) |
+ MV88E6XXX_AVB_CFG_AVB_HI_QPRI_SET(hi_qpri) |
+ MV88E6XXX_AVB_CFG_AVB_LO_FPRI_SET(lo_fpri) |
+ MV88E6XXX_AVB_CFG_AVB_LO_QPRI_SET(lo_qpri);
+}
+
+int mv88e6xxx_avb_enable(struct mv88e6xxx_chip *chip,
+ struct tc_mqprio_qopt_offload *mqprio)
+{
+ const struct mv88e6xxx_qav_info *qav = chip->info->qav;
+ int err, port;
+
+ if (!qav)
+ return -EOPNOTSUPP;
+
+ /* Reserve 64 (1 << 6) isochronous pointers per port for the AVB queues. */
+ err = mv88e6xxx_qav_set_iso_ptr(chip, mv88e6xxx_num_ports(chip) << 6);
+ if (err)
+ return err;
+
+ err = mv88e6xxx_avb_write(chip, MV88E6XXX_AVB_CFG_AVB,
+ mv88e6xxx_avb_pri_map_to_reg(&mqprio->qopt));
+ if (err)
+ goto err_iso_ptr;
+
+ for (port = 0; port < mv88e6xxx_num_ports(chip); port++) {
+ if (!dsa_is_user_port(chip->ds, port))
+ continue;
+
+ err = mv88e6xxx_avb_set_port_avb_mode(chip, port,
+ MV88E6XXX_PORT_AVB_CFG_AVB_MODE_STANDARD);
+ if (err)
+ goto err_port_mode;
+ }
+
+ return 0;
+
+err_port_mode:
+ while (--port >= 0) {
+ if (!dsa_is_user_port(chip->ds, port))
+ continue;
+
+ mv88e6xxx_avb_set_port_avb_mode(chip, port,
+ MV88E6XXX_PORT_AVB_CFG_AVB_MODE_LEGACY);
+ }
+ mv88e6xxx_avb_write(chip, MV88E6XXX_AVB_CFG_AVB, qav->avb_pri_map);
+err_iso_ptr:
+ mv88e6xxx_qav_set_iso_ptr(chip, 0);
+
+ return err;
+}
+
+int mv88e6xxx_avb_disable(struct mv88e6xxx_chip *chip)
+{
+ const struct mv88e6xxx_qav_info *qav = chip->info->qav;
+ int err, port;
+
+ if (!qav)
+ return -EOPNOTSUPP;
+
+ /* Return every user port to legacy. */
+ for (port = 0; port < mv88e6xxx_num_ports(chip); port++) {
+ if (!dsa_is_user_port(chip->ds, port))
+ continue;
+
+ err = mv88e6xxx_avb_set_port_avb_mode(chip, port,
+ MV88E6XXX_PORT_AVB_CFG_AVB_MODE_LEGACY);
+ if (err)
+ return err;
+ }
+
+ err = mv88e6xxx_avb_write(chip, MV88E6XXX_AVB_CFG_AVB, qav->avb_pri_map);
+ if (err)
+ return err;
+
+ return mv88e6xxx_qav_set_iso_ptr(chip, 0);
+}
+
diff --git a/drivers/net/dsa/mv88e6xxx/avb.h b/drivers/net/dsa/mv88e6xxx/avb.h
new file mode 100644
index 0000000000000..f231824d4eac7
--- /dev/null
+++ b/drivers/net/dsa/mv88e6xxx/avb.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Marvell 88E6xxx Switch AVB support
+ *
+ * Copyright (c) 2024-2026 PADL Software Pty Ltd
+ */
+
+#ifndef _MV88E6XXX_AVB_H
+#define _MV88E6XXX_AVB_H
+
+#include "chip.h"
+
+/* Global AVB registers */
+
+/* Offset 0x00: AVB Global Config */
+
+#define MV88E6XXX_AVB_CFG_AVB 0x00
+
+#define MV88E6XXX_AVB_CFG_AVB_HI_FPRI_MASK GENMASK(14, 12)
+#define MV88E6XXX_AVB_CFG_AVB_HI_FPRI_SET(p) FIELD_PREP(MV88E6XXX_AVB_CFG_AVB_HI_FPRI_MASK, p)
+#define MV88E6XXX_AVB_CFG_AVB_HI_FPRI_GET(x) FIELD_GET(MV88E6XXX_AVB_CFG_AVB_HI_FPRI_MASK, x)
+
+#define MV88E6XXX_AVB_CFG_AVB_LO_FPRI_MASK GENMASK(6, 4)
+#define MV88E6XXX_AVB_CFG_AVB_LO_FPRI_SET(p) FIELD_PREP(MV88E6XXX_AVB_CFG_AVB_LO_FPRI_MASK, p)
+#define MV88E6XXX_AVB_CFG_AVB_LO_FPRI_GET(x) FIELD_GET(MV88E6XXX_AVB_CFG_AVB_LO_FPRI_MASK, x)
+
+#define MV88E6XXX_AVB_CFG_AVB_HI_QPRI_MASK GENMASK(10, 8)
+#define MV88E6XXX_AVB_CFG_AVB_HI_QPRI_SET(p) FIELD_PREP(MV88E6XXX_AVB_CFG_AVB_HI_QPRI_MASK, p)
+
+#define MV88E6XXX_AVB_CFG_AVB_LO_QPRI_MASK GENMASK(2, 0)
+#define MV88E6XXX_AVB_CFG_AVB_LO_QPRI_SET(p) FIELD_PREP(MV88E6XXX_AVB_CFG_AVB_LO_QPRI_MASK, p)
+
+/* Global Qav registers */
+#define MV88E6XXX_QAV_CFG 0x00
+
+#define MV88E6XXX_QAV_CFG_GLOBAL_ISO_PTR_MASK GENMASK(9, 0)
+#define MV88E6XXX_QAV_CFG_GLOBAL_ISO_PTR_GET(x) FIELD_GET(MV88E6XXX_QAV_CFG_GLOBAL_ISO_PTR_MASK, x)
+#define MV88E6XXX_QAV_CFG_GLOBAL_ISO_PTR_SET(x) FIELD_PREP(MV88E6XXX_QAV_CFG_GLOBAL_ISO_PTR_MASK, x)
+
+/* allow mgmt frames in isochronous pointer pool */
+#define MV88E6XXX_QAV_CFG_ADMIT_MGMT 0x8000
+
+/* Per-port AVB registers */
+
+/* Offset 0x00: AVB Port Config */
+#define MV88E6XXX_PORT_AVB_CFG 0x00
+#define MV88E6XXX_PORT_AVB_CFG_AVB_MODE GENMASK(15, 14)
+/* all frames legacy (non-AVB) unless overridden */
+#define MV88E6XXX_PORT_AVB_CFG_AVB_MODE_LEGACY 0x0000
+/* AVB frames indicated by priority */
+#define MV88E6XXX_PORT_AVB_CFG_AVB_MODE_STANDARD 0x4000
+/* STANDARD && ATU has STATIC_AVB_NRL bit set */
+#define MV88E6XXX_PORT_AVB_CFG_AVB_MODE_ENHANCED 0x8000
+/* ENHANCED && source port in destination port vector */
+#define MV88E6XXX_PORT_AVB_CFG_AVB_MODE_SECURE 0xc000
+
+#define MV88E6XXX_PORT_AVB_CFG_AVB_OVERRIDE 0x2000
+#define MV88E6XXX_PORT_AVB_CFG_AVB_FILTER_BAD_AVB 0x1000
+#define MV88E6XXX_PORT_AVB_CFG_AVB_TUNNEL 0x0800
+#define MV88E6XXX_PORT_AVB_CFG_AVB_DISCARD_BAD 0x0400
+
+u16 mv88e6xxx_avb_pri_map_to_reg(const struct tc_mqprio_qopt *qopt);
+int mv88e6xxx_avb_enable(struct mv88e6xxx_chip *chip,
+ struct tc_mqprio_qopt_offload *mqprio);
+int mv88e6xxx_avb_disable(struct mv88e6xxx_chip *chip);
+
+#endif /* _MV88E6XXX_AVB_H */
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index dc8f9a04ab5e5..2d619fb809c1e 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -34,6 +34,7 @@
#include <net/dsa.h>
#include <net/pkt_sched.h>
+#include "avb.h"
#include "chip.h"
#include "devlink.h"
#include "global1.h"
@@ -5708,6 +5709,10 @@ static const struct mv88e6xxx_qav_info mv88e6352_qav_info = {
.rate_mask = GENMASK(14, 0),
.hilimit_mask = GENMASK(14, 0),
.queue_mask = GENMASK(3, 0),
+ /* legacy (all queues), lo (queue 1/2), hi (queue 2/3) */
+ .avb_queue_mask = { GENMASK(3, 0), GENMASK(2, 1), GENMASK(3, 2) },
+ /* HI FPri 5/QPri 3, LO FPri 4/QPri 2 */
+ .avb_pri_map = 0x5342,
};
static const struct mv88e6xxx_qav_info mv88e6341_qav_info = {
@@ -5715,6 +5720,10 @@ static const struct mv88e6xxx_qav_info mv88e6341_qav_info = {
.rate_mask = GENMASK(15, 0),
.hilimit_mask = GENMASK(13, 0),
.queue_mask = GENMASK(3, 0),
+ /* legacy (all queues), lo (queue 1/2), hi (queue 2/3) */
+ .avb_queue_mask = { GENMASK(3, 0), GENMASK(2, 1), GENMASK(3, 2) },
+ /* HI FPri 5/QPri 3, LO FPri 4/QPri 2 */
+ .avb_pri_map = 0x5342,
};
static const struct mv88e6xxx_qav_info mv88e6390_qav_info = {
@@ -5722,6 +5731,10 @@ static const struct mv88e6xxx_qav_info mv88e6390_qav_info = {
.rate_mask = GENMASK(15, 0),
.hilimit_mask = GENMASK(13, 0),
.queue_mask = GENMASK(7, 0),
+ /* AVB traffic allowed on all queues */
+ .avb_queue_mask = { GENMASK(7, 0), GENMASK(7, 0), GENMASK(7, 0) },
+ /* HI FPri 3/QPri 7, LO FPri 2/QPri 6 */
+ .avb_pri_map = 0x3726,
};
static const struct mv88e6xxx_info mv88e6xxx_table[] = {
@@ -7244,6 +7257,197 @@ static int mv88e6xxx_crosschip_lag_leave(struct dsa_switch *ds, int sw_index,
return err_sync ? : err_pvt;
}
+static int mv88e6xxx_tc_query_caps(struct tc_query_caps_base *base)
+{
+ switch (base->type) {
+ case TC_SETUP_QDISC_MQPRIO: {
+ struct tc_mqprio_caps *caps = base->caps;
+
+ caps->validate_queue_counts = true;
+
+ return 0;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int mv88e6xxx_validate_tc_mqprio(const struct mv88e6xxx_chip *chip,
+ const struct tc_mqprio_qopt_offload *mqprio)
+{
+ const struct mv88e6xxx_qav_info *qav = chip->info->qav;
+ const struct tc_mqprio_qopt *qopt = &mqprio->qopt;
+ struct netlink_ext_ack *extack = mqprio->extack;
+ u8 avb_tc_set = 0;
+ int tc, fpri;
+
+ if (qopt->num_tc == 0)
+ return 0;
+
+ if (qopt->hw != TC_MQPRIO_HW_OFFLOAD_TCS) {
+ NL_SET_ERR_MSG_MOD(extack, "only full TC hardware offload is supported");
+ return -EOPNOTSUPP;
+ } else if (!qav || !chip->info->ops->avb_ops) {
+ NL_SET_ERR_MSG_MOD(extack, "chip does not support MQPRIO DCB offload");
+ return -EOPNOTSUPP;
+ } else if (mqprio->mode != TC_MQPRIO_MODE_DCB) {
+ NL_SET_ERR_MSG_MOD(extack, "only DCB mode is supported");
+ return -EOPNOTSUPP;
+ } else if (mqprio->shaper != TC_MQPRIO_SHAPER_DCB) {
+ NL_SET_ERR_MSG_MOD(extack, "only DCB shaper is supported for AVB mode");
+ return -EOPNOTSUPP;
+ } else if (mqprio->preemptible_tcs) {
+ NL_SET_ERR_MSG_MOD(extack, "frame preemption is not supported");
+ return -EOPNOTSUPP;
+ } else if (qopt->num_tc > MV88E6XXX_AVB_TC_MAX + 1) {
+ NL_SET_ERR_MSG_MOD(extack, "too many traffic classes for AVB mode");
+ return -EOPNOTSUPP;
+ }
+
+ /* Validate switch-side constraints on AVB traffic classes. TC0 queue
+ * mapping can only be configured on ingress using the DCB PCP app.
+ */
+ for (tc = MV88E6XXX_AVB_TC_LO; tc < qopt->num_tc; tc++) {
+ if (qopt->count[tc] != 1) {
+ NL_SET_ERR_MSG_FMT_MOD(extack, "only one queue supported for TC%d", tc);
+ return -EOPNOTSUPP;
+ } else if ((qav->avb_queue_mask[tc] & BIT(qopt->offset[tc])) == 0) {
+ NL_SET_ERR_MSG_FMT_MOD(extack, "queue %d not valid for TC%d",
+ qopt->offset[tc], tc);
+ return -EOPNOTSUPP;
+ }
+ }
+
+ /* Each AVB traffic class must map exactly one frame priority */
+ for (fpri = 0; fpri < IEEE_8021Q_MAX_PRIORITIES; fpri++) {
+ tc = qopt->prio_tc_map[fpri];
+
+ if (tc == MV88E6XXX_AVB_TC_LEGACY)
+ continue;
+
+ if (avb_tc_set & BIT(tc)) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "only one frame priority can be mapped to TC%d", tc);
+ return -EOPNOTSUPP;
+ }
+
+ avb_tc_set |= BIT(tc);
+ }
+
+ if (avb_tc_set != GENMASK(MV88E6XXX_AVB_TC_HI, MV88E6XXX_AVB_TC_LO)) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "both TC1 and TC2 must have 802.1p priorities assigned");
+ return -EOPNOTSUPP;
+ }
+
+ return qopt->num_tc;
+}
+
+static void mv88e6xxx_mqprio_update_policy(struct mv88e6xxx_tc_policy *pol,
+ int port, int num_tc)
+{
+ if (num_tc) {
+ pol->tc_port_mask |= BIT(port);
+ pol->enabled = true;
+ } else {
+ pol->tc_port_mask &= ~BIT(port);
+ if (!pol->tc_port_mask)
+ pol->enabled = false;
+ }
+}
+
+static int mv88e6xxx_mqprio_netdev_set_tc(struct net_device *user,
+ const struct tc_mqprio_qopt *qopt,
+ int num_tc)
+{
+ int err, tc;
+
+ err = netdev_set_num_tc(user, num_tc);
+ if (err)
+ return err;
+
+ for (tc = 0; tc < num_tc; tc++) {
+ err = netdev_set_tc_queue(user, tc, qopt->count[tc],
+ qopt->offset[tc]);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int mv88e6xxx_setup_tc_mqprio(struct dsa_switch *ds, int port,
+ struct tc_mqprio_qopt_offload *mqprio)
+{
+ struct netlink_ext_ack *extack = mqprio->extack;
+ struct mv88e6xxx_chip *chip = ds->priv;
+ struct mv88e6xxx_tc_policy *pol;
+ struct net_device *user;
+ bool can_update_pol;
+ u16 avb_pri_map;
+ int num_tc, err;
+
+ if (!dsa_is_user_port(ds, port))
+ return -EINVAL;
+
+ num_tc = mv88e6xxx_validate_tc_mqprio(chip, mqprio);
+ if (num_tc < 0)
+ return num_tc;
+
+ avb_pri_map = num_tc ? mv88e6xxx_avb_pri_map_to_reg(&mqprio->qopt) : 0;
+
+ user = dsa_to_port(ds, port)->user;
+
+ mv88e6xxx_reg_lock(chip);
+
+ pol = &chip->tc_policy;
+
+ if (!num_tc && !(pol->tc_port_mask & BIT(port))) {
+ netdev_reset_tc(user);
+ mv88e6xxx_reg_unlock(chip);
+ return 0;
+ }
+
+ can_update_pol = !pol->tc_port_mask || pol->tc_port_mask == BIT(port);
+ if (!can_update_pol && num_tc && avb_pri_map != pol->avb_pri_map) {
+ NL_SET_ERR_MSG_MOD(extack, "only a single priority mapping supported per switch");
+ err = -EEXIST;
+ goto err_unlock;
+ }
+
+ err = mv88e6xxx_mqprio_netdev_set_tc(user, &mqprio->qopt, num_tc);
+ if (err)
+ goto err_reset_tc;
+
+ if (can_update_pol) {
+ err = num_tc ? mv88e6xxx_avb_enable(chip, mqprio)
+ : mv88e6xxx_avb_disable(chip);
+ if (err) {
+ NL_SET_ERR_MSG_FMT_MOD(extack, "failed to %s AVB",
+ num_tc ? "enable" : "disable");
+ goto err_reset_tc;
+ }
+ }
+
+ mv88e6xxx_mqprio_update_policy(pol, port, num_tc);
+
+ if (num_tc && can_update_pol)
+ pol->avb_pri_map = avb_pri_map;
+ else if (!pol->tc_port_mask)
+ pol->avb_pri_map = 0;
+
+ mv88e6xxx_reg_unlock(chip);
+
+ return 0;
+
+err_reset_tc:
+ netdev_reset_tc(user);
+err_unlock:
+ mv88e6xxx_reg_unlock(chip);
+
+ return err;
+}
+
static int mv88e6xxx_setup_tc_cbs(struct dsa_switch *ds, int port,
struct tc_cbs_qopt_offload *cbs)
{
@@ -7335,6 +7539,10 @@ static int mv88e6xxx_port_setup_tc(struct dsa_switch *ds, int port,
enum tc_setup_type type, void *type_data)
{
switch (type) {
+ case TC_QUERY_CAPS:
+ return mv88e6xxx_tc_query_caps(type_data);
+ case TC_SETUP_QDISC_MQPRIO:
+ return mv88e6xxx_setup_tc_mqprio(ds, port, type_data);
case TC_SETUP_QDISC_CBS:
return mv88e6xxx_setup_tc_cbs(ds, port, type_data);
default:
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index d42d839e636bc..5e7c1c6bfbb95 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -8,6 +8,7 @@
#ifndef _MV88E6XXX_CHIP_H
#define _MV88E6XXX_CHIP_H
+#include <linux/dcbnl.h> /* for IEEE_8021Q_MAX_PRIORITIES */
#include <linux/idr.h>
#include <linux/if_vlan.h>
#include <linux/irq.h>
@@ -19,6 +20,7 @@
#include <linux/ptp_clock_kernel.h>
#include <linux/timecounter.h>
#include <net/dsa.h>
+#include <net/pkt_sched.h>
#define EDSA_HLEN 8
#define MV88E6XXX_N_FID 4096
@@ -252,6 +254,30 @@ struct mv88e6xxx_port_hwtstamp {
struct kernel_hwtstamp_config tstamp_config;
};
+/**
+ * enum mv88e6xxx_avb_tc - Traffic class values for AVB mode
+ * @MV88E6XXX_AVB_TC_LEGACY: Non-AVB traffic
+ * @MV88E6XXX_AVB_TC_LO: Low priority AVB (Class B)
+ * @MV88E6XXX_AVB_TC_HI: High priority AVB (Class A)
+ */
+enum mv88e6xxx_avb_tc {
+ MV88E6XXX_AVB_TC_LEGACY = 0,
+ MV88E6XXX_AVB_TC_LO = 1,
+ MV88E6XXX_AVB_TC_HI = 2,
+ MV88E6XXX_AVB_TC_MAX = MV88E6XXX_AVB_TC_HI,
+};
+
+struct mv88e6xxx_tc_policy {
+ /* AVB MQPRIO offload active on the switch */
+ bool enabled;
+
+ /* Ports with MQPRIO TC installed */
+ u16 tc_port_mask;
+
+ /* Switch-wide AVB FPri/QPri register value */
+ u16 avb_pri_map;
+};
+
enum mv88e6xxx_policy_mapping {
MV88E6XXX_POLICY_MAPPING_DA,
MV88E6XXX_POLICY_MAPPING_SA,
@@ -463,6 +489,9 @@ struct mv88e6xxx_chip {
/* TCAM entries */
struct mv88e6xxx_tcam tcam;
+ /* Global MQPRIO traffic class configuration */
+ struct mv88e6xxx_tc_policy tc_policy;
+
/* Global2 scratch register config data3 */
u8 g2_scratch_config3;
};
@@ -777,6 +806,20 @@ struct mv88e6xxx_avb_ops {
/* Access port-scoped 802.1Qav registers */
int (*port_qav_write)(struct mv88e6xxx_chip *chip, int port, int addr,
u16 data);
+
+ /* Access global Class Shaping and Pacing registers */
+ int (*qav_read)(struct mv88e6xxx_chip *chip, int addr, u16 *data,
+ int len);
+ int (*qav_write)(struct mv88e6xxx_chip *chip, int addr, u16 data);
+
+ /* Access port-scoped Audio Video Bridging registers */
+ int (*port_avb_read)(struct mv88e6xxx_chip *chip, int port, int addr,
+ u16 *data, int len);
+ int (*port_avb_write)(struct mv88e6xxx_chip *chip, int port, int addr,
+ u16 data);
+
+ /* Access global Audio Video Bridging registers */
+ int (*avb_write)(struct mv88e6xxx_chip *chip, int addr, u16 data);
};
struct mv88e6xxx_ptp_ops {
@@ -817,6 +860,8 @@ struct mv88e6xxx_qav_info {
u16 rate_mask; /* QPri Rate valid bits mask */
u16 hilimit_mask; /* QPri HiLimit bits mask*/
u8 queue_mask; /* supported queues bitmask */
+ u8 avb_queue_mask[MV88E6XXX_AVB_TC_MAX + 1]; /* AVB supported queues bitmask */
+ u16 avb_pri_map; /* default AVB FPri to QPri map */
};
static inline bool mv88e6xxx_has_stu(struct mv88e6xxx_chip *chip)
diff --git a/drivers/net/dsa/mv88e6xxx/global1.h b/drivers/net/dsa/mv88e6xxx/global1.h
index 3dbb7a1b8fe11..199a826d426f0 100644
--- a/drivers/net/dsa/mv88e6xxx/global1.h
+++ b/drivers/net/dsa/mv88e6xxx/global1.h
@@ -111,6 +111,7 @@
/* Offset 0x0A: ATU Control Register */
#define MV88E6XXX_G1_ATU_CTL 0x0a
+#define MV88E6XXX_G1_ATU_CTL_MAC_AVB 0x8000
#define MV88E6XXX_G1_ATU_CTL_LEARN2ALL 0x0008
#define MV88E6161_G1_ATU_CTL_HASH_MASK 0x0003
diff --git a/drivers/net/dsa/mv88e6xxx/global2.h b/drivers/net/dsa/mv88e6xxx/global2.h
index df52ff3adc88c..ae361cca51274 100644
--- a/drivers/net/dsa/mv88e6xxx/global2.h
+++ b/drivers/net/dsa/mv88e6xxx/global2.h
@@ -176,9 +176,11 @@
#define MV88E6352_G2_AVB_CMD_PORT_TAIGLOBAL 0xe
#define MV88E6165_G2_AVB_CMD_PORT_PTPGLOBAL 0xf
#define MV88E6352_G2_AVB_CMD_PORT_PTPGLOBAL 0xf
+#define MV88E6352_G2_AVB_CMD_PORT_AVBGLOBAL 0xf
#define MV88E6390_G2_AVB_CMD_PORT_MASK 0x1f00
#define MV88E6390_G2_AVB_CMD_PORT_TAIGLOBAL 0x1e
#define MV88E6390_G2_AVB_CMD_PORT_PTPGLOBAL 0x1f
+#define MV88E6390_G2_AVB_CMD_PORT_AVBGLOBAL 0x1f
#define MV88E6352_G2_AVB_CMD_BLOCK_PTP 0
#define MV88E6352_G2_AVB_CMD_BLOCK_AVB 1
#define MV88E6352_G2_AVB_CMD_BLOCK_QAV 2
diff --git a/drivers/net/dsa/mv88e6xxx/global2_avb.c b/drivers/net/dsa/mv88e6xxx/global2_avb.c
index 6b54e275d21ab..fe1607bc78734 100644
--- a/drivers/net/dsa/mv88e6xxx/global2_avb.c
+++ b/drivers/net/dsa/mv88e6xxx/global2_avb.c
@@ -119,6 +119,27 @@ static int mv88e6352_g2_avb_port_qav_write(struct mv88e6xxx_chip *chip,
return mv88e6xxx_g2_avb_write(chip, writeop, data);
}
+static int mv88e6352_g2_avb_port_avb_read(struct mv88e6xxx_chip *chip,
+ int port, int addr, u16 *data,
+ int len)
+{
+ u16 readop = (len == 1 ? MV88E6352_G2_AVB_CMD_OP_READ :
+ MV88E6352_G2_AVB_CMD_OP_READ_INCR) |
+ (port << 8) | (MV88E6352_G2_AVB_CMD_BLOCK_AVB << 5) |
+ addr;
+
+ return mv88e6xxx_g2_avb_read(chip, readop, data, len);
+}
+
+static int mv88e6352_g2_avb_port_avb_write(struct mv88e6xxx_chip *chip,
+ int port, int addr, u16 data)
+{
+ u16 writeop = MV88E6352_G2_AVB_CMD_OP_WRITE | (port << 8) |
+ (MV88E6352_G2_AVB_CMD_BLOCK_AVB << 5) | addr;
+
+ return mv88e6xxx_g2_avb_write(chip, writeop, data);
+}
+
static int mv88e6352_g2_avb_ptp_read(struct mv88e6xxx_chip *chip, int addr,
u16 *data, int len)
{
@@ -151,6 +172,38 @@ static int mv88e6352_g2_avb_tai_write(struct mv88e6xxx_chip *chip, int addr,
addr, data);
}
+static int mv88e6352_g2_avb_qav_read(struct mv88e6xxx_chip *chip, int addr,
+ u16 *data, int len)
+{
+ u16 readop = (len == 1 ? MV88E6352_G2_AVB_CMD_OP_READ :
+ MV88E6352_G2_AVB_CMD_OP_READ_INCR) |
+ (MV88E6352_G2_AVB_CMD_PORT_AVBGLOBAL << 8) |
+ (MV88E6352_G2_AVB_CMD_BLOCK_QAV << 5) |
+ addr;
+
+ return mv88e6xxx_g2_avb_read(chip, readop, data, len);
+}
+
+static int mv88e6352_g2_avb_qav_write(struct mv88e6xxx_chip *chip, int addr,
+ u16 data)
+{
+ u16 writeop = MV88E6352_G2_AVB_CMD_OP_WRITE |
+ (MV88E6352_G2_AVB_CMD_PORT_AVBGLOBAL << 8) |
+ (MV88E6352_G2_AVB_CMD_BLOCK_QAV << 5) | addr;
+
+ return mv88e6xxx_g2_avb_write(chip, writeop, data);
+}
+
+static int mv88e6352_g2_avb_avb_write(struct mv88e6xxx_chip *chip, int addr,
+ u16 data)
+{
+ u16 writeop = MV88E6352_G2_AVB_CMD_OP_WRITE |
+ (MV88E6352_G2_AVB_CMD_PORT_AVBGLOBAL << 8) |
+ (MV88E6352_G2_AVB_CMD_BLOCK_AVB << 5) | addr;
+
+ return mv88e6xxx_g2_avb_write(chip, writeop, data);
+}
+
const struct mv88e6xxx_avb_ops mv88e6352_avb_ops = {
.port_ptp_read = mv88e6352_g2_avb_port_ptp_read,
.port_ptp_write = mv88e6352_g2_avb_port_ptp_write,
@@ -159,6 +212,11 @@ const struct mv88e6xxx_avb_ops mv88e6352_avb_ops = {
.tai_read = mv88e6352_g2_avb_tai_read,
.tai_write = mv88e6352_g2_avb_tai_write,
.port_qav_write = mv88e6352_g2_avb_port_qav_write,
+ .qav_read = mv88e6352_g2_avb_qav_read,
+ .qav_write = mv88e6352_g2_avb_qav_write,
+ .port_avb_read = mv88e6352_g2_avb_port_avb_read,
+ .port_avb_write = mv88e6352_g2_avb_port_avb_write,
+ .avb_write = mv88e6352_g2_avb_avb_write,
};
static int mv88e6165_g2_avb_tai_read(struct mv88e6xxx_chip *chip, int addr,
@@ -185,6 +243,11 @@ const struct mv88e6xxx_avb_ops mv88e6165_avb_ops = {
.tai_read = mv88e6165_g2_avb_tai_read,
.tai_write = mv88e6165_g2_avb_tai_write,
.port_qav_write = mv88e6352_g2_avb_port_qav_write,
+ .qav_read = mv88e6352_g2_avb_qav_read,
+ .qav_write = mv88e6352_g2_avb_qav_write,
+ .port_avb_read = mv88e6352_g2_avb_port_avb_read,
+ .port_avb_write = mv88e6352_g2_avb_port_avb_write,
+ .avb_write = mv88e6352_g2_avb_avb_write,
};
static int mv88e6390_g2_avb_port_ptp_read(struct mv88e6xxx_chip *chip,
@@ -217,6 +280,27 @@ static int mv88e6390_g2_avb_port_qav_write(struct mv88e6xxx_chip *chip,
return mv88e6xxx_g2_avb_write(chip, writeop, data);
}
+static int mv88e6390_g2_avb_port_avb_read(struct mv88e6xxx_chip *chip,
+ int port, int addr, u16 *data,
+ int len)
+{
+ u16 readop = (len == 1 ? MV88E6390_G2_AVB_CMD_OP_READ :
+ MV88E6390_G2_AVB_CMD_OP_READ_INCR) |
+ (port << 8) | (MV88E6352_G2_AVB_CMD_BLOCK_AVB << 5) |
+ addr;
+
+ return mv88e6xxx_g2_avb_read(chip, readop, data, len);
+}
+
+static int mv88e6390_g2_avb_port_avb_write(struct mv88e6xxx_chip *chip,
+ int port, int addr, u16 data)
+{
+ u16 writeop = MV88E6390_G2_AVB_CMD_OP_WRITE | (port << 8) |
+ (MV88E6352_G2_AVB_CMD_BLOCK_AVB << 5) | addr;
+
+ return mv88e6xxx_g2_avb_write(chip, writeop, data);
+}
+
static int mv88e6390_g2_avb_ptp_read(struct mv88e6xxx_chip *chip, int addr,
u16 *data, int len)
{
@@ -249,6 +333,38 @@ static int mv88e6390_g2_avb_tai_write(struct mv88e6xxx_chip *chip, int addr,
addr, data);
}
+static int mv88e6390_g2_avb_qav_read(struct mv88e6xxx_chip *chip, int addr,
+ u16 *data, int len)
+{
+ u16 readop = (len == 1 ? MV88E6390_G2_AVB_CMD_OP_READ :
+ MV88E6390_G2_AVB_CMD_OP_READ_INCR) |
+ (MV88E6390_G2_AVB_CMD_PORT_AVBGLOBAL << 8) |
+ (MV88E6352_G2_AVB_CMD_BLOCK_QAV << 5) |
+ addr;
+
+ return mv88e6xxx_g2_avb_read(chip, readop, data, len);
+}
+
+static int mv88e6390_g2_avb_qav_write(struct mv88e6xxx_chip *chip, int addr,
+ u16 data)
+{
+ u16 writeop = MV88E6390_G2_AVB_CMD_OP_WRITE |
+ (MV88E6390_G2_AVB_CMD_PORT_AVBGLOBAL << 8) |
+ (MV88E6352_G2_AVB_CMD_BLOCK_QAV << 5) | addr;
+
+ return mv88e6xxx_g2_avb_write(chip, writeop, data);
+}
+
+static int mv88e6390_g2_avb_avb_write(struct mv88e6xxx_chip *chip, int addr,
+ u16 data)
+{
+ u16 writeop = MV88E6390_G2_AVB_CMD_OP_WRITE |
+ (MV88E6390_G2_AVB_CMD_PORT_AVBGLOBAL << 8) |
+ (MV88E6352_G2_AVB_CMD_BLOCK_AVB << 5) | addr;
+
+ return mv88e6xxx_g2_avb_write(chip, writeop, data);
+}
+
const struct mv88e6xxx_avb_ops mv88e6390_avb_ops = {
.port_ptp_read = mv88e6390_g2_avb_port_ptp_read,
.port_ptp_write = mv88e6390_g2_avb_port_ptp_write,
@@ -257,4 +373,9 @@ const struct mv88e6xxx_avb_ops mv88e6390_avb_ops = {
.tai_read = mv88e6390_g2_avb_tai_read,
.tai_write = mv88e6390_g2_avb_tai_write,
.port_qav_write = mv88e6390_g2_avb_port_qav_write,
+ .qav_read = mv88e6390_g2_avb_qav_read,
+ .qav_write = mv88e6390_g2_avb_qav_write,
+ .port_avb_read = mv88e6390_g2_avb_port_avb_read,
+ .port_avb_write = mv88e6390_g2_avb_port_avb_write,
+ .avb_write = mv88e6390_g2_avb_avb_write,
};
---
base-commit: 3abbe30231441f1fbb3305e9854c56a34650af53
change-id: 20260602-mv88e6xxx-8021qat-mqprio-46fc466d70e1
prerequisite-change-id: 20260430-net-next-mv88e6xxx-cbs-2121169caa68:v7
prerequisite-patch-id: 8ad59c43368d4639e0cabcc59a7f6e487560d3f7
prerequisite-patch-id: 22f1cda311b1826bd4151fc7d4cdf3d87ca519ed
prerequisite-change-id: 20260603-net-next-mv88e6xxx-pcp-prio-5752caa7926b:v1
prerequisite-patch-id: f324a0334ef91256d6f09562d978e9d6c32b7f3c
prerequisite-patch-id: 79c2b056ae68846b5e2950b9738cb83e73d2710e
Best regards,
--
Luke Howard <lukeh@padl.com>
^ permalink raw reply related
* [PATCH net-next 2/2] net: dsa: mv88e6xxx: use direct ATU hash on 88E6141/6341
From: Luke Howard @ 2026-07-03 6:42 UTC (permalink / raw)
To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn
Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
Luke Howard
In-Reply-To: <20260703-mv88e6x41-fixes-v1-0-fbd3a1bf8965@padl.com>
The default 88E6341/88E6141 ATU hash algorithm appears to result
in frequent collisions, evicting permanent registrations (including
the broadcast address) out of the ATU.
This workaround disables hasing in the ATU control register. It may
have a performance impact (the data sheet notes this is for testing
only), but it also enables correctness, at least in local testing.
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Luke Howard <lukeh@padl.com>
---
drivers/net/dsa/mv88e6xxx/chip.c | 12 ++++++++++++
drivers/net/dsa/mv88e6xxx/global1.h | 6 +++++-
2 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 60d46680fb50e..53ed2b9e30979 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -1699,6 +1699,18 @@ static int mv88e6xxx_atu_setup(struct mv88e6xxx_chip *chip)
{
int err;
+ /* Avoid collisions on the 6341 family by disabling hashing. */
+ if (chip->info->family == MV88E6XXX_FAMILY_6341 &&
+ chip->info->ops->atu_set_hash) {
+ u8 hash = MV88E6161_G1_ATU_CTL_HASH_DIRECT;
+
+ err = chip->info->ops->atu_set_hash(chip, hash);
+ if (err)
+ return err;
+
+ dev_info(chip->dev, "ATU hash mode set to 0x%x\n", hash);
+ }
+
err = mv88e6xxx_g1_atu_flush(chip, 0, true);
if (err)
return err;
diff --git a/drivers/net/dsa/mv88e6xxx/global1.h b/drivers/net/dsa/mv88e6xxx/global1.h
index 3dbb7a1b8fe11..e2a54eac64832 100644
--- a/drivers/net/dsa/mv88e6xxx/global1.h
+++ b/drivers/net/dsa/mv88e6xxx/global1.h
@@ -112,7 +112,11 @@
/* Offset 0x0A: ATU Control Register */
#define MV88E6XXX_G1_ATU_CTL 0x0a
#define MV88E6XXX_G1_ATU_CTL_LEARN2ALL 0x0008
-#define MV88E6161_G1_ATU_CTL_HASH_MASK 0x0003
+#define MV88E6161_G1_ATU_CTL_HASH_MASK 0x0003
+#define MV88E6161_G1_ATU_CTL_HASH_RESERVED_0 0x0000
+#define MV88E6161_G1_ATU_CTL_HASH_DEFAULT 0x0001
+#define MV88E6161_G1_ATU_CTL_HASH_RESERVED_1 0x0002
+#define MV88E6161_G1_ATU_CTL_HASH_DIRECT 0x0003
/* Offset 0x0B: ATU Operation Register */
#define MV88E6XXX_G1_ATU_OP 0x0b
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 1/2] net: dsa: mv88e6xxx: write the ATU FID register on 88E6141/88E6341
From: Luke Howard @ 2026-07-03 6:42 UTC (permalink / raw)
To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn
Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
Luke Howard
In-Reply-To: <20260703-mv88e6x41-fixes-v1-0-fbd3a1bf8965@padl.com>
The existing code assumed the 88E6141/88E6341 did not have a dedicated
ATU FID register because of its database count (256), instead taking
the legacy path which resulted in the FID register never being set.
This resulted in every FDB entry being loaded into FID 0, breaking
VLAN aware bridging.
Fixes: a75961d0ebfd ("net: dsa: mv88e6xxx: Add support for ethernet switch 88E6341")
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Luke Howard <lukeh@padl.com>
---
drivers/net/dsa/mv88e6xxx/chip.c | 2 ++
drivers/net/dsa/mv88e6xxx/chip.h | 1 +
drivers/net/dsa/mv88e6xxx/global1_atu.c | 4 ++--
3 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 80b877c74513d..60d46680fb50e 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -5863,6 +5863,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
.family = MV88E6XXX_FAMILY_6341,
.name = "Marvell 88E6141",
.num_databases = 256,
+ .atu_fid_reg = true,
.num_macs = 2048,
.num_ports = 6,
.num_internal_phys = 5,
@@ -6350,6 +6351,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
.family = MV88E6XXX_FAMILY_6341,
.name = "Marvell 88E6341",
.num_databases = 256,
+ .atu_fid_reg = true,
.num_macs = 2048,
.num_internal_phys = 5,
.num_ports = 6,
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index e966e7c4cc5de..9f23d2a7165f0 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -131,6 +131,7 @@ struct mv88e6xxx_info {
u16 prod_num;
const char *name;
unsigned int num_databases;
+ bool atu_fid_reg;
unsigned int num_macs;
unsigned int num_ports;
unsigned int num_internal_phys;
diff --git a/drivers/net/dsa/mv88e6xxx/global1_atu.c b/drivers/net/dsa/mv88e6xxx/global1_atu.c
index c47f068f56b32..aa5adc78607ca 100644
--- a/drivers/net/dsa/mv88e6xxx/global1_atu.c
+++ b/drivers/net/dsa/mv88e6xxx/global1_atu.c
@@ -135,7 +135,7 @@ static int mv88e6xxx_g1_atu_op(struct mv88e6xxx_chip *chip, u16 fid, u16 op)
int err;
/* FID bits are dispatched all around gradually as more are supported */
- if (mv88e6xxx_num_databases(chip) > 256) {
+ if (mv88e6xxx_num_databases(chip) > 256 || chip->info->atu_fid_reg) {
err = mv88e6xxx_g1_atu_fid_write(chip, fid);
if (err)
return err;
@@ -179,7 +179,7 @@ static int mv88e6xxx_g1_atu_fid_read(struct mv88e6xxx_chip *chip, u16 *fid)
u16 val = 0, upper = 0, op = 0;
int err = -EOPNOTSUPP;
- if (mv88e6xxx_num_databases(chip) > 256) {
+ if (mv88e6xxx_num_databases(chip) > 256 || chip->info->atu_fid_reg) {
err = mv88e6xxx_g1_read(chip, MV88E6352_G1_ATU_FID, &val);
val &= 0xfff;
if (err)
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 0/2] net: dsa: mv88e6xxx: 6141/6341 workarounds
From: Luke Howard @ 2026-07-03 6:42 UTC (permalink / raw)
To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn
Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
Luke Howard
This patch series addresses two issues I found with the 88E6341
switch, which likely also apply to the 88E6141 (which is the same
chip without AVB/TSN support).
The first was a genuine bug, which assumed the chip did not have
a dedicated ATU FID register (it does).
The other is an issue I noticed with hash collisions in the ATU.
I would appreciate some external testing before this commit is
reviewed, but I am including it as it was necessary for reliable
operation with multiple FIDs.
Signed-off-by: Luke Howard <lukeh@padl.com>
---
Luke Howard (2):
net: dsa: mv88e6xxx: write the ATU FID register on 88E6141/88E6341
net: dsa: mv88e6xxx: use direct ATU hash on 88E6141/6341
drivers/net/dsa/mv88e6xxx/chip.c | 14 ++++++++++++++
drivers/net/dsa/mv88e6xxx/chip.h | 1 +
drivers/net/dsa/mv88e6xxx/global1.h | 6 +++++-
drivers/net/dsa/mv88e6xxx/global1_atu.c | 4 ++--
4 files changed, 22 insertions(+), 3 deletions(-)
---
base-commit: 1c664ec4b9ea827b609d296921ed5bad8a40a158
change-id: 20260624-mv88e6x41-fixes-d4c84f955ebc
Best regards,
--
Luke Howard <lukeh@padl.com>
^ permalink raw reply
* [PATCH net-next 2/2] net: dsa: mv88e6xxx: embedded PTP timestamp support
From: Luke Howard @ 2026-07-03 6:41 UTC (permalink / raw)
To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn,
Richard Cochran
Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
Luke Howard
In-Reply-To: <20260703-mv88e6xxx-ptp-fixes-v1-0-0138581889a9@padl.com>
mv88e6xxx switches can support embedding PTP timestamps directly
in the frame, either as a trailer or at a configurable offset
(typically the reserved bytes in the PTP header).
Add support for this on the 88E6341 and 88E6352 switches, being
those on which I was able to verify this. Other switch chips may
also work. The arrival timestamp offsets are relative to the PTP
common header and will work for both L2 and L3 PTP packets; the
respective headers are skipped by the number of bytes set in the
ETJump and IPJump registers, which are initialized to sensible
defaults and are VLAN tag-aware.
(Note: the 6352 datasheet incorrectly states that ETJump and
IPJump are initialized to zero. They are initialized to 12 and
2 respectively; this is corrected in the 6341 data sheet.)
Signed-off-by: Luke Howard <lukeh@padl.com>
---
drivers/net/dsa/mv88e6xxx/chip.c | 3 +
drivers/net/dsa/mv88e6xxx/chip.h | 8 +++
drivers/net/dsa/mv88e6xxx/hwtstamp.c | 122 +++++++++++++++++++++++++++++++----
drivers/net/dsa/mv88e6xxx/hwtstamp.h | 14 ++++
4 files changed, 135 insertions(+), 12 deletions(-)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 80b877c74513d..c3277c1f3d785 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -28,6 +28,7 @@
#include <linux/of_mdio.h>
#include <linux/platform_data/mv88e6xxx.h>
#include <linux/property.h>
+#include <linux/ptp_classify.h>
#include <linux/netdevice.h>
#include <linux/gpio/consumer.h>
#include <linux/phylink.h>
@@ -6370,6 +6371,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
.edsa_support = MV88E6XXX_EDSA_SUPPORTED,
.ptp_support = true,
.ops = &mv88e6341_ops,
+ .arr_ts_mode = offsetof(struct ptp_header, reserved2),
},
[MV88E6350] = {
@@ -6447,6 +6449,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
.edsa_support = MV88E6XXX_EDSA_SUPPORTED,
.ptp_support = true,
.ops = &mv88e6352_ops,
+ .arr_ts_mode = offsetof(struct ptp_header, reserved2),
},
[MV88E6361] = {
.prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6361,
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index e966e7c4cc5de..b6a90eba81c43 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -180,6 +180,14 @@ struct mv88e6xxx_info {
* port 0, 1 means internal PHYs range starts at port 1, etc
*/
unsigned int internal_phys_offset;
+
+ /* Arrival Time Stamp Mode (ArrTSMode); see the ArrTSMode encoding in
+ * hwtstamp.h. Zero (the default) leaves arrival time stamps in the
+ * switch registers; non-zero embeds them in the frame, either appended
+ * as a trailer or overwritten at that byte offset past the start of the
+ * PTP common header.
+ */
+ unsigned int arr_ts_mode;
};
struct mv88e6xxx_atu_entry {
diff --git a/drivers/net/dsa/mv88e6xxx/hwtstamp.c b/drivers/net/dsa/mv88e6xxx/hwtstamp.c
index 57ff77496864f..341a9e33021d7 100644
--- a/drivers/net/dsa/mv88e6xxx/hwtstamp.c
+++ b/drivers/net/dsa/mv88e6xxx/hwtstamp.c
@@ -8,6 +8,8 @@
* Erik Hons <erik.hons@ni.com>
* Brandon Streiff <brandon.streiff@ni.com>
* Dane Wagner <dane.wagner@ni.com>
+ *
+ * Copyright (c) 2025 PADL Software Pty Ltd
*/
#include "chip.h"
@@ -15,6 +17,7 @@
#include "hwtstamp.h"
#include "ptp.h"
#include <linux/ptp_classify.h>
+#include <linux/unaligned.h>
#define SKB_PTP_TYPE(__skb) (*(unsigned int *)((__skb)->cb))
@@ -245,6 +248,63 @@ static int seq_match(struct sk_buff *skb, u16 ts_seqid)
return ts_seqid == ntohs(hdr->sequence_id);
}
+static bool parse_embedded_ts(unsigned int arr_ts_mode,
+ struct sk_buff *skb, u64 *ns)
+{
+ struct ptp_header *hdr;
+
+ *ns = 0;
+
+ /* APPEND means the switch appended the time stamp as a 4-byte trailer
+ * (not all switches support this). Any other non-zero value is the byte
+ * offset past the start of the PTP common header at which the switch
+ * overwrote the time stamp in place (e.g. the reserved header bytes).
+ */
+ if (arr_ts_mode == MV88E6XXX_PTP_ARR_TS_MODE_APPEND && skb->len >= 4) {
+ *ns = (u64)get_unaligned_be32(&skb->data[skb->len - 4]);
+ skb_trim(skb, skb->len - 4);
+ } else if (arr_ts_mode + 4 <= sizeof(*hdr)) {
+ hdr = ptp_parse_header(skb, SKB_PTP_TYPE(skb));
+ if (!hdr)
+ return false;
+
+ *ns = (u64)get_unaligned_be32((u8 *)hdr + arr_ts_mode);
+ memset((u8 *)hdr + arr_ts_mode, 0, 4);
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+static void mv88e6xxx_get_rxts_embedded(struct mv88e6xxx_chip *chip,
+ struct mv88e6xxx_port_hwtstamp *ps,
+ struct sk_buff *skb)
+{
+ struct sk_buff_head *rxq = &ps->rx_queue;
+ struct skb_shared_hwtstamps *shwt;
+ struct sk_buff_head received;
+ unsigned long flags;
+ u64 ns;
+
+ __skb_queue_head_init(&received);
+ spin_lock_irqsave(&rxq->lock, flags);
+ skb_queue_splice_tail_init(rxq, &received);
+ spin_unlock_irqrestore(&rxq->lock, flags);
+
+ for ( ; skb; skb = __skb_dequeue(&received)) {
+ if (parse_embedded_ts(chip->info->arr_ts_mode, skb, &ns)) {
+ mv88e6xxx_reg_lock(chip);
+ ns = timecounter_cyc2time(&chip->tstamp_tc, ns);
+ mv88e6xxx_reg_unlock(chip);
+ shwt = skb_hwtstamps(skb);
+ memset(shwt, 0, sizeof(*shwt));
+ shwt->hwtstamp = ns_to_ktime(ns);
+ }
+ netif_rx(skb);
+ }
+}
+
static void mv88e6xxx_get_rxts(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_port_hwtstamp *ps,
struct sk_buff *skb, u16 reg,
@@ -307,8 +367,21 @@ static void mv88e6xxx_rxtstamp_work(struct mv88e6xxx_chip *chip,
const struct mv88e6xxx_ptp_ops *ptp_ops = chip->info->ops->ptp_ops;
struct sk_buff *skb;
- skb = skb_dequeue(&ps->rx_queue);
+ if (chip->info->arr_ts_mode) {
+ /* If arr_ts_mode is set, the timestamps are embedded in the
+ * frames so a register read is not required. We still need a
+ * work queue rather than processing inline because
+ * timecounter_cyc2time() takes the global mutex and this
+ * cannot be called from mv88e6xxx_port_rxtstamp().
+ */
+ skb = skb_dequeue(&ps->rx_queue);
+ if (skb)
+ mv88e6xxx_get_rxts_embedded(chip, ps, skb);
+ return;
+ }
+
+ skb = skb_dequeue(&ps->rx_queue);
if (skb)
mv88e6xxx_get_rxts(chip, ps, skb, ptp_ops->arr0_sts_reg,
&ps->rx_queue);
@@ -350,7 +423,7 @@ bool mv88e6xxx_port_rxtstamp(struct dsa_switch *ds, int port,
SKB_PTP_TYPE(skb) = type;
- if (is_pdelay_msg(hdr))
+ if (!chip->info->arr_ts_mode && is_pdelay_msg(hdr))
skb_queue_tail(&ps->rx_queue2, skb);
else
skb_queue_tail(&ps->rx_queue, skb);
@@ -530,14 +603,37 @@ int mv88e6165_global_enable(struct mv88e6xxx_chip *chip)
int mv88e6352_hwtstamp_port_disable(struct mv88e6xxx_chip *chip, int port)
{
- return mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG0,
- MV88E6XXX_PORT_PTP_CFG0_DISABLE_PTP);
+ int err;
+
+ err = mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG0,
+ MV88E6XXX_PORT_PTP_CFG0_DISABLE_PTP);
+ if (err)
+ return err;
+
+ err = mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG2, 0);
+ if (err)
+ return err;
+
+ return 0;
}
int mv88e6352_hwtstamp_port_enable(struct mv88e6xxx_chip *chip, int port)
{
- return mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG0,
- MV88E6XXX_PORT_PTP_CFG0_DISABLE_TSPEC_MATCH);
+ int err;
+
+ if (chip->info->arr_ts_mode) {
+ err = mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG2,
+ chip->info->arr_ts_mode << 8);
+ if (err)
+ return err;
+ }
+
+ err = mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG0,
+ MV88E6XXX_PORT_PTP_CFG0_DISABLE_TSPEC_MATCH);
+ if (err)
+ return err;
+
+ return 0;
}
static int mv88e6xxx_hwtstamp_port_setup(struct mv88e6xxx_chip *chip, int port)
@@ -591,12 +687,14 @@ int mv88e6xxx_hwtstamp_setup(struct mv88e6xxx_chip *chip)
if (err)
return err;
- /* Use ARRIVAL1 for peer delay messages. */
- err = mv88e6xxx_ptp_write(chip, MV88E6XXX_PTP_TS_ARRIVAL_PTR,
- MV88E6XXX_PTP_MSGTYPE_PDLAY_REQ |
- MV88E6XXX_PTP_MSGTYPE_PDLAY_RES);
- if (err)
- return err;
+ if (!chip->info->arr_ts_mode) {
+ /* Use ARRIVAL1 for peer delay messages. */
+ err = mv88e6xxx_ptp_write(chip, MV88E6XXX_PTP_TS_ARRIVAL_PTR,
+ MV88E6XXX_PTP_MSGTYPE_PDLAY_REQ |
+ MV88E6XXX_PTP_MSGTYPE_PDLAY_RES);
+ if (err)
+ return err;
+ }
/* 88E6341 devices default to timestamping at the PHY, but this has
* a hardware issue that results in unreliable timestamps. Force
diff --git a/drivers/net/dsa/mv88e6xxx/hwtstamp.h b/drivers/net/dsa/mv88e6xxx/hwtstamp.h
index c359821d5a6ea..c25f53923e768 100644
--- a/drivers/net/dsa/mv88e6xxx/hwtstamp.h
+++ b/drivers/net/dsa/mv88e6xxx/hwtstamp.h
@@ -68,6 +68,20 @@
#define MV88E6XXX_PORT_PTP_CFG2_DEP_IRQ_EN 0x0002
#define MV88E6XXX_PORT_PTP_CFG2_ARR_IRQ_EN 0x0001
+/* Arrival Time Stamp Mode (ArrTSMode), CFG2 bits [15:8]: configures how the
+ * switch embeds the arrival time stamp (PTPArr0Time) into enabled PTP event
+ * frames.
+ * 0x00 frame modification disabled (time stamp read from registers)
+ * 0x01 append the 4-byte time stamp at the end of the frame,
+ * growing the frame by four bytes
+ * 0x04..0xEF overwrite the 4-byte time stamp in place, that many bytes past
+ * the start of the PTP common header, without growing the frame
+ * (offsetof(struct ptp_header, reserved2) targets the reserved
+ * bytes of the header)
+ * others reserved
+ */
+#define MV88E6XXX_PTP_ARR_TS_MODE_APPEND 0x01
+
/* Offset 0x03: PTP LED Configuration */
#define MV88E6XXX_PORT_PTP_LED_CFG 0x03
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v2 2/2] bonding: reuse neigh_setup from slave neigh_parms
From: Kuniyuki Iwashima @ 2026-07-03 6:38 UTC (permalink / raw)
To: paritoshpotukuchi
Cc: andrew+netdev, davem, edumazet, jv, kuba, linux-kernel, netdev,
pabeni, paritosh.potukuchi
In-Reply-To: <20260701081602.3185086-3-paritosh.potukuchi@amd.com>
From: Paritosh Potukuchi <paritoshpotukuchi@gmail.com>
Date: Wed, 1 Jul 2026 08:16:00 +0000
> bond_neigh_init() currently relies on the slave device's
> ndo_neigh_setup() callback to obtain a neigh_setup() handler.
>
> When an initialized neigh_parms instance already exists for the
> slave device, reuse the neigh_setup() callback stored in it instead
> of invoking ndo_neigh_setup() again.
>
> If no neigh_parms instance is found, or no neigh_setup() callback is
> present, retain the existing ndo_neigh_setup() fallback path.
>
> This avoids unnecessary ndo_neigh_setup() invocations while preserving
> existing behaviour.
>
> Signed-off-by: Paritosh Potukuchi <paritosh.potukuchi@amd.com>
> ---
> drivers/net/bonding/bond_main.c | 10 +++++++++-
> 1 file changed, 9 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c
> index e044fc733b8c..d2e4dae4e97c 100644
> --- a/drivers/net/bonding/bond_main.c
> +++ b/drivers/net/bonding/bond_main.c
> @@ -4719,7 +4719,7 @@ static int bond_neigh_init(struct neighbour *n)
> {
> struct bonding *bond = netdev_priv(n->dev);
> const struct net_device_ops *slave_ops;
> - struct neigh_parms parms;
> + struct neigh_parms parms, *p;
> struct slave *slave;
> int ret = 0;
>
> @@ -4727,6 +4727,14 @@ static int bond_neigh_init(struct neighbour *n)
> slave = bond_first_slave_rcu(bond);
> if (!slave)
> goto out;
> +
> + p = neigh_parms_lookup_dev(n->tbl, slave->dev);
This introduces O(n) list traversal while it can be done
with fixed costs (3 dereferences + 1 call).
Since neigh_table is global (arp_tbl or nd_tbl), the O(n)
list traversal could take longer and rather de-optimise.
> +
> + if (p && p->neigh_setup) {
> + ret = p->neigh_setup(n);
> + goto out;
> + }
> +
> slave_ops = slave->dev->netdev_ops;
> if (!slave_ops->ndo_neigh_setup)
> goto out;
> --
> 2.43.0
^ permalink raw reply
* Re: [PATCH net v2] octeontx2-pf: fix SQB pointer leak on init failure
From: patchwork-bot+netdevbpf @ 2026-07-03 6:40 UTC (permalink / raw)
To: Dawei Feng
Cc: sgoutham, rkannoth, gakula, sbhatta, hkelam, bbhushan2,
andrew+netdev, davem, edumazet, kuba, pabeni, jbrandeb,
richardcochran, amakarov, netdev, linux-kernel, stable,
jianhao.xu, zilin
In-Reply-To: <20260630071625.349996-1-dawei.feng@seu.edu.cn>
Hello:
This patch was applied to netdev/net.git (main)
by Paolo Abeni <pabeni@redhat.com>:
On Tue, 30 Jun 2026 15:16:25 +0800 you wrote:
> otx2_init_hw_resources() initializes SQ aura and pool resources before
> several later setup steps. On failure, err_free_sq_ptrs only frees SQB
> pages, leaving the per-SQ sqb_ptrs arrays behind.
>
> Use otx2_free_sq_res() for the SQ unwind path and let it free sqb_ptrs
> even when sq->sqe has not been allocated yet.
>
> [...]
Here is the summary with links:
- [net,v2] octeontx2-pf: fix SQB pointer leak on init failure
https://git.kernel.org/netdev/net/c/62e7df6d042a
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* [PATCH net-next] net: dsa: microchip: enable SGMII port for KSZ9897
From: Tapio Reijonen @ 2026-07-03 6:37 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Russell King
Cc: netdev, linux-kernel, Tapio Reijonen
The KSZ9897 has an SGMII/1000BASE-X capable port 7, but its chip_info
entry never set .sgmii_port. As a result ksz_has_sgmii_port() is false,
ksz9477_pcs_create() is never called, and ksz9477_phylink_mac_select_pcs()
returns NULL for port 7, so no PCS is ever instantiated and a
phy-mode = "sgmii" or "1000base-x" link on that port cannot come up.
The KSZ9897 shares the KSZ9477 register layout and operations
(ksz9477_dev_ops, ksz9477_regs) and reaches its port-7 PCS through the
same SPI-indirect XPCS register window. The register-identical LAN9646
already enables this via .sgmii_port = 7. Set the same for the KSZ9897
so the XPCS instance is created and 1000BASE-X / SGMII links on port 7
negotiate correctly.
Tested on a KSZ9897 with a 1000BASE-X fibre SFP wired directly to
port 7: the link negotiates at 1Gbps/full duplex and passes traffic.
Signed-off-by: Tapio Reijonen <tapio.reijonen@vaisala.com>
---
drivers/net/dsa/microchip/ksz_common.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c
index d1726778bb488e424aed450fb02c8fe4b332a1f4..718c72206ab451cb4c5e88fc91a5eae4892518b7 100644
--- a/drivers/net/dsa/microchip/ksz_common.c
+++ b/drivers/net/dsa/microchip/ksz_common.c
@@ -1591,6 +1591,7 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.internal_phy = {true, true, true, true,
true, false, false},
.gbit_capable = {true, true, true, true, true, true, true},
+ .sgmii_port = 7,
},
[KSZ9893] = {
---
base-commit: 322008f87f917e2217eeac386a9410945092eb2e
change-id: 20260703-ksz9897-sgmii-port-48ddb100e015
Best regards,
--
Tapio Reijonen <tapio.reijonen@vaisala.com>
^ permalink raw reply related
* Re: [PATCH net v2] net: usb: net1080: validate packet_len before pad-byte access in rx_fixup
From: patchwork-bot+netdevbpf @ 2026-07-03 6:30 UTC (permalink / raw)
To: Xiang Mei
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, netdev, linux-usb,
linux-kernel, bestswngs
In-Reply-To: <20260630045121.1565324-1-xmei5@asu.edu>
Hello:
This patch was applied to netdev/net.git (main)
by Paolo Abeni <pabeni@redhat.com>:
On Mon, 29 Jun 2026 21:51:21 -0700 you wrote:
> For an even packet_len, net1080_rx_fixup() reads the pad byte at
> skb->data[packet_len] before the skb->len != packet_len check further
> down, and packet_len is only bounded against NC_MAX_PACKET. A malicious
> NetChip 1080 device can send a short frame advertising a large even
> packet_len (e.g. 0x4000), so the pad-byte read lands past the end of the
> skb:
>
> [...]
Here is the summary with links:
- [net,v2] net: usb: net1080: validate packet_len before pad-byte access in rx_fixup
https://git.kernel.org/netdev/net/c/03f384bc0cb8
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* Re: [PATCH 1/2] af_unix: Do not wait for garbage collector in sendmsg()
From: Kuniyuki Iwashima @ 2026-07-03 6:25 UTC (permalink / raw)
To: Nam Cao
Cc: sashiko-reviews, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, netdev, linux-kernel, linux-rt-devel
In-Reply-To: <87mrw8sjo3.fsf@yellow.woof>
On Thu, Jul 2, 2026 at 10:53 PM Nam Cao <namcao@linutronix.de> wrote:
>
> sashiko-bot@kernel.org writes:
> > Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> > - [High] Garbage collector is no longer scheduled when creating new inflight FDs, leading to permanent cycle accumulation and -ETOOMANYREFS failures in isolated environments.
> > --
> >
> > commit 506189bd29f8ad56f5d9e51f3e0216203ebad5ec
> > Author: Nam Cao <namcao@linutronix.de>
> >
> > af_unix: Do not wait for garbage collector in sendmsg()
> >
> > This patch removes the garbage collector scheduling and waiting from the
> > sendmsg() path for AF_UNIX sockets. It aims to prevent priority inversion
> > by relying on RLIMIT_NOFILE limits and asynchronous garbage collection.
> >
> >> diff --git a/net/unix/garbage.c b/net/unix/garbage.c
> >> index 0783555e25266..f180c59b3da9d 100644
> >> --- a/net/unix/garbage.c
> >> +++ b/net/unix/garbage.c
> >> @@ -300,8 +300,6 @@ int unix_prepare_fpl(struct scm_fp_list *fpl)
> >> if (!fpl->edges)
> >> goto err;
> >>
> >> - unix_schedule_gc(fpl->user);
> >> -
> >
> > [Severity: High]
> > Does this removal prevent the garbage collector from running when a process
> > creates isolated socket cycles?
> >
> > If a process continuously creates socket cycles (e.g., passing FDs to each
> > other) and closes its local FDs, the sockets reference each other in their
> > receive queues, so their file reference counts will not reach zero.
> >
> > Because the refcounts do not drop to zero, unix_release_sock() is never
> > triggered. By removing unix_schedule_gc() from unix_prepare_fpl() here,
> > unix_release_sock() is left as the only trigger for garbage collection.
> >
> > This could allow the inflight count to accumulate indefinitely until it
> > hits RLIMIT_NOFILE, causing subsequent sendmsg() calls to fail with
> > -ETOOMANYREFS and permanently leaking the network namespace unless an
> > unrelated AF_UNIX socket is closed system-wide.
>
> Sashiko found a valid issue here.
This is same with my point, and
> But this is a pre-existing issue. It
> is not introduced in this patch.
your patch makes it much easier to abuse.
UNIX_INFLIGHT_SANE_USER is usually much smaller than
RLIMIT_NOFILE.
unix_schedule_gc() in sendmsg() is to self-regulate malicious users,
otherwise GC relies on unrelated AF_UNIX socket's close() and could
be triggered too late since GC is system-wide.
Previously every sendmsg() had to wait for GC, and now it's only when
there is a circular reference AND user has too many inflight sockets.
Please fix the root cause; the former condition on your system.
--
pw-bot: cr
^ permalink raw reply
* Re: [PATCH ipsec v2] xfrm: policy: preallocate inexact bins before xfrm_hash_rebuild reinsert
From: Manuel Ebner @ 2026-07-03 6:18 UTC (permalink / raw)
To: Xiang Mei (Microsoft), fw, steffen.klassert, herbert, davem,
netdev
Cc: horms, edumazet, kuba, pabeni, AutonomousCodeSecurity, tgopinath,
kys
In-Reply-To: <20260703051932.966884-1-xmei5@asu.edu>
Hi Xiang Mei
On Fri, 2026-07-03 at 05:19 +0000, Xiang Mei (Microsoft) wrote:
> xfrm_hash_rebuild()'s first loop preallocates the bins/chains the reinsert
> loop needs, so the reinsert (after hlist_del_rcu()) cannot allocate or
> fail.
This sentence is difficult to understand for me. Can it be simplified or changed?
This would help a little:
xfrm_hash_rebuild()'s first loop preallocates the bins/chains which the reinsert
loop needs. In this case the reinsert ...
> But its guard is inverted: it skips policies with prefixlen <
> threshold and preallocates for the rest.
>
> prefixlen < threshold is exactly when policy_hash_bysel() returns NULL and
> the reinsert takes the allocating xfrm_policy_inexact_insert() path. So the
> loop preallocates for the exact policies (which never allocate) and skips
> the inexact ones, whose bin/node is then allocated GFP_ATOMIC during
> reinsert. On failure the error path only WARN_ONCE()s and continues,
> leaving a poisoned bydst node;
what's 'bydst'?
> the next rebuild's hlist_del_rcu()
> dereferences LIST_POISON2 and takes a GPF.
/GPF/GFP/
Thanks
Manuel
> Reachable under memory pressure,
> deterministic via failslab.
>
> Invert the guard so preallocation covers exactly the reinserted policies;
> the reinsert then allocates nothing and cannot fail.
>
> Crash:
> Oops: general protection fault, probably for non-canonical address
> 0xfbd59c0000000024: 0000 [#1] SMP KASAN NOPTI
> KASAN: maybe wild-memory-access in range [0xdead...]
> ...
> Workqueue: events xfrm_hash_rebuild
> RIP: 0010:xfrm_hash_rebuild+0x5b3/0x1190
> RAX: dead000000000122 (LIST_POISON2 + offset)
> ...
> Call Trace:
> hlist_del_rcu (include/linux/rculist.h:599)
> xfrm_hash_rebuild (net/xfrm/xfrm_policy.c:1365)
> process_one_work (kernel/workqueue.c:3322)
> worker_thread (kernel/workqueue.c:3486)
> kthread (kernel/kthread.c:436)
> ret_from_fork (arch/x86/kernel/process.c:158)
> ret_from_fork_asm (arch/x86/entry/entry_64.S:245)
> ...
> Kernel panic - not syncing: Fatal exception in interrupt
>
> Fixes: 24969facd704 ("xfrm: policy: store inexact policies in an rhashtable")
> Reported-by: AutonomousCodeSecurity@microsoft.com
> Signed-off-by: Xiang Mei (Microsoft) <xmei5@asu.edu>
> ---
> v2: fix the inverted preallocation guard (root cause)
> instead of avoiding crash
>
> net/xfrm/xfrm_policy.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
> index 7ef861a0e823..932a313b9460 100644
> --- a/net/xfrm/xfrm_policy.c
> +++ b/net/xfrm/xfrm_policy.c
> @@ -1329,8 +1329,8 @@ static void xfrm_hash_rebuild(struct work_struct *work)
> }
> }
>
> - if (policy->selector.prefixlen_d < dbits ||
> - policy->selector.prefixlen_s < sbits)
> + if (policy->selector.prefixlen_d >= dbits &&
> + policy->selector.prefixlen_s >= sbits)
> continue;
>
> bin = xfrm_policy_inexact_alloc_bin(policy, dir);
^ permalink raw reply
* Re: [PATCHv2 net-next] net: dsa: qca8k: fall back to ethernet-ports node name for LEDs
From: Paolo Abeni @ 2026-07-03 6:14 UTC (permalink / raw)
To: Rosen Penev, netdev
Cc: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, open list
In-Reply-To: <20260630015137.1591152-1-rosenp@gmail.com>
On 6/30/26 3:51 AM, Rosen Penev wrote:
> The device tree binding allows both "ports" and "ethernet-ports" as
> the container node name. Try "ethernet-ports" when "ports" is absent
> so that newer DTBs with the preferred name work.
>
> This matches the handling already present in qca8k-8xxx.c
>
> Assisted-by: opencode:big-pickle
> Signed-off-by: Rosen Penev <rosenp@gmail.com>
Apparently the PW bot is still enjoying some PTO...
Applied, thanks!
/P
^ permalink raw reply
* [PATCH rdma-next v9] RDMA: Change capability fields in ib_device_attr from int to u32
From: Erni Sri Satya Vennela @ 2026-07-03 6:02 UTC (permalink / raw)
To: Jason Gunthorpe, Leon Romanovsky, mkalderon, zyjzyj2000, sagi,
mgurtovoy, haris.iqbal, jinpu.wang, bvanassche, kbusch,
Jens Axboe, Christoph Hellwig, kch, smfrench, linkinjeon, metze,
tom, cel, jlayton, neil, okorniev, Dai.Ngo, trondmy, anna,
achender, davem, edumazet, kuba, pabeni, horms, kees,
andriy.shevchenko, clm, ebadger, linux-rdma, linux-kernel,
target-devel, linux-nvme, linux-cifs, samba-technical, linux-nfs,
netdev, rds-devel
Cc: Erni Sri Satya Vennela, Jason Gunthorpe
The capability counter fields in struct ib_device_attr are declared
as signed int, but these values are inherently non-negative. Drivers
maintain their cached caps as u32 and assign them directly into these
int fields; if a cap exceeds INT_MAX the implicit narrowing yields a
negative value visible to the IB core.
Change the signed int capability fields to u32 to match the
underlying nature of the data. Also update consumers across the IB
core, ULPs, NVMe-oF target, RDS, and NFS/RDMA so the new u32 values
are not forced back through signed int or u8 via min()/min_t() or
narrowing local variables.
Suggested-by: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Erni Sri Satya Vennela <ernis@linux.microsoft.com>
Acked-by: Stefan Metzmacher <metze@samba.org> # smbdirect
---
Changes in v9:
* Switch the srq_size module parameter accessors to param_get_uint and
kstrtouint()/param_set_uint() so they match the now-unsigned
nvmet_rdma_srq_size variable.
Changes in v8:
* Convert the remaining non-negative counter fields max_ee_rd_atom,
max_ee_init_rd_atom, max_ee, max_rdd, max_raw_ipv6_qp and max_srq_wr
to u32; keep max_srq as int (its consumer compares it against
ib_device.num_comp_vectors, still int).
* Drop all remaining min_t() where plain min() now works.
* Make the srq_size module parameters unsigned int so the srq_size min()
stays a plain min().
* Replace the ternary-inside-min() with the simpler "if (x) x--;".
* Reorder the send_queue_depth min() to min(value, CONST) to match the
sibling site.
* Restore reverse xmas-tree declaration order.
* Collapse the min()/min3() assignments that now fit onto a single line
within 100 columns.
* Print the now-u32 fields with %u instead of %d.
Changes in v7:
* Drop min_t() in all sites where a plain min() (or min3()) works
cleanly
* Guard nvme/host/rdma.c num_inline_segments computation against a
device reporting max_send_sge == 0, so the u32 subtract
cannot wrap to UINT_MAX.
* Use %u when printing the newly-u32 capability fields
in diagnostic messages.
Changes in v6:
* Fix subject prefix: net-next -> rdma-next.
Changes in v5:
* Add U8_MAX clamps in iser_verbs, nvme/host, nvme/target, isert,
* rds/ib_cm, smbdirect/connect and smbdirect/accept where u32 capability
fields were directly narrowed into u8 rdma_conn_param fields without
clamping.
* Guard the inline_sge_count calculation in nvmet_rdma_find_get_device()
to prevent u32 underflow when both max_sge_rd and max_recv_sge are
zero.
* Expand type migration to 9 additional fields (max_mw, max_raw_ethy_qp,
max_mcast_grp, max_mcast_qp_attach, max_total_mcast_qp_attach, max_ah,
max_srq, max_srq_wr, max_srq_sge)
* Fix min_t(int,...) in svc_rdma_transport; min_t(u32,...) in ipoib,
srpt, nvme/target, rds/ib, rtrs-clt, rtrs-srv, xprtrdma/verbsdd.
* Fix frwr_ops.c u32 underflow guard (reorder check before subtraction)
* Change sc_max_send_sges to unsigned int, inline_sge_count to u32
* Fix %d -> %u in rxe_qp, rxe_srq, ipoib_cm, ib_isert,
* svc_rdma_transport
* Update commit message.
Changes in v4:
* Drop clamping the values in mana_ib_query_device, instead update
the props values from int to u32.
Changes in v3:
* Drop clamping from mana_ib_gd_query_adapter_caps(). The internal u32
caps cache does not need to be clamped.
* Move all clamping exclusively to mana_ib_query_device(), which is the
only place the cached u32 values are narrowed into the signed int
fields of struct ib_device_attr.
* Reframe commit message: this is a u32-to-int type boundary fix, not a
CVM/untrusted-hardware hardening patch.
Changes in v2:
* Update patch title.
---
drivers/infiniband/core/cq.c | 3 +-
drivers/infiniband/hw/qedr/verbs.c | 2 +-
drivers/infiniband/sw/rxe/rxe_qp.c | 22 +++++-----
drivers/infiniband/sw/rxe/rxe_srq.c | 16 +++----
drivers/infiniband/ulp/ipoib/ipoib_cm.c | 10 ++---
drivers/infiniband/ulp/ipoib/ipoib_verbs.c | 3 +-
drivers/infiniband/ulp/iser/iser_verbs.c | 5 +--
drivers/infiniband/ulp/isert/ib_isert.c | 7 ++-
drivers/infiniband/ulp/rtrs/rtrs-clt.c | 11 ++---
drivers/infiniband/ulp/rtrs/rtrs-srv.c | 11 ++---
drivers/infiniband/ulp/srp/ib_srp.c | 2 +-
drivers/infiniband/ulp/srpt/ib_srpt.c | 21 +++++----
drivers/nvme/host/rdma.c | 8 ++--
drivers/nvme/target/rdma.c | 22 ++++++----
fs/smb/smbdirect/accept.c | 5 ++-
fs/smb/smbdirect/connect.c | 5 ++-
fs/smb/smbdirect/connection.c | 8 ++--
include/linux/sunrpc/svc_rdma.h | 4 +-
include/rdma/ib_verbs.h | 50 +++++++++++-----------
net/rds/ib.c | 10 ++---
net/rds/ib_cm.c | 10 ++---
net/sunrpc/xprtrdma/frwr_ops.c | 7 +--
net/sunrpc/xprtrdma/svc_rdma_transport.c | 5 +--
net/sunrpc/xprtrdma/verbs.c | 2 +-
24 files changed, 122 insertions(+), 127 deletions(-)
diff --git a/drivers/infiniband/core/cq.c b/drivers/infiniband/core/cq.c
index 3d7b6cddd131..ee98188e57fb 100644
--- a/drivers/infiniband/core/cq.c
+++ b/drivers/infiniband/core/cq.c
@@ -393,8 +393,7 @@ static int ib_alloc_cqs(struct ib_device *dev, unsigned int nr_cqes,
* a reasonable batch size so that we can share CQs between
* multiple users instead of allocating a larger number of CQs.
*/
- nr_cqes = min_t(unsigned int, dev->attrs.max_cqe,
- max(nr_cqes, IB_MAX_SHARED_CQ_SZ));
+ nr_cqes = min(dev->attrs.max_cqe, max(nr_cqes, IB_MAX_SHARED_CQ_SZ));
nr_cqs = min_t(unsigned int, dev->num_comp_vectors, num_online_cpus());
for (i = 0; i < nr_cqs; i++) {
cq = ib_alloc_cq(dev, NULL, nr_cqes, i, poll_ctx);
diff --git a/drivers/infiniband/hw/qedr/verbs.c b/drivers/infiniband/hw/qedr/verbs.c
index c90a1b5c8ee7..d5416b161340 100644
--- a/drivers/infiniband/hw/qedr/verbs.c
+++ b/drivers/infiniband/hw/qedr/verbs.c
@@ -148,7 +148,7 @@ int qedr_query_device(struct ib_device *ibdev,
attr->max_qp_init_rd_atom =
1 << (fls(qattr->max_qp_req_rd_atomic_resc) - 1);
attr->max_qp_rd_atom =
- min(1 << (fls(qattr->max_qp_resp_rd_atomic_resc) - 1),
+ min(1U << (fls(qattr->max_qp_resp_rd_atomic_resc) - 1),
attr->max_qp_init_rd_atom);
attr->max_srq = qattr->max_srq;
diff --git a/drivers/infiniband/sw/rxe/rxe_qp.c b/drivers/infiniband/sw/rxe/rxe_qp.c
index f3dff1aea96a..7a0529a17992 100644
--- a/drivers/infiniband/sw/rxe/rxe_qp.c
+++ b/drivers/infiniband/sw/rxe/rxe_qp.c
@@ -67,27 +67,27 @@ static int rxe_qp_chk_cap(struct rxe_dev *rxe, struct ib_qp_cap *cap,
int has_srq)
{
if (cap->max_send_wr > rxe->attr.max_qp_wr) {
- rxe_dbg_dev(rxe, "invalid send wr = %u > %d\n",
- cap->max_send_wr, rxe->attr.max_qp_wr);
+ rxe_dbg_dev(rxe, "invalid send wr = %u > %u\n",
+ cap->max_send_wr, rxe->attr.max_qp_wr);
goto err1;
}
if (cap->max_send_sge > rxe->attr.max_send_sge) {
- rxe_dbg_dev(rxe, "invalid send sge = %u > %d\n",
- cap->max_send_sge, rxe->attr.max_send_sge);
+ rxe_dbg_dev(rxe, "invalid send sge = %u > %u\n",
+ cap->max_send_sge, rxe->attr.max_send_sge);
goto err1;
}
if (!has_srq) {
if (cap->max_recv_wr > rxe->attr.max_qp_wr) {
- rxe_dbg_dev(rxe, "invalid recv wr = %u > %d\n",
- cap->max_recv_wr, rxe->attr.max_qp_wr);
+ rxe_dbg_dev(rxe, "invalid recv wr = %u > %u\n",
+ cap->max_recv_wr, rxe->attr.max_qp_wr);
goto err1;
}
if (cap->max_recv_sge > rxe->attr.max_recv_sge) {
- rxe_dbg_dev(rxe, "invalid recv sge = %u > %d\n",
- cap->max_recv_sge, rxe->attr.max_recv_sge);
+ rxe_dbg_dev(rxe, "invalid recv sge = %u > %u\n",
+ cap->max_recv_sge, rxe->attr.max_recv_sge);
goto err1;
}
}
@@ -537,9 +537,9 @@ int rxe_qp_chk_attr(struct rxe_dev *rxe, struct rxe_qp *qp,
if (mask & IB_QP_MAX_QP_RD_ATOMIC) {
if (attr->max_rd_atomic > rxe->attr.max_qp_rd_atom) {
- rxe_dbg_qp(qp, "invalid max_rd_atomic %d > %d\n",
- attr->max_rd_atomic,
- rxe->attr.max_qp_rd_atom);
+ rxe_dbg_qp(qp, "invalid max_rd_atomic %u > %u\n",
+ attr->max_rd_atomic,
+ rxe->attr.max_qp_rd_atom);
goto err1;
}
}
diff --git a/drivers/infiniband/sw/rxe/rxe_srq.c b/drivers/infiniband/sw/rxe/rxe_srq.c
index c9a7cd38953d..74904a6fdf2b 100644
--- a/drivers/infiniband/sw/rxe/rxe_srq.c
+++ b/drivers/infiniband/sw/rxe/rxe_srq.c
@@ -13,8 +13,8 @@ int rxe_srq_chk_init(struct rxe_dev *rxe, struct ib_srq_init_attr *init)
struct ib_srq_attr *attr = &init->attr;
if (attr->max_wr > rxe->attr.max_srq_wr) {
- rxe_dbg_dev(rxe, "max_wr(%d) > max_srq_wr(%d)\n",
- attr->max_wr, rxe->attr.max_srq_wr);
+ rxe_dbg_dev(rxe, "max_wr(%u) > max_srq_wr(%u)\n",
+ attr->max_wr, rxe->attr.max_srq_wr);
goto err1;
}
@@ -27,8 +27,8 @@ int rxe_srq_chk_init(struct rxe_dev *rxe, struct ib_srq_init_attr *init)
attr->max_wr = RXE_MIN_SRQ_WR;
if (attr->max_sge > rxe->attr.max_srq_sge) {
- rxe_dbg_dev(rxe, "max_sge(%d) > max_srq_sge(%d)\n",
- attr->max_sge, rxe->attr.max_srq_sge);
+ rxe_dbg_dev(rxe, "max_sge(%u) > max_srq_sge(%u)\n",
+ attr->max_sge, rxe->attr.max_srq_sge);
goto err1;
}
@@ -107,8 +107,8 @@ int rxe_srq_chk_attr(struct rxe_dev *rxe, struct rxe_srq *srq,
if (mask & IB_SRQ_MAX_WR) {
if (attr->max_wr > rxe->attr.max_srq_wr) {
- rxe_dbg_srq(srq, "max_wr(%d) > max_srq_wr(%d)\n",
- attr->max_wr, rxe->attr.max_srq_wr);
+ rxe_dbg_srq(srq, "max_wr(%u) > max_srq_wr(%u)\n",
+ attr->max_wr, rxe->attr.max_srq_wr);
goto err1;
}
@@ -129,8 +129,8 @@ int rxe_srq_chk_attr(struct rxe_dev *rxe, struct rxe_srq *srq,
if (mask & IB_SRQ_LIMIT) {
if (attr->srq_limit > rxe->attr.max_srq_wr) {
- rxe_dbg_srq(srq, "srq_limit(%d) > max_srq_wr(%d)\n",
- attr->srq_limit, rxe->attr.max_srq_wr);
+ rxe_dbg_srq(srq, "srq_limit(%u) > max_srq_wr(%u)\n",
+ attr->srq_limit, rxe->attr.max_srq_wr);
goto err1;
}
diff --git a/drivers/infiniband/ulp/ipoib/ipoib_cm.c b/drivers/infiniband/ulp/ipoib/ipoib_cm.c
index 57fec88a1629..ed0592898384 100644
--- a/drivers/infiniband/ulp/ipoib/ipoib_cm.c
+++ b/drivers/infiniband/ulp/ipoib/ipoib_cm.c
@@ -1071,8 +1071,7 @@ static struct ib_qp *ipoib_cm_create_tx_qp(struct net_device *dev, struct ipoib_
struct ib_qp *tx_qp;
if (dev->features & NETIF_F_SG)
- attr.cap.max_send_sge = min_t(u32, priv->ca->attrs.max_send_sge,
- MAX_SKB_FRAGS + 1);
+ attr.cap.max_send_sge = min(priv->ca->attrs.max_send_sge, MAX_SKB_FRAGS + 1);
tx_qp = ib_create_qp(priv->pd, &attr);
tx->max_send_sge = attr.cap.max_send_sge;
@@ -1582,7 +1581,8 @@ static void ipoib_cm_create_srq(struct net_device *dev, int max_sge)
int ipoib_cm_dev_init(struct net_device *dev)
{
struct ipoib_dev_priv *priv = ipoib_priv(dev);
- int max_srq_sge, i;
+ u32 max_srq_sge;
+ int i;
u8 addr;
INIT_LIST_HEAD(&priv->cm.passive_ids);
@@ -1600,9 +1600,9 @@ int ipoib_cm_dev_init(struct net_device *dev)
skb_queue_head_init(&priv->cm.skb_queue);
- ipoib_dbg(priv, "max_srq_sge=%d\n", priv->ca->attrs.max_srq_sge);
+ ipoib_dbg(priv, "max_srq_sge=%u\n", priv->ca->attrs.max_srq_sge);
- max_srq_sge = min_t(int, IPOIB_CM_RX_SG, priv->ca->attrs.max_srq_sge);
+ max_srq_sge = min(priv->ca->attrs.max_srq_sge, IPOIB_CM_RX_SG);
ipoib_cm_create_srq(dev, max_srq_sge);
if (ipoib_cm_has_srq(dev)) {
priv->cm.max_cm_mtu = max_srq_sge * PAGE_SIZE - 0x10;
diff --git a/drivers/infiniband/ulp/ipoib/ipoib_verbs.c b/drivers/infiniband/ulp/ipoib/ipoib_verbs.c
index 3ed1ea566690..2490696a1aab 100644
--- a/drivers/infiniband/ulp/ipoib/ipoib_verbs.c
+++ b/drivers/infiniband/ulp/ipoib/ipoib_verbs.c
@@ -147,8 +147,7 @@ int ipoib_transport_dev_init(struct net_device *dev, struct ib_device *ca)
.cap = {
.max_send_wr = ipoib_sendq_size,
.max_recv_wr = ipoib_recvq_size,
- .max_send_sge = min_t(u32, priv->ca->attrs.max_send_sge,
- MAX_SKB_FRAGS + 1),
+ .max_send_sge = min(priv->ca->attrs.max_send_sge, MAX_SKB_FRAGS + 1),
.max_recv_sge = IPOIB_UD_RX_SG
},
.sq_sig_type = IB_SIGNAL_ALL_WR,
diff --git a/drivers/infiniband/ulp/iser/iser_verbs.c b/drivers/infiniband/ulp/iser/iser_verbs.c
index f03b3bb3c0c4..55fe68e5b837 100644
--- a/drivers/infiniband/ulp/iser/iser_verbs.c
+++ b/drivers/infiniband/ulp/iser/iser_verbs.c
@@ -244,8 +244,7 @@ static int iser_create_ib_conn_res(struct ib_conn *ib_conn)
max_send_wr = ISER_QP_SIG_MAX_REQ_DTOS + 1;
else
max_send_wr = ISER_QP_MAX_REQ_DTOS + 1;
- max_send_wr = min_t(unsigned int, max_send_wr,
- (unsigned int)ib_dev->attrs.max_qp_wr);
+ max_send_wr = min(max_send_wr, ib_dev->attrs.max_qp_wr);
cq_size = max_send_wr + ISER_QP_MAX_RECV_DTOS;
ib_conn->cq = ib_cq_pool_get(ib_dev, cq_size, -1, IB_POLL_SOFTIRQ);
@@ -589,7 +588,7 @@ static void iser_route_handler(struct rdma_cm_id *cma_id)
goto failure;
memset(&conn_param, 0, sizeof conn_param);
- conn_param.responder_resources = ib_dev->attrs.max_qp_rd_atom;
+ conn_param.responder_resources = min(ib_dev->attrs.max_qp_rd_atom, U8_MAX);
conn_param.initiator_depth = 1;
conn_param.retry_count = 7;
conn_param.rnr_retry_count = 6;
diff --git a/drivers/infiniband/ulp/isert/ib_isert.c b/drivers/infiniband/ulp/isert/ib_isert.c
index 1015a51f750a..4691845bf815 100644
--- a/drivers/infiniband/ulp/isert/ib_isert.c
+++ b/drivers/infiniband/ulp/isert/ib_isert.c
@@ -214,9 +214,9 @@ isert_create_device_ib_res(struct isert_device *device)
struct ib_device *ib_dev = device->ib_device;
int ret;
- isert_dbg("devattr->max_send_sge: %d devattr->max_recv_sge %d\n",
+ isert_dbg("devattr->max_send_sge: %u devattr->max_recv_sge %u\n",
ib_dev->attrs.max_send_sge, ib_dev->attrs.max_recv_sge);
- isert_dbg("devattr->max_sge_rd: %d\n", ib_dev->attrs.max_sge_rd);
+ isert_dbg("devattr->max_sge_rd: %u\n", ib_dev->attrs.max_sge_rd);
device->pd = ib_alloc_pd(ib_dev, 0);
if (IS_ERR(device->pd)) {
@@ -381,8 +381,7 @@ isert_set_nego_params(struct isert_conn *isert_conn,
struct ib_device_attr *attr = &isert_conn->device->ib_device->attrs;
/* Set max inflight RDMA READ requests */
- isert_conn->initiator_depth = min_t(u8, param->initiator_depth,
- attr->max_qp_init_rd_atom);
+ isert_conn->initiator_depth = min(param->initiator_depth, attr->max_qp_init_rd_atom);
isert_dbg("Using initiator_depth: %u\n", isert_conn->initiator_depth);
if (param->private_data) {
diff --git a/drivers/infiniband/ulp/rtrs/rtrs-clt.c b/drivers/infiniband/ulp/rtrs/rtrs-clt.c
index d34d7e5f34d6..7b2c51ae614f 100644
--- a/drivers/infiniband/ulp/rtrs/rtrs-clt.c
+++ b/drivers/infiniband/ulp/rtrs/rtrs-clt.c
@@ -1675,8 +1675,7 @@ static int create_con_cq_qp(struct rtrs_clt_con *con)
* + 2 for drain and heartbeat
* in case qp gets into error state.
*/
- max_send_wr =
- min_t(int, wr_limit, SERVICE_CON_QUEUE_DEPTH * 2 + 2);
+ max_send_wr = min(wr_limit, SERVICE_CON_QUEUE_DEPTH * 2 + 2);
max_recv_wr = max_send_wr;
} else {
/*
@@ -1692,11 +1691,9 @@ static int create_con_cq_qp(struct rtrs_clt_con *con)
wr_limit = clt_path->s.dev->ib_dev->attrs.max_qp_wr;
/* Shared between connections */
clt_path->s.dev_ref++;
- max_send_wr = min_t(int, wr_limit,
- /* QD * (REQ + RSP + FR REGS or INVS) + drain */
- clt_path->queue_depth * 4 + 1);
- max_recv_wr = min_t(int, wr_limit,
- clt_path->queue_depth * 3 + 1);
+ /* QD * (REQ + RSP + FR REGS or INVS) + drain */
+ max_send_wr = min(wr_limit, clt_path->queue_depth * 4 + 1);
+ max_recv_wr = min(wr_limit, clt_path->queue_depth * 3 + 1);
max_send_sge = 2;
}
atomic_set(&con->c.sq_wr_avail, max_send_wr);
diff --git a/drivers/infiniband/ulp/rtrs/rtrs-srv.c b/drivers/infiniband/ulp/rtrs/rtrs-srv.c
index 7d8e4422cc57..5314599d29fe 100644
--- a/drivers/infiniband/ulp/rtrs/rtrs-srv.c
+++ b/drivers/infiniband/ulp/rtrs/rtrs-srv.c
@@ -1740,21 +1740,16 @@ static int create_con(struct rtrs_srv_path *srv_path,
* All receive and all send (each requiring invalidate)
* + 2 for drain and heartbeat
*/
- max_send_wr = min_t(int, wr_limit,
- SERVICE_CON_QUEUE_DEPTH * 2 + 2);
+ max_send_wr = min(wr_limit, SERVICE_CON_QUEUE_DEPTH * 2 + 2);
max_recv_wr = max_send_wr;
s->signal_interval = min_not_zero(srv->queue_depth,
(size_t)SERVICE_CON_QUEUE_DEPTH);
} else {
/* when always_invlaidate enalbed, we need linv+rinv+mr+imm */
if (always_invalidate)
- max_send_wr =
- min_t(int, wr_limit,
- srv->queue_depth * (1 + 4) + 1);
+ max_send_wr = min(wr_limit, srv->queue_depth * (1 + 4) + 1);
else
- max_send_wr =
- min_t(int, wr_limit,
- srv->queue_depth * (1 + 2) + 1);
+ max_send_wr = min(wr_limit, srv->queue_depth * (1 + 2) + 1);
max_recv_wr = srv->queue_depth + 1;
}
diff --git a/drivers/infiniband/ulp/srp/ib_srp.c b/drivers/infiniband/ulp/srp/ib_srp.c
index acbd787de265..0caebbc2810f 100644
--- a/drivers/infiniband/ulp/srp/ib_srp.c
+++ b/drivers/infiniband/ulp/srp/ib_srp.c
@@ -557,7 +557,7 @@ static int srp_create_ch_ib(struct srp_rdma_ch *ch)
init_attr->cap.max_send_wr = m * target->queue_size;
init_attr->cap.max_recv_wr = target->queue_size + 1;
init_attr->cap.max_recv_sge = 1;
- init_attr->cap.max_send_sge = min(SRP_MAX_SGE, attr->max_send_sge);
+ init_attr->cap.max_send_sge = min(attr->max_send_sge, SRP_MAX_SGE);
init_attr->sq_sig_type = IB_SIGNAL_REQ_WR;
init_attr->qp_type = IB_QPT_RC;
init_attr->send_cq = send_cq;
diff --git a/drivers/infiniband/ulp/srpt/ib_srpt.c b/drivers/infiniband/ulp/srpt/ib_srpt.c
index f66cfd70c263..a760b4fbee90 100644
--- a/drivers/infiniband/ulp/srpt/ib_srpt.c
+++ b/drivers/infiniband/ulp/srpt/ib_srpt.c
@@ -77,8 +77,8 @@ module_param(srp_max_req_size, int, 0444);
MODULE_PARM_DESC(srp_max_req_size,
"Maximum size of SRP request messages in bytes.");
-static int srpt_srq_size = DEFAULT_SRPT_SRQ_SIZE;
-module_param(srpt_srq_size, int, 0444);
+static unsigned int srpt_srq_size = DEFAULT_SRPT_SRQ_SIZE;
+module_param(srpt_srq_size, uint, 0444);
MODULE_PARM_DESC(srpt_srq_size,
"Shared receive queue (SRQ) size.");
@@ -405,8 +405,7 @@ static void srpt_get_ioc(struct srpt_port *sport, u32 slot,
if (sdev->use_srq)
send_queue_depth = sdev->srq_size;
else
- send_queue_depth = min(MAX_SRPT_RQ_SIZE,
- sdev->device->attrs.max_qp_wr);
+ send_queue_depth = min(sdev->device->attrs.max_qp_wr, MAX_SRPT_RQ_SIZE);
memset(iocp, 0, sizeof(*iocp));
strcpy(iocp->id_string, SRPT_ID_STRING);
@@ -1851,7 +1850,7 @@ static int srpt_create_ch_ib(struct srpt_rdma_ch *ch)
struct srpt_port *sport = ch->sport;
struct srpt_device *sdev = sport->sdev;
const struct ib_device_attr *attrs = &sdev->device->attrs;
- int sq_size = sport->port_attrib.srp_sq_size;
+ u32 sq_size = sport->port_attrib.srp_sq_size;
int i, ret;
WARN_ON(ch->rq_size < 1);
@@ -1912,13 +1911,13 @@ static int srpt_create_ch_ib(struct srpt_rdma_ch *ch)
bool retry = sq_size > MIN_SRPT_SQ_SIZE;
if (retry) {
- pr_debug("failed to create queue pair with sq_size = %d (%d) - retrying\n",
+ pr_debug("failed to create queue pair with sq_size = %u (%d) - retrying\n",
sq_size, ret);
ib_cq_pool_put(ch->cq, ch->cq_size);
sq_size = max(sq_size / 2, MIN_SRPT_SQ_SIZE);
goto retry;
} else {
- pr_err("failed to create queue pair with sq_size = %d (%d)\n",
+ pr_err("failed to create queue pair with sq_size = %u (%d)\n",
sq_size, ret);
goto err_destroy_cq;
}
@@ -1926,7 +1925,7 @@ static int srpt_create_ch_ib(struct srpt_rdma_ch *ch)
atomic_set(&ch->sq_wr_avail, qp_init->cap.max_send_wr);
- pr_debug("%s: max_cqe= %d max_sge= %d sq_size = %d ch= %p\n",
+ pr_debug("%s: max_cqe= %d max_sge= %d sq_size = %u ch= %p\n",
__func__, ch->cq->cqe, qp_init->cap.max_send_sge,
qp_init->cap.max_send_wr, ch);
@@ -2299,7 +2298,7 @@ static int srpt_cm_req_recv(struct srpt_device *const sdev,
* depth to avoid that the initiator driver has to report QUEUE_FULL
* to the SCSI mid-layer.
*/
- ch->rq_size = min(MAX_SRPT_RQ_SIZE, sdev->device->attrs.max_qp_wr);
+ ch->rq_size = min(sdev->device->attrs.max_qp_wr, MAX_SRPT_RQ_SIZE);
spin_lock_init(&ch->spinlock);
ch->state = CH_CONNECTING;
INIT_LIST_HEAD(&ch->cmd_wait_list);
@@ -3137,7 +3136,7 @@ static int srpt_alloc_srq(struct srpt_device *sdev)
return PTR_ERR(srq);
}
- pr_debug("create SRQ #wr= %d max_allow=%d dev= %s\n", sdev->srq_size,
+ pr_debug("create SRQ #wr= %d max_allow=%u dev= %s\n", sdev->srq_size,
sdev->device->attrs.max_srq_wr, dev_name(&device->dev));
sdev->req_buf_cache = srpt_cache_get(srp_max_req_size);
@@ -3952,7 +3951,7 @@ static int __init srpt_init_module(void)
if (srpt_srq_size < MIN_SRPT_SRQ_SIZE
|| srpt_srq_size > MAX_SRPT_SRQ_SIZE) {
- pr_err("invalid value %d for kernel module parameter srpt_srq_size -- must be in the range [%d..%d].\n",
+ pr_err("invalid value %u for kernel module parameter srpt_srq_size -- must be in the range [%d..%d].\n",
srpt_srq_size, MIN_SRPT_SRQ_SIZE, MAX_SRPT_SRQ_SIZE);
goto out;
}
diff --git a/drivers/nvme/host/rdma.c b/drivers/nvme/host/rdma.c
index 6909e3542794..56cd228af1d5 100644
--- a/drivers/nvme/host/rdma.c
+++ b/drivers/nvme/host/rdma.c
@@ -394,8 +394,10 @@ nvme_rdma_find_get_device(struct rdma_cm_id *cm_id)
goto out_free_pd;
}
- ndev->num_inline_segments = min(NVME_RDMA_MAX_INLINE_SEGMENTS,
- ndev->dev->attrs.max_send_sge - 1);
+ ndev->num_inline_segments = ndev->dev->attrs.max_send_sge;
+ if (ndev->num_inline_segments)
+ ndev->num_inline_segments--;
+ ndev->num_inline_segments = min(ndev->num_inline_segments, NVME_RDMA_MAX_INLINE_SEGMENTS);
list_add(&ndev->entry, &device_list);
out_unlock:
mutex_unlock(&device_list_mutex);
@@ -1847,7 +1849,7 @@ static int nvme_rdma_route_resolved(struct nvme_rdma_queue *queue)
param.qp_num = queue->qp->qp_num;
param.flow_control = 1;
- param.responder_resources = queue->device->dev->attrs.max_qp_rd_atom;
+ param.responder_resources = min(queue->device->dev->attrs.max_qp_rd_atom, U8_MAX);
/* maximum retry count */
param.retry_count = 7;
param.rnr_retry_count = 7;
diff --git a/drivers/nvme/target/rdma.c b/drivers/nvme/target/rdma.c
index ea1185b8267e..15f3d7aa715c 100644
--- a/drivers/nvme/target/rdma.c
+++ b/drivers/nvme/target/rdma.c
@@ -149,10 +149,10 @@ MODULE_PARM_DESC(use_srq, "Use shared receive queue.");
static int srq_size_set(const char *val, const struct kernel_param *kp);
static const struct kernel_param_ops srq_size_ops = {
.set = srq_size_set,
- .get = param_get_int,
+ .get = param_get_uint,
};
-static int nvmet_rdma_srq_size = 1024;
+static unsigned int nvmet_rdma_srq_size = 1024;
module_param_cb(srq_size, &srq_size_ops, &nvmet_rdma_srq_size, 0644);
MODULE_PARM_DESC(srq_size, "set Shared Receive Queue (SRQ) size, should >= 256 (default: 1024)");
@@ -180,13 +180,14 @@ static const struct nvmet_fabrics_ops nvmet_rdma_ops;
static int srq_size_set(const char *val, const struct kernel_param *kp)
{
- int n = 0, ret;
+ unsigned int n;
+ int ret;
- ret = kstrtoint(val, 10, &n);
+ ret = kstrtouint(val, 10, &n);
if (ret != 0 || n < 256)
return -EINVAL;
- return param_set_int(val, kp);
+ return param_set_uint(val, kp);
}
static int num_pages(int len)
@@ -1199,7 +1200,7 @@ nvmet_rdma_find_get_device(struct rdma_cm_id *cm_id)
struct nvmet_port *nport = port->nport;
struct nvmet_rdma_device *ndev;
int inline_page_count;
- int inline_sge_count;
+ u32 inline_sge_count;
int ret;
mutex_lock(&device_list_mutex);
@@ -1215,7 +1216,9 @@ nvmet_rdma_find_get_device(struct rdma_cm_id *cm_id)
inline_page_count = num_pages(nport->inline_data_size);
inline_sge_count = max(cm_id->device->attrs.max_sge_rd,
- cm_id->device->attrs.max_recv_sge) - 1;
+ cm_id->device->attrs.max_recv_sge);
+ if (inline_sge_count)
+ inline_sge_count--;
if (inline_page_count > inline_sge_count) {
pr_warn("inline_data_size %d cannot be supported by device %s. Reducing to %lu.\n",
nport->inline_data_size, cm_id->device->name,
@@ -1555,8 +1558,9 @@ static int nvmet_rdma_cm_accept(struct rdma_cm_id *cm_id,
param.rnr_retry_count = 7;
param.flow_control = 1;
- param.initiator_depth = min_t(u8, p->initiator_depth,
- queue->dev->device->attrs.max_qp_init_rd_atom);
+ param.initiator_depth = min3(p->initiator_depth,
+ queue->dev->device->attrs.max_qp_init_rd_atom,
+ U8_MAX);
param.private_data = &priv;
param.private_data_len = sizeof(priv);
priv.recfmt = cpu_to_le16(NVME_RDMA_CM_FMT_1_0);
diff --git a/fs/smb/smbdirect/accept.c b/fs/smb/smbdirect/accept.c
index 529740005838..44b681a20725 100644
--- a/fs/smb/smbdirect/accept.c
+++ b/fs/smb/smbdirect/accept.c
@@ -32,8 +32,9 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc,
/*
* First set what the we as server are able to support
*/
- sp->initiator_depth = min_t(u8, sp->initiator_depth,
- sc->ib.dev->attrs.max_qp_rd_atom);
+ sp->initiator_depth = min3(sp->initiator_depth,
+ sc->ib.dev->attrs.max_qp_rd_atom,
+ U8_MAX);
peer_initiator_depth = param->initiator_depth;
peer_responder_resources = param->responder_resources;
diff --git a/fs/smb/smbdirect/connect.c b/fs/smb/smbdirect/connect.c
index cd726b399afe..34a3e72c38fb 100644
--- a/fs/smb/smbdirect/connect.c
+++ b/fs/smb/smbdirect/connect.c
@@ -182,8 +182,9 @@ static int smbdirect_connect_rdma_connect(struct smbdirect_socket *sc)
if (sc->ib.dev->attrs.kernel_cap_flags & IBK_SG_GAPS_REG)
sc->mr_io.type = IB_MR_TYPE_SG_GAPS;
- sp->responder_resources = min_t(u8, sp->responder_resources,
- sc->ib.dev->attrs.max_qp_rd_atom);
+ sp->responder_resources = min3(sp->responder_resources,
+ sc->ib.dev->attrs.max_qp_rd_atom,
+ U8_MAX);
smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_INFO,
"responder_resources=%d\n",
sp->responder_resources);
diff --git a/fs/smb/smbdirect/connection.c b/fs/smb/smbdirect/connection.c
index 8adf58097534..690acb84e1b5 100644
--- a/fs/smb/smbdirect/connection.c
+++ b/fs/smb/smbdirect/connection.c
@@ -287,7 +287,7 @@ int smbdirect_connection_create_qp(struct smbdirect_socket *sc)
qp_cap.max_send_wr > sc->ib.dev->attrs.max_qp_wr) {
pr_err("Possible CQE overrun: max_send_wr %d\n",
qp_cap.max_send_wr);
- pr_err("device %.*s reporting max_cqe %d max_qp_wr %d\n",
+ pr_err("device %.*s reporting max_cqe %u max_qp_wr %u\n",
IB_DEVICE_NAME_MAX,
sc->ib.dev->name,
sc->ib.dev->attrs.max_cqe,
@@ -302,7 +302,7 @@ int smbdirect_connection_create_qp(struct smbdirect_socket *sc)
max_send_wr >= sc->ib.dev->attrs.max_qp_wr)) {
pr_err("Possible CQE overrun: rdma_send_wr %d + max_send_wr %d = %d\n",
rdma_send_wr, qp_cap.max_send_wr, max_send_wr);
- pr_err("device %.*s reporting max_cqe %d max_qp_wr %d\n",
+ pr_err("device %.*s reporting max_cqe %u max_qp_wr %u\n",
IB_DEVICE_NAME_MAX,
sc->ib.dev->name,
sc->ib.dev->attrs.max_cqe,
@@ -316,7 +316,7 @@ int smbdirect_connection_create_qp(struct smbdirect_socket *sc)
qp_cap.max_recv_wr > sc->ib.dev->attrs.max_qp_wr) {
pr_err("Possible CQE overrun: max_recv_wr %d\n",
qp_cap.max_recv_wr);
- pr_err("device %.*s reporting max_cqe %d max_qp_wr %d\n",
+ pr_err("device %.*s reporting max_cqe %u max_qp_wr %u\n",
IB_DEVICE_NAME_MAX,
sc->ib.dev->name,
sc->ib.dev->attrs.max_cqe,
@@ -328,7 +328,7 @@ int smbdirect_connection_create_qp(struct smbdirect_socket *sc)
if (qp_cap.max_send_sge > sc->ib.dev->attrs.max_send_sge ||
qp_cap.max_recv_sge > sc->ib.dev->attrs.max_recv_sge) {
- pr_err("device %.*s max_send_sge/max_recv_sge = %d/%d too small\n",
+ pr_err("device %.*s max_send_sge/max_recv_sge = %u/%u too small\n",
IB_DEVICE_NAME_MAX,
sc->ib.dev->name,
sc->ib.dev->attrs.max_send_sge,
diff --git a/include/linux/sunrpc/svc_rdma.h b/include/linux/sunrpc/svc_rdma.h
index 5aadb47b3b0e..76aa5ec4ab40 100644
--- a/include/linux/sunrpc/svc_rdma.h
+++ b/include/linux/sunrpc/svc_rdma.h
@@ -77,8 +77,8 @@ struct svcxprt_rdma {
struct rdma_cm_id *sc_cm_id; /* RDMA connection id */
struct list_head sc_accept_q; /* Conn. waiting accept */
struct rpcrdma_notification sc_rn; /* removal notification */
- int sc_ord; /* RDMA read limit */
- int sc_max_send_sges;
+ u32 sc_ord; /* RDMA read limit */
+ unsigned int sc_max_send_sges;
bool sc_snd_w_inv; /* OK to use Send With Invalidate */
atomic_t sc_sq_avail; /* SQEs ready to be consumed */
diff --git a/include/rdma/ib_verbs.h b/include/rdma/ib_verbs.h
index 794746de8db0..5ebc5aa9c278 100644
--- a/include/rdma/ib_verbs.h
+++ b/include/rdma/ib_verbs.h
@@ -407,36 +407,36 @@ struct ib_device_attr {
u32 vendor_id;
u32 vendor_part_id;
u32 hw_ver;
- int max_qp;
- int max_qp_wr;
+ u32 max_qp;
+ u32 max_qp_wr;
u64 device_cap_flags;
u64 kernel_cap_flags;
- int max_send_sge;
- int max_recv_sge;
- int max_sge_rd;
- int max_cq;
- int max_cqe;
- int max_mr;
- int max_pd;
- int max_qp_rd_atom;
- int max_ee_rd_atom;
- int max_res_rd_atom;
- int max_qp_init_rd_atom;
- int max_ee_init_rd_atom;
+ u32 max_send_sge;
+ u32 max_recv_sge;
+ u32 max_sge_rd;
+ u32 max_cq;
+ u32 max_cqe;
+ u32 max_mr;
+ u32 max_pd;
+ u32 max_qp_rd_atom;
+ u32 max_ee_rd_atom;
+ u32 max_res_rd_atom;
+ u32 max_qp_init_rd_atom;
+ u32 max_ee_init_rd_atom;
enum ib_atomic_cap atomic_cap;
enum ib_atomic_cap masked_atomic_cap;
- int max_ee;
- int max_rdd;
- int max_mw;
- int max_raw_ipv6_qp;
- int max_raw_ethy_qp;
- int max_mcast_grp;
- int max_mcast_qp_attach;
- int max_total_mcast_qp_attach;
- int max_ah;
+ u32 max_ee;
+ u32 max_rdd;
+ u32 max_mw;
+ u32 max_raw_ipv6_qp;
+ u32 max_raw_ethy_qp;
+ u32 max_mcast_grp;
+ u32 max_mcast_qp_attach;
+ u32 max_total_mcast_qp_attach;
+ u32 max_ah;
int max_srq;
- int max_srq_wr;
- int max_srq_sge;
+ u32 max_srq_wr;
+ u32 max_srq_sge;
unsigned int max_fast_reg_page_list_len;
unsigned int max_pi_fast_reg_page_list_len;
u16 max_pkeys;
diff --git a/net/rds/ib.c b/net/rds/ib.c
index 39f87272e071..c62684d4259c 100644
--- a/net/rds/ib.c
+++ b/net/rds/ib.c
@@ -162,12 +162,12 @@ static int rds_ib_add_one(struct ib_device *device)
IB_ODP_SUPPORT_READ);
rds_ibdev->max_1m_mrs = device->attrs.max_mr ?
- min_t(unsigned int, (device->attrs.max_mr / 2),
- rds_ib_mr_1m_pool_size) : rds_ib_mr_1m_pool_size;
+ min(device->attrs.max_mr / 2,
+ rds_ib_mr_1m_pool_size) : rds_ib_mr_1m_pool_size;
rds_ibdev->max_8k_mrs = device->attrs.max_mr ?
- min_t(unsigned int, ((device->attrs.max_mr / 2) * RDS_MR_8K_SCALE),
- rds_ib_mr_8k_pool_size) : rds_ib_mr_8k_pool_size;
+ min((device->attrs.max_mr / 2) * RDS_MR_8K_SCALE,
+ rds_ib_mr_8k_pool_size) : rds_ib_mr_8k_pool_size;
rds_ibdev->max_initiator_depth = device->attrs.max_qp_init_rd_atom;
rds_ibdev->max_responder_resources = device->attrs.max_qp_rd_atom;
@@ -204,7 +204,7 @@ static int rds_ib_add_one(struct ib_device *device)
goto put_dev;
}
- rdsdebug("RDS/IB: max_mr = %d, max_wrs = %d, max_sge = %d, max_1m_mrs = %d, max_8k_mrs = %d\n",
+ rdsdebug("RDS/IB: max_mr = %u, max_wrs = %d, max_sge = %d, max_1m_mrs = %d, max_8k_mrs = %d\n",
device->attrs.max_mr, rds_ibdev->max_wrs, rds_ibdev->max_sge,
rds_ibdev->max_1m_mrs, rds_ibdev->max_8k_mrs);
diff --git a/net/rds/ib_cm.c b/net/rds/ib_cm.c
index 5667f0173b47..17e587c30076 100644
--- a/net/rds/ib_cm.c
+++ b/net/rds/ib_cm.c
@@ -173,11 +173,11 @@ static void rds_ib_cm_fill_conn_param(struct rds_connection *conn,
memset(conn_param, 0, sizeof(struct rdma_conn_param));
- conn_param->responder_resources =
- min_t(u32, rds_ibdev->max_responder_resources, max_responder_resources);
- conn_param->initiator_depth =
- min_t(u32, rds_ibdev->max_initiator_depth, max_initiator_depth);
- conn_param->retry_count = min_t(unsigned int, rds_ib_retry_count, 7);
+ conn_param->responder_resources = min3(rds_ibdev->max_responder_resources,
+ max_responder_resources, U8_MAX);
+ conn_param->initiator_depth = min3(rds_ibdev->max_initiator_depth,
+ max_initiator_depth, U8_MAX);
+ conn_param->retry_count = min(rds_ib_retry_count, 7U);
conn_param->rnr_retry_count = 7;
if (dp) {
diff --git a/net/sunrpc/xprtrdma/frwr_ops.c b/net/sunrpc/xprtrdma/frwr_ops.c
index e5c71cf705a3..e83cef19e656 100644
--- a/net/sunrpc/xprtrdma/frwr_ops.c
+++ b/net/sunrpc/xprtrdma/frwr_ops.c
@@ -172,8 +172,9 @@ int frwr_mr_init(struct rpcrdma_xprt *r_xprt, struct rpcrdma_mr *mr)
int frwr_query_device(struct rpcrdma_ep *ep, const struct ib_device *device)
{
const struct ib_device_attr *attrs = &device->attrs;
- int max_qp_wr, depth, delta;
unsigned int max_sge;
+ u32 max_qp_wr;
+ int depth, delta;
if (!(attrs->device_cap_flags & IB_DEVICE_MEM_MGT_EXTENSIONS) ||
attrs->max_fast_reg_page_list_len == 0) {
@@ -229,10 +230,10 @@ int frwr_query_device(struct rpcrdma_ep *ep, const struct ib_device *device)
}
max_qp_wr = attrs->max_qp_wr;
+ if (max_qp_wr < RPCRDMA_BACKWARD_WRS + 1 + RPCRDMA_MIN_SLOT_TABLE)
+ return -ENOMEM;
max_qp_wr -= RPCRDMA_BACKWARD_WRS;
max_qp_wr -= 1;
- if (max_qp_wr < RPCRDMA_MIN_SLOT_TABLE)
- return -ENOMEM;
if (ep->re_max_requests > max_qp_wr)
ep->re_max_requests = max_qp_wr;
ep->re_attr.cap.max_send_wr = ep->re_max_requests * depth;
diff --git a/net/sunrpc/xprtrdma/svc_rdma_transport.c b/net/sunrpc/xprtrdma/svc_rdma_transport.c
index 7ca71741106b..e0289adce7f5 100644
--- a/net/sunrpc/xprtrdma/svc_rdma_transport.c
+++ b/net/sunrpc/xprtrdma/svc_rdma_transport.c
@@ -562,8 +562,7 @@ static struct svc_xprt *svc_rdma_accept(struct svc_xprt *xprt)
set_bit(RDMAXPRT_CONN_PENDING, &newxprt->sc_flags);
memset(&conn_param, 0, sizeof conn_param);
conn_param.responder_resources = 0;
- conn_param.initiator_depth = min_t(int, newxprt->sc_ord,
- dev->attrs.max_qp_init_rd_atom);
+ conn_param.initiator_depth = min(newxprt->sc_ord, dev->attrs.max_qp_init_rd_atom);
if (!conn_param.initiator_depth) {
ret = -EINVAL;
trace_svcrdma_initdepth_err(newxprt, ret);
@@ -588,7 +587,7 @@ static struct svc_xprt *svc_rdma_accept(struct svc_xprt *xprt)
dprintk(" local address : %pIS:%u\n", sap, rpc_get_port(sap));
sap = (struct sockaddr *)&newxprt->sc_cm_id->route.addr.dst_addr;
dprintk(" remote address : %pIS:%u\n", sap, rpc_get_port(sap));
- dprintk(" max_sge : %d\n", newxprt->sc_max_send_sges);
+ dprintk(" max_sge : %u\n", newxprt->sc_max_send_sges);
dprintk(" sq_depth : %d\n", newxprt->sc_sq_depth);
dprintk(" rdma_rw_ctxs : %d\n", ctxts);
dprintk(" max_requests : %d\n", newxprt->sc_max_requests);
diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c
index 04b286223b24..be335eed329d 100644
--- a/net/sunrpc/xprtrdma/verbs.c
+++ b/net/sunrpc/xprtrdma/verbs.c
@@ -465,7 +465,7 @@ static int rpcrdma_ep_create(struct rpcrdma_xprt *r_xprt)
/* Client offers RDMA Read but does not initiate */
ep->re_remote_cma.initiator_depth = 0;
ep->re_remote_cma.responder_resources =
- min_t(int, U8_MAX, device->attrs.max_qp_rd_atom);
+ min(device->attrs.max_qp_rd_atom, U8_MAX);
/* Limit transport retries so client can detect server
* GID changes quickly. RPC layer handles re-establishing
--
2.34.1
^ permalink raw reply related
* Re: [PATCH rdma-next v8] RDMA: Change capability fields in ib_device_attr from int to u32
From: Erni Sri Satya Vennela @ 2026-07-03 5:59 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Jason Gunthorpe, Leon Romanovsky, mkalderon, zyjzyj2000, sagi,
mgurtovoy, haris.iqbal, jinpu.wang, bvanassche, kbusch,
Jens Axboe, Christoph Hellwig, kch, smfrench, linkinjeon, metze,
tom, trondmy, anna, chuck.lever, jlayton, neil, okorniev, Dai.Ngo,
achender, davem, edumazet, kuba, pabeni, horms, kees, markzhang,
ebadger, linux-rdma, linux-kernel, target-devel, linux-nvme,
linux-cifs, samba-technical, linux-nfs, netdev, rds-devel,
Jason Gunthorpe
In-Reply-To: <akYocsiHWdH8Tncq@ashevche-desk.local>
On Thu, Jul 02, 2026 at 11:59:30AM +0300, Andy Shevchenko wrote:
> On Wed, Jul 01, 2026 at 11:03:43PM -0700, Erni Sri Satya Vennela wrote:
> > On Fri, Jun 19, 2026 at 01:30:39PM -0700, Erni Sri Satya Vennela wrote:
> > > The capability counter fields in struct ib_device_attr are declared
> > > as signed int, but these values are inherently non-negative. Drivers
> > > maintain their cached caps as u32 and assign them directly into these
> > > int fields; if a cap exceeds INT_MAX the implicit narrowing yields a
> > > negative value visible to the IB core.
> > >
> > > Change the signed int capability fields to u32 to match the
> > > underlying nature of the data. Also update consumers across the IB
> > > core, ULPs, NVMe-oF target, RDS, and NFS/RDMA so the new u32 values
> > > are not forced back through signed int or u8 via min()/min_t() or
> > > narrowing local variables.
> >
> > Just a friendly follow-up on this patch. The Sashiko review mentioned a
> > low-priority item, and I'd appreciate any guidance on whether the change
> > is needed.
>
> As I read this it needs to be fixed, the problem is that there is
> a potential of a weird case (not sure they are IRL) when somebody can use
> INT_MIN (in representation of signed number) to actually mean 0x80000000
> size.
>
> > https://sashiko.dev/#/patchset/20260619203107.606359-1-ernis%40linux.microsoft.com
>
> --
> With Best Regards,
> Andy Shevchenko
>
Thankyou for the confirmation, Andy.
I'll add this change in the next version.
- Vennela
^ permalink raw reply
* Re: [PATCH 1/2] af_unix: Do not wait for garbage collector in sendmsg()
From: Nam Cao @ 2026-07-03 5:53 UTC (permalink / raw)
To: sashiko-reviews, Kuniyuki Iwashima, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev,
linux-kernel, linux-rt-devel
Cc: linux-rt-devel, linux-kernel
In-Reply-To: <20260702163608.0207A1F00A3A@smtp.kernel.org>
sashiko-bot@kernel.org writes:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] Garbage collector is no longer scheduled when creating new inflight FDs, leading to permanent cycle accumulation and -ETOOMANYREFS failures in isolated environments.
> --
>
> commit 506189bd29f8ad56f5d9e51f3e0216203ebad5ec
> Author: Nam Cao <namcao@linutronix.de>
>
> af_unix: Do not wait for garbage collector in sendmsg()
>
> This patch removes the garbage collector scheduling and waiting from the
> sendmsg() path for AF_UNIX sockets. It aims to prevent priority inversion
> by relying on RLIMIT_NOFILE limits and asynchronous garbage collection.
>
>> diff --git a/net/unix/garbage.c b/net/unix/garbage.c
>> index 0783555e25266..f180c59b3da9d 100644
>> --- a/net/unix/garbage.c
>> +++ b/net/unix/garbage.c
>> @@ -300,8 +300,6 @@ int unix_prepare_fpl(struct scm_fp_list *fpl)
>> if (!fpl->edges)
>> goto err;
>>
>> - unix_schedule_gc(fpl->user);
>> -
>
> [Severity: High]
> Does this removal prevent the garbage collector from running when a process
> creates isolated socket cycles?
>
> If a process continuously creates socket cycles (e.g., passing FDs to each
> other) and closes its local FDs, the sockets reference each other in their
> receive queues, so their file reference counts will not reach zero.
>
> Because the refcounts do not drop to zero, unix_release_sock() is never
> triggered. By removing unix_schedule_gc() from unix_prepare_fpl() here,
> unix_release_sock() is left as the only trigger for garbage collection.
>
> This could allow the inflight count to accumulate indefinitely until it
> hits RLIMIT_NOFILE, causing subsequent sendmsg() calls to fail with
> -ETOOMANYREFS and permanently leaking the network namespace unless an
> unrelated AF_UNIX socket is closed system-wide.
Sashiko found a valid issue here. But this is a pre-existing issue. It
is not introduced in this patch.
unix_schedule_gc() does not actually schedule the garbage collector
unless user's inflight unix socket count exceeds UNIX_INFLIGHT_SANE_USER
(2024). So user can already accumulate up to 2023 inflight socket counts
without the garbage collector running until unix_release_sock() is called.
Nam
^ permalink raw reply
* [PATCH net-next v2 11/12] net: mctp: usb: enable v1.1 packet spanning
From: Jeremy Kerr @ 2026-07-03 5:47 UTC (permalink / raw)
To: Matt Johnston, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Greg Kroah-Hartman
Cc: netdev, linux-usb
In-Reply-To: <20260703-dev-mctp-usb-1-1-v2-0-60367b861b33@codeconstruct.com.au>
Now that mctp-usblib supports DSP0283 v1.1 packet spanning, enable it in
our host-side transport driver.
Add a match for the new device subclass (0x02), and indicating spanning
mode to the usblib rx/tx implementation.
Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
---
drivers/net/mctp/mctp-usb.c | 26 +++++++++++++++++++++-----
1 file changed, 21 insertions(+), 5 deletions(-)
diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c
index e071c2f6daea..4bf5546d520c 100644
--- a/drivers/net/mctp/mctp-usb.c
+++ b/drivers/net/mctp/mctp-usb.c
@@ -3,9 +3,9 @@
* mctp-usb.c - MCTP-over-USB (DMTF DSP0283) transport binding driver.
*
* DSP0283 is available at:
- * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.0.1.pdf
+ * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.1.0.pdf
*
- * Copyright (C) 2024-2025 Code Construct Pty Ltd
+ * Copyright (C) 2024-2026 Code Construct Pty Ltd
*/
#include <linux/module.h>
@@ -22,6 +22,7 @@
struct mctp_usb {
struct usb_device *usbdev;
struct usb_interface *intf;
+ bool span;
struct net_device *netdev;
@@ -40,6 +41,11 @@ struct mctp_usb {
struct usb_anchor tx_anchor;
};
+enum {
+ MCTP_USB_SUBCLASS_BASE = 0x00,
+ MCTP_USB_SUBCLASS_SPAN = 0x02,
+};
+
static void mctp_usb_out_complete(struct urb *urb)
{
struct mctp_usblib_tx_ctx *tx_ctx = urb->context;
@@ -68,6 +74,9 @@ static int mctp_usb_tx_send(struct mctp_usblib_tx_ctx *tx_ctx,
usb_sndbulkpipe(mctp_usb->usbdev, mctp_usb->ep_out),
data, len, mctp_usb_out_complete, tx_ctx);
+ if (mctp_usb->span)
+ urb->transfer_flags |= URB_ZERO_PACKET;
+
netif_stop_queue(mctp_usb->netdev);
usb_anchor_urb(urb, &mctp_usb->tx_anchor);
@@ -246,6 +255,7 @@ static int mctp_usb_probe(struct usb_interface *intf,
struct usb_host_interface *iface_desc;
struct net_device *netdev;
struct mctp_usb *dev;
+ bool span;
int rc;
/* only one alternate */
@@ -257,6 +267,8 @@ static int mctp_usb_probe(struct usb_interface *intf,
return rc;
}
+ span = iface_desc->desc.bInterfaceSubClass == MCTP_USB_SUBCLASS_SPAN;
+
netdev = alloc_netdev(sizeof(*dev), "mctpusb%d", NET_NAME_ENUM,
mctp_usb_netdev_setup);
if (!netdev)
@@ -264,14 +276,17 @@ static int mctp_usb_probe(struct usb_interface *intf,
SET_NETDEV_DEV(netdev, &intf->dev);
dev = netdev_priv(netdev);
+ dev->span = span;
dev->netdev = netdev;
dev->usbdev = interface_to_usbdev(intf);
dev->intf = intf;
+ if (dev->span)
+ netdev->max_mtu = MCTP_USB_1_1_MTU_MAX;
spin_lock_init(&dev->rx_lock);
usb_set_intfdata(intf, dev);
- mctp_usblib_rx_init(&dev->rx, false);
- mctp_usblib_tx_init(&dev->tx, &tx_ops, dev, false);
+ mctp_usblib_rx_init(&dev->rx, dev->span);
+ mctp_usblib_tx_init(&dev->tx, &tx_ops, dev, dev->span);
init_usb_anchor(&dev->tx_anchor);
dev->ep_in = ep_in->bEndpointAddress;
@@ -312,7 +327,8 @@ static void mctp_usb_disconnect(struct usb_interface *intf)
}
static const struct usb_device_id mctp_usb_devices[] = {
- { USB_INTERFACE_INFO(USB_CLASS_MCTP, 0x0, 0x1) },
+ { USB_INTERFACE_INFO(USB_CLASS_MCTP, MCTP_USB_SUBCLASS_BASE, 0x1) },
+ { USB_INTERFACE_INFO(USB_CLASS_MCTP, MCTP_USB_SUBCLASS_SPAN, 0x1) },
{ 0 },
};
--
2.47.3
^ permalink raw reply related
* [PATCH net-next v2 10/12] net: mctp: usblib: Add initial kunit tests
From: Jeremy Kerr @ 2026-07-03 5:47 UTC (permalink / raw)
To: Matt Johnston, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Greg Kroah-Hartman
Cc: netdev, linux-usb
In-Reply-To: <20260703-dev-mctp-usb-1-1-v2-0-60367b861b33@codeconstruct.com.au>
Add some initial tests for the usblib receive path, where we're
extracting MCTP packets from incoming USB transfer data.
Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
---
v2:
- account for KUNIT_ASSERT-based exits; do cleanup through kunit_action
facilities.
- fix off-by-one in packet count duing skb length checks, in case we
ended up with more skbs than expected
- perform route updates under rntl lock
---
drivers/net/mctp/Kconfig | 5 +
drivers/net/mctp/mctp-usblib-test.c | 389 ++++++++++++++++++++++++++++++++++++
drivers/net/mctp/mctp-usblib.c | 4 +
3 files changed, 398 insertions(+)
diff --git a/drivers/net/mctp/Kconfig b/drivers/net/mctp/Kconfig
index a564a792801d..c40ac9c665b7 100644
--- a/drivers/net/mctp/Kconfig
+++ b/drivers/net/mctp/Kconfig
@@ -57,6 +57,11 @@ config MCTP_TRANSPORT_USBLIB
This will be automatically enabled by the transport driver.
+config MCTP_TRANSPORT_USBLIB_TEST
+ bool "MCTP usblib tests" if !KUNIT_ALL_TESTS
+ depends on MCTP_TRANSPORT_USBLIB=y && KUNIT=y
+ default KUNIT_ALL_TESTS
+
config MCTP_TRANSPORT_USB
tristate "MCTP USB transport"
depends on USB
diff --git a/drivers/net/mctp/mctp-usblib-test.c b/drivers/net/mctp/mctp-usblib-test.c
new file mode 100644
index 000000000000..17aec83e59e3
--- /dev/null
+++ b/drivers/net/mctp/mctp-usblib-test.c
@@ -0,0 +1,389 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mctp-usblib-test.c - MCTP-over-USB (DMTF DSP0283) transport helper library,
+ * unit test definitions.
+ *
+ * Copyright (C) 2026 Code Construct Pty Ltd
+ */
+
+#include <uapi/linux/netdevice.h>
+#include <linux/netdevice.h>
+#include <kunit/test.h>
+#include <linux/if_arp.h>
+#include <net/mctp.h>
+#include <net/mctpdevice.h>
+#include <linux/usb/mctp-usb.h>
+
+struct mctp_usblib_test_dev {
+ struct net_device *ndev;
+ struct mctp_dev *mdev;
+ struct sk_buff_head rx_pkts;
+};
+
+struct mctp_usblib_test_ctx {
+ struct mctp_usblib_test_dev *dev;
+ struct mctp_route rt;
+};
+
+static netdev_tx_t mctp_usblib_dev_tx(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ /* we don't track any TXed packets at present */
+ kfree_skb(skb);
+ return NETDEV_TX_OK;
+}
+
+static const struct net_device_ops mctp_test_netdev_ops = {
+ .ndo_start_xmit = mctp_usblib_dev_tx,
+};
+
+static const mctp_eid_t local_eid = 8;
+
+static void mctp_usblib_dev_setup(struct net_device *ndev)
+{
+ ndev->type = ARPHRD_MCTP;
+ ndev->mtu = 8192;
+ ndev->flags = IFF_NOARP;
+ ndev->netdev_ops = &mctp_test_netdev_ops;
+ ndev->needs_free_netdev = true;
+ ndev->pcpu_stat_type = NETDEV_PCPU_STAT_DSTATS;
+}
+
+static void mctp_usblib_test_dev_action(void *data)
+{
+ struct mctp_usblib_test_dev *dev = data;
+
+ skb_queue_purge(&dev->rx_pkts);
+ if (dev->mdev)
+ mctp_dev_put(dev->mdev);
+ unregister_netdev(dev->ndev);
+}
+
+static struct mctp_usblib_test_dev *
+mctp_usblib_test_create_dev(struct kunit *test)
+{
+ struct mctp_usblib_test_dev *dev;
+ struct net_device *ndev;
+ int rc;
+
+ ndev = alloc_netdev(sizeof(*dev), "mctptest%d", NET_NAME_ENUM,
+ mctp_usblib_dev_setup);
+ if (!ndev)
+ return NULL;
+
+ dev = netdev_priv(ndev);
+ dev->ndev = ndev;
+ skb_queue_head_init(&dev->rx_pkts);
+
+ rc = register_netdev(ndev);
+ if (rc) {
+ free_netdev(ndev);
+ return NULL;
+ }
+
+ rc = kunit_add_action_or_reset(test, mctp_usblib_test_dev_action, dev);
+ if (rc)
+ return NULL;
+
+ rcu_read_lock();
+ dev->mdev = __mctp_dev_get(ndev);
+ if (dev->mdev)
+ dev->mdev->net = mctp_default_net(dev_net(ndev));
+ rcu_read_unlock();
+
+ if (!dev->mdev)
+ return NULL;
+
+ rtnl_lock();
+ rc = dev_open(ndev, NULL);
+ rtnl_unlock();
+ if (rc)
+ return NULL;
+
+ return dev;
+}
+
+static int mctp_usblib_test_dst_output(struct mctp_dst *dst,
+ struct sk_buff *skb)
+{
+ struct mctp_usblib_test_dev *dev = netdev_priv(skb->dev);
+
+ skb_queue_tail(&dev->rx_pkts, skb);
+
+ return 0;
+}
+
+static void mctp_usblib_test_fini_action(void *data)
+{
+ struct mctp_usblib_test_ctx *ctx = data;
+
+ /* The device will have been destroyed, so ->rt will be unlinked.
+ * Just ensure that the refcount is as expected.
+ */
+ KUNIT_ASSERT_TRUE(current->kunit_test,
+ refcount_dec_and_test(&ctx->rt.refs));
+
+ kfree(ctx);
+}
+
+static struct mctp_usblib_test_ctx *mctp_usblib_test_init(struct kunit *test)
+{
+ struct mctp_usblib_test_ctx *ctx;
+ struct mctp_route *rt;
+ int rc;
+
+ ctx = kzalloc_obj(*ctx);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+ INIT_LIST_HEAD(&ctx->rt.list);
+
+ rc = kunit_add_action_or_reset(test, mctp_usblib_test_fini_action, ctx);
+ KUNIT_ASSERT_EQ(test, rc, 0);
+
+ ctx->dev = mctp_usblib_test_create_dev(test);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->dev);
+
+ rt = &ctx->rt;
+ refcount_set(&rt->refs, 1);
+
+ rt->min = local_eid;
+ rt->max = local_eid;
+ rt->dst_type = MCTP_ROUTE_DIRECT;
+ rt->type = RTN_LOCAL;
+ rt->dev = ctx->dev->mdev;
+ rt->output = mctp_usblib_test_dst_output;
+
+ rtnl_lock();
+ list_add_rcu(&ctx->rt.list, &init_net.mctp.routes);
+ refcount_inc(&rt->refs);
+ rtnl_unlock();
+
+ return ctx;
+}
+
+/* Init a MCTP-over-USB packet within a buffer. @len is the length of the
+ * buffer to write, @payload_len is the reported size of the MCTP-over-USB
+ * packet.
+ */
+static void mctp_usblib_test_init_pkt(void *data, size_t len,
+ size_t payload_len)
+{
+ struct {
+ struct mctp_usb_hdr usb;
+ struct mctp_hdr mctp;
+ } hdr;
+
+ hdr.usb.id = cpu_to_be16(MCTP_USB_DMTF_ID);
+ hdr.usb.len = cpu_to_be16(payload_len);
+ hdr.mctp.ver = 1;
+ hdr.mctp.dest = local_eid;
+ hdr.mctp.src = 0;
+ hdr.mctp.flags_seq_tag = 0;
+
+ memcpy(data, &hdr, min(len, sizeof(hdr)));
+ if (len > sizeof(hdr))
+ memset(data + sizeof(hdr), 0, len - sizeof(hdr));
+}
+
+static void action_rx_fini(void *data)
+{
+ struct mctp_usblib_rx *rx = data;
+
+ mctp_usblib_rx_fini(rx);
+ kfree(rx);
+}
+
+static struct mctp_usblib_rx *
+mctp_usblib_test_rx_init(struct kunit *test, bool span)
+{
+ struct mctp_usblib_rx *rx;
+ int rc;
+
+ rx = kzalloc_obj(*rx);
+ if (rx) {
+ rc = kunit_add_action_or_reset(test, action_rx_fini, rx);
+ KUNIT_ASSERT_EQ(test, rc, 0);
+ }
+ KUNIT_ASSERT_NOT_NULL(test, rx);
+ mctp_usblib_rx_init(rx, span);
+
+ return rx;
+}
+
+/* Single packet, starting on a transfer boundary, contained entirely within
+ * the transfer
+ */
+static void mctp_usblib_test_rx_single(struct kunit *test)
+{
+ struct mctp_usblib_test_dev *dev;
+ struct mctp_usblib_test_ctx *ctx;
+ struct mctp_usblib_rx *rx;
+ struct sk_buff *skb;
+ size_t len;
+ void *buf;
+ int rc;
+
+ ctx = mctp_usblib_test_init(test);
+ dev = ctx->dev;
+
+ rx = mctp_usblib_test_rx_init(test, true);
+
+ rc = mctp_usblib_rx_prepare(dev->ndev, rx, &buf, &len, GFP_KERNEL);
+ KUNIT_ASSERT_EQ(test, rc, 0);
+
+ mctp_usblib_test_init_pkt(buf, 8, 8);
+
+ rc = mctp_usblib_rx_complete(dev->ndev, rx, 8);
+ KUNIT_ASSERT_EQ(test, rc, 0);
+
+ skb = __skb_dequeue(&dev->rx_pkts);
+ KUNIT_EXPECT_NOT_NULL(test, skb);
+ if (skb)
+ KUNIT_EXPECT_EQ(test, skb->len, 4);
+ kfree_skb(skb);
+}
+
+struct mctp_usblib_test_pkt_span {
+ const char *name;
+ size_t n_pkts;
+ size_t pkts[6];
+ size_t n_xfers;
+ size_t xfers[6];
+};
+
+static void
+mctp_usblib_test_pkt_span_to_desc(const struct mctp_usblib_test_pkt_span *t,
+ char *desc)
+{
+ strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+static void
+mctp_usblib_test_pkt_span_validate(struct kunit *test,
+ const struct mctp_usblib_test_pkt_span *span,
+ size_t *len)
+{
+ size_t pkt_len = 0, xfer_len = 0;
+ unsigned int i;
+
+ for (i = 0; i < span->n_pkts; i++) {
+ KUNIT_ASSERT_GE_MSG(test, span->pkts[i], 8,
+ "pkt[%d] len too small (%zd) for %s",
+ i, span->pkts[i], span->name);
+ pkt_len += span->pkts[i];
+ }
+
+ for (i = 0; i < span->n_xfers; i++)
+ xfer_len += span->xfers[i];
+
+ KUNIT_ASSERT_EQ_MSG(test, pkt_len, xfer_len,
+ "invalid pkt_len (%zd) != xfer_len (%zd) for %s",
+ pkt_len, xfer_len, span->name);
+
+ *len = pkt_len;
+}
+
+static void mctp_usblib_test_rx_pkt_span(struct kunit *test)
+{
+ const struct mctp_usblib_test_pkt_span *pkt_span = test->param_value;
+ size_t len, xfer_len, off, xfer_off;
+ struct mctp_usblib_test_dev *dev;
+ struct mctp_usblib_test_ctx *ctx;
+ struct mctp_usblib_rx *rx;
+ unsigned int i;
+ u8 *pktbuf;
+ void *buf;
+ int rc;
+
+ mctp_usblib_test_pkt_span_validate(test, pkt_span, &len);
+ pktbuf = kunit_kmalloc_array(test, 1, len, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, pktbuf);
+
+ /* lay out packets */
+ for (off = 0, i = 0; i < pkt_span->n_pkts; i++) {
+ len = pkt_span->pkts[i];
+ mctp_usblib_test_init_pkt(pktbuf + off, len, len);
+ off += len;
+ }
+
+ ctx = mctp_usblib_test_init(test);
+ dev = ctx->dev;
+
+ rx = mctp_usblib_test_rx_init(test, true);
+
+ /* feed transfers */
+ for (off = 0, xfer_off = 0, i = 0; i < pkt_span->n_xfers;) {
+ xfer_len = pkt_span->xfers[i] - xfer_off;
+ rc = mctp_usblib_rx_prepare(dev->ndev, rx, &buf, &len,
+ GFP_KERNEL);
+ KUNIT_ASSERT_EQ(test, rc, 0);
+
+ len = min(len, xfer_len);
+ memcpy(buf, pktbuf + off, len);
+
+ if (len == xfer_len) {
+ /* whole/end xfer, proceed to next */
+ xfer_off = 0;
+ i++;
+ } else {
+ /* partial */
+ xfer_off += len;
+ }
+
+ rc = mctp_usblib_rx_complete(dev->ndev, rx, len);
+ KUNIT_ASSERT_EQ(test, rc, 0);
+ off += len;
+ }
+
+ /* check received packets */
+ KUNIT_EXPECT_EQ(test, dev->rx_pkts.qlen, pkt_span->n_pkts);
+ for (i = 0; ; i++) {
+ struct sk_buff *skb = __skb_dequeue(&dev->rx_pkts);
+
+ if (!skb)
+ break;
+
+ if (i < pkt_span->n_pkts)
+ KUNIT_EXPECT_EQ(test, skb->len, pkt_span->pkts[i] - 4);
+
+ kfree_skb(skb);
+ }
+}
+
+static const struct mctp_usblib_test_pkt_span mctp_usblib_test_pkt_spans[] = {
+ /* One packet completely within a transfer */
+ { "1p1x-complete", 1, { 8 }, 1, { 8 } },
+ /* Two small packets combined within one transfer */
+ { "2p1x-combined", 2, { 8, 8 }, 1, { 16 } },
+ /* A packet split over two transfers, at the MCTP payload */
+ { "1p2x-split-payload", 1, { 16 }, 2, { 8, 8 } },
+ /* A packet split over two transfers, at the USB transport header */
+ { "1p2x-split-usbhdr", 1, { 16 }, 2, { 2, 14 } },
+ /* A packet split over two transfers, at the MCTP header */
+ { "1p2x-split-mctphdr", 1, { 16 }, 2, { 6, 10 } },
+ /* Single packet split over 3 transfers, middle entirely continuation */
+ { "1p3x-split", 1, { 12 }, 3, { 4, 4, 4 } },
+ /* Max-sized single transfer */
+ { "1p1x-large", 1, { 8191 }, 1, { 8191 } },
+ /* Two large packets, split at the worst-case for allocation, with a
+ * single byte continuing the span
+ */
+ { "2p2x-large-split", 2, { 8190, 8190 }, 2, { 8191, 8189 } },
+};
+
+KUNIT_ARRAY_PARAM(mctp_usblib_test_rx_pkt_span, mctp_usblib_test_pkt_spans,
+ mctp_usblib_test_pkt_span_to_desc);
+
+static struct kunit_case mctp_usblib_test_cases[] = {
+ KUNIT_CASE(mctp_usblib_test_rx_single),
+ KUNIT_CASE_PARAM(mctp_usblib_test_rx_pkt_span,
+ mctp_usblib_test_rx_pkt_span_gen_params),
+ {}
+};
+
+static struct kunit_suite mctp_usblib_test_suite = {
+ .name = "mctp-usblib",
+ .test_cases = mctp_usblib_test_cases,
+};
+
+kunit_test_suite(mctp_usblib_test_suite);
diff --git a/drivers/net/mctp/mctp-usblib.c b/drivers/net/mctp/mctp-usblib.c
index d9d08c59028e..79245294fd21 100644
--- a/drivers/net/mctp/mctp-usblib.c
+++ b/drivers/net/mctp/mctp-usblib.c
@@ -583,3 +583,7 @@ EXPORT_SYMBOL_GPL(mctp_usblib_tx_cancel);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jeremy Kerr <jk@codeconstruct.com.au>");
MODULE_DESCRIPTION("MCTP USB transport library");
+
+#if IS_ENABLED(CONFIG_MCTP_TRANSPORT_USBLIB_TEST)
+#include "mctp-usblib-test.c"
+#endif
--
2.47.3
^ permalink raw reply related
* [PATCH net-next v2 12/12] net: mctp: usb: Allow multiple urbs in flight
From: Jeremy Kerr @ 2026-07-03 5:47 UTC (permalink / raw)
To: Matt Johnston, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Greg Kroah-Hartman
Cc: netdev, linux-usb
In-Reply-To: <20260703-dev-mctp-usb-1-1-v2-0-60367b861b33@codeconstruct.com.au>
Currently, we stop tx queues when we have one urb submitted. This means
we will immediately hit dev_hard_start_xmit's tx-queues-off ->
NETDEV_TX_BUSY case, and revert to the requeue -> gso_skb single-dequeue
path, and no longer be able to pack skbs without an xmit_more
indication.
Instead, allow a few urbs to be in-flight, with a limit of 16kB of data
outstanding (after which we will disable queues). With this, the tx path
will cause fewer requeues (and therefore non-packed transfers) under
normal loads.
Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
---
v2:
- rework tx_qmem locking, prevent race between completion and
submission for stopping/waking the tx queues; perform the wake while
locked
- only wake if netif_running()
---
drivers/net/mctp/mctp-usb.c | 33 +++++++++++++++++++++++++++++----
1 file changed, 29 insertions(+), 4 deletions(-)
diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c
index 4bf5546d520c..fd33f1b97e23 100644
--- a/drivers/net/mctp/mctp-usb.c
+++ b/drivers/net/mctp/mctp-usb.c
@@ -39,6 +39,9 @@ struct mctp_usb {
struct mctp_usblib_tx tx;
struct usb_anchor tx_anchor;
+ /* serialises tx_qmem updates to netdev queue states */
+ spinlock_t tx_qmem_lock;
+ int tx_qmem;
};
enum {
@@ -46,23 +49,41 @@ enum {
MCTP_USB_SUBCLASS_SPAN = 0x02,
};
+/* We use a total-size limit for outstanding URBs, as the transfer counts
+ * may vary a lot between spanning- and non-spanning modes. In spanning mode,
+ * this will allow for a couple of max-sized transfers to be in flight. In
+ * non-spanning mode, 32.
+ *
+ * We want to avoid disabling the tx queue if possible; doing so will end up
+ * requeueing to gso_skb, and we only dequeue from that one skb at a time,
+ * so can no longer perform transfer packing.
+ */
+static const unsigned int TX_QMEM_MAX = 16384;
+
static void mctp_usb_out_complete(struct urb *urb)
{
struct mctp_usblib_tx_ctx *tx_ctx = urb->context;
struct mctp_usb *mctp_usb = mctp_usblib_tx_ctx_priv(tx_ctx);
+ unsigned int len = urb->transfer_buffer_length;
struct net_device *netdev = mctp_usb->netdev;
+ unsigned long flags;
mctp_usblib_tx_send_complete(tx_ctx, netdev, urb->status == 0);
usb_free_urb(urb);
- netif_wake_queue(netdev);
+ spin_lock_irqsave(&mctp_usb->tx_qmem_lock, flags);
+ mctp_usb->tx_qmem -= len;
+ if (mctp_usb->tx_qmem < TX_QMEM_MAX && netif_running(netdev))
+ netif_wake_queue(netdev);
+ spin_unlock_irqrestore(&mctp_usb->tx_qmem_lock, flags);
}
static int mctp_usb_tx_send(struct mctp_usblib_tx_ctx *tx_ctx,
void *data, size_t len)
{
struct mctp_usb *mctp_usb = mctp_usblib_tx_ctx_priv(tx_ctx);
+ unsigned long flags;
struct urb *urb;
int rc;
@@ -77,8 +98,6 @@ static int mctp_usb_tx_send(struct mctp_usblib_tx_ctx *tx_ctx,
if (mctp_usb->span)
urb->transfer_flags |= URB_ZERO_PACKET;
- netif_stop_queue(mctp_usb->netdev);
-
usb_anchor_urb(urb, &mctp_usb->tx_anchor);
rc = usb_submit_urb(urb, GFP_ATOMIC);
@@ -86,7 +105,12 @@ static int mctp_usb_tx_send(struct mctp_usblib_tx_ctx *tx_ctx,
netdev_dbg(mctp_usb->netdev, "TX urb submit failed, %d\n", rc);
usb_unanchor_urb(urb);
usb_free_urb(urb);
- netif_start_queue(mctp_usb->netdev);
+ } else {
+ spin_lock_irqsave(&mctp_usb->tx_qmem_lock, flags);
+ mctp_usb->tx_qmem += len;
+ if (mctp_usb->tx_qmem >= TX_QMEM_MAX)
+ netif_stop_queue(mctp_usb->netdev);
+ spin_unlock_irqrestore(&mctp_usb->tx_qmem_lock, flags);
}
return rc;
@@ -283,6 +307,7 @@ static int mctp_usb_probe(struct usb_interface *intf,
if (dev->span)
netdev->max_mtu = MCTP_USB_1_1_MTU_MAX;
spin_lock_init(&dev->rx_lock);
+ spin_lock_init(&dev->tx_qmem_lock);
usb_set_intfdata(intf, dev);
mctp_usblib_rx_init(&dev->rx, dev->span);
--
2.47.3
^ permalink raw reply related
* [PATCH net-next v2 09/12] net: mctp: usblib: Implement transmit-side packet spanning
From: Jeremy Kerr @ 2026-07-03 5:47 UTC (permalink / raw)
To: Matt Johnston, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Greg Kroah-Hartman
Cc: netdev, linux-usb
In-Reply-To: <20260703-dev-mctp-usb-1-1-v2-0-60367b861b33@codeconstruct.com.au>
Add support for packet spanning as defined in DSP0283 v1.1.
With the existing v1.0 implementation of multi-packet transfers, all we
need here is to adjust the buffer sizes to suit v1.1.
Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
---
drivers/net/mctp/mctp-usb.c | 2 +-
drivers/net/mctp/mctp-usblib.c | 28 +++++++++++++++++++---------
include/linux/usb/mctp-usb.h | 4 +++-
3 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c
index 2d0b8d12b0f5..e071c2f6daea 100644
--- a/drivers/net/mctp/mctp-usb.c
+++ b/drivers/net/mctp/mctp-usb.c
@@ -271,7 +271,7 @@ static int mctp_usb_probe(struct usb_interface *intf,
usb_set_intfdata(intf, dev);
mctp_usblib_rx_init(&dev->rx, false);
- mctp_usblib_tx_init(&dev->tx, &tx_ops, dev);
+ mctp_usblib_tx_init(&dev->tx, &tx_ops, dev, false);
init_usb_anchor(&dev->tx_anchor);
dev->ep_in = ep_in->bEndpointAddress;
diff --git a/drivers/net/mctp/mctp-usblib.c b/drivers/net/mctp/mctp-usblib.c
index 9a86edf02a78..d9d08c59028e 100644
--- a/drivers/net/mctp/mctp-usblib.c
+++ b/drivers/net/mctp/mctp-usblib.c
@@ -213,7 +213,7 @@ EXPORT_SYMBOL_GPL(mctp_usblib_rx_cancel);
struct mctp_usblib_tx_ctx {
struct mctp_usblib_tx *tx;
struct sk_buff_head skbs;
- unsigned int len;
+ unsigned int buf_len, len;
enum mctp_usblib_tx_buf_type {
TX_SINGLE,
TX_FLAT,
@@ -223,18 +223,19 @@ struct mctp_usblib_tx_ctx {
void mctp_usblib_tx_init(struct mctp_usblib_tx *tx,
const struct mctp_usblib_tx_ops *ops,
- void *priv)
+ void *priv, bool span)
{
memset(tx, 0, sizeof(*tx));
tx->ops = *ops;
tx->priv = priv;
+ tx->span = span;
spin_lock_init(&tx->lock);
}
EXPORT_SYMBOL_GPL(mctp_usblib_tx_init);
static int mctp_usblib_tx_avail(struct mctp_usblib_tx_ctx *ctx)
{
- return ctx->buf_type == TX_SINGLE ? 0 : MCTP_USB_1_0_XFER_SIZE - ctx->len;
+ return ctx->buf_type == TX_SINGLE ? 0 : ctx->buf_len - ctx->len;
}
static bool mctp_usblib_tx_should_send(struct mctp_usblib_tx_ctx *ctx)
@@ -323,6 +324,12 @@ void mctp_usblib_tx_fini(struct mctp_usblib_tx *tx)
}
EXPORT_SYMBOL_GPL(mctp_usblib_tx_fini);
+/* Max size of a spanned TX. Since we allocate a separate span buffer, limit
+ * the tx-time allocations to 4k. Larger packets will be sent as single
+ * transfers.
+ */
+static const unsigned int TX_SPAN_MAX = 4096 - sizeof(struct mctp_usblib_tx_ctx);
+
static struct mctp_usblib_tx_ctx *
mctp_usblib_tx_ctx_create(struct mctp_usblib_tx *tx, struct sk_buff *skb,
bool single)
@@ -331,11 +338,11 @@ mctp_usblib_tx_ctx_create(struct mctp_usblib_tx *tx, struct sk_buff *skb,
struct mctp_usblib_tx_ctx *ctx;
size_t sz = 0;
- if (single) {
+ if (single || skb->len > TX_SPAN_MAX) {
type = TX_SINGLE;
} else {
type = TX_FLAT;
- sz = MCTP_USB_1_0_XFER_SIZE;
+ sz = tx->span ? TX_SPAN_MAX : MCTP_USB_1_0_XFER_SIZE;
}
ctx = kzalloc_flex(*ctx, buf, sz, GFP_ATOMIC);
@@ -344,6 +351,7 @@ mctp_usblib_tx_ctx_create(struct mctp_usblib_tx *tx, struct sk_buff *skb,
ctx->tx = tx;
ctx->buf_type = type;
+ ctx->buf_len = sz;
ctx->len = skb->len;
skb_queue_head_init(&ctx->skbs);
__skb_queue_tail(&ctx->skbs, skb);
@@ -408,15 +416,17 @@ EXPORT_SYMBOL_GPL(mctp_usblib_tx_send_complete);
*
* On error, populates @reason.
*/
-static int mctp_usblib_tx_skb_prepare(struct sk_buff *skb,
+static int mctp_usblib_tx_skb_prepare(struct sk_buff *skb, bool span,
enum skb_drop_reason *reason)
{
+ unsigned long plen, max_len;
struct mctp_usb_hdr *hdr;
- unsigned long plen;
int rc;
+ max_len = span ? MCTP_USB_1_1_PKTLEN_MAX : MCTP_USB_1_0_PKTLEN_MAX;
+
plen = skb->len;
- if (plen + sizeof(*hdr) > MCTP_USB_1_0_PKTLEN_MAX) {
+ if (plen + sizeof(*hdr) > max_len) {
*reason = SKB_DROP_REASON_PKT_TOO_BIG;
return -EMSGSIZE;
}
@@ -455,7 +465,7 @@ int mctp_usblib_tx_push(struct net_device *dev,
unsigned long flags;
int try = 1, rc;
- rc = mctp_usblib_tx_skb_prepare(skb, &reason);
+ rc = mctp_usblib_tx_skb_prepare(skb, tx->span, &reason);
if (rc) {
mctp_usblib_tx_stats_single_drop(dev);
kfree_skb_reason(skb, reason);
diff --git a/include/linux/usb/mctp-usb.h b/include/linux/usb/mctp-usb.h
index 7ee979bcf05d..0df6f148c5ce 100644
--- a/include/linux/usb/mctp-usb.h
+++ b/include/linux/usb/mctp-usb.h
@@ -84,6 +84,7 @@ struct mctp_usblib_tx_ops {
struct mctp_usblib_tx {
struct mctp_usblib_tx_ops ops;
void *priv;
+ bool span;
/* protects access to cur_ctx */
spinlock_t lock;
/* context to which we are adding packets, cleared on send */
@@ -91,7 +92,8 @@ struct mctp_usblib_tx {
};
void mctp_usblib_tx_init(struct mctp_usblib_tx *tx,
- const struct mctp_usblib_tx_ops *ops, void *priv);
+ const struct mctp_usblib_tx_ops *ops, void *priv,
+ bool span);
void mctp_usblib_tx_fini(struct mctp_usblib_tx *tx);
void *mctp_usblib_tx_ctx_priv(struct mctp_usblib_tx_ctx *tx_ctx);
--
2.47.3
^ permalink raw reply related
* [PATCH net-next v2 08/12] net: mctp: usblib: Implement receive-side packet spanning
From: Jeremy Kerr @ 2026-07-03 5:47 UTC (permalink / raw)
To: Matt Johnston, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Greg Kroah-Hartman
Cc: netdev, linux-usb
In-Reply-To: <20260703-dev-mctp-usb-1-1-v2-0-60367b861b33@codeconstruct.com.au>
Using the existing prepare/complete API, we can persist the rx skb
across receives to implement v1.1 packet spanning.
Alter the packet-extraction loop to allow truncated packets, returning
early with the skb persisted for the next IN urb completion. When we see
we have a complete packet, netif_rx() that. If the packet boundary
aligns with the urb completion, we can netif_rx() the whole thing.
One subtle change: the mctp_usblib_rx() helper now handles skbs with the
full transport header, so we shift the skb_pull() for the header data to
the helper, before doing the rx_bytes stats update.
We still need to handle non-spanning mode, so error out on
truncated-packet cases there.
Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
---
v2:
- note change in semantics for mctp_usblib_rx
- reject rx packets too short for a MCTP header, rather than deferring
to the MCTP core do do so
---
drivers/net/mctp/mctp-usb.c | 2 +-
drivers/net/mctp/mctp-usblib.c | 141 +++++++++++++++++++++++++++--------------
include/linux/usb/mctp-usb.h | 5 +-
3 files changed, 98 insertions(+), 50 deletions(-)
diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c
index e26ddeac9c73..2d0b8d12b0f5 100644
--- a/drivers/net/mctp/mctp-usb.c
+++ b/drivers/net/mctp/mctp-usb.c
@@ -270,7 +270,7 @@ static int mctp_usb_probe(struct usb_interface *intf,
spin_lock_init(&dev->rx_lock);
usb_set_intfdata(intf, dev);
- mctp_usblib_rx_init(&dev->rx);
+ mctp_usblib_rx_init(&dev->rx, false);
mctp_usblib_tx_init(&dev->tx, &tx_ops, dev);
init_usb_anchor(&dev->tx_anchor);
diff --git a/drivers/net/mctp/mctp-usblib.c b/drivers/net/mctp/mctp-usblib.c
index 13dbeb84a0b7..9a86edf02a78 100644
--- a/drivers/net/mctp/mctp-usblib.c
+++ b/drivers/net/mctp/mctp-usblib.c
@@ -3,7 +3,7 @@
* mctp-usblib.c - MCTP-over-USB (DMTF DSP0283) transport helper library
*
* DSP0283 is available at:
- * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.0.1.pdf
+ * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.1.0.pdf
*
* Copyright (C) 2024-2026 Code Construct Pty Ltd
*/
@@ -13,9 +13,10 @@
#include <linux/usb/mctp-usb.h>
#include <net/mctp.h>
-void mctp_usblib_rx_init(struct mctp_usblib_rx *rx)
+void mctp_usblib_rx_init(struct mctp_usblib_rx *rx, bool span)
{
memset(rx, 0, sizeof(*rx));
+ rx->span = span;
}
EXPORT_SYMBOL_GPL(mctp_usblib_rx_init);
@@ -33,15 +34,39 @@ int mctp_usblib_rx_prepare(struct net_device *netdev,
struct mctp_usblib_rx *rx,
void **bufp, size_t *lenp, gfp_t gfp)
{
- const unsigned int len = MCTP_USB_1_0_XFER_SIZE;
- struct sk_buff *skb;
+ struct sk_buff *skb = rx->skb;
+ unsigned int len = 0;
- skb = __netdev_alloc_skb(netdev, len, gfp);
- if (!skb)
- return -ENOMEM;
+ if (skb && skb->len >= MCTP_USB_1_1_PKTLEN_MAX) {
+ /* something must have gone terribly wrong. clear and restart */
+ mctp_usblib_rx_cancel(rx);
+ skb = NULL;
+ }
+
+ len = rx->span ? MCTP_USB_1_1_PKTLEN_MAX : MCTP_USB_1_0_XFER_SIZE;
+
+ if (skb) {
+ struct sk_buff *skb2;
+
+ skb2 = skb_copy_expand(skb, 0, len, gfp);
+ if (!skb2)
+ return -ENOMEM;
+ dev_kfree_skb_any(skb);
+ skb = skb2;
+ } else {
+ skb = __netdev_alloc_skb(netdev, len, gfp);
+ if (!skb)
+ return -ENOMEM;
+ }
rx->skb = skb;
+ /* Spanning mode allows ZLPs, we don't have to avoid exactly hitting
+ * wMaxPacketSize, and may as well use the whole skb.
+ */
+ if (rx->span)
+ len = skb_tailroom(skb);
+
*bufp = skb_tail_pointer(skb);
*lenp = len;
@@ -55,6 +80,9 @@ static void mctp_usblib_rx(struct net_device *netdev, struct sk_buff *skb)
struct mctp_skb_cb *cb;
unsigned long flags;
+ skb_reset_mac_header(skb);
+ skb_pull(skb, sizeof(struct mctp_usb_hdr));
+
/* we're called from an URB completion handler, and cannot assume local
* irqs are always disabled
*/
@@ -85,70 +113,87 @@ int mctp_usblib_rx_complete(struct net_device *netdev,
__skb_put(skb, len);
- while (skb) {
- struct sk_buff *skb2 = NULL;
+ for (;;) {
struct mctp_usb_hdr *hdr;
- u16 hdr_len;
- /* length of MCTP packet, no USB header */
- u8 pkt_len;
-
- skb_reset_mac_header(skb);
- hdr = skb_pull_data(skb, sizeof(*hdr));
- if (!hdr) {
- rc = -ENOMSG;
+ struct sk_buff *skb2;
+ /* length of MCTP packet, including USB header */
+ u16 pkt_len;
+
+ /* no header yet, resubmit for the rest of the packet */
+ if (skb->len < sizeof(*hdr)) {
+ if (!rx->span) {
+ netdev_dbg(netdev,
+ "rx: tiny xfer (%d) in non-span mode",
+ skb->len);
+ rc = -ENOMSG;
+ goto err_reset;
+ }
break;
}
+ hdr = (struct mctp_usb_hdr *)skb->data;
+
if (be16_to_cpu(hdr->id) != MCTP_USB_DMTF_ID) {
+ /* By resetting here, will start the next IN transfer
+ * at the beginning of the new skb. This will mean
+ * we re-sync when we next see a spanned packet aligned
+ * with the start of a transfer.
+ *
+ * In non-spanning mode, this just means we'll drop
+ * the current transfer only
+ */
netdev_dbg(netdev, "rx: invalid id %04x\n",
be16_to_cpu(hdr->id));
rc = -EPROTO;
- break;
+ goto err_reset;
}
- hdr_len = be16_to_cpu(hdr->len) & MCTP_USB_1_0_PKTLEN_MAX;
-
- if (hdr_len <
- sizeof(struct mctp_hdr) + sizeof(struct mctp_usb_hdr)) {
- netdev_dbg(netdev, "rx: short packet (hdr) %d\n",
- hdr_len);
+ pkt_len = be16_to_cpu(hdr->len);
+ /* v1.1, with span enabled, has a 13-bit length */
+ pkt_len &= rx->span ?
+ MCTP_USB_1_1_PKTLEN_MAX : MCTP_USB_1_0_PKTLEN_MAX;
+ if (pkt_len < sizeof(*hdr) + sizeof(struct mctp_hdr)) {
+ netdev_dbg(netdev, "rx: invalid len %d\n", pkt_len);
rc = -EPROTO;
- break;
+ goto err_reset;
}
- /* we know we have at least sizeof(struct mctp_usb_hdr) here */
- pkt_len = hdr_len - sizeof(struct mctp_usb_hdr);
+ /* span continues to the next transfer, resubmit */
if (pkt_len > skb->len) {
- rc = -EPROTO;
- netdev_dbg(netdev,
- "rx: short packet (xfer) %d, actual %d\n",
- hdr_len, skb->len);
+ if (!rx->span) {
+ netdev_dbg(netdev,
+ "rx: short xfer (%d vs %d) in non-span mode",
+ pkt_len, skb->len);
+ rc = -ENOMSG;
+ goto err_reset;
+ }
break;
}
- if (pkt_len < skb->len) {
- /* more packets may follow - clone to a new
- * skb to use on the next iteration
- */
- skb2 = skb_clone(skb, GFP_ATOMIC);
- if (skb2) {
- if (!skb_pull(skb2, pkt_len)) {
- dev_kfree_skb_any(skb2);
- skb2 = NULL;
- }
- }
- skb_trim(skb, pkt_len);
+ /* we have (exactly) a complete packet, RX it directly */
+ if (pkt_len == skb->len) {
+ mctp_usblib_rx(netdev, skb);
+ rx->skb = NULL;
+ break;
}
- mctp_usblib_rx(netdev, skb);
- skb = skb2;
+ /* more packets follow - RX a clone so that we can continue
+ * processing the current SKB, which may be the start of a
+ * span.
+ */
+ skb2 = skb_clone(skb, GFP_ATOMIC);
+ if (skb2) {
+ skb_trim(skb2, pkt_len);
+ mctp_usblib_rx(netdev, skb2);
+ }
+ skb_pull(skb, pkt_len);
}
- if (skb)
- dev_kfree_skb_any(skb);
+ return 0;
+err_reset:
+ dev_kfree_skb_any(rx->skb);
rx->skb = NULL;
-
return rc;
}
EXPORT_SYMBOL_GPL(mctp_usblib_rx_complete);
diff --git a/include/linux/usb/mctp-usb.h b/include/linux/usb/mctp-usb.h
index 00e94ddfadcb..7ee979bcf05d 100644
--- a/include/linux/usb/mctp-usb.h
+++ b/include/linux/usb/mctp-usb.h
@@ -34,6 +34,8 @@ struct mctp_usb_hdr {
#define MCTP_USB_MTU_MIN MCTP_USB_BTU
#define MCTP_USB_1_0_PKTLEN_MAX U8_MAX
#define MCTP_USB_1_0_MTU_MAX (MCTP_USB_1_0_PKTLEN_MAX - sizeof(struct mctp_usb_hdr))
+#define MCTP_USB_1_1_PKTLEN_MAX GENMASK(12, 0)
+#define MCTP_USB_1_1_MTU_MAX (MCTP_USB_1_1_PKTLEN_MAX - sizeof(struct mctp_usb_hdr))
#define MCTP_USB_DMTF_ID 0x1ab4
/* mctp-usblib */
@@ -46,9 +48,10 @@ struct mctp_usb_hdr {
*/
struct mctp_usblib_rx {
struct sk_buff *skb;
+ bool span;
};
-void mctp_usblib_rx_init(struct mctp_usblib_rx *rx);
+void mctp_usblib_rx_init(struct mctp_usblib_rx *rx, bool span);
void mctp_usblib_rx_fini(struct mctp_usblib_rx *rx);
int mctp_usblib_rx_prepare(struct net_device *netdev,
--
2.47.3
^ permalink raw reply related
* [PATCH net-next v2 07/12] net: mctp: usb: Accommodate DSP0283 v1.1 header format
From: Jeremy Kerr @ 2026-07-03 5:47 UTC (permalink / raw)
To: Matt Johnston, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Greg Kroah-Hartman
Cc: netdev, linux-usb
In-Reply-To: <20260703-dev-mctp-usb-1-1-v2-0-60367b861b33@codeconstruct.com.au>
In the v1.1 update to DSP0283, we have a larger header field, of 13 bits
rather than 8.
In order to accommodate this, in preparation for proper v1.1 support,
expand our struct mctp_usb_hdr's len field to a u16, and endian-convert
when necessary. Because we don't yet support spanning mode, we will
never receive or transmit with the top 5 bits set, so we always mask
out anyway.
This allows for a future change where we allow spanning mode with
>512-byte transfers.
Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
---
v2:
- fix short packet (hdr) debug message: use endian-converted value
---
drivers/net/mctp/mctp-usblib.c | 14 ++++++++------
include/linux/usb/mctp-usb.h | 11 ++++++++---
2 files changed, 16 insertions(+), 9 deletions(-)
diff --git a/drivers/net/mctp/mctp-usblib.c b/drivers/net/mctp/mctp-usblib.c
index f2221dbc1019..13dbeb84a0b7 100644
--- a/drivers/net/mctp/mctp-usblib.c
+++ b/drivers/net/mctp/mctp-usblib.c
@@ -88,6 +88,7 @@ int mctp_usblib_rx_complete(struct net_device *netdev,
while (skb) {
struct sk_buff *skb2 = NULL;
struct mctp_usb_hdr *hdr;
+ u16 hdr_len;
/* length of MCTP packet, no USB header */
u8 pkt_len;
@@ -105,21 +106,23 @@ int mctp_usblib_rx_complete(struct net_device *netdev,
break;
}
- if (hdr->len <
+ hdr_len = be16_to_cpu(hdr->len) & MCTP_USB_1_0_PKTLEN_MAX;
+
+ if (hdr_len <
sizeof(struct mctp_hdr) + sizeof(struct mctp_usb_hdr)) {
netdev_dbg(netdev, "rx: short packet (hdr) %d\n",
- hdr->len);
+ hdr_len);
rc = -EPROTO;
break;
}
/* we know we have at least sizeof(struct mctp_usb_hdr) here */
- pkt_len = hdr->len - sizeof(struct mctp_usb_hdr);
+ pkt_len = hdr_len - sizeof(struct mctp_usb_hdr);
if (pkt_len > skb->len) {
rc = -EPROTO;
netdev_dbg(netdev,
"rx: short packet (xfer) %d, actual %d\n",
- hdr->len, skb->len);
+ hdr_len, skb->len);
break;
}
@@ -386,8 +389,7 @@ static int mctp_usblib_tx_skb_prepare(struct sk_buff *skb,
}
hdr->id = cpu_to_be16(MCTP_USB_DMTF_ID);
- hdr->rsvd = 0;
- hdr->len = plen + sizeof(*hdr);
+ hdr->len = cpu_to_be16(plen + sizeof(*hdr));
return 0;
}
diff --git a/include/linux/usb/mctp-usb.h b/include/linux/usb/mctp-usb.h
index 9d9b1f5eee5c..00e94ddfadcb 100644
--- a/include/linux/usb/mctp-usb.h
+++ b/include/linux/usb/mctp-usb.h
@@ -2,7 +2,7 @@
/*
* mctp-usb.h - MCTP USB transport binding: common definitions,
* based on DMTF0283 specification:
- * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.0.1.pdf
+ * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.1.0.pdf
*
* These are protocol-level definitions, that may be shared between host
* and gadget drivers.
@@ -17,10 +17,15 @@
#include <linux/skbuff.h>
#include <linux/types.h>
+/*
+ * MCTP-over-USB transport header. DSP0283 v1.0 has an 8-bit length field
+ * (preceded by 8 reserved bits), v1.1 has a 13-bit length field (preceded by
+ * 3 reserved bits). We use a be16 for our length to handle the larger v1.1
+ * representation, and mask as appropriate.
+ */
struct mctp_usb_hdr {
__be16 id;
- u8 rsvd;
- u8 len;
+ __be16 len;
} __packed;
/* max transfer size for DSP0283 v1.0 */
--
2.47.3
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox