realtek: dsa: rtl93xx: Support per port throttling
Some checks are pending
Build Kernel / Build all affected Kernels (push) Waiting to run
Build all core packages / Build all core packages for selected target (push) Waiting to run
Build host tools / Build host tools for linux and macos based systems (push) Waiting to run

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@simonwunderlich.de>
Co-developed-by: Sven Eckelmann <se@simonwunderlich.de>
Signed-off-by: Sven Eckelmann <se@simonwunderlich.de>
Link: https://github.com/openwrt/openwrt/pull/20663
Signed-off-by: Robert Marko <robimarko@gmail.com>
This commit is contained in:
Issam Hamdi 2025-07-15 19:23:21 +02:00 committed by Robert Marko
parent 2df73702c5
commit 2e74eb6d93
4 changed files with 250 additions and 0 deletions

View 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); 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 = { const struct dsa_switch_ops rtl83xx_switch_ops = {
.get_tag_protocol = rtl83xx_get_tag_protocol, .get_tag_protocol = rtl83xx_get_tag_protocol,
.setup = rtl83xx_setup, .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_pre_bridge_flags = rtldsa_port_pre_bridge_flags,
.port_bridge_flags = rtl83xx_port_bridge_flags, .port_bridge_flags = rtl83xx_port_bridge_flags,
.cls_flower_add = rtldsa_cls_flower_add,
.cls_flower_del = rtldsa_cls_flower_del,
}; };

View file

@ -406,6 +406,27 @@
#define RTL839X_SPCL_TRAP_SWITCH_MAC_CTRL (0x1068) #define RTL839X_SPCL_TRAP_SWITCH_MAC_CTRL (0x1068)
#define RTL839X_SPCL_TRAP_SWITCH_IPV4_ADDR_CTRL (0x106C) #define RTL839X_SPCL_TRAP_SWITCH_IPV4_ADDR_CTRL (0x106C)
#define RTL839X_SPCL_TRAP_CRC_CTRL (0x1070) #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 */ /* special port action controls */
/* values: /* values:
* 0 = FORWARD (default) * 0 = FORWARD (default)
@ -684,6 +705,8 @@ struct rtl838x_port {
bool is10G:1; bool is10G:1;
bool is2G5:1; bool is2G5:1;
bool isolated:1; bool isolated:1;
bool rate_police_egress:1;
bool rate_police_ingress:1;
u64 pm; u64 pm;
u16 pvid; u16 pvid;
bool eee_enabled; bool eee_enabled;
@ -1074,6 +1097,10 @@ struct rtl838x_reg {
int (*l2_port_new_sa_fwd)(int port); int (*l2_port_new_sa_fwd)(int port);
int (*set_ageing_time)(unsigned long msec); int (*set_ageing_time)(unsigned long msec);
int (*get_mirror_config)(struct rtldsa_mirror_config *config, int group, int port); 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); 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); 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); u64 (*read_cam)(int idx, struct rtl838x_l2_entry *e);

View file

@ -190,6 +190,68 @@ static int rtldsa_930x_get_mirror_config(struct rtldsa_mirror_config *config,
return 0; 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) inline static int rtl930x_trk_mbr_ctr(int group)
{ {
return RTL930X_TRK_MBR_CTRL + (group << 2); 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_salrn = rtl930x_l2_port_new_salrn,
.l2_port_new_sa_fwd = rtl930x_l2_port_new_sa_fwd, .l2_port_new_sa_fwd = rtl930x_l2_port_new_sa_fwd,
.get_mirror_config = rtldsa_930x_get_mirror_config, .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, .read_l2_entry_using_hash = rtl930x_read_l2_entry_using_hash,
.write_l2_entry_using_hash = rtl930x_write_l2_entry_using_hash, .write_l2_entry_using_hash = rtl930x_write_l2_entry_using_hash,
.read_cam = rtl930x_read_cam, .read_cam = rtl930x_read_cam,

View file

@ -304,6 +304,48 @@ static int rtldsa_931x_get_mirror_config(struct rtldsa_mirror_config *config,
return 0; 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) irqreturn_t rtl931x_switch_irq(int irq, void *dev_id)
{ {
struct dsa_switch *ds = 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_salrn = rtl931x_l2_port_new_salrn,
.l2_port_new_sa_fwd = rtl931x_l2_port_new_sa_fwd, .l2_port_new_sa_fwd = rtl931x_l2_port_new_sa_fwd,
.get_mirror_config = rtldsa_931x_get_mirror_config, .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, .read_l2_entry_using_hash = rtl931x_read_l2_entry_using_hash,
.write_l2_entry_using_hash = rtl931x_write_l2_entry_using_hash, .write_l2_entry_using_hash = rtl931x_write_l2_entry_using_hash,
.read_cam = rtl931x_read_cam, .read_cam = rtl931x_read_cam,