// SPDX-License-Identifier: GPL-2.0+ #include #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); } } }