Skip to content

Commit f1288fd

Browse files
vladimirolteandavem330
authored andcommitted
net: dsa: felix: fix VLAN tag loss on CPU reception with ocelot-8021q
There is a major design bug with ocelot-8021q, which is that it expects more of the hardware than the hardware can actually do. The short summary of the issue is that when a port is under a VLAN-aware bridge and we use this tagging protocol, VLAN upper interfaces of this port do not see RX traffic. We use VCAP ES0 (egress rewriter) rules towards the tag_8021q CPU port to encapsulate packets with an outer tag, later stripped by software, that depends on the source user port. We do this so that packets can be identified in ocelot_rcv(). To be precise, we create rules with push_outer_tag = OCELOT_ES0_TAG and push_inner_tag = 0. With this configuration, we expect the switch to keep the inner tag configuration as found in the packet (if it was untagged on user port ingress, keep it untagged, otherwise preserve the VLAN tag unmodified as the inner tag towards the tag_8021q CPU port). But this is not what happens. Instead, table "Tagging Combinations" from the user manual suggests that when the ES0 action is "PUSH_OUTER_TAG=1 and PUSH_INNER_TAG=0", there will be "no inner tag". Experimentation further clarifies what this means. It appears that this "inner tag" which is not pushed into the packet on its egress towards the CPU is none other than the classified VLAN. When the ingress user port is standalone or under a VLAN-unaware bridge, the classified VLAN is a discardable quantity: it is a fixed value - the result of ocelot_vlan_unaware_pvid()'s configuration, and actually independent of the VID from any 802.1Q header that may be in the frame. It is actually preferable to discard the "inner tag" in this case. The problem is when the ingress port is under a VLAN-aware bridge. Then, the classified VLAN is taken from the frame's 802.1Q header, with a fallback on the bridge port's PVID. It would be very good to not discard the "inner tag" here, because if we do, we break communication with any 8021q VLAN uppers that the port might have. These have a processing path outside the bridge. There seems to be nothing else we can do except to change the configuration for VCAP ES0 rules, to actually push the inner VLAN into the frame. There are 2 options for that, first is to push a fixed value specified in the rule, and second is to push a fixed value, plus (aka arithmetic +) the classified VLAN. We choose the second option, and we select that fixed value as 0. Thus, what is pushed in the inner tag is just the classified VLAN. From there, we need to perform software untagging, in the receive path, of stuff that was untagged on the wire. Fixes: 7c83a7c ("net: dsa: add a second tagger for Ocelot switches based on tag_8021q") Signed-off-by: Vladimir Oltean <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 93e4649 commit f1288fd

File tree

1 file changed

+109
-6
lines changed

1 file changed

+109
-6
lines changed

drivers/net/dsa/ocelot/felix.c

Lines changed: 109 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,46 @@ static int felix_cpu_port_for_conduit(struct dsa_switch *ds,
6161
return cpu_dp->index;
6262
}
6363

64+
/**
65+
* felix_update_tag_8021q_rx_rule - Update VCAP ES0 tag_8021q rule after
66+
* vlan_filtering change
67+
* @outer_tagging_rule: Pointer to VCAP filter on which the update is performed
68+
* @vlan_filtering: Current bridge VLAN filtering setting
69+
*
70+
* Source port identification for tag_8021q is done using VCAP ES0 rules on the
71+
* CPU port(s). The ES0 tag B (inner tag from the packet) can be configured as
72+
* either:
73+
* - push_inner_tag=0: the inner tag is never pushed into the frame
74+
* (and we lose info about the classified VLAN). This is
75+
* good when the classified VLAN is a discardable quantity
76+
* for the software RX path: it is either set to
77+
* OCELOT_STANDALONE_PVID, or to
78+
* ocelot_vlan_unaware_pvid(bridge).
79+
* - push_inner_tag=1: the inner tag is always pushed. This is good when the
80+
* classified VLAN is not a discardable quantity (the port
81+
* is under a VLAN-aware bridge, and software needs to
82+
* continue processing the packet in the same VLAN as the
83+
* hardware).
84+
* The point is that what is good for a VLAN-unaware port is not good for a
85+
* VLAN-aware port, and vice versa. Thus, the RX tagging rules must be kept in
86+
* sync with the VLAN filtering state of the port.
87+
*/
88+
static void
89+
felix_update_tag_8021q_rx_rule(struct ocelot_vcap_filter *outer_tagging_rule,
90+
bool vlan_filtering)
91+
{
92+
if (vlan_filtering)
93+
outer_tagging_rule->action.push_inner_tag = OCELOT_ES0_TAG;
94+
else
95+
outer_tagging_rule->action.push_inner_tag = OCELOT_NO_ES0_TAG;
96+
}
97+
6498
/* Set up VCAP ES0 rules for pushing a tag_8021q VLAN towards the CPU such that
6599
* the tagger can perform RX source port identification.
66100
*/
67101
static int felix_tag_8021q_vlan_add_rx(struct dsa_switch *ds, int port,
68-
int upstream, u16 vid)
102+
int upstream, u16 vid,
103+
bool vlan_filtering)
69104
{
70105
struct ocelot_vcap_filter *outer_tagging_rule;
71106
struct ocelot *ocelot = ds->priv;
@@ -96,6 +131,14 @@ static int felix_tag_8021q_vlan_add_rx(struct dsa_switch *ds, int port,
96131
outer_tagging_rule->action.tag_a_tpid_sel = OCELOT_TAG_TPID_SEL_8021AD;
97132
outer_tagging_rule->action.tag_a_vid_sel = 1;
98133
outer_tagging_rule->action.vid_a_val = vid;
134+
felix_update_tag_8021q_rx_rule(outer_tagging_rule, vlan_filtering);
135+
outer_tagging_rule->action.tag_b_tpid_sel = OCELOT_TAG_TPID_SEL_8021Q;
136+
/* Leave TAG_B_VID_SEL at 0 (Classified VID + VID_B_VAL). Since we also
137+
* leave VID_B_VAL at 0, this makes ES0 tag B (the inner tag) equal to
138+
* the classified VID, which we need to see in the DSA tagger's receive
139+
* path. Note: the inner tag is only visible in the packet when pushed
140+
* (push_inner_tag == OCELOT_ES0_TAG).
141+
*/
99142

100143
err = ocelot_vcap_filter_add(ocelot, outer_tagging_rule, NULL);
101144
if (err)
@@ -227,18 +270,20 @@ static int felix_tag_8021q_vlan_del_tx(struct dsa_switch *ds, int port, u16 vid)
227270
static int felix_tag_8021q_vlan_add(struct dsa_switch *ds, int port, u16 vid,
228271
u16 flags)
229272
{
273+
struct dsa_port *dp = dsa_to_port(ds, port);
230274
struct dsa_port *cpu_dp;
231275
int err;
232276

233277
/* tag_8021q.c assumes we are implementing this via port VLAN
234278
* membership, which we aren't. So we don't need to add any VCAP filter
235279
* for the CPU port.
236280
*/
237-
if (!dsa_is_user_port(ds, port))
281+
if (!dsa_port_is_user(dp))
238282
return 0;
239283

240284
dsa_switch_for_each_cpu_port(cpu_dp, ds) {
241-
err = felix_tag_8021q_vlan_add_rx(ds, port, cpu_dp->index, vid);
285+
err = felix_tag_8021q_vlan_add_rx(ds, port, cpu_dp->index, vid,
286+
dsa_port_is_vlan_filtering(dp));
242287
if (err)
243288
return err;
244289
}
@@ -258,10 +303,11 @@ static int felix_tag_8021q_vlan_add(struct dsa_switch *ds, int port, u16 vid,
258303

259304
static int felix_tag_8021q_vlan_del(struct dsa_switch *ds, int port, u16 vid)
260305
{
306+
struct dsa_port *dp = dsa_to_port(ds, port);
261307
struct dsa_port *cpu_dp;
262308
int err;
263309

264-
if (!dsa_is_user_port(ds, port))
310+
if (!dsa_port_is_user(dp))
265311
return 0;
266312

267313
dsa_switch_for_each_cpu_port(cpu_dp, ds) {
@@ -278,11 +324,41 @@ static int felix_tag_8021q_vlan_del(struct dsa_switch *ds, int port, u16 vid)
278324

279325
del_tx_failed:
280326
dsa_switch_for_each_cpu_port(cpu_dp, ds)
281-
felix_tag_8021q_vlan_add_rx(ds, port, cpu_dp->index, vid);
327+
felix_tag_8021q_vlan_add_rx(ds, port, cpu_dp->index, vid,
328+
dsa_port_is_vlan_filtering(dp));
282329

283330
return err;
284331
}
285332

333+
static int felix_update_tag_8021q_rx_rules(struct dsa_switch *ds, int port,
334+
bool vlan_filtering)
335+
{
336+
struct ocelot_vcap_filter *outer_tagging_rule;
337+
struct ocelot_vcap_block *block_vcap_es0;
338+
struct ocelot *ocelot = ds->priv;
339+
struct dsa_port *cpu_dp;
340+
unsigned long cookie;
341+
int err;
342+
343+
block_vcap_es0 = &ocelot->block[VCAP_ES0];
344+
345+
dsa_switch_for_each_cpu_port(cpu_dp, ds) {
346+
cookie = OCELOT_VCAP_ES0_TAG_8021Q_RXVLAN(ocelot, port,
347+
cpu_dp->index);
348+
349+
outer_tagging_rule = ocelot_vcap_block_find_filter_by_id(block_vcap_es0,
350+
cookie, false);
351+
352+
felix_update_tag_8021q_rx_rule(outer_tagging_rule, vlan_filtering);
353+
354+
err = ocelot_vcap_filter_replace(ocelot, outer_tagging_rule);
355+
if (err)
356+
return err;
357+
}
358+
359+
return 0;
360+
}
361+
286362
static int felix_trap_get_cpu_port(struct dsa_switch *ds,
287363
const struct ocelot_vcap_filter *trap)
288364
{
@@ -532,6 +608,16 @@ static int felix_tag_8021q_setup(struct dsa_switch *ds)
532608
ocelot_drain_cpu_queue(ocelot, 0);
533609
ocelot_unlock_xtr_grp_bh(ocelot, 0);
534610

611+
/* Problem: when using push_inner_tag=1 for ES0 tag B, we lose info
612+
* about whether the received packets were VLAN-tagged on the wire,
613+
* since they are always tagged on egress towards the CPU port.
614+
*
615+
* Since using push_inner_tag=1 is unavoidable for VLAN-aware bridges,
616+
* we must work around the fallout by untagging in software to make
617+
* untagged reception work more or less as expected.
618+
*/
619+
ds->untag_vlan_aware_bridge_pvid = true;
620+
535621
return 0;
536622
}
537623

@@ -556,6 +642,8 @@ static void felix_tag_8021q_teardown(struct dsa_switch *ds)
556642
ocelot_port_teardown_dsa_8021q_cpu(ocelot, dp->index);
557643

558644
dsa_tag_8021q_unregister(ds);
645+
646+
ds->untag_vlan_aware_bridge_pvid = false;
559647
}
560648

561649
static unsigned long felix_tag_8021q_get_host_fwd_mask(struct dsa_switch *ds)
@@ -1010,8 +1098,23 @@ static int felix_vlan_filtering(struct dsa_switch *ds, int port, bool enabled,
10101098
struct netlink_ext_ack *extack)
10111099
{
10121100
struct ocelot *ocelot = ds->priv;
1101+
bool using_tag_8021q;
1102+
struct felix *felix;
1103+
int err;
10131104

1014-
return ocelot_port_vlan_filtering(ocelot, port, enabled, extack);
1105+
err = ocelot_port_vlan_filtering(ocelot, port, enabled, extack);
1106+
if (err)
1107+
return err;
1108+
1109+
felix = ocelot_to_felix(ocelot);
1110+
using_tag_8021q = felix->tag_proto == DSA_TAG_PROTO_OCELOT_8021Q;
1111+
if (using_tag_8021q) {
1112+
err = felix_update_tag_8021q_rx_rules(ds, port, enabled);
1113+
if (err)
1114+
return err;
1115+
}
1116+
1117+
return 0;
10151118
}
10161119

10171120
static int felix_vlan_add(struct dsa_switch *ds, int port,

0 commit comments

Comments
 (0)