From 478da5ef3446594e1f045bffc203df37ae7bfca4 Mon Sep 17 00:00:00 2001 From: Julius Bairaktaris Date: Sat, 22 Aug 2026 17:27:58 +0200 Subject: [PATCH] qualcommax: qca_ppe: flush a port's queues on port_disable Disabling a port leaves its queued frames holding buffers charged to the admission and shared groups, so every other port is admitted against a smaller pool. On port_disable, stop each unicast and multicast queue's enqueue and dequeue, flush it through FLUSH_CFG and wait for completion, one queue at a time with the QM clock gate dropped around the pass. The gates stay closed until port_enable, and the CPU port is skipped. On IPQ8074 a full queue drains to empty when its port goes down and fills again once the port is back up. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Julius Bairaktaris Link: https://github.com/openwrt/openwrt/pull/25700 Signed-off-by: Robert Marko --- .../drivers/net/ethernet/qualcomm/qca_ppe.h | 21 ++++++ .../net/ethernet/qualcomm/qca_ppe_main.c | 3 + .../net/ethernet/qualcomm/qca_ppe_scheduler.c | 71 +++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe.h b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe.h index d1a05c54c9d..991af1cd902 100644 --- a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe.h +++ b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe.h @@ -19,6 +19,9 @@ #define QCA_PPE_MAX_BRIDGES 8 /* --- Global --- */ +#define PPE_CLK_GATING_CTRL 0x8 +#define PPE_QM_CLK_GATE_EN BIT(4) + /* GMAC LPI: an enable bit per port from port 1, a wake and an idle timer per * port in microseconds, and the PPE clock cycles that make a microsecond. * qca-ssdk uses the same layout for IPQ807x and IPQ60xx. @@ -611,6 +614,9 @@ #define PPE_TM_RING_Q_MAP(r) (PPE_TM_BASE + 0x2a000 + (r) * 0x40) +#define PPE_TM_DEQ_DIS(q) (PPE_TM_BASE + 0x30000 + (q) * 0x10) +#define PPE_DEQ_DIS BIT(0) + #define PPE_TM_L1_FLOW_MAP(i) (PPE_TM_BASE + 0x40000 + (i) * 0x10) #define PPE_L1_SP_ID GENMASK(3, 0) #define PPE_L1_C_PRI GENMASK(6, 4) @@ -711,6 +717,20 @@ /* --- Queue Manager (base 0x800000) --- */ #define PPE_QM_BASE 0x800000 +/* Draining a queue: the gate that stops it filling, and the flush that hands + * its buffers back to the group. FLUSH_BUSY is written to start the flush and + * the hardware clears it; FLUSH_STATUS says whether it worked. + */ +#define PPE_QM_FLUSH_CFG (PPE_QM_BASE + 0x0) +#define PPE_FLUSH_QID GENMASK(8, 0) +#define PPE_FLUSH_STATUS BIT(10) +#define PPE_FLUSH_DST_PORT GENMASK(23, 21) +#define PPE_FLUSH_ALL_QUEUES BIT(24) +#define PPE_FLUSH_BUSY BIT(31) + +#define PPE_QM_ENQ_OPR(q) (PPE_QM_BASE + 0x5c000 + (q) * 0x10) +#define PPE_ENQ_DISABLE BIT(0) + #define PPE_QM_UCAST_MAP(i) (PPE_QM_BASE + 0x10000 + (i) * 0x10) #define PPE_QM_PROFILE_ID GENMASK(3, 0) #define PPE_QM_QUEUE_ID GENMASK(11, 4) @@ -1045,6 +1065,7 @@ struct flow_cls_offload; #define PPE_DEVLINK_SB 0 void ppe_scheduler_init(struct qca_ppe_priv *priv); +void ppe_port_queues_enable(struct qca_ppe_priv *priv, int port, bool en); int qca_ppe_setup_tc(struct dsa_switch *ds, int port, enum tc_setup_type type, void *type_data); int qca_ppe_devlink_sb_setup(struct dsa_switch *ds); diff --git a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_main.c b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_main.c index 9ab848c7201..578ea330aa0 100644 --- a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_main.c +++ b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_main.c @@ -769,6 +769,8 @@ static int qca_ppe_port_enable(struct dsa_switch *ds, int port, { struct qca_ppe_priv *priv = ds_to_priv(ds); + ppe_port_queues_enable(priv, port, true); + /* A user port's gate is opened by qca_ppe_mac_link_up() once its MAC * is up. DSA calls this before phylink_start(), so opening it here * would aim the fabric at a MAC that is still down and about to be @@ -785,6 +787,7 @@ static void qca_ppe_port_disable(struct dsa_switch *ds, int port) struct qca_ppe_priv *priv = ds_to_priv(ds); ppe_port_bridge_txmac_set(priv, port, false); + ppe_port_queues_enable(priv, port, false); } static struct qca_ppe_bridge_vsi * diff --git a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_scheduler.c b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_scheduler.c index 2665633a0d4..8a8a0f80de5 100644 --- a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_scheduler.c +++ b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_scheduler.c @@ -907,6 +907,77 @@ static void ppe_l0_scheduler_init(struct qca_ppe_priv *priv) } } +static void ppe_queues_gate(struct qca_ppe_priv *priv, u16 base, u8 count, + bool en) +{ + int i; + + for (i = 0; i < count; i++) { + regmap_write(priv->regmap, PPE_QM_ENQ_OPR(base + i), + en ? 0 : PPE_ENQ_DISABLE); + regmap_write(priv->regmap, PPE_TM_DEQ_DIS(base + i), + en ? 0 : PPE_DEQ_DIS); + } +} + +/* The flush is refused unless the queue's enqueue is already stopped; the + * vendor stops its dequeue with it. + */ +static void ppe_queue_flush(struct qca_ppe_priv *priv, int port, u16 queue) +{ + u32 val; + int ret; + + regmap_update_bits(priv->regmap, PPE_QM_FLUSH_CFG, + PPE_FLUSH_QID | PPE_FLUSH_DST_PORT | + PPE_FLUSH_ALL_QUEUES | PPE_FLUSH_BUSY, + FIELD_PREP(PPE_FLUSH_QID, queue) | + FIELD_PREP(PPE_FLUSH_DST_PORT, port) | + PPE_FLUSH_BUSY); + + ret = regmap_read_poll_timeout(priv->regmap, PPE_QM_FLUSH_CFG, val, + !(val & PPE_FLUSH_BUSY), 10, 10000); + if (ret || !(val & PPE_FLUSH_STATUS)) + dev_warn(priv->ds.dev, "port %d: queue %u did not flush\n", + port, queue); +} + +/* What a port that goes down leaves behind: the frames already queued for it + * stay charged to its admission group, and with the fabric gated nothing + * dequeues them. Stop each of the port's queues at both ends and flush it, and + * the buffers come back at once; port_enable is what reopens the gates. + */ +void ppe_port_queues_enable(struct qca_ppe_priv *priv, int port, bool en) +{ + const struct port_l0_params *p = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(port_l0); i++) + if (port_l0[i].port == port) + p = &port_l0[i]; + if (!p) + return; + + ppe_queues_gate(priv, p->ucast_base, p->ucast_count, en); + ppe_queues_gate(priv, p->mcast_base, p->mcast_count, en); + + if (en) + return; + + /* The vendor drops the QM clock gate around its own flush to + * accelerate it, and restores it after. + */ + regmap_clear_bits(priv->regmap, PPE_CLK_GATING_CTRL, + PPE_QM_CLK_GATE_EN); + + for (i = 0; i < p->ucast_count; i++) + ppe_queue_flush(priv, port, p->ucast_base + i); + for (i = 0; i < p->mcast_count; i++) + ppe_queue_flush(priv, port, p->mcast_base + i); + + regmap_set_bits(priv->regmap, PPE_CLK_GATING_CTRL, PPE_QM_CLK_GATE_EN); +} + static void ppe_edma_ring_map_init(struct qca_ppe_priv *priv) { int i;