From 9fa8d8341c947da319574ae07dfcf88c6af57c9b Mon Sep 17 00:00:00 2001 From: Birger Koblitz Date: Tue, 18 Jan 2022 17:16:48 +0100 Subject: [PATCH] realtek: Backport LAG functionality for DSA Add the LAG configuration API for DSA as found in Linux 5.12 so that we can implement it in the dsa driver. Signed-off-by: Sebastian Gottschall Signed-off-by: Birger Koblitz --- .../patches-5.10/709-lag-offloading.patch | 1203 +++++++++++++++++ 1 file changed, 1203 insertions(+) create mode 100644 target/linux/realtek/patches-5.10/709-lag-offloading.patch diff --git a/target/linux/realtek/patches-5.10/709-lag-offloading.patch b/target/linux/realtek/patches-5.10/709-lag-offloading.patch new file mode 100644 index 0000000000..6a61d3e969 --- /dev/null +++ b/target/linux/realtek/patches-5.10/709-lag-offloading.patch @@ -0,0 +1,1203 @@ +diff -urpN linux-5.10.59.old/drivers/net/bonding/bond_main.c linux-5.10.59/drivers/net/bonding/bond_main.c +--- linux-5.10.59.old/drivers/net/bonding/bond_main.c 2021-08-18 01:44:57.333078456 +0700 ++++ linux-5.10.59/drivers/net/bonding/bond_main.c 2021-08-18 01:33:09.584221682 +0700 +@@ -2046,6 +2046,8 @@ int bond_enslave(struct net_device *bond + goto err_unregister; + } + ++ bond_lower_state_changed(new_slave); ++ + res = bond_sysfs_slave_add(new_slave); + if (res) { + slave_dbg(bond_dev, slave_dev, "Error %d calling bond_sysfs_slave_add\n", res); +diff -urpN linux-5.10.59.old/drivers/net/dsa/mv88e6xxx/chip.c linux-5.10.59/drivers/net/dsa/mv88e6xxx/chip.c +--- linux-5.10.59.old/drivers/net/dsa/mv88e6xxx/chip.c 2021-08-18 01:44:57.333078456 +0700 ++++ linux-5.10.59/drivers/net/dsa/mv88e6xxx/chip.c 2021-08-18 01:33:09.584221682 +0700 +@@ -1389,15 +1389,32 @@ static int mv88e6xxx_mac_setup(struct mv + + static int mv88e6xxx_pvt_map(struct mv88e6xxx_chip *chip, int dev, int port) + { ++ struct dsa_switch_tree *dst = chip->ds->dst; ++ struct dsa_switch *ds; ++ struct dsa_port *dp; + u16 pvlan = 0; + + if (!mv88e6xxx_has_pvt(chip)) + return 0; + + /* Skip the local source device, which uses in-chip port VLAN */ +- if (dev != chip->ds->index) ++ if (dev != chip->ds->index) { + pvlan = mv88e6xxx_port_vlan(chip, dev, port); + ++ ds = dsa_switch_find(dst->index, dev); ++ dp = ds ? dsa_to_port(ds, port) : NULL; ++ if (dp && dp->lag_dev) { ++ /* As the PVT is used to limit flooding of ++ * FORWARD frames, which use the LAG ID as the ++ * source port, we must translate dev/port to ++ * the special "LAG device" in the PVT, using ++ * the LAG ID as the port number. ++ */ ++ dev = MV88E6XXX_G2_PVT_ADRR_DEV_TRUNK; ++ port = dsa_lag_id(dst, dp->lag_dev); ++ } ++ } ++ + return mv88e6xxx_g2_pvt_write(chip, dev, port, pvlan); + } + +@@ -5352,6 +5369,271 @@ static int mv88e6xxx_port_egress_floods( + return err; + } + ++static bool mv88e6xxx_lag_can_offload(struct dsa_switch *ds, ++ struct net_device *lag, ++ struct netdev_lag_upper_info *info) ++{ ++ struct dsa_port *dp; ++ int id, members = 0; ++ ++ id = dsa_lag_id(ds->dst, lag); ++ if (id < 0 || id >= ds->num_lag_ids) ++ return false; ++ ++ dsa_lag_foreach_port(dp, ds->dst, lag) ++ /* Includes the port joining the LAG */ ++ members++; ++ ++ if (members > 8) ++ return false; ++ ++ /* We could potentially relax this to include active ++ * backup in the future. ++ */ ++ if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH) ++ return false; ++ ++ /* Ideally we would also validate that the hash type matches ++ * the hardware. Alas, this is always set to unknown on team ++ * interfaces. ++ */ ++ return true; ++} ++ ++static int mv88e6xxx_lag_sync_map(struct dsa_switch *ds, struct net_device *lag) ++{ ++ struct mv88e6xxx_chip *chip = ds->priv; ++ struct dsa_port *dp; ++ u16 map = 0; ++ int id; ++ ++ id = dsa_lag_id(ds->dst, lag); ++ ++ /* Build the map of all ports to distribute flows destined for ++ * this LAG. This can be either a local user port, or a DSA ++ * port if the LAG port is on a remote chip. ++ */ ++ dsa_lag_foreach_port(dp, ds->dst, lag) ++ map |= BIT(dsa_towards_port(ds, dp->ds->index, dp->index)); ++ ++ return mv88e6xxx_g2_trunk_mapping_write(chip, id, map); ++} ++ ++static const u8 mv88e6xxx_lag_mask_table[8][8] = { ++ /* Row number corresponds to the number of active members in a ++ * LAG. Each column states which of the eight hash buckets are ++ * mapped to the column:th port in the LAG. ++ * ++ * Example: In a LAG with three active ports, the second port ++ * ([2][1]) would be selected for traffic mapped to buckets ++ * 3,4,5 (0x38). ++ */ ++ { 0xff, 0, 0, 0, 0, 0, 0, 0 }, ++ { 0x0f, 0xf0, 0, 0, 0, 0, 0, 0 }, ++ { 0x07, 0x38, 0xc0, 0, 0, 0, 0, 0 }, ++ { 0x03, 0x0c, 0x30, 0xc0, 0, 0, 0, 0 }, ++ { 0x03, 0x0c, 0x30, 0x40, 0x80, 0, 0, 0 }, ++ { 0x03, 0x0c, 0x10, 0x20, 0x40, 0x80, 0, 0 }, ++ { 0x03, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0 }, ++ { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 }, ++}; ++ ++static void mv88e6xxx_lag_set_port_mask(u16 *mask, int port, ++ int num_tx, int nth) ++{ ++ u8 active = 0; ++ int i; ++ ++ num_tx = num_tx <= 8 ? num_tx : 8; ++ if (nth < num_tx) ++ active = mv88e6xxx_lag_mask_table[num_tx - 1][nth]; ++ ++ for (i = 0; i < 8; i++) { ++ if (BIT(i) & active) ++ mask[i] |= BIT(port); ++ } ++} ++ ++static int mv88e6xxx_lag_sync_masks(struct dsa_switch *ds) ++{ ++ struct mv88e6xxx_chip *chip = ds->priv; ++ unsigned int id, num_tx; ++ struct net_device *lag; ++ struct dsa_port *dp; ++ int i, err, nth; ++ u16 mask[8]; ++ u16 ivec; ++ ++ /* Assume no port is a member of any LAG. */ ++ ivec = BIT(mv88e6xxx_num_ports(chip)) - 1; ++ ++ /* Disable all masks for ports that _are_ members of a LAG. */ ++ list_for_each_entry(dp, &ds->dst->ports, list) { ++ if (!dp->lag_dev || dp->ds != ds) ++ continue; ++ ++ ivec &= ~BIT(dp->index); ++ } ++ ++ for (i = 0; i < 8; i++) ++ mask[i] = ivec; ++ ++ /* Enable the correct subset of masks for all LAG ports that ++ * are in the Tx set. ++ */ ++ dsa_lags_foreach_id(id, ds->dst) { ++ lag = dsa_lag_dev(ds->dst, id); ++ if (!lag) ++ continue; ++ ++ num_tx = 0; ++ dsa_lag_foreach_port(dp, ds->dst, lag) { ++ if (dp->lag_tx_enabled) ++ num_tx++; ++ } ++ ++ if (!num_tx) ++ continue; ++ ++ nth = 0; ++ dsa_lag_foreach_port(dp, ds->dst, lag) { ++ if (!dp->lag_tx_enabled) ++ continue; ++ ++ if (dp->ds == ds) ++ mv88e6xxx_lag_set_port_mask(mask, dp->index, ++ num_tx, nth); ++ ++ nth++; ++ } ++ } ++ ++ for (i = 0; i < 8; i++) { ++ err = mv88e6xxx_g2_trunk_mask_write(chip, i, true, mask[i]); ++ if (err) ++ return err; ++ } ++ ++ return 0; ++} ++ ++static int mv88e6xxx_lag_sync_masks_map(struct dsa_switch *ds, ++ struct net_device *lag) ++{ ++ int err; ++ ++ err = mv88e6xxx_lag_sync_masks(ds); ++ ++ if (!err) ++ err = mv88e6xxx_lag_sync_map(ds, lag); ++ ++ return err; ++} ++ ++static int mv88e6xxx_port_lag_change(struct dsa_switch *ds, int port) ++{ ++ struct mv88e6xxx_chip *chip = ds->priv; ++ int err; ++ ++ mv88e6xxx_reg_lock(chip); ++ err = mv88e6xxx_lag_sync_masks(ds); ++ mv88e6xxx_reg_unlock(chip); ++ return err; ++} ++ ++static int mv88e6xxx_port_lag_join(struct dsa_switch *ds, int port, ++ struct net_device *lag, ++ struct netdev_lag_upper_info *info) ++{ ++ struct mv88e6xxx_chip *chip = ds->priv; ++ int err, id; ++ ++ if (!mv88e6xxx_lag_can_offload(ds, lag, info)) ++ return -EOPNOTSUPP; ++ ++ id = dsa_lag_id(ds->dst, lag); ++ ++ mv88e6xxx_reg_lock(chip); ++ ++ err = mv88e6xxx_port_set_trunk(chip, port, true, id); ++ if (err) ++ goto err_unlock; ++ ++ err = mv88e6xxx_lag_sync_masks_map(ds, lag); ++ if (err) ++ goto err_clear_trunk; ++ ++ mv88e6xxx_reg_unlock(chip); ++ return 0; ++ ++err_clear_trunk: ++ mv88e6xxx_port_set_trunk(chip, port, false, 0); ++err_unlock: ++ mv88e6xxx_reg_unlock(chip); ++ return err; ++} ++ ++static int mv88e6xxx_port_lag_leave(struct dsa_switch *ds, int port, ++ struct net_device *lag) ++{ ++ struct mv88e6xxx_chip *chip = ds->priv; ++ int err_sync, err_trunk; ++ ++ mv88e6xxx_reg_lock(chip); ++ err_sync = mv88e6xxx_lag_sync_masks_map(ds, lag); ++ err_trunk = mv88e6xxx_port_set_trunk(chip, port, false, 0); ++ mv88e6xxx_reg_unlock(chip); ++ return err_sync ? : err_trunk; ++} ++ ++static int mv88e6xxx_crosschip_lag_change(struct dsa_switch *ds, int sw_index, ++ int port) ++{ ++ struct mv88e6xxx_chip *chip = ds->priv; ++ int err; ++ ++ mv88e6xxx_reg_lock(chip); ++ err = mv88e6xxx_lag_sync_masks(ds); ++ mv88e6xxx_reg_unlock(chip); ++ return err; ++} ++ ++static int mv88e6xxx_crosschip_lag_join(struct dsa_switch *ds, int sw_index, ++ int port, struct net_device *lag, ++ struct netdev_lag_upper_info *info) ++{ ++ struct mv88e6xxx_chip *chip = ds->priv; ++ int err; ++ ++ if (!mv88e6xxx_lag_can_offload(ds, lag, info)) ++ return -EOPNOTSUPP; ++ ++ mv88e6xxx_reg_lock(chip); ++ ++ err = mv88e6xxx_lag_sync_masks_map(ds, lag); ++ if (err) ++ goto unlock; ++ ++ err = mv88e6xxx_pvt_map(chip, sw_index, port); ++ ++unlock: ++ mv88e6xxx_reg_unlock(chip); ++ return err; ++} ++ ++static int mv88e6xxx_crosschip_lag_leave(struct dsa_switch *ds, int sw_index, ++ int port, struct net_device *lag) ++{ ++ struct mv88e6xxx_chip *chip = ds->priv; ++ int err_sync, err_pvt; ++ ++ mv88e6xxx_reg_lock(chip); ++ err_sync = mv88e6xxx_lag_sync_masks_map(ds, lag); ++ err_pvt = mv88e6xxx_pvt_map(chip, sw_index, port); ++ mv88e6xxx_reg_unlock(chip); ++ return err_sync ? : err_pvt; ++} ++ + static const struct dsa_switch_ops mv88e6xxx_switch_ops = { + .get_tag_protocol = mv88e6xxx_get_tag_protocol, + .setup = mv88e6xxx_setup, +@@ -5406,6 +5688,12 @@ static const struct dsa_switch_ops mv88e + .devlink_param_get = mv88e6xxx_devlink_param_get, + .devlink_param_set = mv88e6xxx_devlink_param_set, + .devlink_info_get = mv88e6xxx_devlink_info_get, ++ .port_lag_change = mv88e6xxx_port_lag_change, ++ .port_lag_join = mv88e6xxx_port_lag_join, ++ .port_lag_leave = mv88e6xxx_port_lag_leave, ++ .crosschip_lag_change = mv88e6xxx_crosschip_lag_change, ++ .crosschip_lag_join = mv88e6xxx_crosschip_lag_join, ++ .crosschip_lag_leave = mv88e6xxx_crosschip_lag_leave, + }; + + static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip) +@@ -5426,6 +5714,12 @@ static int mv88e6xxx_register_switch(str + ds->ageing_time_max = chip->info->age_time_coeff * U8_MAX; + ds->assisted_learning_on_cpu_port = true; + ++ /* Some chips support up to 32, but that requires enabling the ++ * 5-bit port mode, which we do not support. 640k^W16 ought to ++ * be enough for anyone. ++ */ ++ ds->num_lag_ids = 16; ++ + dev_set_drvdata(dev, ds); + + return dsa_register_switch(ds); +diff -urpN linux-5.10.59.old/drivers/net/dsa/mv88e6xxx/global2.c linux-5.10.59/drivers/net/dsa/mv88e6xxx/global2.c +--- linux-5.10.59.old/drivers/net/dsa/mv88e6xxx/global2.c 2021-08-18 01:44:57.333078456 +0700 ++++ linux-5.10.59/drivers/net/dsa/mv88e6xxx/global2.c 2021-08-18 01:33:09.584221682 +0700 +@@ -126,8 +126,8 @@ int mv88e6xxx_g2_device_mapping_write(st + + /* Offset 0x07: Trunk Mask Table register */ + +-static int mv88e6xxx_g2_trunk_mask_write(struct mv88e6xxx_chip *chip, int num, +- bool hash, u16 mask) ++int mv88e6xxx_g2_trunk_mask_write(struct mv88e6xxx_chip *chip, int num, ++ bool hash, u16 mask) + { + u16 val = (num << 12) | (mask & mv88e6xxx_port_mask(chip)); + +@@ -140,8 +140,8 @@ static int mv88e6xxx_g2_trunk_mask_write + + /* Offset 0x08: Trunk Mapping Table register */ + +-static int mv88e6xxx_g2_trunk_mapping_write(struct mv88e6xxx_chip *chip, int id, +- u16 map) ++int mv88e6xxx_g2_trunk_mapping_write(struct mv88e6xxx_chip *chip, int id, ++ u16 map) + { + const u16 port_mask = BIT(mv88e6xxx_num_ports(chip)) - 1; + u16 val = (id << 11) | (map & port_mask); +diff -urpN linux-5.10.59.old/drivers/net/dsa/mv88e6xxx/global2.h linux-5.10.59/drivers/net/dsa/mv88e6xxx/global2.h +--- linux-5.10.59.old/drivers/net/dsa/mv88e6xxx/global2.h 2021-08-18 01:44:57.333078456 +0700 ++++ linux-5.10.59/drivers/net/dsa/mv88e6xxx/global2.h 2021-08-18 01:33:09.584221682 +0700 +@@ -101,6 +101,7 @@ + #define MV88E6XXX_G2_PVT_ADDR_OP_WRITE_PVLAN 0x3000 + #define MV88E6XXX_G2_PVT_ADDR_OP_READ 0x4000 + #define MV88E6XXX_G2_PVT_ADDR_PTR_MASK 0x01ff ++#define MV88E6XXX_G2_PVT_ADRR_DEV_TRUNK 0x1f + + /* Offset 0x0C: Cross-chip Port VLAN Data Register */ + #define MV88E6XXX_G2_PVT_DATA 0x0c +@@ -345,6 +346,10 @@ int mv88e6352_g2_mgmt_rsvd2cpu(struct mv + + int mv88e6xxx_g2_pot_clear(struct mv88e6xxx_chip *chip); + ++int mv88e6xxx_g2_trunk_mask_write(struct mv88e6xxx_chip *chip, int num, ++ bool hash, u16 mask); ++int mv88e6xxx_g2_trunk_mapping_write(struct mv88e6xxx_chip *chip, int id, ++ u16 map); + int mv88e6xxx_g2_trunk_clear(struct mv88e6xxx_chip *chip); + + int mv88e6xxx_g2_device_mapping_write(struct mv88e6xxx_chip *chip, int target, +diff -urpN linux-5.10.59.old/drivers/net/dsa/mv88e6xxx/port.c linux-5.10.59/drivers/net/dsa/mv88e6xxx/port.c +--- linux-5.10.59.old/drivers/net/dsa/mv88e6xxx/port.c 2021-08-18 01:44:57.333078456 +0700 ++++ linux-5.10.59/drivers/net/dsa/mv88e6xxx/port.c 2021-08-18 01:33:09.584221682 +0700 +@@ -815,6 +815,27 @@ int mv88e6xxx_port_set_message_port(stru + return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_CTL1, val); + } + ++int mv88e6xxx_port_set_trunk(struct mv88e6xxx_chip *chip, int port, ++ bool trunk, u8 id) ++{ ++ u16 val; ++ int err; ++ ++ err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_CTL1, &val); ++ if (err) ++ return err; ++ ++ val &= ~MV88E6XXX_PORT_CTL1_TRUNK_ID_MASK; ++ ++ if (trunk) ++ val |= MV88E6XXX_PORT_CTL1_TRUNK_PORT | ++ (id << MV88E6XXX_PORT_CTL1_TRUNK_ID_SHIFT); ++ else ++ val &= ~MV88E6XXX_PORT_CTL1_TRUNK_PORT; ++ ++ return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_CTL1, val); ++} ++ + /* Offset 0x06: Port Based VLAN Map */ + + int mv88e6xxx_port_set_vlan_map(struct mv88e6xxx_chip *chip, int port, u16 map) +diff -urpN linux-5.10.59.old/drivers/net/dsa/mv88e6xxx/port.h linux-5.10.59/drivers/net/dsa/mv88e6xxx/port.h +--- linux-5.10.59.old/drivers/net/dsa/mv88e6xxx/port.h 2021-08-18 01:44:57.333078456 +0700 ++++ linux-5.10.59/drivers/net/dsa/mv88e6xxx/port.h 2021-08-18 01:33:09.584221682 +0700 +@@ -168,6 +168,9 @@ + /* Offset 0x05: Port Control 1 */ + #define MV88E6XXX_PORT_CTL1 0x05 + #define MV88E6XXX_PORT_CTL1_MESSAGE_PORT 0x8000 ++#define MV88E6XXX_PORT_CTL1_TRUNK_PORT 0x4000 ++#define MV88E6XXX_PORT_CTL1_TRUNK_ID_MASK 0x0f00 ++#define MV88E6XXX_PORT_CTL1_TRUNK_ID_SHIFT 8 + #define MV88E6XXX_PORT_CTL1_FID_11_4_MASK 0x00ff + + /* Offset 0x06: Port Based VLAN Map */ +@@ -348,6 +351,8 @@ int mv88e6351_port_set_ether_type(struct + u16 etype); + int mv88e6xxx_port_set_message_port(struct mv88e6xxx_chip *chip, int port, + bool message_port); ++int mv88e6xxx_port_set_trunk(struct mv88e6xxx_chip *chip, int port, ++ bool trunk, u8 id); + int mv88e6165_port_set_jumbo_size(struct mv88e6xxx_chip *chip, int port, + size_t size); + int mv88e6095_port_egress_rate_limiting(struct mv88e6xxx_chip *chip, int port); +diff -urpN linux-5.10.59.old/include/net/dsa.h linux-5.10.59/include/net/dsa.h +--- linux-5.10.59.old/include/net/dsa.h 2021-08-18 01:44:57.333078456 +0700 ++++ linux-5.10.59/include/net/dsa.h 2021-08-18 01:33:09.584221682 +0700 +@@ -149,8 +149,41 @@ struct dsa_switch_tree { + + /* List of DSA links composing the routing table */ + struct list_head rtable; ++ ++ /* Maps offloaded LAG netdevs to a zero-based linear ID for ++ * drivers that need it. ++ */ ++ struct net_device **lags; ++ unsigned int lags_len; + }; + ++#define dsa_lags_foreach_id(_id, _dst) \ ++ for ((_id) = 0; (_id) < (_dst)->lags_len; (_id)++) \ ++ if ((_dst)->lags[(_id)]) ++ ++#define dsa_lag_foreach_port(_dp, _dst, _lag) \ ++ list_for_each_entry((_dp), &(_dst)->ports, list) \ ++ if ((_dp)->lag_dev == (_lag)) ++ ++static inline struct net_device *dsa_lag_dev(struct dsa_switch_tree *dst, ++ unsigned int id) ++{ ++ return dst->lags[id]; ++} ++ ++static inline int dsa_lag_id(struct dsa_switch_tree *dst, ++ struct net_device *lag) ++{ ++ unsigned int id; ++ ++ dsa_lags_foreach_id(id, dst) { ++ if (dsa_lag_dev(dst, id) == lag) ++ return id; ++ } ++ ++ return -ENODEV; ++} ++ + /* TC matchall action types */ + enum dsa_port_mall_action_type { + DSA_PORT_MALL_MIRROR, +@@ -220,6 +253,8 @@ struct dsa_port { + bool devlink_port_setup; + struct phylink *pl; + struct phylink_config pl_config; ++ struct net_device *lag_dev; ++ bool lag_tx_enabled; + + struct list_head list; + +@@ -340,6 +375,14 @@ struct dsa_switch { + */ + bool mtu_enforcement_ingress; + ++ /* Drivers that benefit from having an ID associated with each ++ * offloaded LAG should set this to the maximum number of ++ * supported IDs. DSA will then maintain a mapping of _at ++ * least_ these many IDs, accessible to drivers via ++ * dsa_lag_id(). ++ */ ++ unsigned int num_lag_ids; ++ + size_t num_ports; + }; + +@@ -432,6 +475,18 @@ static inline bool dsa_port_is_vlan_filt + return dp->vlan_filtering; + } + ++static inline ++struct net_device *dsa_port_to_bridge_port(const struct dsa_port *dp) ++{ ++ if (!dp->bridge_dev) ++ return NULL; ++ ++ if (dp->lag_dev) ++ return dp->lag_dev; ++ ++ return dp->slave; ++} ++ + typedef int dsa_fdb_dump_cb_t(const unsigned char *addr, u16 vid, + bool is_static, void *data); + struct dsa_switch_ops { +@@ -629,6 +684,13 @@ struct dsa_switch_ops { + void (*crosschip_bridge_leave)(struct dsa_switch *ds, int tree_index, + int sw_index, int port, + struct net_device *br); ++ int (*crosschip_lag_change)(struct dsa_switch *ds, int sw_index, ++ int port); ++ int (*crosschip_lag_join)(struct dsa_switch *ds, int sw_index, ++ int port, struct net_device *lag, ++ struct netdev_lag_upper_info *info); ++ int (*crosschip_lag_leave)(struct dsa_switch *ds, int sw_index, ++ int port, struct net_device *lag); + + /* + * PTP functionality +@@ -660,6 +722,16 @@ struct dsa_switch_ops { + int (*port_change_mtu)(struct dsa_switch *ds, int port, + int new_mtu); + int (*port_max_mtu)(struct dsa_switch *ds, int port); ++ ++ /* ++ * LAG integration ++ */ ++ int (*port_lag_change)(struct dsa_switch *ds, int port); ++ int (*port_lag_join)(struct dsa_switch *ds, int port, ++ struct net_device *lag, ++ struct netdev_lag_upper_info *info); ++ int (*port_lag_leave)(struct dsa_switch *ds, int port, ++ struct net_device *lag); + }; + + #define DSA_DEVLINK_PARAM_DRIVER(_id, _name, _type, _cmodes) \ +diff -urpN linux-5.10.59.old/net/dsa/dsa.c linux-5.10.59/net/dsa/dsa.c +--- linux-5.10.59.old/net/dsa/dsa.c 2021-08-18 01:44:57.333078456 +0700 ++++ linux-5.10.59/net/dsa/dsa.c 2021-08-18 01:33:09.584221682 +0700 +@@ -220,11 +220,21 @@ static int dsa_switch_rcv(struct sk_buff + } + + skb = nskb; +- p = netdev_priv(skb->dev); + skb_push(skb, ETH_HLEN); + skb->pkt_type = PACKET_HOST; + skb->protocol = eth_type_trans(skb, skb->dev); + ++ if (unlikely(!dsa_slave_dev_check(skb->dev))) { ++ /* Packet is to be injected directly on an upper ++ * device, e.g. a team/bond, so skip all DSA-port ++ * specific actions. ++ */ ++ netif_rx(skb); ++ return 0; ++ } ++ ++ p = netdev_priv(skb->dev); ++ + if (unlikely(cpu_dp->ds->untag_bridge_pvid)) { + nskb = dsa_untag_bridge_pvid(skb); + if (!nskb) { +diff -urpN linux-5.10.59.old/net/dsa/dsa2.c linux-5.10.59/net/dsa/dsa2.c +--- linux-5.10.59.old/net/dsa/dsa2.c 2021-08-18 01:44:57.333078456 +0700 ++++ linux-5.10.59/net/dsa/dsa2.c 2021-08-18 01:33:09.584221682 +0700 +@@ -21,6 +21,65 @@ + static DEFINE_MUTEX(dsa2_mutex); + LIST_HEAD(dsa_tree_list); + ++/** ++ * dsa_lag_map() - Map LAG netdev to a linear LAG ID ++ * @dst: Tree in which to record the mapping. ++ * @lag: Netdev that is to be mapped to an ID. ++ * ++ * dsa_lag_id/dsa_lag_dev can then be used to translate between the ++ * two spaces. The size of the mapping space is determined by the ++ * driver by setting ds->num_lag_ids. It is perfectly legal to leave ++ * it unset if it is not needed, in which case these functions become ++ * no-ops. ++ */ ++void dsa_lag_map(struct dsa_switch_tree *dst, struct net_device *lag) ++{ ++ unsigned int id; ++ ++ if (dsa_lag_id(dst, lag) >= 0) ++ /* Already mapped */ ++ return; ++ ++ for (id = 0; id < dst->lags_len; id++) { ++ if (!dsa_lag_dev(dst, id)) { ++ dst->lags[id] = lag; ++ return; ++ } ++ } ++ ++ /* No IDs left, which is OK. Some drivers do not need it. The ++ * ones that do, e.g. mv88e6xxx, will discover that dsa_lag_id ++ * returns an error for this device when joining the LAG. The ++ * driver can then return -EOPNOTSUPP back to DSA, which will ++ * fall back to a software LAG. ++ */ ++} ++ ++/** ++ * dsa_lag_unmap() - Remove a LAG ID mapping ++ * @dst: Tree in which the mapping is recorded. ++ * @lag: Netdev that was mapped. ++ * ++ * As there may be multiple users of the mapping, it is only removed ++ * if there are no other references to it. ++ */ ++void dsa_lag_unmap(struct dsa_switch_tree *dst, struct net_device *lag) ++{ ++ struct dsa_port *dp; ++ unsigned int id; ++ ++ dsa_lag_foreach_port(dp, dst, lag) ++ /* There are remaining users of this mapping */ ++ return; ++ ++ dsa_lags_foreach_id(id, dst) { ++ if (dsa_lag_dev(dst, id) == lag) { ++ dst->lags[id] = NULL; ++ break; ++ } ++ } ++} ++ + struct dsa_switch *dsa_switch_find(int tree_index, int sw_index) + { + struct dsa_switch_tree *dst; +@@ -591,6 +650,32 @@ static void dsa_tree_teardown_master(str + dsa_master_teardown(dp->master); + } + ++static int dsa_tree_setup_lags(struct dsa_switch_tree *dst) ++{ ++ unsigned int len = 0; ++ struct dsa_port *dp; ++ ++ list_for_each_entry(dp, &dst->ports, list) { ++ if (dp->ds->num_lag_ids > len) ++ len = dp->ds->num_lag_ids; ++ } ++ ++ if (!len) ++ return 0; ++ ++ dst->lags = kcalloc(len, sizeof(*dst->lags), GFP_KERNEL); ++ if (!dst->lags) ++ return -ENOMEM; ++ ++ dst->lags_len = len; ++ return 0; ++} ++ ++static void dsa_tree_teardown_lags(struct dsa_switch_tree *dst) ++{ ++ kfree(dst->lags); ++} ++ + static int dsa_tree_setup(struct dsa_switch_tree *dst) + { + bool complete; +@@ -618,12 +703,18 @@ static int dsa_tree_setup(struct dsa_swi + if (err) + goto teardown_switches; + ++ err = dsa_tree_setup_lags(dst); ++ if (err) ++ goto teardown_master; ++ + dst->setup = true; + + pr_info("DSA: tree %d setup\n", dst->index); + + return 0; + ++teardown_master: ++ dsa_tree_teardown_master(dst); + teardown_switches: + dsa_tree_teardown_switches(dst); + teardown_default_cpu: +@@ -639,6 +730,8 @@ static void dsa_tree_teardown(struct dsa + if (!dst->setup) + return; + ++ dsa_tree_teardown_lags(dst); ++ + dsa_tree_teardown_master(dst); + + dsa_tree_teardown_switches(dst); +diff -urpN linux-5.10.59.old/net/dsa/dsa_priv.h linux-5.10.59/net/dsa/dsa_priv.h +--- linux-5.10.59.old/net/dsa/dsa_priv.h 2021-08-18 01:44:57.333078456 +0700 ++++ linux-5.10.59/net/dsa/dsa_priv.h 2021-08-18 01:42:18.952881506 +0700 +@@ -20,6 +20,9 @@ enum { + DSA_NOTIFIER_BRIDGE_LEAVE, + DSA_NOTIFIER_FDB_ADD, + DSA_NOTIFIER_FDB_DEL, ++ DSA_NOTIFIER_LAG_CHANGE, ++ DSA_NOTIFIER_LAG_JOIN, ++ DSA_NOTIFIER_LAG_LEAVE, + DSA_NOTIFIER_MDB_ADD, + DSA_NOTIFIER_MDB_DEL, + DSA_NOTIFIER_VLAN_ADD, +@@ -57,6 +60,15 @@ struct dsa_notifier_mdb_info { + int port; + }; + ++/* DSA_NOTIFIER_LAG_* */ ++struct dsa_notifier_lag_info { ++ struct net_device *lag; ++ int sw_index; ++ int port; ++ ++ struct netdev_lag_upper_info *info; ++}; ++ + /* DSA_NOTIFIER_VLAN_* */ + struct dsa_notifier_vlan_info { + const struct switchdev_obj_port_vlan *vlan; +@@ -149,6 +161,11 @@ void dsa_port_disable_rt(struct dsa_port + void dsa_port_disable(struct dsa_port *dp); + int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br); + void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br); ++int dsa_port_lag_change(struct dsa_port *dp, ++ struct netdev_lag_lower_state_info *linfo); ++int dsa_port_lag_join(struct dsa_port *dp, struct net_device *lag_dev, ++ struct netdev_lag_upper_info *uinfo); ++void dsa_port_lag_leave(struct dsa_port *dp, struct net_device *lag_dev); + int dsa_port_vlan_filtering(struct dsa_port *dp, bool vlan_filtering, + struct switchdev_trans *trans); + bool dsa_port_skip_vlan_configuration(struct dsa_port *dp); +@@ -181,6 +198,71 @@ int dsa_port_link_register_of(struct dsa + void dsa_port_link_unregister_of(struct dsa_port *dp); + extern const struct phylink_mac_ops dsa_port_phylink_mac_ops; + ++static inline bool dsa_port_offloads_netdev(struct dsa_port *dp, ++ struct net_device *dev) ++{ ++ /* Switchdev offloading can be configured on: */ ++ ++ if (dev == dp->slave) ++ /* DSA ports directly connected to a bridge, and event ++ * was emitted for the ports themselves. ++ */ ++ return true; ++ ++ if (dp->bridge_dev == dev) ++ /* DSA ports connected to a bridge, and event was emitted ++ * for the bridge. ++ */ ++ return true; ++ ++ if (dp->lag_dev == dev) ++ /* DSA ports connected to a bridge via a LAG */ ++ return true; ++ ++ return false; ++} ++ ++static inline bool dsa_port_offloads_bridge_port(struct dsa_port *dp, ++ struct net_device *dev) ++{ ++ return dsa_port_to_bridge_port(dp) == dev; ++} ++ ++static inline bool dsa_port_offloads_bridge(struct dsa_port *dp, ++ struct net_device *bridge_dev) ++{ ++ /* DSA ports connected to a bridge, and event was emitted ++ * for the bridge. ++ */ ++ return dp->bridge_dev == bridge_dev; ++} ++ ++/* Returns true if any port of this tree offloads the given net_device */ ++static inline bool dsa_tree_offloads_bridge_port(struct dsa_switch_tree *dst, ++ struct net_device *dev) ++{ ++ struct dsa_port *dp; ++ ++ list_for_each_entry(dp, &dst->ports, list) ++ if (dsa_port_offloads_bridge_port(dp, dev)) ++ return true; ++ ++ return false; ++} ++ ++/* Returns true if any port of this tree offloads the given net_device */ ++static inline bool dsa_tree_offloads_netdev(struct dsa_switch_tree *dst, ++ struct net_device *dev) ++{ ++ struct dsa_port *dp; ++ ++ list_for_each_entry(dp, &dst->ports, list) ++ if (dsa_port_offloads_netdev(dp, dev)) ++ return true; ++ ++ return false; ++} ++ + /* slave.c */ + extern const struct dsa_device_ops notag_netdev_ops; + void dsa_slave_mii_bus_init(struct dsa_switch *ds); +@@ -271,6 +353,9 @@ int dsa_switch_register_notifier(struct + void dsa_switch_unregister_notifier(struct dsa_switch *ds); + + /* dsa2.c */ ++void dsa_lag_map(struct dsa_switch_tree *dst, struct net_device *lag); ++void dsa_lag_unmap(struct dsa_switch_tree *dst, struct net_device *lag); ++ + extern struct list_head dsa_tree_list; + + #endif +diff -urpN linux-5.10.59.old/net/dsa/port.c linux-5.10.59/net/dsa/port.c +--- linux-5.10.59.old/net/dsa/port.c 2021-08-18 01:44:57.333078456 +0700 ++++ linux-5.10.59/net/dsa/port.c 2021-08-18 01:33:09.584221682 +0700 +@@ -193,6 +193,99 @@ void dsa_port_bridge_leave(struct dsa_po + dsa_port_set_state_now(dp, BR_STATE_FORWARDING); + } + ++int dsa_port_lag_change(struct dsa_port *dp, ++ struct netdev_lag_lower_state_info *linfo) ++{ ++ struct dsa_notifier_lag_info info = { ++ .sw_index = dp->ds->index, ++ .port = dp->index, ++ }; ++ bool tx_enabled; ++ ++ if (!dp->lag_dev) ++ return 0; ++ ++ /* On statically configured aggregates (e.g. loadbalance ++ * without LACP) ports will always be tx_enabled, even if the ++ * link is down. Thus we require both link_up and tx_enabled ++ * in order to include it in the tx set. ++ */ ++ tx_enabled = linfo->link_up && linfo->tx_enabled; ++ ++ if (tx_enabled == dp->lag_tx_enabled) ++ return 0; ++ ++ dp->lag_tx_enabled = tx_enabled; ++ ++ return dsa_port_notify(dp, DSA_NOTIFIER_LAG_CHANGE, &info); ++} ++ ++int dsa_port_lag_join(struct dsa_port *dp, struct net_device *lag, ++ struct netdev_lag_upper_info *uinfo) ++{ ++ struct dsa_notifier_lag_info info = { ++ .sw_index = dp->ds->index, ++ .port = dp->index, ++ .lag = lag, ++ .info = uinfo, ++ }; ++ struct net_device *bridge_dev; ++ int err; ++ ++ dsa_lag_map(dp->ds->dst, lag); ++ dp->lag_dev = lag; ++ ++ err = dsa_port_notify(dp, DSA_NOTIFIER_LAG_JOIN, &info); ++ if (err) ++ goto err_lag_join; ++ ++ bridge_dev = netdev_master_upper_dev_get(lag); ++ if (!bridge_dev || !netif_is_bridge_master(bridge_dev)) ++ return 0; ++ ++ err = dsa_port_bridge_join(dp, bridge_dev); ++ if (err) ++ goto err_bridge_join; ++ ++ return 0; ++ ++err_bridge_join: ++ dsa_port_notify(dp, DSA_NOTIFIER_LAG_LEAVE, &info); ++err_lag_join: ++ dp->lag_dev = NULL; ++ dsa_lag_unmap(dp->ds->dst, lag); ++ return err; ++} ++ ++void dsa_port_lag_leave(struct dsa_port *dp, struct net_device *lag) ++{ ++ struct dsa_notifier_lag_info info = { ++ .sw_index = dp->ds->index, ++ .port = dp->index, ++ .lag = lag, ++ }; ++ int err; ++ ++ if (!dp->lag_dev) ++ return; ++ ++ /* Port might have been part of a LAG that in turn was ++ * attached to a bridge. ++ */ ++ if (dp->bridge_dev) ++ dsa_port_bridge_leave(dp, dp->bridge_dev); ++ ++ dp->lag_tx_enabled = false; ++ dp->lag_dev = NULL; ++ ++ err = dsa_port_notify(dp, DSA_NOTIFIER_LAG_LEAVE, &info); ++ if (err) ++ pr_err("DSA: failed to notify DSA_NOTIFIER_LAG_LEAVE: %d\n", ++ err); ++ ++ dsa_lag_unmap(dp->ds->dst, lag); ++} ++ + /* Must be called under rcu_read_lock() */ + static bool dsa_port_can_apply_vlan_filtering(struct dsa_port *dp, + bool vlan_filtering) +diff -urpN linux-5.10.59.old/net/dsa/slave.c linux-5.10.59/net/dsa/slave.c +--- linux-5.10.59.old/net/dsa/slave.c 2021-08-18 01:44:57.333078456 +0700 ++++ linux-5.10.59/net/dsa/slave.c 2021-08-18 01:33:09.584221682 +0700 +@@ -337,9 +355,6 @@ static int dsa_slave_vlan_add(struct net + struct switchdev_obj_port_vlan vlan; + int vid, err; + +- if (obj->orig_dev != dev) +- return -EOPNOTSUPP; +- + if (dsa_port_skip_vlan_configuration(dp)) + return 0; + +@@ -394,11 +409,13 @@ static int dsa_slave_port_obj_add(struct + + switch (obj->id) { + case SWITCHDEV_OBJ_ID_PORT_MDB: +- if (obj->orig_dev != dev) ++ if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev)) + return -EOPNOTSUPP; + err = dsa_port_mdb_add(dp, SWITCHDEV_OBJ_PORT_MDB(obj), trans); + break; + case SWITCHDEV_OBJ_ID_HOST_MDB: ++ if (!dsa_port_offloads_bridge(dp, obj->orig_dev)) ++ return -EOPNOTSUPP; + /* DSA can directly translate this to a normal MDB add, + * but on the CPU port. + */ +@@ -406,6 +423,9 @@ static int dsa_slave_port_obj_add(struct + trans); + break; + case SWITCHDEV_OBJ_ID_PORT_VLAN: ++ if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev)) ++ return -EOPNOTSUPP; ++ + err = dsa_slave_vlan_add(dev, obj, trans); + break; + default: +@@ -424,9 +444,6 @@ static int dsa_slave_vlan_del(struct net + struct switchdev_obj_port_vlan *vlan; + int vid, err; + +- if (obj->orig_dev != dev) +- return -EOPNOTSUPP; +- + if (dsa_port_skip_vlan_configuration(dp)) + return 0; + +@@ -453,17 +470,22 @@ static int dsa_slave_port_obj_del(struct + + switch (obj->id) { + case SWITCHDEV_OBJ_ID_PORT_MDB: +- if (obj->orig_dev != dev) ++ if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev)) + return -EOPNOTSUPP; + err = dsa_port_mdb_del(dp, SWITCHDEV_OBJ_PORT_MDB(obj)); + break; + case SWITCHDEV_OBJ_ID_HOST_MDB: ++ if (!dsa_port_offloads_bridge(dp, obj->orig_dev)) ++ return -EOPNOTSUPP; + /* DSA can directly translate this to a normal MDB add, + * but on the CPU port. + */ + err = dsa_port_mdb_del(dp->cpu_dp, SWITCHDEV_OBJ_PORT_MDB(obj)); + break; + case SWITCHDEV_OBJ_ID_PORT_VLAN: ++ if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev)) ++ return -EOPNOTSUPP; ++ + err = dsa_slave_vlan_del(dev, obj); + break; + default: +@@ -1995,6 +2017,46 @@ static int dsa_slave_changeupper(struct + dsa_port_bridge_leave(dp, info->upper_dev); + err = NOTIFY_OK; + } ++ } else if (netif_is_lag_master(info->upper_dev)) { ++ if (info->linking) { ++ err = dsa_port_lag_join(dp, info->upper_dev, ++ info->upper_info); ++ if (err == -EOPNOTSUPP) { ++ NL_SET_ERR_MSG_MOD(info->info.extack, ++ "Offloading not supported"); ++ err = 0; ++ } ++ err = notifier_from_errno(err); ++ } else { ++ dsa_port_lag_leave(dp, info->upper_dev); ++ err = NOTIFY_OK; ++ } ++ } ++ ++ return err; ++} ++ ++static int ++dsa_slave_lag_changeupper(struct net_device *dev, ++ struct netdev_notifier_changeupper_info *info) ++{ ++ struct net_device *lower; ++ struct list_head *iter; ++ int err = NOTIFY_DONE; ++ struct dsa_port *dp; ++ ++ netdev_for_each_lower_dev(dev, lower, iter) { ++ if (!dsa_slave_dev_check(lower)) ++ continue; ++ ++ dp = dsa_slave_to_port(lower); ++ if (!dp->lag_dev) ++ /* Software LAG */ ++ continue; ++ ++ err = dsa_slave_changeupper(lower, info); ++ if (notifier_to_errno(err)) ++ break; + } + + return err; +@@ -2080,10 +2142,26 @@ static int dsa_slave_netdevice_event(str + break; + } + case NETDEV_CHANGEUPPER: ++ if (dsa_slave_dev_check(dev)) ++ return dsa_slave_changeupper(dev, ptr); ++ ++ if (netif_is_lag_master(dev)) ++ return dsa_slave_lag_changeupper(dev, ptr); ++ ++ break; ++ case NETDEV_CHANGELOWERSTATE: { ++ struct netdev_notifier_changelowerstate_info *info = ptr; ++ struct dsa_port *dp; ++ int err; ++ + if (!dsa_slave_dev_check(dev)) +- return NOTIFY_DONE; ++ break; ++ ++ dp = dsa_slave_to_port(dev); + +- return dsa_slave_changeupper(dev, ptr); ++ err = dsa_port_lag_change(dp, info->lower_state_info); ++ return notifier_from_errno(err); ++ } + } + + return NOTIFY_DONE; +@@ -2231,6 +2309,15 @@ static int dsa_slave_switchdev_event(str + if (!fdb_info->added_by_user && + !dp->ds->assisted_learning_on_cpu_port) + return NOTIFY_DONE; ++ ++ /* When the bridge learns an address on an offloaded ++ * LAG we don't want to send traffic to the CPU, the ++ * other ports bridged with the LAG should be able to ++ * autonomously forward towards it. ++ */ ++ if (dsa_tree_offloads_netdev(dp->ds->dst, dev)) ++ return NOTIFY_DONE; ++ + } + + if (!dp->ds->ops->port_fdb_add || !dp->ds->ops->port_fdb_del) +diff -urpN linux-5.10.59.old/net/dsa/switch.c linux-5.10.59/net/dsa/switch.c +--- linux-5.10.59.old/net/dsa/switch.c 2021-08-18 01:44:57.333078456 +0700 ++++ linux-5.10.59/net/dsa/switch.c 2021-08-18 01:33:09.584221682 +0700 +@@ -178,6 +178,47 @@ static int dsa_switch_fdb_del(struct dsa + return ds->ops->port_fdb_del(ds, port, info->addr, info->vid); + } + ++static int dsa_switch_lag_change(struct dsa_switch *ds, ++ struct dsa_notifier_lag_info *info) ++{ ++ if (ds->index == info->sw_index && ds->ops->port_lag_change) ++ return ds->ops->port_lag_change(ds, info->port); ++ ++ if (ds->index != info->sw_index && ds->ops->crosschip_lag_change) ++ return ds->ops->crosschip_lag_change(ds, info->sw_index, ++ info->port); ++ ++ return 0; ++} ++ ++static int dsa_switch_lag_join(struct dsa_switch *ds, ++ struct dsa_notifier_lag_info *info) ++{ ++ if (ds->index == info->sw_index && ds->ops->port_lag_join) ++ return ds->ops->port_lag_join(ds, info->port, info->lag, ++ info->info); ++ ++ if (ds->index != info->sw_index && ds->ops->crosschip_lag_join) ++ return ds->ops->crosschip_lag_join(ds, info->sw_index, ++ info->port, info->lag, ++ info->info); ++ ++ return -EOPNOTSUPP; ++} ++ ++static int dsa_switch_lag_leave(struct dsa_switch *ds, ++ struct dsa_notifier_lag_info *info) ++{ ++ if (ds->index == info->sw_index && ds->ops->port_lag_leave) ++ return ds->ops->port_lag_leave(ds, info->port, info->lag); ++ ++ if (ds->index != info->sw_index && ds->ops->crosschip_lag_leave) ++ return ds->ops->crosschip_lag_leave(ds, info->sw_index, ++ info->port, info->lag); ++ ++ return -EOPNOTSUPP; ++} ++ + static bool dsa_switch_mdb_match(struct dsa_switch *ds, int port, + struct dsa_notifier_mdb_info *info) + { +@@ -325,6 +366,15 @@ static int dsa_switch_event(struct notif + case DSA_NOTIFIER_FDB_DEL: + err = dsa_switch_fdb_del(ds, info); + break; ++ case DSA_NOTIFIER_LAG_CHANGE: ++ err = dsa_switch_lag_change(ds, info); ++ break; ++ case DSA_NOTIFIER_LAG_JOIN: ++ err = dsa_switch_lag_join(ds, info); ++ break; ++ case DSA_NOTIFIER_LAG_LEAVE: ++ err = dsa_switch_lag_leave(ds, info); ++ break; + case DSA_NOTIFIER_MDB_ADD: + err = dsa_switch_mdb_add(ds, info); + break; +diff -urpN linux-5.10.59.old/net/dsa/tag_dsa.c linux-5.10.59/net/dsa/tag_dsa.c +--- linux-5.10.59.old/net/dsa/tag_dsa.c 2021-08-18 01:44:57.333078456 +0700 ++++ linux-5.10.59/net/dsa/tag_dsa.c 2021-08-18 01:33:09.584221682 +0700 +@@ -82,7 +82,19 @@ static struct sk_buff *dsa_rcv(struct sk + source_device = dsa_header[0] & 0x1f; + source_port = (dsa_header[1] >> 3) & 0x1f; + +- skb->dev = dsa_master_find_slave(dev, source_device, source_port); ++ if (trunk) { ++ struct dsa_port *cpu_dp = dev->dsa_ptr; ++ ++ /* The exact source port is not available in the tag, ++ * so we inject the frame directly on the upper ++ * team/bond. ++ */ ++ skb->dev = dsa_lag_dev(cpu_dp->dst, source_port); ++ } else { ++ skb->dev = dsa_master_find_slave(dev, source_device, ++ source_port); ++ } ++ + if (!skb->dev) + return NULL; + -- 2.30.2