// SPDX-License-Identifier: GPL-2.0+ #include #include "lan966x_main.h" static void lan966x_lag_set_aggr_pgids(struct lan966x *lan966x) { u32 visited = GENMASK(lan966x->num_phys_ports - 1, 0); int p, lag, i; /* Reset destination and aggregation PGIDS */ for (p = 0; p < lan966x->num_phys_ports; ++p) lan_wr(ANA_PGID_PGID_SET(BIT(p)), lan966x, ANA_PGID(p)); for (p = PGID_AGGR; p < PGID_SRC; ++p) lan_wr(ANA_PGID_PGID_SET(visited), lan966x, ANA_PGID(p)); /* The visited ports bitmask holds the list of ports offloading any * bonding interface. Initially we mark all these ports as unvisited, * then every time we visit a port in this bitmask, we know that it is * the lowest numbered port, i.e. the one whose logical ID == physical * port ID == LAG ID. So we mark as visited all further ports in the * bitmask that are offloading the same bonding interface. This way, * we set up the aggregation PGIDs only once per bonding interface. */ for (p = 0; p < lan966x->num_phys_ports; ++p) { struct lan966x_port *port = lan966x->ports[p]; if (!port || !port->bond) continue; visited &= ~BIT(p); } /* Now, set PGIDs for each active LAG */ for (lag = 0; lag < lan966x->num_phys_ports; ++lag) { struct net_device *bond = lan966x->ports[lag]->bond; int num_active_ports = 0; unsigned long bond_mask; u8 aggr_idx[16]; if (!bond || (visited & BIT(lag))) continue; bond_mask = lan966x_lag_get_mask(lan966x, bond); for_each_set_bit(p, &bond_mask, lan966x->num_phys_ports) { struct lan966x_port *port = lan966x->ports[p]; lan_wr(ANA_PGID_PGID_SET(bond_mask), lan966x, ANA_PGID(p)); if (port->lag_tx_active) aggr_idx[num_active_ports++] = p; } for (i = PGID_AGGR; i < PGID_SRC; ++i) { u32 ac; ac = lan_rd(lan966x, ANA_PGID(i)); ac &= ~bond_mask; /* Don't do division by zero if there was no active * port. Just make all aggregation codes zero. */ if (num_active_ports) ac |= BIT(aggr_idx[i % num_active_ports]); lan_wr(ANA_PGID_PGID_SET(ac), lan966x, ANA_PGID(i)); } /* Mark all ports in the same LAG as visited to avoid applying * the same config again. */ for (p = lag; p < lan966x->num_phys_ports; p++) { struct lan966x_port *port = lan966x->ports[p]; if (!port) continue; if (port->bond == bond) visited |= BIT(p); } } } static void lan966x_lag_set_port_ids(struct lan966x *lan966x) { struct lan966x_port *port; u32 bond_mask; u32 lag_id; int p; for (p = 0; p < lan966x->num_phys_ports; ++p) { port = lan966x->ports[p]; if (!port) continue; lag_id = port->chip_port; bond_mask = lan966x_lag_get_mask(lan966x, port->bond); if (bond_mask) lag_id = __ffs(bond_mask); lan_rmw(ANA_PORT_CFG_PORTID_VAL_SET(lag_id), ANA_PORT_CFG_PORTID_VAL, lan966x, ANA_PORT_CFG(port->chip_port)); } } static void lan966x_lag_update_ids(struct lan966x *lan966x) { lan966x_lag_set_port_ids(lan966x); lan966x_update_fwd_mask(lan966x); lan966x_lag_set_aggr_pgids(lan966x); } int lan966x_lag_port_join(struct lan966x_port *port, struct net_device *brport_dev, struct net_device *bond, struct netlink_ext_ack *extack) { struct lan966x *lan966x = port->lan966x; struct net_device *dev = port->dev; u32 lag_id = -1; u32 bond_mask; int err; bond_mask = lan966x_lag_get_mask(lan966x, bond); if (bond_mask) lag_id = __ffs(bond_mask); port->bond = bond; lan966x_lag_update_ids(lan966x); err = switchdev_bridge_port_offload(brport_dev, dev, port, &lan966x_switchdev_nb, &lan966x_switchdev_blocking_nb, false, extack); if (err) goto out; lan966x_port_stp_state_set(port, br_port_get_stp_state(brport_dev)); if (lan966x_lag_first_port(port->bond, port->dev) && lag_id != -1) lan966x_mac_lag_replace_port_entry(lan966x, lan966x->ports[lag_id], port); return 0; out: port->bond = NULL; lan966x_lag_update_ids(lan966x); return err; } void lan966x_lag_port_leave(struct lan966x_port *port, struct net_device *bond) { struct lan966x *lan966x = port->lan966x; u32 bond_mask; u32 lag_id; if (lan966x_lag_first_port(port->bond, port->dev)) { bond_mask = lan966x_lag_get_mask(lan966x, port->bond); bond_mask &= ~BIT(port->chip_port); if (bond_mask) { lag_id = __ffs(bond_mask); lan966x_mac_lag_replace_port_entry(lan966x, port, lan966x->ports[lag_id]); } else { lan966x_mac_lag_remove_port_entry(lan966x, port); } } port->bond = NULL; lan966x_lag_update_ids(lan966x); lan966x_port_stp_state_set(port, BR_STATE_FORWARDING); } static bool lan966x_lag_port_check_hash_types(struct lan966x *lan966x, enum netdev_lag_hash hash_type) { int p; for (p = 0; p < lan966x->num_phys_ports; ++p) { struct lan966x_port *port = lan966x->ports[p]; if (!port || !port->bond) continue; if (port->hash_type != hash_type) return false; } return true; } int lan966x_lag_port_prechangeupper(struct net_device *dev, struct netdev_notifier_changeupper_info *info) { struct lan966x_port *port = netdev_priv(dev); struct lan966x *lan966x = port->lan966x; struct netdev_lag_upper_info *lui; struct netlink_ext_ack *extack; extack = netdev_notifier_info_to_extack(&info->info); lui = info->upper_info; if (!lui) { port->hash_type = NETDEV_LAG_HASH_NONE; return NOTIFY_DONE; } if (lui->tx_type != NETDEV_LAG_TX_TYPE_HASH) { NL_SET_ERR_MSG_MOD(extack, "LAG device using unsupported Tx type"); return -EINVAL; } if (!lan966x_lag_port_check_hash_types(lan966x, lui->hash_type)) { NL_SET_ERR_MSG_MOD(extack, "LAG devices can have only the same hash_type"); return -EINVAL; } switch (lui->hash_type) { case NETDEV_LAG_HASH_L2: lan_wr(ANA_AGGR_CFG_AC_DMAC_ENA_SET(1) | ANA_AGGR_CFG_AC_SMAC_ENA_SET(1), lan966x, ANA_AGGR_CFG); break; case NETDEV_LAG_HASH_L34: lan_wr(ANA_AGGR_CFG_AC_IP6_TCPUDP_ENA_SET(1) | ANA_AGGR_CFG_AC_IP4_TCPUDP_ENA_SET(1) | ANA_AGGR_CFG_AC_IP4_SIPDIP_ENA_SET(1), lan966x, ANA_AGGR_CFG); break; case NETDEV_LAG_HASH_L23: lan_wr(ANA_AGGR_CFG_AC_DMAC_ENA_SET(1) | ANA_AGGR_CFG_AC_SMAC_ENA_SET(1) | ANA_AGGR_CFG_AC_IP6_TCPUDP_ENA_SET(1) | ANA_AGGR_CFG_AC_IP4_TCPUDP_ENA_SET(1), lan966x, ANA_AGGR_CFG); break; default: NL_SET_ERR_MSG_MOD(extack, "LAG device using unsupported hash type"); return -EINVAL; } port->hash_type = lui->hash_type; return NOTIFY_OK; } int lan966x_lag_port_changelowerstate(struct net_device *dev, struct netdev_notifier_changelowerstate_info *info) { struct netdev_lag_lower_state_info *lag = info->lower_state_info; struct lan966x_port *port = netdev_priv(dev); struct lan966x *lan966x = port->lan966x; bool is_active; if (!port->bond) return NOTIFY_DONE; is_active = lag->link_up && lag->tx_enabled; if (port->lag_tx_active == is_active) return NOTIFY_DONE; port->lag_tx_active = is_active; lan966x_lag_set_aggr_pgids(lan966x); return NOTIFY_OK; } int lan966x_lag_netdev_prechangeupper(struct net_device *dev, struct netdev_notifier_changeupper_info *info) { struct lan966x_port *port; struct net_device *lower; struct list_head *iter; int err; netdev_for_each_lower_dev(dev, lower, iter) { if (!lan966x_netdevice_check(lower)) continue; port = netdev_priv(lower); if (port->bond != dev) continue; err = lan966x_port_prechangeupper(lower, dev, info); if (err) return err; } return NOTIFY_DONE; } int lan966x_lag_netdev_changeupper(struct net_device *dev, struct netdev_notifier_changeupper_info *info) { struct lan966x_port *port; struct net_device *lower; struct list_head *iter; int err; netdev_for_each_lower_dev(dev, lower, iter) { if (!lan966x_netdevice_check(lower)) continue; port = netdev_priv(lower); if (port->bond != dev) continue; err = lan966x_port_changeupper(lower, dev, info); if (err) return err; } return NOTIFY_DONE; } bool lan966x_lag_first_port(struct net_device *lag, struct net_device *dev) { struct lan966x_port *port = netdev_priv(dev); struct lan966x *lan966x = port->lan966x; unsigned long bond_mask; if (port->bond != lag) return false; bond_mask = lan966x_lag_get_mask(lan966x, lag); if (bond_mask && port->chip_port == __ffs(bond_mask)) return true; return false; } u32 lan966x_lag_get_mask(struct lan966x *lan966x, struct net_device *bond) { struct lan966x_port *port; u32 mask = 0; int p; if (!bond) return mask; for (p = 0; p < lan966x->num_phys_ports; p++) { port = lan966x->ports[p]; if (!port) continue; if (port->bond == bond) mask |= BIT(p); } return mask; }