@@ -387,7 +387,8 @@ static int sja1105_init_l2_forwarding_params(struct sja1105_private *priv)
/* Disallow dynamic reconfiguration of vlan_pmap */
.max_dynp = 0,
/* Use a single memory partition for all ingress queues */
- .part_spc = { SJA1105_MAX_FRAME_MEMORY, 0, 0, 0, 0, 0, 0, 0 },
+ .part_spc = { SJA1105_MAX_FRAME_MEMORY_RETAGGING,
+ 0, 0, 0, 0, 0, 0, 0 },
};
struct sja1105_table *table;
@@ -1733,6 +1734,31 @@ static int sja1105_is_vlan_configured(struct sja1105_private *priv, u16 vid)
return -1;
}
+/* 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. We do that by removing the pre-retagging VID from the port
+ * membership of the egress port. For this strategy to be effective, we need a
+ * blacklist to ensure that nobody can add that VID back on the destination
+ * port, otherwise we'll see duplicates (with the old and the new VID).
+ */
+static bool sja1105_vlan_is_blacklisted(struct sja1105_private *priv, int port,
+ u16 vid)
+{
+ struct sja1105_retagging_entry *retagging;
+ struct sja1105_table *table;
+ int i;
+
+ table = &priv->static_config.tables[BLK_IDX_RETAGGING];
+ retagging = table->entries;
+
+ for (i = 0; i < table->entry_count; i++)
+ if ((retagging[i].egr_port & BIT(port)) &&
+ (retagging[i].vlan_ing == vid))
+ return true;
+
+ return false;
+}
+
static int sja1105_vlan_apply(struct sja1105_private *priv, int port, u16 vid,
bool enabled, bool untagged)
{
@@ -1741,6 +1767,9 @@ static int sja1105_vlan_apply(struct sja1105_private *priv, int port, u16 vid,
bool keep = true;
int match, rc;
+ if (enabled && sja1105_vlan_is_blacklisted(priv, port, vid))
+ return 0;
+
table = &priv->static_config.tables[BLK_IDX_VLAN_LOOKUP];
match = sja1105_is_vlan_configured(priv, vid);
@@ -1793,6 +1822,194 @@ static int sja1105_vlan_apply(struct sja1105_private *priv, int port, u16 vid,
return 0;
}
+static int sja1105_find_retagging_entry(struct sja1105_private *priv,
+ int from_port, u16 from_vid,
+ int to_port, u16 to_vid)
+{
+ struct sja1105_retagging_entry *retagging;
+ struct sja1105_table *table;
+ int i;
+
+ table = &priv->static_config.tables[BLK_IDX_RETAGGING];
+ retagging = table->entries;
+
+ for (i = 0; i < table->entry_count; i++)
+ if (retagging[i].ing_port & BIT(from_port) &&
+ retagging[i].egr_port & BIT(to_port) &&
+ retagging[i].vlan_ing == from_vid &&
+ retagging[i].vlan_egr == to_vid)
+ return i;
+
+ return -1;
+}
+
+static int sja1105_setup_retagging_vid(struct sja1105_private *priv,
+ int from_port, u16 from_vid, int to_port,
+ u16 to_vid, bool keep, bool untagged)
+{
+ int rc;
+
+ rc = sja1105_vlan_apply(priv, from_port, to_vid, keep, true);
+ if (rc)
+ return rc;
+
+ rc = sja1105_vlan_apply(priv, to_port, to_vid, keep, untagged);
+ if (rc)
+ return rc;
+
+ return sja1105_vlan_apply(priv, to_port, from_vid, false, false);
+}
+
+static int sja1105_retagging_apply(struct sja1105_private *priv, int from_port,
+ u16 from_vid, int to_port, u16 to_vid,
+ bool keep, bool untagged)
+{
+ struct sja1105_retagging_entry *retagging;
+ struct sja1105_table *table;
+ int rc, match;
+
+ rc = sja1105_setup_retagging_vid(priv, from_port, from_vid, to_port,
+ to_vid, keep, untagged);
+ if (rc)
+ return rc;
+
+ table = &priv->static_config.tables[BLK_IDX_RETAGGING];
+
+ match = sja1105_find_retagging_entry(priv, from_port, from_vid,
+ to_port, to_vid);
+ if (match < 0) {
+ /* Can't delete a missing entry. */
+ if (!keep) {
+ dev_err(priv->ds->dev, "can't delete a missing entry\n");
+ return 0;
+ }
+
+ /* No match => new entry */
+ rc = sja1105_table_resize(table, table->entry_count + 1);
+ if (rc) {
+ dev_err(priv->ds->dev, "failed to resize retagging table: %d\n", rc);
+ return rc;
+ }
+
+ match = table->entry_count - 1;
+ }
+
+ /* Assign pointer after the resize (it may be new memory) */
+ retagging = table->entries;
+
+ if (keep) {
+ retagging[match].egr_port = BIT(to_port);
+ retagging[match].ing_port = BIT(from_port);
+ retagging[match].vlan_ing = from_vid;
+ retagging[match].vlan_egr = to_vid;
+ retagging[match].do_not_learn = false;
+ retagging[match].use_dest_ports = true;
+ retagging[match].destports = BIT(to_port);
+
+ dev_err(priv->ds->dev,
+ "%s: entry %d egr_port 0x%llx ing_port 0x%llx vlan_ing %lld vlan_egr %lld do_not_learn %lld use_dest_ports %lld destports %lld\n",
+ __func__, match, retagging[match].egr_port, retagging[match].ing_port, retagging[match].vlan_ing, retagging[match].vlan_egr,
+ retagging[match].do_not_learn, retagging[match].use_dest_ports, retagging[match].destports);
+ return sja1105_dynamic_config_write(priv, BLK_IDX_RETAGGING,
+ match, &retagging[match],
+ true);
+ }
+
+ /* To remove, the strategy is to overwrite the element with
+ * the last one, and then reduce the array size by 1
+ */
+ retagging[match] = retagging[table->entry_count - 1];
+
+ rc = sja1105_dynamic_config_write(priv, BLK_IDX_RETAGGING,
+ table->entry_count - 1,
+ &retagging[table->entry_count - 1],
+ false);
+ if (rc)
+ return rc;
+
+ rc = sja1105_dynamic_config_write(priv, BLK_IDX_RETAGGING, match,
+ &retagging[match], true);
+ if (rc)
+ return rc;
+
+ return sja1105_table_resize(table, table->entry_count - 1);
+}
+
+static int sja1105_find_free_subvlan(struct sja1105_private *priv, int port)
+{
+ struct sja1105_port *sp = &priv->ports[port];
+ int subvlan;
+
+ for (subvlan = 1; subvlan < DSA_8021Q_N_SUBVLAN; subvlan++)
+ if (sp->subvlan_map[subvlan] == VLAN_N_VID)
+ return subvlan;
+
+ return -1;
+}
+
+static int sja1105_find_subvlan(struct sja1105_private *priv, int port, u16 vid)
+{
+ struct sja1105_port *sp = &priv->ports[port];
+ int subvlan;
+
+ for (subvlan = 1; subvlan < DSA_8021Q_N_SUBVLAN; subvlan++)
+ if (sp->subvlan_map[subvlan] == vid)
+ return subvlan;
+
+ return -1;
+}
+
+static int sja1105_subvlan_apply(struct sja1105_private *priv, int port,
+ u16 vid, bool pvid, bool keep)
+{
+ struct sja1105_port *sp = &priv->ports[port];
+ int cpu = dsa_upstream_port(priv->ds, port);
+ int rc, subvlan;
+ u16 rx_vid;
+
+ /* There are several situations when we don't want to add a subvlan */
+ if (!priv->best_effort_vlan_filtering)
+ return 0;
+ if (vid_is_dsa_8021q(vid))
+ return 0;
+ if (!dsa_is_user_port(priv->ds, port))
+ return 0;
+
+ if (keep) {
+ subvlan = sja1105_find_free_subvlan(priv, port);
+ if (subvlan < 0) {
+ dev_err(priv->ds->dev, "No more free subvlans\n");
+ return -ENOSPC;
+ }
+ } else {
+ subvlan = sja1105_find_subvlan(priv, port, vid);
+ if (subvlan < 0)
+ /* A subvlan may not be found because either we ran out
+ * (and that's ok, after all, we only support up to 7
+ * per port), or because the VID was added prior to
+ * best_effort_vlan_filtering getting toggled. So it's
+ * perfectly fine, don't do anything.
+ */
+ return 0;
+ }
+
+ if (pvid)
+ rx_vid = dsa_8021q_rx_vid(priv->ds, port);
+ else
+ rx_vid = dsa_8021q_rx_vid_subvlan(priv->ds, port, subvlan);
+
+ rc = sja1105_retagging_apply(priv, port, vid, cpu, rx_vid, keep, false);
+ if (rc)
+ return rc;
+
+ if (keep)
+ sp->subvlan_map[subvlan] = vid;
+ else
+ sp->subvlan_map[subvlan] = VLAN_N_VID;
+
+ return 0;
+}
+
static int sja1105_crosschip_bridge_join(struct dsa_switch *ds,
int tree_index, int sw_index,
int other_port, struct net_device *br)
@@ -1918,8 +2135,13 @@ static int sja1105_vlan_prepare(struct dsa_switch *ds, int port,
*/
for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
rc = dsa_8021q_vid_validate(ds, port, vid, vlan->flags);
- if (rc < 0)
- return rc;
+ /* Suppress the "wrong pvid" error. We can (and will) retag the
+ * pvid requested by the bridge to the dsa_8021q pvid. Untagged
+ * traffic is still tagged with the dsa_8021q pvid directly and
+ * does not require retagging.
+ */
+ if (rc < 0 && rc != DSA_8021Q_WRONG_PVID)
+ return -EPERM;
}
return 0;
@@ -2017,6 +2239,8 @@ static void sja1105_vlan_add(struct dsa_switch *ds, int port,
int rc;
for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
+ bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
+
rc = sja1105_vlan_apply(priv, port, vid, true, vlan->flags &
BRIDGE_VLAN_INFO_UNTAGGED);
if (rc < 0) {
@@ -2024,7 +2248,7 @@ static void sja1105_vlan_add(struct dsa_switch *ds, int port,
vid, port, rc);
return;
}
- if (vlan->flags & BRIDGE_VLAN_INFO_PVID) {
+ if (pvid) {
rc = sja1105_pvid_apply(ds->priv, port, vid);
if (rc < 0) {
dev_err(ds->dev, "Failed to set pvid %d on port %d: %d\n",
@@ -2032,6 +2256,9 @@ static void sja1105_vlan_add(struct dsa_switch *ds, int port,
return;
}
}
+ rc = sja1105_subvlan_apply(priv, port, vid, pvid, true);
+ if (rc)
+ return;
}
}
@@ -2043,6 +2270,8 @@ static int sja1105_vlan_del(struct dsa_switch *ds, int port,
int rc;
for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
+ bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
+
rc = sja1105_vlan_apply(priv, port, vid, false, vlan->flags &
BRIDGE_VLAN_INFO_UNTAGGED);
if (rc < 0) {
@@ -2050,6 +2279,9 @@ static int sja1105_vlan_del(struct dsa_switch *ds, int port,
vid, port, rc);
return rc;
}
+ rc = sja1105_subvlan_apply(priv, port, vid, pvid, false);
+ if (rc)
+ return rc;
}
return 0;
}
@@ -2728,6 +2960,7 @@ static int sja1105_probe(struct spi_device *spi)
struct sja1105_port *sp = &priv->ports[port];
struct dsa_port *dp = dsa_to_port(ds, port);
struct net_device *slave;
+ int subvlan;
if (!dsa_is_user_port(ds, port))
continue;
@@ -2747,6 +2980,9 @@ static int sja1105_probe(struct spi_device *spi)
goto out;
}
skb_queue_head_init(&sp->xmit_queue);
+
+ for (subvlan = 0; subvlan < DSA_8021Q_N_SUBVLAN; subvlan++)
+ sp->subvlan_map[subvlan] = VLAN_N_VID;
}
return 0;
@@ -20,6 +20,16 @@ struct dsa_8021q_crosschip_link {
refcount_t refcount;
};
+enum dsa_8021q_vid_error {
+ DSA_8021Q_VID_OK = 0,
+ DSA_8021Q_WRONG_PVID = -1,
+ DSA_8021Q_TX_VLAN_WRONG_FLAGS = -2,
+ DSA_8021Q_TX_VLAN_WRONG_PORT = -3,
+ DSA_8021Q_RX_VLAN_WRONG_FLAGS = -4,
+};
+
+#define DSA_8021Q_N_SUBVLAN 8
+
#if IS_ENABLED(CONFIG_NET_DSA_TAG_8021Q)
int dsa_port_setup_8021q_tagging(struct dsa_switch *ds, int index,
@@ -48,10 +58,16 @@ u16 dsa_8021q_tx_vid(struct dsa_switch *ds, int port);
u16 dsa_8021q_rx_vid(struct dsa_switch *ds, int port);
+u16 dsa_8021q_rx_vid_subvlan(struct dsa_switch *ds, int port, u16 subvlan);
+
int dsa_8021q_rx_switch_id(u16 vid);
int dsa_8021q_rx_source_port(u16 vid);
+u16 dsa_8021q_rx_subvlan(u16 vid);
+
+bool vid_is_dsa_8021q(u16 vid);
+
#else
int dsa_port_setup_8021q_tagging(struct dsa_switch *ds, int index,
@@ -104,6 +120,11 @@ u16 dsa_8021q_rx_vid(struct dsa_switch *ds, int port)
return 0;
}
+u16 dsa_8021q_rx_vid_subvlan(struct dsa_switch *ds, int port, u16 subvlan)
+{
+ return 0;
+}
+
int dsa_8021q_rx_switch_id(u16 vid)
{
return 0;
@@ -114,6 +135,16 @@ int dsa_8021q_rx_source_port(u16 vid)
return 0;
}
+u16 dsa_8021q_rx_subvlan(u16 vid)
+{
+ return 0;
+}
+
+bool vid_is_dsa_8021q(u16 vid)
+{
+ return false;
+}
+
#endif /* IS_ENABLED(CONFIG_NET_DSA_TAG_8021Q) */
#endif /* _NET_DSA_8021Q_H */
@@ -9,6 +9,7 @@
#include <linux/skbuff.h>
#include <linux/etherdevice.h>
+#include <linux/dsa/8021q.h>
#include <net/dsa.h>
#define ETH_P_SJA1105 ETH_P_DSA_8021Q
@@ -53,6 +54,7 @@ struct sja1105_skb_cb {
((struct sja1105_skb_cb *)DSA_SKB_CB_PRIV(skb))
struct sja1105_port {
+ u16 subvlan_map[DSA_8021Q_N_SUBVLAN];
struct kthread_worker *xmit_worker;
struct kthread_work xmit_work;
struct sk_buff_head xmit_queue;
@@ -17,7 +17,7 @@
*
* | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
* +-----------+-----+-----------------+-----------+-----------------------+
- * | DIR | RSV | SWITCH_ID | RSV | PORT |
+ * | DIR | SVL | SWITCH_ID | SUBVLAN | PORT |
* +-----------+-----+-----------------+-----------+-----------------------+
*
* DIR - VID[11:10]:
@@ -27,17 +27,24 @@
* These values make the special VIDs of 0, 1 and 4095 to be left
* unused by this coding scheme.
*
- * RSV - VID[9]:
- * To be used for further expansion of SWITCH_ID or for other purposes.
- * Must be transmitted as zero and ignored on receive.
+ * SVL/SUBVLAN - { VID[9], VID[5:4] }:
+ * Sub-VLAN encoding. Valid only when DIR indicates an RX VLAN.
+ * * 0 (0b000): Field does not encode a sub-VLAN, either because
+ * received traffic is untagged, PVID-tagged or because a second
+ * VLAN tag is present after this tag and not inside of it.
+ * * 1 (0b001): Received traffic is tagged with a VID value private
+ * to the host. This field encodes the index in the host's lookup
+ * table through which the value of the ingress VLAN ID can be
+ * recovered.
+ * * 2 (0b010): Field encodes a sub-VLAN.
+ * ...
+ * * 7 (0b111): Field encodes a sub-VLAN.
+ * When DIR indicates a TX VLAN, SUBVLAN must be transmitted as zero
+ * (by the host) and ignored on receive (by the switch).
*
* SWITCH_ID - VID[8:6]:
* Index of switch within DSA tree. Must be between 0 and 7.
*
- * RSV - VID[5:4]:
- * To be used for further expansion of PORT or for other purposes.
- * Must be transmitted as zero and ignored on receive.
- *
* PORT - VID[3:0]:
* Index of switch port. Must be between 0 and 15.
*/
@@ -54,6 +61,18 @@
#define DSA_8021Q_SWITCH_ID(x) (((x) << DSA_8021Q_SWITCH_ID_SHIFT) & \
DSA_8021Q_SWITCH_ID_MASK)
+#define DSA_8021Q_SUBVLAN_HI_SHIFT 9
+#define DSA_8021Q_SUBVLAN_HI_MASK GENMASK(9, 9)
+#define DSA_8021Q_SUBVLAN_LO_SHIFT 4
+#define DSA_8021Q_SUBVLAN_LO_MASK GENMASK(4, 3)
+#define DSA_8021Q_SUBVLAN_HI(x) (((x) & GENMASK(2, 2)) >> 2)
+#define DSA_8021Q_SUBVLAN_LO(x) ((x) & GENMASK(1, 0))
+#define DSA_8021Q_SUBVLAN(x) \
+ (((DSA_8021Q_SUBVLAN_LO(x) << DSA_8021Q_SUBVLAN_LO_SHIFT) & \
+ DSA_8021Q_SUBVLAN_LO_MASK) | \
+ ((DSA_8021Q_SUBVLAN_HI(x) << DSA_8021Q_SUBVLAN_HI_SHIFT) & \
+ DSA_8021Q_SUBVLAN_HI_MASK))
+
#define DSA_8021Q_PORT_SHIFT 0
#define DSA_8021Q_PORT_MASK GENMASK(3, 0)
#define DSA_8021Q_PORT(x) (((x) << DSA_8021Q_PORT_SHIFT) & \
@@ -79,6 +98,13 @@ u16 dsa_8021q_rx_vid(struct dsa_switch *ds, int port)
}
EXPORT_SYMBOL_GPL(dsa_8021q_rx_vid);
+u16 dsa_8021q_rx_vid_subvlan(struct dsa_switch *ds, int port, u16 subvlan)
+{
+ return DSA_8021Q_DIR_RX | DSA_8021Q_SWITCH_ID(ds->index) |
+ DSA_8021Q_PORT(port) | DSA_8021Q_SUBVLAN(subvlan);
+}
+EXPORT_SYMBOL_GPL(dsa_8021q_rx_vid_subvlan);
+
/* Returns the decoded switch ID from the RX VID. */
int dsa_8021q_rx_switch_id(u16 vid)
{
@@ -93,6 +119,27 @@ int dsa_8021q_rx_source_port(u16 vid)
}
EXPORT_SYMBOL_GPL(dsa_8021q_rx_source_port);
+/* Returns the decoded subvlan from the RX VID. */
+u16 dsa_8021q_rx_subvlan(u16 vid)
+{
+ u16 svl_hi, svl_lo;
+
+ svl_hi = (vid & DSA_8021Q_SUBVLAN_HI_MASK) >>
+ DSA_8021Q_SUBVLAN_HI_SHIFT;
+ svl_lo = (vid & DSA_8021Q_SUBVLAN_LO_MASK) >>
+ DSA_8021Q_SUBVLAN_LO_SHIFT;
+
+ return (svl_hi << 2) | svl_lo;
+}
+EXPORT_SYMBOL_GPL(dsa_8021q_rx_subvlan);
+
+bool vid_is_dsa_8021q(u16 vid)
+{
+ return ((vid & DSA_8021Q_DIR_MASK) == DSA_8021Q_DIR_RX ||
+ (vid & DSA_8021Q_DIR_MASK) == DSA_8021Q_DIR_TX);
+}
+EXPORT_SYMBOL_GPL(vid_is_dsa_8021q);
+
static int dsa_8021q_restore_pvid(struct dsa_switch *ds, int port)
{
struct bridge_vlan_info vinfo;
@@ -289,7 +336,8 @@ int dsa_port_setup_8021q_tagging(struct dsa_switch *ds, int port, bool enabled)
}
EXPORT_SYMBOL_GPL(dsa_port_setup_8021q_tagging);
-int dsa_8021q_vid_validate(struct dsa_switch *ds, int port, u16 vid, u16 flags)
+enum dsa_8021q_vid_error dsa_8021q_vid_validate(struct dsa_switch *ds, int port,
+ u16 vid, u16 flags)
{
int upstream = dsa_upstream_port(ds, port);
int rx_vid_of = ds->num_ports;
@@ -299,7 +347,7 @@ int dsa_8021q_vid_validate(struct dsa_switch *ds, int port, u16 vid, u16 flags)
/* @vid wants to be a pvid of @port, but is not equal to its rx_vid */
if ((flags & BRIDGE_VLAN_INFO_PVID) &&
vid != dsa_8021q_rx_vid(ds, port))
- return -EPERM;
+ return DSA_8021Q_WRONG_PVID;
for (other_port = 0; other_port < ds->num_ports; other_port++) {
if (!dsa_is_user_port(ds, other_port))
@@ -318,15 +366,15 @@ int dsa_8021q_vid_validate(struct dsa_switch *ds, int port, u16 vid, u16 flags)
if (tx_vid_of != ds->num_ports) {
if (tx_vid_of == port) {
if (flags != BRIDGE_VLAN_INFO_UNTAGGED)
- return -EPERM;
+ return DSA_8021Q_TX_VLAN_WRONG_FLAGS;
/* Fall through on proper flags */
} else if (port == upstream) {
if (flags != 0)
- return -EPERM;
+ return DSA_8021Q_TX_VLAN_WRONG_FLAGS;
/* Fall through on proper flags */
} else {
/* Trying to configure on other port */
- return -EPERM;
+ return DSA_8021Q_TX_VLAN_WRONG_PORT;
}
}
@@ -335,17 +383,17 @@ int dsa_8021q_vid_validate(struct dsa_switch *ds, int port, u16 vid, u16 flags)
if (rx_vid_of == port) {
if (flags != (BRIDGE_VLAN_INFO_UNTAGGED |
BRIDGE_VLAN_INFO_PVID))
- return -EPERM;
+ return DSA_8021Q_RX_VLAN_WRONG_FLAGS;
/* Fall through on proper flags */
} else if (port == upstream) {
if (flags != 0)
- return -EPERM;
+ return DSA_8021Q_RX_VLAN_WRONG_FLAGS;
/* Fall through on proper flags */
} else if (flags != BRIDGE_VLAN_INFO_UNTAGGED) {
/* Trying to configure on other port, but with
* invalid flags.
*/
- return -EPERM;
+ return DSA_8021Q_RX_VLAN_WRONG_FLAGS;
}
}
@@ -113,7 +113,7 @@ static struct sk_buff *sja1105_xmit(struct sk_buff *skb,
return sja1105_defer_xmit(dp->priv, skb);
if (dsa_port_is_vlan_filtering(dp))
- tpid = ETH_P_8021Q;
+ tpid = ETH_P_8021AD;
else
tpid = ETH_P_SJA1105;
@@ -242,6 +242,20 @@ static struct sk_buff
return skb;
}
+static void sja1105_decode_subvlan(struct sk_buff *skb, u16 subvlan)
+{
+ struct dsa_port *dp = dsa_slave_to_port(skb->dev);
+ struct sja1105_port *sp = dp->priv;
+ u16 vid = sp->subvlan_map[subvlan];
+ u16 vlan_tci;
+
+ if (vid == VLAN_N_VID)
+ return;
+
+ vlan_tci = (skb->priority << VLAN_PRIO_SHIFT) | vid;
+ __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vlan_tci);
+}
+
static struct sk_buff *sja1105_rcv(struct sk_buff *skb,
struct net_device *netdev,
struct packet_type *pt)
@@ -251,6 +265,7 @@ static struct sk_buff *sja1105_rcv(struct sk_buff *skb,
struct ethhdr *hdr;
u16 tpid, vid, tci;
bool is_link_local;
+ u16 subvlan = 0;
bool is_tagged;
bool is_meta;
@@ -274,6 +289,7 @@ static struct sk_buff *sja1105_rcv(struct sk_buff *skb,
source_port = dsa_8021q_rx_source_port(vid);
switch_id = dsa_8021q_rx_switch_id(vid);
skb->priority = (tci & VLAN_PRIO_MASK) >> VLAN_PRIO_SHIFT;
+ subvlan = dsa_8021q_rx_subvlan(vid);
} else if (is_link_local) {
/* Management traffic path. Switch embeds the switch ID and
* port ID into bytes of the destination MAC, courtesy of
@@ -298,6 +314,9 @@ static struct sk_buff *sja1105_rcv(struct sk_buff *skb,
return NULL;
}
+ if (subvlan)
+ sja1105_decode_subvlan(skb, subvlan);
+
return sja1105_rcv_meta_state_machine(skb, &meta, is_link_local,
is_meta);
}