Re: [PATCH net-next v11 1/9] net: dsa: add tag driver for LAN9645X

Jens Emil Schulz Ostergaard <[email protected]>
Newsgroups org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel,org.kernel.vger.netdev
Message-ID <[email protected]>
This is my reply to:
https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260805-dsa_lan9645x_switch_driver_base-v11-0-007ebc983a0a%40microchip.com?part=1

> --- Patch [1]: [PATCH net-next v11 1/9] net: dsa: add tag driver for LAN9645X ---
> commit 16799f4195f9118fdca9d3ba803a3d4d2e6a9414
> Author: Jens Emil Schulz Østergaard <[email protected]>
>
> net: dsa: add tag driver for LAN9645X
>
> This adds a DSA tag driver for the LAN9645x family used with a front port
> as CPU port (NPI mode), with a long prefix on extraction and no prefix on
> injection.  A new header exports the 28 byte internal frame header (IFH)
> field table, and the tagger encodes/decodes it in lan9645x_xmit() and
> lan9645x_rcv().
>
> A few questions below.
>
> > diff --git a/include/linux/dsa/lan9645x.h b/include/linux/dsa/lan9645x.h
> > new file mode 100644
> > index 0000000000000..9896fab649c4d
> > --- /dev/null
> > +++ b/include/linux/dsa/lan9645x.h
> > @@ -0,0 +1,144 @@
>
> [ ... ]
>
> > +#define LAN9645X_IFH_TAG_TYPE_C	0
> > +#define LAN9645X_IFH_TAG_TYPE_S	1
> > +#define LAN9645X_IFH_LEN_U32		7
> > +#define LAN9645X_IFH_LEN		(LAN9645X_IFH_LEN_U32 * sizeof(u32))
> > +#define LAN9645X_IFH_BITS		(LAN9645X_IFH_LEN * BITS_PER_BYTE)
> > +#define LAN9645X_SHORT_PREFIX_LEN	4
> > +#define LAN9645X_LONG_PREFIX_LEN	16
> > +#define LAN9645X_TOTAL_TAG_LEN (LAN9645X_LONG_PREFIX_LEN + LAN9645X_IFH_LEN)
> > +
> > +#define IFH_INJ_TIMESTAMP		192
> > +#define IFH_BYPASS			191
> > +#define IFH_MASQ			190
> > +#define IFH_TIMESTAMP			186
> > +#define IFH_TIMESTAMP_NS		194
> > +#define IFH_TIMESTAMP_SUBNS		186
> > +#define IFH_MASQ_PORT			186
> > +#define IFH_RCT_INJ			185
> > +#define IFH_LEN				171
>
> Discovered by: opus-5 · Missed by: sashiko-gemini
> Can this IFH_LEN definition clash with the existing ones?  There are already
> two in-tree macros with that name and a different meaning:
>
> drivers/net/ethernet/microchip/lan966x/lan966x_ifh.h:
>     #define IFH_LEN                      7
>     #define IFH_LEN_BYTES                (IFH_LEN * sizeof(u32))
>
> drivers/net/ethernet/microchip/sparx5/sparx5_main.h:
>     #define IFH_LEN                9 /* 36 bytes */
>
> Both are u32 word counts for the IFH of sibling chips in the same family,
> while here IFH_LEN is the bit position 171 of the length field.  A
> translation unit that includes this header plus either of those gets a
> redefinition warning, or silently expands 7 or 9 where bit 171 was meant.
>
> The same header also exports LAN9645X_IFH_LEN (28 bytes), so two macros
> differing only by prefix carry unrelated quantities.  The length/format and
> tag type macros already got the LAN9645X_ prefix - would it make sense to
> prefix the whole field table exported from include/linux/dsa/ as well?
>

I will rename LAN9645X_IFH_LEN to LAN9645X_IFH_LEN_BYTES and move the field
table to tag_lan9645x.c.

> > +#define IFH_WRDMODE			169
> > +#define IFH_RTAGD			167
> > +#define IFH_CUTTHRU			166
>
> [ ... ]
>
> > +#define IFH_RTE_INB_UPDATE		148
> > +#define IFH_POP_CNT			146
> > +#define IFH_ETYPE_OFS			144
> > +#define IFH_SRCPORT			140
>
> [ ... ]
>
> > +#define IFH_INJ_TIMESTAMP_SZ		32
> > +#define IFH_BYPASS_SZ			1
> > +#define IFH_MASQ_SZ			1
> > +#define IFH_TIMESTAMP_SZ		38
>
> Discovered by: opus-5 · Missed by: sashiko-gemini
> Is IFH_TIMESTAMP usable with the accessors added by this patch?  Both
> lan9645x_ifh_set() and lan9645x_ifh_get() carry:
>
>     BUILD_BUG_ON_MSG(length > 32, "IFH field size wider than 32.");
>
> and pass the value through u32, so a consumer calling
>
>     lan9645x_ifh_get(ifh, IFH_TIMESTAMP, IFH_TIMESTAMP_SZ)
>
> fails to build.  The usable split already exists as IFH_TIMESTAMP_NS (30
> bits) plus IFH_TIMESTAMP_SUBNS (8 bits), so should the 38 bit alias be
> dropped or annotated?
>
> Related, the table mixes the injection and extraction layouts without
> marking which is which: IFH_TIMESTAMP, IFH_TIMESTAMP_SUBNS and
> IFH_MASQ_PORT all start at bit 186, and IFH_INJ_TIMESTAMP (32 bits at 192)
> overlaps IFH_BYPASS and IFH_MASQ.  Only the .c file mentions that "There
> are two field layouts, one for extraction and one for injection".  Could
> the header note the direction per field?

I will remove IFH_TIMESTAMP and just keep the NS/SUBNS subfield split, and
annotate each field with whether it is used in the injection, extraction or both
headers.

>
> > +#define IFH_TIMESTAMP_NS_SZ		30
> > +#define IFH_TIMESTAMP_SUBNS_SZ		8
>
> [ ... ]
>
> > diff --git a/net/dsa/tag_lan9645x.c b/net/dsa/tag_lan9645x.c
> > new file mode 100644
> > index 0000000000000..6a33216c7792f
> > --- /dev/null
> > +++ b/net/dsa/tag_lan9645x.c
> > @@ -0,0 +1,317 @@
>
> [ ... ]
>
> > +static struct sk_buff *lan9645x_xmit(struct sk_buff *skb,
> > +				     struct net_device *ndev)
> > +{
>
> [ ... ]
>
> > +	lan9645x_ifh_set(ifh, 1, IFH_BYPASS, IFH_BYPASS_SZ);
> > +	lan9645x_ifh_set(ifh, ds->num_ports, IFH_SRCPORT, IFH_SRCPORT_SZ);
> > +	lan9645x_ifh_set(ifh, tag_type, IFH_TAG_TYPE, IFH_TAG_TYPE_SZ);
> > +	lan9645x_ifh_set(ifh, vlan_tci, IFH_TCI, IFH_TCI_SZ);
> > +	lan9645x_ifh_set(ifh, qos_class, IFH_QOS_CLASS, IFH_QOS_CLASS_SZ);
> > +	lan9645x_ifh_set(ifh, BIT(dp->index), IFH_DSTS, IFH_DSTS_SZ);
>
> Discovered by: opus-5 · Missed by: sashiko-gemini
> This isn't a bug today, but should the destination set use
> dsa_xmit_port_mask() instead of open-coding BIT(dp->index)?
>
> net/dsa/tag.h:dsa_xmit_port_mask() is BIT(dp->index) plus the HSR
> duplication fan-out:
>
>     unsigned long mask = BIT(dp->index);
>
>     if (IS_ENABLED(CONFIG_HSR) &&
>         unlikely(dev->features & NETIF_F_HW_HSR_DUP)) {
>             struct net_device *hsr_dev = dp->hsr_dev;
>             struct dsa_port *other_dp;
>
>             dsa_hsr_foreach_port(other_dp, dp->ds, hsr_dev)
>                     mask |= BIT(other_dp->index);
>     }
>
> tag_ocelot.c writes the same IFH DSTS concept through the helper:
>
>     ocelot_ifh_set_dest(injection, dsa_xmit_port_mask(skb, netdev));
>
> and net/dsa/dsa.c documents the contract as "Helpers for switches without
> specific HSR offloads, but which can implement NETIF_F_HW_HSR_DUP because
> their tagger uses dsa_xmit_port_mask()".  I checked the companion switch
> driver in this series and it implements no HSR offload, so there is no
> functional effect right now; a later HSR/PRP offload would however
> duplicate to a single port only, since the partner bit never reaches IFH
> DSTS.
>

I think this would be a bug on this chip, for HSR/PRP v1. When multiple
ports are set in the IFH_DSTS mask, each port receives a byte-identical copy. But
HSR/PRP v1 require the 2 duplicates differ in a bit (lan_id) in the tag which
tells from which port the frame egresses. Typically 0 for port A and 1 for
port B.

This chip is also capable of offloading several HSR and PRP features such as
duplication and duplicate discard. When I implement the callbacks I plan on
using those capabilities.

> > +
> > +	return skb;
> > +}
> > +
> > +static struct sk_buff *lan9645x_rcv(struct sk_buff *skb,
> > +				    struct net_device *ndev)
> > +{
>
> [ ... ]
>
> > +	popcnt = lan9645x_ifh_get(ifh, IFH_POP_CNT, IFH_POP_CNT_SZ);
> > +	etype_ofs = lan9645x_ifh_get(ifh, IFH_ETYPE_OFS, IFH_ETYPE_OFS_SZ);
> > +	src_port = lan9645x_ifh_get(ifh, IFH_SRCPORT, IFH_SRCPORT_SZ);
>
> [ ... ]
>
> > +	if (popcnt == 0 && etype_ofs == 0)
> > +		ifh_gap_len = 2 * VLAN_HLEN;
> > +	else if (popcnt == 3)
> > +		ifh_gap_len = VLAN_HLEN;
>
> Discovered by: opus-5 · Missed by: sashiko-gemini
> Which extraction encodings does this cover?  IFH_POP_CNT and IFH_ETYPE_OFS
> are 2 bits each, so there are 16 combinations, and only two of them get a
> non-zero gap here.  Everything else, including popcnt == 1, popcnt == 2 and
> (popcnt == 0, etype_ofs == 1), falls through to the initialiser
> ifh_gap_len = 0.
>
> The mapping also looks internally inconsistent: on one port an untagged
> frame (popcnt == 0, etype_ofs == 0) gets an 8 byte gap, while a
> single-tagged frame on the same port (popcnt == 0, etype_ofs == 1) gets a 0
> byte gap.  That is an 8 byte difference for one 4 byte tag.  The two
> handled values do differ by exactly one tag, which suggests the real
> discriminator is the number of tags left in the delivered frame
> (ETYPE_OFS) rather than the two POP_CNT comparisons.  Could the full
> datasheet mapping be spelled out here?
>
> The companion switch driver added later in this series programs a pop count
> of 1 on every VLAN-aware port:
>
> drivers/net/dsa/microchip/lan9645x/lan9645x_vlan.c:
>         val |= ANA_VLAN_CFG_VLAN_AWARE_ENA_SET(1) |
>                ANA_VLAN_CFG_VLAN_POP_CNT_SET(1);
>
> so POP_CNT == 1 seems to be an expected extraction encoding that lands in
> the zero-gap default.
>

No the current logic covers all cases properly. The values encoded in the fields
are not intuitive. On a vlan aware port ifh.pop_cnt will not be 1 for instance.

This is how the calculation goes:

DMAC_DT = (ifh.pop_cnt == 0 && ifh.etype_ofs == 0) ? 4 : ifh.pop_cnt
DMAC_OFFSET = TAG_SIZE + 4*(DMAC_DT - 2)

With tag pushing disabled we have either

popcnt=0 and etype_ofs=0     => 2x pop
popcnt=3 and etype_ofs=*     => 1x pop
popcnt=2 and etype_ofs=*     => no pop

The remaining combinations indicate a push and will not occur.

I will include this in a comment, and add WARN_ON_ONCE and a bailout for the
impossible combinations.

> > +
> > +	skb_pull(skb, LAN9645X_IFH_LEN);
> > +
> > +	if (unlikely(!pskb_may_pull(skb, ifh_gap_len + ETH_HLEN))) {
> > +		kfree_skb(skb);
> > +		return NULL;
> > +	}
> > +
> > +	skb_pull(skb, ifh_gap_len);
> > +	skb_reset_mac_header(skb);
> > +	skb_set_network_header(skb, ETH_HLEN);
> > +	skb_reset_mac_len(skb);
>
> Discovered by: opus-5 · Missed by: sashiko-gemini
> Following on from the gap computation above: if ifh_gap_len is smaller than
> the real gap, do these calls describe a header the hardware did not put
> there, so that eth_type_trans() in dsa_switch_rcv() derives a protocol from
> leftover tag bytes?  And if it is larger, are up to 8 bytes of the real
> DMAC pushed back into the headroom and lost?
>
> > +
> > +	/* Reset skb->data past the actual ethernet header. */
> > +	skb_pull(skb, ETH_HLEN);
>
> [ ... ]
>
> > +	skb_postpull_rcsum(skb,
> > +			   skb->data - LAN9645X_TOTAL_TAG_LEN - ifh_gap_len,
> > +			   LAN9645X_TOTAL_TAG_LEN + ifh_gap_len);
>
> Discovered by: opus-5 · Missed by: sashiko-gemini
> The same ifh_gap_len is reused as the checksum fixup length here.  Can a
> wrong gap corrupt skb->csum on a CHECKSUM_COMPLETE conduit, since the
> subtracted byte range then does not match the bytes the conduit actually
> summed?
>
> > +
> > +	skb->dev = dsa_conduit_find_user(ndev, 0, src_port);
> > +	if (WARN_ON_ONCE(!skb->dev)) {
> > +		/* This should never happen since we have disabled reflection
> > +		 * back to the CPU.
> > +		 */
> > +		kfree_skb(skb);
> > +		return NULL;
> > +	}
>
> Discovered by: opus-5 · Missed by: sashiko-gemini
> Is this WARN_ON_ONCE() reachable from frame data?  src_port comes straight
> out of the received IFH:
>
>     src_port = lan9645x_ifh_get(ifh, IFH_SRCPORT, IFH_SRCPORT_SZ);
>
> That is a 4 bit field, so any value 0..15 can arrive, and
> dsa_conduit_find_user() returns NULL for every index that is not a
> registered user port.  Candidates for such an index are the NPI port index
> itself (registered as DSA_PORT_TYPE_CPU), physical ports left unused by the
> device tree, and masqueraded source ports from a frame injected on the bare
> conduit, which the hardware parses as a bare IFH because no injection
> prefix is configured.  With panic_on_warn=1 that becomes a panic from the
> receive path.
>
> The comment relies on reflection being disabled, but the guard in the
> companion driver only covers src_port equal to the CPU port module:
>
> drivers/net/dsa/microchip/lan9645x/lan9645x_npi.c:
>     /* Make sure frames with src_port=<CPU port module> are not reflected
>      * back via the NPI port. ... The *_CPU_KILL_ENA flags above only have an
>      * effect when a frame is output due to a CPU forwarding decision such as
>      * trapping or cpu copy.
>      */
>     lan_rmw(0, BIT(port), lan9645x,
>             ANA_PGID(PGID_SRC + lan9645x->num_phys_ports));
>
> The tagger for the same hardware family deliberately drops silently here:
>
> net/dsa/tag_ocelot.c:ocelot_rcv()
>     skb->dev = dsa_conduit_find_user(netdev, 0, src_port);
>     if (!skb->dev) {
>             /* The switch will reflect back some frames sent through
>              * sockets opened on the bare DSA conduit. ... So don't print any
>              * error message here (ignore and drop those frames).
>              */
>             kfree_skb(skb);
>             return NULL;
>     }
>
> The kfree_skb() and return NULL here are already right - would dropping the
> WARN_ON_ONCE() be preferable?
>

I think it is right that a carefully constructed frame on a raw socket for the
conduit, could be reflected. I will remove the WARN_ON_ONCE().

> [ ... ]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.