Skip to content

Commit

Permalink
net: dsa: sja1105: add support for imprecise RX
Browse files Browse the repository at this point in the history
This is already common knowledge by now, but the sja1105 does not have
hardware support for DSA tagging for data plane packets, and tag_8021q
sets up a unique pvid per port, transmitted as VLAN-tagged towards the
CPU, for the source port to be decoded nonetheless.

When the port is part of a VLAN-aware bridge, the pvid committed to
hardware is taken from the bridge and not from tag_8021q, so we need to
work with that the best we can.

Configure the switches to send all packets to the CPU as VLAN-tagged
(even ones that were originally untagged on the wire) and make use of
dsa_untag_bridge_pvid() to get rid of it before we send those packets up
the network stack.

With the classified VLAN used by hardware known to the tagger, we first
peek at the VID in an attempt to figure out if the packet was received
from a VLAN-unaware port (standalone or under a VLAN-unaware bridge),
case in which we can continue to call dsa_8021q_rcv(). If that is not
the case, the packet probably came from a VLAN-aware bridge. So we call
the DSA helper that finds for us a "designated bridge port" - one that
is a member of the VLAN ID from the packet, and is in the proper STP
state - basically these are all checks performed by br_handle_frame() in
the software RX data path.

The bridge will accept the packet as valid even if the source port was
maybe wrong. So it will maybe learn the MAC SA of the packet on the
wrong port, and its software FDB will be out of sync with the hardware
FDB. So replies towards this same MAC DA will not work, because the
bridge will send towards a different netdev.

This is where the bridge data plane offload ("imprecise TX") added by
the next patch comes in handy. The software FDB is wrong, true, but the
hardware FDB isn't, and by offloading the bridge forwarding plane we
have a chance to right a wrong, and have the hardware look up the FDB
for us for the reply packet. So it all cancels out.

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
vladimiroltean authored and davem330 committed Jul 26, 2021
1 parent 19fa937 commit 884be12
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 45 deletions.
8 changes: 7 additions & 1 deletion drivers/net/dsa/sja1105/sja1105_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -2201,6 +2201,7 @@ static int sja1105_bridge_vlan_add(struct dsa_switch *ds, int port,
struct netlink_ext_ack *extack)
{
struct sja1105_private *priv = ds->priv;
u16 flags = vlan->flags;
int rc;

/* Be sure to deny alterations to the configuration done by tag_8021q.
Expand All @@ -2211,7 +2212,11 @@ static int sja1105_bridge_vlan_add(struct dsa_switch *ds, int port,
return -EBUSY;
}

rc = sja1105_vlan_add(priv, port, vlan->vid, vlan->flags);
/* Always install bridge VLANs as egress-tagged on the CPU port. */
if (dsa_is_cpu_port(ds, port))
flags = 0;

rc = sja1105_vlan_add(priv, port, vlan->vid, flags);
if (rc)
return rc;

Expand Down Expand Up @@ -2361,6 +2366,7 @@ static int sja1105_setup(struct dsa_switch *ds)
* TPID is ETH_P_SJA1105, and the VLAN ID is the port pvid.
*/
ds->vlan_filtering_is_global = true;
ds->untag_bridge_pvid = true;

/* Advertise the 8 egress queues */
ds->num_tx_queues = SJA1105_NUM_TC;
Expand Down
43 changes: 43 additions & 0 deletions net/dsa/dsa_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,49 @@ static inline struct sk_buff *dsa_untag_bridge_pvid(struct sk_buff *skb)
return skb;
}

/* For switches without hardware support for DSA tagging to be able
* to support termination through the bridge.
*/
static inline struct net_device *
dsa_find_designated_bridge_port_by_vid(struct net_device *master, u16 vid)
{
struct dsa_port *cpu_dp = master->dsa_ptr;
struct dsa_switch_tree *dst = cpu_dp->dst;
struct bridge_vlan_info vinfo;
struct net_device *slave;
struct dsa_port *dp;
int err;

list_for_each_entry(dp, &dst->ports, list) {
if (dp->type != DSA_PORT_TYPE_USER)
continue;

if (!dp->bridge_dev)
continue;

if (dp->stp_state != BR_STATE_LEARNING &&
dp->stp_state != BR_STATE_FORWARDING)
continue;

/* Since the bridge might learn this packet, keep the CPU port
* affinity with the port that will be used for the reply on
* xmit.
*/
if (dp->cpu_dp != cpu_dp)
continue;

slave = dp->slave;

err = br_vlan_get_info_rcu(slave, vid, &vinfo);
if (err)
continue;

return slave;
}

return NULL;
}

/* switch.c */
int dsa_switch_register_notifier(struct dsa_switch *ds);
void dsa_switch_unregister_notifier(struct dsa_switch *ds);
Expand Down
87 changes: 43 additions & 44 deletions net/dsa/tag_sja1105.c
Original file line number Diff line number Diff line change
Expand Up @@ -115,40 +115,6 @@ static inline bool sja1105_is_meta_frame(const struct sk_buff *skb)
return true;
}

static bool sja1105_can_use_vlan_as_tags(const struct sk_buff *skb)
{
struct vlan_ethhdr *hdr = vlan_eth_hdr(skb);
u16 vlan_tci;

if (hdr->h_vlan_proto == htons(ETH_P_SJA1105))
return true;

if (hdr->h_vlan_proto != htons(ETH_P_8021Q) &&
!skb_vlan_tag_present(skb))
return false;

if (skb_vlan_tag_present(skb))
vlan_tci = skb_vlan_tag_get(skb);
else
vlan_tci = ntohs(hdr->h_vlan_TCI);

return vid_is_dsa_8021q(vlan_tci & VLAN_VID_MASK);
}

/* This is the first time the tagger sees the frame on RX.
* Figure out if we can decode it.
*/
static bool sja1105_filter(const struct sk_buff *skb, struct net_device *dev)
{
if (sja1105_can_use_vlan_as_tags(skb))
return true;
if (sja1105_is_link_local(skb))
return true;
if (sja1105_is_meta_frame(skb))
return true;
return false;
}

/* Calls sja1105_port_deferred_xmit in sja1105_main.c */
static struct sk_buff *sja1105_defer_xmit(struct sja1105_port *sp,
struct sk_buff *skb)
Expand Down Expand Up @@ -371,15 +337,42 @@ static bool sja1110_skb_has_inband_control_extension(const struct sk_buff *skb)
return ntohs(eth_hdr(skb)->h_proto) == ETH_P_SJA1110;
}

/* Returns true for imprecise RX and sets the @vid.
* Returns false for precise RX and sets @source_port and @switch_id.
*/
static bool sja1105_vlan_rcv(struct sk_buff *skb, int *source_port,
int *switch_id, u16 *vid)
{
struct vlan_ethhdr *hdr = (struct vlan_ethhdr *)skb_mac_header(skb);
u16 vlan_tci;

if (skb_vlan_tag_present(skb))
vlan_tci = skb_vlan_tag_get(skb);
else
vlan_tci = ntohs(hdr->h_vlan_TCI);

if (vid_is_dsa_8021q_rxvlan(vlan_tci & VLAN_VID_MASK)) {
dsa_8021q_rcv(skb, source_port, switch_id);
return false;
}

/* Try our best with imprecise RX */
*vid = vlan_tci & VLAN_VID_MASK;

return true;
}

static struct sk_buff *sja1105_rcv(struct sk_buff *skb,
struct net_device *netdev,
struct packet_type *pt)
{
int source_port = -1, switch_id = -1;
struct sja1105_meta meta = {0};
int source_port, switch_id;
bool imprecise_rx = false;
struct ethhdr *hdr;
bool is_link_local;
bool is_meta;
u16 vid;

hdr = eth_hdr(skb);
is_link_local = sja1105_is_link_local(skb);
Expand All @@ -389,7 +382,8 @@ static struct sk_buff *sja1105_rcv(struct sk_buff *skb,

if (sja1105_skb_has_tag_8021q(skb)) {
/* Normal traffic path. */
dsa_8021q_rcv(skb, &source_port, &switch_id);
imprecise_rx = sja1105_vlan_rcv(skb, &source_port, &switch_id,
&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
Expand All @@ -408,7 +402,10 @@ static struct sk_buff *sja1105_rcv(struct sk_buff *skb,
return NULL;
}

skb->dev = dsa_master_find_slave(netdev, switch_id, source_port);
if (imprecise_rx)
skb->dev = dsa_find_designated_bridge_port_by_vid(netdev, vid);
else
skb->dev = dsa_master_find_slave(netdev, switch_id, source_port);
if (!skb->dev) {
netdev_warn(netdev, "Couldn't decode source port\n");
return NULL;
Expand Down Expand Up @@ -522,6 +519,8 @@ static struct sk_buff *sja1110_rcv(struct sk_buff *skb,
struct packet_type *pt)
{
int source_port = -1, switch_id = -1;
bool imprecise_rx = false;
u16 vid;

skb->offload_fwd_mark = 1;

Expand All @@ -534,13 +533,15 @@ static struct sk_buff *sja1110_rcv(struct sk_buff *skb,

/* Packets with in-band control extensions might still have RX VLANs */
if (likely(sja1105_skb_has_tag_8021q(skb)))
dsa_8021q_rcv(skb, &source_port, &switch_id);
imprecise_rx = sja1105_vlan_rcv(skb, &source_port, &switch_id,
&vid);

skb->dev = dsa_master_find_slave(netdev, switch_id, source_port);
if (imprecise_rx)
skb->dev = dsa_find_designated_bridge_port_by_vid(netdev, vid);
else
skb->dev = dsa_master_find_slave(netdev, switch_id, source_port);
if (!skb->dev) {
netdev_warn(netdev,
"Couldn't decode source port %d and switch id %d\n",
source_port, switch_id);
netdev_warn(netdev, "Couldn't decode source port\n");
return NULL;
}

Expand Down Expand Up @@ -576,7 +577,6 @@ static const struct dsa_device_ops sja1105_netdev_ops = {
.proto = DSA_TAG_PROTO_SJA1105,
.xmit = sja1105_xmit,
.rcv = sja1105_rcv,
.filter = sja1105_filter,
.needed_headroom = VLAN_HLEN,
.flow_dissect = sja1105_flow_dissect,
.promisc_on_master = true,
Expand All @@ -590,7 +590,6 @@ static const struct dsa_device_ops sja1110_netdev_ops = {
.proto = DSA_TAG_PROTO_SJA1110,
.xmit = sja1110_xmit,
.rcv = sja1110_rcv,
.filter = sja1105_filter,
.flow_dissect = sja1110_flow_dissect,
.needed_headroom = SJA1110_HEADER_LEN + VLAN_HLEN,
.needed_tailroom = SJA1110_RX_TRAILER_LEN + SJA1110_MAX_PADDING_LEN,
Expand Down

0 comments on commit 884be12

Please sign in to comment.