162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci#include <linux/if_bridge.h> 462306a36Sopenharmony_ci 562306a36Sopenharmony_ci#include "lan966x_main.h" 662306a36Sopenharmony_ci 762306a36Sopenharmony_cistatic void lan966x_lag_set_aggr_pgids(struct lan966x *lan966x) 862306a36Sopenharmony_ci{ 962306a36Sopenharmony_ci u32 visited = GENMASK(lan966x->num_phys_ports - 1, 0); 1062306a36Sopenharmony_ci int p, lag, i; 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci /* Reset destination and aggregation PGIDS */ 1362306a36Sopenharmony_ci for (p = 0; p < lan966x->num_phys_ports; ++p) 1462306a36Sopenharmony_ci lan_wr(ANA_PGID_PGID_SET(BIT(p)), 1562306a36Sopenharmony_ci lan966x, ANA_PGID(p)); 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci for (p = PGID_AGGR; p < PGID_SRC; ++p) 1862306a36Sopenharmony_ci lan_wr(ANA_PGID_PGID_SET(visited), 1962306a36Sopenharmony_ci lan966x, ANA_PGID(p)); 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci /* The visited ports bitmask holds the list of ports offloading any 2262306a36Sopenharmony_ci * bonding interface. Initially we mark all these ports as unvisited, 2362306a36Sopenharmony_ci * then every time we visit a port in this bitmask, we know that it is 2462306a36Sopenharmony_ci * the lowest numbered port, i.e. the one whose logical ID == physical 2562306a36Sopenharmony_ci * port ID == LAG ID. So we mark as visited all further ports in the 2662306a36Sopenharmony_ci * bitmask that are offloading the same bonding interface. This way, 2762306a36Sopenharmony_ci * we set up the aggregation PGIDs only once per bonding interface. 2862306a36Sopenharmony_ci */ 2962306a36Sopenharmony_ci for (p = 0; p < lan966x->num_phys_ports; ++p) { 3062306a36Sopenharmony_ci struct lan966x_port *port = lan966x->ports[p]; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci if (!port || !port->bond) 3362306a36Sopenharmony_ci continue; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci visited &= ~BIT(p); 3662306a36Sopenharmony_ci } 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci /* Now, set PGIDs for each active LAG */ 3962306a36Sopenharmony_ci for (lag = 0; lag < lan966x->num_phys_ports; ++lag) { 4062306a36Sopenharmony_ci struct lan966x_port *port = lan966x->ports[lag]; 4162306a36Sopenharmony_ci int num_active_ports = 0; 4262306a36Sopenharmony_ci struct net_device *bond; 4362306a36Sopenharmony_ci unsigned long bond_mask; 4462306a36Sopenharmony_ci u8 aggr_idx[16]; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci if (!port || !port->bond || (visited & BIT(lag))) 4762306a36Sopenharmony_ci continue; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci bond = port->bond; 5062306a36Sopenharmony_ci bond_mask = lan966x_lag_get_mask(lan966x, bond); 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci for_each_set_bit(p, &bond_mask, lan966x->num_phys_ports) { 5362306a36Sopenharmony_ci struct lan966x_port *port = lan966x->ports[p]; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci if (!port) 5662306a36Sopenharmony_ci continue; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci lan_wr(ANA_PGID_PGID_SET(bond_mask), 5962306a36Sopenharmony_ci lan966x, ANA_PGID(p)); 6062306a36Sopenharmony_ci if (port->lag_tx_active) 6162306a36Sopenharmony_ci aggr_idx[num_active_ports++] = p; 6262306a36Sopenharmony_ci } 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci for (i = PGID_AGGR; i < PGID_SRC; ++i) { 6562306a36Sopenharmony_ci u32 ac; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci ac = lan_rd(lan966x, ANA_PGID(i)); 6862306a36Sopenharmony_ci ac &= ~bond_mask; 6962306a36Sopenharmony_ci /* Don't do division by zero if there was no active 7062306a36Sopenharmony_ci * port. Just make all aggregation codes zero. 7162306a36Sopenharmony_ci */ 7262306a36Sopenharmony_ci if (num_active_ports) 7362306a36Sopenharmony_ci ac |= BIT(aggr_idx[i % num_active_ports]); 7462306a36Sopenharmony_ci lan_wr(ANA_PGID_PGID_SET(ac), 7562306a36Sopenharmony_ci lan966x, ANA_PGID(i)); 7662306a36Sopenharmony_ci } 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci /* Mark all ports in the same LAG as visited to avoid applying 7962306a36Sopenharmony_ci * the same config again. 8062306a36Sopenharmony_ci */ 8162306a36Sopenharmony_ci for (p = lag; p < lan966x->num_phys_ports; p++) { 8262306a36Sopenharmony_ci struct lan966x_port *port = lan966x->ports[p]; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci if (!port) 8562306a36Sopenharmony_ci continue; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci if (port->bond == bond) 8862306a36Sopenharmony_ci visited |= BIT(p); 8962306a36Sopenharmony_ci } 9062306a36Sopenharmony_ci } 9162306a36Sopenharmony_ci} 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_cistatic void lan966x_lag_set_port_ids(struct lan966x *lan966x) 9462306a36Sopenharmony_ci{ 9562306a36Sopenharmony_ci struct lan966x_port *port; 9662306a36Sopenharmony_ci u32 bond_mask; 9762306a36Sopenharmony_ci u32 lag_id; 9862306a36Sopenharmony_ci int p; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci for (p = 0; p < lan966x->num_phys_ports; ++p) { 10162306a36Sopenharmony_ci port = lan966x->ports[p]; 10262306a36Sopenharmony_ci if (!port) 10362306a36Sopenharmony_ci continue; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci lag_id = port->chip_port; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci bond_mask = lan966x_lag_get_mask(lan966x, port->bond); 10862306a36Sopenharmony_ci if (bond_mask) 10962306a36Sopenharmony_ci lag_id = __ffs(bond_mask); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci lan_rmw(ANA_PORT_CFG_PORTID_VAL_SET(lag_id), 11262306a36Sopenharmony_ci ANA_PORT_CFG_PORTID_VAL, 11362306a36Sopenharmony_ci lan966x, ANA_PORT_CFG(port->chip_port)); 11462306a36Sopenharmony_ci } 11562306a36Sopenharmony_ci} 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_cistatic void lan966x_lag_update_ids(struct lan966x *lan966x) 11862306a36Sopenharmony_ci{ 11962306a36Sopenharmony_ci lan966x_lag_set_port_ids(lan966x); 12062306a36Sopenharmony_ci lan966x_update_fwd_mask(lan966x); 12162306a36Sopenharmony_ci lan966x_lag_set_aggr_pgids(lan966x); 12262306a36Sopenharmony_ci} 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ciint lan966x_lag_port_join(struct lan966x_port *port, 12562306a36Sopenharmony_ci struct net_device *brport_dev, 12662306a36Sopenharmony_ci struct net_device *bond, 12762306a36Sopenharmony_ci struct netlink_ext_ack *extack) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci struct lan966x *lan966x = port->lan966x; 13062306a36Sopenharmony_ci struct net_device *dev = port->dev; 13162306a36Sopenharmony_ci u32 lag_id = -1; 13262306a36Sopenharmony_ci u32 bond_mask; 13362306a36Sopenharmony_ci int err; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci bond_mask = lan966x_lag_get_mask(lan966x, bond); 13662306a36Sopenharmony_ci if (bond_mask) 13762306a36Sopenharmony_ci lag_id = __ffs(bond_mask); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci port->bond = bond; 14062306a36Sopenharmony_ci lan966x_lag_update_ids(lan966x); 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci err = switchdev_bridge_port_offload(brport_dev, dev, port, 14362306a36Sopenharmony_ci &lan966x_switchdev_nb, 14462306a36Sopenharmony_ci &lan966x_switchdev_blocking_nb, 14562306a36Sopenharmony_ci false, extack); 14662306a36Sopenharmony_ci if (err) 14762306a36Sopenharmony_ci goto out; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci lan966x_port_stp_state_set(port, br_port_get_stp_state(brport_dev)); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci if (lan966x_lag_first_port(port->bond, port->dev) && 15262306a36Sopenharmony_ci lag_id != -1) 15362306a36Sopenharmony_ci lan966x_mac_lag_replace_port_entry(lan966x, 15462306a36Sopenharmony_ci lan966x->ports[lag_id], 15562306a36Sopenharmony_ci port); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci return 0; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ciout: 16062306a36Sopenharmony_ci port->bond = NULL; 16162306a36Sopenharmony_ci lan966x_lag_update_ids(lan966x); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci return err; 16462306a36Sopenharmony_ci} 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_civoid lan966x_lag_port_leave(struct lan966x_port *port, struct net_device *bond) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci struct lan966x *lan966x = port->lan966x; 16962306a36Sopenharmony_ci u32 bond_mask; 17062306a36Sopenharmony_ci u32 lag_id; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci if (lan966x_lag_first_port(port->bond, port->dev)) { 17362306a36Sopenharmony_ci bond_mask = lan966x_lag_get_mask(lan966x, port->bond); 17462306a36Sopenharmony_ci bond_mask &= ~BIT(port->chip_port); 17562306a36Sopenharmony_ci if (bond_mask) { 17662306a36Sopenharmony_ci lag_id = __ffs(bond_mask); 17762306a36Sopenharmony_ci lan966x_mac_lag_replace_port_entry(lan966x, port, 17862306a36Sopenharmony_ci lan966x->ports[lag_id]); 17962306a36Sopenharmony_ci } else { 18062306a36Sopenharmony_ci lan966x_mac_lag_remove_port_entry(lan966x, port); 18162306a36Sopenharmony_ci } 18262306a36Sopenharmony_ci } 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci port->bond = NULL; 18562306a36Sopenharmony_ci lan966x_lag_update_ids(lan966x); 18662306a36Sopenharmony_ci lan966x_port_stp_state_set(port, BR_STATE_FORWARDING); 18762306a36Sopenharmony_ci} 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic bool lan966x_lag_port_check_hash_types(struct lan966x *lan966x, 19062306a36Sopenharmony_ci enum netdev_lag_hash hash_type) 19162306a36Sopenharmony_ci{ 19262306a36Sopenharmony_ci int p; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci for (p = 0; p < lan966x->num_phys_ports; ++p) { 19562306a36Sopenharmony_ci struct lan966x_port *port = lan966x->ports[p]; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci if (!port || !port->bond) 19862306a36Sopenharmony_ci continue; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci if (port->hash_type != hash_type) 20162306a36Sopenharmony_ci return false; 20262306a36Sopenharmony_ci } 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci return true; 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ciint lan966x_lag_port_prechangeupper(struct net_device *dev, 20862306a36Sopenharmony_ci struct netdev_notifier_changeupper_info *info) 20962306a36Sopenharmony_ci{ 21062306a36Sopenharmony_ci struct lan966x_port *port = netdev_priv(dev); 21162306a36Sopenharmony_ci struct lan966x *lan966x = port->lan966x; 21262306a36Sopenharmony_ci struct netdev_lag_upper_info *lui; 21362306a36Sopenharmony_ci struct netlink_ext_ack *extack; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci extack = netdev_notifier_info_to_extack(&info->info); 21662306a36Sopenharmony_ci lui = info->upper_info; 21762306a36Sopenharmony_ci if (!lui) { 21862306a36Sopenharmony_ci port->hash_type = NETDEV_LAG_HASH_NONE; 21962306a36Sopenharmony_ci return NOTIFY_DONE; 22062306a36Sopenharmony_ci } 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci if (lui->tx_type != NETDEV_LAG_TX_TYPE_HASH) { 22362306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, 22462306a36Sopenharmony_ci "LAG device using unsupported Tx type"); 22562306a36Sopenharmony_ci return -EINVAL; 22662306a36Sopenharmony_ci } 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci if (!lan966x_lag_port_check_hash_types(lan966x, lui->hash_type)) { 22962306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, 23062306a36Sopenharmony_ci "LAG devices can have only the same hash_type"); 23162306a36Sopenharmony_ci return -EINVAL; 23262306a36Sopenharmony_ci } 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci switch (lui->hash_type) { 23562306a36Sopenharmony_ci case NETDEV_LAG_HASH_L2: 23662306a36Sopenharmony_ci lan_wr(ANA_AGGR_CFG_AC_DMAC_ENA_SET(1) | 23762306a36Sopenharmony_ci ANA_AGGR_CFG_AC_SMAC_ENA_SET(1), 23862306a36Sopenharmony_ci lan966x, ANA_AGGR_CFG); 23962306a36Sopenharmony_ci break; 24062306a36Sopenharmony_ci case NETDEV_LAG_HASH_L34: 24162306a36Sopenharmony_ci lan_wr(ANA_AGGR_CFG_AC_IP6_TCPUDP_ENA_SET(1) | 24262306a36Sopenharmony_ci ANA_AGGR_CFG_AC_IP4_TCPUDP_ENA_SET(1) | 24362306a36Sopenharmony_ci ANA_AGGR_CFG_AC_IP4_SIPDIP_ENA_SET(1), 24462306a36Sopenharmony_ci lan966x, ANA_AGGR_CFG); 24562306a36Sopenharmony_ci break; 24662306a36Sopenharmony_ci case NETDEV_LAG_HASH_L23: 24762306a36Sopenharmony_ci lan_wr(ANA_AGGR_CFG_AC_DMAC_ENA_SET(1) | 24862306a36Sopenharmony_ci ANA_AGGR_CFG_AC_SMAC_ENA_SET(1) | 24962306a36Sopenharmony_ci ANA_AGGR_CFG_AC_IP6_TCPUDP_ENA_SET(1) | 25062306a36Sopenharmony_ci ANA_AGGR_CFG_AC_IP4_TCPUDP_ENA_SET(1), 25162306a36Sopenharmony_ci lan966x, ANA_AGGR_CFG); 25262306a36Sopenharmony_ci break; 25362306a36Sopenharmony_ci default: 25462306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, 25562306a36Sopenharmony_ci "LAG device using unsupported hash type"); 25662306a36Sopenharmony_ci return -EINVAL; 25762306a36Sopenharmony_ci } 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci port->hash_type = lui->hash_type; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci return NOTIFY_OK; 26262306a36Sopenharmony_ci} 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ciint lan966x_lag_port_changelowerstate(struct net_device *dev, 26562306a36Sopenharmony_ci struct netdev_notifier_changelowerstate_info *info) 26662306a36Sopenharmony_ci{ 26762306a36Sopenharmony_ci struct netdev_lag_lower_state_info *lag = info->lower_state_info; 26862306a36Sopenharmony_ci struct lan966x_port *port = netdev_priv(dev); 26962306a36Sopenharmony_ci struct lan966x *lan966x = port->lan966x; 27062306a36Sopenharmony_ci bool is_active; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci if (!port->bond) 27362306a36Sopenharmony_ci return NOTIFY_DONE; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci is_active = lag->link_up && lag->tx_enabled; 27662306a36Sopenharmony_ci if (port->lag_tx_active == is_active) 27762306a36Sopenharmony_ci return NOTIFY_DONE; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci port->lag_tx_active = is_active; 28062306a36Sopenharmony_ci lan966x_lag_set_aggr_pgids(lan966x); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci return NOTIFY_OK; 28362306a36Sopenharmony_ci} 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ciint lan966x_lag_netdev_prechangeupper(struct net_device *dev, 28662306a36Sopenharmony_ci struct netdev_notifier_changeupper_info *info) 28762306a36Sopenharmony_ci{ 28862306a36Sopenharmony_ci struct lan966x_port *port; 28962306a36Sopenharmony_ci struct net_device *lower; 29062306a36Sopenharmony_ci struct list_head *iter; 29162306a36Sopenharmony_ci int err; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci netdev_for_each_lower_dev(dev, lower, iter) { 29462306a36Sopenharmony_ci if (!lan966x_netdevice_check(lower)) 29562306a36Sopenharmony_ci continue; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci port = netdev_priv(lower); 29862306a36Sopenharmony_ci if (port->bond != dev) 29962306a36Sopenharmony_ci continue; 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci err = lan966x_port_prechangeupper(lower, dev, info); 30262306a36Sopenharmony_ci if (err) 30362306a36Sopenharmony_ci return err; 30462306a36Sopenharmony_ci } 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci return NOTIFY_DONE; 30762306a36Sopenharmony_ci} 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ciint lan966x_lag_netdev_changeupper(struct net_device *dev, 31062306a36Sopenharmony_ci struct netdev_notifier_changeupper_info *info) 31162306a36Sopenharmony_ci{ 31262306a36Sopenharmony_ci struct lan966x_port *port; 31362306a36Sopenharmony_ci struct net_device *lower; 31462306a36Sopenharmony_ci struct list_head *iter; 31562306a36Sopenharmony_ci int err; 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci netdev_for_each_lower_dev(dev, lower, iter) { 31862306a36Sopenharmony_ci if (!lan966x_netdevice_check(lower)) 31962306a36Sopenharmony_ci continue; 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci port = netdev_priv(lower); 32262306a36Sopenharmony_ci if (port->bond != dev) 32362306a36Sopenharmony_ci continue; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci err = lan966x_port_changeupper(lower, dev, info); 32662306a36Sopenharmony_ci if (err) 32762306a36Sopenharmony_ci return err; 32862306a36Sopenharmony_ci } 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci return NOTIFY_DONE; 33162306a36Sopenharmony_ci} 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_cibool lan966x_lag_first_port(struct net_device *lag, struct net_device *dev) 33462306a36Sopenharmony_ci{ 33562306a36Sopenharmony_ci struct lan966x_port *port = netdev_priv(dev); 33662306a36Sopenharmony_ci struct lan966x *lan966x = port->lan966x; 33762306a36Sopenharmony_ci unsigned long bond_mask; 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci if (port->bond != lag) 34062306a36Sopenharmony_ci return false; 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci bond_mask = lan966x_lag_get_mask(lan966x, lag); 34362306a36Sopenharmony_ci if (bond_mask && port->chip_port == __ffs(bond_mask)) 34462306a36Sopenharmony_ci return true; 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci return false; 34762306a36Sopenharmony_ci} 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ciu32 lan966x_lag_get_mask(struct lan966x *lan966x, struct net_device *bond) 35062306a36Sopenharmony_ci{ 35162306a36Sopenharmony_ci struct lan966x_port *port; 35262306a36Sopenharmony_ci u32 mask = 0; 35362306a36Sopenharmony_ci int p; 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci if (!bond) 35662306a36Sopenharmony_ci return mask; 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci for (p = 0; p < lan966x->num_phys_ports; p++) { 35962306a36Sopenharmony_ci port = lan966x->ports[p]; 36062306a36Sopenharmony_ci if (!port) 36162306a36Sopenharmony_ci continue; 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci if (port->bond == bond) 36462306a36Sopenharmony_ci mask |= BIT(p); 36562306a36Sopenharmony_ci } 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci return mask; 36862306a36Sopenharmony_ci} 369