Keep track of bridge mdb objects in the driver. Similar to the previous patch, since the driver doesn't get explicit notifications about mdb group creation or destruction, just create the mdb group when the first port joins the group via mv88e6xxx_port_mdb_add(), and destroys the group when the last port left the group via mv88e6xxx_port_mdb_del(). Use the group's L2 address together with the VLAN ID as the key to the list. Port membership is again stored in a bitmask. Signed-off-by: Joseph Huang <Joseph.Huang@xxxxxxxxxx> --- drivers/net/dsa/mv88e6xxx/chip.c | 117 +++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c index f66ddde484dc..32a613c965b1 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -47,6 +47,14 @@ struct mv88e6xxx_bridge { struct list_head head; struct net_device *br_dev; u16 ports; + struct list_head br_mdb_list; +}; + +struct mv88e6xxx_br_mdb { + struct list_head head; + unsigned char addr[ETH_ALEN]; + u16 vid; + u16 portvec; }; static void assert_reg_lock(struct mv88e6xxx_chip *chip) @@ -2974,6 +2982,7 @@ mv88e6xxx_bridge_create(struct mv88e6xxx_chip *chip, struct net_device *br_dev) return ERR_PTR(-ENOMEM); mv_bridge->br_dev = br_dev; + INIT_LIST_HEAD(&mv_bridge->br_mdb_list); list_add(&mv_bridge->head, &chip->bridge_list); return mv_bridge; @@ -2984,6 +2993,7 @@ static void mv88e6xxx_bridge_destroy(struct mv88e6xxx_bridge *mv_bridge) list_del(&mv_bridge->head); WARN_ON(mv_bridge->ports); + WARN_ON(!list_empty(&mv_bridge->br_mdb_list)); kfree(mv_bridge); } @@ -6583,16 +6593,101 @@ static int mv88e6xxx_change_tag_protocol(struct dsa_switch *ds, return err; } +static struct mv88e6xxx_br_mdb * +mv88e6xxx_br_mdb_create(struct mv88e6xxx_bridge *mv_bridge, + const struct switchdev_obj_port_mdb *mdb) +{ + struct mv88e6xxx_br_mdb *mv_br_mdb; + + mv_br_mdb = kzalloc(sizeof(*mv_br_mdb), GFP_KERNEL); + if (!mv_br_mdb) + return ERR_PTR(-ENOMEM); + + ether_addr_copy(mv_br_mdb->addr, mdb->addr); + mv_br_mdb->vid = mdb->vid; + list_add(&mv_br_mdb->head, &mv_bridge->br_mdb_list); + + return mv_br_mdb; +} + +static void mv88e6xxx_br_mdb_destroy(struct mv88e6xxx_br_mdb *mv_br_mdb) +{ + list_del(&mv_br_mdb->head); + + WARN_ON(mv_br_mdb->portvec); + kfree(mv_br_mdb); +} + +static struct mv88e6xxx_br_mdb * +mv88e6xxx_br_mdb_find(struct mv88e6xxx_bridge *mv_bridge, + const struct switchdev_obj_port_mdb *mdb) +{ + struct mv88e6xxx_br_mdb *mv_br_mdb; + + list_for_each_entry(mv_br_mdb, &mv_bridge->br_mdb_list, head) + if (ether_addr_equal(mv_br_mdb->addr, mdb->addr) && + mv_br_mdb->vid == mdb->vid) + return mv_br_mdb; + + return NULL; +} + +static struct mv88e6xxx_br_mdb * +mv88e6xxx_br_mdb_get(struct mv88e6xxx_bridge *mv_bridge, + const struct switchdev_obj_port_mdb *mdb) +{ + struct mv88e6xxx_br_mdb *mv_br_mdb; + + mv_br_mdb = mv88e6xxx_br_mdb_find(mv_bridge, mdb); + if (!mv_br_mdb) + mv_br_mdb = mv88e6xxx_br_mdb_create(mv_bridge, mdb); + + return mv_br_mdb; +} + +static void mv88e6xxx_br_mdb_put(struct mv88e6xxx_br_mdb *mv_br_mdb) +{ + if (!mv_br_mdb->portvec) + mv88e6xxx_br_mdb_destroy(mv_br_mdb); +} + static int mv88e6xxx_port_mdb_add(struct dsa_switch *ds, int port, const struct switchdev_obj_port_mdb *mdb, struct dsa_db db) { struct mv88e6xxx_chip *chip = ds->priv; + struct mv88e6xxx_bridge *mv_bridge; + struct mv88e6xxx_br_mdb *mv_br_mdb; + struct net_device *orig_dev; + struct net_device *br_dev; int err; + orig_dev = mdb->obj.orig_dev; + br_dev = netdev_master_upper_dev_get(orig_dev); + if (!br_dev) + br_dev = orig_dev; + + mv_bridge = mv88e6xxx_bridge_by_dev(chip, br_dev); + if (!mv_bridge) + return -EINVAL; + + mv_br_mdb = mv88e6xxx_br_mdb_get(mv_bridge, mdb); + if (IS_ERR(mv_br_mdb)) + return PTR_ERR(mv_br_mdb); + + if (mv_br_mdb->portvec & BIT(port)) + return -EEXIST; + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_db_load_purge(chip, port, mdb->addr, mdb->vid, MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC); + + if (err) + goto out; + + mv_br_mdb->portvec |= BIT(port); + +out: mv88e6xxx_reg_unlock(chip); return err; @@ -6603,10 +6698,32 @@ static int mv88e6xxx_port_mdb_del(struct dsa_switch *ds, int port, struct dsa_db db) { struct mv88e6xxx_chip *chip = ds->priv; + struct mv88e6xxx_bridge *mv_bridge; + struct mv88e6xxx_br_mdb *mv_br_mdb; + struct net_device *orig_dev; + struct net_device *br_dev; int err; + orig_dev = mdb->obj.orig_dev; + br_dev = netdev_master_upper_dev_get(orig_dev); + if (!br_dev) + br_dev = orig_dev; + + mv_bridge = mv88e6xxx_bridge_by_dev(chip, br_dev); + if (!mv_bridge) + return -EINVAL; + + mv_br_mdb = mv88e6xxx_br_mdb_find(mv_bridge, mdb); + if (!mv_br_mdb) + return -ENOENT; + + if (!(mv_br_mdb->portvec & BIT(port))) + return -ENOENT; + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_db_load_purge(chip, port, mdb->addr, mdb->vid, 0); + mv_br_mdb->portvec &= ~BIT(port); + mv88e6xxx_br_mdb_put(mv_br_mdb); mv88e6xxx_reg_unlock(chip); return err; -- 2.17.1