Skip to content

Commit 67c3ca2

Browse files
vladimirolteandavem330
authored andcommitted
net: mscc: ocelot: use ocelot_xmit_get_vlan_info() also for FDMA and register injection
Problem description ------------------- On an NXP LS1028A (felix DSA driver) with the following configuration: - ocelot-8021q tagging protocol - VLAN-aware bridge (with STP) spanning at least swp0 and swp1 - 8021q VLAN upper interfaces on swp0 and swp1: swp0.700, swp1.700 - ptp4l on swp0.700 and swp1.700 we see that the ptp4l instances do not see each other's traffic, and they all go to the grand master state due to the ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES condition. Jumping to the conclusion for the impatient ------------------------------------------- There is a zero-day bug in the ocelot switchdev driver in the way it handles VLAN-tagged packet injection. The correct logic already exists in the source code, in function ocelot_xmit_get_vlan_info() added by commit 5ca721c ("net: dsa: tag_ocelot: set the classified VLAN during xmit"). But it is used only for normal NPI-based injection with the DSA "ocelot" tagging protocol. The other injection code paths (register-based and FDMA-based) roll their own wrong logic. This affects and was noticed on the DSA "ocelot-8021q" protocol because it uses register-based injection. By moving ocelot_xmit_get_vlan_info() to a place that's common for both the DSA tagger and the ocelot switch library, it can also be called from ocelot_port_inject_frame() in ocelot.c. We need to touch the lines with ocelot_ifh_port_set()'s prototype anyway, so let's rename it to something clearer regarding what it does, and add a kernel-doc. ocelot_ifh_set_basic() should do. Investigation notes ------------------- Debugging reveals that PTP event (aka those carrying timestamps, like Sync) frames injected into swp0.700 (but also swp1.700) hit the wire with two VLAN tags: 00000000: 01 1b 19 00 00 00 00 01 02 03 04 05 81 00 02 bc ~~~~~~~~~~~ 00000010: 81 00 02 bc 88 f7 00 12 00 2c 00 00 02 00 00 00 ~~~~~~~~~~~ 00000020: 00 00 00 00 00 00 00 00 00 00 00 01 02 ff fe 03 00000030: 04 05 00 01 00 04 00 00 00 00 00 00 00 00 00 00 00000040: 00 00 The second (unexpected) VLAN tag makes felix_check_xtr_pkt() -> ptp_classify_raw() fail to see these as PTP packets at the link partner's receiving end, and return PTP_CLASS_NONE (because the BPF classifier is not written to expect 2 VLAN tags). The reason why packets have 2 VLAN tags is because the transmission code treats VLAN incorrectly. Neither ocelot switchdev, nor felix DSA, declare the NETIF_F_HW_VLAN_CTAG_TX feature. Therefore, at xmit time, all VLANs should be in the skb head, and none should be in the hwaccel area. This is done by: static struct sk_buff *validate_xmit_vlan(struct sk_buff *skb, netdev_features_t features) { if (skb_vlan_tag_present(skb) && !vlan_hw_offload_capable(features, skb->vlan_proto)) skb = __vlan_hwaccel_push_inside(skb); return skb; } But ocelot_port_inject_frame() handles things incorrectly: ocelot_ifh_port_set(ifh, port, rew_op, skb_vlan_tag_get(skb)); void ocelot_ifh_port_set(struct sk_buff *skb, void *ifh, int port, u32 rew_op) { (...) if (vlan_tag) ocelot_ifh_set_vlan_tci(ifh, vlan_tag); (...) } The way __vlan_hwaccel_push_inside() pushes the tag inside the skb head is by calling: static inline void __vlan_hwaccel_clear_tag(struct sk_buff *skb) { skb->vlan_present = 0; } which does _not_ zero out skb->vlan_tci as seen by skb_vlan_tag_get(). This means that ocelot, when it calls skb_vlan_tag_get(), sees (and uses) a residual skb->vlan_tci, while the same VLAN tag is _already_ in the skb head. The trivial fix for double VLAN headers is to replace the content of ocelot_ifh_port_set() with: if (skb_vlan_tag_present(skb)) ocelot_ifh_set_vlan_tci(ifh, skb_vlan_tag_get(skb)); but this would not be correct either, because, as mentioned, vlan_hw_offload_capable() is false for us, so we'd be inserting dead code and we'd always transmit packets with VID=0 in the injection frame header. I can't actually test the ocelot switchdev driver and rely exclusively on code inspection, but I don't think traffic from 8021q uppers has ever been injected properly, and not double-tagged. Thus I'm blaming the introduction of VLAN fields in the injection header - early driver code. As hinted at in the early conclusion, what we _want_ to happen for VLAN transmission was already described once in commit 5ca721c ("net: dsa: tag_ocelot: set the classified VLAN during xmit"). ocelot_xmit_get_vlan_info() intends to ensure that if the port through which we're transmitting is under a VLAN-aware bridge, the outer VLAN tag from the skb head is stripped from there and inserted into the injection frame header (so that the packet is processed in hardware through that actual VLAN). And in all other cases, the packet is sent with VID=0 in the injection frame header, since the port is VLAN-unaware and has logic to strip this VID on egress (making it invisible to the wire). Fixes: 08d0236 ("net: mscc: fix the injection header") Signed-off-by: Vladimir Oltean <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent e29b82e commit 67c3ca2

File tree

5 files changed

+75
-43
lines changed

5 files changed

+75
-43
lines changed

drivers/net/ethernet/mscc/ocelot.c

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,17 +1193,34 @@ bool ocelot_can_inject(struct ocelot *ocelot, int grp)
11931193
}
11941194
EXPORT_SYMBOL(ocelot_can_inject);
11951195

1196-
void ocelot_ifh_port_set(void *ifh, int port, u32 rew_op, u32 vlan_tag)
1196+
/**
1197+
* ocelot_ifh_set_basic - Set basic information in Injection Frame Header
1198+
* @ifh: Pointer to Injection Frame Header memory
1199+
* @ocelot: Switch private data structure
1200+
* @port: Egress port number
1201+
* @rew_op: Egress rewriter operation for PTP
1202+
* @skb: Pointer to socket buffer (packet)
1203+
*
1204+
* Populate the Injection Frame Header with basic information for this skb: the
1205+
* analyzer bypass bit, destination port, VLAN info, egress rewriter info.
1206+
*/
1207+
void ocelot_ifh_set_basic(void *ifh, struct ocelot *ocelot, int port,
1208+
u32 rew_op, struct sk_buff *skb)
11971209
{
1210+
struct ocelot_port *ocelot_port = ocelot->ports[port];
1211+
u64 vlan_tci, tag_type;
1212+
1213+
ocelot_xmit_get_vlan_info(skb, ocelot_port->bridge, &vlan_tci,
1214+
&tag_type);
1215+
11981216
ocelot_ifh_set_bypass(ifh, 1);
11991217
ocelot_ifh_set_dest(ifh, BIT_ULL(port));
1200-
ocelot_ifh_set_tag_type(ifh, IFH_TAG_TYPE_C);
1201-
if (vlan_tag)
1202-
ocelot_ifh_set_vlan_tci(ifh, vlan_tag);
1218+
ocelot_ifh_set_tag_type(ifh, tag_type);
1219+
ocelot_ifh_set_vlan_tci(ifh, vlan_tci);
12031220
if (rew_op)
12041221
ocelot_ifh_set_rew_op(ifh, rew_op);
12051222
}
1206-
EXPORT_SYMBOL(ocelot_ifh_port_set);
1223+
EXPORT_SYMBOL(ocelot_ifh_set_basic);
12071224

12081225
void ocelot_port_inject_frame(struct ocelot *ocelot, int port, int grp,
12091226
u32 rew_op, struct sk_buff *skb)
@@ -1214,7 +1231,7 @@ void ocelot_port_inject_frame(struct ocelot *ocelot, int port, int grp,
12141231
ocelot_write_rix(ocelot, QS_INJ_CTRL_GAP_SIZE(1) |
12151232
QS_INJ_CTRL_SOF, QS_INJ_CTRL, grp);
12161233

1217-
ocelot_ifh_port_set(ifh, port, rew_op, skb_vlan_tag_get(skb));
1234+
ocelot_ifh_set_basic(ifh, ocelot, port, rew_op, skb);
12181235

12191236
for (i = 0; i < OCELOT_TAG_LEN / 4; i++)
12201237
ocelot_write_rix(ocelot, ifh[i], QS_INJ_WR, grp);

drivers/net/ethernet/mscc/ocelot_fdma.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ static int ocelot_fdma_prepare_skb(struct ocelot *ocelot, int port, u32 rew_op,
666666
ifh = skb_push(skb, OCELOT_TAG_LEN);
667667
skb_put(skb, ETH_FCS_LEN);
668668
memset(ifh, 0, OCELOT_TAG_LEN);
669-
ocelot_ifh_port_set(ifh, port, rew_op, skb_vlan_tag_get(skb));
669+
ocelot_ifh_set_basic(ifh, ocelot, port, rew_op, skb);
670670

671671
return 0;
672672
}

include/linux/dsa/ocelot.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#ifndef _NET_DSA_TAG_OCELOT_H
66
#define _NET_DSA_TAG_OCELOT_H
77

8+
#include <linux/if_bridge.h>
9+
#include <linux/if_vlan.h>
810
#include <linux/kthread.h>
911
#include <linux/packing.h>
1012
#include <linux/skbuff.h>
@@ -273,4 +275,49 @@ static inline u32 ocelot_ptp_rew_op(struct sk_buff *skb)
273275
return rew_op;
274276
}
275277

278+
/**
279+
* ocelot_xmit_get_vlan_info: Determine VLAN_TCI and TAG_TYPE for injected frame
280+
* @skb: Pointer to socket buffer
281+
* @br: Pointer to bridge device that the port is under, if any
282+
* @vlan_tci:
283+
* @tag_type:
284+
*
285+
* If the port is under a VLAN-aware bridge, remove the VLAN header from the
286+
* payload and move it into the DSA tag, which will make the switch classify
287+
* the packet to the bridge VLAN. Otherwise, leave the classified VLAN at zero,
288+
* which is the pvid of standalone ports (OCELOT_STANDALONE_PVID), although not
289+
* of VLAN-unaware bridge ports (that would be ocelot_vlan_unaware_pvid()).
290+
* Anyway, VID 0 is fine because it is stripped on egress for these port modes,
291+
* and source address learning is not performed for packets injected from the
292+
* CPU anyway, so it doesn't matter that the VID is "wrong".
293+
*/
294+
static inline void ocelot_xmit_get_vlan_info(struct sk_buff *skb,
295+
struct net_device *br,
296+
u64 *vlan_tci, u64 *tag_type)
297+
{
298+
struct vlan_ethhdr *hdr;
299+
u16 proto, tci;
300+
301+
if (!br || !br_vlan_enabled(br)) {
302+
*vlan_tci = 0;
303+
*tag_type = IFH_TAG_TYPE_C;
304+
return;
305+
}
306+
307+
hdr = (struct vlan_ethhdr *)skb_mac_header(skb);
308+
br_vlan_get_proto(br, &proto);
309+
310+
if (ntohs(hdr->h_vlan_proto) == proto) {
311+
vlan_remove_tag(skb, &tci);
312+
*vlan_tci = tci;
313+
} else {
314+
rcu_read_lock();
315+
br_vlan_get_pvid_rcu(br, &tci);
316+
rcu_read_unlock();
317+
*vlan_tci = tci;
318+
}
319+
320+
*tag_type = (proto != ETH_P_8021Q) ? IFH_TAG_TYPE_S : IFH_TAG_TYPE_C;
321+
}
322+
276323
#endif

include/soc/mscc/ocelot.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -969,7 +969,8 @@ void __ocelot_target_write_ix(struct ocelot *ocelot, enum ocelot_target target,
969969
bool ocelot_can_inject(struct ocelot *ocelot, int grp);
970970
void ocelot_port_inject_frame(struct ocelot *ocelot, int port, int grp,
971971
u32 rew_op, struct sk_buff *skb);
972-
void ocelot_ifh_port_set(void *ifh, int port, u32 rew_op, u32 vlan_tag);
972+
void ocelot_ifh_set_basic(void *ifh, struct ocelot *ocelot, int port,
973+
u32 rew_op, struct sk_buff *skb);
973974
int ocelot_xtr_poll_frame(struct ocelot *ocelot, int grp, struct sk_buff **skb);
974975
void ocelot_drain_cpu_queue(struct ocelot *ocelot, int grp);
975976
void ocelot_ptp_rx_timestamp(struct ocelot *ocelot, struct sk_buff *skb,

net/dsa/tag_ocelot.c

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,6 @@
88
#define OCELOT_NAME "ocelot"
99
#define SEVILLE_NAME "seville"
1010

11-
/* If the port is under a VLAN-aware bridge, remove the VLAN header from the
12-
* payload and move it into the DSA tag, which will make the switch classify
13-
* the packet to the bridge VLAN. Otherwise, leave the classified VLAN at zero,
14-
* which is the pvid of standalone and VLAN-unaware bridge ports.
15-
*/
16-
static void ocelot_xmit_get_vlan_info(struct sk_buff *skb, struct dsa_port *dp,
17-
u64 *vlan_tci, u64 *tag_type)
18-
{
19-
struct net_device *br = dsa_port_bridge_dev_get(dp);
20-
struct vlan_ethhdr *hdr;
21-
u16 proto, tci;
22-
23-
if (!br || !br_vlan_enabled(br)) {
24-
*vlan_tci = 0;
25-
*tag_type = IFH_TAG_TYPE_C;
26-
return;
27-
}
28-
29-
hdr = skb_vlan_eth_hdr(skb);
30-
br_vlan_get_proto(br, &proto);
31-
32-
if (ntohs(hdr->h_vlan_proto) == proto) {
33-
vlan_remove_tag(skb, &tci);
34-
*vlan_tci = tci;
35-
} else {
36-
rcu_read_lock();
37-
br_vlan_get_pvid_rcu(br, &tci);
38-
rcu_read_unlock();
39-
*vlan_tci = tci;
40-
}
41-
42-
*tag_type = (proto != ETH_P_8021Q) ? IFH_TAG_TYPE_S : IFH_TAG_TYPE_C;
43-
}
44-
4511
static void ocelot_xmit_common(struct sk_buff *skb, struct net_device *netdev,
4612
__be32 ifh_prefix, void **ifh)
4713
{
@@ -53,7 +19,8 @@ static void ocelot_xmit_common(struct sk_buff *skb, struct net_device *netdev,
5319
u32 rew_op = 0;
5420
u64 qos_class;
5521

56-
ocelot_xmit_get_vlan_info(skb, dp, &vlan_tci, &tag_type);
22+
ocelot_xmit_get_vlan_info(skb, dsa_port_bridge_dev_get(dp), &vlan_tci,
23+
&tag_type);
5724

5825
qos_class = netdev_get_num_tc(netdev) ?
5926
netdev_get_prio_tc_map(netdev, skb->priority) : skb->priority;

0 commit comments

Comments
 (0)