| // 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); |
| } |
| } |
| } |