diff options
Diffstat (limited to 'drivers/net/ethernet/microchip/lan966x/lan966x_mdb.c')
-rw-r--r-- | drivers/net/ethernet/microchip/lan966x/lan966x_mdb.c | 551 |
1 files changed, 551 insertions, 0 deletions
diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_mdb.c b/drivers/net/ethernet/microchip/lan966x/lan966x_mdb.c new file mode 100644 index 000000000..2af55268b --- /dev/null +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_mdb.c @@ -0,0 +1,551 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <net/switchdev.h> + +#include "lan966x_main.h" + +struct lan966x_pgid_entry { + struct list_head list; + int index; + refcount_t refcount; + u16 ports; +}; + +struct lan966x_mdb_entry { + struct list_head list; + unsigned char mac[ETH_ALEN]; + u16 vid; + u16 ports; + struct lan966x_pgid_entry *pgid; + u8 cpu_copy; +}; + +void lan966x_mdb_init(struct lan966x *lan966x) +{ + INIT_LIST_HEAD(&lan966x->mdb_entries); + INIT_LIST_HEAD(&lan966x->pgid_entries); +} + +static void lan966x_mdb_purge_mdb_entries(struct lan966x *lan966x) +{ + struct lan966x_mdb_entry *mdb_entry, *tmp; + + list_for_each_entry_safe(mdb_entry, tmp, &lan966x->mdb_entries, list) { + list_del(&mdb_entry->list); + kfree(mdb_entry); + } +} + +static void lan966x_mdb_purge_pgid_entries(struct lan966x *lan966x) +{ + struct lan966x_pgid_entry *pgid_entry, *tmp; + + list_for_each_entry_safe(pgid_entry, tmp, &lan966x->pgid_entries, list) { + list_del(&pgid_entry->list); + kfree(pgid_entry); + } +} + +void lan966x_mdb_deinit(struct lan966x *lan966x) +{ + lan966x_mdb_purge_mdb_entries(lan966x); + lan966x_mdb_purge_pgid_entries(lan966x); +} + +static struct lan966x_mdb_entry * +lan966x_mdb_entry_get(struct lan966x *lan966x, + const unsigned char *mac, + u16 vid) +{ + struct lan966x_mdb_entry *mdb_entry; + + list_for_each_entry(mdb_entry, &lan966x->mdb_entries, list) { + if (ether_addr_equal(mdb_entry->mac, mac) && + mdb_entry->vid == vid) + return mdb_entry; + } + + return NULL; +} + +static struct lan966x_mdb_entry * +lan966x_mdb_entry_add(struct lan966x *lan966x, + const struct switchdev_obj_port_mdb *mdb) +{ + struct lan966x_mdb_entry *mdb_entry; + + mdb_entry = kzalloc(sizeof(*mdb_entry), GFP_KERNEL); + if (!mdb_entry) + return ERR_PTR(-ENOMEM); + + ether_addr_copy(mdb_entry->mac, mdb->addr); + mdb_entry->vid = mdb->vid; + + list_add_tail(&mdb_entry->list, &lan966x->mdb_entries); + + return mdb_entry; +} + +static void lan966x_mdb_encode_mac(unsigned char *mac, + struct lan966x_mdb_entry *mdb_entry, + enum macaccess_entry_type type) +{ + ether_addr_copy(mac, mdb_entry->mac); + + if (type == ENTRYTYPE_MACV4) { + mac[0] = 0; + mac[1] = mdb_entry->ports >> 8; + mac[2] = mdb_entry->ports & 0xff; + } else if (type == ENTRYTYPE_MACV6) { + mac[0] = mdb_entry->ports >> 8; + mac[1] = mdb_entry->ports & 0xff; + } +} + +static int lan966x_mdb_ip_add(struct lan966x_port *port, + const struct switchdev_obj_port_mdb *mdb, + enum macaccess_entry_type type) +{ + bool cpu_port = netif_is_bridge_master(mdb->obj.orig_dev); + struct lan966x *lan966x = port->lan966x; + struct lan966x_mdb_entry *mdb_entry; + unsigned char mac[ETH_ALEN]; + bool cpu_copy = false; + + mdb_entry = lan966x_mdb_entry_get(lan966x, mdb->addr, mdb->vid); + if (!mdb_entry) { + mdb_entry = lan966x_mdb_entry_add(lan966x, mdb); + if (IS_ERR(mdb_entry)) + return PTR_ERR(mdb_entry); + } else { + lan966x_mdb_encode_mac(mac, mdb_entry, type); + lan966x_mac_forget(lan966x, mac, mdb_entry->vid, type); + } + + if (cpu_port) + mdb_entry->cpu_copy++; + else + mdb_entry->ports |= BIT(port->chip_port); + + /* Copy the frame to CPU only if the CPU is in the VLAN */ + if (lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x, mdb_entry->vid) && + mdb_entry->cpu_copy) + cpu_copy = true; + + lan966x_mdb_encode_mac(mac, mdb_entry, type); + return lan966x_mac_ip_learn(lan966x, cpu_copy, + mac, mdb_entry->vid, type); +} + +static int lan966x_mdb_ip_del(struct lan966x_port *port, + const struct switchdev_obj_port_mdb *mdb, + enum macaccess_entry_type type) +{ + bool cpu_port = netif_is_bridge_master(mdb->obj.orig_dev); + struct lan966x *lan966x = port->lan966x; + struct lan966x_mdb_entry *mdb_entry; + unsigned char mac[ETH_ALEN]; + u16 ports; + + mdb_entry = lan966x_mdb_entry_get(lan966x, mdb->addr, mdb->vid); + if (!mdb_entry) + return -ENOENT; + + ports = mdb_entry->ports; + if (cpu_port) { + /* If there are still other references to the CPU port then + * there is no point to delete and add again the same entry + */ + mdb_entry->cpu_copy--; + if (mdb_entry->cpu_copy) + return 0; + } else { + ports &= ~BIT(port->chip_port); + } + + lan966x_mdb_encode_mac(mac, mdb_entry, type); + lan966x_mac_forget(lan966x, mac, mdb_entry->vid, type); + + mdb_entry->ports = ports; + + if (!mdb_entry->ports && !mdb_entry->cpu_copy) { + list_del(&mdb_entry->list); + kfree(mdb_entry); + return 0; + } + + lan966x_mdb_encode_mac(mac, mdb_entry, type); + return lan966x_mac_ip_learn(lan966x, mdb_entry->cpu_copy, + mac, mdb_entry->vid, type); +} + +static struct lan966x_pgid_entry * +lan966x_pgid_entry_add(struct lan966x *lan966x, int index, u16 ports) +{ + struct lan966x_pgid_entry *pgid_entry; + + pgid_entry = kzalloc(sizeof(*pgid_entry), GFP_KERNEL); + if (!pgid_entry) + return ERR_PTR(-ENOMEM); + + pgid_entry->ports = ports; + pgid_entry->index = index; + refcount_set(&pgid_entry->refcount, 1); + + list_add_tail(&pgid_entry->list, &lan966x->pgid_entries); + + return pgid_entry; +} + +static struct lan966x_pgid_entry * +lan966x_pgid_entry_get(struct lan966x *lan966x, + struct lan966x_mdb_entry *mdb_entry) +{ + struct lan966x_pgid_entry *pgid_entry; + int index; + + /* Try to find an existing pgid that uses the same ports as the + * mdb_entry + */ + list_for_each_entry(pgid_entry, &lan966x->pgid_entries, list) { + if (pgid_entry->ports == mdb_entry->ports) { + refcount_inc(&pgid_entry->refcount); + return pgid_entry; + } + } + + /* Try to find an empty pgid entry and allocate one in case it finds it, + * otherwise it means that there are no more resources + */ + for (index = PGID_GP_START; index < PGID_GP_END; index++) { + bool used = false; + + list_for_each_entry(pgid_entry, &lan966x->pgid_entries, list) { + if (pgid_entry->index == index) { + used = true; + break; + } + } + + if (!used) + return lan966x_pgid_entry_add(lan966x, index, + mdb_entry->ports); + } + + return ERR_PTR(-ENOSPC); +} + +static void lan966x_pgid_entry_del(struct lan966x *lan966x, + struct lan966x_pgid_entry *pgid_entry) +{ + if (!refcount_dec_and_test(&pgid_entry->refcount)) + return; + + list_del(&pgid_entry->list); + kfree(pgid_entry); +} + +static int lan966x_mdb_l2_add(struct lan966x_port *port, + const struct switchdev_obj_port_mdb *mdb, + enum macaccess_entry_type type) +{ + bool cpu_port = netif_is_bridge_master(mdb->obj.orig_dev); + struct lan966x *lan966x = port->lan966x; + struct lan966x_pgid_entry *pgid_entry; + struct lan966x_mdb_entry *mdb_entry; + unsigned char mac[ETH_ALEN]; + + mdb_entry = lan966x_mdb_entry_get(lan966x, mdb->addr, mdb->vid); + if (!mdb_entry) { + mdb_entry = lan966x_mdb_entry_add(lan966x, mdb); + if (IS_ERR(mdb_entry)) + return PTR_ERR(mdb_entry); + } else { + lan966x_pgid_entry_del(lan966x, mdb_entry->pgid); + lan966x_mdb_encode_mac(mac, mdb_entry, type); + lan966x_mac_forget(lan966x, mac, mdb_entry->vid, type); + } + + if (cpu_port) { + mdb_entry->ports |= BIT(CPU_PORT); + mdb_entry->cpu_copy++; + } else { + mdb_entry->ports |= BIT(port->chip_port); + } + + pgid_entry = lan966x_pgid_entry_get(lan966x, mdb_entry); + if (IS_ERR(pgid_entry)) { + list_del(&mdb_entry->list); + kfree(mdb_entry); + return PTR_ERR(pgid_entry); + } + mdb_entry->pgid = pgid_entry; + + /* Copy the frame to CPU only if the CPU is in the VLAN */ + if (!lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x, mdb_entry->vid) && + mdb_entry->cpu_copy) + mdb_entry->ports &= BIT(CPU_PORT); + + lan_rmw(ANA_PGID_PGID_SET(mdb_entry->ports), + ANA_PGID_PGID, + lan966x, ANA_PGID(pgid_entry->index)); + + return lan966x_mac_learn(lan966x, pgid_entry->index, mdb_entry->mac, + mdb_entry->vid, type); +} + +static int lan966x_mdb_l2_del(struct lan966x_port *port, + const struct switchdev_obj_port_mdb *mdb, + enum macaccess_entry_type type) +{ + bool cpu_port = netif_is_bridge_master(mdb->obj.orig_dev); + struct lan966x *lan966x = port->lan966x; + struct lan966x_pgid_entry *pgid_entry; + struct lan966x_mdb_entry *mdb_entry; + unsigned char mac[ETH_ALEN]; + u16 ports; + + mdb_entry = lan966x_mdb_entry_get(lan966x, mdb->addr, mdb->vid); + if (!mdb_entry) + return -ENOENT; + + ports = mdb_entry->ports; + if (cpu_port) { + /* If there are still other references to the CPU port then + * there is no point to delete and add again the same entry + */ + mdb_entry->cpu_copy--; + if (mdb_entry->cpu_copy) + return 0; + + ports &= ~BIT(CPU_PORT); + } else { + ports &= ~BIT(port->chip_port); + } + + lan966x_mdb_encode_mac(mac, mdb_entry, type); + lan966x_mac_forget(lan966x, mac, mdb_entry->vid, type); + lan966x_pgid_entry_del(lan966x, mdb_entry->pgid); + + mdb_entry->ports = ports; + + if (!mdb_entry->ports) { + list_del(&mdb_entry->list); + kfree(mdb_entry); + return 0; + } + + pgid_entry = lan966x_pgid_entry_get(lan966x, mdb_entry); + if (IS_ERR(pgid_entry)) { + list_del(&mdb_entry->list); + kfree(mdb_entry); + return PTR_ERR(pgid_entry); + } + mdb_entry->pgid = pgid_entry; + + lan_rmw(ANA_PGID_PGID_SET(mdb_entry->ports), + ANA_PGID_PGID, + lan966x, ANA_PGID(pgid_entry->index)); + + return lan966x_mac_learn(lan966x, pgid_entry->index, mdb_entry->mac, + mdb_entry->vid, type); +} + +static enum macaccess_entry_type +lan966x_mdb_classify(const unsigned char *mac) +{ + if (mac[0] == 0x01 && mac[1] == 0x00 && mac[2] == 0x5e) + return ENTRYTYPE_MACV4; + if (mac[0] == 0x33 && mac[1] == 0x33) + return ENTRYTYPE_MACV6; + return ENTRYTYPE_LOCKED; +} + +int lan966x_handle_port_mdb_add(struct lan966x_port *port, + const struct switchdev_obj *obj) +{ + const struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj); + enum macaccess_entry_type type; + + /* Split the way the entries are added for ipv4/ipv6 and for l2. The + * reason is that for ipv4/ipv6 it doesn't require to use any pgid + * entry, while for l2 is required to use pgid entries + */ + type = lan966x_mdb_classify(mdb->addr); + if (type == ENTRYTYPE_MACV4 || type == ENTRYTYPE_MACV6) + return lan966x_mdb_ip_add(port, mdb, type); + + return lan966x_mdb_l2_add(port, mdb, type); +} + +int lan966x_handle_port_mdb_del(struct lan966x_port *port, + const struct switchdev_obj *obj) +{ + const struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj); + enum macaccess_entry_type type; + + /* Split the way the entries are removed for ipv4/ipv6 and for l2. The + * reason is that for ipv4/ipv6 it doesn't require to use any pgid + * entry, while for l2 is required to use pgid entries + */ + type = lan966x_mdb_classify(mdb->addr); + if (type == ENTRYTYPE_MACV4 || type == ENTRYTYPE_MACV6) + return lan966x_mdb_ip_del(port, mdb, type); + + return lan966x_mdb_l2_del(port, mdb, type); +} + +static void lan966x_mdb_ip_cpu_copy(struct lan966x *lan966x, + struct lan966x_mdb_entry *mdb_entry, + enum macaccess_entry_type type) +{ + unsigned char mac[ETH_ALEN]; + + lan966x_mdb_encode_mac(mac, mdb_entry, type); + lan966x_mac_forget(lan966x, mac, mdb_entry->vid, type); + lan966x_mac_ip_learn(lan966x, true, mac, mdb_entry->vid, type); +} + +static void lan966x_mdb_l2_cpu_copy(struct lan966x *lan966x, + struct lan966x_mdb_entry *mdb_entry, + enum macaccess_entry_type type) +{ + struct lan966x_pgid_entry *pgid_entry; + unsigned char mac[ETH_ALEN]; + + lan966x_pgid_entry_del(lan966x, mdb_entry->pgid); + lan966x_mdb_encode_mac(mac, mdb_entry, type); + lan966x_mac_forget(lan966x, mac, mdb_entry->vid, type); + + mdb_entry->ports |= BIT(CPU_PORT); + + pgid_entry = lan966x_pgid_entry_get(lan966x, mdb_entry); + if (IS_ERR(pgid_entry)) + return; + + mdb_entry->pgid = pgid_entry; + + lan_rmw(ANA_PGID_PGID_SET(mdb_entry->ports), + ANA_PGID_PGID, + lan966x, ANA_PGID(pgid_entry->index)); + + lan966x_mac_learn(lan966x, pgid_entry->index, mdb_entry->mac, + mdb_entry->vid, type); +} + +void lan966x_mdb_write_entries(struct lan966x *lan966x, u16 vid) +{ + struct lan966x_mdb_entry *mdb_entry; + enum macaccess_entry_type type; + + list_for_each_entry(mdb_entry, &lan966x->mdb_entries, list) { + if (mdb_entry->vid != vid || !mdb_entry->cpu_copy) + continue; + + type = lan966x_mdb_classify(mdb_entry->mac); + if (type == ENTRYTYPE_MACV4 || type == ENTRYTYPE_MACV6) + lan966x_mdb_ip_cpu_copy(lan966x, mdb_entry, type); + else + lan966x_mdb_l2_cpu_copy(lan966x, mdb_entry, type); + } +} + +static void lan966x_mdb_ip_cpu_remove(struct lan966x *lan966x, + struct lan966x_mdb_entry *mdb_entry, + enum macaccess_entry_type type) +{ + unsigned char mac[ETH_ALEN]; + + lan966x_mdb_encode_mac(mac, mdb_entry, type); + lan966x_mac_forget(lan966x, mac, mdb_entry->vid, type); + lan966x_mac_ip_learn(lan966x, false, mac, mdb_entry->vid, type); +} + +static void lan966x_mdb_l2_cpu_remove(struct lan966x *lan966x, + struct lan966x_mdb_entry *mdb_entry, + enum macaccess_entry_type type) +{ + struct lan966x_pgid_entry *pgid_entry; + unsigned char mac[ETH_ALEN]; + + lan966x_pgid_entry_del(lan966x, mdb_entry->pgid); + lan966x_mdb_encode_mac(mac, mdb_entry, type); + lan966x_mac_forget(lan966x, mac, mdb_entry->vid, type); + + mdb_entry->ports &= ~BIT(CPU_PORT); + + pgid_entry = lan966x_pgid_entry_get(lan966x, mdb_entry); + if (IS_ERR(pgid_entry)) + return; + + mdb_entry->pgid = pgid_entry; + + lan_rmw(ANA_PGID_PGID_SET(mdb_entry->ports), + ANA_PGID_PGID, + lan966x, ANA_PGID(pgid_entry->index)); + + lan966x_mac_learn(lan966x, pgid_entry->index, mdb_entry->mac, + mdb_entry->vid, type); +} + +void lan966x_mdb_erase_entries(struct lan966x *lan966x, u16 vid) +{ + struct lan966x_mdb_entry *mdb_entry; + enum macaccess_entry_type type; + + list_for_each_entry(mdb_entry, &lan966x->mdb_entries, list) { + if (mdb_entry->vid != vid || !mdb_entry->cpu_copy) + continue; + + type = lan966x_mdb_classify(mdb_entry->mac); + if (type == ENTRYTYPE_MACV4 || type == ENTRYTYPE_MACV6) + lan966x_mdb_ip_cpu_remove(lan966x, mdb_entry, type); + else + lan966x_mdb_l2_cpu_remove(lan966x, mdb_entry, type); + } +} + +void lan966x_mdb_clear_entries(struct lan966x *lan966x) +{ + struct lan966x_mdb_entry *mdb_entry; + enum macaccess_entry_type type; + unsigned char mac[ETH_ALEN]; + + list_for_each_entry(mdb_entry, &lan966x->mdb_entries, list) { + type = lan966x_mdb_classify(mdb_entry->mac); + + lan966x_mdb_encode_mac(mac, mdb_entry, type); + /* Remove just the MAC entry, still keep the PGID in case of L2 + * entries because this can be restored at later point + */ + lan966x_mac_forget(lan966x, mac, mdb_entry->vid, type); + } +} + +void lan966x_mdb_restore_entries(struct lan966x *lan966x) +{ + struct lan966x_mdb_entry *mdb_entry; + enum macaccess_entry_type type; + unsigned char mac[ETH_ALEN]; + bool cpu_copy = false; + + list_for_each_entry(mdb_entry, &lan966x->mdb_entries, list) { + type = lan966x_mdb_classify(mdb_entry->mac); + + lan966x_mdb_encode_mac(mac, mdb_entry, type); + if (type == ENTRYTYPE_MACV4 || type == ENTRYTYPE_MACV6) { + /* Copy the frame to CPU only if the CPU is in the VLAN */ + if (lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x, + mdb_entry->vid) && + mdb_entry->cpu_copy) + cpu_copy = true; + + lan966x_mac_ip_learn(lan966x, cpu_copy, mac, + mdb_entry->vid, type); + } else { + lan966x_mac_learn(lan966x, mdb_entry->pgid->index, + mdb_entry->mac, + mdb_entry->vid, type); + } + } +} |