[openwrt/openwrt] realtek: dsa: rtl93xx: Support per port throttling

LEDE Commits lede-commits at lists.infradead.org
Fri Nov 7 03:34:13 PST 2025


robimarko pushed a commit to openwrt/openwrt.git, branch main:
https://git.openwrt.org/2e74eb6d93a7db37bb8e0ec901ae08078afa9a65

commit 2e74eb6d93a7db37bb8e0ec901ae08078afa9a65
Author: Issam Hamdi <ih at simonwunderlich.de>
AuthorDate: Tue Jul 15 19:23:21 2025 +0200

    realtek: dsa: rtl93xx: Support per port throttling
    
    The RTL930x and RTL931x have an ingress and egress bandwidth controller for
    each port. They can can be used to reduce the throughput for each port.
    
    They can be programmed via the the DSA flower offloading capabilities. Only
    a limited functionality (bytes based rate limiter for ingress/egress) is
    supported.
    
    With kmod-sched-act-police, kmod-sched-flower and tc installed, each port
    can have its ingress/egress rate limit applied in hardware using:
    
        # tc qdisc del dev lan1 clsact
        tc qdisc add dev lan1 clsact
        tc filter add dev lan1 ingress flower skip_sw action police rate 100mbit burst 64k conform-exceed drop
        tc filter add dev lan1 egress flower skip_sw action police rate 150mbit burst 64k conform-exceed drop
    
    Signed-off-by: Issam Hamdi <ih at simonwunderlich.de>
    Co-developed-by: Sven Eckelmann <se at simonwunderlich.de>
    Signed-off-by: Sven Eckelmann <se at simonwunderlich.de>
    Link: https://github.com/openwrt/openwrt/pull/20663
    Signed-off-by: Robert Marko <robimarko at gmail.com>
---
 .../files-6.12/drivers/net/dsa/rtl83xx/dsa.c       | 115 +++++++++++++++++++++
 .../files-6.12/drivers/net/dsa/rtl83xx/rtl838x.h   |  27 +++++
 .../files-6.12/drivers/net/dsa/rtl83xx/rtl930x.c   |  64 ++++++++++++
 .../files-6.12/drivers/net/dsa/rtl83xx/rtl931x.c   |  44 ++++++++
 4 files changed, 250 insertions(+)

diff --git a/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/dsa.c b/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/dsa.c
index 0e34116376..2b743dd53a 100644
--- a/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/dsa.c
+++ b/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/dsa.c
@@ -2495,6 +2495,118 @@ static int rtldsa_phy_write(struct dsa_switch *ds, int addr, int regnum, u16 val
 	return mdiobus_write_nested(priv->parent_bus, addr, regnum, val);
 }
 
+static const struct flow_action_entry *rtldsa_rate_policy_extract(struct flow_cls_offload *cls)
+{
+	struct flow_rule *rule;
+
+	/* only simple rules with a single action are supported */
+	rule = flow_cls_offload_flow_rule(cls);
+
+	if (!flow_action_basic_hw_stats_check(&cls->rule->action,
+					      cls->common.extack))
+		return NULL;
+
+	if (!flow_offload_has_one_action(&rule->action))
+		return NULL;
+
+	return &rule->action.entries[0];
+}
+
+static bool rtldsa_port_rate_police_validate(const struct flow_action_entry *act)
+{
+	if (!act)
+		return false;
+
+	/* only allow action which just limit rate with by dropping packets */
+	if (act->id != FLOW_ACTION_POLICE)
+		return false;
+
+	if (act->police.rate_pkt_ps > 0)
+		return false;
+
+	if (act->police.exceed.act_id != FLOW_ACTION_DROP)
+		return false;
+
+	if (act->police.notexceed.act_id != FLOW_ACTION_ACCEPT)
+		return false;
+
+	return true;
+}
+
+static int rtldsa_cls_flower_add(struct dsa_switch *ds, int port,
+				 struct flow_cls_offload *cls,
+				 bool ingress)
+{
+	struct rtl838x_switch_priv *priv = ds->priv;
+	struct rtl838x_port *p = &priv->ports[port];
+	const struct flow_action_entry *act;
+	int ret;
+
+	if (!priv->r->port_rate_police_add)
+		return -EOPNOTSUPP;
+
+	/* the single action must be a rate/bandwidth limiter */
+	act = rtldsa_rate_policy_extract(cls);
+
+	if (!rtldsa_port_rate_police_validate(act))
+		return -EOPNOTSUPP;
+
+	mutex_lock(&priv->reg_mutex);
+
+	/* only allow one offloaded police for ingress/egress */
+	if (ingress && p->rate_police_ingress) {
+		ret = -EOPNOTSUPP;
+		goto unlock;
+	}
+
+	if (!ingress && p->rate_police_egress) {
+		ret = -EOPNOTSUPP;
+		goto unlock;
+	}
+
+	ret = priv->r->port_rate_police_add(ds, port, act, ingress);
+	if (ret < 0)
+		goto unlock;
+
+	if (ingress)
+		p->rate_police_ingress = true;
+	else
+		p->rate_police_egress = true;
+
+unlock:
+	mutex_unlock(&priv->reg_mutex);
+
+	return ret;
+}
+
+static int rtldsa_cls_flower_del(struct dsa_switch *ds, int port,
+				 struct flow_cls_offload *cls,
+				 bool ingress)
+{
+	struct rtl838x_switch_priv *priv = ds->priv;
+	struct rtl838x_port *p = &priv->ports[port];
+	int ret;
+
+	if (!priv->r->port_rate_police_del)
+		return -EOPNOTSUPP;
+
+	mutex_lock(&priv->reg_mutex);
+
+	ret = priv->r->port_rate_police_del(ds, port, cls, ingress);
+	if (ret < 0)
+		goto unlock;
+
+	if (ingress)
+		p->rate_police_ingress = false;
+	else
+		p->rate_police_egress = false;
+
+unlock:
+	mutex_unlock(&priv->reg_mutex);
+
+	return ret;
+}
+
 const struct dsa_switch_ops rtl83xx_switch_ops = {
 	.get_tag_protocol	= rtl83xx_get_tag_protocol,
 	.setup			= rtl83xx_setup,
@@ -2607,4 +2719,7 @@ const struct dsa_switch_ops rtl93xx_switch_ops = {
 
 	.port_pre_bridge_flags	= rtldsa_port_pre_bridge_flags,
 	.port_bridge_flags	= rtl83xx_port_bridge_flags,
+
+	.cls_flower_add		= rtldsa_cls_flower_add,
+	.cls_flower_del		= rtldsa_cls_flower_del,
 };
diff --git a/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl838x.h b/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl838x.h
index 5dba9d5fef..1ab943a681 100644
--- a/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl838x.h
+++ b/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl838x.h
@@ -406,6 +406,27 @@
 #define RTL839X_SPCL_TRAP_SWITCH_MAC_CTRL	(0x1068)
 #define RTL839X_SPCL_TRAP_SWITCH_IPV4_ADDR_CTRL	(0x106C)
 #define RTL839X_SPCL_TRAP_CRC_CTRL		(0x1070)
+
+#define RTL930X_BANDWIDTH_CTRL_EGRESS(port)	(0x7660 + (port * 16))
+#define RTL930X_BANDWIDTH_CTRL_INGRESS(port)	(0x8068 + (port * 4))
+#define RTL930X_BANDWIDTH_CTRL_MAX_BURST	(64 * 1000)
+#define RTL930X_BANDWIDTH_CTRL_INGRESS_BURST_HIGH_ON(port) \
+						(0x80DC + (port * 8))
+#define RTL930X_BANDWIDTH_CTRL_INGRESS_BURST_HIGH_OFF(port) \
+						(0x80E0 + (port * 8))
+#define RTL930X_BANDWIDTH_CTRL_INGRESS_BURST_MAX \
+						GENMASK(30, 0)
+
+#define RTL931X_BANDWIDTH_CTRL_EGRESS(port)	(0x2164 + (port * 8))
+#define RTL931X_BANDWIDTH_CTRL_INGRESS(port)	(0xe008 + (port * 8))
+
+#define RTL93XX_BANDWIDTH_CTRL_RATE_MAX		GENMASK(19, 0)
+#define RTL93XX_BANDWIDTH_CTRL_ENABLE		BIT(20)
+#define RTL931X_BANDWIDTH_CTRL_MAX_BURST	GENMASK(15, 0)
+
+#define RTL930X_INGRESS_FC_CTRL(port)		(0x81CC + ((port / 29) * 4))
+#define RTL930X_INGRESS_FC_CTRL_EN(port)	BIT(port % 29)
+
 /* special port action controls */
 /* values:
  *      0 = FORWARD (default)
@@ -684,6 +705,8 @@ struct rtl838x_port {
 	bool is10G:1;
 	bool is2G5:1;
 	bool isolated:1;
+	bool rate_police_egress:1;
+	bool rate_police_ingress:1;
 	u64 pm;
 	u16 pvid;
 	bool eee_enabled;
@@ -1074,6 +1097,10 @@ struct rtl838x_reg {
 	int  (*l2_port_new_sa_fwd)(int port);
 	int (*set_ageing_time)(unsigned long msec);
 	int (*get_mirror_config)(struct rtldsa_mirror_config *config, int group, int port);
+	int (*port_rate_police_add)(struct dsa_switch *ds, int port,
+				    const struct flow_action_entry *act, bool ingress);
+	int (*port_rate_police_del)(struct dsa_switch *ds, int port, struct flow_cls_offload *cls,
+				    bool ingress);
 	u64 (*read_l2_entry_using_hash)(u32 hash, u32 position, struct rtl838x_l2_entry *e);
 	void (*write_l2_entry_using_hash)(u32 hash, u32 pos, struct rtl838x_l2_entry *e);
 	u64 (*read_cam)(int idx, struct rtl838x_l2_entry *e);
diff --git a/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl930x.c b/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl930x.c
index d8eee64354..dddee9b7a9 100644
--- a/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl930x.c
+++ b/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl930x.c
@@ -190,6 +190,68 @@ static int rtldsa_930x_get_mirror_config(struct rtldsa_mirror_config *config,
 	return 0;
 }
 
+static int rtldsa_930x_port_rate_police_add(struct dsa_switch *ds, int port,
+					    const struct flow_action_entry *act,
+					    bool ingress)
+{
+	u32 burst;
+	u64 rate;
+	u32 addr;
+
+	/* rate has unit 16000 bit */
+	rate = div_u64(act->police.rate_bytes_ps, 2000);
+	rate = min_t(u64, rate, RTL93XX_BANDWIDTH_CTRL_RATE_MAX);
+	rate |= RTL93XX_BANDWIDTH_CTRL_ENABLE;
+
+	if (ingress)
+		addr = RTL930X_BANDWIDTH_CTRL_INGRESS(port);
+	else
+		addr = RTL930X_BANDWIDTH_CTRL_EGRESS(port);
+
+	if (ingress) {
+		burst = min_t(u32, act->police.burst, RTL930X_BANDWIDTH_CTRL_INGRESS_BURST_MAX);
+
+		/* set burst high on/off the same to avoid TCP oscillation */
+		sw_w32(burst, RTL930X_BANDWIDTH_CTRL_INGRESS_BURST_HIGH_ON(port));
+		sw_w32(burst, RTL930X_BANDWIDTH_CTRL_INGRESS_BURST_HIGH_OFF(port));
+
+		/* Enable ingress bandwidth flow control to improve TCP throughput and avoid
+		 * the drops behavior of the RTL930x ingress rate limiter which seem to not
+		 * play well with any congestion control algorithm
+		 */
+		sw_w32_mask(0, RTL930X_INGRESS_FC_CTRL_EN(port),
+			    RTL930X_INGRESS_FC_CTRL(port));
+	} else {
+		burst = min_t(u32, act->police.burst, RTL930X_BANDWIDTH_CTRL_MAX_BURST);
+
+		sw_w32(burst, addr + 4);
+	}
+
+	sw_w32(rate, addr);
+
+	return 0;
+}
+
+static int rtldsa_930x_port_rate_police_del(struct dsa_switch *ds, int port,
+					    struct flow_cls_offload *cls,
+					    bool ingress)
+{
+	u32 addr;
+
+	if (ingress)
+		addr = RTL930X_BANDWIDTH_CTRL_INGRESS(port);
+	else
+		addr = RTL930X_BANDWIDTH_CTRL_EGRESS(port);
+
+	sw_w32_mask(RTL93XX_BANDWIDTH_CTRL_ENABLE, 0, addr);
+
+	if (ingress)
+		sw_w32_mask(RTL930X_INGRESS_FC_CTRL_EN(port), 0,
+			    RTL930X_INGRESS_FC_CTRL(port));
+
+	return 0;
+}
+
 inline static int rtl930x_trk_mbr_ctr(int group)
 {
 	return RTL930X_TRK_MBR_CTRL + (group << 2);
@@ -2495,6 +2557,8 @@ const struct rtl838x_reg rtl930x_reg = {
 	.l2_port_new_salrn = rtl930x_l2_port_new_salrn,
 	.l2_port_new_sa_fwd = rtl930x_l2_port_new_sa_fwd,
 	.get_mirror_config = rtldsa_930x_get_mirror_config,
+	.port_rate_police_add = rtldsa_930x_port_rate_police_add,
+	.port_rate_police_del = rtldsa_930x_port_rate_police_del,
 	.read_l2_entry_using_hash = rtl930x_read_l2_entry_using_hash,
 	.write_l2_entry_using_hash = rtl930x_write_l2_entry_using_hash,
 	.read_cam = rtl930x_read_cam,
diff --git a/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl931x.c b/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl931x.c
index 90e450cf96..9f25f195e9 100644
--- a/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl931x.c
+++ b/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl931x.c
@@ -304,6 +304,48 @@ static int rtldsa_931x_get_mirror_config(struct rtldsa_mirror_config *config,
 	return 0;
 }
 
+static int rtldsa_931x_port_rate_police_add(struct dsa_switch *ds, int port,
+					    const struct flow_action_entry *act,
+					    bool ingress)
+{
+	u32 burst;
+	u64 rate;
+	u32 addr;
+
+	/* rate has unit 16000 bit */
+	rate = div_u64(act->police.rate_bytes_ps, 2000);
+	rate = min_t(u64, rate, RTL93XX_BANDWIDTH_CTRL_RATE_MAX);
+	rate |= RTL93XX_BANDWIDTH_CTRL_ENABLE;
+
+	burst = min_t(u32, act->police.burst, RTL931X_BANDWIDTH_CTRL_MAX_BURST);
+
+	if (ingress)
+		addr = RTL931X_BANDWIDTH_CTRL_INGRESS(port);
+	else
+		addr = RTL931X_BANDWIDTH_CTRL_EGRESS(port);
+
+	sw_w32(burst, addr + 4);
+	sw_w32(rate, addr);
+
+	return 0;
+}
+
+static int rtldsa_931x_port_rate_police_del(struct dsa_switch *ds, int port,
+					    struct flow_cls_offload *cls,
+					    bool ingress)
+{
+	u32 addr;
+
+	if (ingress)
+		addr = RTL931X_BANDWIDTH_CTRL_INGRESS(port);
+	else
+		addr = RTL931X_BANDWIDTH_CTRL_EGRESS(port);
+
+	sw_w32_mask(RTL93XX_BANDWIDTH_CTRL_ENABLE, 0, addr);
+
+	return 0;
+}
+
 irqreturn_t rtl931x_switch_irq(int irq, void *dev_id)
 {
 	struct dsa_switch *ds = dev_id;
@@ -1618,6 +1660,8 @@ const struct rtl838x_reg rtl931x_reg = {
 	.l2_port_new_salrn = rtl931x_l2_port_new_salrn,
 	.l2_port_new_sa_fwd = rtl931x_l2_port_new_sa_fwd,
 	.get_mirror_config = rtldsa_931x_get_mirror_config,
+	.port_rate_police_add = rtldsa_931x_port_rate_police_add,
+	.port_rate_police_del = rtldsa_931x_port_rate_police_del,
 	.read_l2_entry_using_hash = rtl931x_read_l2_entry_using_hash,
 	.write_l2_entry_using_hash = rtl931x_write_l2_entry_using_hash,
 	.read_cam = rtl931x_read_cam,




More information about the lede-commits mailing list