Skip to content

Commit 36dd114

Browse files
vladimirolteandavem330
authored andcommitted
net: mscc: ocelot: treat 802.1ad tagged traffic as 802.1Q-untagged
I was revisiting the topic of 802.1ad treatment in the Ocelot switch [0] and realized that not only is its basic VLAN classification pipeline improper for offloading vlan_protocol 802.1ad bridges, but also improper for offloading regular 802.1Q bridges already. Namely, 802.1ad-tagged traffic should be treated as VLAN-untagged by bridged ports, but this switch treats it as if it was 802.1Q-tagged with the same VID as in the 802.1ad header. This is markedly different to what the Linux bridge expects; see the "other_tpid()" function in tools/testing/selftests/net/forwarding/bridge_vlan_aware.sh. An idea came to me that the VCAP IS1 TCAM is more powerful than I'm giving it credit for, and that it actually overwrites the classified VID before the VLAN Table lookup takes place. In other words, it can be used even to save a packet from being dropped on ingress due to VLAN membership. Add a sophisticated TCAM rule hardcoded into the driver to force the switch to behave like a Linux bridge with vlan_filtering 1 vlan_protocol 802.1Q. Regarding the lifetime of the filter: eventually the bridge will disappear, and vlan_filtering on the port will be restored to 0 for standalone mode. Then the filter will be deleted. [0]: https://lore.kernel.org/netdev/20201009122947.nvhye4hvcha3tljh@skbuf/ Fixes: 7142529 ("net: mscc: ocelot: add VLAN filtering") Signed-off-by: Vladimir Oltean <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent f1288fd commit 36dd114

File tree

3 files changed

+180
-11
lines changed

3 files changed

+180
-11
lines changed

drivers/net/ethernet/mscc/ocelot.c

Lines changed: 177 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -453,9 +453,158 @@ static u16 ocelot_vlan_unaware_pvid(struct ocelot *ocelot,
453453
return VLAN_N_VID - bridge_num - 1;
454454
}
455455

456+
/**
457+
* ocelot_update_vlan_reclassify_rule() - Make switch aware only to bridge VLAN TPID
458+
*
459+
* @ocelot: Switch private data structure
460+
* @port: Index of ingress port
461+
*
462+
* IEEE 802.1Q-2018 clauses "5.5 C-VLAN component conformance" and "5.6 S-VLAN
463+
* component conformance" suggest that a C-VLAN component should only recognize
464+
* and filter on C-Tags, and an S-VLAN component should only recognize and
465+
* process based on C-Tags.
466+
*
467+
* In Linux, as per commit 1a0b20b25732 ("Merge branch 'bridge-next'"), C-VLAN
468+
* components are largely represented by a bridge with vlan_protocol 802.1Q,
469+
* and S-VLAN components by a bridge with vlan_protocol 802.1ad.
470+
*
471+
* Currently the driver only offloads vlan_protocol 802.1Q, but the hardware
472+
* design is non-conformant, because the switch assigns each frame to a VLAN
473+
* based on an entirely different question, as detailed in figure "Basic VLAN
474+
* Classification Flow" from its manual and reproduced below.
475+
*
476+
* Set TAG_TYPE, PCP, DEI, VID to port-default values in VLAN_CFG register
477+
* if VLAN_AWARE_ENA[port] and frame has outer tag then:
478+
* if VLAN_INNER_TAG_ENA[port] and frame has inner tag then:
479+
* TAG_TYPE = (Frame.InnerTPID <> 0x8100)
480+
* Set PCP, DEI, VID to values from inner VLAN header
481+
* else:
482+
* TAG_TYPE = (Frame.OuterTPID <> 0x8100)
483+
* Set PCP, DEI, VID to values from outer VLAN header
484+
* if VID == 0 then:
485+
* VID = VLAN_CFG.VLAN_VID
486+
*
487+
* Summarized, the switch will recognize both 802.1Q and 802.1ad TPIDs as VLAN
488+
* "with equal rights", and just set the TAG_TYPE bit to 0 (if 802.1Q) or to 1
489+
* (if 802.1ad). It will classify based on whichever of the tags is "outer", no
490+
* matter what TPID that may have (or "inner", if VLAN_INNER_TAG_ENA[port]).
491+
*
492+
* In the VLAN Table, the TAG_TYPE information is not accessible - just the
493+
* classified VID is - so it is as if each VLAN Table entry is for 2 VLANs:
494+
* C-VLAN X, and S-VLAN X.
495+
*
496+
* Whereas the Linux bridge behavior is to only filter on frames with a TPID
497+
* equal to the vlan_protocol, and treat everything else as VLAN-untagged.
498+
*
499+
* Consider an ingress packet tagged with 802.1ad VID=3 and 802.1Q VID=5,
500+
* received on a bridge vlan_filtering=1 vlan_protocol=802.1Q port. This frame
501+
* should be treated as 802.1Q-untagged, and classified to the PVID of that
502+
* bridge port. Not to VID=3, and not to VID=5.
503+
*
504+
* The VCAP IS1 TCAM has everything we need to overwrite the choices made in
505+
* the basic VLAN classification pipeline: it can match on TAG_TYPE in the key,
506+
* and it can modify the classified VID in the action. Thus, for each port
507+
* under a vlan_filtering bridge, we can insert a rule in VCAP IS1 lookup 0 to
508+
* match on 802.1ad tagged frames and modify their classified VID to the 802.1Q
509+
* PVID of the port. This effectively makes it appear to the outside world as
510+
* if those packets were processed as VLAN-untagged.
511+
*
512+
* The rule needs to be updated each time the bridge PVID changes, and needs
513+
* to be deleted if the bridge PVID is deleted, or if the port becomes
514+
* VLAN-unaware.
515+
*/
516+
static int ocelot_update_vlan_reclassify_rule(struct ocelot *ocelot, int port)
517+
{
518+
unsigned long cookie = OCELOT_VCAP_IS1_VLAN_RECLASSIFY(ocelot, port);
519+
struct ocelot_vcap_block *block_vcap_is1 = &ocelot->block[VCAP_IS1];
520+
struct ocelot_port *ocelot_port = ocelot->ports[port];
521+
const struct ocelot_bridge_vlan *pvid_vlan;
522+
struct ocelot_vcap_filter *filter;
523+
int err, val, pcp, dei;
524+
bool vid_replace_ena;
525+
u16 vid;
526+
527+
pvid_vlan = ocelot_port->pvid_vlan;
528+
vid_replace_ena = ocelot_port->vlan_aware && pvid_vlan;
529+
530+
filter = ocelot_vcap_block_find_filter_by_id(block_vcap_is1, cookie,
531+
false);
532+
if (!vid_replace_ena) {
533+
/* If the reclassification filter doesn't need to exist, delete
534+
* it if it was previously installed, and exit doing nothing
535+
* otherwise.
536+
*/
537+
if (filter)
538+
return ocelot_vcap_filter_del(ocelot, filter);
539+
540+
return 0;
541+
}
542+
543+
/* The reclassification rule must apply. See if it already exists
544+
* or if it must be created.
545+
*/
546+
547+
/* Treating as VLAN-untagged means using as classified VID equal to
548+
* the bridge PVID, and PCP/DEI set to the port default QoS values.
549+
*/
550+
vid = pvid_vlan->vid;
551+
val = ocelot_read_gix(ocelot, ANA_PORT_QOS_CFG, port);
552+
pcp = ANA_PORT_QOS_CFG_QOS_DEFAULT_VAL_X(val);
553+
dei = !!(val & ANA_PORT_QOS_CFG_DP_DEFAULT_VAL);
554+
555+
if (filter) {
556+
bool changed = false;
557+
558+
/* Filter exists, just update it */
559+
if (filter->action.vid != vid) {
560+
filter->action.vid = vid;
561+
changed = true;
562+
}
563+
if (filter->action.pcp != pcp) {
564+
filter->action.pcp = pcp;
565+
changed = true;
566+
}
567+
if (filter->action.dei != dei) {
568+
filter->action.dei = dei;
569+
changed = true;
570+
}
571+
572+
if (!changed)
573+
return 0;
574+
575+
return ocelot_vcap_filter_replace(ocelot, filter);
576+
}
577+
578+
/* Filter doesn't exist, create it */
579+
filter = kzalloc(sizeof(*filter), GFP_KERNEL);
580+
if (!filter)
581+
return -ENOMEM;
582+
583+
filter->key_type = OCELOT_VCAP_KEY_ANY;
584+
filter->ingress_port_mask = BIT(port);
585+
filter->vlan.tpid = OCELOT_VCAP_BIT_1;
586+
filter->prio = 1;
587+
filter->id.cookie = cookie;
588+
filter->id.tc_offload = false;
589+
filter->block_id = VCAP_IS1;
590+
filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
591+
filter->lookup = 0;
592+
filter->action.vid_replace_ena = true;
593+
filter->action.pcp_dei_ena = true;
594+
filter->action.vid = vid;
595+
filter->action.pcp = pcp;
596+
filter->action.dei = dei;
597+
598+
err = ocelot_vcap_filter_add(ocelot, filter, NULL);
599+
if (err)
600+
kfree(filter);
601+
602+
return err;
603+
}
604+
456605
/* Default vlan to clasify for untagged frames (may be zero) */
457-
static void ocelot_port_set_pvid(struct ocelot *ocelot, int port,
458-
const struct ocelot_bridge_vlan *pvid_vlan)
606+
static int ocelot_port_set_pvid(struct ocelot *ocelot, int port,
607+
const struct ocelot_bridge_vlan *pvid_vlan)
459608
{
460609
struct ocelot_port *ocelot_port = ocelot->ports[port];
461610
u16 pvid = ocelot_vlan_unaware_pvid(ocelot, ocelot_port->bridge);
@@ -475,15 +624,23 @@ static void ocelot_port_set_pvid(struct ocelot *ocelot, int port,
475624
* happens automatically), but also 802.1p traffic which gets
476625
* classified to VLAN 0, but that is always in our RX filter, so it
477626
* would get accepted were it not for this setting.
627+
*
628+
* Also, we only support the bridge 802.1Q VLAN protocol, so
629+
* 802.1ad-tagged frames (carrying S-Tags) should be considered
630+
* 802.1Q-untagged, and also dropped.
478631
*/
479632
if (!pvid_vlan && ocelot_port->vlan_aware)
480633
val = ANA_PORT_DROP_CFG_DROP_PRIO_S_TAGGED_ENA |
481-
ANA_PORT_DROP_CFG_DROP_PRIO_C_TAGGED_ENA;
634+
ANA_PORT_DROP_CFG_DROP_PRIO_C_TAGGED_ENA |
635+
ANA_PORT_DROP_CFG_DROP_S_TAGGED_ENA;
482636

483637
ocelot_rmw_gix(ocelot, val,
484638
ANA_PORT_DROP_CFG_DROP_PRIO_S_TAGGED_ENA |
485-
ANA_PORT_DROP_CFG_DROP_PRIO_C_TAGGED_ENA,
639+
ANA_PORT_DROP_CFG_DROP_PRIO_C_TAGGED_ENA |
640+
ANA_PORT_DROP_CFG_DROP_S_TAGGED_ENA,
486641
ANA_PORT_DROP_CFG, port);
642+
643+
return ocelot_update_vlan_reclassify_rule(ocelot, port);
487644
}
488645

489646
static struct ocelot_bridge_vlan *ocelot_bridge_vlan_find(struct ocelot *ocelot,
@@ -631,7 +788,10 @@ int ocelot_port_vlan_filtering(struct ocelot *ocelot, int port,
631788
ANA_PORT_VLAN_CFG_VLAN_POP_CNT_M,
632789
ANA_PORT_VLAN_CFG, port);
633790

634-
ocelot_port_set_pvid(ocelot, port, ocelot_port->pvid_vlan);
791+
err = ocelot_port_set_pvid(ocelot, port, ocelot_port->pvid_vlan);
792+
if (err)
793+
return err;
794+
635795
ocelot_port_manage_port_tag(ocelot, port);
636796

637797
return 0;
@@ -684,9 +844,12 @@ int ocelot_vlan_add(struct ocelot *ocelot, int port, u16 vid, bool pvid,
684844
return err;
685845

686846
/* Default ingress vlan classification */
687-
if (pvid)
688-
ocelot_port_set_pvid(ocelot, port,
689-
ocelot_bridge_vlan_find(ocelot, vid));
847+
if (pvid) {
848+
err = ocelot_port_set_pvid(ocelot, port,
849+
ocelot_bridge_vlan_find(ocelot, vid));
850+
if (err)
851+
return err;
852+
}
690853

691854
/* Untagged egress vlan clasification */
692855
ocelot_port_manage_port_tag(ocelot, port);
@@ -712,8 +875,11 @@ int ocelot_vlan_del(struct ocelot *ocelot, int port, u16 vid)
712875
return err;
713876

714877
/* Ingress */
715-
if (del_pvid)
716-
ocelot_port_set_pvid(ocelot, port, NULL);
878+
if (del_pvid) {
879+
err = ocelot_port_set_pvid(ocelot, port, NULL);
880+
if (err)
881+
return err;
882+
}
717883

718884
/* Egress */
719885
ocelot_port_manage_port_tag(ocelot, port);
@@ -2607,7 +2773,7 @@ int ocelot_port_set_default_prio(struct ocelot *ocelot, int port, u8 prio)
26072773
ANA_PORT_QOS_CFG,
26082774
port);
26092775

2610-
return 0;
2776+
return ocelot_update_vlan_reclassify_rule(ocelot, port);
26112777
}
26122778
EXPORT_SYMBOL_GPL(ocelot_port_set_default_prio);
26132779

drivers/net/ethernet/mscc/ocelot_vcap.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,7 @@ static void is1_entry_set(struct ocelot *ocelot, int ix,
695695
vcap_key_bit_set(vcap, &data, VCAP_IS1_HK_L2_MC, filter->dmac_mc);
696696
vcap_key_bit_set(vcap, &data, VCAP_IS1_HK_L2_BC, filter->dmac_bc);
697697
vcap_key_bit_set(vcap, &data, VCAP_IS1_HK_VLAN_TAGGED, tag->tagged);
698+
vcap_key_bit_set(vcap, &data, VCAP_IS1_HK_TPID, tag->tpid);
698699
vcap_key_set(vcap, &data, VCAP_IS1_HK_VID,
699700
tag->vid.value, tag->vid.mask);
700701
vcap_key_set(vcap, &data, VCAP_IS1_HK_PCP,

include/soc/mscc/ocelot_vcap.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414
#define OCELOT_VCAP_ES0_TAG_8021Q_RXVLAN(ocelot, port, upstream) ((upstream) << 16 | (port))
1515
#define OCELOT_VCAP_IS1_TAG_8021Q_TXVLAN(ocelot, port) (port)
16+
#define OCELOT_VCAP_IS1_VLAN_RECLASSIFY(ocelot, port) ((ocelot)->num_phys_ports + (port))
1617
#define OCELOT_VCAP_IS2_TAG_8021Q_TXVLAN(ocelot, port) (port)
1718
#define OCELOT_VCAP_IS2_MRP_REDIRECT(ocelot, port) ((ocelot)->num_phys_ports + (port))
1819
#define OCELOT_VCAP_IS2_MRP_TRAP(ocelot) ((ocelot)->num_phys_ports * 2)
@@ -499,6 +500,7 @@ struct ocelot_vcap_key_vlan {
499500
struct ocelot_vcap_u8 pcp; /* PCP (3 bit) */
500501
enum ocelot_vcap_bit dei; /* DEI */
501502
enum ocelot_vcap_bit tagged; /* Tagged/untagged frame */
503+
enum ocelot_vcap_bit tpid;
502504
};
503505

504506
struct ocelot_vcap_key_etype {

0 commit comments

Comments
 (0)