[PATCH net-next 8/9] net: sparx5/lan969x: scale the TAS guard band with the MTU

From: Daniel Machon

Date: Thu Nov 05 2026 - 09:04:16 EST


The TAS guard band (QMAXSDU_VAL, in units of 64 bytes) is how long
before a gate closes the scheduler stops starting frames of a traffic
class, so that a frame started just before the close still finishes in
time. The driver leaves it at the reset value of 24 x 64 = 1536 bytes,
so with a larger MTU, a frame started inside the guard band would
overrun the gate close.

The MAC maximum frame length only limits the frames a port receives, so
a port can transmit frames of the largest MTU of all ports, for example
when forwarding from a port with a larger MTU. Therefore, size the guard
band of all ports for a frame of the largest MTU, plus the Ethernet
header, FCS and two VLAN tags, rounded up to 64 bytes. Add the
HSCH_TAS_QMAXSDU_CFG register, program the guard band at init, and add
sparx5_tas_gb_update() for use when the MTU changes.

With the default MTU of 1500 the guard band is unchanged. As the
scheduler does not start a frame inside the guard band, a gate window
shorter than the guard band at the link speed cannot transmit, as is
already the case for the default 1536 byte guard band.

Signed-off-by: Daniel Machon <daniel.machon@xxxxxxxxxxxxx>
---
.../net/ethernet/microchip/sparx5/sparx5_main.h | 1 +
.../ethernet/microchip/sparx5/sparx5_main_regs.h | 12 ++++++
drivers/net/ethernet/microchip/sparx5/sparx5_tas.c | 43 +++++++++++++++++++++-
3 files changed, 54 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
index ac3aa798ae303..d150a76fc917e 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
@@ -745,6 +745,7 @@ int sparx5_tas_enable(struct sparx5_port *port,
struct tc_taprio_qopt_offload *qopt);
int sparx5_tas_disable(struct sparx5_port *port);
void sparx5_tas_speed(struct sparx5_port *port, int speed);
+void sparx5_tas_gb_update(struct sparx5 *sparx5);

/* sparx5_mirror.c */
int sparx5_mirror_add(struct sparx5_mall_entry *entry);
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h
index 5302c7686e207..8a6de30042b5d 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h
@@ -5424,6 +5424,18 @@ extern const struct sparx5_regs *regs;
#define HSCH_TAS_STATEMACHINE_CFG_REVISIT_DLY_GET(x)\
FIELD_GET(HSCH_TAS_STATEMACHINE_CFG_REVISIT_DLY, x)

+/* HSCH:TAS_PROFILE_CFG:TAS_QMAXSDU_CFG */
+#define HSCH_TAS_QMAXSDU_CFG(g, r) \
+ __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_PROFILE_CFG], g, \
+ regs->gcnt[GC_HSCH_TAS_PROFILE_CFG], \
+ regs->gsize[GW_HSCH_TAS_PROFILE_CFG], 0, r, 8, 4)
+
+#define HSCH_TAS_QMAXSDU_CFG_QMAXSDU_VAL GENMASK(7, 0)
+#define HSCH_TAS_QMAXSDU_CFG_QMAXSDU_VAL_SET(x)\
+ FIELD_PREP(HSCH_TAS_QMAXSDU_CFG_QMAXSDU_VAL, x)
+#define HSCH_TAS_QMAXSDU_CFG_QMAXSDU_VAL_GET(x)\
+ FIELD_GET(HSCH_TAS_QMAXSDU_CFG_QMAXSDU_VAL, x)
+
/* HSCH:TAS_PROFILE_CFG:TAS_PROFILE_CONFIG */
#define HSCH_TAS_PROFILE_CONFIG(g) \
__REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_PROFILE_CFG], g, \
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_tas.c b/drivers/net/ethernet/microchip/sparx5/sparx5_tas.c
index ec80bc69371ab..32e502bc4f18f 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_tas.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_tas.c
@@ -29,6 +29,9 @@ enum sparx5_tas_link_speed {
/* Maximum supported cycle time in nanoseconds */
#define TAS_MAX_CYCLE_TIME_NS ((1 * NSEC_PER_SEC) - 1) /* 999.999.999 nsec */

+/* HSCH_TAS_QMAXSDU_CFG.QMAXSDU_VAL, the guard band, counts 64 byte units */
+#define TAS_QMAXSDU_UNIT 64
+
/* STARTUP_TIME in units of 256 ns, must be non-zero for a list to start */
#define TAS_STARTUP_TIME_DEFAULT 8

@@ -48,8 +51,8 @@ enum sparx5_tas_link_speed {
* We use 1 TAS profile per port, shared by both of its lists:
* index = portno;
*
- * We do not yet support frame preemption and the default guard band
- * of 1536 bytes is always used on all queues.
+ * We do not yet support frame preemption. All queues of all ports use a
+ * guard band that fits the largest frame that can be forwarded.
*
* The gate mask of each entry is written as is to the port's 8 queues,
* where bit n gates queue (QoS class) n. The mqprio parameters of the
@@ -718,6 +721,40 @@ int sparx5_tas_disable(struct sparx5_port *port)
return err;
}

+/* Size the guard band of all ports for a frame of the largest MTU of all
+ * ports, as a port can transmit frames forwarded from any port.
+ */
+void sparx5_tas_gb_update(struct sparx5 *sparx5)
+{
+ u32 max_mtu = 0, gb;
+ int i, tc;
+
+ for (i = 0; i < sparx5->data->consts->n_ports; i++) {
+ if (sparx5->ports[i])
+ max_mtu = max_t(u32, max_mtu,
+ READ_ONCE(sparx5->ports[i]->ndev->mtu));
+ }
+
+ gb = DIV_ROUND_UP(SPX5_HW_MTU(max_mtu) + 2 * VLAN_HLEN,
+ TAS_QMAXSDU_UNIT);
+
+ mutex_lock(&sparx5->tas_lock);
+
+ /* Each port uses the TAS profile of its own number */
+ for (i = 0; i < sparx5->data->consts->n_ports; i++) {
+ if (!sparx5->ports[i])
+ continue;
+
+ for (tc = 0; tc < SPX5_PRIOS; tc++)
+ spx5_rmw(HSCH_TAS_QMAXSDU_CFG_QMAXSDU_VAL_SET(gb),
+ HSCH_TAS_QMAXSDU_CFG_QMAXSDU_VAL,
+ sparx5,
+ HSCH_TAS_QMAXSDU_CFG(i, tc));
+ }
+
+ mutex_unlock(&sparx5->tas_lock);
+}
+
int sparx5_tas_init(struct sparx5 *sparx5)
{
const struct sparx5_consts *consts = sparx5->data->consts;
@@ -764,6 +801,8 @@ int sparx5_tas_init(struct sparx5 *sparx5)
}
}

+ sparx5_tas_gb_update(sparx5);
+
return 0;
}


--
2.34.1