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 <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
This commit is contained in:
Julius Bairaktaris authored and Robert Marko committed 2026-10-18 12:50:07 +02:00
1 parent cfaf5b3e4c
commit 478da5ef34
3 files changed
+95

No files matched your search

@@ -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);
@@ -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 *
@@ -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;