net: airoha: Add matchall filter offload support

Introduce tc matchall filter offload support in airoha_eth driver.
Matchall hw filter is used to implement hw rate policing via tc action
police:

$tc qdisc add dev eth0 handle ffff: ingress
$tc filter add dev eth0 parent ffff: matchall action police \
 rate 100mbit burst 1000k drop

The current implementation supports just drop/accept as exceed/notexceed
actions. Moreover, rate and burst are the only supported configuration
parameters.

Reviewed-by: Davide Caratti <dcaratti@redhat.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
Link: https://patch.msgid.link/20250415-airoha-hw-rx-ratelimit-v4-1-03458784fbc3@kernel.org
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Lorenzo Bianconi 2025-04-15 09:14:34 +02:00 committed by Paolo Abeni
parent 4e34a84061
commit df8398fb7b
4 changed files with 286 additions and 11 deletions

View File

@ -527,6 +527,25 @@ static int airoha_fe_init(struct airoha_eth *eth)
/* disable IFC by default */
airoha_fe_clear(eth, REG_FE_CSR_IFC_CFG, FE_IFC_EN_MASK);
airoha_fe_wr(eth, REG_PPE_DFT_CPORT0(0),
FIELD_PREP(DFT_CPORT_MASK(7), FE_PSE_PORT_CDM1) |
FIELD_PREP(DFT_CPORT_MASK(6), FE_PSE_PORT_CDM1) |
FIELD_PREP(DFT_CPORT_MASK(5), FE_PSE_PORT_CDM1) |
FIELD_PREP(DFT_CPORT_MASK(4), FE_PSE_PORT_CDM1) |
FIELD_PREP(DFT_CPORT_MASK(3), FE_PSE_PORT_CDM1) |
FIELD_PREP(DFT_CPORT_MASK(2), FE_PSE_PORT_CDM1) |
FIELD_PREP(DFT_CPORT_MASK(1), FE_PSE_PORT_CDM1) |
FIELD_PREP(DFT_CPORT_MASK(0), FE_PSE_PORT_CDM1));
airoha_fe_wr(eth, REG_PPE_DFT_CPORT0(1),
FIELD_PREP(DFT_CPORT_MASK(7), FE_PSE_PORT_CDM2) |
FIELD_PREP(DFT_CPORT_MASK(6), FE_PSE_PORT_CDM2) |
FIELD_PREP(DFT_CPORT_MASK(5), FE_PSE_PORT_CDM2) |
FIELD_PREP(DFT_CPORT_MASK(4), FE_PSE_PORT_CDM2) |
FIELD_PREP(DFT_CPORT_MASK(3), FE_PSE_PORT_CDM2) |
FIELD_PREP(DFT_CPORT_MASK(2), FE_PSE_PORT_CDM2) |
FIELD_PREP(DFT_CPORT_MASK(1), FE_PSE_PORT_CDM2) |
FIELD_PREP(DFT_CPORT_MASK(0), FE_PSE_PORT_CDM2));
/* enable 1:N vlan action, init vlan table */
airoha_fe_set(eth, REG_MC_VLAN_EN, MC_VLAN_EN_MASK);
@ -1631,7 +1650,6 @@ static void airhoha_set_gdm2_loopback(struct airoha_gdm_port *port)
if (port->id == 3) {
/* FIXME: handle XSI_PCE1_PORT */
airoha_fe_wr(eth, REG_PPE_DFT_CPORT0(0), 0x5500);
airoha_fe_rmw(eth, REG_FE_WAN_PORT,
WAN1_EN_MASK | WAN1_MASK | WAN0_MASK,
FIELD_PREP(WAN0_MASK, HSGMII_LAN_PCIE0_SRCPORT));
@ -2109,6 +2127,125 @@ static int airoha_tc_setup_qdisc_ets(struct airoha_gdm_port *port,
}
}
static int airoha_qdma_get_rl_param(struct airoha_qdma *qdma, int queue_id,
u32 addr, enum trtcm_param_type param,
u32 *val_low, u32 *val_high)
{
u32 idx = QDMA_METER_IDX(queue_id), group = QDMA_METER_GROUP(queue_id);
u32 val, config = FIELD_PREP(RATE_LIMIT_PARAM_TYPE_MASK, param) |
FIELD_PREP(RATE_LIMIT_METER_GROUP_MASK, group) |
FIELD_PREP(RATE_LIMIT_PARAM_INDEX_MASK, idx);
airoha_qdma_wr(qdma, REG_TRTCM_CFG_PARAM(addr), config);
if (read_poll_timeout(airoha_qdma_rr, val,
val & RATE_LIMIT_PARAM_RW_DONE_MASK,
USEC_PER_MSEC, 10 * USEC_PER_MSEC, true, qdma,
REG_TRTCM_CFG_PARAM(addr)))
return -ETIMEDOUT;
*val_low = airoha_qdma_rr(qdma, REG_TRTCM_DATA_LOW(addr));
if (val_high)
*val_high = airoha_qdma_rr(qdma, REG_TRTCM_DATA_HIGH(addr));
return 0;
}
static int airoha_qdma_set_rl_param(struct airoha_qdma *qdma, int queue_id,
u32 addr, enum trtcm_param_type param,
u32 val)
{
u32 idx = QDMA_METER_IDX(queue_id), group = QDMA_METER_GROUP(queue_id);
u32 config = RATE_LIMIT_PARAM_RW_MASK |
FIELD_PREP(RATE_LIMIT_PARAM_TYPE_MASK, param) |
FIELD_PREP(RATE_LIMIT_METER_GROUP_MASK, group) |
FIELD_PREP(RATE_LIMIT_PARAM_INDEX_MASK, idx);
airoha_qdma_wr(qdma, REG_TRTCM_DATA_LOW(addr), val);
airoha_qdma_wr(qdma, REG_TRTCM_CFG_PARAM(addr), config);
return read_poll_timeout(airoha_qdma_rr, val,
val & RATE_LIMIT_PARAM_RW_DONE_MASK,
USEC_PER_MSEC, 10 * USEC_PER_MSEC, true,
qdma, REG_TRTCM_CFG_PARAM(addr));
}
static int airoha_qdma_set_rl_config(struct airoha_qdma *qdma, int queue_id,
u32 addr, bool enable, u32 enable_mask)
{
u32 val;
int err;
err = airoha_qdma_get_rl_param(qdma, queue_id, addr, TRTCM_MISC_MODE,
&val, NULL);
if (err)
return err;
val = enable ? val | enable_mask : val & ~enable_mask;
return airoha_qdma_set_rl_param(qdma, queue_id, addr, TRTCM_MISC_MODE,
val);
}
static int airoha_qdma_set_rl_token_bucket(struct airoha_qdma *qdma,
int queue_id, u32 rate_val,
u32 bucket_size)
{
u32 val, config, tick, unit, rate, rate_frac;
int err;
err = airoha_qdma_get_rl_param(qdma, queue_id, REG_INGRESS_TRTCM_CFG,
TRTCM_MISC_MODE, &config, NULL);
if (err)
return err;
val = airoha_qdma_rr(qdma, REG_INGRESS_TRTCM_CFG);
tick = FIELD_GET(INGRESS_FAST_TICK_MASK, val);
if (config & TRTCM_TICK_SEL)
tick *= FIELD_GET(INGRESS_SLOW_TICK_RATIO_MASK, val);
if (!tick)
return -EINVAL;
unit = (config & TRTCM_PKT_MODE) ? 1000000 / tick : 8000 / tick;
if (!unit)
return -EINVAL;
rate = rate_val / unit;
rate_frac = rate_val % unit;
rate_frac = FIELD_PREP(TRTCM_TOKEN_RATE_MASK, rate_frac) / unit;
rate = FIELD_PREP(TRTCM_TOKEN_RATE_MASK, rate) |
FIELD_PREP(TRTCM_TOKEN_RATE_FRACTION_MASK, rate_frac);
err = airoha_qdma_set_rl_param(qdma, queue_id, REG_INGRESS_TRTCM_CFG,
TRTCM_TOKEN_RATE_MODE, rate);
if (err)
return err;
val = bucket_size;
if (!(config & TRTCM_PKT_MODE))
val = max_t(u32, val, MIN_TOKEN_SIZE);
val = min_t(u32, __fls(val), MAX_TOKEN_SIZE_OFFSET);
return airoha_qdma_set_rl_param(qdma, queue_id, REG_INGRESS_TRTCM_CFG,
TRTCM_BUCKETSIZE_SHIFT_MODE, val);
}
static int airoha_qdma_init_rl_config(struct airoha_qdma *qdma, int queue_id,
bool enable, enum trtcm_unit_type unit)
{
bool tick_sel = queue_id == 0 || queue_id == 2 || queue_id == 8;
enum trtcm_param mode = TRTCM_METER_MODE;
int err;
mode |= unit == TRTCM_PACKET_UNIT ? TRTCM_PKT_MODE : 0;
err = airoha_qdma_set_rl_config(qdma, queue_id, REG_INGRESS_TRTCM_CFG,
enable, mode);
if (err)
return err;
return airoha_qdma_set_rl_config(qdma, queue_id, REG_INGRESS_TRTCM_CFG,
tick_sel, TRTCM_TICK_SEL);
}
static int airoha_qdma_get_trtcm_param(struct airoha_qdma *qdma, int channel,
u32 addr, enum trtcm_param_type param,
enum trtcm_mode_type mode,
@ -2273,10 +2410,142 @@ static int airoha_tc_htb_alloc_leaf_queue(struct airoha_gdm_port *port,
return 0;
}
static int airoha_qdma_set_rx_meter(struct airoha_gdm_port *port,
u32 rate, u32 bucket_size,
enum trtcm_unit_type unit_type)
{
struct airoha_qdma *qdma = port->qdma;
int i;
for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
int err;
if (!qdma->q_rx[i].ndesc)
continue;
err = airoha_qdma_init_rl_config(qdma, i, !!rate, unit_type);
if (err)
return err;
err = airoha_qdma_set_rl_token_bucket(qdma, i, rate,
bucket_size);
if (err)
return err;
}
return 0;
}
static int airoha_tc_matchall_act_validate(struct tc_cls_matchall_offload *f)
{
const struct flow_action *actions = &f->rule->action;
const struct flow_action_entry *act;
if (!flow_action_has_entries(actions)) {
NL_SET_ERR_MSG_MOD(f->common.extack,
"filter run with no actions");
return -EINVAL;
}
if (!flow_offload_has_one_action(actions)) {
NL_SET_ERR_MSG_MOD(f->common.extack,
"only once action per filter is supported");
return -EOPNOTSUPP;
}
act = &actions->entries[0];
if (act->id != FLOW_ACTION_POLICE) {
NL_SET_ERR_MSG_MOD(f->common.extack, "unsupported action");
return -EOPNOTSUPP;
}
if (act->police.exceed.act_id != FLOW_ACTION_DROP) {
NL_SET_ERR_MSG_MOD(f->common.extack,
"invalid exceed action id");
return -EOPNOTSUPP;
}
if (act->police.notexceed.act_id != FLOW_ACTION_ACCEPT) {
NL_SET_ERR_MSG_MOD(f->common.extack,
"invalid notexceed action id");
return -EOPNOTSUPP;
}
if (act->police.notexceed.act_id == FLOW_ACTION_ACCEPT &&
!flow_action_is_last_entry(actions, act)) {
NL_SET_ERR_MSG_MOD(f->common.extack,
"action accept must be last");
return -EOPNOTSUPP;
}
if (act->police.peakrate_bytes_ps || act->police.avrate ||
act->police.overhead || act->police.mtu) {
NL_SET_ERR_MSG_MOD(f->common.extack,
"peakrate/avrate/overhead/mtu unsupported");
return -EOPNOTSUPP;
}
return 0;
}
static int airoha_dev_tc_matchall(struct net_device *dev,
struct tc_cls_matchall_offload *f)
{
enum trtcm_unit_type unit_type = TRTCM_BYTE_UNIT;
struct airoha_gdm_port *port = netdev_priv(dev);
u32 rate = 0, bucket_size = 0;
switch (f->command) {
case TC_CLSMATCHALL_REPLACE: {
const struct flow_action_entry *act;
int err;
err = airoha_tc_matchall_act_validate(f);
if (err)
return err;
act = &f->rule->action.entries[0];
if (act->police.rate_pkt_ps) {
rate = act->police.rate_pkt_ps;
bucket_size = act->police.burst_pkt;
unit_type = TRTCM_PACKET_UNIT;
} else {
rate = div_u64(act->police.rate_bytes_ps, 1000);
rate = rate << 3; /* Kbps */
bucket_size = act->police.burst;
}
fallthrough;
}
case TC_CLSMATCHALL_DESTROY:
return airoha_qdma_set_rx_meter(port, rate, bucket_size,
unit_type);
default:
return -EOPNOTSUPP;
}
}
static int airoha_dev_setup_tc_block_cb(enum tc_setup_type type,
void *type_data, void *cb_priv)
{
struct net_device *dev = cb_priv;
if (!tc_can_offload(dev))
return -EOPNOTSUPP;
switch (type) {
case TC_SETUP_CLSFLOWER:
return airoha_ppe_setup_tc_block_cb(dev, type_data);
case TC_SETUP_CLSMATCHALL:
return airoha_dev_tc_matchall(dev, type_data);
default:
return -EOPNOTSUPP;
}
}
static int airoha_dev_setup_tc_block(struct airoha_gdm_port *port,
struct flow_block_offload *f)
{
flow_setup_cb_t *cb = airoha_ppe_setup_tc_block_cb;
flow_setup_cb_t *cb = airoha_dev_setup_tc_block_cb;
static LIST_HEAD(block_cb_list);
struct flow_block_cb *block_cb;

View File

@ -127,6 +127,11 @@ enum tx_sched_mode {
TC_SCH_WRR2,
};
enum trtcm_unit_type {
TRTCM_BYTE_UNIT,
TRTCM_PACKET_UNIT,
};
enum trtcm_param_type {
TRTCM_MISC_MODE, /* meter_en, pps_mode, tick_sel */
TRTCM_TOKEN_RATE_MODE,
@ -554,8 +559,7 @@ bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
void airoha_ppe_check_skb(struct airoha_ppe *ppe, struct sk_buff *skb,
u16 hash);
int airoha_ppe_setup_tc_block_cb(enum tc_setup_type type, void *type_data,
void *cb_priv);
int airoha_ppe_setup_tc_block_cb(struct net_device *dev, void *type_data);
int airoha_ppe_init(struct airoha_eth *eth);
void airoha_ppe_deinit(struct airoha_eth *eth);
struct airoha_foe_entry *airoha_ppe_foe_get_entry(struct airoha_ppe *ppe,

View File

@ -967,18 +967,13 @@ static int airoha_ppe_offload_setup(struct airoha_eth *eth)
return err;
}
int airoha_ppe_setup_tc_block_cb(enum tc_setup_type type, void *type_data,
void *cb_priv)
int airoha_ppe_setup_tc_block_cb(struct net_device *dev, void *type_data)
{
struct flow_cls_offload *cls = type_data;
struct net_device *dev = cb_priv;
struct airoha_gdm_port *port = netdev_priv(dev);
struct flow_cls_offload *cls = type_data;
struct airoha_eth *eth = port->qdma->eth;
int err = 0;
if (!tc_can_offload(dev) || type != TC_SETUP_CLSFLOWER)
return -EOPNOTSUPP;
mutex_lock(&flow_offload_mutex);
if (!eth->npu)

View File

@ -283,6 +283,7 @@
#define PPE_HASH_SEED 0x12345678
#define REG_PPE_DFT_CPORT0(_n) (((_n) ? PPE2_BASE : PPE1_BASE) + 0x248)
#define DFT_CPORT_MASK(_n) GENMASK(3 + ((_n) << 2), ((_n) << 2))
#define REG_PPE_DFT_CPORT1(_n) (((_n) ? PPE2_BASE : PPE1_BASE) + 0x24c)
@ -691,6 +692,12 @@
#define REG_TRTCM_DATA_LOW(_n) ((_n) + 0x8)
#define REG_TRTCM_DATA_HIGH(_n) ((_n) + 0xc)
#define RATE_LIMIT_PARAM_RW_MASK BIT(31)
#define RATE_LIMIT_PARAM_RW_DONE_MASK BIT(30)
#define RATE_LIMIT_PARAM_TYPE_MASK GENMASK(29, 28)
#define RATE_LIMIT_METER_GROUP_MASK GENMASK(27, 26)
#define RATE_LIMIT_PARAM_INDEX_MASK GENMASK(23, 16)
#define REG_TXWRR_MODE_CFG 0x1020
#define TWRR_WEIGHT_SCALE_MASK BIT(31)
#define TWRR_WEIGHT_BASE_MASK BIT(3)