OSDN Git Service

net: dsa: sja1105: implement VLAN retagging for dsa_8021q sub-VLANs
authorVladimir Oltean <vladimir.oltean@nxp.com>
Tue, 12 May 2020 17:20:38 +0000 (20:20 +0300)
committerDavid S. Miller <davem@davemloft.net>
Tue, 12 May 2020 20:08:08 +0000 (13:08 -0700)
Expand the delta commit procedure for VLANs with additional logic for
treating bridge_vlans in the newly introduced operating mode,
SJA1105_VLAN_BEST_EFFORT.

For every bridge VLAN on every user port, a sub-VLAN index is calculated
and retagging rules are installed towards a dsa_8021q rx_vid that
encodes that sub-VLAN index. This way, the tagger can identify the
original VLANs.

Extra care is taken for VLANs to still work as intended in cross-chip
scenarios. Retagging may have unintended consequences for these because
a sub-VLAN encoding that works for the CPU does not make any sense for a
front-panel port.

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/dsa/sja1105/sja1105_main.c

index 7746221..44ce788 100644 (file)
@@ -1869,6 +1869,57 @@ sja1105_get_tag_protocol(struct dsa_switch *ds, int port,
        return DSA_TAG_PROTO_SJA1105;
 }
 
+static int sja1105_find_free_subvlan(u16 *subvlan_map, bool pvid)
+{
+       int subvlan;
+
+       if (pvid)
+               return 0;
+
+       for (subvlan = 1; subvlan < DSA_8021Q_N_SUBVLAN; subvlan++)
+               if (subvlan_map[subvlan] == VLAN_N_VID)
+                       return subvlan;
+
+       return -1;
+}
+
+static int sja1105_find_subvlan(u16 *subvlan_map, u16 vid)
+{
+       int subvlan;
+
+       for (subvlan = 0; subvlan < DSA_8021Q_N_SUBVLAN; subvlan++)
+               if (subvlan_map[subvlan] == vid)
+                       return subvlan;
+
+       return -1;
+}
+
+static int sja1105_find_committed_subvlan(struct sja1105_private *priv,
+                                         int port, u16 vid)
+{
+       struct sja1105_port *sp = &priv->ports[port];
+
+       return sja1105_find_subvlan(sp->subvlan_map, vid);
+}
+
+static void sja1105_init_subvlan_map(u16 *subvlan_map)
+{
+       int subvlan;
+
+       for (subvlan = 0; subvlan < DSA_8021Q_N_SUBVLAN; subvlan++)
+               subvlan_map[subvlan] = VLAN_N_VID;
+}
+
+static void sja1105_commit_subvlan_map(struct sja1105_private *priv, int port,
+                                      u16 *subvlan_map)
+{
+       struct sja1105_port *sp = &priv->ports[port];
+       int subvlan;
+
+       for (subvlan = 0; subvlan < DSA_8021Q_N_SUBVLAN; subvlan++)
+               sp->subvlan_map[subvlan] = subvlan_map[subvlan];
+}
+
 static int sja1105_is_vlan_configured(struct sja1105_private *priv, u16 vid)
 {
        struct sja1105_vlan_lookup_entry *vlan;
@@ -1885,9 +1936,29 @@ static int sja1105_is_vlan_configured(struct sja1105_private *priv, u16 vid)
        return -1;
 }
 
+static int
+sja1105_find_retagging_entry(struct sja1105_retagging_entry *retagging,
+                            int count, int from_port, u16 from_vid,
+                            u16 to_vid)
+{
+       int i;
+
+       for (i = 0; i < count; i++)
+               if (retagging[i].ing_port == BIT(from_port) &&
+                   retagging[i].vlan_ing == from_vid &&
+                   retagging[i].vlan_egr == to_vid)
+                       return i;
+
+       /* Return an invalid entry index if not found */
+       return -1;
+}
+
 static int sja1105_commit_vlans(struct sja1105_private *priv,
-                               struct sja1105_vlan_lookup_entry *new_vlan)
+                               struct sja1105_vlan_lookup_entry *new_vlan,
+                               struct sja1105_retagging_entry *new_retagging,
+                               int num_retagging)
 {
+       struct sja1105_retagging_entry *retagging;
        struct sja1105_vlan_lookup_entry *vlan;
        struct sja1105_table *table;
        int num_vlans = 0;
@@ -1947,9 +2018,50 @@ static int sja1105_commit_vlans(struct sja1105_private *priv,
                vlan[k++] = new_vlan[i];
        }
 
+       /* VLAN Retagging Table */
+       table = &priv->static_config.tables[BLK_IDX_RETAGGING];
+       retagging = table->entries;
+
+       for (i = 0; i < table->entry_count; i++) {
+               rc = sja1105_dynamic_config_write(priv, BLK_IDX_RETAGGING,
+                                                 i, &retagging[i], false);
+               if (rc)
+                       return rc;
+       }
+
+       if (table->entry_count)
+               kfree(table->entries);
+
+       table->entries = kcalloc(num_retagging, table->ops->unpacked_entry_size,
+                                GFP_KERNEL);
+       if (!table->entries)
+               return -ENOMEM;
+
+       table->entry_count = num_retagging;
+       retagging = table->entries;
+
+       for (i = 0; i < num_retagging; i++) {
+               retagging[i] = new_retagging[i];
+
+               /* Update entry */
+               rc = sja1105_dynamic_config_write(priv, BLK_IDX_RETAGGING,
+                                                 i, &retagging[i], true);
+               if (rc < 0)
+                       return rc;
+       }
+
        return 0;
 }
 
+struct sja1105_crosschip_vlan {
+       struct list_head list;
+       u16 vid;
+       bool untagged;
+       int port;
+       int other_port;
+       struct dsa_switch *other_ds;
+};
+
 struct sja1105_crosschip_switch {
        struct list_head list;
        struct dsa_switch *other_ds;
@@ -2021,6 +2133,265 @@ sja1105_build_dsa_8021q_vlans(struct sja1105_private *priv,
        return 0;
 }
 
+static int sja1105_build_subvlans(struct sja1105_private *priv,
+                                 u16 subvlan_map[][DSA_8021Q_N_SUBVLAN],
+                                 struct sja1105_vlan_lookup_entry *new_vlan,
+                                 struct sja1105_retagging_entry *new_retagging,
+                                 int *num_retagging)
+{
+       struct sja1105_bridge_vlan *v;
+       int k = *num_retagging;
+
+       if (priv->vlan_state != SJA1105_VLAN_BEST_EFFORT)
+               return 0;
+
+       list_for_each_entry(v, &priv->bridge_vlans, list) {
+               int upstream = dsa_upstream_port(priv->ds, v->port);
+               int match, subvlan;
+               u16 rx_vid;
+
+               /* Only sub-VLANs on user ports need to be applied.
+                * Bridge VLANs also include VLANs added automatically
+                * by DSA on the CPU port.
+                */
+               if (!dsa_is_user_port(priv->ds, v->port))
+                       continue;
+
+               subvlan = sja1105_find_subvlan(subvlan_map[v->port],
+                                              v->vid);
+               if (subvlan < 0) {
+                       subvlan = sja1105_find_free_subvlan(subvlan_map[v->port],
+                                                           v->pvid);
+                       if (subvlan < 0) {
+                               dev_err(priv->ds->dev, "No more free subvlans\n");
+                               return -ENOSPC;
+                       }
+               }
+
+               rx_vid = dsa_8021q_rx_vid_subvlan(priv->ds, v->port, subvlan);
+
+               /* @v->vid on @v->port needs to be retagged to @rx_vid
+                * on @upstream. Assume @v->vid on @v->port and on
+                * @upstream was already configured by the previous
+                * iteration over bridge_vlans.
+                */
+               match = rx_vid;
+               new_vlan[match].vlanid = rx_vid;
+               new_vlan[match].vmemb_port |= BIT(v->port);
+               new_vlan[match].vmemb_port |= BIT(upstream);
+               new_vlan[match].vlan_bc |= BIT(v->port);
+               new_vlan[match].vlan_bc |= BIT(upstream);
+               /* The "untagged" flag is set the same as for the
+                * original VLAN
+                */
+               if (!v->untagged)
+                       new_vlan[match].tag_port |= BIT(v->port);
+               /* But it's always tagged towards the CPU */
+               new_vlan[match].tag_port |= BIT(upstream);
+
+               /* The Retagging Table generates packet *clones* with
+                * the new VLAN. This is a very odd hardware quirk
+                * which we need to suppress by dropping the original
+                * packet.
+                * Deny egress of the original VLAN towards the CPU
+                * port. This will force the switch to drop it, and
+                * we'll see only the retagged packets.
+                */
+               match = v->vid;
+               new_vlan[match].vlan_bc &= ~BIT(upstream);
+
+               /* And the retagging itself */
+               new_retagging[k].vlan_ing = v->vid;
+               new_retagging[k].vlan_egr = rx_vid;
+               new_retagging[k].ing_port = BIT(v->port);
+               new_retagging[k].egr_port = BIT(upstream);
+               if (k++ == SJA1105_MAX_RETAGGING_COUNT) {
+                       dev_err(priv->ds->dev, "No more retagging rules\n");
+                       return -ENOSPC;
+               }
+
+               subvlan_map[v->port][subvlan] = v->vid;
+       }
+
+       *num_retagging = k;
+
+       return 0;
+}
+
+/* Sadly, in crosschip scenarios where the CPU port is also the link to another
+ * switch, we should retag backwards (the dsa_8021q vid to the original vid) on
+ * the CPU port of neighbour switches.
+ */
+static int
+sja1105_build_crosschip_subvlans(struct sja1105_private *priv,
+                                struct sja1105_vlan_lookup_entry *new_vlan,
+                                struct sja1105_retagging_entry *new_retagging,
+                                int *num_retagging)
+{
+       struct sja1105_crosschip_vlan *tmp, *pos;
+       struct dsa_8021q_crosschip_link *c;
+       struct sja1105_bridge_vlan *v, *w;
+       struct list_head crosschip_vlans;
+       int k = *num_retagging;
+       int rc = 0;
+
+       if (priv->vlan_state != SJA1105_VLAN_BEST_EFFORT)
+               return 0;
+
+       INIT_LIST_HEAD(&crosschip_vlans);
+
+       list_for_each_entry(c, &priv->crosschip_links, list) {
+               struct sja1105_private *other_priv = c->other_ds->priv;
+
+               if (other_priv->vlan_state == SJA1105_VLAN_FILTERING_FULL)
+                       continue;
+
+               /* Crosschip links are also added to the CPU ports.
+                * Ignore those.
+                */
+               if (!dsa_is_user_port(priv->ds, c->port))
+                       continue;
+               if (!dsa_is_user_port(c->other_ds, c->other_port))
+                       continue;
+
+               /* Search for VLANs on the remote port */
+               list_for_each_entry(v, &other_priv->bridge_vlans, list) {
+                       bool already_added = false;
+                       bool we_have_it = false;
+
+                       if (v->port != c->other_port)
+                               continue;
+
+                       /* If @v is a pvid on @other_ds, it does not need
+                        * re-retagging, because its SVL field is 0 and we
+                        * already allow that, via the dsa_8021q crosschip
+                        * links.
+                        */
+                       if (v->pvid)
+                               continue;
+
+                       /* Search for the VLAN on our local port */
+                       list_for_each_entry(w, &priv->bridge_vlans, list) {
+                               if (w->port == c->port && w->vid == v->vid) {
+                                       we_have_it = true;
+                                       break;
+                               }
+                       }
+
+                       if (!we_have_it)
+                               continue;
+
+                       list_for_each_entry(tmp, &crosschip_vlans, list) {
+                               if (tmp->vid == v->vid &&
+                                   tmp->untagged == v->untagged &&
+                                   tmp->port == c->port &&
+                                   tmp->other_port == v->port &&
+                                   tmp->other_ds == c->other_ds) {
+                                       already_added = true;
+                                       break;
+                               }
+                       }
+
+                       if (already_added)
+                               continue;
+
+                       tmp = kzalloc(sizeof(*tmp), GFP_KERNEL);
+                       if (!tmp) {
+                               dev_err(priv->ds->dev, "Failed to allocate memory\n");
+                               rc = -ENOMEM;
+                               goto out;
+                       }
+                       tmp->vid = v->vid;
+                       tmp->port = c->port;
+                       tmp->other_port = v->port;
+                       tmp->other_ds = c->other_ds;
+                       tmp->untagged = v->untagged;
+                       list_add(&tmp->list, &crosschip_vlans);
+               }
+       }
+
+       list_for_each_entry(tmp, &crosschip_vlans, list) {
+               struct sja1105_private *other_priv = tmp->other_ds->priv;
+               int upstream = dsa_upstream_port(priv->ds, tmp->port);
+               int match, subvlan;
+               u16 rx_vid;
+
+               subvlan = sja1105_find_committed_subvlan(other_priv,
+                                                        tmp->other_port,
+                                                        tmp->vid);
+               /* If this happens, it's a bug. The neighbour switch does not
+                * have a subvlan for tmp->vid on tmp->other_port, but it
+                * should, since we already checked for its vlan_state.
+                */
+               if (WARN_ON(subvlan < 0)) {
+                       rc = -EINVAL;
+                       goto out;
+               }
+
+               rx_vid = dsa_8021q_rx_vid_subvlan(tmp->other_ds,
+                                                 tmp->other_port,
+                                                 subvlan);
+
+               /* The @rx_vid retagged from @tmp->vid on
+                * {@tmp->other_ds, @tmp->other_port} needs to be
+                * re-retagged to @tmp->vid on the way back to us.
+                *
+                * Assume the original @tmp->vid is already configured
+                * on this local switch, otherwise we wouldn't be
+                * retagging its subvlan on the other switch in the
+                * first place. We just need to add a reverse retagging
+                * rule for @rx_vid and install @rx_vid on our ports.
+                */
+               match = rx_vid;
+               new_vlan[match].vlanid = rx_vid;
+               new_vlan[match].vmemb_port |= BIT(tmp->port);
+               new_vlan[match].vmemb_port |= BIT(upstream);
+               /* The "untagged" flag is set the same as for the
+                * original VLAN. And towards the CPU, it doesn't
+                * really matter, because @rx_vid will only receive
+                * traffic on that port. For consistency with other dsa_8021q
+                * VLANs, we'll keep the CPU port tagged.
+                */
+               if (!tmp->untagged)
+                       new_vlan[match].tag_port |= BIT(tmp->port);
+               new_vlan[match].tag_port |= BIT(upstream);
+               /* Deny egress of @rx_vid towards our front-panel port.
+                * This will force the switch to drop it, and we'll see
+                * only the re-retagged packets (having the original,
+                * pre-initial-retagging, VLAN @tmp->vid).
+                */
+               new_vlan[match].vlan_bc &= ~BIT(tmp->port);
+
+               /* On reverse retagging, the same ingress VLAN goes to multiple
+                * ports. So we have an opportunity to create composite rules
+                * to not waste the limited space in the retagging table.
+                */
+               k = sja1105_find_retagging_entry(new_retagging, *num_retagging,
+                                                upstream, rx_vid, tmp->vid);
+               if (k < 0) {
+                       if (*num_retagging == SJA1105_MAX_RETAGGING_COUNT) {
+                               dev_err(priv->ds->dev, "No more retagging rules\n");
+                               rc = -ENOSPC;
+                               goto out;
+                       }
+                       k = (*num_retagging)++;
+               }
+               /* And the retagging itself */
+               new_retagging[k].vlan_ing = rx_vid;
+               new_retagging[k].vlan_egr = tmp->vid;
+               new_retagging[k].ing_port = BIT(upstream);
+               new_retagging[k].egr_port |= BIT(tmp->port);
+       }
+
+out:
+       list_for_each_entry_safe(tmp, pos, &crosschip_vlans, list) {
+               list_del(&tmp->list);
+               kfree(tmp);
+       }
+
+       return rc;
+}
+
 static int sja1105_build_vlan_table(struct sja1105_private *priv, bool notify);
 
 static int sja1105_notify_crosschip_switches(struct sja1105_private *priv)
@@ -2074,10 +2445,12 @@ out:
 
 static int sja1105_build_vlan_table(struct sja1105_private *priv, bool notify)
 {
+       u16 subvlan_map[SJA1105_NUM_PORTS][DSA_8021Q_N_SUBVLAN];
+       struct sja1105_retagging_entry *new_retagging;
        struct sja1105_vlan_lookup_entry *new_vlan;
        struct sja1105_table *table;
+       int i, num_retagging = 0;
        int rc;
-       int i;
 
        table = &priv->static_config.tables[BLK_IDX_VLAN_LOOKUP];
        new_vlan = kcalloc(VLAN_N_VID,
@@ -2085,9 +2458,23 @@ static int sja1105_build_vlan_table(struct sja1105_private *priv, bool notify)
        if (!new_vlan)
                return -ENOMEM;
 
+       table = &priv->static_config.tables[BLK_IDX_VLAN_LOOKUP];
+       new_retagging = kcalloc(SJA1105_MAX_RETAGGING_COUNT,
+                               table->ops->unpacked_entry_size, GFP_KERNEL);
+       if (!new_retagging) {
+               kfree(new_vlan);
+               return -ENOMEM;
+       }
+
        for (i = 0; i < VLAN_N_VID; i++)
                new_vlan[i].vlanid = VLAN_N_VID;
 
+       for (i = 0; i < SJA1105_MAX_RETAGGING_COUNT; i++)
+               new_retagging[i].vlan_ing = VLAN_N_VID;
+
+       for (i = 0; i < priv->ds->num_ports; i++)
+               sja1105_init_subvlan_map(subvlan_map[i]);
+
        /* Bridge VLANs */
        rc = sja1105_build_bridge_vlans(priv, new_vlan);
        if (rc)
@@ -2102,7 +2489,22 @@ static int sja1105_build_vlan_table(struct sja1105_private *priv, bool notify)
        if (rc)
                goto out;
 
-       rc = sja1105_commit_vlans(priv, new_vlan);
+       /* Private VLANs necessary for dsa_8021q operation, which we need to
+        * determine on our own:
+        * - Sub-VLANs
+        * - Sub-VLANs of crosschip switches
+        */
+       rc = sja1105_build_subvlans(priv, subvlan_map, new_vlan, new_retagging,
+                                   &num_retagging);
+       if (rc)
+               goto out;
+
+       rc = sja1105_build_crosschip_subvlans(priv, new_vlan, new_retagging,
+                                             &num_retagging);
+       if (rc)
+               goto out;
+
+       rc = sja1105_commit_vlans(priv, new_vlan, new_retagging, num_retagging);
        if (rc)
                goto out;
 
@@ -2110,6 +2512,9 @@ static int sja1105_build_vlan_table(struct sja1105_private *priv, bool notify)
        if (rc)
                goto out;
 
+       for (i = 0; i < priv->ds->num_ports; i++)
+               sja1105_commit_subvlan_map(priv, i, subvlan_map[i]);
+
        if (notify) {
                rc = sja1105_notify_crosschip_switches(priv);
                if (rc)
@@ -2118,6 +2523,7 @@ static int sja1105_build_vlan_table(struct sja1105_private *priv, bool notify)
 
 out:
        kfree(new_vlan);
+       kfree(new_retagging);
 
        return rc;
 }