• Vladimir Oltean's avatar
    net: mscc: ocelot: use ocelot_xmit_get_vlan_info() also for FDMA and register injection · 67c3ca2c
    Vladimir Oltean authored
    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
    5ca721c5 ("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 5ca721c5
    ("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: 08d02364 ("net: mscc: fix the injection header")
    Signed-off-by: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
    Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
    67c3ca2c
ocelot_fdma.c 22.3 KB