162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci#include <net/switchdev.h> 462306a36Sopenharmony_ci#include "lan966x_main.h" 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#define LAN966X_MAC_COLUMNS 4 762306a36Sopenharmony_ci#define MACACCESS_CMD_IDLE 0 862306a36Sopenharmony_ci#define MACACCESS_CMD_LEARN 1 962306a36Sopenharmony_ci#define MACACCESS_CMD_FORGET 2 1062306a36Sopenharmony_ci#define MACACCESS_CMD_AGE 3 1162306a36Sopenharmony_ci#define MACACCESS_CMD_GET_NEXT 4 1262306a36Sopenharmony_ci#define MACACCESS_CMD_INIT 5 1362306a36Sopenharmony_ci#define MACACCESS_CMD_READ 6 1462306a36Sopenharmony_ci#define MACACCESS_CMD_WRITE 7 1562306a36Sopenharmony_ci#define MACACCESS_CMD_SYNC_GET_NEXT 8 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define LAN966X_MAC_INVALID_ROW -1 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_cistruct lan966x_mac_entry { 2062306a36Sopenharmony_ci struct list_head list; 2162306a36Sopenharmony_ci unsigned char mac[ETH_ALEN] __aligned(2); 2262306a36Sopenharmony_ci u16 vid; 2362306a36Sopenharmony_ci u16 port_index; 2462306a36Sopenharmony_ci int row; 2562306a36Sopenharmony_ci bool lag; 2662306a36Sopenharmony_ci}; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistruct lan966x_mac_raw_entry { 2962306a36Sopenharmony_ci u32 mach; 3062306a36Sopenharmony_ci u32 macl; 3162306a36Sopenharmony_ci u32 maca; 3262306a36Sopenharmony_ci bool processed; 3362306a36Sopenharmony_ci}; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistatic int lan966x_mac_get_status(struct lan966x *lan966x) 3662306a36Sopenharmony_ci{ 3762306a36Sopenharmony_ci return lan_rd(lan966x, ANA_MACACCESS); 3862306a36Sopenharmony_ci} 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistatic int lan966x_mac_wait_for_completion(struct lan966x *lan966x) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci u32 val; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci return readx_poll_timeout_atomic(lan966x_mac_get_status, 4562306a36Sopenharmony_ci lan966x, val, 4662306a36Sopenharmony_ci (ANA_MACACCESS_MAC_TABLE_CMD_GET(val)) == 4762306a36Sopenharmony_ci MACACCESS_CMD_IDLE, 4862306a36Sopenharmony_ci TABLE_UPDATE_SLEEP_US, 4962306a36Sopenharmony_ci TABLE_UPDATE_TIMEOUT_US); 5062306a36Sopenharmony_ci} 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistatic void lan966x_mac_select(struct lan966x *lan966x, 5362306a36Sopenharmony_ci const unsigned char mac[ETH_ALEN], 5462306a36Sopenharmony_ci unsigned int vid) 5562306a36Sopenharmony_ci{ 5662306a36Sopenharmony_ci u32 macl = 0, mach = 0; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci /* Set the MAC address to handle and the vlan associated in a format 5962306a36Sopenharmony_ci * understood by the hardware. 6062306a36Sopenharmony_ci */ 6162306a36Sopenharmony_ci mach |= vid << 16; 6262306a36Sopenharmony_ci mach |= mac[0] << 8; 6362306a36Sopenharmony_ci mach |= mac[1] << 0; 6462306a36Sopenharmony_ci macl |= mac[2] << 24; 6562306a36Sopenharmony_ci macl |= mac[3] << 16; 6662306a36Sopenharmony_ci macl |= mac[4] << 8; 6762306a36Sopenharmony_ci macl |= mac[5] << 0; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci lan_wr(macl, lan966x, ANA_MACLDATA); 7062306a36Sopenharmony_ci lan_wr(mach, lan966x, ANA_MACHDATA); 7162306a36Sopenharmony_ci} 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic int __lan966x_mac_learn_locked(struct lan966x *lan966x, int pgid, 7462306a36Sopenharmony_ci bool cpu_copy, 7562306a36Sopenharmony_ci const unsigned char mac[ETH_ALEN], 7662306a36Sopenharmony_ci unsigned int vid, 7762306a36Sopenharmony_ci enum macaccess_entry_type type) 7862306a36Sopenharmony_ci{ 7962306a36Sopenharmony_ci lockdep_assert_held(&lan966x->mac_lock); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci lan966x_mac_select(lan966x, mac, vid); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci /* Issue a write command */ 8462306a36Sopenharmony_ci lan_wr(ANA_MACACCESS_VALID_SET(1) | 8562306a36Sopenharmony_ci ANA_MACACCESS_CHANGE2SW_SET(0) | 8662306a36Sopenharmony_ci ANA_MACACCESS_MAC_CPU_COPY_SET(cpu_copy) | 8762306a36Sopenharmony_ci ANA_MACACCESS_DEST_IDX_SET(pgid) | 8862306a36Sopenharmony_ci ANA_MACACCESS_ENTRYTYPE_SET(type) | 8962306a36Sopenharmony_ci ANA_MACACCESS_MAC_TABLE_CMD_SET(MACACCESS_CMD_LEARN), 9062306a36Sopenharmony_ci lan966x, ANA_MACACCESS); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci return lan966x_mac_wait_for_completion(lan966x); 9362306a36Sopenharmony_ci} 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic int __lan966x_mac_learn(struct lan966x *lan966x, int pgid, 9662306a36Sopenharmony_ci bool cpu_copy, 9762306a36Sopenharmony_ci const unsigned char mac[ETH_ALEN], 9862306a36Sopenharmony_ci unsigned int vid, 9962306a36Sopenharmony_ci enum macaccess_entry_type type) 10062306a36Sopenharmony_ci{ 10162306a36Sopenharmony_ci int ret; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci spin_lock(&lan966x->mac_lock); 10462306a36Sopenharmony_ci ret = __lan966x_mac_learn_locked(lan966x, pgid, cpu_copy, mac, vid, type); 10562306a36Sopenharmony_ci spin_unlock(&lan966x->mac_lock); 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci return ret; 10862306a36Sopenharmony_ci} 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci/* The mask of the front ports is encoded inside the mac parameter via a call 11162306a36Sopenharmony_ci * to lan966x_mdb_encode_mac(). 11262306a36Sopenharmony_ci */ 11362306a36Sopenharmony_ciint lan966x_mac_ip_learn(struct lan966x *lan966x, 11462306a36Sopenharmony_ci bool cpu_copy, 11562306a36Sopenharmony_ci const unsigned char mac[ETH_ALEN], 11662306a36Sopenharmony_ci unsigned int vid, 11762306a36Sopenharmony_ci enum macaccess_entry_type type) 11862306a36Sopenharmony_ci{ 11962306a36Sopenharmony_ci WARN_ON(type != ENTRYTYPE_MACV4 && type != ENTRYTYPE_MACV6); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci return __lan966x_mac_learn(lan966x, 0, cpu_copy, mac, vid, type); 12262306a36Sopenharmony_ci} 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ciint lan966x_mac_learn(struct lan966x *lan966x, int port, 12562306a36Sopenharmony_ci const unsigned char mac[ETH_ALEN], 12662306a36Sopenharmony_ci unsigned int vid, 12762306a36Sopenharmony_ci enum macaccess_entry_type type) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci WARN_ON(type != ENTRYTYPE_NORMAL && type != ENTRYTYPE_LOCKED); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci return __lan966x_mac_learn(lan966x, port, false, mac, vid, type); 13262306a36Sopenharmony_ci} 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistatic int lan966x_mac_learn_locked(struct lan966x *lan966x, int port, 13562306a36Sopenharmony_ci const unsigned char mac[ETH_ALEN], 13662306a36Sopenharmony_ci unsigned int vid, 13762306a36Sopenharmony_ci enum macaccess_entry_type type) 13862306a36Sopenharmony_ci{ 13962306a36Sopenharmony_ci WARN_ON(type != ENTRYTYPE_NORMAL && type != ENTRYTYPE_LOCKED); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci return __lan966x_mac_learn_locked(lan966x, port, false, mac, vid, type); 14262306a36Sopenharmony_ci} 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_cistatic int lan966x_mac_forget_locked(struct lan966x *lan966x, 14562306a36Sopenharmony_ci const unsigned char mac[ETH_ALEN], 14662306a36Sopenharmony_ci unsigned int vid, 14762306a36Sopenharmony_ci enum macaccess_entry_type type) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci lockdep_assert_held(&lan966x->mac_lock); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci lan966x_mac_select(lan966x, mac, vid); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci /* Issue a forget command */ 15462306a36Sopenharmony_ci lan_wr(ANA_MACACCESS_ENTRYTYPE_SET(type) | 15562306a36Sopenharmony_ci ANA_MACACCESS_MAC_TABLE_CMD_SET(MACACCESS_CMD_FORGET), 15662306a36Sopenharmony_ci lan966x, ANA_MACACCESS); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci return lan966x_mac_wait_for_completion(lan966x); 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ciint lan966x_mac_forget(struct lan966x *lan966x, 16262306a36Sopenharmony_ci const unsigned char mac[ETH_ALEN], 16362306a36Sopenharmony_ci unsigned int vid, 16462306a36Sopenharmony_ci enum macaccess_entry_type type) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci int ret; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci spin_lock(&lan966x->mac_lock); 16962306a36Sopenharmony_ci ret = lan966x_mac_forget_locked(lan966x, mac, vid, type); 17062306a36Sopenharmony_ci spin_unlock(&lan966x->mac_lock); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci return ret; 17362306a36Sopenharmony_ci} 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ciint lan966x_mac_cpu_learn(struct lan966x *lan966x, const char *addr, u16 vid) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci return lan966x_mac_learn(lan966x, PGID_CPU, addr, vid, ENTRYTYPE_LOCKED); 17862306a36Sopenharmony_ci} 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ciint lan966x_mac_cpu_forget(struct lan966x *lan966x, const char *addr, u16 vid) 18162306a36Sopenharmony_ci{ 18262306a36Sopenharmony_ci return lan966x_mac_forget(lan966x, addr, vid, ENTRYTYPE_LOCKED); 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_civoid lan966x_mac_set_ageing(struct lan966x *lan966x, 18662306a36Sopenharmony_ci u32 ageing) 18762306a36Sopenharmony_ci{ 18862306a36Sopenharmony_ci lan_rmw(ANA_AUTOAGE_AGE_PERIOD_SET(ageing / 2), 18962306a36Sopenharmony_ci ANA_AUTOAGE_AGE_PERIOD, 19062306a36Sopenharmony_ci lan966x, ANA_AUTOAGE); 19162306a36Sopenharmony_ci} 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_civoid lan966x_mac_init(struct lan966x *lan966x) 19462306a36Sopenharmony_ci{ 19562306a36Sopenharmony_ci /* Clear the MAC table */ 19662306a36Sopenharmony_ci lan_wr(MACACCESS_CMD_INIT, lan966x, ANA_MACACCESS); 19762306a36Sopenharmony_ci lan966x_mac_wait_for_completion(lan966x); 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci spin_lock_init(&lan966x->mac_lock); 20062306a36Sopenharmony_ci INIT_LIST_HEAD(&lan966x->mac_entries); 20162306a36Sopenharmony_ci} 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_cistatic struct lan966x_mac_entry *lan966x_mac_alloc_entry(struct lan966x_port *port, 20462306a36Sopenharmony_ci const unsigned char *mac, 20562306a36Sopenharmony_ci u16 vid) 20662306a36Sopenharmony_ci{ 20762306a36Sopenharmony_ci struct lan966x_mac_entry *mac_entry; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci mac_entry = kzalloc(sizeof(*mac_entry), GFP_ATOMIC); 21062306a36Sopenharmony_ci if (!mac_entry) 21162306a36Sopenharmony_ci return NULL; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci memcpy(mac_entry->mac, mac, ETH_ALEN); 21462306a36Sopenharmony_ci mac_entry->vid = vid; 21562306a36Sopenharmony_ci mac_entry->port_index = port->chip_port; 21662306a36Sopenharmony_ci mac_entry->row = LAN966X_MAC_INVALID_ROW; 21762306a36Sopenharmony_ci mac_entry->lag = port->bond ? true : false; 21862306a36Sopenharmony_ci return mac_entry; 21962306a36Sopenharmony_ci} 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_cistatic struct lan966x_mac_entry *lan966x_mac_find_entry(struct lan966x *lan966x, 22262306a36Sopenharmony_ci const unsigned char *mac, 22362306a36Sopenharmony_ci u16 vid, u16 port_index) 22462306a36Sopenharmony_ci{ 22562306a36Sopenharmony_ci struct lan966x_mac_entry *res = NULL; 22662306a36Sopenharmony_ci struct lan966x_mac_entry *mac_entry; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci list_for_each_entry(mac_entry, &lan966x->mac_entries, list) { 22962306a36Sopenharmony_ci if (mac_entry->vid == vid && 23062306a36Sopenharmony_ci ether_addr_equal(mac, mac_entry->mac) && 23162306a36Sopenharmony_ci mac_entry->port_index == port_index) { 23262306a36Sopenharmony_ci res = mac_entry; 23362306a36Sopenharmony_ci break; 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci } 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci return res; 23862306a36Sopenharmony_ci} 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_cistatic int lan966x_mac_lookup(struct lan966x *lan966x, 24162306a36Sopenharmony_ci const unsigned char mac[ETH_ALEN], 24262306a36Sopenharmony_ci unsigned int vid, enum macaccess_entry_type type) 24362306a36Sopenharmony_ci{ 24462306a36Sopenharmony_ci int ret; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci lan966x_mac_select(lan966x, mac, vid); 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci /* Issue a read command */ 24962306a36Sopenharmony_ci lan_wr(ANA_MACACCESS_ENTRYTYPE_SET(type) | 25062306a36Sopenharmony_ci ANA_MACACCESS_VALID_SET(1) | 25162306a36Sopenharmony_ci ANA_MACACCESS_MAC_TABLE_CMD_SET(MACACCESS_CMD_READ), 25262306a36Sopenharmony_ci lan966x, ANA_MACACCESS); 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci ret = lan966x_mac_wait_for_completion(lan966x); 25562306a36Sopenharmony_ci if (ret) 25662306a36Sopenharmony_ci return ret; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci return ANA_MACACCESS_VALID_GET(lan_rd(lan966x, ANA_MACACCESS)); 25962306a36Sopenharmony_ci} 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_cistatic void lan966x_fdb_call_notifiers(enum switchdev_notifier_type type, 26262306a36Sopenharmony_ci const char *mac, u16 vid, 26362306a36Sopenharmony_ci struct net_device *dev) 26462306a36Sopenharmony_ci{ 26562306a36Sopenharmony_ci struct switchdev_notifier_fdb_info info = { 0 }; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci info.addr = mac; 26862306a36Sopenharmony_ci info.vid = vid; 26962306a36Sopenharmony_ci info.offloaded = true; 27062306a36Sopenharmony_ci call_switchdev_notifiers(type, dev, &info.info, NULL); 27162306a36Sopenharmony_ci} 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ciint lan966x_mac_add_entry(struct lan966x *lan966x, struct lan966x_port *port, 27462306a36Sopenharmony_ci const unsigned char *addr, u16 vid) 27562306a36Sopenharmony_ci{ 27662306a36Sopenharmony_ci struct lan966x_mac_entry *mac_entry; 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci spin_lock(&lan966x->mac_lock); 27962306a36Sopenharmony_ci if (lan966x_mac_lookup(lan966x, addr, vid, ENTRYTYPE_NORMAL)) { 28062306a36Sopenharmony_ci spin_unlock(&lan966x->mac_lock); 28162306a36Sopenharmony_ci return 0; 28262306a36Sopenharmony_ci } 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci /* In case the entry already exists, don't add it again to SW, 28562306a36Sopenharmony_ci * just update HW, but we need to look in the actual HW because 28662306a36Sopenharmony_ci * it is possible for an entry to be learn by HW and before we 28762306a36Sopenharmony_ci * get the interrupt the frame will reach CPU and the CPU will 28862306a36Sopenharmony_ci * add the entry but without the extern_learn flag. 28962306a36Sopenharmony_ci */ 29062306a36Sopenharmony_ci mac_entry = lan966x_mac_find_entry(lan966x, addr, vid, port->chip_port); 29162306a36Sopenharmony_ci if (mac_entry) { 29262306a36Sopenharmony_ci spin_unlock(&lan966x->mac_lock); 29362306a36Sopenharmony_ci goto mac_learn; 29462306a36Sopenharmony_ci } 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci mac_entry = lan966x_mac_alloc_entry(port, addr, vid); 29762306a36Sopenharmony_ci if (!mac_entry) { 29862306a36Sopenharmony_ci spin_unlock(&lan966x->mac_lock); 29962306a36Sopenharmony_ci return -ENOMEM; 30062306a36Sopenharmony_ci } 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci list_add_tail(&mac_entry->list, &lan966x->mac_entries); 30362306a36Sopenharmony_ci spin_unlock(&lan966x->mac_lock); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci lan966x_fdb_call_notifiers(SWITCHDEV_FDB_OFFLOADED, addr, vid, 30662306a36Sopenharmony_ci port->bond ?: port->dev); 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_cimac_learn: 30962306a36Sopenharmony_ci lan966x_mac_learn(lan966x, port->chip_port, addr, vid, ENTRYTYPE_LOCKED); 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci return 0; 31262306a36Sopenharmony_ci} 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ciint lan966x_mac_del_entry(struct lan966x *lan966x, const unsigned char *addr, 31562306a36Sopenharmony_ci u16 vid) 31662306a36Sopenharmony_ci{ 31762306a36Sopenharmony_ci struct lan966x_mac_entry *mac_entry, *tmp; 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci spin_lock(&lan966x->mac_lock); 32062306a36Sopenharmony_ci list_for_each_entry_safe(mac_entry, tmp, &lan966x->mac_entries, 32162306a36Sopenharmony_ci list) { 32262306a36Sopenharmony_ci if (mac_entry->vid == vid && 32362306a36Sopenharmony_ci ether_addr_equal(addr, mac_entry->mac)) { 32462306a36Sopenharmony_ci lan966x_mac_forget_locked(lan966x, mac_entry->mac, 32562306a36Sopenharmony_ci mac_entry->vid, 32662306a36Sopenharmony_ci ENTRYTYPE_LOCKED); 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci list_del(&mac_entry->list); 32962306a36Sopenharmony_ci kfree(mac_entry); 33062306a36Sopenharmony_ci } 33162306a36Sopenharmony_ci } 33262306a36Sopenharmony_ci spin_unlock(&lan966x->mac_lock); 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci return 0; 33562306a36Sopenharmony_ci} 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_civoid lan966x_mac_lag_replace_port_entry(struct lan966x *lan966x, 33862306a36Sopenharmony_ci struct lan966x_port *src, 33962306a36Sopenharmony_ci struct lan966x_port *dst) 34062306a36Sopenharmony_ci{ 34162306a36Sopenharmony_ci struct lan966x_mac_entry *mac_entry; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci spin_lock(&lan966x->mac_lock); 34462306a36Sopenharmony_ci list_for_each_entry(mac_entry, &lan966x->mac_entries, list) { 34562306a36Sopenharmony_ci if (mac_entry->port_index == src->chip_port && 34662306a36Sopenharmony_ci mac_entry->lag) { 34762306a36Sopenharmony_ci lan966x_mac_forget_locked(lan966x, mac_entry->mac, 34862306a36Sopenharmony_ci mac_entry->vid, 34962306a36Sopenharmony_ci ENTRYTYPE_LOCKED); 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci lan966x_mac_learn_locked(lan966x, dst->chip_port, 35262306a36Sopenharmony_ci mac_entry->mac, mac_entry->vid, 35362306a36Sopenharmony_ci ENTRYTYPE_LOCKED); 35462306a36Sopenharmony_ci mac_entry->port_index = dst->chip_port; 35562306a36Sopenharmony_ci } 35662306a36Sopenharmony_ci } 35762306a36Sopenharmony_ci spin_unlock(&lan966x->mac_lock); 35862306a36Sopenharmony_ci} 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_civoid lan966x_mac_lag_remove_port_entry(struct lan966x *lan966x, 36162306a36Sopenharmony_ci struct lan966x_port *src) 36262306a36Sopenharmony_ci{ 36362306a36Sopenharmony_ci struct lan966x_mac_entry *mac_entry, *tmp; 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci spin_lock(&lan966x->mac_lock); 36662306a36Sopenharmony_ci list_for_each_entry_safe(mac_entry, tmp, &lan966x->mac_entries, 36762306a36Sopenharmony_ci list) { 36862306a36Sopenharmony_ci if (mac_entry->port_index == src->chip_port && 36962306a36Sopenharmony_ci mac_entry->lag) { 37062306a36Sopenharmony_ci lan966x_mac_forget_locked(lan966x, mac_entry->mac, 37162306a36Sopenharmony_ci mac_entry->vid, 37262306a36Sopenharmony_ci ENTRYTYPE_LOCKED); 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci list_del(&mac_entry->list); 37562306a36Sopenharmony_ci kfree(mac_entry); 37662306a36Sopenharmony_ci } 37762306a36Sopenharmony_ci } 37862306a36Sopenharmony_ci spin_unlock(&lan966x->mac_lock); 37962306a36Sopenharmony_ci} 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_civoid lan966x_mac_purge_entries(struct lan966x *lan966x) 38262306a36Sopenharmony_ci{ 38362306a36Sopenharmony_ci struct lan966x_mac_entry *mac_entry, *tmp; 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci spin_lock(&lan966x->mac_lock); 38662306a36Sopenharmony_ci list_for_each_entry_safe(mac_entry, tmp, &lan966x->mac_entries, 38762306a36Sopenharmony_ci list) { 38862306a36Sopenharmony_ci lan966x_mac_forget_locked(lan966x, mac_entry->mac, 38962306a36Sopenharmony_ci mac_entry->vid, ENTRYTYPE_LOCKED); 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci list_del(&mac_entry->list); 39262306a36Sopenharmony_ci kfree(mac_entry); 39362306a36Sopenharmony_ci } 39462306a36Sopenharmony_ci spin_unlock(&lan966x->mac_lock); 39562306a36Sopenharmony_ci} 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_cistatic void lan966x_mac_notifiers(enum switchdev_notifier_type type, 39862306a36Sopenharmony_ci unsigned char *mac, u32 vid, 39962306a36Sopenharmony_ci struct net_device *dev) 40062306a36Sopenharmony_ci{ 40162306a36Sopenharmony_ci rtnl_lock(); 40262306a36Sopenharmony_ci lan966x_fdb_call_notifiers(type, mac, vid, dev); 40362306a36Sopenharmony_ci rtnl_unlock(); 40462306a36Sopenharmony_ci} 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_cistatic void lan966x_mac_process_raw_entry(struct lan966x_mac_raw_entry *raw_entry, 40762306a36Sopenharmony_ci u8 *mac, u16 *vid, u32 *dest_idx) 40862306a36Sopenharmony_ci{ 40962306a36Sopenharmony_ci mac[0] = (raw_entry->mach >> 8) & 0xff; 41062306a36Sopenharmony_ci mac[1] = (raw_entry->mach >> 0) & 0xff; 41162306a36Sopenharmony_ci mac[2] = (raw_entry->macl >> 24) & 0xff; 41262306a36Sopenharmony_ci mac[3] = (raw_entry->macl >> 16) & 0xff; 41362306a36Sopenharmony_ci mac[4] = (raw_entry->macl >> 8) & 0xff; 41462306a36Sopenharmony_ci mac[5] = (raw_entry->macl >> 0) & 0xff; 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci *vid = (raw_entry->mach >> 16) & 0xfff; 41762306a36Sopenharmony_ci *dest_idx = ANA_MACACCESS_DEST_IDX_GET(raw_entry->maca); 41862306a36Sopenharmony_ci} 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_cistatic void lan966x_mac_irq_process(struct lan966x *lan966x, u32 row, 42162306a36Sopenharmony_ci struct lan966x_mac_raw_entry *raw_entries) 42262306a36Sopenharmony_ci{ 42362306a36Sopenharmony_ci struct lan966x_mac_entry *mac_entry, *tmp; 42462306a36Sopenharmony_ci unsigned char mac[ETH_ALEN] __aligned(2); 42562306a36Sopenharmony_ci struct list_head mac_deleted_entries; 42662306a36Sopenharmony_ci struct lan966x_port *port; 42762306a36Sopenharmony_ci u32 dest_idx; 42862306a36Sopenharmony_ci u32 column; 42962306a36Sopenharmony_ci u16 vid; 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci INIT_LIST_HEAD(&mac_deleted_entries); 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci spin_lock(&lan966x->mac_lock); 43462306a36Sopenharmony_ci list_for_each_entry_safe(mac_entry, tmp, &lan966x->mac_entries, list) { 43562306a36Sopenharmony_ci bool found = false; 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci if (mac_entry->row != row) 43862306a36Sopenharmony_ci continue; 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci for (column = 0; column < LAN966X_MAC_COLUMNS; ++column) { 44162306a36Sopenharmony_ci /* All the valid entries are at the start of the row, 44262306a36Sopenharmony_ci * so when get one invalid entry it can just skip the 44362306a36Sopenharmony_ci * rest of the columns 44462306a36Sopenharmony_ci */ 44562306a36Sopenharmony_ci if (!ANA_MACACCESS_VALID_GET(raw_entries[column].maca)) 44662306a36Sopenharmony_ci break; 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci lan966x_mac_process_raw_entry(&raw_entries[column], 44962306a36Sopenharmony_ci mac, &vid, &dest_idx); 45062306a36Sopenharmony_ci if (WARN_ON(dest_idx >= lan966x->num_phys_ports)) 45162306a36Sopenharmony_ci continue; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci /* If the entry in SW is found, then there is nothing 45462306a36Sopenharmony_ci * to do 45562306a36Sopenharmony_ci */ 45662306a36Sopenharmony_ci if (mac_entry->vid == vid && 45762306a36Sopenharmony_ci ether_addr_equal(mac_entry->mac, mac) && 45862306a36Sopenharmony_ci mac_entry->port_index == dest_idx) { 45962306a36Sopenharmony_ci raw_entries[column].processed = true; 46062306a36Sopenharmony_ci found = true; 46162306a36Sopenharmony_ci break; 46262306a36Sopenharmony_ci } 46362306a36Sopenharmony_ci } 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci if (!found) { 46662306a36Sopenharmony_ci list_del(&mac_entry->list); 46762306a36Sopenharmony_ci /* Move the entry from SW list to a tmp list such that 46862306a36Sopenharmony_ci * it would be deleted later 46962306a36Sopenharmony_ci */ 47062306a36Sopenharmony_ci list_add_tail(&mac_entry->list, &mac_deleted_entries); 47162306a36Sopenharmony_ci } 47262306a36Sopenharmony_ci } 47362306a36Sopenharmony_ci spin_unlock(&lan966x->mac_lock); 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci list_for_each_entry_safe(mac_entry, tmp, &mac_deleted_entries, list) { 47662306a36Sopenharmony_ci /* Notify the bridge that the entry doesn't exist 47762306a36Sopenharmony_ci * anymore in the HW 47862306a36Sopenharmony_ci */ 47962306a36Sopenharmony_ci port = lan966x->ports[mac_entry->port_index]; 48062306a36Sopenharmony_ci lan966x_mac_notifiers(SWITCHDEV_FDB_DEL_TO_BRIDGE, 48162306a36Sopenharmony_ci mac_entry->mac, mac_entry->vid, 48262306a36Sopenharmony_ci port->bond ?: port->dev); 48362306a36Sopenharmony_ci list_del(&mac_entry->list); 48462306a36Sopenharmony_ci kfree(mac_entry); 48562306a36Sopenharmony_ci } 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci /* Now go to the list of columns and see if any entry was not in the SW 48862306a36Sopenharmony_ci * list, then that means that the entry is new so it needs to notify the 48962306a36Sopenharmony_ci * bridge. 49062306a36Sopenharmony_ci */ 49162306a36Sopenharmony_ci for (column = 0; column < LAN966X_MAC_COLUMNS; ++column) { 49262306a36Sopenharmony_ci /* All the valid entries are at the start of the row, so when 49362306a36Sopenharmony_ci * get one invalid entry it can just skip the rest of the columns 49462306a36Sopenharmony_ci */ 49562306a36Sopenharmony_ci if (!ANA_MACACCESS_VALID_GET(raw_entries[column].maca)) 49662306a36Sopenharmony_ci break; 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci /* If the entry already exists then don't do anything */ 49962306a36Sopenharmony_ci if (raw_entries[column].processed) 50062306a36Sopenharmony_ci continue; 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_ci lan966x_mac_process_raw_entry(&raw_entries[column], 50362306a36Sopenharmony_ci mac, &vid, &dest_idx); 50462306a36Sopenharmony_ci if (WARN_ON(dest_idx >= lan966x->num_phys_ports)) 50562306a36Sopenharmony_ci continue; 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci spin_lock(&lan966x->mac_lock); 50862306a36Sopenharmony_ci mac_entry = lan966x_mac_find_entry(lan966x, mac, vid, dest_idx); 50962306a36Sopenharmony_ci if (mac_entry) { 51062306a36Sopenharmony_ci spin_unlock(&lan966x->mac_lock); 51162306a36Sopenharmony_ci continue; 51262306a36Sopenharmony_ci } 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci port = lan966x->ports[dest_idx]; 51562306a36Sopenharmony_ci mac_entry = lan966x_mac_alloc_entry(port, mac, vid); 51662306a36Sopenharmony_ci if (!mac_entry) { 51762306a36Sopenharmony_ci spin_unlock(&lan966x->mac_lock); 51862306a36Sopenharmony_ci return; 51962306a36Sopenharmony_ci } 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci mac_entry->row = row; 52262306a36Sopenharmony_ci list_add_tail(&mac_entry->list, &lan966x->mac_entries); 52362306a36Sopenharmony_ci spin_unlock(&lan966x->mac_lock); 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci lan966x_mac_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE, 52662306a36Sopenharmony_ci mac, vid, port->bond ?: port->dev); 52762306a36Sopenharmony_ci } 52862306a36Sopenharmony_ci} 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ciirqreturn_t lan966x_mac_irq_handler(struct lan966x *lan966x) 53162306a36Sopenharmony_ci{ 53262306a36Sopenharmony_ci struct lan966x_mac_raw_entry entry[LAN966X_MAC_COLUMNS] = { 0 }; 53362306a36Sopenharmony_ci u32 index, column; 53462306a36Sopenharmony_ci bool stop = true; 53562306a36Sopenharmony_ci u32 val; 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_ci /* Start the scan from 0, 0 */ 53862306a36Sopenharmony_ci lan_wr(ANA_MACTINDX_M_INDEX_SET(0) | 53962306a36Sopenharmony_ci ANA_MACTINDX_BUCKET_SET(0), 54062306a36Sopenharmony_ci lan966x, ANA_MACTINDX); 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci while (1) { 54362306a36Sopenharmony_ci spin_lock(&lan966x->mac_lock); 54462306a36Sopenharmony_ci lan_rmw(ANA_MACACCESS_MAC_TABLE_CMD_SET(MACACCESS_CMD_SYNC_GET_NEXT), 54562306a36Sopenharmony_ci ANA_MACACCESS_MAC_TABLE_CMD, 54662306a36Sopenharmony_ci lan966x, ANA_MACACCESS); 54762306a36Sopenharmony_ci lan966x_mac_wait_for_completion(lan966x); 54862306a36Sopenharmony_ci 54962306a36Sopenharmony_ci val = lan_rd(lan966x, ANA_MACTINDX); 55062306a36Sopenharmony_ci index = ANA_MACTINDX_M_INDEX_GET(val); 55162306a36Sopenharmony_ci column = ANA_MACTINDX_BUCKET_GET(val); 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci /* The SYNC-GET-NEXT returns all the entries(4) in a row in 55462306a36Sopenharmony_ci * which is suffered a change. By change it means that new entry 55562306a36Sopenharmony_ci * was added or an entry was removed because of ageing. 55662306a36Sopenharmony_ci * It would return all the columns for that row. And after that 55762306a36Sopenharmony_ci * it would return the next row The stop conditions of the 55862306a36Sopenharmony_ci * SYNC-GET-NEXT is when it reaches 'directly' to row 0 55962306a36Sopenharmony_ci * column 3. So if SYNC-GET-NEXT returns row 0 and column 0 56062306a36Sopenharmony_ci * then it is required to continue to read more even if it 56162306a36Sopenharmony_ci * reaches row 0 and column 3. 56262306a36Sopenharmony_ci */ 56362306a36Sopenharmony_ci if (index == 0 && column == 0) 56462306a36Sopenharmony_ci stop = false; 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci if (column == LAN966X_MAC_COLUMNS - 1 && 56762306a36Sopenharmony_ci index == 0 && stop) { 56862306a36Sopenharmony_ci spin_unlock(&lan966x->mac_lock); 56962306a36Sopenharmony_ci break; 57062306a36Sopenharmony_ci } 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci entry[column].mach = lan_rd(lan966x, ANA_MACHDATA); 57362306a36Sopenharmony_ci entry[column].macl = lan_rd(lan966x, ANA_MACLDATA); 57462306a36Sopenharmony_ci entry[column].maca = lan_rd(lan966x, ANA_MACACCESS); 57562306a36Sopenharmony_ci spin_unlock(&lan966x->mac_lock); 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci /* Once all the columns are read process them */ 57862306a36Sopenharmony_ci if (column == LAN966X_MAC_COLUMNS - 1) { 57962306a36Sopenharmony_ci lan966x_mac_irq_process(lan966x, index, entry); 58062306a36Sopenharmony_ci /* A row was processed so it is safe to assume that the 58162306a36Sopenharmony_ci * next row/column can be the stop condition 58262306a36Sopenharmony_ci */ 58362306a36Sopenharmony_ci stop = true; 58462306a36Sopenharmony_ci } 58562306a36Sopenharmony_ci } 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci lan_rmw(ANA_ANAINTR_INTR_SET(0), 58862306a36Sopenharmony_ci ANA_ANAINTR_INTR, 58962306a36Sopenharmony_ci lan966x, ANA_ANAINTR); 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci return IRQ_HANDLED; 59262306a36Sopenharmony_ci} 593