LinuxPPC-Dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: wei.fang@oss.nxp.com
To: richardcochran@gmail.com, vladimir.oltean@nxp.com,
	xiaoning.wang@nxp.com, andrew+netdev@lunn.ch,
	davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
	pabeni@redhat.com, andrew@lunn.ch, olteanv@gmail.com
Cc: wei.fang@nxp.com, chleroy@kernel.org, imx@lists.linux.dev,
	netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	linuxppc-dev@lists.ozlabs.org,
	linux-arm-kernel@lists.infradead.org
Subject: [PATCH net-next 7/7] net: dsa: netc: add PTP one-step timestamping support
Date: Tue, 28 Jul 2026 18:45:48 +0800	[thread overview]
Message-ID: <20260728104548.3301214-8-wei.fang@oss.nxp.com> (raw)
In-Reply-To: <20260728104548.3301214-1-wei.fang@oss.nxp.com>

From: Wei Fang <wei.fang@nxp.com>

The NETC switch supports one-step TX timestamping for PTP Sync frames. On
transmit the MAC captures the time at which it sends the frame SFD, reads
the correction field at the offset given by PM_SINGLE_STEP[OFFSET], adds
the residence time (SFD TX time minus the software timestamp carried with
the packet) to that field, and writes the result back before the frame
leaves the wire.

A one-step request reaches the switch through a To_Port tag with SubType
set to 1, and the software timestamp is the low 30 bits of the PTP Timer
value placed in the tag Timestamp field. PM_SINGLE_STEP[EN] enables
single-step on the port, PM_SINGLE_STEP[OFFSET] locates the correction
field, and PM_SINGLE_STEP[CH] additionally updates the UDP checksum for
PTP-over-UDP.

PM_SINGLE_STEP is a single per-port register that must be programmed
with the correct offset before each one-step Sync is transmitted. If two
one-step Sync frames are handed to the same port concurrently (e.g. from
two CPUs), the second write to PM_SINGLE_STEP overwrites the value
programmed for the first frame before it leaves the MAC, so the hardware
applies the wrong offset and corrupts the PTP correction field.

Serialize one-step Sync transmission per port so that only one frame is
in flight through PM_SINGLE_STEP at a time. A per-port PTP spinlock
(ptp_lock) guards the NETC_FLAG_ONESTEP_IN_PROGRESS in-flight slot and
the deferral queue (skb_onestep_queue) as one atomic critical section:

- netc_port_txtstamp(), from dsa_user_xmit(), classifies the frame. A
  genuine one-step Sync (twoStepFlag cleared) has its correction and
  timestamp offsets and UDP flag cached in the skb control block and is
  marked NETC_PTP_FLAG_ONESTEP; a two-step request or non-Sync falls
  back to the two-step path. Frames that cannot be handled as one-step
  (parse failure, originTimestamp not in the linear area, or a zerocopy
  skb) are sent unchanged as normal frames.

- netc_onestep_sync_handler(), a tagger callback invoked from
  netc_xmit(), does the serialization. If the slot is free it claims it,
  reads the time, programs PM_SINGLE_STEP and installs a TX-completion
  destructor, then returns the skb so netc_xmit() tags it with SubType1
  and sends it through dsa_user_xmit(). If the slot is busy it queues the
  skb and returns NULL, transferring ownership to the queue.

- When the in-flight frame completes TX the conduit frees the skb, the
  destructor fires and schedules onestep_work. onestep_work dequeues the
  next skb while still holding the slot (so a newly arriving Sync cannot
  jump ahead), programs it with a fresh timestamp and sends it via the
  tagger onestep_sync_xmit() callback, which pushes the SubType1 tag and
  calls dsa_enqueue_skb() directly. This bypasses dsa_user_xmit() so the
  TX stats are not counted twice and the deferred frames stay ordered;
  it is safe because the skb already went through
  skb_ensure_writable_head_tail() and eth_skb_pad() and nothing shrank
  its head/tail room since. The slot is released only when the queue
  drains empty.

The one-step path reads the PTP time with netc_timer_get_current_time(),
which is provided by the NETC v4 timer driver, so update the Kconfig
dependency from PTP_1588_CLOCK_OPTIONAL to PTP_NETC_V4_TIMER. The
"|| PTP_NETC_V4_TIMER=n" form still allows the switch driver to build
when the timer is disabled, in which case it links against the inline
stub in <linux/fsl/netc_global.h>.

If the PTP timer is unavailable when a one-step Sync is about to be
programmed, drop the frame with a rate-limited message rather than send
a bogus correction field, and re-kick onestep_work so the queue keeps
draining.

Assisted-by: Wchat:claude-opus-4-8
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
 drivers/net/dsa/netc/Kconfig          |   2 +-
 drivers/net/dsa/netc/netc_main.c      |   7 +-
 drivers/net/dsa/netc/netc_ptp.c       | 256 +++++++++++++++++++++++++-
 drivers/net/dsa/netc/netc_switch.h    |  12 ++
 drivers/net/dsa/netc/netc_switch_hw.h |   5 +
 include/linux/dsa/tag_netc.h          |  19 ++
 net/dsa/tag_netc.c                    |  72 ++++++++
 7 files changed, 369 insertions(+), 4 deletions(-)

diff --git a/drivers/net/dsa/netc/Kconfig b/drivers/net/dsa/netc/Kconfig
index 8770b65d0f62..55b1f1338a87 100644
--- a/drivers/net/dsa/netc/Kconfig
+++ b/drivers/net/dsa/netc/Kconfig
@@ -4,7 +4,7 @@ config NET_DSA_NETC_SWITCH
 	depends on ARM64 || COMPILE_TEST
 	depends on NET_DSA && PCI
 	depends on NET_VENDOR_FREESCALE
-	depends on PTP_1588_CLOCK_OPTIONAL
+	depends on PTP_NETC_V4_TIMER || PTP_NETC_V4_TIMER=n
 	select NET_DSA_TAG_NETC
 	select FSL_ENETC_MDIO
 	select NXP_NTMP
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index d98ee40a1af7..9855e02b4726 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -74,6 +74,7 @@ static int netc_connect_tag_protocol(struct dsa_switch *ds,
 		return -EPROTONOSUPPORT;
 
 	tagger_data = ds->tagger_data;
+	tagger_data->onestep_sync_handler = netc_onestep_sync_handler;
 	tagger_data->twostep_tstamp_handler = netc_twostep_tstamp_handler;
 
 	return 0;
@@ -94,7 +95,7 @@ static void netc_port_rmw(struct netc_port *np, u32 reg,
 	netc_port_wr(np, reg, new);
 }
 
-static void netc_mac_port_wr(struct netc_port *np, u32 reg, u32 val)
+void netc_mac_port_wr(struct netc_port *np, u32 reg, u32 val)
 {
 	if (is_netc_pseudo_port(np))
 		return;
@@ -318,6 +319,8 @@ static int netc_init_all_ports(struct netc_switch *priv)
 			netc_port_init_ptp_ipft_eid(np);
 			spin_lock_init(&np->ptp_lock);
 			__skb_queue_head_init(&np->skb_txtstamp_queue);
+			__skb_queue_head_init(&np->skb_onestep_queue);
+			INIT_WORK(&np->onestep_work, netc_port_onestep_work);
 		}
 	}
 
@@ -1011,6 +1014,8 @@ static void netc_free_ports_resources(struct netc_switch *priv)
 		 * (sock_efree) while holding a spinlock.
 		 */
 		__skb_queue_purge(&np->skb_txtstamp_queue);
+		cancel_work_sync(&np->onestep_work);
+		__skb_queue_purge(&np->skb_onestep_queue);
 	}
 }
 
diff --git a/drivers/net/dsa/netc/netc_ptp.c b/drivers/net/dsa/netc/netc_ptp.c
index 7288eac11b46..ff7d8e5e68d2 100644
--- a/drivers/net/dsa/netc/netc_ptp.c
+++ b/drivers/net/dsa/netc/netc_ptp.c
@@ -37,7 +37,8 @@ int netc_get_ts_info(struct dsa_switch *ds, int port,
 				 SOF_TIMESTAMPING_RX_HARDWARE |
 				 SOF_TIMESTAMPING_RAW_HARDWARE;
 
-	info->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON);
+	info->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON) |
+			 BIT(HWTSTAMP_TX_ONESTEP_SYNC);
 
 	info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
 			   BIT(HWTSTAMP_FILTER_PTP_V2_EVENT) |
@@ -237,11 +238,18 @@ int netc_port_hwtstamp_set(struct dsa_switch *ds, int port,
 			   struct netlink_ext_ack *extack)
 {
 	struct netc_port *np = NETC_PORT(ds, port);
+	struct netc_switch *priv = ds->priv;
 	int rx_filter, err;
 
 	switch (config->tx_type) {
 	case HWTSTAMP_TX_ON:
 	case HWTSTAMP_TX_OFF:
+		np->ptp_tx_type = config->tx_type;
+		break;
+	case HWTSTAMP_TX_ONESTEP_SYNC:
+		if (!priv->tmr_dev)
+			return -ERANGE;
+
 		np->ptp_tx_type = config->tx_type;
 		break;
 	default:
@@ -384,9 +392,89 @@ bool netc_port_rxtstamp(struct dsa_switch *ds, int port,
 	return false;
 }
 
+static void netc_port_prepare_onestep_sync(struct netc_port *np,
+					   struct sk_buff *skb,
+					   u32 ptp_class, bool *twostep)
+{
+	struct netc_switch *priv = np->switch_priv;
+	u16 correction_offset, timestamp_offset;
+	struct ptp_header *ptp_hdr;
+	u8 msg_type, twostep_flag;
+	bool is_udp = false;
+	u32 pkt_type;
+	u8 *pkt_hdr;
+
+	ptp_hdr = ptp_parse_header(skb, ptp_class);
+	if (!ptp_hdr) {
+		dev_dbg_ratelimited(priv->dev,
+				    "Port %d failed to parse Sync header\n",
+				    np->dp->index);
+		return;
+	}
+
+	pkt_hdr = skb_mac_header(skb);
+	correction_offset = (u8 *)&ptp_hdr->correction - pkt_hdr;
+	timestamp_offset = (u8 *)ptp_hdr + sizeof(*ptp_hdr) - pkt_hdr;
+
+	/* Ensure that the entire originTimestamp field is present in the
+	 * linear buffer of the skb.
+	 */
+	if (pkt_hdr + timestamp_offset + 10 > skb->data + skb_headlen(skb)) {
+		dev_dbg_ratelimited(priv->dev,
+				    "Port %d Sync header not in linear area\n",
+				    np->dp->index);
+		return;
+	}
+
+	msg_type = ptp_get_msgtype(ptp_hdr, ptp_class);
+	twostep_flag = ptp_hdr->flag_field[0] & 0x2;
+
+	/* Only a Sync frame with the twoStepFlag cleared can use one-step
+	 * timestamping. A frame that requests two-step (or is not a Sync)
+	 * carries different on-wire fields, so this is a real classification;
+	 * report it through *twostep so the caller falls back to the two-step
+	 * path.
+	 */
+	if (msg_type != PTP_MSGTYPE_SYNC || twostep_flag != 0) {
+		*twostep = true;
+		return;
+	}
+
+	/* This is a genuine one-step Sync frame. skb_shinfo()->destructor_arg
+	 * is later used to pass the netc_port pointer to
+	 * netc_onestep_skb_destructor() for TX completion notification.
+	 * MSG_ZEROCOPY also uses destructor_arg (via skb_zcopy_init()) to
+	 * track user-space page references. Overwriting it in that case would
+	 * leak the ubuf_info reference and prevent user pages from being
+	 * released. PTP applications do not use MSG_ZEROCOPY, but guard
+	 * against it defensively.
+	 */
+	if (skb_zcopy(skb)) {
+		dev_dbg_ratelimited(priv->dev,
+				    "Port %d one-step Sync not supported on zerocopy skb\n",
+				    np->dp->index);
+		return;
+	}
+
+	pkt_type = ptp_class & PTP_CLASS_PMASK;
+	if (pkt_type == PTP_CLASS_IPV4 || pkt_type == PTP_CLASS_IPV6)
+		is_udp = true;
+
+	/* Cache the parsing results so the tagger xmit path and the deferred
+	 * work do not need to re-parse the PTP header, and so that
+	 * netc_port_program_onestep() can derive these parameters from the
+	 * skb.
+	 */
+	NETC_SKB_CB(skb)->correction_offset = correction_offset;
+	NETC_SKB_CB(skb)->timestamp_offset = timestamp_offset;
+	NETC_SKB_CB(skb)->is_udp = is_udp;
+	NETC_SKB_CB(skb)->ptp_flag = NETC_PTP_FLAG_ONESTEP;
+}
+
 void netc_port_txtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb)
 {
 	struct netc_port *np = NETC_PORT(ds, port);
+	bool twostep = false;
 	u32 ptp_class;
 
 	NETC_SKB_CB(skb)->ptp_flag = 0;
@@ -394,7 +482,10 @@ void netc_port_txtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb)
 	if (ptp_class == PTP_CLASS_NONE)
 		return;
 
-	if (np->ptp_tx_type == HWTSTAMP_TX_ON) {
+	if (np->ptp_tx_type == HWTSTAMP_TX_ONESTEP_SYNC)
+		netc_port_prepare_onestep_sync(np, skb, ptp_class, &twostep);
+
+	if (np->ptp_tx_type == HWTSTAMP_TX_ON || twostep) {
 		struct sk_buff *clone = skb_clone_sk(skb);
 
 		if (unlikely(!clone))
@@ -409,3 +500,164 @@ void netc_port_txtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb)
 		NETC_SKB_CB(skb)->ptp_flag = NETC_PTP_FLAG_TWOSTEP;
 	}
 }
+
+static void netc_port_set_onestep_control(struct netc_port *np, bool udp,
+					  int offset)
+{
+	u32 val;
+
+	val = PM_SINGLE_STEP_EN | FIELD_PREP(PM_SINGLE_STEP_OFFSET, offset);
+	if (udp)
+		val |= PM_SINGLE_STEP_CH;
+	netc_mac_port_wr(np, NETC_PM_SINGLE_STEP(0), val);
+}
+
+static void netc_onestep_skb_destructor(struct sk_buff *skb)
+{
+	struct netc_port *np = skb_shinfo(skb)->destructor_arg;
+
+	/* skb has been transmitted by hardware, schedule work to send
+	 * the next queued one-step Sync packet.
+	 */
+	schedule_work(&np->onestep_work);
+}
+
+static void netc_port_program_onestep(struct netc_port *np,
+				      struct sk_buff *skb,
+				      u64 tstamp)
+{
+	u16 correction_offset = NETC_SKB_CB(skb)->correction_offset;
+	u16 timestamp_offset = NETC_SKB_CB(skb)->timestamp_offset;
+	bool is_udp = NETC_SKB_CB(skb)->is_udp;
+	u8 *pkt_hdr = skb_mac_header(skb);
+	u64 sec;
+	u32 ns;
+
+	NETC_SKB_CB(skb)->tstamp = tstamp;
+	NETC_SKB_CB(skb)->ptp_flag = NETC_PTP_FLAG_ONESTEP;
+
+	/* Update originTimestamp field of Sync packet
+	 * - 48 bits seconds field
+	 * - 32 bits nanoseconds field
+	 */
+	sec = div_u64_rem(tstamp, NSEC_PER_SEC, &ns);
+	put_unaligned_be16((sec >> 32) & 0xffff, pkt_hdr + timestamp_offset);
+	put_unaligned_be32(sec & 0xffffffff, pkt_hdr + timestamp_offset + 2);
+	put_unaligned_be32(ns, pkt_hdr + timestamp_offset + 6);
+
+	netc_port_set_onestep_control(np, is_udp, correction_offset);
+
+	/* Orphan the skb to release the socket send buffer quota immediately.
+	 * This is safe because sock_wfree() only updates sk_wmem_alloc and
+	 * does not touch skb->data. After skb_orphan(), we install our own
+	 * destructor so that when the conduit driver frees the skb after TX
+	 * completion, we get notified to send the next queued Sync packet.
+	 */
+	skb_orphan(skb);
+	skb_shinfo(skb)->destructor_arg = np;
+	skb->destructor = netc_onestep_skb_destructor;
+}
+
+void netc_port_onestep_work(struct work_struct *work)
+{
+	struct netc_port *np = container_of(work, struct netc_port,
+					    onestep_work);
+	struct netc_switch *priv = np->switch_priv;
+	struct netc_tagger_data *tagger_data;
+	struct sk_buff *skb;
+	u64 tstamp;
+
+	/* Dequeue the next pending skb while still holding the in-flight slot,
+	 * so a newly arriving one-step Sync cannot jump ahead of it. Only
+	 * release the slot when the queue is empty. This keeps ordering and
+	 * closes the enqueue/wakeup race.
+	 */
+	spin_lock_bh(&np->ptp_lock);
+	skb = __skb_dequeue(&np->skb_onestep_queue);
+	if (!skb) {
+		__clear_bit(NETC_FLAG_ONESTEP_IN_PROGRESS, &np->flags);
+		spin_unlock_bh(&np->ptp_lock);
+		return;
+	}
+	spin_unlock_bh(&np->ptp_lock);
+
+	tstamp = netc_timer_get_current_time(priv->tmr_dev);
+	if (!tstamp) {
+		/* The PTP timer is not available, so there is no correct
+		 * timestamp to program. Drop this frame and re-kick to
+		 * process the remaining queued frames (or release the slot).
+		 *
+		 * netc_port_program_onestep() has not run for this skb yet, so
+		 * netc_onestep_skb_destructor() is not installed on it. Freeing
+		 * it therefore does not reschedule onestep_work, so the work
+		 * must be rescheduled explicitly to keep draining the queue.
+		 */
+		dev_dbg_ratelimited(priv->dev,
+				    "Port %d PTP timer unavailable, drop Sync\n",
+				    np->dp->index);
+		kfree_skb(skb);
+		schedule_work(&np->onestep_work);
+		return;
+	}
+
+	/* Reuse the offsets cached at enqueue time; only the timestamp is
+	 * read fresh so it reflects the actual TX moment.
+	 */
+	netc_port_program_onestep(np, skb, tstamp);
+
+	/* Tag and hand the frame directly to the conduit via the tagger,
+	 * bypassing dsa_user_xmit() so the TX stats are not counted twice.
+	 */
+	tagger_data = priv->ds->tagger_data;
+	tagger_data->onestep_sync_xmit(skb, np->dp->user);
+}
+
+struct sk_buff *netc_onestep_sync_handler(struct dsa_switch *ds, int port,
+					  struct sk_buff *skb)
+{
+	struct netc_port *np = NETC_PORT(ds, port);
+	struct netc_switch *priv = ds->priv;
+	u64 tstamp;
+
+	/* Serialize one-step Sync packets: only one can be in-flight at a
+	 * time because the SINGLE_STEP register is shared and must match the
+	 * packet currently being transmitted. Claim the in-flight slot under
+	 * ptp_lock. If another one-step Sync is already in-flight, queue this
+	 * skb and return NULL; ownership is transferred to the queue, so no
+	 * extra reference is needed and netc_xmit() stops processing it.
+	 */
+	spin_lock_bh(&np->ptp_lock);
+	if (test_bit(NETC_FLAG_ONESTEP_IN_PROGRESS, &np->flags)) {
+		__skb_queue_tail(&np->skb_onestep_queue, skb);
+		spin_unlock_bh(&np->ptp_lock);
+
+		return NULL;
+	}
+
+	tstamp = netc_timer_get_current_time(priv->tmr_dev);
+	if (!tstamp) {
+		spin_unlock_bh(&np->ptp_lock);
+
+		/* This is a valid one-step Sync frame, but the PTP timer is
+		 * not available, so there is no correct timestamp to program.
+		 * Drop the frame rather than transmit a Sync with a bogus
+		 * correction field.
+		 */
+		dev_dbg_ratelimited(priv->dev,
+				    "Port %d PTP timer unavailable, drop Sync\n",
+				    np->dp->index);
+		kfree_skb(skb);
+
+		return NULL;
+	}
+
+	__set_bit(NETC_FLAG_ONESTEP_IN_PROGRESS, &np->flags);
+	spin_unlock_bh(&np->ptp_lock);
+
+	/* We own the in-flight slot. Program the register and install the
+	 * destructor.
+	 */
+	netc_port_program_onestep(np, skb, tstamp);
+
+	return skb;
+}
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
index 61ecd12b5836..296df1d72333 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -82,6 +82,10 @@ enum netc_host_reason {
 	NETC_HR_PTP_TRAP   = 9,
 };
 
+enum netc_port_flags {
+	NETC_FLAG_ONESTEP_IN_PROGRESS = 0,
+};
+
 struct netc_port {
 	void __iomem *iobase;
 	struct netc_switch *switch_priv;
@@ -97,10 +101,14 @@ struct netc_port {
 	u16 pvid;
 	u32 ipft_hf_eid;
 
+	unsigned long flags;
 	/* Serialize PTP operations */
 	spinlock_t ptp_lock;
 	/* skb queue for two-step timestamp frames */
 	struct sk_buff_head skb_txtstamp_queue;
+	/* skb queue for one-step sync frames */
+	struct sk_buff_head skb_onestep_queue;
+	struct work_struct onestep_work;
 	int ptp_tx_type;
 	int ptp_rx_filter;
 	u32 ptp_ipft_eid[NETC_PTP_MAX];
@@ -207,6 +215,7 @@ static inline void netc_del_vlan_entry(struct netc_vlan_entry *entry)
 }
 
 int netc_switch_platform_probe(struct netc_switch *priv);
+void netc_mac_port_wr(struct netc_port *np, u32 reg, u32 val);
 
 /* ethtool APIs */
 void netc_port_get_pause_stats(struct dsa_switch *ds, int port,
@@ -237,5 +246,8 @@ bool netc_port_rxtstamp(struct dsa_switch *ds, int port,
 			struct sk_buff *skb, unsigned int type);
 void netc_port_txtstamp(struct dsa_switch *ds, int port_id,
 			struct sk_buff *skb);
+void netc_port_onestep_work(struct work_struct *work);
+struct sk_buff *netc_onestep_sync_handler(struct dsa_switch *ds, int port,
+					  struct sk_buff *skb);
 
 #endif
diff --git a/drivers/net/dsa/netc/netc_switch_hw.h b/drivers/net/dsa/netc/netc_switch_hw.h
index 1404ae41c7bc..37d1dd7ec2c7 100644
--- a/drivers/net/dsa/netc/netc_switch_hw.h
+++ b/drivers/net/dsa/netc/netc_switch_hw.h
@@ -203,6 +203,11 @@ enum netc_stg_stage {
 #define   SSP_10M			1
 #define   SSP_1G			2
 
+#define NETC_PM_SINGLE_STEP(a)		(0x10c0 + (a) * 0x400)
+#define  PM_SINGLE_STEP_CH		BIT(6)
+#define  PM_SINGLE_STEP_OFFSET		GENMASK(15, 7)
+#define  PM_SINGLE_STEP_EN		BIT(31)
+
 /* Port MAC 0/1 Receive Ethernet Octets Counter */
 #define NETC_PM_REOCT(a)		(0x1100 + (a) * 0x400)
 
diff --git a/include/linux/dsa/tag_netc.h b/include/linux/dsa/tag_netc.h
index f73dca5b5f24..229c70cb7a8a 100644
--- a/include/linux/dsa/tag_netc.h
+++ b/include/linux/dsa/tag_netc.h
@@ -11,6 +11,7 @@
 
 #define NETC_TAG_MAX_LEN			14
 #define NETC_TAG_TS_REQ_ID			GENMASK(3, 0)
+#define NETC_PTP_FLAG_ONESTEP			BIT(0)
 #define NETC_PTP_FLAG_TWOSTEP			BIT(1)
 
 struct netc_skb_cb {
@@ -19,6 +20,14 @@ struct netc_skb_cb {
 	u64 tstamp;
 	u8 ptp_flag;
 	u8 ts_req_id;
+	/* One-step Sync parsing results, computed in netc_port_txtstamp()
+	 * and reused in the tagger xmit path and the deferred work, to avoid
+	 * re-parsing the PTP header. Valid only while
+	 * ptp_flag == NETC_PTP_FLAG_ONESTEP.
+	 */
+	u16 correction_offset;
+	u16 timestamp_offset;
+	bool is_udp;
 };
 
 #define NETC_SKB_CB(skb)	((struct netc_skb_cb *)((skb)->cb))
@@ -28,10 +37,20 @@ struct netc_skb_cb {
  * @twostep_tstamp_handler: Called by the tagger when a two-step transmit
  *	timestamp response is received, to deliver the timestamp to the
  *	switch driver.
+ * @onestep_sync_handler: Called from the tagger xmit path for a one-step Sync
+ *	frame. Returns the skb if it can be transmitted immediately (the
+ *	in-flight slot has been claimed and PM_SINGLE_STEP programmed), or NULL
+ *	if the frame has been queued for deferred transmission or dropped.
+ * @onestep_sync_xmit: Called by the switch driver to transmit a deferred
+ *	one-step Sync frame directly to the conduit, bypassing dsa_user_xmit().
  */
 struct netc_tagger_data {
 	void (*twostep_tstamp_handler)(struct dsa_switch *ds, int port,
 				       u8 ts_req_id, u64 ts);
+	struct sk_buff *(*onestep_sync_handler)(struct dsa_switch *ds,
+						int port, struct sk_buff *skb);
+	netdev_tx_t (*onestep_sync_xmit)(struct sk_buff *skb,
+					 struct net_device *ndev);
 };
 
 #endif
diff --git a/net/dsa/tag_netc.c b/net/dsa/tag_netc.c
index 33c285c8d1ce..94cca20ffcda 100644
--- a/net/dsa/tag_netc.c
+++ b/net/dsa/tag_netc.c
@@ -16,6 +16,8 @@
 #define NETC_TAG_TO_PORT		1
 /* SubType0: No request to perform timestamping */
 #define NETC_TAG_TP_SUBTYPE0		0
+/* SubType1: Request to perform one-step timestamping */
+#define NETC_TAG_TP_SUBTYPE1		1
 /* SubType2: Request to perform two-step timestamping */
 #define NETC_TAG_TP_SUBTYPE2		2
 
@@ -31,6 +33,7 @@
 /* NETC switch tag lengths */
 #define NETC_TAG_FORWARD_LEN		6
 #define NETC_TAG_TP_SUBTYPE0_LEN	6
+#define NETC_TAG_TP_SUBTYPE1_LEN	10
 #define NETC_TAG_TP_SUBTYPE2_LEN	6
 #define NETC_TAG_TH_SUBTYPE0_LEN	6
 #define NETC_TAG_TH_SUBTYPE1_LEN	14
@@ -43,6 +46,7 @@
 #define NETC_TAG_IPV			GENMASK(4, 2)
 #define NETC_TAG_SWITCH			GENMASK(2, 0)
 #define NETC_TAG_PORT			GENMASK(7, 3)
+#define NETC_TAG_TIMESTAMP		GENMASK(29, 0)
 
 struct netc_tag_cmn {
 	__be16 tpid;
@@ -51,6 +55,12 @@ struct netc_tag_cmn {
 	u8 switch_port;
 } __packed;
 
+struct netc_tag_tp_subtype1 {
+	struct netc_tag_cmn cmn;
+	u8 resv;
+	__be32 timestamp;
+} __packed;
+
 struct netc_tag_tp_subtype2 {
 	struct netc_tag_cmn cmn;
 	u8 ts_req_id;
@@ -117,6 +127,17 @@ static void netc_fill_tp_tag_subtype0(struct sk_buff *skb,
 				NETC_TAG_TP_SUBTYPE0_LEN);
 }
 
+static void netc_fill_tp_tag_subtype1(struct sk_buff *skb,
+				      struct net_device *ndev)
+{
+	u32 ts = FIELD_PREP(NETC_TAG_TIMESTAMP, NETC_SKB_CB(skb)->tstamp);
+	struct netc_tag_tp_subtype1 *tag;
+
+	tag = netc_fill_common_tp_tag(skb, ndev, NETC_TAG_TP_SUBTYPE1,
+				      NETC_TAG_TP_SUBTYPE1_LEN);
+	tag->timestamp = htonl(ts);
+}
+
 static void netc_fill_tp_tag_subtype2(struct sk_buff *skb,
 				      struct net_device *ndev)
 {
@@ -129,6 +150,49 @@ static void netc_fill_tp_tag_subtype2(struct sk_buff *skb,
 	tag->ts_req_id = FIELD_PREP(NETC_TAG_TS_REQ_ID, ts_req_id);
 }
 
+static netdev_tx_t netc_onestep_sync_xmit(struct sk_buff *skb,
+					  struct net_device *dev)
+{
+	/* This deferred one-step Sync frame already went through
+	 * dsa_user_xmit()'s skb_ensure_writable_head_tail() and eth_skb_pad()
+	 * before it was queued in netc_xmit(), and nothing has cloned it or
+	 * shrunk its head/tail room since. So the head/tail room is still
+	 * guaranteed and the skb is still writable; only the tag needs to be
+	 * pushed before handing it directly to the conduit, bypassing
+	 * dsa_user_xmit() so that dev_sw_netstats_tx_add() is not invoked a
+	 * second time for the same frame.
+	 */
+	netc_fill_tp_tag_subtype1(skb, dev);
+
+	return dsa_enqueue_skb(skb, dev);
+}
+
+static struct sk_buff *netc_onestep_sync_process(struct sk_buff *skb,
+						 struct net_device *ndev)
+{
+	struct dsa_port *dp = dsa_user_to_port(ndev);
+	struct netc_tagger_data *tagger_data;
+	struct sk_buff *nskb;
+
+	tagger_data = dp->ds->tagger_data;
+	if (unlikely(!tagger_data->onestep_sync_handler)) {
+		kfree_skb(skb);
+		return NULL;
+	}
+
+	/* Decide, under ptp_lock, whether this one-step Sync can be sent
+	 * now or must be deferred. Returns the skb (with the in-flight
+	 * slot claimed and PM_SINGLE_STEP already programmed) for
+	 * immediate transmission, or NULL if it was queued for deferred
+	 * transmission or dropped.
+	 */
+	nskb = tagger_data->onestep_sync_handler(dp->ds, dp->index, skb);
+	if (unlikely(!nskb))
+		return NULL;
+
+	return nskb;
+}
+
 static struct sk_buff *netc_xmit(struct sk_buff *skb,
 				 struct net_device *ndev)
 {
@@ -138,6 +202,13 @@ static struct sk_buff *netc_xmit(struct sk_buff *skb,
 	if (likely(!ptp_flag)) {
 		netc_fill_tp_tag_subtype0(skb, ndev);
 		return skb;
+	}
+
+	if (ptp_flag == NETC_PTP_FLAG_ONESTEP) {
+		if (!netc_onestep_sync_process(skb, ndev))
+			return NULL;
+
+		netc_fill_tp_tag_subtype1(skb, ndev);
 	} else if (ptp_flag == NETC_PTP_FLAG_TWOSTEP) {
 		netc_fill_tp_tag_subtype2(skb, ndev);
 	} else {
@@ -291,6 +362,7 @@ static int netc_connect(struct dsa_switch *ds)
 	if (!tagger_data)
 		return -ENOMEM;
 
+	tagger_data->onestep_sync_xmit = netc_onestep_sync_xmit;
 	ds->tagger_data = tagger_data;
 
 	return 0;
-- 
2.34.1



      parent reply	other threads:[~2026-07-28 10:43 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-28 10:45 [PATCH net-next 0/7] net: dsa: netc: add PTP support for NETC switch wei.fang
2026-07-28 10:45 ` [PATCH net-next 1/7] ptp: netc: use ioread64_lo_hi/iowrite64_lo_hi for 64-bit register access wei.fang
2026-07-28 10:45 ` [PATCH net-next 2/7] ptp: netc: remove unnecessary pcie_flr() call in probe wei.fang
2026-07-28 10:45 ` [PATCH net-next 3/7] ptp: netc: export netc_timer_get_current_time() for cross-driver use wei.fang
2026-07-28 10:45 ` [PATCH net-next 4/7] net: dsa: netc: use entry ID instead of pointer to track host flood rule wei.fang
2026-07-28 10:45 ` [PATCH net-next 5/7] net: dsa: netc: enable ingress port filtering lookup by default wei.fang
2026-07-28 10:45 ` [PATCH net-next 6/7] net: dsa: netc: add PTP two-step timestamping support wei.fang
2026-07-28 10:45 ` wei.fang [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260728104548.3301214-8-wei.fang@oss.nxp.com \
    --to=wei.fang@oss.nxp.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=andrew@lunn.ch \
    --cc=chleroy@kernel.org \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=imx@lists.linux.dev \
    --cc=kuba@kernel.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linuxppc-dev@lists.ozlabs.org \
    --cc=netdev@vger.kernel.org \
    --cc=olteanv@gmail.com \
    --cc=pabeni@redhat.com \
    --cc=richardcochran@gmail.com \
    --cc=vladimir.oltean@nxp.com \
    --cc=wei.fang@nxp.com \
    --cc=xiaoning.wang@nxp.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox