Thread (24 messages) read the whole thread 24 messages, 3 authors, 6h ago

Re: [PATCH net-next v3 08/10] net: dsa: microchip: add KSZ8463 tail tag handling

From: Bastien Curutchet <hidden>
Date: 2026-07-31 09:59:48
Also in: lkml

Hi,

On 7/31/26 4:23 AM, Jakub Kicinski wrote:
quoted hunk ↗ jump to hunk
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
--- Patch [8]: [PATCH net-next v3 08/10] net: dsa: microchip: add KSZ8463 tail tag handling ---
commit 20a43ca1297a3640a458d9df2837f23e6b9191ea
Author: Bastien Curutchet (Schneider Electric) [off-list ref]

net: dsa: microchip: add KSZ8463 tail tag handling

This adds a KSZ8463-specific DSA tag driver so that RX timestamps can be
taken from the reserved2 field of the PTP header instead of from the tail
tag, and switches ksz8463_get_tag_protocol() over to the new
DSA_TAG_PROTO_KSZ8463.  A few questions below about the new receive and
transmit paths.
quoted
diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c
index 5e5bfc5cae2d..ac9e8ef5774a 100644
--- a/drivers/net/dsa/microchip/ksz8.c
+++ b/drivers/net/dsa/microchip/ksz8.c
@@ -2966,7 +2966,7 @@ static enum dsa_tag_protocol ksz8463_get_tag_protocol(struct dsa_switch *ds,
  						      int port,
  						      enum dsa_tag_protocol mp)
  {
-	return DSA_TAG_PROTO_KSZ9893;
+	return DSA_TAG_PROTO_KSZ8463;
  }
  
  static int ksz8463_connect_tag_protocol(struct dsa_switch *ds,
@@ -2974,7 +2974,7 @@ static int ksz8463_connect_tag_protocol(struct dsa_switch *ds,
  {
  	struct ksz_tagger_data *tagger_data;
  
-	if (proto != DSA_TAG_PROTO_KSZ9893)
+	if (proto != DSA_TAG_PROTO_KSZ8463)
  		return -EPROTONOSUPPORT;
[Severity: Low]
This changes the user-visible tagging protocol name for KSZ8463 boards,
which is reachable both through the conduit's sysfs "tagging" attribute and
through the "dsa-tag-protocol" device tree property.

A board that currently pins the previous default:

	dsa-tag-protocol = "ksz9893";

now hits the -EPROTONOSUPPORT return in ksz8463_connect_tag_protocol()
instead of degrading to the old behaviour.  Would it be worth mentioning
this compatibility aspect in the commit message?
I don't have any strong opinion on this.
quoted
  
  	tagger_data = ksz_tagger_data(ds);
[ ... include/net/dsa.h hunks snipped ... ]
quoted
diff --git a/net/dsa/tag_ksz.c b/net/dsa/tag_ksz.c
index f8b40437c5fa..b4d70ba930dc 100644
--- a/net/dsa/tag_ksz.c
+++ b/net/dsa/tag_ksz.c
[ ... ]
quoted
@@ -396,6 +397,70 @@ static const struct dsa_device_ops ksz9893_netdev_ops = {
  DSA_TAG_DRIVER(ksz9893_netdev_ops);
  MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ9893, KSZ9893_NAME);
  
+#define KSZ8463_TAIL_TAG_PRIO		GENMASK(4, 3)
+#define KSZ8463_TAIL_TAG_EG_PORT_M	GENMASK(2, 0)
+
+static struct sk_buff *ksz8463_xmit(struct sk_buff *skb,
+				    struct net_device *dev)
+{
+	u16 queue_mapping = skb_get_queue_mapping(skb);
+	u8 prio = netdev_txq_to_tc(dev, queue_mapping);
+
+	return ksz_common_xmit(skb, dev, false,
+			       FIELD_PREP(KSZ8463_TAIL_TAG_PRIO, prio),
+			       0);
+}
[Severity: Medium]
Is the loss of the tail tag override bit intentional here?
Yes it is, it doesn't exist on KSZ8463.
Until this patch KSZ8463 used ksz9893_xmit(), which passes
KSZ9893_TAIL_TAG_OVERRIDE (BIT(5)) as the override_mask argument, and
ksz8795_xmit() likewise passes KSZ8795_TAIL_TAG_OVERRIDE.  ksz8463_xmit()
passes 0, which makes this branch of ksz_common_xmit() dead for the new
tagger:

	if (is_link_local_ether_addr(hdr->h_dest))
		*tag |= override_mask;

Does that mean CPU-injected link-local frames (STP BPDUs, peer delay
multicast, LLDP) are now subject to normal port state filtering, so a port
in BLOCKING or LISTENING state drops them?

KSZ8463_TAIL_TAG_PRIO is GENMASK(4, 3), i.e. the same layout as the KSZ9893
tail tag, which suggests the override bit does exist at BIT(5) on this part.
The KSZ8463 tail tag layout is not documented anywhere in the driver, so if
BIT(5) is in fact reserved on this chip, could the change (and the now
constant override_mask argument) get an explicit note?

[Severity: Medium]
The do_tstamp = false argument means ksz_xmit_timestamp() never runs for
KSZ8463, and that is the only consumer of
KSZ_SKB_CB(skb)->update_correction:

net/dsa/tag_ksz.c:ksz_xmit_timestamp() {
	...
	/* Set correction field to 0 and update UDP checksum */
	ptp_header_update_correction(skb, ptp_type, ptp_hdr, 0);
	...
}

ksz_port_txtstamp() in drivers/net/dsa/microchip/ksz_ptp.c still sets that
field and returns without cloning, expecting the tagger to do the work:

	case PTP_MSGTYPE_PDELAY_RESP:
		if (prt->tstamp_config.tx_type == HWTSTAMP_TX_ONESTEP_P2P) {
			KSZ_SKB_CB(skb)->ptp_type = type;
			KSZ_SKB_CB(skb)->update_correction = true;
			return;
		}

ksz_get_ts_info() also keeps advertising the mode for this chip:

	ts->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ONESTEP_P2P);

	if (is_lan937x(dev))
		ts->tx_types |= BIT(HWTSTAMP_TX_ON);

So user space can select HWTSTAMP_TX_ONESTEP_P2P on a KSZ8463 port while the
tag driver performs no correction field handling at all, and
ksz_port_rxtstamp() still subtracts the ingress timestamp from the
PDelay_Req correction in that mode.  Is the correction field expected to be
handled entirely in hardware here?  If so, could the commit message say so,
since the xmit side behaviour change is not mentioned?
This series only adds support for two step PTP, that's why 
ksz_xmit_timestamp() isn't called by the KSZ8463 code path.
quoted
+
+static struct sk_buff *ksz8463_rcv(struct sk_buff *skb, struct net_device *dev)
+{
+	unsigned int len = KSZ_EGRESS_TAG_LEN;
+	struct ptp_header *ptp_hdr;
+	unsigned int ptp_class;
+	unsigned int port;
+	ktime_t ts;
+	u8 *tag;
+
+	if (skb_linearize(skb)) {
+		kfree_skb(skb);
+		return NULL;
+	}
+
+	KSZ_SKB_CB(skb)->tstamp = 0;
+
+	/* Tag decoding */
+	tag = skb_tail_pointer(skb) - KSZ_EGRESS_TAG_LEN;
+	port = tag[0] & KSZ8463_TAIL_TAG_EG_PORT_M;
+
+	__skb_push(skb, ETH_HLEN);
+	ptp_class = ptp_classify_raw(skb);
+	__skb_pull(skb, ETH_HLEN);
[Severity: Low]
This isn't a bug, but the DSA core runs the same classifier a few
instructions later and hands the result to the driver:

net/dsa/tag.c:dsa_skb_defer_rx_timestamp() {
	...
	__skb_push(skb, ETH_HLEN);
	type = ptp_classify_raw(skb);
	__skb_pull(skb, ETH_HLEN);
	if (type == PTP_CLASS_NONE)
		return false;
	return ds->ops->port_rxtstamp(ds, p->dp->index, skb, type);
}

and ksz_port_rxtstamp() already calls ptp_parse_header() itself.  So every
received frame on a KSZ8463 conduit now runs the PTP BPF classifier in the
tagger, even with hardware timestamping disabled, and PTP frames are
classified twice.

Unlike KSZ9477/KSZ9893, where the timestamp sits in the tail tag and has to
be consumed before ksz_common_rcv() trims it, ptp_hdr->reserved2 survives
into ksz_port_rxtstamp() untouched.  Could the reserved2 extraction live in
ksz_port_rxtstamp() instead, where the PTP class is already available?
That would mean adding ksz8463-specific handling in the common 
ksz_port_rxtstamp() or create a ksz8463_port_rxtstamp() with once again 
its load of code duplication.
quoted
+	if (ptp_class == PTP_CLASS_NONE)
+		goto common_rcv;
+
+	ptp_hdr = ptp_parse_header(skb, ptp_class);
[Severity: Medium]
Only PTP_CLASS_NONE is filtered here, but the code below interprets the
frame with the PTPv2 struct ptp_header layout.  ptp_classify_raw() also
returns PTP_CLASS_V1_IPV4/PTP_CLASS_V1_IPV6, as documented in
include/linux/ptp_classify.h:

  * PTP_CLASS_V1_IPV{4,6}, PTP_CLASS_V2_IPV{4,6} or
  * PTP_CLASS_V2_{L2,VLAN}, depending on the packet content.

and the classifier program simply masks the version nibble, so any value
1..15 comes back ORed with the encapsulation bits:

net/core/ptp_classifier.c:
	{ 0x48, 0, 0, 0x0016 },	/* ldh [22] */
	{ 0x54, 0, 0, 0x000f },	/* and #0xf */
	{ 0x44, 0, 0, 0x0010 },	/* or #0x10 */

ptp_parse_header() does no version filtering either, it only skips
VLAN/IP/UDP headers and bounds-checks sizeof(struct ptp_header).  For a
PTPv1 frame, offsets 8..11 are inside the 16-byte subdomain field rather
than a reserved field, so real header content would be read as a timestamp
and then zeroed.  Should there be a "if (!(ptp_class & PTP_CLASS_V2))" gate
before the reserved2 access?
ksz8463_set_hwtstamp_config() in PATCH 10 refuses PTP V1 so we 
explicitly tell userspace that V1 isn't supported.
quoted
+	if (ptp_hdr) {
+		ts = ksz_decode_tstamp(get_unaligned_be32(&ptp_hdr->reserved2));
+		KSZ_SKB_CB(skb)->tstamp = ts;
+		ptp_hdr->reserved2 = 0;
+	}
[Severity: Medium]
Is there anything that keeps this from running while the switch PTP unit is
disabled?  The tagger never looks at KSZ_HWTS_EN here, and does not even
fetch ksz_tagger_private(), while the transmit side in the same file does
gate on it:

net/dsa/tag_ksz.c:ksz_xmit_timestamp() {
	...
	if (!test_bit(KSZ_HWTS_EN, &priv->state))
		return;
	...
}

That bit is set together with PTP_ENABLE in ksz_ptp_enable_mode():

	tagger_data->hwtstamp_set_state(dev->ds, tag_en);

	return ksz_rmw16(dev, regs[PTP_MSG_CONF1], PTP_ENABLE,
			 tag_en ? PTP_ENABLE : 0);

so "PTP disabled" is the default state, in which the hardware never wrote
anything into reserved2.  In that state, does this code destroy 4 bytes of a
genuine remote frame (messageTypeSpecific in IEEE 1588-2019) and build
KSZ_SKB_CB(skb)->tstamp out of remote bytes?  ksz_port_rxtstamp() then
publishes it with no prt->hwts_rx_en test:

drivers/net/dsa/microchip/ksz_ptp.c:ksz_port_rxtstamp() {
	...
	tstamp = KSZ_SKB_CB(skb)->tstamp;
	memset(hwtstamps, 0, sizeof(*hwtstamps));
	hwtstamps->hwtstamp = ksz_tstamp_reconstruct(dev, tstamp);
	...
}

ksz9477_rcv() only trusts the timestamp when the hardware sets
KSZ9477_PTP_TAG_INDICATION, and the commit message says KSZ8463 provides no
such indication.  For comparison, hellcreek gates on the enable bit before
touching reserved2, in hellcreek_should_tstamp():

	if (!test_bit(HELLCREEK_HWTSTAMP_ENABLED, &ps->state))
		return NULL;

Would the same gating be appropriate here?
This is only done when we receive PTP frames because otherwise ptp_hdr 
is NULL. I think it's not a big deal to zero-out the reserved area of 
PTP frames we received even if the PTP is disabled, this area is very 
likely already set to 0 by the peer. Then ksz_port_rxtstamp indeed 
reconstructs the timestamp but it also does it for the other switches, 
not only for the KSZ8463.
[Severity: Medium]
Does the "ptp_hdr->reserved2 = 0" store need checksum bookkeeping?  For PTP
over UDP (advertised by ksz_get_ts_info() through
HWTSTAMP_FILTER_PTP_V2_L4_EVENT) these 4 bytes are covered by the UDP
checksum, but neither the checksum field nor skb->csum / skb->ip_summed are
updated.

On a conduit reporting CHECKSUM_COMPLETE, skb->csum then still describes the
unmodified bytes and is carried forward by ksz_common_rcv():

	if (pskb_trim_rcsum(skb, skb->len - len)) {

In HWTSTAMP_TX_ONESTEP_P2P mode, ksz_port_rxtstamp() additionally calls
ptp_header_update_correction() on the same frame, which does:

include/linux/ptp_classify.h:ptp_header_update_correction() {
	...
	uhdr->check = csum_fold(ptp_check_diff8(correction_old, hdr->correction,
						~csum_unfold(uhdr->check)));
	...
	skb->ip_summed = CHECKSUM_NONE;
}

so the stack recomputes the UDP checksum over data whose reserved2 delta was
never accounted for.  If the switch updates the UDP checksum when it inserts
the timestamp, would PDelay_Req frames then be dropped in udp_rcv()?  The
same file already uses ptp_header_update_correction() from
ksz_xmit_timestamp() for in-place PTP header edits.
This series adds support for two-step PTP on the L2 layer so the IP 
checksums won't be involved in the transactions.


Best regards,
Bastien
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help