From: Vladimir Oltean <vladimir.oltean@nxp.com> Date: 2021-10-01 15:15:47
This patch set adds support for modifying a VLAN ID at the egress stage
of Ocelot/Felix switch ports. It is useful for replicating a packet on
multiple ports, and each egress port sends it using a different VLAN ID.
Tested by rewriting the VLAN ID of both
(a) packets injected from the CPU port
(b) packets received from an external station on a front-facing port
Adding a selftest to make sure it doesn't bit-rot, and if it does, that
it can be traced back easily.
Vladimir Oltean (6):
net: mscc: ocelot: support egress VLAN rewriting via VCAP ES0
net: mscc: ocelot: write full VLAN TCI in the injection header
net: dsa: tag_ocelot: set the classified VLAN during xmit
selftests: net: mscc: ocelot: bring up the ports automatically
selftests: net: mscc: ocelot: rename the VLAN modification test to
ingress
selftests: net: mscc: ocelot: add a test for egress VLAN modification
drivers/net/ethernet/mscc/ocelot.c | 2 +-
drivers/net/ethernet/mscc/ocelot_flower.c | 125 +++++++++++++++---
include/linux/dsa/ocelot.h | 4 +-
include/soc/mscc/ocelot_vcap.h | 10 ++
net/dsa/tag_ocelot.c | 39 ++++++
.../drivers/net/ocelot/tc_flower_chains.sh | 50 ++++++-
6 files changed, 204 insertions(+), 26 deletions(-)
--
2.25.1
From: Vladimir Oltean <vladimir.oltean@nxp.com> Date: 2021-10-01 15:15:48
Currently the ocelot driver does support the 'vlan modify' action, but
in the ingress chain, and it is offloaded to VCAP IS1. This action
changes the classified VLAN before the packet enters the bridging
service, and the bridging works with the classified VLAN modified by
VCAP IS1.
That is good for some use cases, but there are others where the VLAN
must be modified at the stage of the egress port, after the packet has
exited the bridging service. One example is simulating IEEE 802.1CB
active stream identification filters ("active" means that not only the
rule matches on a packet flow, but it is also able to change some
headers). For example, a stream is replicated on two egress ports, but
they must have different VLAN IDs on egress ports A and B.
This seems like a task for the VCAP ES0, but that currently only
supports pushing the ES0 tag A, which is specified in the rule. Pushing
another VLAN header is not what we want, but rather overwriting the
existing one.
It looks like when we push the ES0 tag A, it is actually possible to not
only take the ES0 tag A's value from the rule itself (VID_A_VAL), but
derive it from the following formula:
ES0_TAG_A = Classified VID + VID_A_VAL
Otherwise said, ES0_TAG_A can be used to increment with a given value
the VLAN ID that the packet was already classified to, and the packet
will have this value as an outer VLAN tag. This new VLAN ID value then
gets stripped on egress (or not) according to the value of the native
VLAN from the bridging service.
While the hardware will happily increment the classified VLAN ID for all
packets that match the ES0 rule, in practice this would be rather
insane, so we only allow this kind of ES0 action if the ES0 filter
contains a VLAN ID too, so as to restrict the matching on a known
classified VLAN. If we program VID_A_VAL with the delta between the
desired final VLAN (ES0_TAG_A) and the classified VLAN, we obtain the
desired behavior.
It doesn't look like it is possible with the tc-vlan action to modify
the VLAN ID but not the PCP. In hardware it is possible to leave the PCP
to the classified value, but we unconditionally program it to overwrite
it with the PCP value from the rule.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
drivers/net/ethernet/mscc/ocelot_flower.c | 125 ++++++++++++++++++----
include/soc/mscc/ocelot_vcap.h | 10 ++
2 files changed, 115 insertions(+), 20 deletions(-)
@@ -142,17 +142,77 @@ ocelot_find_vcap_filter_that_points_at(struct ocelot *ocelot, int chain)returnNULL;}+staticint+ocelot_flower_parse_ingress_vlan_modify(structocelot*ocelot,intport,+structocelot_vcap_filter*filter,+conststructflow_action_entry*a,+structnetlink_ext_ack*extack)+{+structocelot_port*ocelot_port=ocelot->ports[port];++if(filter->goto_target!=-1){+NL_SET_ERR_MSG_MOD(extack,+"Last action must be GOTO");+return-EOPNOTSUPP;+}++if(!ocelot_port->vlan_aware){+NL_SET_ERR_MSG_MOD(extack,+"Can only modify VLAN under VLAN aware bridge");+return-EOPNOTSUPP;+}++filter->action.vid_replace_ena=true;+filter->action.pcp_dei_ena=true;+filter->action.vid=a->vlan.vid;+filter->action.pcp=a->vlan.prio;+filter->type=OCELOT_VCAP_FILTER_OFFLOAD;++return0;+}++staticint+ocelot_flower_parse_egress_vlan_modify(structocelot_vcap_filter*filter,+conststructflow_action_entry*a,+structnetlink_ext_ack*extack)+{+enumocelot_tag_tpid_seltpid;++switch(ntohs(a->vlan.proto)){+caseETH_P_8021Q:+tpid=OCELOT_TAG_TPID_SEL_8021Q;+break;+caseETH_P_8021AD:+tpid=OCELOT_TAG_TPID_SEL_8021AD;+break;+default:+NL_SET_ERR_MSG_MOD(extack,+"Cannot modify custom TPID");+return-EOPNOTSUPP;+}++filter->action.tag_a_tpid_sel=tpid;+filter->action.push_outer_tag=OCELOT_ES0_TAG;+filter->action.tag_a_vid_sel=OCELOT_ES0_VID_PLUS_CLASSIFIED_VID;+filter->action.vid_a_val=a->vlan.vid;+filter->action.pcp_a_val=a->vlan.prio;+filter->action.tag_a_pcp_sel=OCELOT_ES0_PCP;+filter->type=OCELOT_VCAP_FILTER_OFFLOAD;++return0;+}+staticintocelot_flower_parse_action(structocelot*ocelot,intport,boolingress,structflow_cls_offload*f,structocelot_vcap_filter*filter){-structocelot_port*ocelot_port=ocelot->ports[port];structnetlink_ext_ack*extack=f->common.extack;boolallow_missing_goto_target=false;conststructflow_action_entry*a;enumocelot_tag_tpid_seltpid;inti,chain,egress_port;u64rate;+interr;if(!flow_action_basic_hw_stats_check(&f->rule->action,f->common.extack))
@@ -273,26 +333,20 @@ static int ocelot_flower_parse_action(struct ocelot *ocelot, int port,filter->type=OCELOT_VCAP_FILTER_OFFLOAD;break;caseFLOW_ACTION_VLAN_MANGLE:-if(filter->block_id!=VCAP_IS1){-NL_SET_ERR_MSG_MOD(extack,-"VLAN modify action can only be offloaded to VCAP IS1");-return-EOPNOTSUPP;-}-if(filter->goto_target!=-1){+if(filter->block_id==VCAP_IS1){+err=ocelot_flower_parse_ingress_vlan_modify(ocelot,port,+filter,a,+extack);+}elseif(filter->block_id==VCAP_ES0){+err=ocelot_flower_parse_egress_vlan_modify(filter,a,+extack);+}else{NL_SET_ERR_MSG_MOD(extack,-"Last action must be GOTO");-return-EOPNOTSUPP;+"VLAN modify action can only be offloaded to VCAP IS1 or ES0");+err=-EOPNOTSUPP;}-if(!ocelot_port->vlan_aware){-NL_SET_ERR_MSG_MOD(extack,-"Can only modify VLAN under VLAN aware bridge");-return-EOPNOTSUPP;-}-filter->action.vid_replace_ena=true;-filter->action.pcp_dei_ena=true;-filter->action.vid=a->vlan.vid;-filter->action.pcp=a->vlan.prio;-filter->type=OCELOT_VCAP_FILTER_OFFLOAD;+if(err)+returnerr;break;caseFLOW_ACTION_PRIORITY:if(filter->block_id!=VCAP_IS1){
@@ -340,7 +394,7 @@ static int ocelot_flower_parse_action(struct ocelot *ocelot, int port,}filter->action.tag_a_tpid_sel=tpid;filter->action.push_outer_tag=OCELOT_ES0_TAG;-filter->action.tag_a_vid_sel=1;+filter->action.tag_a_vid_sel=OCELOT_ES0_VID;filter->action.vid_a_val=a->vlan.vid;filter->action.pcp_a_val=a->vlan.prio;filter->type=OCELOT_VCAP_FILTER_OFFLOAD;
@@ -678,6 +732,31 @@ static int ocelot_vcap_dummy_filter_del(struct ocelot *ocelot,return0;}+/* If we have an egress VLAN modification rule, we need to actually write the+*deltabetweentheinputVLAN(fromthekey)andtheoutputVLAN(fromthe+*action),buttheactionwasparsedfirst.Soweneedtopatchthedeltainto+*theactionhere.+*/+staticint+ocelot_flower_patch_es0_vlan_modify(structocelot_vcap_filter*filter,+structnetlink_ext_ack*extack)+{+if(filter->block_id!=VCAP_ES0||+filter->action.tag_a_vid_sel!=OCELOT_ES0_VID_PLUS_CLASSIFIED_VID)+return0;++if(filter->vlan.vid.mask!=VLAN_VID_MASK){+NL_SET_ERR_MSG_MOD(extack,+"VCAP ES0 VLAN rewriting needs a full VLAN in the key");+return-EOPNOTSUPP;+}++filter->action.vid_a_val-=filter->vlan.vid.value;+filter->action.vid_a_val&=VLAN_VID_MASK;++return0;+}+intocelot_cls_flower_replace(structocelot*ocelot,intport,structflow_cls_offload*f,boolingress){
@@ -701,6 +780,12 @@ int ocelot_cls_flower_replace(struct ocelot *ocelot, int port,returnret;}+ret=ocelot_flower_patch_es0_vlan_modify(filter,extack);+if(ret){+kfree(filter);+returnret;+}+/* The non-optional GOTOs for the TCAM skeleton don't need*tobeactuallyoffloaded.*/
From: Vladimir Oltean <vladimir.oltean@nxp.com> Date: 2021-10-01 15:15:50
The VLAN TCI contains more than the VLAN ID, it also has the VLAN PCP
and Drop Eligibility Indicator.
If the ocelot driver is going to write the VLAN header inside the DSA
tag, it could just as well write the entire TCI.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
drivers/net/ethernet/mscc/ocelot.c | 2 +-
include/linux/dsa/ocelot.h | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
From: Vladimir Oltean <vladimir.oltean@nxp.com> Date: 2021-10-01 15:15:52
Currently, all packets injected into Ocelot switches are classified to
VLAN 0, regardless of whether they are VLAN-tagged or not. This is
because the switch only looks at the VLAN TCI from the DSA tag.
VLAN 0 is then stripped on egress due to REW_TAG_CFG_TAG_CFG. There are
2 cases really, below is the explanation for ocelot_port_set_native_vlan:
- Port is VLAN-aware, we set REW_TAG_CFG_TAG_CFG to 1 (egress-tag all
frames except VID 0 and the native VLAN) if a native VLAN exists, or
to 3 otherwise (tag all frames, including VID 0).
- Port is VLAN-unaware, we set REW_TAG_CFG_TAG_CFG to 0 (port tagging
disabled, classified VLAN never appears in the packet).
One can already see an inconsistency: when a native VLAN exists, VID 0
is egress-untagged, but when it doesn't, VID 0 is egress-tagged.
So when we do this:
ip link add br0 type bridge vlan_filtering 1
ip link set swp0 master br0
bridge vlan del dev swp0 vid 1
bridge vlan add dev swp0 vid 1 pvid # but not untagged
and we ping through swp0, packets will look like this:
MAC > 33:33:00:00:00:02, ethertype 802.1Q (0x8100): vlan 0, p 0,
ethertype 802.1Q (0x8100), vlan 1, p 0, ethertype IPv6 (0x86dd),
ICMP6, router solicitation, length 16
So VID 1 frames (sent that way by the Linux bridge) are encapsulated in
a VID 0 header - the classified VLAN of the packets as far as the hw is
concerned. To avoid that, what we really need to do is stop injecting
packets using the classified VLAN of 0.
This patch strips the VLAN header from the skb payload, if that VLAN
exists and if the port is under a VLAN-aware bridge. Then it copies that
VLAN header into the DSA injection frame header.
A positive side effect is that VCAP ES0 VLAN rewriting rules now work
for packets injected from the CPU into a port that's under a VLAN-aware
bridge, and we are able to match those packets by the VLAN ID that was
sent by the network stack, and not by VLAN ID 0.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
net/dsa/tag_ocelot.c | 39 +++++++++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)
@@ -5,15 +5,52 @@#include<soc/mscc/ocelot.h>#include"dsa_priv.h"+/* If the port is under a VLAN-aware bridge, remove the VLAN header from the+*payloadandmoveitintotheDSAtag,whichwillmaketheswitchclassify+*thepackettothebridgeVLAN.Otherwise,leavetheclassifiedVLANatzero,+*whichisthepvidofstandaloneandVLAN-unawarebridgeports.+*/+staticvoidocelot_xmit_get_vlan_info(structsk_buff*skb,structdsa_port*dp,+u64*vlan_tci,u64*tag_type)+{+structnet_device*br=READ_ONCE(dp->bridge_dev);+structvlan_ethhdr*hdr;+u16proto,tci;++if(!br||!br_vlan_enabled(br)){+*vlan_tci=0;+*tag_type=IFH_TAG_TYPE_C;+return;+}++hdr=(structvlan_ethhdr*)skb_mac_header(skb);+br_vlan_get_proto(br,&proto);++if(ntohs(hdr->h_vlan_proto)==proto){+__skb_vlan_pop(skb,&tci);+*vlan_tci=tci;+}else{+rcu_read_lock();+br_vlan_get_pvid_rcu(br,&tci);+rcu_read_unlock();+*vlan_tci=tci;+}++*tag_type=(proto!=ETH_P_8021Q)?IFH_TAG_TYPE_S:IFH_TAG_TYPE_C;+}+staticvoidocelot_xmit_common(structsk_buff*skb,structnet_device*netdev,__be32ifh_prefix,void**ifh){structdsa_port*dp=dsa_slave_to_port(netdev);structdsa_switch*ds=dp->ds;+u64vlan_tci,tag_type;void*injection;__be32*prefix;u32rew_op=0;+ocelot_xmit_get_vlan_info(skb,dp,&vlan_tci,&tag_type);+injection=skb_push(skb,OCELOT_TAG_LEN);prefix=skb_push(skb,OCELOT_SHORT_PREFIX_LEN);
From: Vladimir Oltean <vladimir.oltean@nxp.com> Date: 2021-10-01 15:15:59
Looks like when I wrote the selftests I was using a network manager that
brought up the ports automatically. In order to not rely on that, let
the script open them up.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
.../testing/selftests/drivers/net/ocelot/tc_flower_chains.sh | 5 +++++
1 file changed, 5 insertions(+)
From: Vladimir Oltean <vladimir.oltean@nxp.com> Date: 2021-10-01 15:16:03
There will be one more VLAN modification selftest added, this time for
egress. Rename the one that exists right now to be more specific.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
.../selftests/drivers/net/ocelot/tc_flower_chains.sh | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
From: Vladimir Oltean <vladimir.oltean@nxp.com> Date: 2021-10-01 15:16:05
For this test we are exercising the VCAP ES0 block's ability to match on
a packet with a given VLAN ID, and push an ES0 TAG A with a VID derived
from VID_A_VAL plus the classified VLAN.
$eth3.200 is the generator port
$eth0 is the bridged DUT port that receives
$eth1 is the bridged DUT port that forwards and rewrites VID 200 to 300
on egress via VCAP ES0
$eth2 is the port that receives from the DUT port $eth1
Since the egress rewriting happens outside the bridging service, VID 300
does not need to be in the bridge VLAN table of $eth1.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
.../drivers/net/ocelot/tc_flower_chains.sh | 39 +++++++++++++++++++
1 file changed, 39 insertions(+)
Hello:
This series was applied to netdev/net-next.git (refs/heads/master):
On Fri, 1 Oct 2021 18:15:25 +0300 you wrote:
This patch set adds support for modifying a VLAN ID at the egress stage
of Ocelot/Felix switch ports. It is useful for replicating a packet on
multiple ports, and each egress port sends it using a different VLAN ID.
Tested by rewriting the VLAN ID of both
(a) packets injected from the CPU port
(b) packets received from an external station on a front-facing port
[...]