+static int rtl83xx_port_mdb_prepare(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb)
+{
+ struct rtl838x_switch_priv *priv = ds->priv;
+
+ if (priv->id >= 0x9300)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int rtl83xx_mc_group_alloc(struct rtl838x_switch_priv *priv, int port)
+{
+ int mc_group = find_first_zero_bit(priv->mc_group_bm, MAX_MC_GROUPS - 1);
+ u64 portmask;
+
+ if (mc_group >= MAX_MC_GROUPS - 1)
+ return -1;
+
+ pr_debug("Using MC group %d\n", mc_group);
+ set_bit(mc_group, priv->mc_group_bm);
+ mc_group++; // We cannot use group 0, as this is used for lookup miss flooding
+ portmask = BIT_ULL(port);
+ priv->r->write_mcast_pmask(mc_group, portmask);
+
+ return mc_group;
+}
+
+static u64 rtl83xx_mc_group_add_port(struct rtl838x_switch_priv *priv, int mc_group, int port)
+{
+ u64 portmask = priv->r->read_mcast_pmask(mc_group);
+
+ portmask |= BIT_ULL(port);
+ priv->r->write_mcast_pmask(mc_group, portmask);
+
+ return portmask;
+}
+
+static u64 rtl83xx_mc_group_del_port(struct rtl838x_switch_priv *priv, int mc_group, int port)
+{
+ u64 portmask = priv->r->read_mcast_pmask(mc_group);
+
+ portmask &= ~BIT_ULL(port);
+ priv->r->write_mcast_pmask(mc_group, portmask);
+ if (!portmask)
+ clear_bit(mc_group, priv->mc_group_bm);
+
+ return portmask;
+}
+
+static void rtl83xx_port_mdb_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb)
+{
+ struct rtl838x_switch_priv *priv = ds->priv;
+ u64 mac = ether_addr_to_u64(mdb->addr);
+ struct rtl838x_l2_entry e;
+ int err = 0, idx;
+ int vid = mdb->vid;
+ u64 seed = priv->r->l2_hash_seed(mac, vid);
+ int mc_group;
+
+ pr_debug("In %s port %d, mac %llx, vid: %d\n", __func__, port, mac, vid);
+ mutex_lock(&priv->reg_mutex);
+
+ idx = rtl83xx_find_l2_hash_entry(priv, seed, false, &e);
+
+ // Found an existing or empty entry
+ if (idx >= 0) {
+ if (e.valid) {
+ pr_debug("Found an existing entry %016llx, mc_group %d\n",
+ ether_addr_to_u64(e.mac), e.mc_portmask_index);
+ rtl83xx_mc_group_add_port(priv, e.mc_portmask_index, port);
+ } else {
+ pr_debug("New entry for seed %016llx\n", seed);
+ mc_group = rtl83xx_mc_group_alloc(priv, port);
+ if (mc_group < 0) {
+ err = -ENOTSUPP;
+ goto out;
+ }
+ rtl83xx_setup_l2_mc_entry(priv, &e, vid, mac, mc_group);
+ priv->r->write_l2_entry_using_hash(idx >> 2, idx & 0x3, &e);
+ }
+ goto out;
+ }
+
+ // Hash buckets full, try CAM
+ rtl83xx_find_l2_cam_entry(priv, seed, false, &e);
+
+ if (idx >= 0) {
+ if (e.valid) {
+ pr_debug("Found existing CAM entry %016llx, mc_group %d\n",
+ ether_addr_to_u64(e.mac), e.mc_portmask_index);
+ rtl83xx_mc_group_add_port(priv, e.mc_portmask_index, port);
+ } else {
+ pr_debug("New entry\n");
+ mc_group = rtl83xx_mc_group_alloc(priv, port);
+ if (mc_group < 0) {
+ err = -ENOTSUPP;
+ goto out;
+ }
+ rtl83xx_setup_l2_mc_entry(priv, &e, vid, mac, mc_group);
+ priv->r->write_cam(idx, &e);
+ }
+ goto out;
+ }
+
+ err = -ENOTSUPP;
+out:
+ mutex_unlock(&priv->reg_mutex);
+ if (err)
+ dev_err(ds->dev, "failed to add MDB entry\n");
+}
+
+int rtl83xx_port_mdb_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb)
+{
+ struct rtl838x_switch_priv *priv = ds->priv;
+ u64 mac = ether_addr_to_u64(mdb->addr);
+ struct rtl838x_l2_entry e;
+ int err = 0, idx;
+ int vid = mdb->vid;
+ u64 seed = priv->r->l2_hash_seed(mac, vid);
+ u64 portmask;
+
+ pr_debug("In %s, port %d, mac %llx, vid: %d\n", __func__, port, mac, vid);
+ mutex_lock(&priv->reg_mutex);
+
+ idx = rtl83xx_find_l2_hash_entry(priv, seed, true, &e);
+
+ pr_debug("Found entry index %d, key %d and bucket %d\n", idx, idx >> 2, idx & 3);
+ if (idx >= 0) {
+ portmask = rtl83xx_mc_group_del_port(priv, e.mc_portmask_index, port);
+ if (!portmask) {
+ e.valid = false;
+ // dump_l2_entry(&e);
+ priv->r->write_l2_entry_using_hash(idx >> 2, idx & 0x3, &e);
+ }
+ goto out;
+ }
+
+ /* Check CAM for spillover from hash buckets */
+ rtl83xx_find_l2_cam_entry(priv, seed, true, &e);
+
+ if (idx >= 0) {
+ portmask = rtl83xx_mc_group_del_port(priv, e.mc_portmask_index, port);
+ if (!portmask) {
+ e.valid = false;
+ // dump_l2_entry(&e);
+ priv->r->write_cam(idx, &e);
+ }
+ goto out;
+ }
+ // TODO: Re-enable with a newer kernel: err = -ENOENT;
+out:
+ mutex_unlock(&priv->reg_mutex);
+ return err;
+}
+