Commit fa8274b7 authored by David S. Miller's avatar David S. Miller

Merge branch 'ocelot-vlan'

Vladimir Oltean says:

====================
Egress VLAN modification using VCAP ES0 on Ocelot switches

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.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents f533bc14 434ef350
...@@ -916,7 +916,7 @@ void ocelot_port_inject_frame(struct ocelot *ocelot, int port, int grp, ...@@ -916,7 +916,7 @@ void ocelot_port_inject_frame(struct ocelot *ocelot, int port, int grp,
ocelot_ifh_set_bypass(ifh, 1); ocelot_ifh_set_bypass(ifh, 1);
ocelot_ifh_set_dest(ifh, BIT_ULL(port)); ocelot_ifh_set_dest(ifh, BIT_ULL(port));
ocelot_ifh_set_tag_type(ifh, IFH_TAG_TYPE_C); ocelot_ifh_set_tag_type(ifh, IFH_TAG_TYPE_C);
ocelot_ifh_set_vid(ifh, skb_vlan_tag_get(skb)); ocelot_ifh_set_vlan_tci(ifh, skb_vlan_tag_get(skb));
ocelot_ifh_set_rew_op(ifh, rew_op); ocelot_ifh_set_rew_op(ifh, rew_op);
for (i = 0; i < OCELOT_TAG_LEN / 4; i++) for (i = 0; i < OCELOT_TAG_LEN / 4; i++)
......
...@@ -142,17 +142,77 @@ ocelot_find_vcap_filter_that_points_at(struct ocelot *ocelot, int chain) ...@@ -142,17 +142,77 @@ ocelot_find_vcap_filter_that_points_at(struct ocelot *ocelot, int chain)
return NULL; return NULL;
} }
static int
ocelot_flower_parse_ingress_vlan_modify(struct ocelot *ocelot, int port,
struct ocelot_vcap_filter *filter,
const struct flow_action_entry *a,
struct netlink_ext_ack *extack)
{
struct ocelot_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;
return 0;
}
static int
ocelot_flower_parse_egress_vlan_modify(struct ocelot_vcap_filter *filter,
const struct flow_action_entry *a,
struct netlink_ext_ack *extack)
{
enum ocelot_tag_tpid_sel tpid;
switch (ntohs(a->vlan.proto)) {
case ETH_P_8021Q:
tpid = OCELOT_TAG_TPID_SEL_8021Q;
break;
case ETH_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;
return 0;
}
static int ocelot_flower_parse_action(struct ocelot *ocelot, int port, static int ocelot_flower_parse_action(struct ocelot *ocelot, int port,
bool ingress, struct flow_cls_offload *f, bool ingress, struct flow_cls_offload *f,
struct ocelot_vcap_filter *filter) struct ocelot_vcap_filter *filter)
{ {
struct ocelot_port *ocelot_port = ocelot->ports[port];
struct netlink_ext_ack *extack = f->common.extack; struct netlink_ext_ack *extack = f->common.extack;
bool allow_missing_goto_target = false; bool allow_missing_goto_target = false;
const struct flow_action_entry *a; const struct flow_action_entry *a;
enum ocelot_tag_tpid_sel tpid; enum ocelot_tag_tpid_sel tpid;
int i, chain, egress_port; int i, chain, egress_port;
u64 rate; u64 rate;
int err;
if (!flow_action_basic_hw_stats_check(&f->rule->action, if (!flow_action_basic_hw_stats_check(&f->rule->action,
f->common.extack)) f->common.extack))
...@@ -273,26 +333,20 @@ static int ocelot_flower_parse_action(struct ocelot *ocelot, int port, ...@@ -273,26 +333,20 @@ static int ocelot_flower_parse_action(struct ocelot *ocelot, int port,
filter->type = OCELOT_VCAP_FILTER_OFFLOAD; filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
break; break;
case FLOW_ACTION_VLAN_MANGLE: case FLOW_ACTION_VLAN_MANGLE:
if (filter->block_id != VCAP_IS1) { if (filter->block_id == VCAP_IS1) {
NL_SET_ERR_MSG_MOD(extack, err = ocelot_flower_parse_ingress_vlan_modify(ocelot, port,
"VLAN modify action can only be offloaded to VCAP IS1"); filter, a,
return -EOPNOTSUPP; extack);
} } else if (filter->block_id == VCAP_ES0) {
if (filter->goto_target != -1) { err = ocelot_flower_parse_egress_vlan_modify(filter, a,
NL_SET_ERR_MSG_MOD(extack, extack);
"Last action must be GOTO"); } else {
return -EOPNOTSUPP;
}
if (!ocelot_port->vlan_aware) {
NL_SET_ERR_MSG_MOD(extack, NL_SET_ERR_MSG_MOD(extack,
"Can only modify VLAN under VLAN aware bridge"); "VLAN modify action can only be offloaded to VCAP IS1 or ES0");
return -EOPNOTSUPP; err = -EOPNOTSUPP;
} }
filter->action.vid_replace_ena = true; if (err)
filter->action.pcp_dei_ena = true; return err;
filter->action.vid = a->vlan.vid;
filter->action.pcp = a->vlan.prio;
filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
break; break;
case FLOW_ACTION_PRIORITY: case FLOW_ACTION_PRIORITY:
if (filter->block_id != VCAP_IS1) { if (filter->block_id != VCAP_IS1) {
...@@ -340,7 +394,7 @@ static int ocelot_flower_parse_action(struct ocelot *ocelot, int port, ...@@ -340,7 +394,7 @@ static int ocelot_flower_parse_action(struct ocelot *ocelot, int port,
} }
filter->action.tag_a_tpid_sel = tpid; filter->action.tag_a_tpid_sel = tpid;
filter->action.push_outer_tag = OCELOT_ES0_TAG; 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.vid_a_val = a->vlan.vid;
filter->action.pcp_a_val = a->vlan.prio; filter->action.pcp_a_val = a->vlan.prio;
filter->type = OCELOT_VCAP_FILTER_OFFLOAD; filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
...@@ -678,6 +732,31 @@ static int ocelot_vcap_dummy_filter_del(struct ocelot *ocelot, ...@@ -678,6 +732,31 @@ static int ocelot_vcap_dummy_filter_del(struct ocelot *ocelot,
return 0; return 0;
} }
/* If we have an egress VLAN modification rule, we need to actually write the
* delta between the input VLAN (from the key) and the output VLAN (from the
* action), but the action was parsed first. So we need to patch the delta into
* the action here.
*/
static int
ocelot_flower_patch_es0_vlan_modify(struct ocelot_vcap_filter *filter,
struct netlink_ext_ack *extack)
{
if (filter->block_id != VCAP_ES0 ||
filter->action.tag_a_vid_sel != OCELOT_ES0_VID_PLUS_CLASSIFIED_VID)
return 0;
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;
return 0;
}
int ocelot_cls_flower_replace(struct ocelot *ocelot, int port, int ocelot_cls_flower_replace(struct ocelot *ocelot, int port,
struct flow_cls_offload *f, bool ingress) struct flow_cls_offload *f, bool ingress)
{ {
...@@ -701,6 +780,12 @@ int ocelot_cls_flower_replace(struct ocelot *ocelot, int port, ...@@ -701,6 +780,12 @@ int ocelot_cls_flower_replace(struct ocelot *ocelot, int port,
return ret; return ret;
} }
ret = ocelot_flower_patch_es0_vlan_modify(filter, extack);
if (ret) {
kfree(filter);
return ret;
}
/* The non-optional GOTOs for the TCAM skeleton don't need /* The non-optional GOTOs for the TCAM skeleton don't need
* to be actually offloaded. * to be actually offloaded.
*/ */
......
...@@ -210,9 +210,9 @@ static inline void ocelot_ifh_set_tag_type(void *injection, u64 tag_type) ...@@ -210,9 +210,9 @@ static inline void ocelot_ifh_set_tag_type(void *injection, u64 tag_type)
packing(injection, &tag_type, 16, 16, OCELOT_TAG_LEN, PACK, 0); packing(injection, &tag_type, 16, 16, OCELOT_TAG_LEN, PACK, 0);
} }
static inline void ocelot_ifh_set_vid(void *injection, u64 vid) static inline void ocelot_ifh_set_vlan_tci(void *injection, u64 vlan_tci)
{ {
packing(injection, &vid, 11, 0, OCELOT_TAG_LEN, PACK, 0); packing(injection, &vlan_tci, 15, 0, OCELOT_TAG_LEN, PACK, 0);
} }
#endif #endif
...@@ -576,6 +576,16 @@ enum ocelot_mask_mode { ...@@ -576,6 +576,16 @@ enum ocelot_mask_mode {
OCELOT_MASK_MODE_REDIRECT, OCELOT_MASK_MODE_REDIRECT,
}; };
enum ocelot_es0_vid_sel {
OCELOT_ES0_VID_PLUS_CLASSIFIED_VID = 0,
OCELOT_ES0_VID = 1,
};
enum ocelot_es0_pcp_sel {
OCELOT_CLASSIFIED_PCP = 0,
OCELOT_ES0_PCP = 1,
};
enum ocelot_es0_tag { enum ocelot_es0_tag {
OCELOT_NO_ES0_TAG, OCELOT_NO_ES0_TAG,
OCELOT_ES0_TAG, OCELOT_ES0_TAG,
......
...@@ -5,15 +5,52 @@ ...@@ -5,15 +5,52 @@
#include <soc/mscc/ocelot.h> #include <soc/mscc/ocelot.h>
#include "dsa_priv.h" #include "dsa_priv.h"
/* If the port is under a VLAN-aware bridge, remove the VLAN header from the
* payload and move it into the DSA tag, which will make the switch classify
* the packet to the bridge VLAN. Otherwise, leave the classified VLAN at zero,
* which is the pvid of standalone and VLAN-unaware bridge ports.
*/
static void ocelot_xmit_get_vlan_info(struct sk_buff *skb, struct dsa_port *dp,
u64 *vlan_tci, u64 *tag_type)
{
struct net_device *br = READ_ONCE(dp->bridge_dev);
struct vlan_ethhdr *hdr;
u16 proto, tci;
if (!br || !br_vlan_enabled(br)) {
*vlan_tci = 0;
*tag_type = IFH_TAG_TYPE_C;
return;
}
hdr = (struct vlan_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;
}
static void ocelot_xmit_common(struct sk_buff *skb, struct net_device *netdev, static void ocelot_xmit_common(struct sk_buff *skb, struct net_device *netdev,
__be32 ifh_prefix, void **ifh) __be32 ifh_prefix, void **ifh)
{ {
struct dsa_port *dp = dsa_slave_to_port(netdev); struct dsa_port *dp = dsa_slave_to_port(netdev);
struct dsa_switch *ds = dp->ds; struct dsa_switch *ds = dp->ds;
u64 vlan_tci, tag_type;
void *injection; void *injection;
__be32 *prefix; __be32 *prefix;
u32 rew_op = 0; u32 rew_op = 0;
ocelot_xmit_get_vlan_info(skb, dp, &vlan_tci, &tag_type);
injection = skb_push(skb, OCELOT_TAG_LEN); injection = skb_push(skb, OCELOT_TAG_LEN);
prefix = skb_push(skb, OCELOT_SHORT_PREFIX_LEN); prefix = skb_push(skb, OCELOT_SHORT_PREFIX_LEN);
...@@ -22,6 +59,8 @@ static void ocelot_xmit_common(struct sk_buff *skb, struct net_device *netdev, ...@@ -22,6 +59,8 @@ static void ocelot_xmit_common(struct sk_buff *skb, struct net_device *netdev,
ocelot_ifh_set_bypass(injection, 1); ocelot_ifh_set_bypass(injection, 1);
ocelot_ifh_set_src(injection, ds->num_ports); ocelot_ifh_set_src(injection, ds->num_ports);
ocelot_ifh_set_qos_class(injection, skb->priority); ocelot_ifh_set_qos_class(injection, skb->priority);
ocelot_ifh_set_vlan_tci(injection, vlan_tci);
ocelot_ifh_set_tag_type(injection, tag_type);
rew_op = ocelot_ptp_rew_op(skb); rew_op = ocelot_ptp_rew_op(skb);
if (rew_op) if (rew_op)
......
...@@ -156,6 +156,11 @@ create_tcam_skeleton() ...@@ -156,6 +156,11 @@ create_tcam_skeleton()
setup_prepare() setup_prepare()
{ {
ip link set $eth0 up
ip link set $eth1 up
ip link set $eth2 up
ip link set $eth3 up
create_tcam_skeleton $eth0 create_tcam_skeleton $eth0
ip link add br0 type bridge ip link add br0 type bridge
...@@ -242,9 +247,9 @@ test_vlan_push() ...@@ -242,9 +247,9 @@ test_vlan_push()
tcpdump_cleanup tcpdump_cleanup
} }
test_vlan_modify() test_vlan_ingress_modify()
{ {
printf "Testing VLAN modification.. " printf "Testing ingress VLAN modification.. "
ip link set br0 type bridge vlan_filtering 1 ip link set br0 type bridge vlan_filtering 1
bridge vlan add dev $eth0 vid 200 bridge vlan add dev $eth0 vid 200
...@@ -280,6 +285,44 @@ test_vlan_modify() ...@@ -280,6 +285,44 @@ test_vlan_modify()
ip link set br0 type bridge vlan_filtering 0 ip link set br0 type bridge vlan_filtering 0
} }
test_vlan_egress_modify()
{
printf "Testing egress VLAN modification.. "
tc qdisc add dev $eth1 clsact
ip link set br0 type bridge vlan_filtering 1
bridge vlan add dev $eth0 vid 200
bridge vlan add dev $eth1 vid 200
tc filter add dev $eth1 egress chain $(ES0) pref 3 \
protocol 802.1Q flower skip_sw vlan_id 200 vlan_prio 0 \
action vlan modify id 300 priority 7
tcpdump_start $eth2
$MZ $eth3.200 -q -c 1 -p 64 -a $eth3_mac -b $eth2_mac -t ip
sleep 1
tcpdump_stop
if tcpdump_show | grep -q "$eth3_mac > $eth2_mac, .* vlan 300"; then
echo "OK"
else
echo "FAIL"
fi
tcpdump_cleanup
tc filter del dev $eth1 egress chain $(ES0) pref 3
tc qdisc del dev $eth1 clsact
bridge vlan del dev $eth0 vid 200
bridge vlan del dev $eth1 vid 200
ip link set br0 type bridge vlan_filtering 0
}
test_skbedit_priority() test_skbedit_priority()
{ {
local num_pkts=100 local num_pkts=100
...@@ -304,7 +347,8 @@ trap cleanup EXIT ...@@ -304,7 +347,8 @@ trap cleanup EXIT
ALL_TESTS=" ALL_TESTS="
test_vlan_pop test_vlan_pop
test_vlan_push test_vlan_push
test_vlan_modify test_vlan_ingress_modify
test_vlan_egress_modify
test_skbedit_priority test_skbedit_priority
" "
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment