realtek: dsa: rtl93xx: Support per port throttling
authorIssam Hamdi <[email protected]>
Tue, 15 Jul 2025 17:23:21 +0000 (19:23 +0200)
committerRobert Marko <[email protected]>
Fri, 7 Nov 2025 11:34:09 +0000 (12:34 +0100)
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 <[email protected]>
Co-developed-by: Sven Eckelmann <[email protected]>
Signed-off-by: Sven Eckelmann <[email protected]>
Link: https://github.com/openwrt/openwrt/pull/20663
Signed-off-by: Robert Marko <[email protected]>
target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/dsa.c
target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl838x.h
target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl930x.c
target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl931x.c

index 0e341163760f4602fc94bce1512907af02829959..2b743dd53a4351c78cb8ff9582b9dd7f85f53da0 100644 (file)
@@ -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,
 };
index 5dba9d5fef0346dd76b599770914832e3bd8d05a..1ab943a6816a5628f80990b851154d30c173c962 100644 (file)
 #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);
index d8eee64354f2824a8b3ceffd334da0a7b35adbcd..dddee9b7a90a94c6bba199f72af45618602b1363 100644 (file)
@@ -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,
index 90e450cf96bdde26ec424a1eb19203d6b08c7653..9f25f195e9b85bfdb21ebc9ed4966cca812789d3 100644 (file)
@@ -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,