* [PATCH net-next 0/6] net: dsa: motorcomm: TC offload follow-ups
@ 2026-09-26 21:17 David Yang
2026-09-26 21:17 ` [PATCH net-next 1/6] net: dsa: motorcomm: Split TC module David Yang
` (5 more replies)
0 siblings, 6 replies; 12+ messages in thread
From: David Yang @ 2026-09-26 21:17 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
This series continues the traffic control work on the Motorcomm YT9215S
DSA driver: it reorganizes the offload code and then adds ACL enablement
on demand, flower priority mapping, flow statistics, and
broadcast/multicast policers.
David Yang (6):
net: dsa: motorcomm: Split TC module
net: dsa: motorcomm: Use NSEC_PER_SEC
net: dsa: motorcomm: Enable ACL on demand
net: dsa: motorcomm: Add TC prio support
net: dsa: motorcomm: Add limited ACL flow statistics support
net: dsa: motorcomm: Add broadcast/multicast policers via tc police
drivers/net/dsa/motorcomm/Makefile | 1 +
drivers/net/dsa/motorcomm/chip.c | 1835 +++-------------------------
drivers/net/dsa/motorcomm/chip.h | 360 +-----
drivers/net/dsa/motorcomm/smi.c | 33 +
drivers/net/dsa/motorcomm/smi.h | 2 +
drivers/net/dsa/motorcomm/tc.c | 1792 +++++++++++++++++++++++++++
drivers/net/dsa/motorcomm/tc.h | 414 +++++++
7 files changed, 2459 insertions(+), 1978 deletions(-)
create mode 100644 drivers/net/dsa/motorcomm/tc.c
create mode 100644 drivers/net/dsa/motorcomm/tc.h
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net-next 1/6] net: dsa: motorcomm: Split TC module
2026-09-26 21:17 [PATCH net-next 0/6] net: dsa: motorcomm: TC offload follow-ups David Yang
@ 2026-09-26 21:17 ` David Yang
2026-09-30 3:19 ` netdev-bot+sashiko
2026-09-26 21:17 ` [PATCH net-next 2/6] net: dsa: motorcomm: Use NSEC_PER_SEC David Yang
` (4 subsequent siblings)
5 siblings, 1 reply; 12+ messages in thread
From: David Yang @ 2026-09-26 21:17 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
Move the traffic control code from chip.c into a separate tc.c, to
reduce the length of chip.c.
Signed-off-by: David Yang <mmyangfl@gmail.com>
---
drivers/net/dsa/motorcomm/Makefile | 1 +
drivers/net/dsa/motorcomm/chip.c | 1458 +---------------------------
drivers/net/dsa/motorcomm/chip.h | 350 +------
drivers/net/dsa/motorcomm/tc.c | 1457 +++++++++++++++++++++++++++
drivers/net/dsa/motorcomm/tc.h | 380 ++++++++
5 files changed, 1849 insertions(+), 1797 deletions(-)
create mode 100644 drivers/net/dsa/motorcomm/tc.c
create mode 100644 drivers/net/dsa/motorcomm/tc.h
diff --git a/drivers/net/dsa/motorcomm/Makefile b/drivers/net/dsa/motorcomm/Makefile
index 1d2c1b3064c4..b4a577012b0f 100644
--- a/drivers/net/dsa/motorcomm/Makefile
+++ b/drivers/net/dsa/motorcomm/Makefile
@@ -5,3 +5,4 @@ yt921x-$(CONFIG_NET_DSA_YT921X_LEDS) += leds.o
yt921x-objs += mdio_bus.o
yt921x-objs += pcs-921x.o
yt921x-objs += smi.o
+yt921x-objs += tc.o
diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index 3ff2de383157..e9d3058462e5 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -23,13 +23,13 @@
#include <net/dsa.h>
#include <net/dscp.h>
#include <net/ieee8021q.h>
-#include <net/pkt_cls.h>
#include "chip.h"
#include "leds.h"
#include "mdio_bus.h"
#include "pcs.h"
#include "smi.h"
+#include "tc.h"
struct yt921x_mib_desc {
unsigned int size;
@@ -174,16 +174,6 @@ struct yt921x_reg_mdio {
#define to_yt921x_priv(_ds) container_of_const(_ds, struct yt921x_priv, ds)
#define to_device(priv) ((priv)->ds.dev)
-static u32 ethaddr_hi4_to_u32(const unsigned char *addr)
-{
- return (addr[0] << 24) | (addr[1] << 16) | (addr[2] << 8) | addr[3];
-}
-
-static u32 ethaddr_lo2_to_u32(const unsigned char *addr)
-{
- return (addr[4] << 8) | addr[5];
-}
-
static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
{
struct yt921x_reg_mdio *mdio = context;
@@ -608,13 +598,6 @@ yt921x_dsa_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_keee *e)
return res;
}
-static int yt921x_mtu_fetch(struct yt921x_priv *priv, int port)
-{
- struct dsa_port *dp = dsa_to_port(&priv->ds, port);
-
- return dp->user ? READ_ONCE(dp->user->mtu) : ETH_DATA_LEN;
-}
-
static int
yt921x_dsa_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
{
@@ -646,1445 +629,6 @@ static int yt921x_dsa_port_max_mtu(struct dsa_switch *ds, int port)
return YT921X_FRAME_SIZE_MAX - ETH_HLEN - ETH_FCS_LEN - YT921X_TAG_LEN;
}
-/* v * 2^e */
-static u64 ldexpu64(u64 v, int e)
-{
- return e >= 0 ? v << e : v >> -e;
-}
-
-/* slot (ns) * rate (/s) / 10^9 (ns/s) = 2^C * token * 4^unit */
-static u32 rate2token(u64 rate, unsigned int slot_ns, int unit, int C)
-{
- int e = 2 * unit + C + YT921X_TOKEN_RATE_C;
-
- return div_u64(ldexpu64(slot_ns * rate, -e), 1000000000);
-}
-
-static u64 token2rate(u32 token, unsigned int slot_ns, int unit, int C)
-{
- int e = 2 * unit + C + YT921X_TOKEN_RATE_C;
-
- return div_u64(ldexpu64(mul_u32_u32(1000000000, token), e), slot_ns);
-}
-
-/* burst = 2^C * token * 4^unit */
-static u32 burst2token(u64 burst, int unit, int C)
-{
- return ldexpu64(burst, -(2 * unit + C));
-}
-
-static u64 token2burst(u32 token, int unit, int C)
-{
- return ldexpu64(token, 2 * unit + C);
-}
-
-struct yt921x_marker {
- u32 cir;
- u32 cbs;
- u32 ebs;
- int unit;
- bool pkt_mode;
-};
-
-#define YT921X_MARKER_PKT_MODE BIT(0)
-#define YT921X_MARKER_SINGLE_BUCKET BIT(1)
-
-static int
-yt921x_marker_tfm(struct yt921x_marker *marker, u64 rate, u64 burst,
- unsigned int flags, unsigned int slot_ns, u32 cir_max,
- u32 cbs_max, int unit_max, struct yt921x_priv *priv, int port,
- struct netlink_ext_ack *extack)
-{
- const int C = flags & YT921X_MARKER_PKT_MODE ? YT921X_TOKEN_PKT_C :
- YT921X_TOKEN_BYTE_C;
- struct device *dev = to_device(priv);
- struct yt921x_marker m;
- u64 burst_est;
- u64 burst_sug;
- u64 burst_max;
- u64 rate_max;
-
- m.unit = unit_max;
- rate_max = token2rate(cir_max, slot_ns, m.unit, C);
- burst_max = token2burst(cbs_max, m.unit, C);
-
- /* Check for unusual values */
- if (rate > rate_max || burst > burst_max) {
- NL_SET_ERR_MSG_MOD(extack, "Unexpected tremendous rate");
- return -ERANGE;
- }
-
- /* Check for matching burst */
- burst_est = div_u64(slot_ns * rate, 1000000000);
- burst_sug = burst_est;
- if (flags & YT921X_MARKER_PKT_MODE)
- burst_sug++;
- else
- burst_sug += ETH_HLEN + yt921x_mtu_fetch(priv, port) +
- ETH_FCS_LEN;
- if (burst_sug > burst)
- NL_SET_ERR_MSG_FMT_MOD(extack,
- "Consider match rate %llu with burst at least %llu",
- rate, burst_sug);
-
- /* Select unit */
- for (; m.unit > 0; m.unit--) {
- if (rate > (rate_max >> 2) || burst > (burst_max >> 2))
- break;
- rate_max >>= 2;
- burst_max >>= 2;
- }
-
- /* Calculate information rate and bucket size */
- m.cir = rate2token(rate, slot_ns, m.unit, C);
- if (!m.cir)
- m.cir = 1;
- else if (WARN_ON(m.cir > cir_max))
- m.cir = cir_max;
- m.cbs = burst2token(burst, m.unit, C);
- if (!m.cbs)
- m.cbs = 1;
- else if (WARN_ON(m.cbs > cbs_max))
- m.cbs = cbs_max;
-
- /* Cut EBS */
- m.ebs = 0;
- if (!(flags & YT921X_MARKER_SINGLE_BUCKET)) {
- /* We don't have a chance to adjust rate when MTU is changed */
- if (flags & YT921X_MARKER_PKT_MODE)
- burst_est++;
- else
- burst_est += YT921X_FRAME_SIZE_MAX;
-
- if (burst_est < burst) {
- u32 pbs = m.cbs;
-
- m.cbs = burst2token(burst_est, m.unit, C);
- if (!m.cbs)
- m.cbs = 1;
- else if (WARN_ON(m.cbs > cbs_max))
- m.cbs = cbs_max;
-
- if (pbs > m.cbs)
- m.ebs = pbs - m.cbs;
- }
- }
-
- dev_dbg(dev,
- "slot %u ns, rate %llu, burst %llu -> unit %d, cir %u, cbs %u, ebs %u\n",
- slot_ns, rate, burst, m.unit, m.cir, m.cbs, m.ebs);
-
- m.pkt_mode = flags & YT921X_MARKER_PKT_MODE;
- *marker = m;
- return 0;
-}
-
-static int
-yt921x_marker_tfm_police(struct yt921x_marker *marker,
- const struct flow_action_police *police,
- unsigned int flags, struct yt921x_priv *priv, int port,
- struct netlink_ext_ack *extack)
-{
- bool pkt_mode = !!police->rate_pkt_ps;
- u64 burst;
- u64 rate;
-
- rate = pkt_mode ? police->rate_pkt_ps : police->rate_bytes_ps;
- burst = pkt_mode ? police->burst_pkt : police->burst;
- if (pkt_mode)
- flags |= YT921X_MARKER_PKT_MODE;
-
- return yt921x_marker_tfm(marker, rate, burst, flags,
- priv->meter_slot_ns, YT921X_METER_CIR_MAX,
- YT921X_METER_CBS_MAX, YT921X_METER_UNIT_MAX,
- priv, port, extack);
-}
-
-static int
-yt921x_marker_tfm_shape(struct yt921x_marker *marker, u64 rate, u64 burst,
- unsigned int flags, struct yt921x_priv *priv, int port,
- struct netlink_ext_ack *extack)
-{
- return yt921x_marker_tfm(marker, rate, burst, flags,
- priv->port_shape_slot_ns, YT921X_SHAPE_CIR_MAX,
- YT921X_SHAPE_CBS_MAX, YT921X_SHAPE_UNIT_MAX,
- priv, port, extack);
-}
-
-static int
-yt921x_police_validate(const struct flow_action_police *police,
- const struct flow_action *action,
- const struct flow_action_entry *act,
- struct netlink_ext_ack *extack)
-{
- if (police->exceed.act_id != FLOW_ACTION_DROP) {
- NL_SET_ERR_MSG_MOD(extack,
- "Offload not supported when exceed action is not drop");
- return -EOPNOTSUPP;
- }
-
- if (police->notexceed.act_id != FLOW_ACTION_PIPE &&
- police->notexceed.act_id != FLOW_ACTION_ACCEPT) {
- NL_SET_ERR_MSG_MOD(extack,
- "Offload not supported when conform action is not pipe or ok");
- return -EOPNOTSUPP;
- }
-
- if (police->notexceed.act_id == FLOW_ACTION_ACCEPT && action && act &&
- !flow_action_is_last_entry(action, act)) {
- NL_SET_ERR_MSG_MOD(extack,
- "Offload not supported when conform action is ok, but action is not last");
- return -EOPNOTSUPP;
- }
-
- /* mtu defaults to unlimited but we got 2040 here, don't know why */
- if (police->peakrate_bytes_ps || police->avrate || police->overhead) {
- NL_SET_ERR_MSG_MOD(extack,
- "Offload not supported when peakrate/avrate/overhead is configured");
- return -EOPNOTSUPP;
- }
-
- return 0;
-}
-
-static int
-yt921x_meter_config(struct yt921x_priv *priv, unsigned int id,
- const struct yt921x_marker *marker)
-{
- u32 ctrls[3];
-
- ctrls[0] = 0;
- ctrls[1] = YT921X_METER_CTRLb_CIR(marker->cir);
- ctrls[2] = YT921X_METER_CTRLc_UNIT(marker->unit) |
- YT921X_METER_CTRLc_DROP_R |
- YT921X_METER_CTRLc_TOKEN_OVERFLOW_EN |
- YT921X_METER_CTRLc_METER_EN;
- if (marker->pkt_mode)
- ctrls[2] |= YT921X_METER_CTRLc_PKT_MODE;
- update_ctrls_unaligned(&ctrls[0], &ctrls[1],
- YT921X_METER_CTRLab_EBS_M,
- YT921X_METER_CTRLab_EBS(marker->ebs));
- update_ctrls_unaligned(&ctrls[1], &ctrls[2],
- YT921X_METER_CTRLbc_CBS_M,
- YT921X_METER_CTRLbc_CBS(marker->cbs));
-
- return yt921x_reg96_write(priv, YT921X_METERn_CTRL(id), ctrls);
-}
-
-static void yt921x_dsa_port_policer_del(struct dsa_switch *ds, int port)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct device *dev = to_device(priv);
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_reg_write(priv, YT921X_PORTn_METER(port), 0);
- mutex_unlock(&priv->reg_lock);
-
- if (res)
- dev_err(dev, "Failed to %s port %d: %i\n", "delete policer on",
- port, res);
-}
-
-static int
-yt921x_dsa_port_policer_add(struct dsa_switch *ds, int port,
- const struct flow_action_police *police,
- struct netlink_ext_ack *extack)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct yt921x_marker marker;
- u32 ctrl;
- int res;
-
- res = yt921x_police_validate(police, NULL, NULL, extack);
- if (res)
- return res;
-
- res = yt921x_marker_tfm_police(&marker, police, 0, priv, port, extack);
- if (res)
- return res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_meter_config(priv, port + YT921X_METER_NUM, &marker);
- if (res)
- goto end;
-
- ctrl = YT921X_PORT_METER_ID(port) | YT921X_PORT_METER_EN;
- res = yt921x_reg_write(priv, YT921X_PORTn_METER(port), ctrl);
-end:
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_dsa_port_setup_tc_tbf_port(struct dsa_switch *ds, int port,
- const struct tc_tbf_qopt_offload *qopt)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct netlink_ext_ack *extack = qopt->extack;
- u32 ctrls[2];
- int res;
-
- if (qopt->parent != TC_H_ROOT)
- return -EOPNOTSUPP;
-
- switch (qopt->command) {
- case TC_TBF_STATS:
- /* Unfortunately the convention for TC_*_STATS is a mess,
- * neither 0 nor -EOPNOTSUPP is perfect here.
- */
- return -EOPNOTSUPP;
- case TC_TBF_DESTROY:
- ctrls[0] = 0;
- ctrls[1] = 0;
- break;
- case TC_TBF_REPLACE: {
- const struct tc_tbf_qopt_offload_replace_params *p;
- struct yt921x_marker marker;
-
- p = &qopt->replace_params;
-
- res = yt921x_marker_tfm_shape(&marker, p->rate.rate_bytes_ps,
- p->max_size,
- YT921X_MARKER_SINGLE_BUCKET,
- priv, port, extack);
- if (res)
- return res;
-
- ctrls[0] = YT921X_PORT_SHAPE_CTRLa_CIR(marker.cir) |
- YT921X_PORT_SHAPE_CTRLa_CBS(marker.cbs);
- ctrls[1] = YT921X_PORT_SHAPE_CTRLb_UNIT(marker.unit) |
- YT921X_PORT_SHAPE_CTRLb_EN;
- break;
- }
- default:
- return -EOPNOTSUPP;
- }
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_reg64_write(priv, YT921X_PORTn_SHAPE_CTRL(port), ctrls);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_dsa_port_setup_tc(struct dsa_switch *ds, int port,
- enum tc_setup_type type, void *type_data)
-{
- switch (type) {
- case TC_SETUP_QDISC_TBF: {
- const struct tc_tbf_qopt_offload *qopt = type_data;
-
- return yt921x_dsa_port_setup_tc_tbf_port(ds, port, qopt);
- }
- default:
- return -EOPNOTSUPP;
- }
-}
-
-/* ACL: 48 blocks * 8 entries
- *
- * One rule can span multiple entries, but within a block.
- */
-
-static void
-yt921x_acl_entry_set(struct yt921x_acl_entry *entry, unsigned int offset,
- u32 flags, bool set)
-{
- if (set)
- entry->key[offset] |= flags;
- entry->mask[offset] |= flags;
-}
-
-static unsigned int
-yt921x_acl_entries_set_is_fragment(struct yt921x_acl_entry *entries,
- unsigned int size, bool set)
-{
- for (unsigned int i = 0; i < size; i++)
- switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) {
- case YT921X_ACL_TYPE_IPV4_DA:
- case YT921X_ACL_TYPE_IPV4_SA:
- yt921x_acl_entry_set(&entries[i], 1,
- YT921X_ACL_BINb_IPV4_FRAG, set);
- return size;
- case YT921X_ACL_TYPE_IPV6_DA3:
- case YT921X_ACL_TYPE_IPV6_SA3:
- yt921x_acl_entry_set(&entries[i], 1,
- YT921X_ACL_BINb_IPV6_xA3_FRAG,
- set);
- return size;
- case YT921X_ACL_TYPE_MISC:
- yt921x_acl_entry_set(&entries[i], 1,
- YT921X_ACL_BINb_MISC_FRAG, set);
- return size;
- case YT921X_ACL_TYPE_L4:
- yt921x_acl_entry_set(&entries[i], 1,
- YT921X_ACL_BINb_L4_FRAG, set);
- return size;
- }
-
- if (size >= YT921X_ACL_ENT_PER_BLK)
- return 0;
-
- entries[size] = (typeof(*entries)){};
- entries[size].key[1] = YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC);
- yt921x_acl_entry_set(&entries[size], 1, YT921X_ACL_BINb_MISC_FRAG, set);
-
- return size + 1;
-}
-
-static unsigned int
-yt921x_acl_entries_set_first_frag(struct yt921x_acl_entry *entries,
- unsigned int size, bool set)
-{
- for (unsigned int i = 0; i < size; i++)
- switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) {
- case YT921X_ACL_TYPE_IPV6_DA2:
- case YT921X_ACL_TYPE_IPV6_SA2:
- yt921x_acl_entry_set(&entries[i], 1,
- YT921X_ACL_BINb_IPV6_xA2_FIRST_FRAG,
- set);
- return size;
- case YT921X_ACL_TYPE_MISC:
- yt921x_acl_entry_set(&entries[i], 0,
- YT921X_ACL_BINa_MISC_FIRST_FRAG,
- set);
- return size;
- }
-
- if (size >= YT921X_ACL_ENT_PER_BLK)
- return 0;
-
- entries[size] = (typeof(*entries)){};
- entries[size].key[1] = YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC);
- yt921x_acl_entry_set(&entries[size], 0,
- YT921X_ACL_BINa_MISC_FIRST_FRAG, set);
-
- return size + 1;
-}
-
-static unsigned int
-yt921x_acl_entries_set_l3_type(struct yt921x_acl_entry *entries,
- unsigned int size, enum yt921x_l3_type type)
-{
- for (unsigned int i = 0; i < size; i++)
- switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) {
- case YT921X_ACL_TYPE_MAC_DA0:
- case YT921X_ACL_TYPE_MAC_SA0:
- entries[i].key[1] |= YT921X_ACL_BINb_MAC_xA0_L3_TYPE(type);
- entries[i].mask[1] |= YT921X_ACL_BINb_MAC_xA0_L3_TYPE_M;
- return size;
- case YT921X_ACL_TYPE_MISC:
- entries[i].key[0] |= YT921X_ACL_BINa_MISC_L3_TYPE(type);
- entries[i].mask[0] |= YT921X_ACL_BINa_MISC_L3_TYPE_M;
- return size;
- }
-
- if (size >= YT921X_ACL_ENT_PER_BLK)
- return 0;
-
- entries[size] = (typeof(*entries)){};
- entries[size].key[0] = YT921X_ACL_BINa_MISC_L3_TYPE(type);
- entries[size].key[1] = YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC);
- entries[size].mask[0] = YT921X_ACL_BINa_MISC_L3_TYPE_M;
-
- return size + 1;
-}
-
-static unsigned int
-yt921x_acl_entries_set_l4_type(struct yt921x_acl_entry *entries,
- unsigned int size, enum yt921x_l4_type type)
-{
- for (unsigned int i = 0; i < size; i++)
- switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) {
- case YT921X_ACL_TYPE_IPV4_DA:
- case YT921X_ACL_TYPE_IPV4_SA:
- entries[i].key[1] |= YT921X_ACL_BINb_IPV4_L4_TYPE(type);
- entries[i].mask[1] |= YT921X_ACL_BINb_IPV4_L4_TYPE_M;
- return size;
- case YT921X_ACL_TYPE_IPV6_DA0:
- case YT921X_ACL_TYPE_IPV6_DA1:
- case YT921X_ACL_TYPE_IPV6_DA2:
- case YT921X_ACL_TYPE_IPV6_DA3:
- case YT921X_ACL_TYPE_IPV6_SA0:
- case YT921X_ACL_TYPE_IPV6_SA1:
- case YT921X_ACL_TYPE_IPV6_SA2:
- case YT921X_ACL_TYPE_IPV6_SA3:
- entries[i].key[1] |= YT921X_ACL_BINb_IPV6_L4_TYPE(type);
- entries[i].mask[1] |= YT921X_ACL_BINb_IPV6_L4_TYPE_M;
- return size;
- case YT921X_ACL_TYPE_L4:
- entries[i].key[1] |= YT921X_ACL_BINb_L4_TYPE(type);
- entries[i].mask[1] |= YT921X_ACL_BINb_L4_TYPE_M;
- return size;
- case YT921X_ACL_TYPE_MISC:
- entries[i].key[1] |= YT921X_ACL_BINb_MISC_L4_TYPE(type);
- entries[i].mask[1] |= YT921X_ACL_BINb_MISC_L4_TYPE_M;
- return size;
- }
-
- if (size >= YT921X_ACL_ENT_PER_BLK)
- return 0;
-
- entries[size] = (typeof(*entries)){};
- entries[size].key[1] = YT921X_ACL_BINb_MISC_L4_TYPE(type) |
- YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC);
- entries[size].mask[1] = YT921X_ACL_BINb_MISC_L4_TYPE_M;
-
- return size + 1;
-}
-
-static struct yt921x_acl_entry *
-yt921x_acl_entries_new(struct yt921x_acl_entry *entries, unsigned int *sizep,
- u32 type)
-{
- unsigned int size = *sizep;
-
- if (size >= YT921X_ACL_ENT_PER_BLK)
- return NULL;
-
- entries[size] = (typeof(*entries)){};
- entries[size].key[1] = YT921X_ACL_KEYb_TYPE(type);
-
- (*sizep)++;
- return &entries[size];
-}
-
-static struct yt921x_acl_entry *
-yt921x_acl_entries_find(struct yt921x_acl_entry *entries, unsigned int *sizep,
- u32 type)
-{
- for (unsigned int i = 0; i < *sizep; i++)
- if (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1]) ==
- type)
- return &entries[i];
- return yt921x_acl_entries_new(entries, sizep, type);
-}
-
-static void
-yt921x_acl_rule_set_ports(struct yt921x_acl_rule *aclrule, u16 ord,
- u16 ports_mask)
-{
- struct yt921x_acl_entry *entries = aclrule->entries;
-
- for (unsigned int i = 0; i < hweight8(aclrule->mask); i++) {
- entries[i].key[1] |= YT921X_ACL_KEYb_SPORTS(ports_mask) |
- YT921X_ACL_KEYb_ORD(ord);
- }
-}
-
-struct yt921x_acl_rule_ext {
- struct yt921x_acl_rule r;
-
- struct yt921x_marker marker;
-};
-
-static int
-yt921x_acl_rule_ext_parse_flow_entries(struct yt921x_acl_rule_ext *ruleext,
- const struct flow_cls_offload *cls)
-{
- const struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
- struct yt921x_acl_entry *entries = ruleext->r.entries;
- struct netlink_ext_ack *extack = cls->common.extack;
- const struct flow_dissector *dissector;
- struct yt921x_acl_entry *entry;
- unsigned int size = 0;
- bool use_dport;
- bool use_sport;
-
- /* Incomplete and probably won't, since it supports custom u32 filters.
- * New adapters are welcome.
- */
- dissector = rule->match.dissector;
- if (dissector->used_keys &
- ~(BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL) |
- BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) |
- BIT_ULL(FLOW_DISSECTOR_KEY_IPV4_ADDRS) |
- BIT_ULL(FLOW_DISSECTOR_KEY_IPV6_ADDRS) |
- BIT_ULL(FLOW_DISSECTOR_KEY_PORTS) |
- BIT_ULL(FLOW_DISSECTOR_KEY_PORTS_RANGE) |
- BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS) |
- BIT_ULL(FLOW_DISSECTOR_KEY_IP) |
- BIT_ULL(FLOW_DISSECTOR_KEY_TCP))) {
- NL_SET_ERR_MSG_MOD(extack, "Unsupported keys used");
- return -EOPNOTSUPP;
- }
-
- /* Entries */
- if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV4_ADDRS)) {
- struct flow_match_ipv4_addrs match;
-
- flow_rule_match_ipv4_addrs(rule, &match);
-
- if (match.mask->dst) {
- entry = yt921x_acl_entries_new(entries, &size,
- YT921X_ACL_TYPE_IPV4_DA);
- if (!entry)
- goto err;
-
- entry->key[0] |= ntohl(match.key->dst);
- entry->mask[0] |= ntohl(match.mask->dst);
- }
-
- if (match.mask->src) {
- entry = yt921x_acl_entries_new(entries, &size,
- YT921X_ACL_TYPE_IPV4_SA);
- if (!entry)
- goto err;
-
- entry->key[0] |= ntohl(match.key->src);
- entry->mask[0] |= ntohl(match.mask->src);
- }
- }
-
- if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV6_ADDRS)) {
- struct flow_match_ipv6_addrs match;
-
- flow_rule_match_ipv6_addrs(rule, &match);
-
- for (unsigned int i = 0; i < 4; i++) {
- if (!match.mask->dst.s6_addr32[i])
- continue;
-
- entry = yt921x_acl_entries_new(entries, &size,
- YT921X_ACL_TYPE_IPV6_DA0 + i);
- if (!entry)
- goto err;
-
- entry->key[0] |= ntohl(match.key->dst.s6_addr32[i]);
- entry->mask[0] |= ntohl(match.mask->dst.s6_addr32[i]);
- }
-
- for (unsigned int i = 0; i < 4; i++) {
- if (!match.mask->src.s6_addr32[i])
- continue;
-
- entry = yt921x_acl_entries_new(entries, &size,
- YT921X_ACL_TYPE_IPV6_SA0 + i);
- if (!entry)
- goto err;
-
- entry->key[0] |= ntohl(match.key->src.s6_addr32[i]);
- entry->mask[0] |= ntohl(match.mask->src.s6_addr32[i]);
- }
- }
-
- use_dport = false;
- use_sport = false;
- if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) {
- struct flow_match_ports match;
-
- entry = yt921x_acl_entries_new(entries, &size,
- YT921X_ACL_TYPE_L4);
- if (!entry)
- goto err;
-
- flow_rule_match_ports(rule, &match);
-
- use_dport = !!match.mask->dst;
- use_sport = !!match.mask->src;
-
- entry->key[0] |= (ntohs(match.key->dst) << 16) |
- ntohs(match.key->src);
- entry->mask[0] |= (ntohs(match.mask->dst) << 16) |
- ntohs(match.mask->src);
- }
-
- if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS_RANGE)) {
- struct flow_match_ports_range match;
-
- entry = yt921x_acl_entries_find(entries, &size,
- YT921X_ACL_TYPE_L4);
- if (!entry)
- goto err;
-
- flow_rule_match_ports_range(rule, &match);
-
- if ((use_dport && match.mask->tp.dst) ||
- (use_sport && match.mask->tp.src)) {
- NL_SET_ERR_MSG_MOD(extack,
- "Port mask and range are mutually exclusive");
- return -EINVAL;
- }
-
- if (match.mask->tp.dst) {
- entry->key[0] |= ntohs(match.key->tp_min.dst) << 16;
- entry->key[1] |= YT921X_ACL_KEYb_L4_DPORT_RANGE_EN;
- entry->mask[0] |= ntohs(match.key->tp_max.dst) << 16;
- }
-
- if (match.mask->tp.src) {
- entry->key[0] |= ntohs(match.key->tp_min.src);
- entry->key[1] |= YT921X_ACL_KEYb_L4_SPORT_RANGE_EN;
- entry->mask[0] |= ntohs(match.key->tp_max.src);
- }
- }
-
- if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
- struct flow_match_eth_addrs match;
- u32 mask;
-
- flow_rule_match_eth_addrs(rule, &match);
-
- mask = ethaddr_hi4_to_u32(match.mask->dst);
- if (mask) {
- entry = yt921x_acl_entries_new(entries, &size,
- YT921X_ACL_TYPE_MAC_DA0);
- if (!entry)
- goto err;
-
- entry->key[0] |= ethaddr_hi4_to_u32(match.key->dst);
- entry->mask[0] |= mask;
- }
-
- mask = ethaddr_hi4_to_u32(match.mask->src);
- if (mask) {
- entry = yt921x_acl_entries_new(entries, &size,
- YT921X_ACL_TYPE_MAC_SA0);
- if (!entry)
- goto err;
-
- entry->key[0] |= ethaddr_hi4_to_u32(match.key->src);
- entry->mask[0] |= mask;
- }
-
- mask = (ethaddr_lo2_to_u32(match.mask->dst) << 16) |
- ethaddr_lo2_to_u32(match.mask->src);
- if (mask) {
- entry = yt921x_acl_entries_new(entries, &size,
- YT921X_ACL_TYPE_MAC_DA1_SA1);
- if (!entry)
- goto err;
-
- entry->key[0] |= (ethaddr_lo2_to_u32(match.key->dst) << 16) |
- ethaddr_lo2_to_u32(match.key->src);
- entry->mask[0] |= mask;
- }
- }
-
- /* Entries + Misc */
- if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
- struct flow_match_basic match;
-
- flow_rule_match_basic(rule, &match);
-
- if (match.mask->n_proto) {
- enum yt921x_l3_type l3type = YT921X_L3_TYPE_OTHER;
-
- if (match.mask->n_proto == htons(~0))
- switch (match.key->n_proto) {
- case htons(ETH_P_IP):
- l3type = YT921X_L3_TYPE_IPV4;
- break;
- case htons(ETH_P_IPV6):
- l3type = YT921X_L3_TYPE_IPV6;
- break;
- case htons(ETH_P_ARP):
- l3type = YT921X_L3_TYPE_ARP;
- break;
- case htons(ETH_P_LLDP):
- l3type = YT921X_L3_TYPE_LLDP;
- break;
- case htons(ETH_P_PAE):
- l3type = YT921X_L3_TYPE_PAE;
- break;
- case htons(ETH_P_CFM):
- l3type = YT921X_L3_TYPE_ERP;
- break;
- }
-
- if (l3type != YT921X_L3_TYPE_OTHER) {
- size = yt921x_acl_entries_set_l3_type(entries,
- size,
- l3type);
- if (!size)
- goto err;
- } else {
- entry = yt921x_acl_entries_new(entries, &size,
- YT921X_ACL_TYPE_ETHERTYPE);
- if (!entry)
- goto err;
-
- entry->key[0] |= ntohs(match.key->n_proto);
- entry->mask[0] |= ntohs(match.mask->n_proto);
- }
- }
-
- if (match.mask->ip_proto) {
- enum yt921x_l4_type l4type = YT921X_L4_TYPE_OTHER;
-
- if (match.mask->ip_proto == (u8)~0)
- switch (match.key->ip_proto) {
- case IPPROTO_TCP:
- l4type = YT921X_L4_TYPE_TCP;
- break;
- case IPPROTO_UDP:
- l4type = YT921X_L4_TYPE_UDP;
- break;
- case IPPROTO_UDPLITE:
- l4type = YT921X_L4_TYPE_UDPLITE;
- break;
- case IPPROTO_ICMP:
- l4type = YT921X_L4_TYPE_ICMP;
- break;
- case IPPROTO_IGMP:
- l4type = YT921X_L4_TYPE_IGMP;
- break;
- }
-
- if (l4type != YT921X_L4_TYPE_OTHER) {
- size = yt921x_acl_entries_set_l4_type(entries,
- size,
- l4type);
- if (!size)
- goto err;
- } else {
- entry = yt921x_acl_entries_find(entries, &size,
- YT921X_ACL_TYPE_MISC);
- if (!entry)
- goto err;
-
- entry->key[0] |= YT921X_ACL_BINa_MISC_IP_PROTO(match.key->ip_proto);
- entry->mask[0] |= YT921X_ACL_BINa_MISC_IP_PROTO(match.mask->ip_proto);
- }
- }
- }
-
- if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) {
- u32 supp_flags = FLOW_DIS_IS_FRAGMENT | FLOW_DIS_FIRST_FRAG;
- struct flow_match_control match;
-
- flow_rule_match_control(rule, &match);
- if (!flow_rule_is_supp_control_flags(supp_flags,
- match.mask->flags, extack))
- return -EOPNOTSUPP;
-
- if (match.mask->flags & FLOW_DIS_IS_FRAGMENT) {
- bool set = match.key->flags & FLOW_DIS_IS_FRAGMENT;
-
- size = yt921x_acl_entries_set_is_fragment(entries, size,
- set);
- if (!size)
- goto err;
- }
- if (match.mask->flags & FLOW_DIS_FIRST_FRAG) {
- bool set = match.key->flags & FLOW_DIS_FIRST_FRAG;
-
- size = yt921x_acl_entries_set_first_frag(entries, size,
- set);
- if (!size)
- goto err;
- }
- }
-
- /* Misc only */
- if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IP)) {
- struct flow_match_ip match;
-
- flow_rule_match_ip(rule, &match);
- if (match.mask->ttl) {
- NL_SET_ERR_MSG_MOD(extack,
- "Matching on TTL not supported");
- return -EOPNOTSUPP;
- }
-
- if (match.mask->tos) {
- entry = yt921x_acl_entries_find(entries, &size,
- YT921X_ACL_TYPE_MISC);
- if (!entry)
- goto err;
-
- entry->key[0] |= YT921X_ACL_BINa_MISC_TOS(match.key->tos);
- entry->mask[0] |= YT921X_ACL_BINa_MISC_TOS(match.mask->tos);
- }
- }
-
- if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_TCP)) {
- struct flow_match_tcp match;
-
- flow_rule_match_tcp(rule, &match);
- if (match.mask->flags & htons(~0xff)) {
- NL_SET_ERR_MSG_MOD(extack, "Unsupported TCP flags");
- return -EOPNOTSUPP;
- }
-
- if (match.mask->flags) {
- entry = yt921x_acl_entries_find(entries, &size,
- YT921X_ACL_TYPE_MISC);
- if (!entry)
- goto err;
-
- entry->key[0] |= YT921X_ACL_BINa_MISC_TCP_FLAGS(ntohs(match.key->flags));
- entry->mask[0] |= YT921X_ACL_BINa_MISC_TCP_FLAGS(ntohs(match.mask->flags));
- }
- }
-
- if (!size) {
- NL_SET_ERR_MSG_MOD(extack, "Empty rule generated, this should not happen");
- return -EOPNOTSUPP;
- }
-
- ruleext->r.mask = (1 << size) - 1;
- return 0;
-
-err:
- NL_SET_ERR_MSG_MOD(extack, "Rule too complex");
- return -EOPNOTSUPP;
-}
-
-static int
-yt921x_acl_rule_ext_parse_flow_action(struct yt921x_acl_rule_ext *ruleext,
- const struct flow_cls_offload *cls,
- struct yt921x_priv *priv, int port)
-{
- const struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
- const struct flow_action *flow_action = &rule->action;
- struct netlink_ext_ack *extack = cls->common.extack;
- enum flow_action_id redir_act = NUM_FLOW_ACTIONS;
- const struct flow_action_entry *act;
- u32 *action = ruleext->r.action;
- bool seen_priority = false;
- const char *reason = NULL;
- bool seen_police = false;
- unsigned int i;
- int res;
-
- memset(action, 0, 3 * sizeof(*action));
- flow_action_for_each(i, act, flow_action)
- switch (act->id) {
- case FLOW_ACTION_ACCEPT:
- case FLOW_ACTION_DROP:
- case FLOW_ACTION_REDIRECT:
- if (redir_act != NUM_FLOW_ACTIONS &&
- redir_act != act->id) {
- reason = "Different redirect actions";
- goto fallback;
- }
- redir_act = act->id;
-
- switch (act->id) {
- case FLOW_ACTION_ACCEPT:
- action[2] |= YT921X_ACL_ACTc_FWD_EN |
- YT921X_ACL_ACTc_FWD_FWD;
- break;
- case FLOW_ACTION_DROP:
- action[2] |= YT921X_ACL_ACTc_FWD_EN |
- YT921X_ACL_ACTc_FWD_REDIR;
- break;
- case FLOW_ACTION_REDIRECT: {
- struct dsa_port *to_dp;
-
- to_dp = dsa_port_from_netdev(act->dev);
- if (IS_ERR(to_dp) || to_dp->ds != &priv->ds) {
- reason = "Redirect to non-local port";
- goto fallback;
- }
-
- action[2] |= YT921X_ACL_ACTc_FWD_EN |
- YT921X_ACL_ACTc_FWD_REDIR |
- YT921X_ACL_ACTc_FWD_REDIR_DPORTn(to_dp->index);
- break;
- }
- default:
- break;
- }
- break;
- case FLOW_ACTION_PRIORITY:
- if (seen_priority) {
- action[0] &= ~YT921X_ACL_ACTa_PRIO_EN;
- action[1] &= ~YT921X_ACL_ACTb_PRIO_M;
-
- reason = "Multiple priority actions";
- goto fallback;
- }
- seen_priority = true;
-
- if (act->priority >= YT921X_PRIO_NUM) {
- NL_SET_ERR_MSG_MOD(extack,
- "Priority value is too high");
- return -EOPNOTSUPP;
- }
- action[0] |= YT921X_ACL_ACTa_PRIO_EN;
- action[1] |= YT921X_ACL_ACTb_PRIO(act->priority);
- break;
- case FLOW_ACTION_POLICE: {
- const struct flow_action_police *police = &act->police;
-
- if (seen_police) {
- action[0] &= ~YT921X_ACL_ACTa_METER_EN;
-
- reason = "Multiple police actions";
- goto fallback;
- }
- seen_police = true;
-
- res = yt921x_police_validate(police, flow_action, act,
- extack);
- if (res)
- return res;
-
- res = yt921x_marker_tfm_police(&ruleext->marker, police,
- 0, priv, port, extack);
- if (res)
- return res;
-
- action[0] |= YT921X_ACL_ACTa_METER_EN;
- break;
- }
- default:
-fallback:
- if (cls->common.skip_sw) {
- NL_SET_ERR_MSG_FMT_MOD(extack,
- "Action not supported when skip_sw: %s",
- reason);
- return -EOPNOTSUPP;
- }
- fallthrough;
- case FLOW_ACTION_TRAP:
- redir_act = FLOW_ACTION_TRAP;
-
- action[2] &= ~YT921X_ACL_ACTc_FWD_REDIR_DPORTS_M &
- ~YT921X_ACL_ACTc_FWD_M;
- action[2] |= YT921X_ACL_ACTc_FWD_EN |
- YT921X_ACL_ACTc_FWD_TRAP;
- break;
- }
-
- ruleext->r.sw_assisted = !cls->common.skip_sw;
- return 0;
-}
-
-static int
-yt921x_acl_rule_ext_parse_flow(struct yt921x_acl_rule_ext *ruleext, int port,
- const struct flow_cls_offload *cls, bool ingress,
- struct yt921x_priv *priv)
-{
- struct netlink_ext_ack *extack = cls->common.extack;
- int res;
-
- if (!ingress) {
- NL_SET_ERR_MSG_MOD(extack, "Only ingress is supported");
- return -EOPNOTSUPP;
- }
-
- if (cls->common.chain_index) {
- NL_SET_ERR_MSG(extack, "Only chain 0 is supported");
- return -EOPNOTSUPP;
- }
-
- res = yt921x_acl_rule_ext_parse_flow_action(ruleext, cls, priv, port);
- if (res)
- return res;
- res = yt921x_acl_rule_ext_parse_flow_entries(ruleext, cls);
- if (res)
- return res;
-
- yt921x_acl_rule_set_ports(&ruleext->r, 0, BIT(port));
- ruleext->r.tag = cls->cookie;
- ruleext->r.type = TC_SETUP_CLSFLOWER;
- return 0;
-}
-
-static unsigned int
-yt921x_acl_find(const struct yt921x_priv *priv, enum tc_setup_type type,
- unsigned long tag)
-{
- for (unsigned int blkid = 0; blkid < YT921X_ACL_BLK_NUM; blkid++) {
- const struct yt921x_acl_blk *aclblk = priv->acl_blks[blkid];
-
- if (!aclblk)
- continue;
-
- for (unsigned int i = 0; i < YT921X_ACL_ENT_PER_BLK; i++)
- if (aclblk->rules[i] && aclblk->rules[i]->tag == tag &&
- aclblk->rules[i]->type == type)
- return YT921X_ACL_ENT_PER_BLK * blkid + i;
- }
-
- return UINT_MAX;
-}
-
-static unsigned int
-yt921x_acl_reserve(struct yt921x_priv *priv, unsigned int entscnt,
- struct netlink_ext_ack *extack)
-{
- int candidates[YT921X_ACL_ENT_PER_BLK + 1];
- unsigned int acl_used_cnt = 0;
-
- if (WARN_ON(entscnt > YT921X_ACL_ENT_PER_BLK))
- return UINT_MAX;
-
- for (unsigned int i = 0; i < ARRAY_SIZE(candidates); i++)
- candidates[i] = -1;
- for (unsigned int i = YT921X_ACL_BLK_NUM; i-- > 0;) {
- unsigned int blk_used_cnt = hweight8(priv->acl_masks[i]);
-
- candidates[blk_used_cnt] = i;
- acl_used_cnt += blk_used_cnt;
- }
-
- if (acl_used_cnt >= YT921X_ACL_NUM) {
- NL_SET_ERR_MSG_MOD(extack, "ACL entry limit reached");
- return UINT_MAX;
- }
- if (acl_used_cnt + entscnt <= YT921X_ACL_NUM)
- for (unsigned int i = YT921X_ACL_ENT_PER_BLK - entscnt + 1;
- i-- > 0;)
- if (candidates[i] >= 0)
- return YT921X_ACL_ENT_PER_BLK * candidates[i] +
- ffz(priv->acl_masks[candidates[i]]);
-
- NL_SET_ERR_MSG_MOD(extack,
- "ACL entry allocation failed, simplify your rules or remove existing rules");
- return UINT_MAX;
-}
-
-static int
-yt921x_acl_commit(struct yt921x_priv *priv, unsigned int entid, u8 entsmask)
-{
- const struct yt921x_acl_rule *aclrule;
- const struct yt921x_acl_blk *aclblk;
- unsigned int blkid;
- unsigned int binid;
- unsigned long mask;
- u32 zeros[3] = {};
- unsigned int i;
- unsigned int o;
- u32 ctrl;
- int res;
-
- blkid = entid / YT921X_ACL_ENT_PER_BLK;
- binid = entid % YT921X_ACL_ENT_PER_BLK;
- aclblk = priv->acl_blks[blkid];
- aclrule = aclblk->rules[binid];
-
- /* Write actions */
- res = yt921x_reg96_write(priv, YT921X_ACLn_ACT(entid),
- aclrule ? aclrule->action : zeros);
- if (res)
- return res;
-
- /* Select the block */
- ctrl = YT921X_ACL_BLK_CMD_MODIFY | YT921X_ACL_BLK_CMD_BLKID(blkid);
- res = yt921x_reg_write(priv, YT921X_ACL_BLK_CMD, ctrl);
- if (res)
- return res;
-
- /* Write keys and masks */
- ctrl = 0;
- for (unsigned int i = 0; i < YT921X_ACL_ENT_PER_BLK; i++)
- ctrl |= YT921X_ACL_BLK_KEEP_KEEPn(i);
-
- mask = entsmask;
- i = 0;
- for_each_set_bit(o, &mask, YT921X_ACL_ENT_PER_BLK) {
- res = yt921x_reg64_write(priv, YT921X_ACLn_KEYm(blkid, o),
- aclrule ? aclrule->entries[i].key :
- zeros);
- if (res)
- return res;
-
- res = yt921x_reg64_write(priv, YT921X_ACLn_MASKm(blkid, o),
- aclrule ? aclrule->entries[i].mask :
- zeros);
- if (res)
- return res;
-
- ctrl &= ~YT921X_ACL_BLK_KEEP_KEEPn(o);
- i++;
- }
-
- res = yt921x_reg_write(priv, YT921X_ACL_BLK_KEEP, ctrl);
- if (res)
- return res;
-
- ctrl = 0;
- for (unsigned int i = 0; i < YT921X_ACL_ENT_PER_BLK; i++) {
- const struct yt921x_acl_rule *other = aclblk->rules[i];
-
- if (!other)
- continue;
-
- mask = other->mask;
- for_each_set_bit(o, &mask, YT921X_ACL_ENT_PER_BLK)
- ctrl |= YT921X_ACL_ENTRY_ENm(o) |
- YT921X_ACL_ENTRY_GRPIDm(o, i);
- }
- res = yt921x_reg_write(priv, YT921X_ACLn_ENTRY(blkid), ctrl);
- if (res)
- return res;
-
- /* Commit the block */
- ctrl = YT921X_ACL_BLK_CMD_BLKID(blkid);
- res = yt921x_reg_write(priv, YT921X_ACL_BLK_CMD, ctrl);
- if (res)
- return res;
-
- return 0;
-}
-
-static int
-yt921x_acl_del(struct yt921x_priv *priv, enum tc_setup_type type,
- unsigned long tag)
-{
- struct yt921x_acl_rule *aclrule;
- struct yt921x_acl_blk *aclblk;
- unsigned int binid;
- unsigned int blkid;
- unsigned int entid;
- int res;
-
- entid = yt921x_acl_find(priv, type, tag);
- if (entid == UINT_MAX)
- return -ENOENT;
-
- blkid = entid / YT921X_ACL_ENT_PER_BLK;
- binid = entid % YT921X_ACL_ENT_PER_BLK;
- aclblk = priv->acl_blks[blkid];
- aclrule = aclblk->rules[binid];
-
- aclblk->rules[binid] = NULL;
- res = yt921x_acl_commit(priv, entid, aclrule->mask);
- /* the kernel never rolls back on failure */
-
- if (aclrule->action[0] & YT921X_ACL_ACTa_METER_EN)
- clear_bit(FIELD_GET(YT921X_ACL_ACTa_METER_ID_M,
- aclrule->action[0]),
- priv->meters_map);
- priv->acl_masks[blkid] &= ~aclrule->mask;
- kvfree(aclrule);
- if (!priv->acl_masks[blkid]) {
- kvfree(aclblk);
- priv->acl_blks[blkid] = NULL;
- }
- return res;
-}
-
-static int
-yt921x_acl_add(struct yt921x_priv *priv,
- const struct yt921x_acl_rule_ext *ruleext,
- struct netlink_ext_ack *extack)
-{
- unsigned int entscnt = hweight8(ruleext->r.mask);
- struct yt921x_acl_rule *aclrule;
- struct yt921x_acl_blk *aclblk;
- bool use_trap = false;
- unsigned int meterid;
- unsigned long mask;
- unsigned int binid;
- unsigned int blkid;
- unsigned int entid;
- unsigned int o;
- int res;
-
- /* Allocate resources */
- entid = yt921x_acl_reserve(priv, entscnt, extack);
- if (entid == UINT_MAX)
- return -EOPNOTSUPP;
-
- if (!(ruleext->r.action[0] & YT921X_ACL_ACTa_METER_EN)) {
- meterid = YT921X_METER_NUM;
- } else {
- meterid = find_first_zero_bit(priv->meters_map,
- YT921X_METER_NUM);
- if (meterid < YT921X_METER_NUM) {
- res = yt921x_meter_config(priv, meterid,
- &ruleext->marker);
- if (res)
- return res;
- } else if (ruleext->r.sw_assisted) {
- use_trap = true;
- } else {
- NL_SET_ERR_MSG_MOD(extack,
- "No more meters available");
- return -EOPNOTSUPP;
- }
- }
-
- /* Prepare acl block ctrlblk */
- blkid = entid / YT921X_ACL_ENT_PER_BLK;
- binid = entid % YT921X_ACL_ENT_PER_BLK;
- aclblk = priv->acl_blks[blkid];
- if (!aclblk) {
- aclblk = kvzalloc_obj(*aclblk);
- if (!aclblk)
- return -ENOMEM;
- priv->acl_blks[blkid] = aclblk;
- }
-
- /* Prepare acl rule ctrlblk */
- aclrule = kvmemdup(&ruleext->r,
- offsetof(struct yt921x_acl_rule, entries[entscnt]),
- GFP_KERNEL);
- if (!aclrule) {
- res = -ENOMEM;
- goto err;
- }
-
- /* Replace the placeholder resource IDs */
- aclrule->mask = 0;
- mask = priv->acl_masks[blkid];
- for_each_clear_bit(o, &mask, YT921X_ACL_ENT_PER_BLK) {
- aclrule->mask |= BIT(o);
- entscnt--;
- if (!entscnt)
- break;
- }
-
- if (use_trap) {
- aclrule->action[2] &= ~YT921X_ACL_ACTc_FWD_REDIR_DPORTS_M &
- ~YT921X_ACL_ACTc_FWD_M;
- aclrule->action[2] |= YT921X_ACL_ACTc_FWD_EN |
- YT921X_ACL_ACTc_FWD_TRAP;
- }
- if (meterid < YT921X_METER_NUM)
- aclrule->action[0] |= YT921X_ACL_ACTa_METER_ID(meterid);
- else
- aclrule->action[0] &= ~YT921X_ACL_ACTa_METER_EN;
-
- /* Write rules */
- aclblk->rules[binid] = aclrule;
- res = yt921x_acl_commit(priv, entid, aclrule->mask);
- if (res) {
- aclblk->rules[binid] = NULL;
- kvfree(aclrule);
- goto err;
- }
-
- if (meterid < YT921X_METER_NUM)
- set_bit(meterid, priv->meters_map);
- priv->acl_masks[blkid] |= aclrule->mask;
- return 0;
-
-err:
- if (!priv->acl_masks[blkid]) {
- kvfree(aclblk);
- priv->acl_blks[blkid] = NULL;
- }
- return res;
-}
-
-static int
-yt921x_dsa_cls_flower_del(struct dsa_switch *ds, int port,
- struct flow_cls_offload *cls, bool ingress)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_acl_del(priv, TC_SETUP_CLSFLOWER, cls->cookie);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_dsa_cls_flower_add(struct dsa_switch *ds, int port,
- struct flow_cls_offload *cls, bool ingress)
-{
- struct netlink_ext_ack *extack = cls->common.extack;
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct yt921x_acl_rule_ext ruleext;
- int res;
-
- res = yt921x_acl_rule_ext_parse_flow(&ruleext, port, cls, ingress,
- priv);
- if (res)
- return res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_acl_add(priv, &ruleext, extack);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_mirror_del(struct yt921x_priv *priv, int port, bool ingress)
-{
- u32 mask;
-
- if (ingress)
- mask = YT921X_MIRROR_IGR_PORTn(port);
- else
- mask = YT921X_MIRROR_EGR_PORTn(port);
- return yt921x_reg_clear_bits(priv, YT921X_MIRROR, mask);
-}
-
-static int
-yt921x_mirror_add(struct yt921x_priv *priv, int port, bool ingress,
- int to_local_port, struct netlink_ext_ack *extack)
-{
- u32 srcs;
- u32 ctrl;
- u32 val;
- u32 dst;
- int res;
-
- if (ingress)
- srcs = YT921X_MIRROR_IGR_PORTn(port);
- else
- srcs = YT921X_MIRROR_EGR_PORTn(port);
- dst = YT921X_MIRROR_PORT(to_local_port);
-
- res = yt921x_reg_read(priv, YT921X_MIRROR, &val);
- if (res)
- return res;
-
- /* other mirror tasks & different dst port -> conflict */
- if ((val & ~srcs & (YT921X_MIRROR_EGR_PORTS_M |
- YT921X_MIRROR_IGR_PORTS_M)) &&
- (val & YT921X_MIRROR_PORT_M) != dst) {
- NL_SET_ERR_MSG_MOD(extack,
- "Sniffer port is already configured, delete existing rules & retry");
- return -EBUSY;
- }
-
- ctrl = val & ~YT921X_MIRROR_PORT_M;
- ctrl |= srcs;
- ctrl |= dst;
-
- if (ctrl == val)
- return 0;
-
- return yt921x_reg_write(priv, YT921X_MIRROR, ctrl);
-}
-
-static void
-yt921x_dsa_port_mirror_del(struct dsa_switch *ds, int port,
- struct dsa_mall_mirror_tc_entry *mirror)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct device *dev = to_device(priv);
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_mirror_del(priv, port, mirror->ingress);
- mutex_unlock(&priv->reg_lock);
-
- if (res)
- dev_err(dev, "Failed to %s port %d: %i\n", "unmirror",
- port, res);
-}
-
-static int
-yt921x_dsa_port_mirror_add(struct dsa_switch *ds, int port,
- struct dsa_mall_mirror_tc_entry *mirror,
- bool ingress, struct netlink_ext_ack *extack)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_mirror_add(priv, port, ingress,
- mirror->to_local_port, extack);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
static int yt921x_lag_hash(struct yt921x_priv *priv, u32 ctrl, bool unique_lag,
struct netlink_ext_ack *extack)
{
diff --git a/drivers/net/dsa/motorcomm/chip.h b/drivers/net/dsa/motorcomm/chip.h
index 28e4f26f9cc4..02419bcde3f2 100644
--- a/drivers/net/dsa/motorcomm/chip.h
+++ b/drivers/net/dsa/motorcomm/chip.h
@@ -434,249 +434,6 @@ enum yt921x_app_selector {
#define YT921X_VLAN_CTRLa_METER_EN BIT(5)
#define YT921X_VLAN_CTRLa_METER_ID_M GENMASK(4, 0)
-#define YT921X_ACLn_ACT(n) (0x1c0000 + 0x10 * (n))
-#define YT921X_ACL_ACTc_STAG_M GENMASK(26, 25)
-#define YT921X_ACL_ACTc_STAG(x) FIELD_PREP(YT921X_ACL_ACTc_STAG_M, (x))
-#define YT921X_ACL_ACTc_STAG_DONTCARE YT921X_ACL_ACTc_STAG(0)
-#define YT921X_ACL_ACTc_STAG_UNTAG YT921X_ACL_ACTc_STAG(1)
-#define YT921X_ACL_ACTc_STAG_TAG YT921X_ACL_ACTc_STAG(2)
-#define YT921X_ACL_ACTc_STAG_KEEP YT921X_ACL_ACTc_STAG(3)
-#define YT921X_ACL_ACTc_CTAG_M GENMASK(24, 23)
-#define YT921X_ACL_ACTc_CTAG(x) FIELD_PREP(YT921X_ACL_ACTc_CTAG_M, (x))
-#define YT921X_ACL_ACTc_CTAG_DONTCARE YT921X_ACL_ACTc_CTAG(0)
-#define YT921X_ACL_ACTc_CTAG_UNTAG YT921X_ACL_ACTc_CTAG(1)
-#define YT921X_ACL_ACTc_CTAG_TAG YT921X_ACL_ACTc_CTAG(2)
-#define YT921X_ACL_ACTc_CTAG_KEEP YT921X_ACL_ACTc_CTAG(3)
-#define YT921X_ACL_ACTc_FWD_M GENMASK(22, 21)
-#define YT921X_ACL_ACTc_FWD(x) FIELD_PREP(YT921X_ACL_ACTc_FWD_M, (x))
-#define YT921X_ACL_ACTc_FWD_FWD YT921X_ACL_ACTc_FWD(0)
-#define YT921X_ACL_ACTc_FWD_COPY YT921X_ACL_ACTc_FWD(1)
-#define YT921X_ACL_ACTc_FWD_REDIR YT921X_ACL_ACTc_FWD(2)
-#define YT921X_ACL_ACTc_FWD_TRAP YT921X_ACL_ACTc_FWD(3)
-#define YT921X_ACL_ACTc_FWD_REDIR_DPORTS_M GENMASK(20, 10)
-#define YT921X_ACL_ACTc_FWD_REDIR_DPORTS(x) FIELD_PREP(YT921X_ACL_ACTc_FWD_REDIR_DPORTS_M, (x))
-#define YT921X_ACL_ACTc_FWD_REDIR_DPORTn(port) BIT((port) + 10)
-#define YT921X_ACL_ACTc_FWD_EN BIT(9)
-#define YT921X_ACL_ACTc_SDEI BIT(8)
-#define YT921X_ACL_ACTc_SDEI_REPLACE BIT(7)
-#define YT921X_ACL_ACTc_SPRI_M GENMASK(6, 4)
-#define YT921X_ACL_ACTc_SPRI(x) FIELD_PREP(YT921X_ACL_ACTc_SPRI_M, (x))
-#define YT921X_ACL_ACTc_SPRI_REPLACE BIT(3)
-#define YT921X_ACL_ACTbc_SVID_M GENMASK_ULL(34, 23)
-#define YT921X_ACL_ACTbc_SVID(x) FIELD_PREP(YT921X_ACL_ACTbc_SVID_M, (x))
-#define YT921X_ACL_ACTb_SVID_REPLACE BIT(22)
-#define YT921X_ACL_ACTb_CDEI BIT(21)
-#define YT921X_ACL_ACTb_CDEI_REPLACE BIT(20)
-#define YT921X_ACL_ACTb_CPRI_M GENMASK(19, 17)
-#define YT921X_ACL_ACTb_CPRI(x) FIELD_PREP(YT921X_ACL_ACTb_CPRI_M, (x))
-#define YT921X_ACL_ACTb_CPRI_REPLACE BIT(16)
-#define YT921X_ACL_ACTb_CVID_M GENMASK(15, 4)
-#define YT921X_ACL_ACTb_CVID(x) FIELD_PREP(YT921X_ACL_ACTb_CVID_M, (x))
-#define YT921X_ACL_ACTb_CVID_REPLACE BIT(3)
-#define YT921X_ACL_ACTb_PRIO_M GENMASK(2, 0)
-#define YT921X_ACL_ACTb_PRIO(x) FIELD_PREP(YT921X_ACL_ACTb_PRIO_M, (x))
-#define YT921X_ACL_ACTa_PRIO_EN BIT(31)
-#define YT921X_ACL_ACTa_COLOR_M GENMASK(30, 29)
-#define YT921X_ACL_ACTa_COLOR(x) FIELD_PREP(YT921X_ACL_ACTa_COLOR_M, (x))
-#define YT921X_ACL_ACTa_COLOR_GREEN YT921X_ACL_ACTa_COLOR(0)
-#define YT921X_ACL_ACTa_COLOR_YELLOW YT921X_ACL_ACTa_COLOR(1)
-#define YT921X_ACL_ACTa_COLOR_RED YT921X_ACL_ACTa_COLOR(2)
-#define YT921X_ACL_ACTa_COLOR_EN BIT(28)
-#define YT921X_ACL_ACTa_DSCP_M GENMASK(27, 22)
-#define YT921X_ACL_ACTa_DSCP(x) FIELD_PREP(YT921X_ACL_ACTa_DSCP_M, (x))
-#define YT921X_ACL_ACTa_DSCP_REPLACE BIT(21)
-#define YT921X_ACL_ACTa_METER_ID_M GENMASK(20, 15)
-#define YT921X_ACL_ACTa_METER_ID(x) FIELD_PREP(YT921X_ACL_ACTa_METER_ID_M, (x))
-#define YT921X_ACL_ACTa_METER_EN BIT(14)
-#define YT921X_ACL_ACTa_MIRROR_EN BIT(13)
-#define YT921X_ACL_ACTa_FLOWSTAT_EN BIT(12)
-#define YT921X_ACL_ACTa_FLOWSTAT_ID_M GENMASK(11, 6)
-#define YT921X_ACL_ACTa_FLOWSTAT_ID(x) FIELD_PREP(YT921X_ACL_ACTa_FLOWSTAT_ID_M, (x))
-#define YT921X_ACL_ACTa_GPIO_EN BIT(5)
-#define YT921X_ACL_ACTa_GPIO_PIN_M GENMASK(4, 1)
-#define YT921X_ACL_ACTa_GPIO_PIN(x) FIELD_PREP(YT921X_ACL_ACTa_GPIO_PIN_M, (x))
-#define YT921X_ACL_ACTa_INTR_EN BIT(0)
-#define YT921X_ACL_BLK_KEEP 0x201000
-#define YT921X_ACL_BLK_KEEP_GRPIDn_M(bin) (7 << (4 * (bin) + 1))
-#define YT921X_ACL_BLK_KEEP_GRPIDn(bin, x) ((x) << (4 * (bin) + 1))
-#define YT921X_ACL_BLK_KEEP_KEEPn(bin) BIT(4 * (bin))
-#define YT921X_ACL_PORT 0x202000
-#define YT921X_ACL_PORT_PORTS_M GENMASK(10, 0)
-#define YT921X_ACL_PORT_PORTS(x) FIELD_PREP(YT921X_ACL_PORT_PORTS_M, (x))
-#define YT921X_ACL_PORT_PORTn(port) BIT(port)
-#define YT921X_ACL_BLK_CMD 0x202004
-#define YT921X_ACL_BLK_CMD_BLKID_M GENMASK(6, 1)
-#define YT921X_ACL_BLK_CMD_BLKID(x) FIELD_PREP(YT921X_ACL_BLK_CMD_BLKID_M, (x))
-#define YT921X_ACL_BLK_CMD_MODIFY BIT(0)
-#define YT921X_ACLn_ENTRY(blk) (0x203000 + 4 * (blk))
-#define YT921X_ACL_ENTRY_GRPIDm_M(bin) (7 << (4 * (bin) + 1))
-#define YT921X_ACL_ENTRY_GRPIDm(bin, x) ((x) << (4 * (bin) + 1))
-#define YT921X_ACL_ENTRY_ENm(bin) BIT(4 * (bin))
-#define YT921X_ACLn_KEYm(blk, bin) (0x204000 + 0x200 * (bin) + 8 * (blk))
-#define YT921X_ACL_KEYb_ORD_M GENMASK(29, 21)
-#define YT921X_ACL_KEYb_ORD(x) FIELD_PREP(YT921X_ACL_KEYb_ORD_M, (x))
-#define YT921X_ACL_KEYb_SPORTS_M GENMASK(20, 10)
-#define YT921X_ACL_KEYb_SPORTS(x) FIELD_PREP(YT921X_ACL_KEYb_SPORTS_M, (x))
-#define YT921X_ACL_KEYb_SPORTn(port) BIT((port) + 10)
-#define YT921X_ACL_KEYb_REVERSE BIT(9) /* reverse match */
-#define YT921X_ACL_KEYb_TYPE_M GENMASK(8, 4)
-#define YT921X_ACL_KEYb_TYPE(x) FIELD_PREP(YT921X_ACL_KEYb_TYPE_M, (x))
-/* KEY_* fields need no masks */
-#define YT921X_ACLn_MASKm(blk, bin) (0x205000 + 0x200 * (bin) + 8 * (blk))
-
-enum yt921x_acl_type {
- YT921X_ACL_TYPE_NA,
- YT921X_ACL_TYPE_MAC_DA0,
- YT921X_ACL_TYPE_MAC_SA0,
- YT921X_ACL_TYPE_MAC_DA1_SA1,
- YT921X_ACL_TYPE_VLAN,
- YT921X_ACL_TYPE_VTAG,
- YT921X_ACL_TYPE_IPV4_DA,
- YT921X_ACL_TYPE_IPV4_SA,
- YT921X_ACL_TYPE_IPV6_DA0,
- YT921X_ACL_TYPE_IPV6_DA1,
- YT921X_ACL_TYPE_IPV6_DA2,
- YT921X_ACL_TYPE_IPV6_DA3,
- YT921X_ACL_TYPE_IPV6_SA0,
- YT921X_ACL_TYPE_IPV6_SA1,
- YT921X_ACL_TYPE_IPV6_SA2,
- YT921X_ACL_TYPE_IPV6_SA3,
- YT921X_ACL_TYPE_MISC,
- YT921X_ACL_TYPE_L4,
- YT921X_ACL_TYPE_UDF0,
- YT921X_ACL_TYPE_UDF1,
- YT921X_ACL_TYPE_UDF2,
- YT921X_ACL_TYPE_UDF3,
- YT921X_ACL_TYPE_UDF4,
- YT921X_ACL_TYPE_UDF5,
- YT921X_ACL_TYPE_UDF6,
- YT921X_ACL_TYPE_UDF7,
- YT921X_ACL_TYPE_ETHERTYPE,
- YT921X_ACL_TYPE_NUM
-};
-
-/* Range: turn KEY:MASK into MIN:MAX */
-
-#define YT921X_ACL_BINb_MAC_xA0_L3_TYPE_M GENMASK(3, 0)
-#define YT921X_ACL_BINb_MAC_xA0_L3_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_MAC_xA0_L3_TYPE_M, (x))
-#define YT921X_ACL_BINa_MAC_xA0_MAC_xA0_M GENMASK(31, 0)
-
-#define YT921X_ACL_BINb_MAC_DA1_SA1_L2_TYPE_M GENMASK(2, 0)
-#define YT921X_ACL_BINb_MAC_DA1_SA1_L2_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_MAC_DA1_SA1_L2_TYPE_M, (x))
-#define YT921X_ACL_BINa_MAC_DA1_SA1_MAC_DA1_M GENMASK(31, 16)
-#define YT921X_ACL_BINa_MAC_DA1_SA1_MAC_SA1_M GENMASK(15, 0)
-
-#define YT921X_ACL_KEYb_VLAN_SVID_RANGE_EN BIT(31)
-#define YT921X_ACL_KEYb_VLAN_CVID_RANGE_EN BIT(30)
-#define YT921X_ACL_BINb_VLAN_CDEI BIT(3)
-#define YT921X_ACL_BINb_VLAN_CPRI_M GENMASK(2, 0)
-#define YT921X_ACL_BINb_VLAN_CPRI(x) FIELD_PREP(YT921X_ACL_BINb_VLAN_CPRI_M, (x))
-#define YT921X_ACL_BINa_VLAN_CTAG_FMT_M GENMASK(31, 30)
-#define YT921X_ACL_BINa_VLAN_CTAG_FMT(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_CTAG_FMT_M, (x))
-#define YT921X_ACL_BINa_VLAN_SDEI BIT(29)
-#define YT921X_ACL_BINa_VLAN_SPRI_M GENMASK(28, 26)
-#define YT921X_ACL_BINa_VLAN_SPRI(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_SPRI_M, (x))
-#define YT921X_ACL_BINa_VLAN_STAG_FMT_M GENMASK(25, 24)
-#define YT921X_ACL_BINa_VLAN_STAG_FMT(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_STAG_FMT_M, (x))
-#define YT921X_ACL_BINa_VLAN_SVID_M GENMASK(23, 12)
-#define YT921X_ACL_BINa_VLAN_SVID(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_SVID_M, (x))
-#define YT921X_ACL_BINa_VLAN_CVID_M GENMASK(11, 0)
-#define YT921X_ACL_BINa_VLAN_CVID(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_CVID_M, (x))
-
-#define YT921X_ACL_KEYb_VTAG_SVID_RANGE_EN BIT(31)
-#define YT921X_ACL_KEYb_VTAG_CVID_RANGE_EN BIT(30)
-#define YT921X_ACL_BINa_VTAG_CDEI BIT(31)
-#define YT921X_ACL_BINa_VTAG_CPRI_M GENMASK(30, 28)
-#define YT921X_ACL_BINa_VTAG_CPRI(x) FIELD_PREP(YT921X_ACL_BINa_VTAG_CPRI_M, (x))
-#define YT921X_ACL_BINa_VTAG_SDEI BIT(27)
-#define YT921X_ACL_BINa_VTAG_SPRI_M GENMASK(26, 24)
-#define YT921X_ACL_BINa_VTAG_SPRI(x) FIELD_PREP(YT921X_ACL_BINa_VTAG_SPRI_M, (x))
-#define YT921X_ACL_BINa_VTAG_SVID_M GENMASK(23, 12)
-#define YT921X_ACL_BINa_VTAG_SVID(x) FIELD_PREP(YT921X_ACL_BINa_VTAG_SVID_M, (x))
-#define YT921X_ACL_BINa_VTAG_CVID_M GENMASK(11, 0)
-#define YT921X_ACL_BINa_VTAG_CVID(x) FIELD_PREP(YT921X_ACL_BINa_VTAG_CVID_M, (x))
-
-#define YT921X_ACL_KEYb_IPV4_ADDR_RANGE_EN BIT(30)
-#define YT921X_ACL_BINb_IPV4_FRAG BIT(3)
-#define YT921X_ACL_BINb_IPV4_L4_TYPE_M GENMASK(2, 0)
-#define YT921X_ACL_BINb_IPV4_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_IPV4_L4_TYPE_M, (x))
-#define YT921X_ACL_BINa_IPV4_ADDR_M GENMASK(31, 0)
-
-#define YT921X_ACL_BINb_IPV6_L4_TYPE_M GENMASK(2, 0)
-#define YT921X_ACL_BINb_IPV6_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_IPV6_L4_TYPE_M, (x))
-#define YT921X_ACL_BINa_IPV6_ADDRx_M GENMASK(31, 0)
-
-#define YT921X_ACL_BINb_IPV6_xA1_IP_OPTION BIT(3)
-
-#define YT921X_ACL_BINb_IPV6_xA2_FIRST_FRAG BIT(3)
-
-#define YT921X_ACL_KEYb_IPV6_xA3_ADDR_RANGE_EN BIT(30)
-#define YT921X_ACL_BINb_IPV6_xA3_FRAG BIT(3)
-
-#define YT921X_ACL_BINb_MISC_FRAG BIT(3)
-#define YT921X_ACL_BINb_MISC_L4_TYPE_M GENMASK(2, 0)
-#define YT921X_ACL_BINb_MISC_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_MISC_L4_TYPE_M, (x))
-#define YT921X_ACL_BINa_MISC_PPPOE_FLAG BIT(30)
-#define YT921X_ACL_BINa_MISC_FIRST_FRAG BIT(29)
-#define YT921X_ACL_BINa_MISC_IP_OPTION BIT(28)
-#define YT921X_ACL_BINa_MISC_TCP_FLAGS_M GENMASK(27, 20)
-#define YT921X_ACL_BINa_MISC_TCP_FLAGS(x) FIELD_PREP(YT921X_ACL_BINa_MISC_TCP_FLAGS_M, (x))
-#define YT921X_ACL_BINa_MISC_IP_PROTO_M GENMASK(19, 12)
-#define YT921X_ACL_BINa_MISC_IP_PROTO(x) FIELD_PREP(YT921X_ACL_BINa_MISC_IP_PROTO_M, (x))
-#define YT921X_ACL_BINa_MISC_TOS_M GENMASK(11, 4)
-#define YT921X_ACL_BINa_MISC_TOS(x) FIELD_PREP(YT921X_ACL_BINa_MISC_TOS_M, (x))
-#define YT921X_ACL_BINa_MISC_L3_TYPE_M GENMASK(3, 0)
-#define YT921X_ACL_BINa_MISC_L3_TYPE(x) FIELD_PREP(YT921X_ACL_BINa_MISC_L3_TYPE_M, (x))
-
-#define YT921X_ACL_KEYb_L4_DPORT_RANGE_EN BIT(31)
-#define YT921X_ACL_KEYb_L4_SPORT_RANGE_EN BIT(30)
-#define YT921X_ACL_BINb_L4_FRAG BIT(3)
-#define YT921X_ACL_BINb_L4_TYPE_M GENMASK(2, 0)
-#define YT921X_ACL_BINb_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_L4_TYPE_M, (x))
-#define YT921X_ACL_BINa_L4_DPORT_M GENMASK(31, 16)
-#define YT921X_ACL_BINa_L4_SPORT_M GENMASK(15, 0)
-
-#define YT921X_ACL_BINb_UDF_IS_IGMP BIT(0)
-#define YT921X_ACL_BINa_UDF_UDF0_M GENMASK(31, 16)
-#define YT921X_ACL_BINa_UDF_UDF0(x) FIELD_PREP(YT921X_ACL_BINa_UDF_UDF0_M, (x))
-#define YT921X_ACL_BINa_UDF_UDF1_M GENMASK(15, 0)
-#define YT921X_ACL_BINa_UDF_UDF1(x) FIELD_PREP(YT921X_ACL_BINa_UDF_UDF1_M, (x))
-
-#define YT921X_ACL_KEYb_ETHERTYPE_ETHERTYPE_RANGE_EN BIT(30)
-#define YT921X_ACL_BINb_ETHERTYPE_L4_TYPE_M GENMASK(2, 0)
-#define YT921X_ACL_BINb_ETHERTYPE_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_ETHERTYPE_L4_TYPE_M, (x))
-#define YT921X_ACL_BINa_ETHERTYPE_ETHERTYPE_M GENMASK(15, 0)
-#define YT921X_ACL_BINa_ETHERTYPE_ETHERTYPE(x) FIELD_PREP(YT921X_ACL_BINa_ETHERTYPE_ETHERTYPE_M, (x))
-
-enum yt921x_l2_type {
- YT921X_L2_TYPE_ETH,
- YT921X_L2_TYPE_ETHV2,
- YT921X_L2_TYPE_ETHSAP,
- YT921X_L2_TYPE_ETHSNAP,
-};
-
-enum yt921x_l3_type {
- YT921X_L3_TYPE_OTHER,
- YT921X_L3_TYPE_IPV4,
- YT921X_L3_TYPE_IPV6,
- YT921X_L3_TYPE_ARP,
- YT921X_L3_TYPE_LLDP,
- YT921X_L3_TYPE_PAE,
- YT921X_L3_TYPE_ERP,
- YT921X_L3_TYPE_SLOW_PROTOCOL,
-};
-
-enum yt921x_l4_type {
- YT921X_L4_TYPE_OTHER,
- YT921X_L4_TYPE_TCP,
- YT921X_L4_TYPE_UDP,
- YT921X_L4_TYPE_UDPLITE,
- YT921X_L4_TYPE_ICMP,
- YT921X_L4_TYPE_IGMP,
- YT921X_L4_TYPE_MLD,
- YT921X_L4_TYPE_ND,
-};
-
#define YT921X_TPID_IGRn(x) (0x210000 + 4 * (x)) /* [0, 3] */
#define YT921X_TPID_IGR_TPID_M GENMASK(15, 0)
#define YT921X_PORTn_IGR_TPID(port) (0x210010 + 4 * (port))
@@ -693,47 +450,6 @@ enum yt921x_l4_type {
#define YT921X_LAG_HASH_MAC_SA BIT(2)
#define YT921X_LAG_HASH_MAC_DA BIT(1)
#define YT921X_LAG_HASH_SRC_PORT BIT(0)
-#define YT921X_UDFn_CTRL(x) (0x210094 + 4 * (x))
-#define YT921X_UDF_CTRL_UDF_TYPE_M GENMASK(8, 7)
-#define YT921X_UDF_CTRL_UDF_TYPE(x) FIELD_PREP(YT921X_UDF_CTRL_UDF_TYPE_M, (x))
-#define YT921X_UDF_CTRL_UDF_TYPE_ETH YT921X_UDF_CTRL_UDF_TYPE(0)
-#define YT921X_UDF_CTRL_UDF_TYPE_L3 YT921X_UDF_CTRL_UDF_TYPE(1)
-#define YT921X_UDF_CTRL_UDF_TYPE_L4 YT921X_UDF_CTRL_UDF_TYPE(2)
-#define YT921X_UDF_CTRL_UDF_OFFSET_M GENMASK(6, 0)
-#define YT921X_UDF_CTRL_UDF_OFFSET(x) FIELD_PREP(YT921X_UDF_CTRL_UDF_OFFSET_M, (x))
-
-#define YT921X_PORTn_RATE(port) (0x220000 + 4 * (port))
-#define YT921X_PORT_RATE_GAP_VALUE GENMASK(4, 0) /* default 20 */
-#define YT921X_METER_SLOT 0x220104
-#define YT921X_METER_SLOT_SLOT_M GENMASK(11, 0)
-#define YT921X_PORTn_METER(port) (0x220108 + 4 * (port))
-#define YT921X_PORT_METER_EN BIT(4)
-#define YT921X_PORT_METER_ID_M GENMASK(3, 0)
-#define YT921X_PORT_METER_ID(x) FIELD_PREP(YT921X_PORT_METER_ID_M, (x))
-#define YT921X_METERn_CTRL(x) (0x220800 + 0x10 * (x))
-#define YT921X_METER_CTRLc_METER_EN BIT(14)
-#define YT921X_METER_CTRLc_TOKEN_OVERFLOW_EN BIT(13) /* RFC4115: yellow use unused green bw */
-#define YT921X_METER_CTRLc_DROP_M GENMASK(12, 11)
-#define YT921X_METER_CTRLc_DROP(x) FIELD_PREP(YT921X_METER_CTRLc_DROP_M, (x))
-#define YT921X_METER_CTRLc_DROP_GYR YT921X_METER_CTRLc_DROP(0)
-#define YT921X_METER_CTRLc_DROP_YR YT921X_METER_CTRLc_DROP(1)
-#define YT921X_METER_CTRLc_DROP_R YT921X_METER_CTRLc_DROP(2)
-#define YT921X_METER_CTRLc_DROP_NONE YT921X_METER_CTRLc_DROP(3)
-#define YT921X_METER_CTRLc_COLOR_BLIND BIT(10)
-#define YT921X_METER_CTRLc_UNIT_M GENMASK(9, 7)
-#define YT921X_METER_CTRLc_UNIT(x) FIELD_PREP(YT921X_METER_CTRLc_UNIT_M, (x))
-#define YT921X_METER_CTRLc_BYTE_MODE_INCLUDE_GAP BIT(6) /* +GAP_VALUE bytes each packet */
-#define YT921X_METER_CTRLc_PKT_MODE BIT(5) /* 0: byte rate mode */
-#define YT921X_METER_CTRLc_RFC2698 BIT(4) /* 0: RFC4115 */
-#define YT921X_METER_CTRLbc_CBS_M GENMASK_ULL(35, 20)
-#define YT921X_METER_CTRLbc_CBS(x) FIELD_PREP(YT921X_METER_CTRLbc_CBS_M, (x))
-#define YT921X_METER_CTRLb_CIR_M GENMASK(19, 2)
-#define YT921X_METER_CTRLb_CIR(x) FIELD_PREP(YT921X_METER_CTRLb_CIR_M, (x))
-#define YT921X_METER_CTRLab_EBS_M GENMASK_ULL(33, 18)
-#define YT921X_METER_CTRLab_EBS(x) FIELD_PREP(YT921X_METER_CTRLab_EBS_M, (x))
-#define YT921X_METER_CTRLa_EIR_M GENMASK(17, 0)
-#define YT921X_METER_CTRLa_EIR(x) FIELD_PREP(YT921X_METER_CTRLa_EIR_M, (x))
-#define YT921X_METERn_STAT(x) (0x221000 + 8 * (x))
#define YT921X_PORTn_VLAN_CTRL(port) (0x230010 + 4 * (port))
#define YT921X_PORT_VLAN_CTRL_SVLAN_PRIO_EN BIT(31)
@@ -752,29 +468,6 @@ enum yt921x_l4_type {
#define YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_TAGGED BIT(1)
#define YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED BIT(0)
-#define YT921X_MIRROR 0x300300
-#define YT921X_MIRROR_IGR_PORTS_M GENMASK(26, 16)
-#define YT921X_MIRROR_IGR_PORTS(x) FIELD_PREP(YT921X_MIRROR_IGR_PORTS_M, (x))
-#define YT921X_MIRROR_IGR_PORTn(port) BIT((port) + 16)
-#define YT921X_MIRROR_EGR_PORTS_M GENMASK(14, 4)
-#define YT921X_MIRROR_EGR_PORTS(x) FIELD_PREP(YT921X_MIRROR_EGR_PORTS_M, (x))
-#define YT921X_MIRROR_EGR_PORTn(port) BIT((port) + 4)
-#define YT921X_MIRROR_PORT_M GENMASK(3, 0)
-#define YT921X_MIRROR_PORT(x) FIELD_PREP(YT921X_MIRROR_PORT_M, (x))
-
-#define YT921X_PORT_SHAPE_SLOT 0x34000c
-#define YT921X_PORT_SHAPE_SLOT_SLOT_M GENMASK(11, 0)
-#define YT921X_PORTn_SHAPE_CTRL(port) (0x354000 + 8 * (port))
-#define YT921X_PORT_SHAPE_CTRLb_EN BIT(4)
-#define YT921X_PORT_SHAPE_CTRLb_PKT_MODE BIT(3) /* 0: byte rate mode */
-#define YT921X_PORT_SHAPE_CTRLb_UNIT_M GENMASK(2, 0)
-#define YT921X_PORT_SHAPE_CTRLb_UNIT(x) FIELD_PREP(YT921X_PORT_SHAPE_CTRLb_UNIT_M, (x))
-#define YT921X_PORT_SHAPE_CTRLa_CBS_M GENMASK(31, 18)
-#define YT921X_PORT_SHAPE_CTRLa_CBS(x) FIELD_PREP(YT921X_PORT_SHAPE_CTRLa_CBS_M, (x))
-#define YT921X_PORT_SHAPE_CTRLa_CIR_M GENMASK(17, 0)
-#define YT921X_PORT_SHAPE_CTRLa_CIR(x) FIELD_PREP(YT921X_PORT_SHAPE_CTRLa_CIR_M, (x))
-#define YT921X_PORTn_SHAPE_STAT(port) (0x356000 + 4 * (port))
-
#define YT921X_EDATA_EXTMODE 0xfb
#define YT921X_EDATA_LEN 0x100
@@ -791,19 +484,7 @@ enum yt921x_fdb_entry_status {
#define YT921X_MSTI_NUM 16
-#define YT921X_TOKEN_BYTE_C 1 /* 1 token = 2^1 byte */
-#define YT921X_TOKEN_PKT_C -6 /* 1 token = 2^-6 packets */
-#define YT921X_TOKEN_RATE_C -15
-/* Custom meters only, not including dedicated port meters (11) */
#define YT921X_METER_NUM 64
-#define YT921X_METER_SLOT_MIN 80
-#define YT921X_METER_UNIT_MAX ((1 << 3) - 1)
-#define YT921X_METER_CIR_MAX ((1 << 18) - 1)
-#define YT921X_METER_CBS_MAX ((1 << 16) - 1)
-#define YT921X_PORT_SHAPE_SLOT_MIN 80
-#define YT921X_SHAPE_UNIT_MAX ((1 << 3) - 1)
-#define YT921X_SHAPE_CIR_MAX ((1 << 18) - 1)
-#define YT921X_SHAPE_CBS_MAX ((1 << 14) - 1)
#define YT921X_LAG_NUM 2
#define YT921X_LAG_PORT_NUM 4
@@ -823,7 +504,6 @@ enum yt921x_fdb_entry_status {
#define YT921X_ACL_BLK_NUM 48
#define YT921X_ACL_ENT_PER_BLK 8
#define YT921X_ACL_NUM (YT921X_ACL_BLK_NUM * YT921X_ACL_ENT_PER_BLK)
-#define YT921X_UDF_NUM 8
#define YT921X_LED_GROUP_NUM 3
#define YT921X_LED_PORT_NUM 10
@@ -885,26 +565,6 @@ struct yt921x_mib {
u64 tx_oam;
};
-struct yt921x_acl_entry {
- u32 key[2];
- u32 mask[2];
-};
-
-struct yt921x_acl_rule {
- unsigned long tag;
- enum tc_setup_type type;
-
- u32 action[3];
- bool sw_assisted;
-
- u8 mask;
- struct yt921x_acl_entry entries[YT921X_ACL_ENT_PER_BLK];
-};
-
-struct yt921x_acl_blk {
- struct yt921x_acl_rule *rules[YT921X_ACL_ENT_PER_BLK];
-};
-
struct yt921x_port {
unsigned char index;
@@ -1005,4 +665,14 @@ static inline int ethtool_speed_to_yt921x(int speed)
}
}
+static inline u32 ethaddr_hi4_to_u32(const unsigned char *addr)
+{
+ return (addr[0] << 24) | (addr[1] << 16) | (addr[2] << 8) | addr[3];
+}
+
+static inline u32 ethaddr_lo2_to_u32(const unsigned char *addr)
+{
+ return (addr[4] << 8) | addr[5];
+}
+
#endif
diff --git a/drivers/net/dsa/motorcomm/tc.c b/drivers/net/dsa/motorcomm/tc.c
new file mode 100644
index 000000000000..38dd4ec60bbc
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/tc.c
@@ -0,0 +1,1457 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#include "chip.h"
+#include "smi.h"
+#include "tc.h"
+
+#define to_yt921x_priv(_ds) container_of_const(_ds, struct yt921x_priv, ds)
+#define to_device(priv) ((priv)->ds.dev)
+
+static int yt921x_mtu_fetch(struct yt921x_priv *priv, int port)
+{
+ struct dsa_port *dp = dsa_to_port(&priv->ds, port);
+
+ return dp->user ? READ_ONCE(dp->user->mtu) : ETH_DATA_LEN;
+}
+
+/* v * 2^e */
+static u64 ldexpu64(u64 v, int e)
+{
+ return e >= 0 ? v << e : v >> -e;
+}
+
+/* slot (ns) * rate (/s) / 10^9 (ns/s) = 2^C * token * 4^unit */
+static u32 rate2token(u64 rate, unsigned int slot_ns, int unit, int C)
+{
+ int e = 2 * unit + C + YT921X_TOKEN_RATE_C;
+
+ return div_u64(ldexpu64(slot_ns * rate, -e), 1000000000);
+}
+
+static u64 token2rate(u32 token, unsigned int slot_ns, int unit, int C)
+{
+ int e = 2 * unit + C + YT921X_TOKEN_RATE_C;
+
+ return div_u64(ldexpu64(mul_u32_u32(1000000000, token), e), slot_ns);
+}
+
+/* burst = 2^C * token * 4^unit */
+static u32 burst2token(u64 burst, int unit, int C)
+{
+ return ldexpu64(burst, -(2 * unit + C));
+}
+
+static u64 token2burst(u32 token, int unit, int C)
+{
+ return ldexpu64(token, 2 * unit + C);
+}
+
+struct yt921x_marker {
+ u32 cir;
+ u32 cbs;
+ u32 ebs;
+ int unit;
+ bool pkt_mode;
+};
+
+#define YT921X_MARKER_PKT_MODE BIT(0)
+#define YT921X_MARKER_SINGLE_BUCKET BIT(1)
+
+static int
+yt921x_marker_tfm(struct yt921x_marker *marker, u64 rate, u64 burst,
+ unsigned int flags, unsigned int slot_ns, u32 cir_max,
+ u32 cbs_max, int unit_max, struct yt921x_priv *priv, int port,
+ struct netlink_ext_ack *extack)
+{
+ const int C = flags & YT921X_MARKER_PKT_MODE ? YT921X_TOKEN_PKT_C :
+ YT921X_TOKEN_BYTE_C;
+ struct device *dev = to_device(priv);
+ struct yt921x_marker m;
+ u64 burst_est;
+ u64 burst_sug;
+ u64 burst_max;
+ u64 rate_max;
+
+ m.unit = unit_max;
+ rate_max = token2rate(cir_max, slot_ns, m.unit, C);
+ burst_max = token2burst(cbs_max, m.unit, C);
+
+ /* Check for unusual values */
+ if (rate > rate_max || burst > burst_max) {
+ NL_SET_ERR_MSG_MOD(extack, "Unexpected tremendous rate");
+ return -ERANGE;
+ }
+
+ /* Check for matching burst */
+ burst_est = div_u64(slot_ns * rate, 1000000000);
+ burst_sug = burst_est;
+ if (flags & YT921X_MARKER_PKT_MODE)
+ burst_sug++;
+ else
+ burst_sug += ETH_HLEN + yt921x_mtu_fetch(priv, port) +
+ ETH_FCS_LEN;
+ if (burst_sug > burst)
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "Consider match rate %llu with burst at least %llu",
+ rate, burst_sug);
+
+ /* Select unit */
+ for (; m.unit > 0; m.unit--) {
+ if (rate > (rate_max >> 2) || burst > (burst_max >> 2))
+ break;
+ rate_max >>= 2;
+ burst_max >>= 2;
+ }
+
+ /* Calculate information rate and bucket size */
+ m.cir = rate2token(rate, slot_ns, m.unit, C);
+ if (!m.cir)
+ m.cir = 1;
+ else if (WARN_ON(m.cir > cir_max))
+ m.cir = cir_max;
+ m.cbs = burst2token(burst, m.unit, C);
+ if (!m.cbs)
+ m.cbs = 1;
+ else if (WARN_ON(m.cbs > cbs_max))
+ m.cbs = cbs_max;
+
+ /* Cut EBS */
+ m.ebs = 0;
+ if (!(flags & YT921X_MARKER_SINGLE_BUCKET)) {
+ /* We don't have a chance to adjust rate when MTU is changed */
+ if (flags & YT921X_MARKER_PKT_MODE)
+ burst_est++;
+ else
+ burst_est += YT921X_FRAME_SIZE_MAX;
+
+ if (burst_est < burst) {
+ u32 pbs = m.cbs;
+
+ m.cbs = burst2token(burst_est, m.unit, C);
+ if (!m.cbs)
+ m.cbs = 1;
+ else if (WARN_ON(m.cbs > cbs_max))
+ m.cbs = cbs_max;
+
+ if (pbs > m.cbs)
+ m.ebs = pbs - m.cbs;
+ }
+ }
+
+ dev_dbg(dev,
+ "slot %u ns, rate %llu, burst %llu -> unit %d, cir %u, cbs %u, ebs %u\n",
+ slot_ns, rate, burst, m.unit, m.cir, m.cbs, m.ebs);
+
+ m.pkt_mode = flags & YT921X_MARKER_PKT_MODE;
+ *marker = m;
+ return 0;
+}
+
+static int
+yt921x_marker_tfm_police(struct yt921x_marker *marker,
+ const struct flow_action_police *police,
+ unsigned int flags, struct yt921x_priv *priv, int port,
+ struct netlink_ext_ack *extack)
+{
+ bool pkt_mode = !!police->rate_pkt_ps;
+ u64 burst;
+ u64 rate;
+
+ rate = pkt_mode ? police->rate_pkt_ps : police->rate_bytes_ps;
+ burst = pkt_mode ? police->burst_pkt : police->burst;
+ if (pkt_mode)
+ flags |= YT921X_MARKER_PKT_MODE;
+
+ return yt921x_marker_tfm(marker, rate, burst, flags,
+ priv->meter_slot_ns, YT921X_METER_CIR_MAX,
+ YT921X_METER_CBS_MAX, YT921X_METER_UNIT_MAX,
+ priv, port, extack);
+}
+
+static int
+yt921x_marker_tfm_shape(struct yt921x_marker *marker, u64 rate, u64 burst,
+ unsigned int flags, struct yt921x_priv *priv, int port,
+ struct netlink_ext_ack *extack)
+{
+ return yt921x_marker_tfm(marker, rate, burst, flags,
+ priv->port_shape_slot_ns, YT921X_SHAPE_CIR_MAX,
+ YT921X_SHAPE_CBS_MAX, YT921X_SHAPE_UNIT_MAX,
+ priv, port, extack);
+}
+
+static int
+yt921x_police_validate(const struct flow_action_police *police,
+ const struct flow_action *action,
+ const struct flow_action_entry *act,
+ struct netlink_ext_ack *extack)
+{
+ if (police->exceed.act_id != FLOW_ACTION_DROP) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Offload not supported when exceed action is not drop");
+ return -EOPNOTSUPP;
+ }
+
+ if (police->notexceed.act_id != FLOW_ACTION_PIPE &&
+ police->notexceed.act_id != FLOW_ACTION_ACCEPT) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Offload not supported when conform action is not pipe or ok");
+ return -EOPNOTSUPP;
+ }
+
+ if (police->notexceed.act_id == FLOW_ACTION_ACCEPT && action && act &&
+ !flow_action_is_last_entry(action, act)) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Offload not supported when conform action is ok, but action is not last");
+ return -EOPNOTSUPP;
+ }
+
+ /* mtu defaults to unlimited but we got 2040 here, don't know why */
+ if (police->peakrate_bytes_ps || police->avrate || police->overhead) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Offload not supported when peakrate/avrate/overhead is configured");
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_meter_config(struct yt921x_priv *priv, unsigned int id,
+ const struct yt921x_marker *marker)
+{
+ u32 ctrls[3];
+
+ ctrls[0] = 0;
+ ctrls[1] = YT921X_METER_CTRLb_CIR(marker->cir);
+ ctrls[2] = YT921X_METER_CTRLc_UNIT(marker->unit) |
+ YT921X_METER_CTRLc_DROP_R |
+ YT921X_METER_CTRLc_TOKEN_OVERFLOW_EN |
+ YT921X_METER_CTRLc_METER_EN;
+ if (marker->pkt_mode)
+ ctrls[2] |= YT921X_METER_CTRLc_PKT_MODE;
+ update_ctrls_unaligned(&ctrls[0], &ctrls[1],
+ YT921X_METER_CTRLab_EBS_M,
+ YT921X_METER_CTRLab_EBS(marker->ebs));
+ update_ctrls_unaligned(&ctrls[1], &ctrls[2],
+ YT921X_METER_CTRLbc_CBS_M,
+ YT921X_METER_CTRLbc_CBS(marker->cbs));
+
+ return yt921x_reg96_write(priv, YT921X_METERn_CTRL(id), ctrls);
+}
+
+void yt921x_dsa_port_policer_del(struct dsa_switch *ds, int port)
+{
+ struct yt921x_priv *priv = to_yt921x_priv(ds);
+ struct device *dev = to_device(priv);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_reg_write(priv, YT921X_PORTn_METER(port), 0);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ dev_err(dev, "Failed to %s port %d: %i\n", "delete policer on",
+ port, res);
+}
+
+int
+yt921x_dsa_port_policer_add(struct dsa_switch *ds, int port,
+ const struct flow_action_police *police,
+ struct netlink_ext_ack *extack)
+{
+ struct yt921x_priv *priv = to_yt921x_priv(ds);
+ struct yt921x_marker marker;
+ u32 ctrl;
+ int res;
+
+ res = yt921x_police_validate(police, NULL, NULL, extack);
+ if (res)
+ return res;
+
+ res = yt921x_marker_tfm_police(&marker, police, 0, priv, port, extack);
+ if (res)
+ return res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_meter_config(priv, port + YT921X_METER_NUM, &marker);
+ if (res)
+ goto end;
+
+ ctrl = YT921X_PORT_METER_ID(port) | YT921X_PORT_METER_EN;
+ res = yt921x_reg_write(priv, YT921X_PORTn_METER(port), ctrl);
+end:
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+int
+yt921x_dsa_port_setup_tc_tbf_port(struct dsa_switch *ds, int port,
+ const struct tc_tbf_qopt_offload *qopt)
+{
+ struct yt921x_priv *priv = to_yt921x_priv(ds);
+ struct netlink_ext_ack *extack = qopt->extack;
+ u32 ctrls[2];
+ int res;
+
+ if (qopt->parent != TC_H_ROOT)
+ return -EOPNOTSUPP;
+
+ switch (qopt->command) {
+ case TC_TBF_STATS:
+ /* Unfortunately the convention for TC_*_STATS is a mess,
+ * neither 0 nor -EOPNOTSUPP is perfect here.
+ */
+ return -EOPNOTSUPP;
+ case TC_TBF_DESTROY:
+ ctrls[0] = 0;
+ ctrls[1] = 0;
+ break;
+ case TC_TBF_REPLACE: {
+ const struct tc_tbf_qopt_offload_replace_params *p;
+ struct yt921x_marker marker;
+
+ p = &qopt->replace_params;
+
+ res = yt921x_marker_tfm_shape(&marker, p->rate.rate_bytes_ps,
+ p->max_size,
+ YT921X_MARKER_SINGLE_BUCKET,
+ priv, port, extack);
+ if (res)
+ return res;
+
+ ctrls[0] = YT921X_PORT_SHAPE_CTRLa_CIR(marker.cir) |
+ YT921X_PORT_SHAPE_CTRLa_CBS(marker.cbs);
+ ctrls[1] = YT921X_PORT_SHAPE_CTRLb_UNIT(marker.unit) |
+ YT921X_PORT_SHAPE_CTRLb_EN;
+ break;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_reg64_write(priv, YT921X_PORTn_SHAPE_CTRL(port), ctrls);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+int
+yt921x_dsa_port_setup_tc(struct dsa_switch *ds, int port,
+ enum tc_setup_type type, void *type_data)
+{
+ switch (type) {
+ case TC_SETUP_QDISC_TBF: {
+ const struct tc_tbf_qopt_offload *qopt = type_data;
+
+ return yt921x_dsa_port_setup_tc_tbf_port(ds, port, qopt);
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+/* ACL: 48 blocks * 8 entries
+ *
+ * One rule can span multiple entries, but within a block.
+ */
+
+static void
+yt921x_acl_entry_set(struct yt921x_acl_entry *entry, unsigned int offset,
+ u32 flags, bool set)
+{
+ if (set)
+ entry->key[offset] |= flags;
+ entry->mask[offset] |= flags;
+}
+
+static unsigned int
+yt921x_acl_entries_set_is_fragment(struct yt921x_acl_entry *entries,
+ unsigned int size, bool set)
+{
+ for (unsigned int i = 0; i < size; i++)
+ switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) {
+ case YT921X_ACL_TYPE_IPV4_DA:
+ case YT921X_ACL_TYPE_IPV4_SA:
+ yt921x_acl_entry_set(&entries[i], 1,
+ YT921X_ACL_BINb_IPV4_FRAG, set);
+ return size;
+ case YT921X_ACL_TYPE_IPV6_DA3:
+ case YT921X_ACL_TYPE_IPV6_SA3:
+ yt921x_acl_entry_set(&entries[i], 1,
+ YT921X_ACL_BINb_IPV6_xA3_FRAG,
+ set);
+ return size;
+ case YT921X_ACL_TYPE_MISC:
+ yt921x_acl_entry_set(&entries[i], 1,
+ YT921X_ACL_BINb_MISC_FRAG, set);
+ return size;
+ case YT921X_ACL_TYPE_L4:
+ yt921x_acl_entry_set(&entries[i], 1,
+ YT921X_ACL_BINb_L4_FRAG, set);
+ return size;
+ }
+
+ if (size >= YT921X_ACL_ENT_PER_BLK)
+ return 0;
+
+ entries[size] = (typeof(*entries)){};
+ entries[size].key[1] = YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC);
+ yt921x_acl_entry_set(&entries[size], 1, YT921X_ACL_BINb_MISC_FRAG, set);
+
+ return size + 1;
+}
+
+static unsigned int
+yt921x_acl_entries_set_first_frag(struct yt921x_acl_entry *entries,
+ unsigned int size, bool set)
+{
+ for (unsigned int i = 0; i < size; i++)
+ switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) {
+ case YT921X_ACL_TYPE_IPV6_DA2:
+ case YT921X_ACL_TYPE_IPV6_SA2:
+ yt921x_acl_entry_set(&entries[i], 1,
+ YT921X_ACL_BINb_IPV6_xA2_FIRST_FRAG,
+ set);
+ return size;
+ case YT921X_ACL_TYPE_MISC:
+ yt921x_acl_entry_set(&entries[i], 0,
+ YT921X_ACL_BINa_MISC_FIRST_FRAG,
+ set);
+ return size;
+ }
+
+ if (size >= YT921X_ACL_ENT_PER_BLK)
+ return 0;
+
+ entries[size] = (typeof(*entries)){};
+ entries[size].key[1] = YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC);
+ yt921x_acl_entry_set(&entries[size], 0,
+ YT921X_ACL_BINa_MISC_FIRST_FRAG, set);
+
+ return size + 1;
+}
+
+static unsigned int
+yt921x_acl_entries_set_l3_type(struct yt921x_acl_entry *entries,
+ unsigned int size, enum yt921x_l3_type type)
+{
+ for (unsigned int i = 0; i < size; i++)
+ switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) {
+ case YT921X_ACL_TYPE_MAC_DA0:
+ case YT921X_ACL_TYPE_MAC_SA0:
+ entries[i].key[1] |= YT921X_ACL_BINb_MAC_xA0_L3_TYPE(type);
+ entries[i].mask[1] |= YT921X_ACL_BINb_MAC_xA0_L3_TYPE_M;
+ return size;
+ case YT921X_ACL_TYPE_MISC:
+ entries[i].key[0] |= YT921X_ACL_BINa_MISC_L3_TYPE(type);
+ entries[i].mask[0] |= YT921X_ACL_BINa_MISC_L3_TYPE_M;
+ return size;
+ }
+
+ if (size >= YT921X_ACL_ENT_PER_BLK)
+ return 0;
+
+ entries[size] = (typeof(*entries)){};
+ entries[size].key[0] = YT921X_ACL_BINa_MISC_L3_TYPE(type);
+ entries[size].key[1] = YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC);
+ entries[size].mask[0] = YT921X_ACL_BINa_MISC_L3_TYPE_M;
+
+ return size + 1;
+}
+
+static unsigned int
+yt921x_acl_entries_set_l4_type(struct yt921x_acl_entry *entries,
+ unsigned int size, enum yt921x_l4_type type)
+{
+ for (unsigned int i = 0; i < size; i++)
+ switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) {
+ case YT921X_ACL_TYPE_IPV4_DA:
+ case YT921X_ACL_TYPE_IPV4_SA:
+ entries[i].key[1] |= YT921X_ACL_BINb_IPV4_L4_TYPE(type);
+ entries[i].mask[1] |= YT921X_ACL_BINb_IPV4_L4_TYPE_M;
+ return size;
+ case YT921X_ACL_TYPE_IPV6_DA0:
+ case YT921X_ACL_TYPE_IPV6_DA1:
+ case YT921X_ACL_TYPE_IPV6_DA2:
+ case YT921X_ACL_TYPE_IPV6_DA3:
+ case YT921X_ACL_TYPE_IPV6_SA0:
+ case YT921X_ACL_TYPE_IPV6_SA1:
+ case YT921X_ACL_TYPE_IPV6_SA2:
+ case YT921X_ACL_TYPE_IPV6_SA3:
+ entries[i].key[1] |= YT921X_ACL_BINb_IPV6_L4_TYPE(type);
+ entries[i].mask[1] |= YT921X_ACL_BINb_IPV6_L4_TYPE_M;
+ return size;
+ case YT921X_ACL_TYPE_L4:
+ entries[i].key[1] |= YT921X_ACL_BINb_L4_TYPE(type);
+ entries[i].mask[1] |= YT921X_ACL_BINb_L4_TYPE_M;
+ return size;
+ case YT921X_ACL_TYPE_MISC:
+ entries[i].key[1] |= YT921X_ACL_BINb_MISC_L4_TYPE(type);
+ entries[i].mask[1] |= YT921X_ACL_BINb_MISC_L4_TYPE_M;
+ return size;
+ }
+
+ if (size >= YT921X_ACL_ENT_PER_BLK)
+ return 0;
+
+ entries[size] = (typeof(*entries)){};
+ entries[size].key[1] = YT921X_ACL_BINb_MISC_L4_TYPE(type) |
+ YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC);
+ entries[size].mask[1] = YT921X_ACL_BINb_MISC_L4_TYPE_M;
+
+ return size + 1;
+}
+
+static struct yt921x_acl_entry *
+yt921x_acl_entries_new(struct yt921x_acl_entry *entries, unsigned int *sizep,
+ u32 type)
+{
+ unsigned int size = *sizep;
+
+ if (size >= YT921X_ACL_ENT_PER_BLK)
+ return NULL;
+
+ entries[size] = (typeof(*entries)){};
+ entries[size].key[1] = YT921X_ACL_KEYb_TYPE(type);
+
+ (*sizep)++;
+ return &entries[size];
+}
+
+static struct yt921x_acl_entry *
+yt921x_acl_entries_find(struct yt921x_acl_entry *entries, unsigned int *sizep,
+ u32 type)
+{
+ for (unsigned int i = 0; i < *sizep; i++)
+ if (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1]) ==
+ type)
+ return &entries[i];
+ return yt921x_acl_entries_new(entries, sizep, type);
+}
+
+static void
+yt921x_acl_rule_set_ports(struct yt921x_acl_rule *aclrule, u16 ord,
+ u16 ports_mask)
+{
+ struct yt921x_acl_entry *entries = aclrule->entries;
+
+ for (unsigned int i = 0; i < hweight8(aclrule->mask); i++) {
+ entries[i].key[1] |= YT921X_ACL_KEYb_SPORTS(ports_mask) |
+ YT921X_ACL_KEYb_ORD(ord);
+ }
+}
+
+struct yt921x_acl_rule_ext {
+ struct yt921x_acl_rule r;
+
+ struct yt921x_marker marker;
+};
+
+static int
+yt921x_acl_rule_ext_parse_flow_entries(struct yt921x_acl_rule_ext *ruleext,
+ const struct flow_cls_offload *cls)
+{
+ const struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
+ struct yt921x_acl_entry *entries = ruleext->r.entries;
+ struct netlink_ext_ack *extack = cls->common.extack;
+ const struct flow_dissector *dissector;
+ struct yt921x_acl_entry *entry;
+ unsigned int size = 0;
+ bool use_dport;
+ bool use_sport;
+
+ /* Incomplete and probably won't, since it supports custom u32 filters.
+ * New adapters are welcome.
+ */
+ dissector = rule->match.dissector;
+ if (dissector->used_keys &
+ ~(BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_IPV4_ADDRS) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_IPV6_ADDRS) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_PORTS) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_PORTS_RANGE) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_IP) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_TCP))) {
+ NL_SET_ERR_MSG_MOD(extack, "Unsupported keys used");
+ return -EOPNOTSUPP;
+ }
+
+ /* Entries */
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV4_ADDRS)) {
+ struct flow_match_ipv4_addrs match;
+
+ flow_rule_match_ipv4_addrs(rule, &match);
+
+ if (match.mask->dst) {
+ entry = yt921x_acl_entries_new(entries, &size,
+ YT921X_ACL_TYPE_IPV4_DA);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= ntohl(match.key->dst);
+ entry->mask[0] |= ntohl(match.mask->dst);
+ }
+
+ if (match.mask->src) {
+ entry = yt921x_acl_entries_new(entries, &size,
+ YT921X_ACL_TYPE_IPV4_SA);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= ntohl(match.key->src);
+ entry->mask[0] |= ntohl(match.mask->src);
+ }
+ }
+
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV6_ADDRS)) {
+ struct flow_match_ipv6_addrs match;
+
+ flow_rule_match_ipv6_addrs(rule, &match);
+
+ for (unsigned int i = 0; i < 4; i++) {
+ if (!match.mask->dst.s6_addr32[i])
+ continue;
+
+ entry = yt921x_acl_entries_new(entries, &size,
+ YT921X_ACL_TYPE_IPV6_DA0 + i);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= ntohl(match.key->dst.s6_addr32[i]);
+ entry->mask[0] |= ntohl(match.mask->dst.s6_addr32[i]);
+ }
+
+ for (unsigned int i = 0; i < 4; i++) {
+ if (!match.mask->src.s6_addr32[i])
+ continue;
+
+ entry = yt921x_acl_entries_new(entries, &size,
+ YT921X_ACL_TYPE_IPV6_SA0 + i);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= ntohl(match.key->src.s6_addr32[i]);
+ entry->mask[0] |= ntohl(match.mask->src.s6_addr32[i]);
+ }
+ }
+
+ use_dport = false;
+ use_sport = false;
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) {
+ struct flow_match_ports match;
+
+ entry = yt921x_acl_entries_new(entries, &size,
+ YT921X_ACL_TYPE_L4);
+ if (!entry)
+ goto err;
+
+ flow_rule_match_ports(rule, &match);
+
+ use_dport = !!match.mask->dst;
+ use_sport = !!match.mask->src;
+
+ entry->key[0] |= (ntohs(match.key->dst) << 16) |
+ ntohs(match.key->src);
+ entry->mask[0] |= (ntohs(match.mask->dst) << 16) |
+ ntohs(match.mask->src);
+ }
+
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS_RANGE)) {
+ struct flow_match_ports_range match;
+
+ entry = yt921x_acl_entries_find(entries, &size,
+ YT921X_ACL_TYPE_L4);
+ if (!entry)
+ goto err;
+
+ flow_rule_match_ports_range(rule, &match);
+
+ if ((use_dport && match.mask->tp.dst) ||
+ (use_sport && match.mask->tp.src)) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Port mask and range are mutually exclusive");
+ return -EINVAL;
+ }
+
+ if (match.mask->tp.dst) {
+ entry->key[0] |= ntohs(match.key->tp_min.dst) << 16;
+ entry->key[1] |= YT921X_ACL_KEYb_L4_DPORT_RANGE_EN;
+ entry->mask[0] |= ntohs(match.key->tp_max.dst) << 16;
+ }
+
+ if (match.mask->tp.src) {
+ entry->key[0] |= ntohs(match.key->tp_min.src);
+ entry->key[1] |= YT921X_ACL_KEYb_L4_SPORT_RANGE_EN;
+ entry->mask[0] |= ntohs(match.key->tp_max.src);
+ }
+ }
+
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
+ struct flow_match_eth_addrs match;
+ u32 mask;
+
+ flow_rule_match_eth_addrs(rule, &match);
+
+ mask = ethaddr_hi4_to_u32(match.mask->dst);
+ if (mask) {
+ entry = yt921x_acl_entries_new(entries, &size,
+ YT921X_ACL_TYPE_MAC_DA0);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= ethaddr_hi4_to_u32(match.key->dst);
+ entry->mask[0] |= mask;
+ }
+
+ mask = ethaddr_hi4_to_u32(match.mask->src);
+ if (mask) {
+ entry = yt921x_acl_entries_new(entries, &size,
+ YT921X_ACL_TYPE_MAC_SA0);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= ethaddr_hi4_to_u32(match.key->src);
+ entry->mask[0] |= mask;
+ }
+
+ mask = (ethaddr_lo2_to_u32(match.mask->dst) << 16) |
+ ethaddr_lo2_to_u32(match.mask->src);
+ if (mask) {
+ entry = yt921x_acl_entries_new(entries, &size,
+ YT921X_ACL_TYPE_MAC_DA1_SA1);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= (ethaddr_lo2_to_u32(match.key->dst) << 16) |
+ ethaddr_lo2_to_u32(match.key->src);
+ entry->mask[0] |= mask;
+ }
+ }
+
+ /* Entries + Misc */
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
+ struct flow_match_basic match;
+
+ flow_rule_match_basic(rule, &match);
+
+ if (match.mask->n_proto) {
+ enum yt921x_l3_type l3type = YT921X_L3_TYPE_OTHER;
+
+ if (match.mask->n_proto == htons(~0))
+ switch (match.key->n_proto) {
+ case htons(ETH_P_IP):
+ l3type = YT921X_L3_TYPE_IPV4;
+ break;
+ case htons(ETH_P_IPV6):
+ l3type = YT921X_L3_TYPE_IPV6;
+ break;
+ case htons(ETH_P_ARP):
+ l3type = YT921X_L3_TYPE_ARP;
+ break;
+ case htons(ETH_P_LLDP):
+ l3type = YT921X_L3_TYPE_LLDP;
+ break;
+ case htons(ETH_P_PAE):
+ l3type = YT921X_L3_TYPE_PAE;
+ break;
+ case htons(ETH_P_CFM):
+ l3type = YT921X_L3_TYPE_ERP;
+ break;
+ }
+
+ if (l3type != YT921X_L3_TYPE_OTHER) {
+ size = yt921x_acl_entries_set_l3_type(entries,
+ size,
+ l3type);
+ if (!size)
+ goto err;
+ } else {
+ entry = yt921x_acl_entries_new(entries, &size,
+ YT921X_ACL_TYPE_ETHERTYPE);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= ntohs(match.key->n_proto);
+ entry->mask[0] |= ntohs(match.mask->n_proto);
+ }
+ }
+
+ if (match.mask->ip_proto) {
+ enum yt921x_l4_type l4type = YT921X_L4_TYPE_OTHER;
+
+ if (match.mask->ip_proto == (u8)~0)
+ switch (match.key->ip_proto) {
+ case IPPROTO_TCP:
+ l4type = YT921X_L4_TYPE_TCP;
+ break;
+ case IPPROTO_UDP:
+ l4type = YT921X_L4_TYPE_UDP;
+ break;
+ case IPPROTO_UDPLITE:
+ l4type = YT921X_L4_TYPE_UDPLITE;
+ break;
+ case IPPROTO_ICMP:
+ l4type = YT921X_L4_TYPE_ICMP;
+ break;
+ case IPPROTO_IGMP:
+ l4type = YT921X_L4_TYPE_IGMP;
+ break;
+ }
+
+ if (l4type != YT921X_L4_TYPE_OTHER) {
+ size = yt921x_acl_entries_set_l4_type(entries,
+ size,
+ l4type);
+ if (!size)
+ goto err;
+ } else {
+ entry = yt921x_acl_entries_find(entries, &size,
+ YT921X_ACL_TYPE_MISC);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= YT921X_ACL_BINa_MISC_IP_PROTO(match.key->ip_proto);
+ entry->mask[0] |= YT921X_ACL_BINa_MISC_IP_PROTO(match.mask->ip_proto);
+ }
+ }
+ }
+
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) {
+ u32 supp_flags = FLOW_DIS_IS_FRAGMENT | FLOW_DIS_FIRST_FRAG;
+ struct flow_match_control match;
+
+ flow_rule_match_control(rule, &match);
+ if (!flow_rule_is_supp_control_flags(supp_flags,
+ match.mask->flags, extack))
+ return -EOPNOTSUPP;
+
+ if (match.mask->flags & FLOW_DIS_IS_FRAGMENT) {
+ bool set = match.key->flags & FLOW_DIS_IS_FRAGMENT;
+
+ size = yt921x_acl_entries_set_is_fragment(entries, size,
+ set);
+ if (!size)
+ goto err;
+ }
+ if (match.mask->flags & FLOW_DIS_FIRST_FRAG) {
+ bool set = match.key->flags & FLOW_DIS_FIRST_FRAG;
+
+ size = yt921x_acl_entries_set_first_frag(entries, size,
+ set);
+ if (!size)
+ goto err;
+ }
+ }
+
+ /* Misc only */
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IP)) {
+ struct flow_match_ip match;
+
+ flow_rule_match_ip(rule, &match);
+ if (match.mask->ttl) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Matching on TTL not supported");
+ return -EOPNOTSUPP;
+ }
+
+ if (match.mask->tos) {
+ entry = yt921x_acl_entries_find(entries, &size,
+ YT921X_ACL_TYPE_MISC);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= YT921X_ACL_BINa_MISC_TOS(match.key->tos);
+ entry->mask[0] |= YT921X_ACL_BINa_MISC_TOS(match.mask->tos);
+ }
+ }
+
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_TCP)) {
+ struct flow_match_tcp match;
+
+ flow_rule_match_tcp(rule, &match);
+ if (match.mask->flags & htons(~0xff)) {
+ NL_SET_ERR_MSG_MOD(extack, "Unsupported TCP flags");
+ return -EOPNOTSUPP;
+ }
+
+ if (match.mask->flags) {
+ entry = yt921x_acl_entries_find(entries, &size,
+ YT921X_ACL_TYPE_MISC);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= YT921X_ACL_BINa_MISC_TCP_FLAGS(ntohs(match.key->flags));
+ entry->mask[0] |= YT921X_ACL_BINa_MISC_TCP_FLAGS(ntohs(match.mask->flags));
+ }
+ }
+
+ if (!size) {
+ NL_SET_ERR_MSG_MOD(extack, "Empty rule generated, this should not happen");
+ return -EOPNOTSUPP;
+ }
+
+ ruleext->r.mask = (1 << size) - 1;
+ return 0;
+
+err:
+ NL_SET_ERR_MSG_MOD(extack, "Rule too complex");
+ return -EOPNOTSUPP;
+}
+
+static int
+yt921x_acl_rule_ext_parse_flow_action(struct yt921x_acl_rule_ext *ruleext,
+ const struct flow_cls_offload *cls,
+ struct yt921x_priv *priv, int port)
+{
+ const struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
+ const struct flow_action *flow_action = &rule->action;
+ struct netlink_ext_ack *extack = cls->common.extack;
+ enum flow_action_id redir_act = NUM_FLOW_ACTIONS;
+ const struct flow_action_entry *act;
+ u32 *action = ruleext->r.action;
+ bool seen_priority = false;
+ const char *reason = NULL;
+ bool seen_police = false;
+ unsigned int i;
+ int res;
+
+ memset(action, 0, 3 * sizeof(*action));
+ flow_action_for_each(i, act, flow_action)
+ switch (act->id) {
+ case FLOW_ACTION_ACCEPT:
+ case FLOW_ACTION_DROP:
+ case FLOW_ACTION_REDIRECT:
+ if (redir_act != NUM_FLOW_ACTIONS &&
+ redir_act != act->id) {
+ reason = "Different redirect actions";
+ goto fallback;
+ }
+ redir_act = act->id;
+
+ switch (act->id) {
+ case FLOW_ACTION_ACCEPT:
+ action[2] |= YT921X_ACL_ACTc_FWD_EN |
+ YT921X_ACL_ACTc_FWD_FWD;
+ break;
+ case FLOW_ACTION_DROP:
+ action[2] |= YT921X_ACL_ACTc_FWD_EN |
+ YT921X_ACL_ACTc_FWD_REDIR;
+ break;
+ case FLOW_ACTION_REDIRECT: {
+ struct dsa_port *to_dp;
+
+ to_dp = dsa_port_from_netdev(act->dev);
+ if (IS_ERR(to_dp) || to_dp->ds != &priv->ds) {
+ reason = "Redirect to non-local port";
+ goto fallback;
+ }
+
+ action[2] |= YT921X_ACL_ACTc_FWD_EN |
+ YT921X_ACL_ACTc_FWD_REDIR |
+ YT921X_ACL_ACTc_FWD_REDIR_DPORTn(to_dp->index);
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ case FLOW_ACTION_PRIORITY:
+ if (seen_priority) {
+ action[0] &= ~YT921X_ACL_ACTa_PRIO_EN;
+ action[1] &= ~YT921X_ACL_ACTb_PRIO_M;
+
+ reason = "Multiple priority actions";
+ goto fallback;
+ }
+ seen_priority = true;
+
+ if (act->priority >= YT921X_PRIO_NUM) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Priority value is too high");
+ return -EOPNOTSUPP;
+ }
+ action[0] |= YT921X_ACL_ACTa_PRIO_EN;
+ action[1] |= YT921X_ACL_ACTb_PRIO(act->priority);
+ break;
+ case FLOW_ACTION_POLICE: {
+ const struct flow_action_police *police = &act->police;
+
+ if (seen_police) {
+ action[0] &= ~YT921X_ACL_ACTa_METER_EN;
+
+ reason = "Multiple police actions";
+ goto fallback;
+ }
+ seen_police = true;
+
+ res = yt921x_police_validate(police, flow_action, act,
+ extack);
+ if (res)
+ return res;
+
+ res = yt921x_marker_tfm_police(&ruleext->marker, police,
+ 0, priv, port, extack);
+ if (res)
+ return res;
+
+ action[0] |= YT921X_ACL_ACTa_METER_EN;
+ break;
+ }
+ default:
+fallback:
+ if (cls->common.skip_sw) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "Action not supported when skip_sw: %s",
+ reason);
+ return -EOPNOTSUPP;
+ }
+ fallthrough;
+ case FLOW_ACTION_TRAP:
+ redir_act = FLOW_ACTION_TRAP;
+
+ action[2] &= ~YT921X_ACL_ACTc_FWD_REDIR_DPORTS_M &
+ ~YT921X_ACL_ACTc_FWD_M;
+ action[2] |= YT921X_ACL_ACTc_FWD_EN |
+ YT921X_ACL_ACTc_FWD_TRAP;
+ break;
+ }
+
+ ruleext->r.sw_assisted = !cls->common.skip_sw;
+ return 0;
+}
+
+static int
+yt921x_acl_rule_ext_parse_flow(struct yt921x_acl_rule_ext *ruleext, int port,
+ const struct flow_cls_offload *cls, bool ingress,
+ struct yt921x_priv *priv)
+{
+ struct netlink_ext_ack *extack = cls->common.extack;
+ int res;
+
+ if (!ingress) {
+ NL_SET_ERR_MSG_MOD(extack, "Only ingress is supported");
+ return -EOPNOTSUPP;
+ }
+
+ if (cls->common.chain_index) {
+ NL_SET_ERR_MSG(extack, "Only chain 0 is supported");
+ return -EOPNOTSUPP;
+ }
+
+ res = yt921x_acl_rule_ext_parse_flow_action(ruleext, cls, priv, port);
+ if (res)
+ return res;
+ res = yt921x_acl_rule_ext_parse_flow_entries(ruleext, cls);
+ if (res)
+ return res;
+
+ yt921x_acl_rule_set_ports(&ruleext->r, 0, BIT(port));
+ ruleext->r.tag = cls->cookie;
+ ruleext->r.type = TC_SETUP_CLSFLOWER;
+ return 0;
+}
+
+static unsigned int
+yt921x_acl_find(const struct yt921x_priv *priv, enum tc_setup_type type,
+ unsigned long tag)
+{
+ for (unsigned int blkid = 0; blkid < YT921X_ACL_BLK_NUM; blkid++) {
+ const struct yt921x_acl_blk *aclblk = priv->acl_blks[blkid];
+
+ if (!aclblk)
+ continue;
+
+ for (unsigned int i = 0; i < YT921X_ACL_ENT_PER_BLK; i++)
+ if (aclblk->rules[i] && aclblk->rules[i]->tag == tag &&
+ aclblk->rules[i]->type == type)
+ return YT921X_ACL_ENT_PER_BLK * blkid + i;
+ }
+
+ return UINT_MAX;
+}
+
+static unsigned int
+yt921x_acl_reserve(struct yt921x_priv *priv, unsigned int entscnt,
+ struct netlink_ext_ack *extack)
+{
+ int candidates[YT921X_ACL_ENT_PER_BLK + 1];
+ unsigned int acl_used_cnt = 0;
+
+ if (WARN_ON(entscnt > YT921X_ACL_ENT_PER_BLK))
+ return UINT_MAX;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(candidates); i++)
+ candidates[i] = -1;
+ for (unsigned int i = YT921X_ACL_BLK_NUM; i-- > 0;) {
+ unsigned int blk_used_cnt = hweight8(priv->acl_masks[i]);
+
+ candidates[blk_used_cnt] = i;
+ acl_used_cnt += blk_used_cnt;
+ }
+
+ if (acl_used_cnt >= YT921X_ACL_NUM) {
+ NL_SET_ERR_MSG_MOD(extack, "ACL entry limit reached");
+ return UINT_MAX;
+ }
+ if (acl_used_cnt + entscnt <= YT921X_ACL_NUM)
+ for (unsigned int i = YT921X_ACL_ENT_PER_BLK - entscnt + 1;
+ i-- > 0;)
+ if (candidates[i] >= 0)
+ return YT921X_ACL_ENT_PER_BLK * candidates[i] +
+ ffz(priv->acl_masks[candidates[i]]);
+
+ NL_SET_ERR_MSG_MOD(extack,
+ "ACL entry allocation failed, simplify your rules or remove existing rules");
+ return UINT_MAX;
+}
+
+static int
+yt921x_acl_commit(struct yt921x_priv *priv, unsigned int entid, u8 entsmask)
+{
+ const struct yt921x_acl_rule *aclrule;
+ const struct yt921x_acl_blk *aclblk;
+ unsigned int blkid;
+ unsigned int binid;
+ unsigned long mask;
+ u32 zeros[3] = {};
+ unsigned int i;
+ unsigned int o;
+ u32 ctrl;
+ int res;
+
+ blkid = entid / YT921X_ACL_ENT_PER_BLK;
+ binid = entid % YT921X_ACL_ENT_PER_BLK;
+ aclblk = priv->acl_blks[blkid];
+ aclrule = aclblk->rules[binid];
+
+ /* Write actions */
+ res = yt921x_reg96_write(priv, YT921X_ACLn_ACT(entid),
+ aclrule ? aclrule->action : zeros);
+ if (res)
+ return res;
+
+ /* Select the block */
+ ctrl = YT921X_ACL_BLK_CMD_MODIFY | YT921X_ACL_BLK_CMD_BLKID(blkid);
+ res = yt921x_reg_write(priv, YT921X_ACL_BLK_CMD, ctrl);
+ if (res)
+ return res;
+
+ /* Write keys and masks */
+ ctrl = 0;
+ for (unsigned int i = 0; i < YT921X_ACL_ENT_PER_BLK; i++)
+ ctrl |= YT921X_ACL_BLK_KEEP_KEEPn(i);
+
+ mask = entsmask;
+ i = 0;
+ for_each_set_bit(o, &mask, YT921X_ACL_ENT_PER_BLK) {
+ res = yt921x_reg64_write(priv, YT921X_ACLn_KEYm(blkid, o),
+ aclrule ? aclrule->entries[i].key :
+ zeros);
+ if (res)
+ return res;
+
+ res = yt921x_reg64_write(priv, YT921X_ACLn_MASKm(blkid, o),
+ aclrule ? aclrule->entries[i].mask :
+ zeros);
+ if (res)
+ return res;
+
+ ctrl &= ~YT921X_ACL_BLK_KEEP_KEEPn(o);
+ i++;
+ }
+
+ res = yt921x_reg_write(priv, YT921X_ACL_BLK_KEEP, ctrl);
+ if (res)
+ return res;
+
+ ctrl = 0;
+ for (unsigned int i = 0; i < YT921X_ACL_ENT_PER_BLK; i++) {
+ const struct yt921x_acl_rule *other = aclblk->rules[i];
+
+ if (!other)
+ continue;
+
+ mask = other->mask;
+ for_each_set_bit(o, &mask, YT921X_ACL_ENT_PER_BLK)
+ ctrl |= YT921X_ACL_ENTRY_ENm(o) |
+ YT921X_ACL_ENTRY_GRPIDm(o, i);
+ }
+ res = yt921x_reg_write(priv, YT921X_ACLn_ENTRY(blkid), ctrl);
+ if (res)
+ return res;
+
+ /* Commit the block */
+ ctrl = YT921X_ACL_BLK_CMD_BLKID(blkid);
+ res = yt921x_reg_write(priv, YT921X_ACL_BLK_CMD, ctrl);
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static int
+yt921x_acl_del(struct yt921x_priv *priv, enum tc_setup_type type,
+ unsigned long tag)
+{
+ struct yt921x_acl_rule *aclrule;
+ struct yt921x_acl_blk *aclblk;
+ unsigned int binid;
+ unsigned int blkid;
+ unsigned int entid;
+ int res;
+
+ entid = yt921x_acl_find(priv, type, tag);
+ if (entid == UINT_MAX)
+ return -ENOENT;
+
+ blkid = entid / YT921X_ACL_ENT_PER_BLK;
+ binid = entid % YT921X_ACL_ENT_PER_BLK;
+ aclblk = priv->acl_blks[blkid];
+ aclrule = aclblk->rules[binid];
+
+ aclblk->rules[binid] = NULL;
+ res = yt921x_acl_commit(priv, entid, aclrule->mask);
+ /* the kernel never rolls back on failure */
+
+ if (aclrule->action[0] & YT921X_ACL_ACTa_METER_EN)
+ clear_bit(FIELD_GET(YT921X_ACL_ACTa_METER_ID_M,
+ aclrule->action[0]),
+ priv->meters_map);
+ priv->acl_masks[blkid] &= ~aclrule->mask;
+ kvfree(aclrule);
+ if (!priv->acl_masks[blkid]) {
+ kvfree(aclblk);
+ priv->acl_blks[blkid] = NULL;
+ }
+ return res;
+}
+
+static int
+yt921x_acl_add(struct yt921x_priv *priv,
+ const struct yt921x_acl_rule_ext *ruleext,
+ struct netlink_ext_ack *extack)
+{
+ unsigned int entscnt = hweight8(ruleext->r.mask);
+ struct yt921x_acl_rule *aclrule;
+ struct yt921x_acl_blk *aclblk;
+ bool use_trap = false;
+ unsigned int meterid;
+ unsigned long mask;
+ unsigned int binid;
+ unsigned int blkid;
+ unsigned int entid;
+ unsigned int o;
+ int res;
+
+ /* Allocate resources */
+ entid = yt921x_acl_reserve(priv, entscnt, extack);
+ if (entid == UINT_MAX)
+ return -EOPNOTSUPP;
+
+ if (!(ruleext->r.action[0] & YT921X_ACL_ACTa_METER_EN)) {
+ meterid = YT921X_METER_NUM;
+ } else {
+ meterid = find_first_zero_bit(priv->meters_map,
+ YT921X_METER_NUM);
+ if (meterid < YT921X_METER_NUM) {
+ res = yt921x_meter_config(priv, meterid,
+ &ruleext->marker);
+ if (res)
+ return res;
+ } else if (ruleext->r.sw_assisted) {
+ use_trap = true;
+ } else {
+ NL_SET_ERR_MSG_MOD(extack,
+ "No more meters available");
+ return -EOPNOTSUPP;
+ }
+ }
+
+ /* Prepare acl block ctrlblk */
+ blkid = entid / YT921X_ACL_ENT_PER_BLK;
+ binid = entid % YT921X_ACL_ENT_PER_BLK;
+ aclblk = priv->acl_blks[blkid];
+ if (!aclblk) {
+ aclblk = kvzalloc_obj(*aclblk);
+ if (!aclblk)
+ return -ENOMEM;
+ priv->acl_blks[blkid] = aclblk;
+ }
+
+ /* Prepare acl rule ctrlblk */
+ aclrule = kvmemdup(&ruleext->r,
+ offsetof(struct yt921x_acl_rule, entries[entscnt]),
+ GFP_KERNEL);
+ if (!aclrule) {
+ res = -ENOMEM;
+ goto err;
+ }
+
+ /* Replace the placeholder resource IDs */
+ aclrule->mask = 0;
+ mask = priv->acl_masks[blkid];
+ for_each_clear_bit(o, &mask, YT921X_ACL_ENT_PER_BLK) {
+ aclrule->mask |= BIT(o);
+ entscnt--;
+ if (!entscnt)
+ break;
+ }
+
+ if (use_trap) {
+ aclrule->action[2] &= ~YT921X_ACL_ACTc_FWD_REDIR_DPORTS_M &
+ ~YT921X_ACL_ACTc_FWD_M;
+ aclrule->action[2] |= YT921X_ACL_ACTc_FWD_EN |
+ YT921X_ACL_ACTc_FWD_TRAP;
+ }
+ if (meterid < YT921X_METER_NUM)
+ aclrule->action[0] |= YT921X_ACL_ACTa_METER_ID(meterid);
+ else
+ aclrule->action[0] &= ~YT921X_ACL_ACTa_METER_EN;
+
+ /* Write rules */
+ aclblk->rules[binid] = aclrule;
+ res = yt921x_acl_commit(priv, entid, aclrule->mask);
+ if (res) {
+ aclblk->rules[binid] = NULL;
+ kvfree(aclrule);
+ goto err;
+ }
+
+ if (meterid < YT921X_METER_NUM)
+ set_bit(meterid, priv->meters_map);
+ priv->acl_masks[blkid] |= aclrule->mask;
+ return 0;
+
+err:
+ if (!priv->acl_masks[blkid]) {
+ kvfree(aclblk);
+ priv->acl_blks[blkid] = NULL;
+ }
+ return res;
+}
+
+int
+yt921x_dsa_cls_flower_del(struct dsa_switch *ds, int port,
+ struct flow_cls_offload *cls, bool ingress)
+{
+ struct yt921x_priv *priv = to_yt921x_priv(ds);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_acl_del(priv, TC_SETUP_CLSFLOWER, cls->cookie);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+int
+yt921x_dsa_cls_flower_add(struct dsa_switch *ds, int port,
+ struct flow_cls_offload *cls, bool ingress)
+{
+ struct netlink_ext_ack *extack = cls->common.extack;
+ struct yt921x_priv *priv = to_yt921x_priv(ds);
+ struct yt921x_acl_rule_ext ruleext;
+ int res;
+
+ res = yt921x_acl_rule_ext_parse_flow(&ruleext, port, cls, ingress,
+ priv);
+ if (res)
+ return res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_acl_add(priv, &ruleext, extack);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_mirror_del(struct yt921x_priv *priv, int port, bool ingress)
+{
+ u32 mask;
+
+ if (ingress)
+ mask = YT921X_MIRROR_IGR_PORTn(port);
+ else
+ mask = YT921X_MIRROR_EGR_PORTn(port);
+ return yt921x_reg_clear_bits(priv, YT921X_MIRROR, mask);
+}
+
+static int
+yt921x_mirror_add(struct yt921x_priv *priv, int port, bool ingress,
+ int to_local_port, struct netlink_ext_ack *extack)
+{
+ u32 srcs;
+ u32 ctrl;
+ u32 val;
+ u32 dst;
+ int res;
+
+ if (ingress)
+ srcs = YT921X_MIRROR_IGR_PORTn(port);
+ else
+ srcs = YT921X_MIRROR_EGR_PORTn(port);
+ dst = YT921X_MIRROR_PORT(to_local_port);
+
+ res = yt921x_reg_read(priv, YT921X_MIRROR, &val);
+ if (res)
+ return res;
+
+ /* other mirror tasks & different dst port -> conflict */
+ if ((val & ~srcs & (YT921X_MIRROR_EGR_PORTS_M |
+ YT921X_MIRROR_IGR_PORTS_M)) &&
+ (val & YT921X_MIRROR_PORT_M) != dst) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Sniffer port is already configured, delete existing rules & retry");
+ return -EBUSY;
+ }
+
+ ctrl = val & ~YT921X_MIRROR_PORT_M;
+ ctrl |= srcs;
+ ctrl |= dst;
+
+ if (ctrl == val)
+ return 0;
+
+ return yt921x_reg_write(priv, YT921X_MIRROR, ctrl);
+}
+
+void
+yt921x_dsa_port_mirror_del(struct dsa_switch *ds, int port,
+ struct dsa_mall_mirror_tc_entry *mirror)
+{
+ struct yt921x_priv *priv = to_yt921x_priv(ds);
+ struct device *dev = to_device(priv);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_mirror_del(priv, port, mirror->ingress);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ dev_err(dev, "Failed to %s port %d: %i\n", "unmirror",
+ port, res);
+}
+
+int
+yt921x_dsa_port_mirror_add(struct dsa_switch *ds, int port,
+ struct dsa_mall_mirror_tc_entry *mirror,
+ bool ingress, struct netlink_ext_ack *extack)
+{
+ struct yt921x_priv *priv = to_yt921x_priv(ds);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_mirror_add(priv, port, ingress,
+ mirror->to_local_port, extack);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
diff --git a/drivers/net/dsa/motorcomm/tc.h b/drivers/net/dsa/motorcomm/tc.h
new file mode 100644
index 000000000000..3d4a8a24f8a8
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/tc.h
@@ -0,0 +1,380 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#ifndef _YT_TC_H
+#define _YT_TC_H
+
+#include <net/dsa.h>
+#include <net/pkt_cls.h>
+
+#define YT921X_ACLn_ACT(n) (0x1c0000 + 0x10 * (n))
+#define YT921X_ACL_ACTc_STAG_M GENMASK(26, 25)
+#define YT921X_ACL_ACTc_STAG(x) FIELD_PREP(YT921X_ACL_ACTc_STAG_M, (x))
+#define YT921X_ACL_ACTc_STAG_DONTCARE YT921X_ACL_ACTc_STAG(0)
+#define YT921X_ACL_ACTc_STAG_UNTAG YT921X_ACL_ACTc_STAG(1)
+#define YT921X_ACL_ACTc_STAG_TAG YT921X_ACL_ACTc_STAG(2)
+#define YT921X_ACL_ACTc_STAG_KEEP YT921X_ACL_ACTc_STAG(3)
+#define YT921X_ACL_ACTc_CTAG_M GENMASK(24, 23)
+#define YT921X_ACL_ACTc_CTAG(x) FIELD_PREP(YT921X_ACL_ACTc_CTAG_M, (x))
+#define YT921X_ACL_ACTc_CTAG_DONTCARE YT921X_ACL_ACTc_CTAG(0)
+#define YT921X_ACL_ACTc_CTAG_UNTAG YT921X_ACL_ACTc_CTAG(1)
+#define YT921X_ACL_ACTc_CTAG_TAG YT921X_ACL_ACTc_CTAG(2)
+#define YT921X_ACL_ACTc_CTAG_KEEP YT921X_ACL_ACTc_CTAG(3)
+#define YT921X_ACL_ACTc_FWD_M GENMASK(22, 21)
+#define YT921X_ACL_ACTc_FWD(x) FIELD_PREP(YT921X_ACL_ACTc_FWD_M, (x))
+#define YT921X_ACL_ACTc_FWD_FWD YT921X_ACL_ACTc_FWD(0)
+#define YT921X_ACL_ACTc_FWD_COPY YT921X_ACL_ACTc_FWD(1)
+#define YT921X_ACL_ACTc_FWD_REDIR YT921X_ACL_ACTc_FWD(2)
+#define YT921X_ACL_ACTc_FWD_TRAP YT921X_ACL_ACTc_FWD(3)
+#define YT921X_ACL_ACTc_FWD_REDIR_DPORTS_M GENMASK(20, 10)
+#define YT921X_ACL_ACTc_FWD_REDIR_DPORTS(x) FIELD_PREP(YT921X_ACL_ACTc_FWD_REDIR_DPORTS_M, (x))
+#define YT921X_ACL_ACTc_FWD_REDIR_DPORTn(port) BIT((port) + 10)
+#define YT921X_ACL_ACTc_FWD_EN BIT(9)
+#define YT921X_ACL_ACTc_SDEI BIT(8)
+#define YT921X_ACL_ACTc_SDEI_REPLACE BIT(7)
+#define YT921X_ACL_ACTc_SPRI_M GENMASK(6, 4)
+#define YT921X_ACL_ACTc_SPRI(x) FIELD_PREP(YT921X_ACL_ACTc_SPRI_M, (x))
+#define YT921X_ACL_ACTc_SPRI_REPLACE BIT(3)
+#define YT921X_ACL_ACTbc_SVID_M GENMASK_ULL(34, 23)
+#define YT921X_ACL_ACTbc_SVID(x) FIELD_PREP(YT921X_ACL_ACTbc_SVID_M, (x))
+#define YT921X_ACL_ACTb_SVID_REPLACE BIT(22)
+#define YT921X_ACL_ACTb_CDEI BIT(21)
+#define YT921X_ACL_ACTb_CDEI_REPLACE BIT(20)
+#define YT921X_ACL_ACTb_CPRI_M GENMASK(19, 17)
+#define YT921X_ACL_ACTb_CPRI(x) FIELD_PREP(YT921X_ACL_ACTb_CPRI_M, (x))
+#define YT921X_ACL_ACTb_CPRI_REPLACE BIT(16)
+#define YT921X_ACL_ACTb_CVID_M GENMASK(15, 4)
+#define YT921X_ACL_ACTb_CVID(x) FIELD_PREP(YT921X_ACL_ACTb_CVID_M, (x))
+#define YT921X_ACL_ACTb_CVID_REPLACE BIT(3)
+#define YT921X_ACL_ACTb_PRIO_M GENMASK(2, 0)
+#define YT921X_ACL_ACTb_PRIO(x) FIELD_PREP(YT921X_ACL_ACTb_PRIO_M, (x))
+#define YT921X_ACL_ACTa_PRIO_EN BIT(31)
+#define YT921X_ACL_ACTa_COLOR_M GENMASK(30, 29)
+#define YT921X_ACL_ACTa_COLOR(x) FIELD_PREP(YT921X_ACL_ACTa_COLOR_M, (x))
+#define YT921X_ACL_ACTa_COLOR_GREEN YT921X_ACL_ACTa_COLOR(0)
+#define YT921X_ACL_ACTa_COLOR_YELLOW YT921X_ACL_ACTa_COLOR(1)
+#define YT921X_ACL_ACTa_COLOR_RED YT921X_ACL_ACTa_COLOR(2)
+#define YT921X_ACL_ACTa_COLOR_EN BIT(28)
+#define YT921X_ACL_ACTa_DSCP_M GENMASK(27, 22)
+#define YT921X_ACL_ACTa_DSCP(x) FIELD_PREP(YT921X_ACL_ACTa_DSCP_M, (x))
+#define YT921X_ACL_ACTa_DSCP_REPLACE BIT(21)
+#define YT921X_ACL_ACTa_METER_ID_M GENMASK(20, 15)
+#define YT921X_ACL_ACTa_METER_ID(x) FIELD_PREP(YT921X_ACL_ACTa_METER_ID_M, (x))
+#define YT921X_ACL_ACTa_METER_EN BIT(14)
+#define YT921X_ACL_ACTa_MIRROR_EN BIT(13)
+#define YT921X_ACL_ACTa_FLOWSTAT_EN BIT(12)
+#define YT921X_ACL_ACTa_FLOWSTAT_ID_M GENMASK(11, 6)
+#define YT921X_ACL_ACTa_FLOWSTAT_ID(x) FIELD_PREP(YT921X_ACL_ACTa_FLOWSTAT_ID_M, (x))
+#define YT921X_ACL_ACTa_GPIO_EN BIT(5)
+#define YT921X_ACL_ACTa_GPIO_PIN_M GENMASK(4, 1)
+#define YT921X_ACL_ACTa_GPIO_PIN(x) FIELD_PREP(YT921X_ACL_ACTa_GPIO_PIN_M, (x))
+#define YT921X_ACL_ACTa_INTR_EN BIT(0)
+#define YT921X_ACL_BLK_KEEP 0x201000
+#define YT921X_ACL_BLK_KEEP_GRPIDn_M(bin) (7 << (4 * (bin) + 1))
+#define YT921X_ACL_BLK_KEEP_GRPIDn(bin, x) ((x) << (4 * (bin) + 1))
+#define YT921X_ACL_BLK_KEEP_KEEPn(bin) BIT(4 * (bin))
+#define YT921X_ACL_PORT 0x202000
+#define YT921X_ACL_PORT_PORTS_M GENMASK(10, 0)
+#define YT921X_ACL_PORT_PORTS(x) FIELD_PREP(YT921X_ACL_PORT_PORTS_M, (x))
+#define YT921X_ACL_PORT_PORTn(port) BIT(port)
+#define YT921X_ACL_BLK_CMD 0x202004
+#define YT921X_ACL_BLK_CMD_BLKID_M GENMASK(6, 1)
+#define YT921X_ACL_BLK_CMD_BLKID(x) FIELD_PREP(YT921X_ACL_BLK_CMD_BLKID_M, (x))
+#define YT921X_ACL_BLK_CMD_MODIFY BIT(0)
+#define YT921X_ACLn_ENTRY(blk) (0x203000 + 4 * (blk))
+#define YT921X_ACL_ENTRY_GRPIDm_M(bin) (7 << (4 * (bin) + 1))
+#define YT921X_ACL_ENTRY_GRPIDm(bin, x) ((x) << (4 * (bin) + 1))
+#define YT921X_ACL_ENTRY_ENm(bin) BIT(4 * (bin))
+#define YT921X_ACLn_KEYm(blk, bin) (0x204000 + 0x200 * (bin) + 8 * (blk))
+#define YT921X_ACL_KEYb_ORD_M GENMASK(29, 21)
+#define YT921X_ACL_KEYb_ORD(x) FIELD_PREP(YT921X_ACL_KEYb_ORD_M, (x))
+#define YT921X_ACL_KEYb_SPORTS_M GENMASK(20, 10)
+#define YT921X_ACL_KEYb_SPORTS(x) FIELD_PREP(YT921X_ACL_KEYb_SPORTS_M, (x))
+#define YT921X_ACL_KEYb_SPORTn(port) BIT((port) + 10)
+#define YT921X_ACL_KEYb_REVERSE BIT(9) /* reverse match */
+#define YT921X_ACL_KEYb_TYPE_M GENMASK(8, 4)
+#define YT921X_ACL_KEYb_TYPE(x) FIELD_PREP(YT921X_ACL_KEYb_TYPE_M, (x))
+/* KEY_* fields need no masks */
+#define YT921X_ACLn_MASKm(blk, bin) (0x205000 + 0x200 * (bin) + 8 * (blk))
+
+enum yt921x_acl_type {
+ YT921X_ACL_TYPE_NA,
+ YT921X_ACL_TYPE_MAC_DA0,
+ YT921X_ACL_TYPE_MAC_SA0,
+ YT921X_ACL_TYPE_MAC_DA1_SA1,
+ YT921X_ACL_TYPE_VLAN,
+ YT921X_ACL_TYPE_VTAG,
+ YT921X_ACL_TYPE_IPV4_DA,
+ YT921X_ACL_TYPE_IPV4_SA,
+ YT921X_ACL_TYPE_IPV6_DA0,
+ YT921X_ACL_TYPE_IPV6_DA1,
+ YT921X_ACL_TYPE_IPV6_DA2,
+ YT921X_ACL_TYPE_IPV6_DA3,
+ YT921X_ACL_TYPE_IPV6_SA0,
+ YT921X_ACL_TYPE_IPV6_SA1,
+ YT921X_ACL_TYPE_IPV6_SA2,
+ YT921X_ACL_TYPE_IPV6_SA3,
+ YT921X_ACL_TYPE_MISC,
+ YT921X_ACL_TYPE_L4,
+ YT921X_ACL_TYPE_UDF0,
+ YT921X_ACL_TYPE_UDF1,
+ YT921X_ACL_TYPE_UDF2,
+ YT921X_ACL_TYPE_UDF3,
+ YT921X_ACL_TYPE_UDF4,
+ YT921X_ACL_TYPE_UDF5,
+ YT921X_ACL_TYPE_UDF6,
+ YT921X_ACL_TYPE_UDF7,
+ YT921X_ACL_TYPE_ETHERTYPE,
+ YT921X_ACL_TYPE_NUM
+};
+
+/* Range: turn KEY:MASK into MIN:MAX */
+
+#define YT921X_ACL_BINb_MAC_xA0_L3_TYPE_M GENMASK(3, 0)
+#define YT921X_ACL_BINb_MAC_xA0_L3_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_MAC_xA0_L3_TYPE_M, (x))
+#define YT921X_ACL_BINa_MAC_xA0_MAC_xA0_M GENMASK(31, 0)
+
+#define YT921X_ACL_BINb_MAC_DA1_SA1_L2_TYPE_M GENMASK(2, 0)
+#define YT921X_ACL_BINb_MAC_DA1_SA1_L2_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_MAC_DA1_SA1_L2_TYPE_M, (x))
+#define YT921X_ACL_BINa_MAC_DA1_SA1_MAC_DA1_M GENMASK(31, 16)
+#define YT921X_ACL_BINa_MAC_DA1_SA1_MAC_SA1_M GENMASK(15, 0)
+
+#define YT921X_ACL_KEYb_VLAN_SVID_RANGE_EN BIT(31)
+#define YT921X_ACL_KEYb_VLAN_CVID_RANGE_EN BIT(30)
+#define YT921X_ACL_BINb_VLAN_CDEI BIT(3)
+#define YT921X_ACL_BINb_VLAN_CPRI_M GENMASK(2, 0)
+#define YT921X_ACL_BINb_VLAN_CPRI(x) FIELD_PREP(YT921X_ACL_BINb_VLAN_CPRI_M, (x))
+#define YT921X_ACL_BINa_VLAN_CTAG_FMT_M GENMASK(31, 30)
+#define YT921X_ACL_BINa_VLAN_CTAG_FMT(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_CTAG_FMT_M, (x))
+#define YT921X_ACL_BINa_VLAN_SDEI BIT(29)
+#define YT921X_ACL_BINa_VLAN_SPRI_M GENMASK(28, 26)
+#define YT921X_ACL_BINa_VLAN_SPRI(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_SPRI_M, (x))
+#define YT921X_ACL_BINa_VLAN_STAG_FMT_M GENMASK(25, 24)
+#define YT921X_ACL_BINa_VLAN_STAG_FMT(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_STAG_FMT_M, (x))
+#define YT921X_ACL_BINa_VLAN_SVID_M GENMASK(23, 12)
+#define YT921X_ACL_BINa_VLAN_SVID(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_SVID_M, (x))
+#define YT921X_ACL_BINa_VLAN_CVID_M GENMASK(11, 0)
+#define YT921X_ACL_BINa_VLAN_CVID(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_CVID_M, (x))
+
+#define YT921X_ACL_KEYb_VTAG_SVID_RANGE_EN BIT(31)
+#define YT921X_ACL_KEYb_VTAG_CVID_RANGE_EN BIT(30)
+#define YT921X_ACL_BINa_VTAG_CDEI BIT(31)
+#define YT921X_ACL_BINa_VTAG_CPRI_M GENMASK(30, 28)
+#define YT921X_ACL_BINa_VTAG_CPRI(x) FIELD_PREP(YT921X_ACL_BINa_VTAG_CPRI_M, (x))
+#define YT921X_ACL_BINa_VTAG_SDEI BIT(27)
+#define YT921X_ACL_BINa_VTAG_SPRI_M GENMASK(26, 24)
+#define YT921X_ACL_BINa_VTAG_SPRI(x) FIELD_PREP(YT921X_ACL_BINa_VTAG_SPRI_M, (x))
+#define YT921X_ACL_BINa_VTAG_SVID_M GENMASK(23, 12)
+#define YT921X_ACL_BINa_VTAG_SVID(x) FIELD_PREP(YT921X_ACL_BINa_VTAG_SVID_M, (x))
+#define YT921X_ACL_BINa_VTAG_CVID_M GENMASK(11, 0)
+#define YT921X_ACL_BINa_VTAG_CVID(x) FIELD_PREP(YT921X_ACL_BINa_VTAG_CVID_M, (x))
+
+#define YT921X_ACL_KEYb_IPV4_ADDR_RANGE_EN BIT(30)
+#define YT921X_ACL_BINb_IPV4_FRAG BIT(3)
+#define YT921X_ACL_BINb_IPV4_L4_TYPE_M GENMASK(2, 0)
+#define YT921X_ACL_BINb_IPV4_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_IPV4_L4_TYPE_M, (x))
+#define YT921X_ACL_BINa_IPV4_ADDR_M GENMASK(31, 0)
+
+#define YT921X_ACL_BINb_IPV6_L4_TYPE_M GENMASK(2, 0)
+#define YT921X_ACL_BINb_IPV6_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_IPV6_L4_TYPE_M, (x))
+#define YT921X_ACL_BINa_IPV6_ADDRx_M GENMASK(31, 0)
+
+#define YT921X_ACL_BINb_IPV6_xA1_IP_OPTION BIT(3)
+
+#define YT921X_ACL_BINb_IPV6_xA2_FIRST_FRAG BIT(3)
+
+#define YT921X_ACL_KEYb_IPV6_xA3_ADDR_RANGE_EN BIT(30)
+#define YT921X_ACL_BINb_IPV6_xA3_FRAG BIT(3)
+
+#define YT921X_ACL_BINb_MISC_FRAG BIT(3)
+#define YT921X_ACL_BINb_MISC_L4_TYPE_M GENMASK(2, 0)
+#define YT921X_ACL_BINb_MISC_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_MISC_L4_TYPE_M, (x))
+#define YT921X_ACL_BINa_MISC_PPPOE_FLAG BIT(30)
+#define YT921X_ACL_BINa_MISC_FIRST_FRAG BIT(29)
+#define YT921X_ACL_BINa_MISC_IP_OPTION BIT(28)
+#define YT921X_ACL_BINa_MISC_TCP_FLAGS_M GENMASK(27, 20)
+#define YT921X_ACL_BINa_MISC_TCP_FLAGS(x) FIELD_PREP(YT921X_ACL_BINa_MISC_TCP_FLAGS_M, (x))
+#define YT921X_ACL_BINa_MISC_IP_PROTO_M GENMASK(19, 12)
+#define YT921X_ACL_BINa_MISC_IP_PROTO(x) FIELD_PREP(YT921X_ACL_BINa_MISC_IP_PROTO_M, (x))
+#define YT921X_ACL_BINa_MISC_TOS_M GENMASK(11, 4)
+#define YT921X_ACL_BINa_MISC_TOS(x) FIELD_PREP(YT921X_ACL_BINa_MISC_TOS_M, (x))
+#define YT921X_ACL_BINa_MISC_L3_TYPE_M GENMASK(3, 0)
+#define YT921X_ACL_BINa_MISC_L3_TYPE(x) FIELD_PREP(YT921X_ACL_BINa_MISC_L3_TYPE_M, (x))
+
+#define YT921X_ACL_KEYb_L4_DPORT_RANGE_EN BIT(31)
+#define YT921X_ACL_KEYb_L4_SPORT_RANGE_EN BIT(30)
+#define YT921X_ACL_BINb_L4_FRAG BIT(3)
+#define YT921X_ACL_BINb_L4_TYPE_M GENMASK(2, 0)
+#define YT921X_ACL_BINb_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_L4_TYPE_M, (x))
+#define YT921X_ACL_BINa_L4_DPORT_M GENMASK(31, 16)
+#define YT921X_ACL_BINa_L4_SPORT_M GENMASK(15, 0)
+
+#define YT921X_ACL_BINb_UDF_IS_IGMP BIT(0)
+#define YT921X_ACL_BINa_UDF_UDF0_M GENMASK(31, 16)
+#define YT921X_ACL_BINa_UDF_UDF0(x) FIELD_PREP(YT921X_ACL_BINa_UDF_UDF0_M, (x))
+#define YT921X_ACL_BINa_UDF_UDF1_M GENMASK(15, 0)
+#define YT921X_ACL_BINa_UDF_UDF1(x) FIELD_PREP(YT921X_ACL_BINa_UDF_UDF1_M, (x))
+
+#define YT921X_ACL_KEYb_ETHERTYPE_ETHERTYPE_RANGE_EN BIT(30)
+#define YT921X_ACL_BINb_ETHERTYPE_L4_TYPE_M GENMASK(2, 0)
+#define YT921X_ACL_BINb_ETHERTYPE_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_ETHERTYPE_L4_TYPE_M, (x))
+#define YT921X_ACL_BINa_ETHERTYPE_ETHERTYPE_M GENMASK(15, 0)
+#define YT921X_ACL_BINa_ETHERTYPE_ETHERTYPE(x) FIELD_PREP(YT921X_ACL_BINa_ETHERTYPE_ETHERTYPE_M, (x))
+
+enum yt921x_l2_type {
+ YT921X_L2_TYPE_ETH,
+ YT921X_L2_TYPE_ETHV2,
+ YT921X_L2_TYPE_ETHSAP,
+ YT921X_L2_TYPE_ETHSNAP,
+};
+
+enum yt921x_l3_type {
+ YT921X_L3_TYPE_OTHER,
+ YT921X_L3_TYPE_IPV4,
+ YT921X_L3_TYPE_IPV6,
+ YT921X_L3_TYPE_ARP,
+ YT921X_L3_TYPE_LLDP,
+ YT921X_L3_TYPE_PAE,
+ YT921X_L3_TYPE_ERP,
+ YT921X_L3_TYPE_SLOW_PROTOCOL,
+};
+
+enum yt921x_l4_type {
+ YT921X_L4_TYPE_OTHER,
+ YT921X_L4_TYPE_TCP,
+ YT921X_L4_TYPE_UDP,
+ YT921X_L4_TYPE_UDPLITE,
+ YT921X_L4_TYPE_ICMP,
+ YT921X_L4_TYPE_IGMP,
+ YT921X_L4_TYPE_MLD,
+ YT921X_L4_TYPE_ND,
+};
+
+#define YT921X_UDFn_CTRL(x) (0x210094 + 4 * (x))
+#define YT921X_UDF_CTRL_UDF_TYPE_M GENMASK(8, 7)
+#define YT921X_UDF_CTRL_UDF_TYPE(x) FIELD_PREP(YT921X_UDF_CTRL_UDF_TYPE_M, (x))
+#define YT921X_UDF_CTRL_UDF_TYPE_ETH YT921X_UDF_CTRL_UDF_TYPE(0)
+#define YT921X_UDF_CTRL_UDF_TYPE_L3 YT921X_UDF_CTRL_UDF_TYPE(1)
+#define YT921X_UDF_CTRL_UDF_TYPE_L4 YT921X_UDF_CTRL_UDF_TYPE(2)
+#define YT921X_UDF_CTRL_UDF_OFFSET_M GENMASK(6, 0)
+#define YT921X_UDF_CTRL_UDF_OFFSET(x) FIELD_PREP(YT921X_UDF_CTRL_UDF_OFFSET_M, (x))
+
+#define YT921X_PORTn_RATE(port) (0x220000 + 4 * (port))
+#define YT921X_PORT_RATE_GAP_VALUE GENMASK(4, 0) /* default 20 */
+#define YT921X_METER_SLOT 0x220104
+#define YT921X_METER_SLOT_SLOT_M GENMASK(11, 0)
+#define YT921X_PORTn_METER(port) (0x220108 + 4 * (port))
+#define YT921X_PORT_METER_EN BIT(4)
+#define YT921X_PORT_METER_ID_M GENMASK(3, 0)
+#define YT921X_PORT_METER_ID(x) FIELD_PREP(YT921X_PORT_METER_ID_M, (x))
+#define YT921X_METERn_CTRL(x) (0x220800 + 0x10 * (x))
+#define YT921X_METER_CTRLc_METER_EN BIT(14)
+#define YT921X_METER_CTRLc_TOKEN_OVERFLOW_EN BIT(13) /* RFC4115: yellow use unused green bw */
+#define YT921X_METER_CTRLc_DROP_M GENMASK(12, 11)
+#define YT921X_METER_CTRLc_DROP(x) FIELD_PREP(YT921X_METER_CTRLc_DROP_M, (x))
+#define YT921X_METER_CTRLc_DROP_GYR YT921X_METER_CTRLc_DROP(0)
+#define YT921X_METER_CTRLc_DROP_YR YT921X_METER_CTRLc_DROP(1)
+#define YT921X_METER_CTRLc_DROP_R YT921X_METER_CTRLc_DROP(2)
+#define YT921X_METER_CTRLc_DROP_NONE YT921X_METER_CTRLc_DROP(3)
+#define YT921X_METER_CTRLc_COLOR_BLIND BIT(10)
+#define YT921X_METER_CTRLc_UNIT_M GENMASK(9, 7)
+#define YT921X_METER_CTRLc_UNIT(x) FIELD_PREP(YT921X_METER_CTRLc_UNIT_M, (x))
+#define YT921X_METER_CTRLc_BYTE_MODE_INCLUDE_GAP BIT(6) /* +GAP_VALUE bytes each packet */
+#define YT921X_METER_CTRLc_PKT_MODE BIT(5) /* 0: byte rate mode */
+#define YT921X_METER_CTRLc_RFC2698 BIT(4) /* 0: RFC4115 */
+#define YT921X_METER_CTRLbc_CBS_M GENMASK_ULL(35, 20)
+#define YT921X_METER_CTRLbc_CBS(x) FIELD_PREP(YT921X_METER_CTRLbc_CBS_M, (x))
+#define YT921X_METER_CTRLb_CIR_M GENMASK(19, 2)
+#define YT921X_METER_CTRLb_CIR(x) FIELD_PREP(YT921X_METER_CTRLb_CIR_M, (x))
+#define YT921X_METER_CTRLab_EBS_M GENMASK_ULL(33, 18)
+#define YT921X_METER_CTRLab_EBS(x) FIELD_PREP(YT921X_METER_CTRLab_EBS_M, (x))
+#define YT921X_METER_CTRLa_EIR_M GENMASK(17, 0)
+#define YT921X_METER_CTRLa_EIR(x) FIELD_PREP(YT921X_METER_CTRLa_EIR_M, (x))
+#define YT921X_METERn_STAT(x) (0x221000 + 8 * (x))
+
+#define YT921X_MIRROR 0x300300
+#define YT921X_MIRROR_IGR_PORTS_M GENMASK(26, 16)
+#define YT921X_MIRROR_IGR_PORTS(x) FIELD_PREP(YT921X_MIRROR_IGR_PORTS_M, (x))
+#define YT921X_MIRROR_IGR_PORTn(port) BIT((port) + 16)
+#define YT921X_MIRROR_EGR_PORTS_M GENMASK(14, 4)
+#define YT921X_MIRROR_EGR_PORTS(x) FIELD_PREP(YT921X_MIRROR_EGR_PORTS_M, (x))
+#define YT921X_MIRROR_EGR_PORTn(port) BIT((port) + 4)
+#define YT921X_MIRROR_PORT_M GENMASK(3, 0)
+#define YT921X_MIRROR_PORT(x) FIELD_PREP(YT921X_MIRROR_PORT_M, (x))
+
+#define YT921X_PORT_SHAPE_SLOT 0x34000c
+#define YT921X_PORT_SHAPE_SLOT_SLOT_M GENMASK(11, 0)
+#define YT921X_PORTn_SHAPE_CTRL(port) (0x354000 + 8 * (port))
+#define YT921X_PORT_SHAPE_CTRLb_EN BIT(4)
+#define YT921X_PORT_SHAPE_CTRLb_PKT_MODE BIT(3) /* 0: byte rate mode */
+#define YT921X_PORT_SHAPE_CTRLb_UNIT_M GENMASK(2, 0)
+#define YT921X_PORT_SHAPE_CTRLb_UNIT(x) FIELD_PREP(YT921X_PORT_SHAPE_CTRLb_UNIT_M, (x))
+#define YT921X_PORT_SHAPE_CTRLa_CBS_M GENMASK(31, 18)
+#define YT921X_PORT_SHAPE_CTRLa_CBS(x) FIELD_PREP(YT921X_PORT_SHAPE_CTRLa_CBS_M, (x))
+#define YT921X_PORT_SHAPE_CTRLa_CIR_M GENMASK(17, 0)
+#define YT921X_PORT_SHAPE_CTRLa_CIR(x) FIELD_PREP(YT921X_PORT_SHAPE_CTRLa_CIR_M, (x))
+#define YT921X_PORTn_SHAPE_STAT(port) (0x356000 + 4 * (port))
+
+#define YT921X_TOKEN_BYTE_C 1 /* 1 token = 2^1 byte */
+#define YT921X_TOKEN_PKT_C -6 /* 1 token = 2^-6 packets */
+#define YT921X_TOKEN_RATE_C -15
+/* Custom meters only, not including dedicated port meters (11) */
+#define YT921X_METER_SLOT_MIN 80
+#define YT921X_METER_UNIT_MAX ((1 << 3) - 1)
+#define YT921X_METER_CIR_MAX ((1 << 18) - 1)
+#define YT921X_METER_CBS_MAX ((1 << 16) - 1)
+#define YT921X_PORT_SHAPE_SLOT_MIN 80
+#define YT921X_SHAPE_UNIT_MAX ((1 << 3) - 1)
+#define YT921X_SHAPE_CIR_MAX ((1 << 18) - 1)
+#define YT921X_SHAPE_CBS_MAX ((1 << 14) - 1)
+
+#define YT921X_UDF_NUM 8
+
+struct yt921x_acl_entry {
+ u32 key[2];
+ u32 mask[2];
+};
+
+struct yt921x_acl_rule {
+ unsigned long tag;
+ enum tc_setup_type type;
+
+ u32 action[3];
+ bool sw_assisted;
+
+ u8 mask;
+ struct yt921x_acl_entry entries[YT921X_ACL_ENT_PER_BLK];
+};
+
+struct yt921x_acl_blk {
+ struct yt921x_acl_rule *rules[YT921X_ACL_ENT_PER_BLK];
+};
+
+void yt921x_dsa_port_policer_del(struct dsa_switch *ds, int port);
+int
+yt921x_dsa_port_policer_add(struct dsa_switch *ds, int port,
+ const struct flow_action_police *police,
+ struct netlink_ext_ack *extack);
+int
+yt921x_dsa_port_setup_tc_tbf_port(struct dsa_switch *ds, int port,
+ const struct tc_tbf_qopt_offload *qopt);
+int
+yt921x_dsa_port_setup_tc(struct dsa_switch *ds, int port,
+ enum tc_setup_type type, void *type_data);
+int
+yt921x_dsa_cls_flower_del(struct dsa_switch *ds, int port,
+ struct flow_cls_offload *cls, bool ingress);
+int
+yt921x_dsa_cls_flower_add(struct dsa_switch *ds, int port,
+ struct flow_cls_offload *cls, bool ingress);
+void
+yt921x_dsa_port_mirror_del(struct dsa_switch *ds, int port,
+ struct dsa_mall_mirror_tc_entry *mirror);
+int
+yt921x_dsa_port_mirror_add(struct dsa_switch *ds, int port,
+ struct dsa_mall_mirror_tc_entry *mirror,
+ bool ingress, struct netlink_ext_ack *extack);
+
+#endif
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net-next 2/6] net: dsa: motorcomm: Use NSEC_PER_SEC
2026-09-26 21:17 [PATCH net-next 0/6] net: dsa: motorcomm: TC offload follow-ups David Yang
2026-09-26 21:17 ` [PATCH net-next 1/6] net: dsa: motorcomm: Split TC module David Yang
@ 2026-09-26 21:17 ` David Yang
2026-09-26 21:17 ` [PATCH net-next 3/6] net: dsa: motorcomm: Enable ACL on demand David Yang
` (3 subsequent siblings)
5 siblings, 0 replies; 12+ messages in thread
From: David Yang @ 2026-09-26 21:17 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
Get rid of magic numbers.
Signed-off-by: David Yang <mmyangfl@gmail.com>
---
drivers/net/dsa/motorcomm/tc.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/net/dsa/motorcomm/tc.c b/drivers/net/dsa/motorcomm/tc.c
index 38dd4ec60bbc..7717f1e17923 100644
--- a/drivers/net/dsa/motorcomm/tc.c
+++ b/drivers/net/dsa/motorcomm/tc.c
@@ -28,14 +28,14 @@ static u32 rate2token(u64 rate, unsigned int slot_ns, int unit, int C)
{
int e = 2 * unit + C + YT921X_TOKEN_RATE_C;
- return div_u64(ldexpu64(slot_ns * rate, -e), 1000000000);
+ return div_u64(ldexpu64(slot_ns * rate, -e), NSEC_PER_SEC);
}
static u64 token2rate(u32 token, unsigned int slot_ns, int unit, int C)
{
int e = 2 * unit + C + YT921X_TOKEN_RATE_C;
- return div_u64(ldexpu64(mul_u32_u32(1000000000, token), e), slot_ns);
+ return div_u64(ldexpu64(mul_u32_u32(NSEC_PER_SEC, token), e), slot_ns);
}
/* burst = 2^C * token * 4^unit */
@@ -86,7 +86,7 @@ yt921x_marker_tfm(struct yt921x_marker *marker, u64 rate, u64 burst,
}
/* Check for matching burst */
- burst_est = div_u64(slot_ns * rate, 1000000000);
+ burst_est = div_u64(slot_ns * rate, NSEC_PER_SEC);
burst_sug = burst_est;
if (flags & YT921X_MARKER_PKT_MODE)
burst_sug++;
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net-next 3/6] net: dsa: motorcomm: Enable ACL on demand
2026-09-26 21:17 [PATCH net-next 0/6] net: dsa: motorcomm: TC offload follow-ups David Yang
2026-09-26 21:17 ` [PATCH net-next 1/6] net: dsa: motorcomm: Split TC module David Yang
2026-09-26 21:17 ` [PATCH net-next 2/6] net: dsa: motorcomm: Use NSEC_PER_SEC David Yang
@ 2026-09-26 21:17 ` David Yang
2026-09-30 3:19 ` netdev-bot+sashiko
2026-09-26 21:17 ` [PATCH net-next 4/6] net: dsa: motorcomm: Add TC prio support David Yang
` (2 subsequent siblings)
5 siblings, 1 reply; 12+ messages in thread
From: David Yang @ 2026-09-26 21:17 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
Track the usage of ACL rules and enable it per port only when it is
actually used.
Signed-off-by: David Yang <mmyangfl@gmail.com>
---
drivers/net/dsa/motorcomm/chip.c | 3 +-
drivers/net/dsa/motorcomm/chip.h | 2 ++
drivers/net/dsa/motorcomm/tc.c | 61 +++++++++++++++++++++++++++++---
3 files changed, 59 insertions(+), 7 deletions(-)
diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index e9d3058462e5..be48ecc3e3df 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -2689,8 +2689,7 @@ static int yt921x_chip_setup_acl(struct yt921x_priv *priv)
if (res)
return res;
- ctrl = YT921X_ACL_PORT_PORTS_M;
- res = yt921x_reg_write(priv, YT921X_ACL_PORT, ctrl);
+ res = yt921x_reg_write(priv, YT921X_ACL_PORT, 0);
if (res)
return res;
diff --git a/drivers/net/dsa/motorcomm/chip.h b/drivers/net/dsa/motorcomm/chip.h
index 02419bcde3f2..c191cec94d59 100644
--- a/drivers/net/dsa/motorcomm/chip.h
+++ b/drivers/net/dsa/motorcomm/chip.h
@@ -582,6 +582,8 @@ struct yt921x_port {
u64 rx_frames;
u64 tx_frames;
+ unsigned short acl_cnt;
+
#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
unsigned char led_duty;
unsigned short led_cycle;
diff --git a/drivers/net/dsa/motorcomm/tc.c b/drivers/net/dsa/motorcomm/tc.c
index 7717f1e17923..0f6d7a4675fc 100644
--- a/drivers/net/dsa/motorcomm/tc.c
+++ b/drivers/net/dsa/motorcomm/tc.c
@@ -1058,6 +1058,17 @@ yt921x_acl_rule_ext_parse_flow(struct yt921x_acl_rule_ext *ruleext, int port,
return 0;
}
+static u16 yt921x_acl_activated_ports(const struct yt921x_priv *priv)
+{
+ u16 mask = 0;
+
+ for (int port = 0; port < YT921X_PORT_NUM; port++)
+ if (priv->ports[port].acl_cnt)
+ mask |= BIT(port);
+
+ return mask;
+}
+
static unsigned int
yt921x_acl_find(const struct yt921x_priv *priv, enum tc_setup_type type,
unsigned long tag)
@@ -1202,10 +1213,15 @@ yt921x_acl_del(struct yt921x_priv *priv, enum tc_setup_type type,
{
struct yt921x_acl_rule *aclrule;
struct yt921x_acl_blk *aclblk;
+ bool refresh_en = false;
unsigned int binid;
unsigned int blkid;
unsigned int entid;
+ unsigned long m;
+ unsigned int o;
+ u32 ctrl;
int res;
+ int ret;
entid = yt921x_acl_find(priv, type, tag);
if (entid == UINT_MAX)
@@ -1217,9 +1233,27 @@ yt921x_acl_del(struct yt921x_priv *priv, enum tc_setup_type type,
aclrule = aclblk->rules[binid];
aclblk->rules[binid] = NULL;
- res = yt921x_acl_commit(priv, entid, aclrule->mask);
+ ret = yt921x_acl_commit(priv, entid, aclrule->mask);
/* the kernel never rolls back on failure */
+ m = FIELD_GET(YT921X_ACL_KEYb_SPORTS_M, aclrule->entries[0].key[1]);
+ for_each_set_bit(o, &m, YT921X_PORT_NUM) {
+ struct yt921x_port *pp = &priv->ports[o];
+
+ if (!WARN_ON(!pp->acl_cnt)) {
+ pp->acl_cnt--;
+ if (pp->acl_cnt)
+ continue;
+ }
+ refresh_en = true;
+ }
+ if (refresh_en) {
+ ctrl = yt921x_acl_activated_ports(priv);
+ res = yt921x_reg_write(priv, YT921X_ACL_PORT, ctrl);
+ if (res)
+ ret = res;
+ }
+
if (aclrule->action[0] & YT921X_ACL_ACTa_METER_EN)
clear_bit(FIELD_GET(YT921X_ACL_ACTa_METER_ID_M,
aclrule->action[0]),
@@ -1230,7 +1264,7 @@ yt921x_acl_del(struct yt921x_priv *priv, enum tc_setup_type type,
kvfree(aclblk);
priv->acl_blks[blkid] = NULL;
}
- return res;
+ return ret;
}
static int
@@ -1243,11 +1277,12 @@ yt921x_acl_add(struct yt921x_priv *priv,
struct yt921x_acl_blk *aclblk;
bool use_trap = false;
unsigned int meterid;
- unsigned long mask;
unsigned int binid;
unsigned int blkid;
unsigned int entid;
+ unsigned long m;
unsigned int o;
+ u32 ctrl;
int res;
/* Allocate resources */
@@ -1274,6 +1309,18 @@ yt921x_acl_add(struct yt921x_priv *priv,
}
}
+ ctrl = 0;
+ m = FIELD_GET(YT921X_ACL_KEYb_SPORTS_M, ruleext->r.entries[0].key[1]);
+ for_each_set_bit(o, &m, YT921X_PORT_NUM)
+ if (!priv->ports[o].acl_cnt)
+ ctrl |= YT921X_ACL_PORT_PORTn(o);
+ if (ctrl) {
+ ctrl |= yt921x_acl_activated_ports(priv);
+ res = yt921x_reg_write(priv, YT921X_ACL_PORT, ctrl);
+ if (res)
+ return res;
+ }
+
/* Prepare acl block ctrlblk */
blkid = entid / YT921X_ACL_ENT_PER_BLK;
binid = entid % YT921X_ACL_ENT_PER_BLK;
@@ -1296,8 +1343,8 @@ yt921x_acl_add(struct yt921x_priv *priv,
/* Replace the placeholder resource IDs */
aclrule->mask = 0;
- mask = priv->acl_masks[blkid];
- for_each_clear_bit(o, &mask, YT921X_ACL_ENT_PER_BLK) {
+ m = priv->acl_masks[blkid];
+ for_each_clear_bit(o, &m, YT921X_ACL_ENT_PER_BLK) {
aclrule->mask |= BIT(o);
entscnt--;
if (!entscnt)
@@ -1324,6 +1371,10 @@ yt921x_acl_add(struct yt921x_priv *priv,
goto err;
}
+ m = FIELD_GET(YT921X_ACL_KEYb_SPORTS_M, ruleext->r.entries[0].key[1]);
+ for_each_set_bit(o, &m, YT921X_PORT_NUM)
+ priv->ports[o].acl_cnt++;
+
if (meterid < YT921X_METER_NUM)
set_bit(meterid, priv->meters_map);
priv->acl_masks[blkid] |= aclrule->mask;
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net-next 4/6] net: dsa: motorcomm: Add TC prio support
2026-09-26 21:17 [PATCH net-next 0/6] net: dsa: motorcomm: TC offload follow-ups David Yang
` (2 preceding siblings ...)
2026-09-26 21:17 ` [PATCH net-next 3/6] net: dsa: motorcomm: Enable ACL on demand David Yang
@ 2026-09-26 21:17 ` David Yang
2026-09-30 3:19 ` netdev-bot+sashiko
2026-09-26 21:17 ` [PATCH net-next 5/6] net: dsa: motorcomm: Add limited ACL flow statistics support David Yang
2026-09-26 21:17 ` [PATCH net-next 6/6] net: dsa: motorcomm: Add broadcast/multicast policers via tc police David Yang
5 siblings, 1 reply; 12+ messages in thread
From: David Yang @ 2026-09-26 21:17 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
The HW supports fewer ACL priorities than TC does. Notice the default
priority for flower is 49152 and catchall filter has priority 65535, so
scale the priority accordingly and reject the ones that do not map onto
a hardware priority. Also adjust the register field name.
Signed-off-by: David Yang <mmyangfl@gmail.com>
---
drivers/net/dsa/motorcomm/tc.c | 21 ++++++++++++++++++---
drivers/net/dsa/motorcomm/tc.h | 5 +++--
2 files changed, 21 insertions(+), 5 deletions(-)
diff --git a/drivers/net/dsa/motorcomm/tc.c b/drivers/net/dsa/motorcomm/tc.c
index 0f6d7a4675fc..d919743d89c1 100644
--- a/drivers/net/dsa/motorcomm/tc.c
+++ b/drivers/net/dsa/motorcomm/tc.c
@@ -535,14 +535,14 @@ yt921x_acl_entries_find(struct yt921x_acl_entry *entries, unsigned int *sizep,
}
static void
-yt921x_acl_rule_set_ports(struct yt921x_acl_rule *aclrule, u16 ord,
+yt921x_acl_rule_set_ports(struct yt921x_acl_rule *aclrule, u16 prio,
u16 ports_mask)
{
struct yt921x_acl_entry *entries = aclrule->entries;
for (unsigned int i = 0; i < hweight8(aclrule->mask); i++) {
entries[i].key[1] |= YT921X_ACL_KEYb_SPORTS(ports_mask) |
- YT921X_ACL_KEYb_ORD(ord);
+ YT921X_ACL_KEYb_PRIO(prio);
}
}
@@ -1032,7 +1032,9 @@ yt921x_acl_rule_ext_parse_flow(struct yt921x_acl_rule_ext *ruleext, int port,
const struct flow_cls_offload *cls, bool ingress,
struct yt921x_priv *priv)
{
+ const unsigned int scale = (U16_MAX + 1) / YT921X_ACL_PRIO_NUM;
struct netlink_ext_ack *extack = cls->common.extack;
+ u32 prio = cls->common.prio;
int res;
if (!ingress) {
@@ -1045,6 +1047,15 @@ yt921x_acl_rule_ext_parse_flow(struct yt921x_acl_rule_ext *ruleext, int port,
return -EOPNOTSUPP;
}
+ if (prio == U16_MAX) {
+ prio++;
+ } else if (prio % scale) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "Invalid priority %u, must be a multiple of %u",
+ prio, scale);
+ return -EOPNOTSUPP;
+ }
+
res = yt921x_acl_rule_ext_parse_flow_action(ruleext, cls, priv, port);
if (res)
return res;
@@ -1052,7 +1063,11 @@ yt921x_acl_rule_ext_parse_flow(struct yt921x_acl_rule_ext *ruleext, int port,
if (res)
return res;
- yt921x_acl_rule_set_ports(&ruleext->r, 0, BIT(port));
+ /* TC prio can't be 0 & TC lowest first -> HW highest first
+ * map 1...ACL_PRIO_NUM to (ACL_PRIO_NUM - 1)...0
+ */
+ prio = YT921X_ACL_PRIO_NUM - prio / scale;
+ yt921x_acl_rule_set_ports(&ruleext->r, prio, BIT(port));
ruleext->r.tag = cls->cookie;
ruleext->r.type = TC_SETUP_CLSFLOWER;
return 0;
diff --git a/drivers/net/dsa/motorcomm/tc.h b/drivers/net/dsa/motorcomm/tc.h
index 3d4a8a24f8a8..0eee2e3808c5 100644
--- a/drivers/net/dsa/motorcomm/tc.h
+++ b/drivers/net/dsa/motorcomm/tc.h
@@ -88,8 +88,8 @@
#define YT921X_ACL_ENTRY_GRPIDm(bin, x) ((x) << (4 * (bin) + 1))
#define YT921X_ACL_ENTRY_ENm(bin) BIT(4 * (bin))
#define YT921X_ACLn_KEYm(blk, bin) (0x204000 + 0x200 * (bin) + 8 * (blk))
-#define YT921X_ACL_KEYb_ORD_M GENMASK(29, 21)
-#define YT921X_ACL_KEYb_ORD(x) FIELD_PREP(YT921X_ACL_KEYb_ORD_M, (x))
+#define YT921X_ACL_KEYb_PRIO_M GENMASK(29, 21) /* highest first */
+#define YT921X_ACL_KEYb_PRIO(x) FIELD_PREP(YT921X_ACL_KEYb_PRIO_M, (x))
#define YT921X_ACL_KEYb_SPORTS_M GENMASK(20, 10)
#define YT921X_ACL_KEYb_SPORTS(x) FIELD_PREP(YT921X_ACL_KEYb_SPORTS_M, (x))
#define YT921X_ACL_KEYb_SPORTn(port) BIT((port) + 10)
@@ -330,6 +330,7 @@ enum yt921x_l4_type {
#define YT921X_SHAPE_CIR_MAX ((1 << 18) - 1)
#define YT921X_SHAPE_CBS_MAX ((1 << 14) - 1)
+#define YT921X_ACL_PRIO_NUM 512
#define YT921X_UDF_NUM 8
struct yt921x_acl_entry {
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net-next 5/6] net: dsa: motorcomm: Add limited ACL flow statistics support
2026-09-26 21:17 [PATCH net-next 0/6] net: dsa: motorcomm: TC offload follow-ups David Yang
` (3 preceding siblings ...)
2026-09-26 21:17 ` [PATCH net-next 4/6] net: dsa: motorcomm: Add TC prio support David Yang
@ 2026-09-26 21:17 ` David Yang
2026-09-30 3:19 ` netdev-bot+sashiko
2026-09-26 21:17 ` [PATCH net-next 6/6] net: dsa: motorcomm: Add broadcast/multicast policers via tc police David Yang
5 siblings, 1 reply; 12+ messages in thread
From: David Yang @ 2026-09-26 21:17 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
The yt921x supports flow statistics, which might be used to implement
.cls_flower_stats(). However, the number of flow counter are limited,
and you must choose between byte mode or packet mode. As there is no
interface for statistics preference for now, we pick one on our own
initiative.
Signed-off-by: David Yang <mmyangfl@gmail.com>
---
drivers/net/dsa/motorcomm/chip.c | 1 +
drivers/net/dsa/motorcomm/chip.h | 3 +
drivers/net/dsa/motorcomm/smi.c | 33 ++++++++++
drivers/net/dsa/motorcomm/smi.h | 2 +
drivers/net/dsa/motorcomm/tc.c | 110 +++++++++++++++++++++++++++++--
drivers/net/dsa/motorcomm/tc.h | 18 ++++-
6 files changed, 161 insertions(+), 6 deletions(-)
diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index be48ecc3e3df..0405f1441f2b 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -2886,6 +2886,7 @@ static const struct dsa_switch_ops yt921x_dsa_switch_ops = {
.port_policer_add = yt921x_dsa_port_policer_add,
.port_setup_tc = yt921x_dsa_port_setup_tc,
/* acl */
+ .cls_flower_stats = yt921x_dsa_cls_flower_stats,
.cls_flower_del = yt921x_dsa_cls_flower_del,
.cls_flower_add = yt921x_dsa_cls_flower_add,
/* hsr */
diff --git a/drivers/net/dsa/motorcomm/chip.h b/drivers/net/dsa/motorcomm/chip.h
index c191cec94d59..d7f2b22ae504 100644
--- a/drivers/net/dsa/motorcomm/chip.h
+++ b/drivers/net/dsa/motorcomm/chip.h
@@ -486,6 +486,8 @@ enum yt921x_fdb_entry_status {
#define YT921X_METER_NUM 64
+#define YT921X_FLOWSTAT_NUM 64
+
#define YT921X_LAG_NUM 2
#define YT921X_LAG_PORT_NUM 4
@@ -641,6 +643,7 @@ struct yt921x_priv {
u16 eee_ports_mask;
DECLARE_BITMAP(meters_map, YT921X_METER_NUM);
+ DECLARE_BITMAP(flowstats_map, YT921X_FLOWSTAT_NUM);
u8 acl_masks[YT921X_ACL_BLK_NUM];
struct yt921x_acl_blk *acl_blks[YT921X_ACL_BLK_NUM];
diff --git a/drivers/net/dsa/motorcomm/smi.c b/drivers/net/dsa/motorcomm/smi.c
index bf3adfd64165..c50d66b2ada7 100644
--- a/drivers/net/dsa/motorcomm/smi.c
+++ b/drivers/net/dsa/motorcomm/smi.c
@@ -63,6 +63,39 @@ int yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask, u32 val)
return yt921x_reg_write(priv, reg, u);
}
+/* Reliably read a 64bit counter */
+int yt921x_counter_read(struct yt921x_priv *priv, u32 reg, u64 *valp)
+{
+ u32 old_lo;
+ int res;
+ u32 hi;
+ u32 lo;
+
+ res = yt921x_reg_read(priv, reg, &old_lo);
+ if (res)
+ return res;
+
+ /* Actually, it is expected finished in two round; anything else is an
+ * indication of a bug. Still we play safe here.
+ */
+ for (int i = 0; i < 4; i++) {
+ res = yt921x_reg_read(priv, reg + 4, &hi);
+ if (res)
+ return res;
+ res = yt921x_reg_read(priv, reg, &lo);
+ if (res)
+ return res;
+
+ if (lo >= old_lo) {
+ *valp = ((u64)hi << 32) | lo;
+ return 0;
+ }
+ old_lo = lo;
+ }
+
+ return -ETIMEDOUT;
+}
+
static int
yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,
unsigned int num_regs)
diff --git a/drivers/net/dsa/motorcomm/smi.h b/drivers/net/dsa/motorcomm/smi.h
index 212e20f71d80..d34240548af7 100644
--- a/drivers/net/dsa/motorcomm/smi.h
+++ b/drivers/net/dsa/motorcomm/smi.h
@@ -35,6 +35,8 @@ yt921x_reg_toggle_bits(struct yt921x_priv *priv, u32 reg, u32 mask, bool set)
return yt921x_reg_update_bits(priv, reg, mask, !set ? 0 : mask);
}
+int yt921x_counter_read(struct yt921x_priv *priv, u32 reg, u64 *valp);
+
/* Some multi-word registers, like VLANn_CTRL, should be treated as a single
* long register. More specifically, writes to parts of its words won't become
* visible, until the last word is written.
diff --git a/drivers/net/dsa/motorcomm/tc.c b/drivers/net/dsa/motorcomm/tc.c
index d919743d89c1..05d4a5957af9 100644
--- a/drivers/net/dsa/motorcomm/tc.c
+++ b/drivers/net/dsa/motorcomm/tc.c
@@ -1070,6 +1070,9 @@ yt921x_acl_rule_ext_parse_flow(struct yt921x_acl_rule_ext *ruleext, int port,
yt921x_acl_rule_set_ports(&ruleext->r, prio, BIT(port));
ruleext->r.tag = cls->cookie;
ruleext->r.type = TC_SETUP_CLSFLOWER;
+ /* Align with sja1105 */
+ ruleext->r.stat_pkt_mode = true;
+ ruleext->r.laststat = 0;
return 0;
}
@@ -1138,6 +1141,45 @@ yt921x_acl_reserve(struct yt921x_priv *priv, unsigned int entscnt,
return UINT_MAX;
}
+static int
+yt921x_acl_stat(struct yt921x_priv *priv, enum tc_setup_type type,
+ unsigned long tag, struct flow_stats *stats)
+{
+ struct yt921x_acl_rule *aclrule;
+ const struct yt921x_acl_blk *aclblk;
+ unsigned int statid;
+ unsigned int binid;
+ unsigned int blkid;
+ unsigned int entid;
+ u64 diff;
+ u64 stat;
+ int res;
+
+ entid = yt921x_acl_find(priv, type, tag);
+ if (entid == UINT_MAX)
+ return -ENOENT;
+
+ blkid = entid / YT921X_ACL_ENT_PER_BLK;
+ binid = entid % YT921X_ACL_ENT_PER_BLK;
+ aclblk = priv->acl_blks[blkid];
+ aclrule = aclblk->rules[binid];
+
+ if (!(aclrule->action[0] & YT921X_ACL_ACTa_FLOWSTAT_EN))
+ return -EOPNOTSUPP;
+
+ statid = FIELD_GET(YT921X_ACL_ACTa_FLOWSTAT_ID_M, aclrule->action[0]);
+ res = yt921x_counter_read(priv, YT921X_FLOWSTATn_STAT(statid), &stat);
+ if (res)
+ return res;
+
+ diff = stat - aclrule->laststat;
+ aclrule->laststat = stat;
+ flow_stats_update(stats, aclrule->stat_pkt_mode ? 0 : diff,
+ !aclrule->stat_pkt_mode ? 0 : diff, 0,
+ jiffies, FLOW_ACTION_HW_STATS_IMMEDIATE);
+ return 0;
+}
+
static int
yt921x_acl_commit(struct yt921x_priv *priv, unsigned int entid, u8 entsmask)
{
@@ -1270,9 +1312,13 @@ yt921x_acl_del(struct yt921x_priv *priv, enum tc_setup_type type,
}
if (aclrule->action[0] & YT921X_ACL_ACTa_METER_EN)
- clear_bit(FIELD_GET(YT921X_ACL_ACTa_METER_ID_M,
- aclrule->action[0]),
- priv->meters_map);
+ __clear_bit(FIELD_GET(YT921X_ACL_ACTa_METER_ID_M,
+ aclrule->action[0]),
+ priv->meters_map);
+ if (aclrule->action[0] & YT921X_ACL_ACTa_FLOWSTAT_EN)
+ __clear_bit(FIELD_GET(YT921X_ACL_ACTa_FLOWSTAT_ID_M,
+ aclrule->action[0]),
+ priv->flowstats_map);
priv->acl_masks[blkid] &= ~aclrule->mask;
kvfree(aclrule);
if (!priv->acl_masks[blkid]) {
@@ -1290,13 +1336,14 @@ yt921x_acl_add(struct yt921x_priv *priv,
unsigned int entscnt = hweight8(ruleext->r.mask);
struct yt921x_acl_rule *aclrule;
struct yt921x_acl_blk *aclblk;
- bool use_trap = false;
unsigned int meterid;
+ unsigned int statid;
unsigned int binid;
unsigned int blkid;
unsigned int entid;
unsigned long m;
unsigned int o;
+ bool use_trap;
u32 ctrl;
int res;
@@ -1305,6 +1352,10 @@ yt921x_acl_add(struct yt921x_priv *priv,
if (entid == UINT_MAX)
return -EOPNOTSUPP;
+ use_trap = (ruleext->r.action[2] & YT921X_ACL_ACTc_FWD_EN) &&
+ (FIELD_GET(YT921X_ACL_ACTc_FWD_M,
+ ruleext->r.action[2]) == YT921X_ACL_ACTc_FWD_TRAP);
+
if (!(ruleext->r.action[0] & YT921X_ACL_ACTa_METER_EN)) {
meterid = YT921X_METER_NUM;
} else {
@@ -1336,6 +1387,35 @@ yt921x_acl_add(struct yt921x_priv *priv,
return res;
}
+ if (ruleext->r.sw_assisted && use_trap) {
+ statid = YT921X_FLOWSTAT_NUM;
+ } else {
+ statid = find_first_zero_bit(priv->flowstats_map,
+ YT921X_FLOWSTAT_NUM);
+ if (statid >= YT921X_FLOWSTAT_NUM) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "No more flowstats, HW stats disabled");
+ } else {
+ u32 zeros[2] = {};
+
+ ctrl = YT921X_FLOWSTAT_CTRL_TYPE_FLOW |
+ YT921X_FLOWSTAT_CTRL_EN;
+ if (ruleext->r.stat_pkt_mode)
+ ctrl |= YT921X_FLOWSTAT_CTRL_PKT_MODE;
+ res = yt921x_reg_write(priv,
+ YT921X_FLOWSTATn_CTRL(statid),
+ ctrl);
+ if (res)
+ return res;
+
+ res = yt921x_reg64_write(priv,
+ YT921X_FLOWSTATn_STAT(statid),
+ zeros);
+ if (res)
+ return res;
+ }
+ }
+
/* Prepare acl block ctrlblk */
blkid = entid / YT921X_ACL_ENT_PER_BLK;
binid = entid % YT921X_ACL_ENT_PER_BLK;
@@ -1376,6 +1456,9 @@ yt921x_acl_add(struct yt921x_priv *priv,
aclrule->action[0] |= YT921X_ACL_ACTa_METER_ID(meterid);
else
aclrule->action[0] &= ~YT921X_ACL_ACTa_METER_EN;
+ if (statid < YT921X_FLOWSTAT_NUM)
+ aclrule->action[0] |= YT921X_ACL_ACTa_FLOWSTAT_EN |
+ YT921X_ACL_ACTa_FLOWSTAT_ID(statid);
/* Write rules */
aclblk->rules[binid] = aclrule;
@@ -1391,7 +1474,9 @@ yt921x_acl_add(struct yt921x_priv *priv,
priv->ports[o].acl_cnt++;
if (meterid < YT921X_METER_NUM)
- set_bit(meterid, priv->meters_map);
+ __set_bit(meterid, priv->meters_map);
+ if (statid < YT921X_FLOWSTAT_NUM)
+ __set_bit(statid, priv->flowstats_map);
priv->acl_masks[blkid] |= aclrule->mask;
return 0;
@@ -1403,6 +1488,21 @@ yt921x_acl_add(struct yt921x_priv *priv,
return res;
}
+int
+yt921x_dsa_cls_flower_stats(struct dsa_switch *ds, int port,
+ struct flow_cls_offload *cls, bool ingress)
+{
+ struct yt921x_priv *priv = to_yt921x_priv(ds);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_acl_stat(priv, TC_SETUP_CLSFLOWER, cls->cookie,
+ &cls->stats);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
int
yt921x_dsa_cls_flower_del(struct dsa_switch *ds, int port,
struct flow_cls_offload *cls, bool ingress)
diff --git a/drivers/net/dsa/motorcomm/tc.h b/drivers/net/dsa/motorcomm/tc.h
index 0eee2e3808c5..f2ea0ac32f7e 100644
--- a/drivers/net/dsa/motorcomm/tc.h
+++ b/drivers/net/dsa/motorcomm/tc.h
@@ -293,6 +293,16 @@ enum yt921x_l4_type {
#define YT921X_METER_CTRLa_EIR_M GENMASK(17, 0)
#define YT921X_METER_CTRLa_EIR(x) FIELD_PREP(YT921X_METER_CTRLa_EIR_M, (x))
#define YT921X_METERn_STAT(x) (0x221000 + 8 * (x))
+#define YT921X_FLOWSTATn_STAT(x) (0x221400 + 8 * (x))
+#define YT921X_FLOWSTATn_CTRL(x) (0x221c00 + 4 * (x))
+#define YT921X_FLOWSTAT_CTRL_EN BIT(3)
+#define YT921X_FLOWSTAT_CTRL_PKT_MODE BIT(2) /* 0: byte mode */
+#define YT921X_FLOWSTAT_CTRL_TYPE_M GENMASK(1, 0)
+#define YT921X_FLOWSTAT_CTRL_TYPE(x) FIELD_PREP(YT921X_FLOWSTAT_CTRL_TYPE_M, (x))
+#define YT921X_FLOWSTAT_CTRL_TYPE_FLOW YT921X_FLOWSTAT_CTRL_TYPE(0)
+#define YT921X_FLOWSTAT_CTRL_TYPE_CPU_CODE YT921X_FLOWSTAT_CTRL_TYPE(1)
+#define YT921X_FLOWSTAT_CTRL_TYPE_DROP_CODE YT921X_FLOWSTAT_CTRL_TYPE(2)
+#define YT921X_FLOWSTAT_CTRL_TYPE_PORT YT921X_FLOWSTAT_CTRL_TYPE(3)
#define YT921X_MIRROR 0x300300
#define YT921X_MIRROR_IGR_PORTS_M GENMASK(26, 16)
@@ -343,7 +353,10 @@ struct yt921x_acl_rule {
enum tc_setup_type type;
u32 action[3];
- bool sw_assisted;
+ u64 laststat;
+
+ bool sw_assisted:1;
+ bool stat_pkt_mode:1;
u8 mask;
struct yt921x_acl_entry entries[YT921X_ACL_ENT_PER_BLK];
@@ -365,6 +378,9 @@ int
yt921x_dsa_port_setup_tc(struct dsa_switch *ds, int port,
enum tc_setup_type type, void *type_data);
int
+yt921x_dsa_cls_flower_stats(struct dsa_switch *ds, int port,
+ struct flow_cls_offload *cls, bool ingress);
+int
yt921x_dsa_cls_flower_del(struct dsa_switch *ds, int port,
struct flow_cls_offload *cls, bool ingress);
int
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net-next 6/6] net: dsa: motorcomm: Add broadcast/multicast policers via tc police
2026-09-26 21:17 [PATCH net-next 0/6] net: dsa: motorcomm: TC offload follow-ups David Yang
` (4 preceding siblings ...)
2026-09-26 21:17 ` [PATCH net-next 5/6] net: dsa: motorcomm: Add limited ACL flow statistics support David Yang
@ 2026-09-26 21:17 ` David Yang
2026-09-30 3:19 ` netdev-bot+sashiko
5 siblings, 1 reply; 12+ messages in thread
From: David Yang @ 2026-09-26 21:17 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
Recognize the following tc flower filters
tc filter add dev lan1 ingress protocol all flower skip_sw \
dst_mac ff:ff:ff:ff:ff:ff action police rate 8mbit burst 64k
(and dst_mac 01:00:00:00:00:00/01:00:00:00:00:00)
and map it onto dedicated per-port storm control, following the sja1105
broadcast/multicast policer approach.
Signed-off-by: David Yang <mmyangfl@gmail.com>
---
drivers/net/dsa/motorcomm/chip.c | 11 ++
drivers/net/dsa/motorcomm/chip.h | 9 ++
drivers/net/dsa/motorcomm/tc.c | 175 ++++++++++++++++++++++++++++++-
drivers/net/dsa/motorcomm/tc.h | 17 +++
4 files changed, 209 insertions(+), 3 deletions(-)
diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index 0405f1441f2b..fb2514e01f91 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -2676,6 +2676,12 @@ static int yt921x_chip_setup_tc(struct yt921x_priv *priv)
return res;
priv->port_shape_slot_ns = ctrl * op_ns;
+ ctrl = YT921X_STORM_SLOT_DEF;
+ res = yt921x_reg_write(priv, YT921X_STORM_SLOT, ctrl);
+ if (res)
+ return res;
+ priv->storm_slot_ns = ctrl * 10 * NSEC_PER_USEC;
+
return 0;
}
@@ -2781,6 +2787,11 @@ static int yt921x_chip_setup(struct yt921x_priv *priv)
if (res)
return res;
+ res = yt921x_reg_write(priv, YT921X_STORM_UNK_MCAST,
+ YT921X_STORM_UNK_MCAST_PORTS_M);
+ if (res)
+ return res;
+
return 0;
}
diff --git a/drivers/net/dsa/motorcomm/chip.h b/drivers/net/dsa/motorcomm/chip.h
index d7f2b22ae504..4b2a688663df 100644
--- a/drivers/net/dsa/motorcomm/chip.h
+++ b/drivers/net/dsa/motorcomm/chip.h
@@ -567,6 +567,12 @@ struct yt921x_mib {
u64 tx_oam;
};
+enum yt921x_storm_type {
+ YT921X_STORM_BCAST,
+ YT921X_STORM_MCAST,
+ YT921X_STORM_NUM
+};
+
struct yt921x_port {
unsigned char index;
@@ -586,6 +592,8 @@ struct yt921x_port {
unsigned short acl_cnt;
+ unsigned long storm_tags[YT921X_STORM_NUM];
+
#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
unsigned char led_duty;
unsigned short led_cycle;
@@ -625,6 +633,7 @@ struct yt921x_priv {
const struct yt921x_info *info;
unsigned int meter_slot_ns;
unsigned int port_shape_slot_ns;
+ unsigned int storm_slot_ns;
/* cache of dsa_cpu_ports(ds) */
u16 cpu_ports_mask;
unsigned char cycle_ns;
diff --git a/drivers/net/dsa/motorcomm/tc.c b/drivers/net/dsa/motorcomm/tc.c
index 05d4a5957af9..3a0c6e0fbc52 100644
--- a/drivers/net/dsa/motorcomm/tc.c
+++ b/drivers/net/dsa/motorcomm/tc.c
@@ -182,6 +182,41 @@ yt921x_marker_tfm_shape(struct yt921x_marker *marker, u64 rate, u64 burst,
priv, port, extack);
}
+static int
+yt921x_marker_tfm_storm(struct yt921x_marker *marker,
+ const struct flow_action_police *police,
+ struct yt921x_priv *priv, int port)
+{
+ unsigned int slot_ns = priv->storm_slot_ns;
+ bool pkt_mode = !!police->rate_pkt_ps;
+ u64 burst;
+ u64 rate;
+ u32 cbs;
+ u32 cir;
+
+ rate = pkt_mode ? police->rate_pkt_ps : police->rate_bytes_ps;
+ burst = pkt_mode ? police->burst_pkt : police->burst;
+ if (pkt_mode) {
+ rate *= 1024;
+ burst *= 1024;
+ }
+
+ /* 1 token = 1 rate */
+ cir = div_u64(slot_ns * rate, NSEC_PER_SEC);
+ if (cir > YT921X_STORM_CIR_MAX)
+ return -ERANGE;
+ cbs = div_u64(slot_ns * burst, NSEC_PER_SEC);
+ if (cbs > YT921X_STORM_CBS_MAX)
+ return -ERANGE;
+
+ *marker = (typeof(*marker)){
+ .cir = cir,
+ .cbs = cbs,
+ .pkt_mode = pkt_mode,
+ };
+ return 0;
+}
+
static int
yt921x_police_validate(const struct flow_action_police *police,
const struct flow_action *action,
@@ -550,6 +585,12 @@ struct yt921x_acl_rule_ext {
struct yt921x_acl_rule r;
struct yt921x_marker marker;
+ struct yt921x_marker marker_storm;
+
+ bool non_storm:1;
+ bool marker_storm_set:1;
+ bool storm_type_set:1;
+ unsigned char storm_type;
};
static int
@@ -597,6 +638,8 @@ yt921x_acl_rule_ext_parse_flow_entries(struct yt921x_acl_rule_ext *ruleext,
entry->key[0] |= ntohl(match.key->dst);
entry->mask[0] |= ntohl(match.mask->dst);
+
+ ruleext->non_storm = true;
}
if (match.mask->src) {
@@ -607,6 +650,8 @@ yt921x_acl_rule_ext_parse_flow_entries(struct yt921x_acl_rule_ext *ruleext,
entry->key[0] |= ntohl(match.key->src);
entry->mask[0] |= ntohl(match.mask->src);
+
+ ruleext->non_storm = true;
}
}
@@ -626,6 +671,8 @@ yt921x_acl_rule_ext_parse_flow_entries(struct yt921x_acl_rule_ext *ruleext,
entry->key[0] |= ntohl(match.key->dst.s6_addr32[i]);
entry->mask[0] |= ntohl(match.mask->dst.s6_addr32[i]);
+
+ ruleext->non_storm = true;
}
for (unsigned int i = 0; i < 4; i++) {
@@ -639,6 +686,8 @@ yt921x_acl_rule_ext_parse_flow_entries(struct yt921x_acl_rule_ext *ruleext,
entry->key[0] |= ntohl(match.key->src.s6_addr32[i]);
entry->mask[0] |= ntohl(match.mask->src.s6_addr32[i]);
+
+ ruleext->non_storm = true;
}
}
@@ -661,6 +710,8 @@ yt921x_acl_rule_ext_parse_flow_entries(struct yt921x_acl_rule_ext *ruleext,
ntohs(match.key->src);
entry->mask[0] |= (ntohs(match.mask->dst) << 16) |
ntohs(match.mask->src);
+
+ ruleext->non_storm = true;
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS_RANGE)) {
@@ -684,21 +735,40 @@ yt921x_acl_rule_ext_parse_flow_entries(struct yt921x_acl_rule_ext *ruleext,
entry->key[0] |= ntohs(match.key->tp_min.dst) << 16;
entry->key[1] |= YT921X_ACL_KEYb_L4_DPORT_RANGE_EN;
entry->mask[0] |= ntohs(match.key->tp_max.dst) << 16;
+
+ ruleext->non_storm = true;
}
if (match.mask->tp.src) {
entry->key[0] |= ntohs(match.key->tp_min.src);
entry->key[1] |= YT921X_ACL_KEYb_L4_SPORT_RANGE_EN;
entry->mask[0] |= ntohs(match.key->tp_max.src);
+
+ ruleext->non_storm = true;
}
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
+ static const u8 mcast_addr[ETH_ALEN] = {0x01};
struct flow_match_eth_addrs match;
u32 mask;
flow_rule_match_eth_addrs(rule, &match);
+ if (!ruleext->non_storm) {
+ if (is_broadcast_ether_addr(match.mask->dst) &&
+ is_broadcast_ether_addr(match.key->dst)) {
+ ruleext->storm_type = YT921X_STORM_BCAST;
+ ruleext->storm_type_set = true;
+ } else if (ether_addr_equal(match.mask->dst, mcast_addr) &&
+ is_multicast_ether_addr(match.key->dst)) {
+ ruleext->storm_type = YT921X_STORM_MCAST;
+ ruleext->storm_type_set = true;
+ } else {
+ ruleext->non_storm = true;
+ }
+ }
+
mask = ethaddr_hi4_to_u32(match.mask->dst);
if (mask) {
entry = yt921x_acl_entries_new(entries, &size,
@@ -781,6 +851,8 @@ yt921x_acl_rule_ext_parse_flow_entries(struct yt921x_acl_rule_ext *ruleext,
entry->key[0] |= ntohs(match.key->n_proto);
entry->mask[0] |= ntohs(match.mask->n_proto);
}
+
+ ruleext->non_storm = true;
}
if (match.mask->ip_proto) {
@@ -820,6 +892,8 @@ yt921x_acl_rule_ext_parse_flow_entries(struct yt921x_acl_rule_ext *ruleext,
entry->key[0] |= YT921X_ACL_BINa_MISC_IP_PROTO(match.key->ip_proto);
entry->mask[0] |= YT921X_ACL_BINa_MISC_IP_PROTO(match.mask->ip_proto);
}
+
+ ruleext->non_storm = true;
}
}
@@ -839,6 +913,8 @@ yt921x_acl_rule_ext_parse_flow_entries(struct yt921x_acl_rule_ext *ruleext,
set);
if (!size)
goto err;
+
+ ruleext->non_storm = true;
}
if (match.mask->flags & FLOW_DIS_FIRST_FRAG) {
bool set = match.key->flags & FLOW_DIS_FIRST_FRAG;
@@ -847,6 +923,8 @@ yt921x_acl_rule_ext_parse_flow_entries(struct yt921x_acl_rule_ext *ruleext,
set);
if (!size)
goto err;
+
+ ruleext->non_storm = true;
}
}
@@ -869,6 +947,8 @@ yt921x_acl_rule_ext_parse_flow_entries(struct yt921x_acl_rule_ext *ruleext,
entry->key[0] |= YT921X_ACL_BINa_MISC_TOS(match.key->tos);
entry->mask[0] |= YT921X_ACL_BINa_MISC_TOS(match.mask->tos);
+
+ ruleext->non_storm = true;
}
}
@@ -889,6 +969,8 @@ yt921x_acl_rule_ext_parse_flow_entries(struct yt921x_acl_rule_ext *ruleext,
entry->key[0] |= YT921X_ACL_BINa_MISC_TCP_FLAGS(ntohs(match.key->flags));
entry->mask[0] |= YT921X_ACL_BINa_MISC_TCP_FLAGS(ntohs(match.mask->flags));
+
+ ruleext->non_storm = true;
}
}
@@ -923,7 +1005,10 @@ yt921x_acl_rule_ext_parse_flow_action(struct yt921x_acl_rule_ext *ruleext,
int res;
memset(action, 0, 3 * sizeof(*action));
- flow_action_for_each(i, act, flow_action)
+ flow_action_for_each(i, act, flow_action) {
+ if (act->id != FLOW_ACTION_POLICE)
+ ruleext->non_storm = true;
+
switch (act->id) {
case FLOW_ACTION_ACCEPT:
case FLOW_ACTION_DROP:
@@ -996,6 +1081,16 @@ yt921x_acl_rule_ext_parse_flow_action(struct yt921x_acl_rule_ext *ruleext,
if (res)
return res;
+ if (!ruleext->non_storm) {
+ res = yt921x_marker_tfm_storm(&ruleext->marker_storm,
+ police, priv,
+ port);
+ if (!res)
+ ruleext->marker_storm_set = true;
+ else
+ ruleext->non_storm = true;
+ }
+
res = yt921x_marker_tfm_police(&ruleext->marker, police,
0, priv, port, extack);
if (res)
@@ -1022,6 +1117,7 @@ yt921x_acl_rule_ext_parse_flow_action(struct yt921x_acl_rule_ext *ruleext,
YT921X_ACL_ACTc_FWD_TRAP;
break;
}
+ }
ruleext->r.sw_assisted = !cls->common.skip_sw;
return 0;
@@ -1056,6 +1152,9 @@ yt921x_acl_rule_ext_parse_flow(struct yt921x_acl_rule_ext *ruleext, int port,
return -EOPNOTSUPP;
}
+ ruleext->non_storm = false;
+ ruleext->marker_storm_set = false;
+ ruleext->storm_type_set = false;
res = yt921x_acl_rule_ext_parse_flow_action(ruleext, cls, priv, port);
if (res)
return res;
@@ -1488,6 +1587,76 @@ yt921x_acl_add(struct yt921x_priv *priv,
return res;
}
+static int
+yt921x_storm_del(struct yt921x_priv *priv, int port, unsigned int type)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ int res;
+
+ res = yt921x_reg_write(priv, YT921X_STORM_CTRLnm(type, port), 0);
+ /* the kernel never rolls back on failure */
+
+ pp->storm_tags[type] = 0;
+
+ return res;
+}
+
+static int
+yt921x_storm_add(struct yt921x_priv *priv, int port, unsigned long cookie,
+ unsigned int type, const struct yt921x_marker *marker,
+ struct netlink_ext_ack *extack)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ u32 ctrl;
+ int res;
+
+ if (pp->storm_tags[type])
+ return -ENOSPC;
+
+ ctrl = YT921X_STORM_CTRL_EN | YT921X_STORM_CTRL_CBS(marker->cbs) |
+ YT921X_STORM_CTRL_CIR(marker->cir);
+ if (marker->pkt_mode)
+ ctrl |= YT921X_STORM_CTRL_PKT_MODE;
+ res = yt921x_reg_write(priv, YT921X_STORM_CTRLnm(type, port), ctrl);
+ if (res)
+ return res;
+
+ pp->storm_tags[type] = cookie;
+ return 0;
+}
+
+static int
+yt921x_flower_del(struct yt921x_priv *priv, int port, unsigned long cookie)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+
+ for (unsigned int type = YT921X_STORM_BCAST; type < YT921X_STORM_NUM;
+ type++)
+ if (pp->storm_tags[type] == cookie)
+ return yt921x_storm_del(priv, port, type);
+
+ return yt921x_acl_del(priv, TC_SETUP_CLSFLOWER, cookie);
+}
+
+static int
+yt921x_flower_add(struct yt921x_priv *priv, int port,
+ const struct yt921x_acl_rule_ext *ruleext,
+ struct netlink_ext_ack *extack)
+{
+ int res;
+
+ if (!ruleext->non_storm && ruleext->marker_storm_set &&
+ ruleext->storm_type_set) {
+ res = yt921x_storm_add(priv, port, ruleext->r.tag,
+ ruleext->storm_type,
+ &ruleext->marker_storm, extack);
+ if (res != -ENOSPC)
+ return res;
+ }
+
+ return yt921x_acl_add(priv, ruleext, extack);
+}
+
int
yt921x_dsa_cls_flower_stats(struct dsa_switch *ds, int port,
struct flow_cls_offload *cls, bool ingress)
@@ -1511,7 +1680,7 @@ yt921x_dsa_cls_flower_del(struct dsa_switch *ds, int port,
int res;
mutex_lock(&priv->reg_lock);
- res = yt921x_acl_del(priv, TC_SETUP_CLSFLOWER, cls->cookie);
+ res = yt921x_flower_del(priv, port, cls->cookie);
mutex_unlock(&priv->reg_lock);
return res;
@@ -1532,7 +1701,7 @@ yt921x_dsa_cls_flower_add(struct dsa_switch *ds, int port,
return res;
mutex_lock(&priv->reg_lock);
- res = yt921x_acl_add(priv, &ruleext, extack);
+ res = yt921x_flower_add(priv, port, &ruleext, extack);
mutex_unlock(&priv->reg_lock);
return res;
diff --git a/drivers/net/dsa/motorcomm/tc.h b/drivers/net/dsa/motorcomm/tc.h
index f2ea0ac32f7e..a9107f97a1b9 100644
--- a/drivers/net/dsa/motorcomm/tc.h
+++ b/drivers/net/dsa/motorcomm/tc.h
@@ -263,12 +263,26 @@ enum yt921x_l4_type {
#define YT921X_PORTn_RATE(port) (0x220000 + 4 * (port))
#define YT921X_PORT_RATE_GAP_VALUE GENMASK(4, 0) /* default 20 */
+#define YT921X_STORM_SLOT 0x220100
+#define YT921X_STORM_SLOT_SLOT_M GENMASK(11, 0)
#define YT921X_METER_SLOT 0x220104
#define YT921X_METER_SLOT_SLOT_M GENMASK(11, 0)
#define YT921X_PORTn_METER(port) (0x220108 + 4 * (port))
#define YT921X_PORT_METER_EN BIT(4)
#define YT921X_PORT_METER_ID_M GENMASK(3, 0)
#define YT921X_PORT_METER_ID(x) FIELD_PREP(YT921X_PORT_METER_ID_M, (x))
+#define YT921X_STORM_UNK_MCAST 0x220140
+#define YT921X_STORM_UNK_MCAST_PORTS_M GENMASK(10, 0)
+#define YT921X_STORM_UNK_MCAST_PORT(x) BIT(x)
+#define YT921X_STORM_CTRLnm(type, port) (0x220200 + 4 * (11 * (type) + (port)))
+#define YT921X_STORM_CTRL_CIR_M GENMASK(31, 13)
+#define YT921X_STORM_CTRL_CIR(x) FIELD_PREP(YT921X_STORM_CTRL_CIR_M, (x))
+#define YT921X_STORM_CTRL_CBS_M GENMASK(12, 3)
+#define YT921X_STORM_CTRL_CBS(x) FIELD_PREP(YT921X_STORM_CTRL_CBS_M, (x))
+#define YT921X_STORM_CTRL_BYTE_MODE_INCLUDE_GAP BIT(2) /* +GAP_VALUE bytes each packet */
+#define YT921X_STORM_CTRL_PKT_MODE BIT(1) /* 0: byte rate mode */
+#define YT921X_STORM_CTRL_EN BIT(0)
+#define YT921X_STORM_STATnm(type, port) (0x220400 + 4 * (11 * (type) + (port)))
#define YT921X_METERn_CTRL(x) (0x220800 + 0x10 * (x))
#define YT921X_METER_CTRLc_METER_EN BIT(14)
#define YT921X_METER_CTRLc_TOKEN_OVERFLOW_EN BIT(13) /* RFC4115: yellow use unused green bw */
@@ -339,6 +353,9 @@ enum yt921x_l4_type {
#define YT921X_SHAPE_UNIT_MAX ((1 << 3) - 1)
#define YT921X_SHAPE_CIR_MAX ((1 << 18) - 1)
#define YT921X_SHAPE_CBS_MAX ((1 << 14) - 1)
+#define YT921X_STORM_SLOT_DEF 100
+#define YT921X_STORM_CIR_MAX ((1 << 19) - 1)
+#define YT921X_STORM_CBS_MAX ((1 << 10) - 1)
#define YT921X_ACL_PRIO_NUM 512
#define YT921X_UDF_NUM 8
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net-next 1/6] net: dsa: motorcomm: Split TC module
2026-09-26 21:17 ` [PATCH net-next 1/6] net: dsa: motorcomm: Split TC module David Yang
@ 2026-09-30 3:19 ` netdev-bot+sashiko
0 siblings, 0 replies; 12+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 3:19 UTC (permalink / raw)
To: mmyangfl
Cc: netdev, andrew, olteanv, davem, edumazet, kuba, pabeni, linux-kernel
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The patch moved the comment "/* Custom meters only, not including
dedicated port meters (11) */" away from the define it describes.
- [Low] tc.h does not compile on its own.
- [Low] yt921x_dsa_port_setup_tc_tbf_port() is declared in tc.h (line
361) and defined without `static` in tc.c (line 292).
--
net: dsa: motorcomm: Split TC module
This commit moves the traffic control code (policers, TBF shaping, flower
ACL offload and port mirroring) out of chip.c and into a new file, tc.c.
The related register definitions and ACL structs move from chip.h into a
new tc.h. The ethaddr helpers become static inline functions in chip.h.
> diff --git a/drivers/net/dsa/motorcomm/chip.h b/drivers/net/dsa/motorcomm/chip.h
> index 28e4f26f9cc49..02419bcde3f21 100644
> --- a/drivers/net/dsa/motorcomm/chip.h
> +++ b/drivers/net/dsa/motorcomm/chip.h
[ ... ]
> @@ -791,19 +484,7 @@ enum yt921x_fdb_entry_status {
>
> #define YT921X_MSTI_NUM 16
>
> -#define YT921X_TOKEN_BYTE_C 1 /* 1 token = 2^1 byte */
> -#define YT921X_TOKEN_PKT_C -6 /* 1 token = 2^-6 packets */
> -#define YT921X_TOKEN_RATE_C -15
> -/* Custom meters only, not including dedicated port meters (11) */
> #define YT921X_METER_NUM 64
> -#define YT921X_METER_SLOT_MIN 80
> -#define YT921X_METER_UNIT_MAX ((1 << 3) - 1)
> -#define YT921X_METER_CIR_MAX ((1 << 18) - 1)
> -#define YT921X_METER_CBS_MAX ((1 << 16) - 1)
> -#define YT921X_PORT_SHAPE_SLOT_MIN 80
> -#define YT921X_SHAPE_UNIT_MAX ((1 << 3) - 1)
> -#define YT921X_SHAPE_CIR_MAX ((1 << 18) - 1)
> -#define YT921X_SHAPE_CBS_MAX ((1 << 14) - 1)
[Severity: Low]
This isn't a bug, but the "Custom meters only" comment described
YT921X_METER_NUM, and that define stays in chip.h. In tc.h the comment now
sits above an unrelated define:
tc.h:
#define YT921X_TOKEN_RATE_C -15
/* Custom meters only, not including dedicated port meters (11) */
#define YT921X_METER_SLOT_MIN 80
Could the comment stay with YT921X_METER_NUM in chip.h? It explains why
yt921x_dsa_port_policer_add() programs meter port + YT921X_METER_NUM. With
the comment moved, YT921X_METER_NUM has no explanation.
This still looks the same at the end of the series.
[ ... ]
> diff --git a/drivers/net/dsa/motorcomm/tc.c b/drivers/net/dsa/motorcomm/tc.c
> new file mode 100644
> index 0000000000000..38dd4ec60bbc1
> --- /dev/null
> +++ b/drivers/net/dsa/motorcomm/tc.c
> @@ -0,0 +1,1457 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2026 David Yang
> + */
> +
> +#include "chip.h"
> +#include "smi.h"
> +#include "tc.h"
> +
> +#define to_yt921x_priv(_ds) container_of_const(_ds, struct yt921x_priv, ds)
> +#define to_device(priv) ((priv)->ds.dev)
[ ... ]
> +int
> +yt921x_dsa_port_setup_tc_tbf_port(struct dsa_switch *ds, int port,
> + const struct tc_tbf_qopt_offload *qopt)
> +{
[ ... ]
> +int
> +yt921x_dsa_port_setup_tc(struct dsa_switch *ds, int port,
> + enum tc_setup_type type, void *type_data)
> +{
> + switch (type) {
> + case TC_SETUP_QDISC_TBF: {
> + const struct tc_tbf_qopt_offload *qopt = type_data;
> +
> + return yt921x_dsa_port_setup_tc_tbf_port(ds, port, qopt);
> + }
[Severity: Low]
This isn't a bug, but the only caller of
yt921x_dsa_port_setup_tc_tbf_port() is yt921x_dsa_port_setup_tc(), here in
the same file. Could it stay static and be left out of tc.h?
Also, the to_yt921x_priv() and to_device() macros at the top of tc.c are
exact copies of the ones in chip.c:
chip.c:
#define to_yt921x_priv(_ds) container_of_const(_ds, struct yt921x_priv, ds)
#define to_device(priv) ((priv)->ds.dev)
Would it be better to define them once in chip.h, next to struct
yt921x_priv, so the two copies can't drift apart?
[ ... ]
> diff --git a/drivers/net/dsa/motorcomm/tc.h b/drivers/net/dsa/motorcomm/tc.h
> new file mode 100644
> index 0000000000000..3d4a8a24f8a85
> --- /dev/null
> +++ b/drivers/net/dsa/motorcomm/tc.h
> @@ -0,0 +1,380 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (c) 2026 David Yang
> + */
> +
> +#ifndef _YT_TC_H
> +#define _YT_TC_H
> +
> +#include <net/dsa.h>
> +#include <net/pkt_cls.h>
> +
[ ... ]
> +struct yt921x_acl_rule {
> + unsigned long tag;
> + enum tc_setup_type type;
> +
> + u32 action[3];
> + bool sw_assisted;
> +
> + u8 mask;
> + struct yt921x_acl_entry entries[YT921X_ACL_ENT_PER_BLK];
> +};
> +
> +struct yt921x_acl_blk {
> + struct yt921x_acl_rule *rules[YT921X_ACL_ENT_PER_BLK];
> +};
[Severity: Low]
Is tc.h meant to compile on its own? YT921X_ACL_ENT_PER_BLK is still
defined only in chip.h, and tc.h includes only <net/dsa.h> and
<net/pkt_cls.h>. These arrays build only because chip.c and tc.c both
include "chip.h" before "tc.h".
The dependency also runs the other way. struct yt921x_priv in chip.h still
holds struct yt921x_acl_blk *acl_blks[], but that type is now defined only
in tc.h. A pointer to an incomplete type is legal, but the split between
the two headers is inconsistent.
The other local headers (leds.h, mdio_bus.h, smi.h) include what they need
and forward-declare struct yt921x_priv. Could tc.h do the same and include
what it depends on, such as the header that defines YT921X_ACL_ENT_PER_BLK?
As it stands, reordering the includes or adding a new user of tc.h would
break the build.
This also looks unchanged at the end of the series.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926211813.935723-1-mmyangfl%40gmail.com
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net-next 3/6] net: dsa: motorcomm: Enable ACL on demand
2026-09-26 21:17 ` [PATCH net-next 3/6] net: dsa: motorcomm: Enable ACL on demand David Yang
@ 2026-09-30 3:19 ` netdev-bot+sashiko
0 siblings, 0 replies; 12+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 3:19 UTC (permalink / raw)
To: mmyangfl
Cc: netdev, andrew, olteanv, davem, edumazet, kuba, pabeni, linux-kernel
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] In yt921x_acl_add() (drivers/net/dsa/motorcomm/tc.c), ACL
lookup is switched on in hardware for every source port whose acl_cnt…
--
net: dsa: motorcomm: Enable ACL on demand
This patch adds a per-port acl_cnt field to struct yt921x_port to count
ACL rules. YT921X_ACL_PORT is now initialised to 0 in
yt921x_chip_setup_acl(). yt921x_acl_add() and yt921x_acl_del() then turn
the per-port enable bits on and off based on those counts.
> diff --git a/drivers/net/dsa/motorcomm/tc.c b/drivers/net/dsa/motorcomm/tc.c
> index 7717f1e179235..0f6d7a4675fcd 100644
> --- a/drivers/net/dsa/motorcomm/tc.c
> +++ b/drivers/net/dsa/motorcomm/tc.c
[ ... ]
> @@ -1274,6 +1309,18 @@ yt921x_acl_add(struct yt921x_priv *priv,
> }
> }
>
> + ctrl = 0;
> + m = FIELD_GET(YT921X_ACL_KEYb_SPORTS_M, ruleext->r.entries[0].key[1]);
> + for_each_set_bit(o, &m, YT921X_PORT_NUM)
> + if (!priv->ports[o].acl_cnt)
> + ctrl |= YT921X_ACL_PORT_PORTn(o);
> + if (ctrl) {
> + ctrl |= yt921x_acl_activated_ports(priv);
> + res = yt921x_reg_write(priv, YT921X_ACL_PORT, ctrl);
> + if (res)
> + return res;
> + }
[Severity: Medium]
If yt921x_acl_add() fails after this write, can the switch and the driver
end up disagreeing about YT921X_ACL_PORT?
The register is written before several steps that can still fail.
However, acl_cnt is only incremented after yt921x_acl_commit() succeeds.
None of the later failure paths write YT921X_ACL_PORT again:
aclblk = kvzalloc_obj(*aclblk);
if (!aclblk)
return -ENOMEM;
...
aclrule = kvmemdup(...);
if (!aclrule) {
res = -ENOMEM;
goto err;
}
...
res = yt921x_acl_commit(priv, entid, aclrule->mask);
if (res) {
aclblk->rules[binid] = NULL;
kvfree(aclrule);
goto err;
}
On any of these paths, the newly enabled port bits stay set in the switch.
Meanwhile, yt921x_acl_activated_ports() still reports those ports as
inactive.
Deleting a rule on the same port can't clear the stale bit, because the
failed rule was never recorded. The bit is only cleared if a later
yt921x_acl_del() drops some other port's acl_cnt to zero, sets
refresh_en, and rewrites the register from the counts.
A later patch in the series, "net: dsa: motorcomm: Add limited ACL flow
statistics support", adds two more direct-return failure points after
this write: the YT921X_FLOWSTATn_CTRL write and the YT921X_FLOWSTATn_STAT
reg64 write. That makes the window wider.
Would it work to write YT921X_ACL_PORT only after yt921x_acl_commit()
succeeds? Another option is to write yt921x_acl_activated_ports(priv)
back on each error path after this point. That value is still the one
from before the add, because acl_cnt hasn't changed yet.
> /* Prepare acl block ctrlblk */
> blkid = entid / YT921X_ACL_ENT_PER_BLK;
> binid = entid % YT921X_ACL_ENT_PER_BLK;
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926211813.935723-1-mmyangfl%40gmail.com
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net-next 4/6] net: dsa: motorcomm: Add TC prio support
2026-09-26 21:17 ` [PATCH net-next 4/6] net: dsa: motorcomm: Add TC prio support David Yang
@ 2026-09-30 3:19 ` netdev-bot+sashiko
0 siblings, 0 replies; 12+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 3:19 UTC (permalink / raw)
To: mmyangfl
Cc: netdev, andrew, olteanv, davem, edumazet, kuba, pabeni, linux-kernel
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 0 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] yt921x_acl_rule_ext_parse_flow() in
drivers/net/dsa/motorcomm/tc.c now accepts only priorities that are…
- [Low] In yt921x_acl_rule_ext_parse_flow()
(drivers/net/dsa/motorcomm/tc.c), the extack error reads 'Invalid…
--
net: dsa: motorcomm: Add TC prio support
This commit maps the TC flower priority onto the 512 hardware ACL
priorities. It scales the TC prio by 128, treats 65535 as the catch-all
and rejects any prio that doesn't map onto a hardware priority. It also
renames the ACL key field from ORD to PRIO.
> diff --git a/drivers/net/dsa/motorcomm/tc.c b/drivers/net/dsa/motorcomm/tc.c
> index 0f6d7a4675fcd..d919743d89c17 100644
> --- a/drivers/net/dsa/motorcomm/tc.c
> +++ b/drivers/net/dsa/motorcomm/tc.c
[ ... ]
> @@ -1032,7 +1032,9 @@ yt921x_acl_rule_ext_parse_flow(struct yt921x_acl_rule_ext *ruleext, int port,
> const struct flow_cls_offload *cls, bool ingress,
> struct yt921x_priv *priv)
> {
> + const unsigned int scale = (U16_MAX + 1) / YT921X_ACL_PRIO_NUM;
> struct netlink_ext_ack *extack = cls->common.extack;
> + u32 prio = cls->common.prio;
> int res;
>
> if (!ingress) {
> @@ -1045,6 +1047,15 @@ yt921x_acl_rule_ext_parse_flow(struct yt921x_acl_rule_ext *ruleext, int port,
> return -EOPNOTSUPP;
> }
>
> + if (prio == U16_MAX) {
> + prio++;
> + } else if (prio % scale) {
[Severity: High]
Does this reject most flower filters that are added without an explicit
pref?
When no prio is given, tc_new_tfilter() sets prio_allocate and picks a
value with tcf_auto_prio():
net/sched/cls_api.c:tcf_auto_prio() {
u32 first = TC_H_MAKE(0xC0000000U, 0U);
if (tp)
first = tp->prio - 1;
return TC_H_MAJ(first);
}
The first auto-prio filter gets 49152. Each later one gets a new
tcf_proto with the next lower value: 49151, then 49150, and so on.
tc_cls_common_offload_init() passes that value to the driver unchanged,
as cls->common.prio = tp->prio >> 16.
With scale = 65536 / 512 = 128, only the first of those values passes
the prio % scale check. The second one fails with "Invalid priority
49151, must be a multiple of 128".
Before this patch, all of these filters were offloaded with
yt921x_acl_rule_set_ports(&ruleext->r, 0, BIT(port)).
So with a sequence like this:
tc filter add dev lan1 ingress flower ... skip_sw action drop
tc filter add dev lan1 ingress flower ... skip_sw action drop
the second command now fails. Without skip_sw, the second filter quietly
stays software-only. Its drop, redirect and police actions then no
longer apply to traffic the switch forwards in hardware on that port.
This check runs before yt921x_acl_rule_ext_parse_flow_action(), so
police-only storm control rules with an auto-allocated prio are rejected
as well.
The commit message says:
"Notice the default priority for flower is 49152"
That seems to hold only for the first auto-allocated filter in a chain.
Would some kind of relative ordering work better here than a fixed
scale-and-reject mapping?
As far as I can tell, the later patches in the series leave this code
unchanged.
> + NL_SET_ERR_MSG_FMT_MOD(extack,
> + "Invalid priority %u, must be a multiple of %u",
> + prio, scale);
[Severity: Low]
This isn't a bug, but the message says the priority must be a multiple
of 128, while the prio == U16_MAX branch above also accepts 65535. Could
the message mention 65535 too?
> + return -EOPNOTSUPP;
> + }
> +
> res = yt921x_acl_rule_ext_parse_flow_action(ruleext, cls, priv, port);
> if (res)
> return res;
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926211813.935723-1-mmyangfl%40gmail.com
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net-next 5/6] net: dsa: motorcomm: Add limited ACL flow statistics support
2026-09-26 21:17 ` [PATCH net-next 5/6] net: dsa: motorcomm: Add limited ACL flow statistics support David Yang
@ 2026-09-30 3:19 ` netdev-bot+sashiko
0 siblings, 0 replies; 12+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 3:19 UTC (permalink / raw)
To: mmyangfl
Cc: netdev, andrew, olteanv, davem, edumazet, kuba, pabeni, linux-kernel
Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 4 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] yt921x_acl_add() (drivers/net/dsa/motorcomm/tc.c:1355-1357) is
meant to detect whether a rule already has a trap action.
- [Medium] yt921x_dsa_cls_flower_stats() (tc.c:1492) ignores its `port`
argument.
- [Medium] The patch adds hardware flow statistics for flower rules but
ignores the per-action hw_stats type the user requested.
- [Medium] yt921x_acl_stat() (tc.c:1177-1179) always passes `jiffies` as
`lastused` to flow_stats_update(), even when the counter did not…
- [Low] Flow counter slots are released only in the software bitmap,
without regard to hardware state.
--
net: dsa: motorcomm: Add limited ACL flow statistics support
This commit adds .cls_flower_stats() support to the yt921x driver using
the switch's 64 hardware flow counters. At add time, each flower ACL rule
gets a packet-mode counter, except software-assisted trap rules. The
counter is read back and reported as a delta through flow_stats_update().
> diff --git a/drivers/net/dsa/motorcomm/tc.c b/drivers/net/dsa/motorcomm/tc.c
> index d919743d89c17..05d4a5957af95 100644
> --- a/drivers/net/dsa/motorcomm/tc.c
> +++ b/drivers/net/dsa/motorcomm/tc.c
[ ... ]
> @@ -1138,6 +1141,45 @@ yt921x_acl_reserve(struct yt921x_priv *priv, unsigned int entscnt,
> return UINT_MAX;
> }
>
> +static int
> +yt921x_acl_stat(struct yt921x_priv *priv, enum tc_setup_type type,
> + unsigned long tag, struct flow_stats *stats)
> +{
[ ... ]
> + diff = stat - aclrule->laststat;
> + aclrule->laststat = stat;
> + flow_stats_update(stats, aclrule->stat_pkt_mode ? 0 : diff,
> + !aclrule->stat_pkt_mode ? 0 : diff, 0,
> + jiffies, FLOW_ACTION_HW_STATS_IMMEDIATE);
[Severity: Medium]
Should lastused be set to jiffies even when diff is 0?
flow_stats_update() updates lastused whatever the packet and byte
deltas are:
include/net/flow_offload.h:flow_stats_update() {
...
flow_stats->lastused = max_t(u64, flow_stats->lastused, lastused);
...
}
tcf_gact_stats_update() then copies that into tm->lastuse. As a result,
every offloaded rule with a counter looks just used on every stats
poll, and tc -s filter show always reports it that way.
Userspace that ages out idle offloaded flows by tc lastuse, such as the
OVS tc-offload revalidator, would never see these flows as idle.
sja1105_vl.c has the same pattern. The "Align with sja1105" comment only
covers stat_pkt_mode.
Could jiffies be passed only when diff is non-zero?
> + return 0;
> +}
[ ... ]
> @@ -1270,9 +1312,13 @@ yt921x_acl_del(struct yt921x_priv *priv, enum tc_setup_type type,
> }
>
> if (aclrule->action[0] & YT921X_ACL_ACTa_METER_EN)
> - clear_bit(FIELD_GET(YT921X_ACL_ACTa_METER_ID_M,
> - aclrule->action[0]),
> - priv->meters_map);
> + __clear_bit(FIELD_GET(YT921X_ACL_ACTa_METER_ID_M,
> + aclrule->action[0]),
> + priv->meters_map);
> + if (aclrule->action[0] & YT921X_ACL_ACTa_FLOWSTAT_EN)
> + __clear_bit(FIELD_GET(YT921X_ACL_ACTa_FLOWSTAT_ID_M,
> + aclrule->action[0]),
> + priv->flowstats_map);
[Severity: Low]
Can a statid be reused while the hardware still references it?
Earlier in yt921x_acl_del(), the commit result is saved, but a failure
does not stop the resources from being released:
aclblk->rules[binid] = NULL;
ret = yt921x_acl_commit(priv, entid, aclrule->mask);
/* the kernel never rolls back on failure */
Suppose the first write in yt921x_acl_commit(),
yt921x_reg96_write(YT921X_ACLn_ACT(entid), zeros), fails on a bus error.
The old ACT word with FLOWSTAT_EN | FLOWSTAT_ID(statid), plus the
entry's key and mask, stay live in hardware.
The statid is still cleared here. The next yt921x_acl_add() can then
pick it with find_first_zero_bit(), zero the counter and set
laststat = 0. Hits on the orphaned entry would then show up in the new
rule's stats.
Also, YT921X_FLOWSTATn_CTRL(statid) is never cleared, either here or on
the yt921x_acl_add() error paths after the CTRL write. Those paths are
a STAT zeroing failure, a kvzalloc_obj() or kvmemdup() failure, and a
yt921x_acl_commit() failure. Is it intended to leave those counters
armed while flowstats_map treats them as free?
This needs a bus failure to trigger. The same pattern already exists
for meters and ACL slots.
> priv->acl_masks[blkid] &= ~aclrule->mask;
> kvfree(aclrule);
[ ... ]
> @@ -1305,6 +1352,10 @@ yt921x_acl_add(struct yt921x_priv *priv,
> if (entid == UINT_MAX)
> return -EOPNOTSUPP;
>
> + use_trap = (ruleext->r.action[2] & YT921X_ACL_ACTc_FWD_EN) &&
> + (FIELD_GET(YT921X_ACL_ACTc_FWD_M,
> + ruleext->r.action[2]) == YT921X_ACL_ACTc_FWD_TRAP);
[Severity: Medium]
Can this comparison ever be true? The definitions are:
drivers/net/dsa/motorcomm/tc.h:
#define YT921X_ACL_ACTc_FWD_M GENMASK(22, 21)
#define YT921X_ACL_ACTc_FWD(x) FIELD_PREP(YT921X_ACL_ACTc_FWD_M, (x))
...
#define YT921X_ACL_ACTc_FWD_TRAP YT921X_ACL_ACTc_FWD(3)
FIELD_GET() returns a value in 0..3, but YT921X_ACL_ACTc_FWD_TRAP is
the shifted value 0x600000.
That would leave use_trap false for explicit FLOW_ACTION_TRAP rules. It
would also be false for rules that fell back to trap in
yt921x_acl_rule_ext_parse_flow_action(). Only the meter exhaustion path
further down sets it.
The new check below then goes to the else branch for software-assisted
trap rules:
if (ruleext->r.sw_assisted && use_trap) {
statid = YT921X_FLOWSTAT_NUM;
} else {
statid = find_first_zero_bit(priv->flowstats_map,
So these rules get a hardware counter, which is the case the patch
meant to exclude. The software flower filter also counts the trapped
packets, so tc stats count them twice. The rules also use up counters
from the 64-entry pool.
Would comparing the masked value work instead?
(ruleext->r.action[2] & YT921X_ACL_ACTc_FWD_M) == YT921X_ACL_ACTc_FWD_TRAP
> +
> if (!(ruleext->r.action[0] & YT921X_ACL_ACTa_METER_EN)) {
> meterid = YT921X_METER_NUM;
> } else {
[ ... ]
> @@ -1336,6 +1387,35 @@ yt921x_acl_add(struct yt921x_priv *priv,
> return res;
> }
>
> + if (ruleext->r.sw_assisted && use_trap) {
> + statid = YT921X_FLOWSTAT_NUM;
> + } else {
> + statid = find_first_zero_bit(priv->flowstats_map,
> + YT921X_FLOWSTAT_NUM);
> + if (statid >= YT921X_FLOWSTAT_NUM) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "No more flowstats, HW stats disabled");
> + } else {
[Severity: Medium]
Should the per-action hw_stats type be checked here?
tc_setup_action() copies the requested type into each action entry:
entry[k].hw_stats = tc_act_hw_stats(act->hw_stats);
Nothing under drivers/net/dsa/motorcomm/ reads act->hw_stats or calls
flow_action_hw_stats_check() or flow_action_basic_hw_stats_check().
Drivers such as ocelot, sparx5, lan966x, bnxt and mlx5 do. This has
three effects.
A rule added with "hw_stats disabled" still takes a counter from the
64-entry flowstats_map. There are 384 ACL entries, so such rules can use
up the pool and leave later rules that want stats with none.
A rule that explicitly asks for "hw_stats immediate" is still accepted
when the pool is empty. Only the extack message is set, and 0 is
returned. Later stats queries get -EOPNOTSUPP from yt921x_acl_stat(),
which the core ignores, so the user sees no stats and no error.
yt921x_acl_stat() always reports FLOW_ACTION_HW_STATS_IMMEDIATE, even
for "hw_stats delayed" requests.
The commit message says:
As there is no interface for statistics preference for now, we pick
one on our own initiative.
That covers byte vs packet mode. The enabled, disabled, immediate and
delayed preference does exist in the uAPI.
Could the driver reject unsupported hw_stats types and skip counter
allocation when stats are disabled?
> + u32 zeros[2] = {};
[ ... ]
> @@ -1403,6 +1488,21 @@ yt921x_acl_add(struct yt921x_priv *priv,
> return res;
> }
>
> +int
> +yt921x_dsa_cls_flower_stats(struct dsa_switch *ds, int port,
> + struct flow_cls_offload *cls, bool ingress)
> +{
> + struct yt921x_priv *priv = to_yt921x_priv(ds);
> + int res;
> +
> + mutex_lock(&priv->reg_lock);
> + res = yt921x_acl_stat(priv, TC_SETUP_CLSFLOWER, cls->cookie,
> + &cls->stats);
[Severity: Medium]
What happens when a shared block offloads the same filter on several
ports?
The port argument is dropped here. yt921x_acl_find() matches only on
tag and type and returns the first match:
if (aclblk->rules[i] && aclblk->rules[i]->tag == tag &&
aclblk->rules[i]->type == type)
return YT921X_ACL_ENT_PER_BLK * blkid + i;
For example:
tc qdisc add dev swpA ingress_block 1 clsact
tc qdisc add dev swpB ingress_block 1 clsact
tc filter add block 1 flower ... skip_sw
yt921x_acl_rule_ext_parse_flow() creates one ACL rule per port, with
SPORTS = BIT(port) and tag == (unsigned long)f. Each rule has its own
flowstat counter. fl_hw_update_stats() -> tc_setup_cb_call() then calls
each port's callback with the same cookie.
Each of those calls finds the same first rule. The first call reports
its delta and updates laststat, and the later calls report about zero.
The other ports' counters are never read, so their hits never reach
tc -s filter show.
Would matching on the port avoid losing those hits? The port is already
encoded in the SPORTS field of entries[0].key[1].
> + mutex_unlock(&priv->reg_lock);
> +
> + return res;
> +}
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926211813.935723-1-mmyangfl%40gmail.com
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net-next 6/6] net: dsa: motorcomm: Add broadcast/multicast policers via tc police
2026-09-26 21:17 ` [PATCH net-next 6/6] net: dsa: motorcomm: Add broadcast/multicast policers via tc police David Yang
@ 2026-09-30 3:19 ` netdev-bot+sashiko
0 siblings, 0 replies; 12+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 3:19 UTC (permalink / raw)
To: mmyangfl
Cc: netdev, andrew, olteanv, davem, edumazet, kuba, pabeni, linux-kernel
Thank you for your contribution! Sashiko AI review found 10 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 6 · Low: 3
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] net: dsa: motorcomm: cbs calculation erroneously scales burst
capacity by time
- [Medium] A flower rule that matches a broadcast or multicast dst_mac
and also a src_mac is still sent to the per-port storm policer, and…
- [Medium] Flower rules offloaded to a storm policer never report
hardware statistics.
- [Medium] The storm policer bypasses tc priority, and the silent -ENOSPC
fallback means hardware can police the same frames twice.
- [Medium] yt921x_chip_setup() now writes YT921X_STORM_UNK_MCAST with
every port bit set (YT921X_STORM_UNK_MCAST_PORTS_M) on every probe.
- [Medium] yt921x_marker_tfm_storm() computes cir = div_u64(slot_ns *
rate, NSEC_PER_SEC) and cbs = div_u64(slot_ns * burst, NSEC_PER_SEC)…
- [Medium] A non-skip_sw rule with two police actions and a plain
bcast/mcast dst_mac is installed as a single-rate storm policer, and…
- [Low] yt921x_storm_add() takes a struct netlink_ext_ack *extack that it
never uses.
- [Low] yt921x_chip_setup_tc() programs YT921X_STORM_SLOT with the
constant YT921X_STORM_SLOT_DEF (100) and interprets it as `ctrl * 10 *…
- [Low] In yt921x_acl_rule_ext_parse_flow_entries(), `static const u8
mcast_addr[ETH_ALEN] = {0x01};` is passed to…
--
net: dsa: motorcomm: Add broadcast/multicast policers via tc police
This patch recognizes tc flower filters that match a broadcast or
multicast destination MAC and carry a police action. It offloads them
onto the per-port storm control blocks instead of an ACL rule with a
meter. It also programs the storm slot register at setup and tracks the
owning cookie per port and storm type.
> diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
> index 0405f1441f2ba..fb2514e01f919 100644
> --- a/drivers/net/dsa/motorcomm/chip.c
> +++ b/drivers/net/dsa/motorcomm/chip.c
> @@ -2676,6 +2676,12 @@ static int yt921x_chip_setup_tc(struct yt921x_priv *priv)
> return res;
> priv->port_shape_slot_ns = ctrl * op_ns;
>
> + ctrl = YT921X_STORM_SLOT_DEF;
> + res = yt921x_reg_write(priv, YT921X_STORM_SLOT, ctrl);
> + if (res)
> + return res;
> + priv->storm_slot_ns = ctrl * 10 * NSEC_PER_USEC;
[Severity: Low]
Is a 10 us tick the right unit for YT921X_STORM_SLOT?
The other two slot registers set up earlier in yt921x_chip_setup_tc()
are scaled by the core clock and clamped to a minimum:
op_ns = 8 * priv->cycle_ns;
ctrl = max(priv->meter_slot_ns / op_ns, YT921X_METER_SLOT_MIN);
res = yt921x_reg_write(priv, YT921X_METER_SLOT, ctrl);
...
priv->meter_slot_ns = ctrl * op_ns;
YT921X_STORM_SLOT (0x220100) sits next to YT921X_METER_SLOT (0x220104)
and has the same GENMASK(11, 0) field. Here, though, it gets a fixed,
clock-independent time base and no minimum clamp.
If the storm slot is clocked like the others, then with an 8 ns cycle
storm_slot_ns would be 100 * 8 * 8 ns = 6.4 us, not 1 ms. Every CIR/CBS
computed in yt921x_marker_tfm_storm() would then be off by about 150x.
Could a comment or datasheet reference explain the different unit?
> @@ -2781,6 +2787,11 @@ static int yt921x_chip_setup(struct yt921x_priv *priv)
> if (res)
> return res;
>
> + res = yt921x_reg_write(priv, YT921X_STORM_UNK_MCAST,
> + YT921X_STORM_UNK_MCAST_PORTS_M);
> + if (res)
> + return res;
[Severity: Medium]
What does YT921X_STORM_UNK_MCAST select? This sets every port bit on
each probe, and neither the commit message nor a comment says what that
changes. The commit message also doesn't mention the YT921X_STORM_SLOT
write in yt921x_chip_setup_tc().
A related question is how the multicast storm class lines up with the
software filter. yt921x_acl_rule_ext_parse_flow_entries() maps the
group-bit match onto the MCAST class:
} else if (ether_addr_equal(match.mask->dst, mcast_addr) &&
is_multicast_ether_addr(match.key->dst)) {
ruleext->storm_type = YT921X_STORM_MCAST;
In software, dst_mac 01:00:00:00:00:00/01:00:00:00:00:00 also matches
broadcast frames. In hardware, broadcast has its own storm block
(YT921X_STORM_CTRLnm() type 0 vs type 1). Depending on UNK_MCAST, the
multicast block may also cover only unknown multicast.
Could the hardware MCAST policer then police a different set of frames
than the software filter, for example leaving out broadcast or known
multicast?
> diff --git a/drivers/net/dsa/motorcomm/tc.c b/drivers/net/dsa/motorcomm/tc.c
> index 05d4a5957af95..3a0c6e0fbc52e 100644
> --- a/drivers/net/dsa/motorcomm/tc.c
> +++ b/drivers/net/dsa/motorcomm/tc.c
> @@ -182,6 +182,41 @@ yt921x_marker_tfm_shape(struct yt921x_marker *marker, u64 rate, u64 burst,
> priv, port, extack);
> }
>
> +static int
> +yt921x_marker_tfm_storm(struct yt921x_marker *marker,
> + const struct flow_action_police *police,
> + struct yt921x_priv *priv, int port)
> +{
[ ... ]
> + /* 1 token = 1 rate */
> + cir = div_u64(slot_ns * rate, NSEC_PER_SEC);
> + if (cir > YT921X_STORM_CIR_MAX)
> + return -ERANGE;
> + cbs = div_u64(slot_ns * burst, NSEC_PER_SEC);
> + if (cbs > YT921X_STORM_CBS_MAX)
> + return -ERANGE;
[Severity: High]
Why is the burst scaled by the slot length here?
burst is a bucket capacity in bytes, or in packets in pkt_mode. It is
not a rate. Multiplying it by slot_ns / NSEC_PER_SEC treats it as if
it were bytes per second.
With storm_slot_ns = 1 ms, this divides the configured burst by 1000.
Take the example from the commit message:
police rate 8mbit burst 64k
That gives cbs = 65. The cir calculation just above implies that one
token is one byte. If so, the hardware bucket holds 65 bytes, which is
smaller than a minimum-size Ethernet frame.
Wouldn't that drop almost every broadcast or multicast frame on the
port, instead of allowing the configured 64k burst? pkt_mode has the
same problem, because the extra factor of 1024 is applied to both rate
and burst.
The meter path does not do this. In yt921x_marker_tfm(), only the rate
goes through rate2token() with slot_ns. The burst is converted with
burst2token(burst, unit, C) and no slot scaling.
On the other hand, if CBS really is in bytes, YT921X_STORM_CBS_MAX
limits the bucket to 1023 bytes. That cannot hold a full-size frame
either. Dropping the slot scaling would then make the -ERANGE check
reject most realistic bursts, so these filters would always fall back
to the ACL meter.
Could you say what unit the CBS field of YT921X_STORM_CTRLnm() uses,
and how a byte or packet burst should map onto it?
[Severity: Medium]
What happens when cir or cbs rounds down to zero here?
With storm_slot_ns = 1 ms, a byte-mode rate under 1000 B/s (for example
police rate 4kbit) gives cir = 0. A burst under 1000 bytes gives
cbs = 0.
Only the upper bounds are checked, so marker_storm_set becomes true.
yt921x_storm_add() then writes YT921X_STORM_CTRL_EN with a CIR or CBS of
zero.
Wouldn't that block nearly all broadcast or multicast traffic on the
port? Software police with the same parameters still passes traffic at
the configured rate.
The meter path in yt921x_marker_tfm() clamps these values:
m.cir = rate2token(rate, slot_ns, m.unit, C);
if (!m.cir)
m.cir = 1;
...
m.cbs = burst2token(burst, m.unit, C);
if (!m.cbs)
m.cbs = 1;
That path also warns when the burst is smaller than one frame. Should
the storm path clamp, warn, or fall back to the ACL meter in the same
way?
[ ... ]
> @@ -684,21 +735,40 @@ yt921x_acl_rule_ext_parse_flow_entries(struct yt921x_acl_rule_ext *ruleext,
[ ... ]
> if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
> + static const u8 mcast_addr[ETH_ALEN] = {0x01};
[Severity: Low]
This isn't a bug, but mcast_addr is later passed to ether_addr_equal().
include/linux/etherdevice.h says both addresses must be u16 aligned, and
a plain u8[6] only guarantees byte alignment.
Would static const u8 mcast_addr[ETH_ALEN] __aligned(2) work here? Or
ether_addr_equal_unaligned()?
> struct flow_match_eth_addrs match;
> u32 mask;
>
> flow_rule_match_eth_addrs(rule, &match);
>
> + if (!ruleext->non_storm) {
> + if (is_broadcast_ether_addr(match.mask->dst) &&
> + is_broadcast_ether_addr(match.key->dst)) {
> + ruleext->storm_type = YT921X_STORM_BCAST;
> + ruleext->storm_type_set = true;
> + } else if (ether_addr_equal(match.mask->dst, mcast_addr) &&
> + is_multicast_ether_addr(match.key->dst)) {
> + ruleext->storm_type = YT921X_STORM_MCAST;
> + ruleext->storm_type_set = true;
> + } else {
> + ruleext->non_storm = true;
> + }
> + }
[Severity: Medium]
What happens to a src_mac match here? Storm eligibility is decided from
the dst MAC alone.
The source MAC entries built just below never set non_storm, unlike
every other match key in this function:
mask = ethaddr_hi4_to_u32(match.mask->src);
if (mask) {
entry = yt921x_acl_entries_new(entries, &size,
YT921X_ACL_TYPE_MAC_SA0);
...
The MAC_DA1_SA1 entry works the same way.
Take this filter:
flower skip_sw dst_mac ff:ff:ff:ff:ff:ff src_mac 00:11:22:33:44:55 \
action police rate 8mbit burst 64k
non_storm stays false. yt921x_flower_add() then installs the rule
through yt921x_storm_add(), and the ACL entries that carry the SA match
are never installed.
Wouldn't that rate-limit broadcast from every source on the port, with
no extack warning? For comparison, sja1105_flower_parse_key() rejects
this case with "Matching on source MAC not supported".
[ ... ]
> @@ -996,6 +1081,16 @@ yt921x_acl_rule_ext_parse_flow_action(struct yt921x_acl_rule_ext *ruleext,
> if (res)
> return res;
>
> + if (!ruleext->non_storm) {
> + res = yt921x_marker_tfm_storm(&ruleext->marker_storm,
> + police, priv,
> + port);
> + if (!res)
> + ruleext->marker_storm_set = true;
> + else
> + ruleext->non_storm = true;
> + }
[Severity: Medium]
How does this work with a second police action? non_storm is set only
for actions other than FLOW_ACTION_POLICE:
if (act->id != FLOW_ACTION_POLICE)
ruleext->non_storm = true;
Take a rule without skip_sw that has a plain broadcast or multicast
dst_mac and two police actions. The first police action sets
marker_storm_set. The second hits:
if (seen_police) {
action[0] &= ~YT921X_ACL_ACTa_METER_EN;
reason = "Multiple police actions";
goto fallback;
}
It then falls through to FLOW_ACTION_TRAP so that software can apply
both policers. But non_storm is still false.
yt921x_flower_add() therefore installs only the first policer via
yt921x_storm_add() and returns 0. The trap action in ruleext->r.action
is dropped.
Doesn't that leave the second policer unenforced for frames forwarded
in hardware?
[ ... ]
> @@ -1488,6 +1587,76 @@ yt921x_acl_add(struct yt921x_priv *priv,
> return res;
> }
>
[ ... ]
> +static int
> +yt921x_storm_add(struct yt921x_priv *priv, int port, unsigned long cookie,
> + unsigned int type, const struct yt921x_marker *marker,
> + struct netlink_ext_ack *extack)
[Severity: Low]
This isn't a bug, but yt921x_storm_add() never uses extack, and
yt921x_marker_tfm_storm() doesn't take one. So the storm path never
reports the rate and burst it programs or warns about truncation. It
also says nothing when it falls back to an ACL on -ENOSPC.
yt921x_marker_tfm_police() still runs during parsing and can emit its
"Consider match rate X with burst at least Y" message. That value is
computed for the meter slot, though, not for the storm policer that
actually gets installed.
[ ... ]
> +static int
> +yt921x_flower_add(struct yt921x_priv *priv, int port,
> + const struct yt921x_acl_rule_ext *ruleext,
> + struct netlink_ext_ack *extack)
> +{
> + int res;
> +
> + if (!ruleext->non_storm && ruleext->marker_storm_set &&
> + ruleext->storm_type_set) {
> + res = yt921x_storm_add(priv, port, ruleext->r.tag,
> + ruleext->storm_type,
> + &ruleext->marker_storm, extack);
> + if (res != -ENOSPC)
> + return res;
> + }
> +
> + return yt921x_acl_add(priv, ruleext, extack);
> +}
[Severity: Medium]
Does this keep tc priority semantics?
The storm register is port-wide and ignores cls->common.prio and any
installed ACL rules. In software, cls_flower stops at the first matching
filter, because police with conform pipe/ok returns >= 0 from
__tcf_classify().
Suppose two dst_mac ff:ff:ff:ff:ff:ff police filters are added on one
port with different prios. The first takes the storm slot. For the
second, yt921x_storm_add() returns -ENOSPC, and the filter is silently
installed as an ACL meter.
Would hardware then apply both the storm policer and the ACL meter to
the same broadcast frames? Software applies only the higher-priority
filter.
The commit message says this follows "the sja1105 broadcast/multicast
policer approach". sja1105 has only a broadcast policer, and
sja1105_setup_bcast_policer() rejects a second one instead of falling
back:
NL_SET_ERR_MSG_MOD(extack,
"Port already has a broadcast policer");
rc = -EEXIST;
> +
> int
> yt921x_dsa_cls_flower_stats(struct dsa_switch *ds, int port,
> struct flow_cls_offload *cls, bool ingress)
[Severity: Medium]
Should yt921x_dsa_cls_flower_stats() be storm-aware as well? Add and
delete now go through yt921x_flower_add() and yt921x_flower_del(), but
stats still calls yt921x_acl_stat() directly:
res = yt921x_acl_stat(priv, TC_SETUP_CLSFLOWER, cls->cookie,
&cls->stats);
A storm-offloaded cookie exists only in pp->storm_tags[] and has no ACL
rule. So yt921x_acl_find() returns UINT_MAX and yt921x_acl_stat()
returns -ENOENT.
fl_hw_update_stats() ignores the error, so tc -s filter show never
shows hardware counters for these filters.
The patch also adds YT921X_STORM_STATnm() in tc.h, but nothing reads
it. Was it meant to be used here?
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926211813.935723-1-mmyangfl%40gmail.com
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-09-30 3:19 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-26 21:17 [PATCH net-next 0/6] net: dsa: motorcomm: TC offload follow-ups David Yang
2026-09-26 21:17 ` [PATCH net-next 1/6] net: dsa: motorcomm: Split TC module David Yang
2026-09-30 3:19 ` netdev-bot+sashiko
2026-09-26 21:17 ` [PATCH net-next 2/6] net: dsa: motorcomm: Use NSEC_PER_SEC David Yang
2026-09-26 21:17 ` [PATCH net-next 3/6] net: dsa: motorcomm: Enable ACL on demand David Yang
2026-09-30 3:19 ` netdev-bot+sashiko
2026-09-26 21:17 ` [PATCH net-next 4/6] net: dsa: motorcomm: Add TC prio support David Yang
2026-09-30 3:19 ` netdev-bot+sashiko
2026-09-26 21:17 ` [PATCH net-next 5/6] net: dsa: motorcomm: Add limited ACL flow statistics support David Yang
2026-09-30 3:19 ` netdev-bot+sashiko
2026-09-26 21:17 ` [PATCH net-next 6/6] net: dsa: motorcomm: Add broadcast/multicast policers via tc police David Yang
2026-09-30 3:19 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®