]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
net: dsa: yt921x: Add port police support
authorDavid Yang <mmyangfl@gmail.com>
Thu, 30 Apr 2026 11:45:26 +0000 (19:45 +0800)
committerJakub Kicinski <kuba@kernel.org>
Sat, 2 May 2026 17:38:58 +0000 (10:38 -0700)
Enable rate meter ability and support limiting the rate of incoming
traffic.

Signed-off-by: David Yang <mmyangfl@gmail.com>
Link: https://patch.msgid.link/20260430114529.3536911-4-mmyangfl@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/dsa/yt921x.c
drivers/net/dsa/yt921x.h

index c9ae95704fdc8fcf8069842860197f0c878c5596..42dca9617fb1708b2493dcbb87a1bc894f06ba19 100644 (file)
@@ -263,6 +263,14 @@ yt921x_reg_toggle_bits(struct yt921x_priv *priv, u32 reg, u32 mask, bool set)
  * eliminate potential issues, although partial reads/writes are also possible.
  */
 
+static void update_ctrls_unaligned(u32 *lo, u32 *hi, u64 mask, u64 val)
+{
+       *lo &= ~lower_32_bits(mask);
+       *hi &= ~upper_32_bits(mask);
+       *lo |= lower_32_bits(val);
+       *hi |= upper_32_bits(val);
+}
+
 static int
 yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,
                 unsigned int num_regs)
@@ -373,6 +381,12 @@ yt921x_reg64_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks)
        return yt921x_regs_clear_bits(priv, reg, masks, 2);
 }
 
+static int
+yt921x_reg96_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
+{
+       return yt921x_regs_write(priv, reg, vals, 3);
+}
+
 static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
 {
        struct yt921x_reg_mdio *mdio = context;
@@ -1066,6 +1080,13 @@ 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)
 {
@@ -1097,6 +1118,266 @@ 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_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_mirror_del(struct yt921x_priv *priv, int port, bool ingress)
 {
@@ -3051,6 +3332,7 @@ static int yt921x_chip_detect(struct yt921x_priv *priv)
        u32 chipid;
        u32 major;
        u32 mode;
+       u32 val;
        int res;
 
        res = yt921x_reg_read(priv, YT921X_CHIP_ID, &chipid);
@@ -3085,12 +3367,27 @@ static int yt921x_chip_detect(struct yt921x_priv *priv)
                return -ENODEV;
        }
 
+       res = yt921x_reg_read(priv, YT921X_SYS_CLK, &val);
+       if (res)
+               return res;
+       switch (FIELD_GET(YT921X_SYS_CLK_SEL_M, val)) {
+       case 0:
+               priv->cycle_ns = info->major == YT9215_MAJOR ? 8 : 6;
+               break;
+       case YT921X_SYS_CLK_143M:
+               priv->cycle_ns = 7;
+               break;
+       default:
+               priv->cycle_ns = 8;
+       }
+
        /* Print chipid here since we are interested in lower 16 bits */
        dev_info(dev,
                 "Motorcomm %s ethernet switch, chipid: 0x%x, chipmode: 0x%x 0x%x\n",
                 info->name, chipid, mode, extmode);
 
        priv->info = info;
+
        return 0;
 }
 
@@ -3212,6 +3509,23 @@ static int yt921x_chip_setup_dsa(struct yt921x_priv *priv)
        return 0;
 }
 
+static int yt921x_chip_setup_tc(struct yt921x_priv *priv)
+{
+       unsigned int op_ns;
+       u32 ctrl;
+       int res;
+
+       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);
+       if (res)
+               return res;
+       priv->meter_slot_ns = ctrl * op_ns;
+
+       return 0;
+}
+
 static int __maybe_unused yt921x_chip_setup_qos(struct yt921x_priv *priv)
 {
        u32 ctrl;
@@ -3258,7 +3572,7 @@ static int yt921x_chip_setup(struct yt921x_priv *priv)
        u32 ctrl;
        int res;
 
-       ctrl = YT921X_FUNC_MIB;
+       ctrl = YT921X_FUNC_MIB | YT921X_FUNC_METER;
        res = yt921x_reg_set_bits(priv, YT921X_FUNC, ctrl);
        if (res)
                return res;
@@ -3267,6 +3581,10 @@ static int yt921x_chip_setup(struct yt921x_priv *priv)
        if (res)
                return res;
 
+       res = yt921x_chip_setup_tc(priv);
+       if (res)
+               return res;
+
 #if IS_ENABLED(CONFIG_DCB)
        res = yt921x_chip_setup_qos(priv);
        if (res)
@@ -3358,6 +3676,9 @@ static const struct dsa_switch_ops yt921x_dsa_switch_ops = {
        /* mtu */
        .port_change_mtu        = yt921x_dsa_port_change_mtu,
        .port_max_mtu           = yt921x_dsa_port_max_mtu,
+       /* rate */
+       .port_policer_del       = yt921x_dsa_port_policer_del,
+       .port_policer_add       = yt921x_dsa_port_policer_add,
        /* hsr */
        .port_hsr_leave         = dsa_port_simple_hsr_leave,
        .port_hsr_join          = dsa_port_simple_hsr_join,
index 4989d87c2492e67fd9d7d959aa3aafdae33083a9..546b12a8994a489aa4532adce702ec1c5ac3574b 100644 (file)
@@ -23,6 +23,7 @@
 #define  YT921X_RST_HW                         BIT(31)
 #define  YT921X_RST_SW                         BIT(1)
 #define YT921X_FUNC                    0x80004
+#define  YT921X_FUNC_METER                     BIT(4)
 #define  YT921X_FUNC_MIB                       BIT(1)
 #define YT921X_CHIP_ID                 0x80008
 #define  YT921X_CHIP_ID_MAJOR                  GENMASK(31, 16)
 #define  YT921X_EDATA_DATA_STATUS_M            GENMASK(3, 0)
 #define   YT921X_EDATA_DATA_STATUS(x)                  FIELD_PREP(YT921X_EDATA_DATA_STATUS_M, (x))
 #define   YT921X_EDATA_DATA_IDLE                       YT921X_EDATA_DATA_STATUS(3)
+#define YT921X_SYS_CLK                 0xe0040
+#define  YT921X_SYS_CLK_SEL_M                  GENMASK(1, 0)  /* unknown: 167M */
+#define   YT9215_SYS_CLK_125M                          0
+#define   YT9218_SYS_CLK_167M                          0
+#define   YT921X_SYS_CLK_143M                          1
 
 #define YT921X_EXT_MBUS_OP             0x6a000
 #define YT921X_INT_MBUS_OP             0xf0000
@@ -465,6 +471,39 @@ enum yt921x_app_selector {
 #define  YT921X_LAG_HASH_MAC_DA                        BIT(1)
 #define  YT921X_LAG_HASH_SRC_PORT              BIT(0)
 
+#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)
 #define  YT921X_PORT_VLAN_CTRL_CVLAN_PRIO_EN   BIT(30)
@@ -508,6 +547,16 @@ 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_LAG_NUM         2
 #define YT921X_LAG_PORT_NUM    4
 
@@ -602,8 +651,10 @@ struct yt921x_priv {
        struct dsa_switch ds;
 
        const struct yt921x_info *info;
+       unsigned int meter_slot_ns;
        /* cache of dsa_cpu_ports(ds) */
        u16 cpu_ports_mask;
+       unsigned char cycle_ns;
 
        /* protect the access to the switch registers */
        struct mutex reg_lock;