]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
net: dsa: yt921x: Add LAG offloading support
authorDavid Yang <mmyangfl@gmail.com>
Sat, 17 Jan 2026 16:21:11 +0000 (00:21 +0800)
committerPaolo Abeni <pabeni@redhat.com>
Thu, 22 Jan 2026 09:06:33 +0000 (10:06 +0100)
Add offloading for a link aggregation group supported by the YT921x
switches.

Signed-off-by: David Yang <mmyangfl@gmail.com>
Link: https://patch.msgid.link/20260117162116.1063043-1-mmyangfl@gmail.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
drivers/net/dsa/yt921x.c
drivers/net/dsa/yt921x.h

index 1c511f5dc6ab23626ab1bda14d93b204447591aa..f750844329eebf43f175adb1b2f25e82222d897a 100644 (file)
@@ -1117,6 +1117,188 @@ yt921x_dsa_port_mirror_add(struct dsa_switch *ds, int port,
        return res;
 }
 
+static int yt921x_lag_hash(struct yt921x_priv *priv, u32 ctrl, bool unique_lag,
+                          struct netlink_ext_ack *extack)
+{
+       u32 val;
+       int res;
+
+       /* Hash Mode is global. Make sure the same Hash Mode is set to all the
+        * 2 possible lags.
+        * If we are the unique LAG we can set whatever hash mode we want.
+        * To change hash mode it's needed to remove all LAG and change the mode
+        * with the latest.
+        */
+       if (unique_lag) {
+               res = yt921x_reg_write(priv, YT921X_LAG_HASH, ctrl);
+               if (res)
+                       return res;
+       } else {
+               res = yt921x_reg_read(priv, YT921X_LAG_HASH, &val);
+               if (res)
+                       return res;
+
+               if (val != ctrl) {
+                       NL_SET_ERR_MSG_MOD(extack,
+                                          "Mismatched Hash Mode across different lags is not supported");
+                       return -EOPNOTSUPP;
+               }
+       }
+
+       return 0;
+}
+
+static int yt921x_lag_set(struct yt921x_priv *priv, u8 index, u16 ports_mask)
+{
+       unsigned long targets_mask = ports_mask;
+       unsigned int cnt;
+       u32 ctrl;
+       int port;
+       int res;
+
+       cnt = 0;
+       for_each_set_bit(port, &targets_mask, YT921X_PORT_NUM) {
+               ctrl = YT921X_LAG_MEMBER_PORT(port);
+               res = yt921x_reg_write(priv, YT921X_LAG_MEMBERnm(index, cnt),
+                                      ctrl);
+               if (res)
+                       return res;
+
+               cnt++;
+       }
+
+       ctrl = YT921X_LAG_GROUP_PORTS(ports_mask) |
+              YT921X_LAG_GROUP_MEMBER_NUM(cnt);
+       return yt921x_reg_write(priv, YT921X_LAG_GROUPn(index), ctrl);
+}
+
+static int
+yt921x_dsa_port_lag_leave(struct dsa_switch *ds, int port, struct dsa_lag lag)
+{
+       struct yt921x_priv *priv = to_yt921x_priv(ds);
+       struct dsa_port *dp;
+       u32 ctrl;
+       int res;
+
+       if (!lag.id)
+               return -EINVAL;
+
+       ctrl = 0;
+       dsa_lag_foreach_port(dp, ds->dst, &lag)
+               ctrl |= BIT(dp->index);
+
+       mutex_lock(&priv->reg_lock);
+       res = yt921x_lag_set(priv, lag.id - 1, ctrl);
+       mutex_unlock(&priv->reg_lock);
+
+       return res;
+}
+
+static int
+yt921x_dsa_port_lag_check(struct dsa_switch *ds, struct dsa_lag lag,
+                         struct netdev_lag_upper_info *info,
+                         struct netlink_ext_ack *extack)
+{
+       unsigned int members;
+       struct dsa_port *dp;
+
+       if (!lag.id)
+               return -EINVAL;
+
+       members = 0;
+       dsa_lag_foreach_port(dp, ds->dst, &lag)
+               /* Includes the port joining the LAG */
+               members++;
+
+       if (members > YT921X_LAG_PORT_NUM) {
+               NL_SET_ERR_MSG_MOD(extack,
+                                  "Cannot offload more than 4 LAG ports");
+               return -EOPNOTSUPP;
+       }
+
+       if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH) {
+               NL_SET_ERR_MSG_MOD(extack,
+                                  "Can only offload LAG using hash TX type");
+               return -EOPNOTSUPP;
+       }
+
+       if (info->hash_type != NETDEV_LAG_HASH_L2 &&
+           info->hash_type != NETDEV_LAG_HASH_L23 &&
+           info->hash_type != NETDEV_LAG_HASH_L34) {
+               NL_SET_ERR_MSG_MOD(extack,
+                                  "Can only offload L2 or L2+L3 or L3+L4 TX hash");
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
+}
+
+static int
+yt921x_dsa_port_lag_join(struct dsa_switch *ds, int port, struct dsa_lag lag,
+                        struct netdev_lag_upper_info *info,
+                        struct netlink_ext_ack *extack)
+{
+       struct yt921x_priv *priv = to_yt921x_priv(ds);
+       struct dsa_port *dp;
+       bool unique_lag;
+       unsigned int i;
+       u32 ctrl;
+       int res;
+
+       res = yt921x_dsa_port_lag_check(ds, lag, info, extack);
+       if (res)
+               return res;
+
+       ctrl = 0;
+       switch (info->hash_type) {
+       case NETDEV_LAG_HASH_L34:
+               ctrl |= YT921X_LAG_HASH_IP_DST;
+               ctrl |= YT921X_LAG_HASH_IP_SRC;
+               ctrl |= YT921X_LAG_HASH_IP_PROTO;
+
+               ctrl |= YT921X_LAG_HASH_L4_DPORT;
+               ctrl |= YT921X_LAG_HASH_L4_SPORT;
+               break;
+       case NETDEV_LAG_HASH_L23:
+               ctrl |= YT921X_LAG_HASH_MAC_DA;
+               ctrl |= YT921X_LAG_HASH_MAC_SA;
+
+               ctrl |= YT921X_LAG_HASH_IP_DST;
+               ctrl |= YT921X_LAG_HASH_IP_SRC;
+               ctrl |= YT921X_LAG_HASH_IP_PROTO;
+               break;
+       case NETDEV_LAG_HASH_L2:
+               ctrl |= YT921X_LAG_HASH_MAC_DA;
+               ctrl |= YT921X_LAG_HASH_MAC_SA;
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       /* Check if we are the unique configured LAG */
+       unique_lag = true;
+       dsa_lags_foreach_id(i, ds->dst)
+               if (i != lag.id && dsa_lag_by_id(ds->dst, i)) {
+                       unique_lag = false;
+                       break;
+               }
+
+       mutex_lock(&priv->reg_lock);
+       do {
+               res = yt921x_lag_hash(priv, ctrl, unique_lag, extack);
+               if (res)
+                       break;
+
+               ctrl = 0;
+               dsa_lag_foreach_port(dp, ds->dst, &lag)
+                       ctrl |= BIT(dp->index);
+               res = yt921x_lag_set(priv, lag.id - 1, ctrl);
+       } while (0);
+       mutex_unlock(&priv->reg_lock);
+
+       return res;
+}
+
 static int yt921x_fdb_wait(struct yt921x_priv *priv, u32 *valp)
 {
        struct device *dev = to_device(priv);
@@ -2880,6 +3062,9 @@ static const struct dsa_switch_ops yt921x_dsa_switch_ops = {
        /* mirror */
        .port_mirror_del        = yt921x_dsa_port_mirror_del,
        .port_mirror_add        = yt921x_dsa_port_mirror_add,
+       /* lag */
+       .port_lag_leave         = yt921x_dsa_port_lag_leave,
+       .port_lag_join          = yt921x_dsa_port_lag_join,
        /* fdb */
        .port_fdb_dump          = yt921x_dsa_port_fdb_dump,
        .port_fast_age          = yt921x_dsa_port_fast_age,
@@ -2976,6 +3161,7 @@ static int yt921x_mdio_probe(struct mdio_device *mdiodev)
        ds->ageing_time_min = 1 * 5000;
        ds->ageing_time_max = U16_MAX * 5000;
        ds->phylink_mac_ops = &yt921x_phylink_mac_ops;
+       ds->num_lag_ids = YT921X_LAG_NUM;
        ds->num_ports = YT921X_PORT_NUM;
 
        mdiodev_set_drvdata(mdiodev, priv);
index 61bb0ab3b09a3882bfb765feb28eadb3cbd8abd5..bacd4ccaa8e50495a866317f198c01e11170ad42 100644 (file)
 #define  YT921X_FILTER_PORTn(port)             BIT(port)
 #define YT921X_VLAN_EGR_FILTER         0x180598
 #define  YT921X_VLAN_EGR_FILTER_PORTn(port)    BIT(port)
+#define YT921X_LAG_GROUPn(n)           (0x1805a8 + 4 * (n))
+#define  YT921X_LAG_GROUP_PORTS_M              GENMASK(13, 3)
+#define   YT921X_LAG_GROUP_PORTS(x)                    FIELD_PREP(YT921X_LAG_GROUP_PORTS_M, (x))
+#define  YT921X_LAG_GROUP_MEMBER_NUM_M         GENMASK(2, 0)
+#define   YT921X_LAG_GROUP_MEMBER_NUM(x)               FIELD_PREP(YT921X_LAG_GROUP_MEMBER_NUM_M, (x))
+#define YT921X_LAG_MEMBERnm(n, m)      (0x1805b0 + 4 * (4 * (n) + (m)))
+#define  YT921X_LAG_MEMBER_PORT_M              GENMASK(3, 0)
+#define   YT921X_LAG_MEMBER_PORT(x)                    FIELD_PREP(YT921X_LAG_MEMBER_PORT_M, (x))
 #define YT921X_CPU_COPY                        0x180690
 #define  YT921X_CPU_COPY_FORCE_INT_PORT                BIT(2)
 #define  YT921X_CPU_COPY_TO_INT_CPU            BIT(1)
 #define  YT921X_PORT_IGR_TPIDn_STAG(x)         BIT((x) + 4)
 #define  YT921X_PORT_IGR_TPIDn_CTAG_M          GENMASK(3, 0)
 #define  YT921X_PORT_IGR_TPIDn_CTAG(x)         BIT(x)
+#define YT921X_LAG_HASH                        0x210090
+#define  YT921X_LAG_HASH_L4_SPORT              BIT(7)
+#define  YT921X_LAG_HASH_L4_DPORT              BIT(6)
+#define  YT921X_LAG_HASH_IP_PROTO              BIT(5)
+#define  YT921X_LAG_HASH_IP_SRC                        BIT(4)
+#define  YT921X_LAG_HASH_IP_DST                        BIT(3)
+#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_PORTn_VLAN_CTRL(port)   (0x230010 + 4 * (port))
 #define  YT921X_PORT_VLAN_CTRL_SVLAN_PRI_EN    BIT(31)
@@ -458,6 +475,9 @@ enum yt921x_fdb_entry_status {
 
 #define YT921X_MSTI_NUM                16
 
+#define YT921X_LAG_NUM         2
+#define YT921X_LAG_PORT_NUM    4
+
 #define YT9215_MAJOR   0x9002
 #define YT9218_MAJOR   0x9001