[PATCH v2 net-next 7/7] net: dsa: netc: add PTP one-step timestamping support

[email protected]
Newsgroups gmane.linux.ports.ppc.embedded,gmane.linux.network,gmane.linux.kernel,gmane.linux.ports.arm.kernel
Message-ID <[email protected]>
From: Wei Fang <[email protected]>

The NETC switch supports one-step TX timestamping for PTP Sync frames.
The MAC captures the SFD transmit time, adds the residence time to the
correction field at the offset given by PM_SINGLE_STEP[OFFSET], and
writes the result back before the frame leaves the wire. The software
timestamp (low 30 bits of the PTP Timer value) is carried in the
To_Port SubType 1 tag.

PM_SINGLE_STEP is a per-port register that can describe only one
in-flight frame at a time, and programming it requires reading the
current PTP time, which may sleep. Both constraints rule out handling
one-step Sync on the xmit path.

Instead, defer transmission to a per-port process-context work. The
xmit path classifies the frame in netc_port_txtstamp(): a genuine
one-step Sync (twoStepFlag cleared) has its PTP header offsets cached
in the skb control block; frames that cannot be handled as one-step
fall back to the two-step path or are sent as normal frames.
netc_xmit() hands the classified frame to the switch driver via the
onestep_sync_enqueue tagger callback, which queues it and kicks the
work if no frame is currently in flight.

The work dequeues one frame at a time, reads a fresh PTP time,
programs PM_SINGLE_STEP, updates the originTimestamp field, and
transmits the frame directly to the conduit via the onestep_sync_xmit
tagger callback, bypassing dsa_user_xmit() to avoid double-counting
TX stats. Only one frame is in flight at a time: the frame carries a
TX-completion destructor that reschedules the work when the conduit
frees the skb, keeping PM_SINGLE_STEP always matched to the frame
being transmitted.

The one-step context is reference-counted and its lifetime is decoupled
from the devm-allocated netc_port. In-flight skbs hold a reference via
their destructor, so the context outlives port disable until the conduit
frees the last in-flight skb. Port disable clears @active and purges the
queue under work_lock; a work that runs afterwards observes @active
cleared and returns without touching the freed port resources.

Assisted-by: Wchat:claude-opus-4-8
Signed-off-by: Wei Fang <[email protected]>
---
 drivers/net/dsa/netc/netc_main.c      |  61 +++-
 drivers/net/dsa/netc/netc_ptp.c       | 418 +++++++++++++++++++++++++-
 drivers/net/dsa/netc/netc_switch.h    |  48 +++
 drivers/net/dsa/netc/netc_switch_hw.h |   5 +
 include/linux/dsa/tag_netc.h          |  22 ++
 net/dsa/tag_netc.c                    |  70 ++++-
 6 files changed, 615 insertions(+), 9 deletions(-)

diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index 4e139ffc2f76..55664045ba19 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_enqueue = netc_port_onestep_sync_enqueue;
 	tagger_data->twostep_tstamp_handler = netc_port_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;
@@ -252,6 +253,21 @@ static void netc_get_switch_capabilities(struct netc_switch *priv)
 	priv->num_bp = FIELD_GET(BPCAPR_NUM_BP, val);
 }
 
+static void netc_free_user_ports(struct netc_switch *priv)
+{
+	struct dsa_switch *ds = priv->ds;
+	struct dsa_port *dp;
+
+	dsa_switch_for_each_user_port(dp, ds) {
+		struct netc_port *np = NETC_PORT(ds, dp->index);
+
+		if (np->onestep) {
+			netc_onestep_put(np->onestep);
+			np->onestep = NULL;
+		}
+	}
+}
+
 static int netc_init_all_ports(struct netc_switch *priv)
 {
 	struct device *dev = priv->dev;
@@ -292,13 +308,13 @@ static int netc_init_all_ports(struct netc_switch *priv)
 
 		err = netc_port_get_info_from_dt(np, dp->dn, dev);
 		if (err)
-			return err;
+			goto free_user_ports;
 
 		if (dsa_port_is_user(dp)) {
 			err = netc_port_create_mdio_bus(np, dp->dn);
 			if (err) {
 				dev_err(dev, "Failed to create MDIO bus\n");
-				return err;
+				goto free_user_ports;
 			}
 
 			/* The ipft_hf_eid is initialized to an invalid entry
@@ -314,11 +330,16 @@ static int netc_init_all_ports(struct netc_switch *priv)
 			 */
 			err = netc_port_ptp_init(np);
 			if (err)
-				return err;
+				goto free_user_ports;
 		}
 	}
 
 	return 0;
+
+free_user_ports:
+	netc_free_user_ports(priv);
+
+	return err;
 }
 
 static void netc_init_ntmp_tbl_versions(struct netc_switch *priv)
@@ -941,7 +962,7 @@ static int netc_setup(struct dsa_switch *ds)
 
 	err = netc_init_ntmp_user(priv);
 	if (err)
-		goto put_ptp_timer;
+		goto free_user_ports;
 
 	INIT_HLIST_HEAD(&priv->fdb_list);
 	mutex_init(&priv->fdbt_lock);
@@ -980,6 +1001,8 @@ static int netc_setup(struct dsa_switch *ds)
 	mutex_destroy(&priv->fdbt_lock);
 	mutex_destroy(&priv->vft_lock);
 	netc_free_ntmp_user(priv);
+free_user_ports:
+	netc_free_user_ports(priv);
 put_ptp_timer:
 	pci_dev_put(priv->tmr_dev);
 
@@ -1005,6 +1028,19 @@ static void netc_free_ports_resources(struct netc_switch *priv)
 			continue;
 
 		netc_port_purge_txtstamp_queue(np);
+
+		/* dsa_tree_teardown() calls dsa_tree_teardown_ports() before
+		 * dsa_tree_teardown_switches(), so netc_port_disable() is
+		 * executed before netc_teardown() and purges onestep->queue,
+		 * so here we only need to drop the port's owner reference.
+		 * In-flight one-step skbs still hold references via the
+		 * destructor; the context (and its work) is freed only after
+		 * the conduit frees the last in-flight skb. By then np may
+		 * be gone, but the work no longer dereferences np because
+		 * onestep->active has been cleared.
+		 */
+		netc_onestep_put(np->onestep);
+		np->onestep = NULL;
 	}
 }
 
@@ -1559,6 +1595,7 @@ static int netc_port_enable(struct dsa_switch *ds, int port,
 			    struct phy_device *phy)
 {
 	struct netc_port *np = NETC_PORT(ds, port);
+	struct netc_onestep *onestep = np->onestep;
 	int err;
 
 	if (np->enable)
@@ -1571,6 +1608,12 @@ static int netc_port_enable(struct dsa_switch *ds, int port,
 		return err;
 	}
 
+	if (onestep) {
+		mutex_lock(&onestep->work_lock);
+		onestep->active = true;
+		mutex_unlock(&onestep->work_lock);
+	}
+
 	np->enable = true;
 
 	return 0;
@@ -1579,6 +1622,7 @@ static int netc_port_enable(struct dsa_switch *ds, int port,
 static void netc_port_disable(struct dsa_switch *ds, int port)
 {
 	struct netc_port *np = NETC_PORT(ds, port);
+	struct netc_onestep *onestep = np->onestep;
 
 	/* When .port_disable() is called, .port_enable() may not have been
 	 * called. In this case, both the prepare_count and enable_count of
@@ -1588,6 +1632,13 @@ static void netc_port_disable(struct dsa_switch *ds, int port)
 	if (!np->enable)
 		return;
 
+	if (onestep) {
+		mutex_lock(&onestep->work_lock);
+		onestep->active = false;
+		netc_port_purge_onestep_queue(onestep, true);
+		mutex_unlock(&onestep->work_lock);
+	}
+
 	clk_disable_unprepare(np->ref_clk);
 	np->enable = false;
 }
diff --git a/drivers/net/dsa/netc/netc_ptp.c b/drivers/net/dsa/netc/netc_ptp.c
index 1384a6f31d1c..d0423b3c8c33 100644
--- a/drivers/net/dsa/netc/netc_ptp.c
+++ b/drivers/net/dsa/netc/netc_ptp.c
@@ -4,13 +4,270 @@
  * Copyright 2025-2026 NXP
  */
 
+#include <linux/kref.h>
 #include <linux/ptp_classify.h>
 #include <linux/ptp_clock_kernel.h>
+#include <linux/slab.h>
 
 #include "netc_switch.h"
 
 #define NETC_NUM_TS_REQ_ID		16
 #define NETC_TXTSTAMP_TIMEOUT		(5 * HZ)
+#define NETC_MAX_STEP_OFFSET		0x1ff
+
+static void netc_port_set_onestep_control(struct netc_port *np,
+					  bool csum_update, int offset)
+{
+	u32 val;
+
+	val = PM_SINGLE_STEP_EN | FIELD_PREP(PM_SINGLE_STEP_OFFSET, offset);
+	if (csum_update)
+		val |= PM_SINGLE_STEP_CH;
+	netc_mac_port_wr(np, NETC_PM_SINGLE_STEP(0), val);
+}
+
+static void netc_onestep_destroy_work(struct work_struct *work)
+{
+	struct netc_onestep *onestep = container_of(work, struct netc_onestep,
+						    destroy_work);
+
+	/* refcnt reaching zero does not by itself mean onestep->work has
+	 * stopped: the last in-flight skb destructor calls schedule_work(&work)
+	 * *before* the netc_onestep_put() that drops the final reference, so at
+	 * the moment refcnt hits zero onestep->work may still be pending or
+	 * running on another CPU. destroy_work and work are distinct work_structs
+	 * and can run concurrently, so cancel_work_sync() is required to drain
+	 * onestep->work before mutex_destroy()/kfree() below, otherwise a
+	 * still-running work would touch freed memory. No new schedule_work(&work)
+	 * can occur after this point because no references remain, so this
+	 * cancel is final.
+	 */
+	cancel_work_sync(&onestep->work);
+	mutex_destroy(&onestep->work_lock);
+	kfree(onestep);
+}
+
+static void netc_onestep_release(struct kref *ref)
+{
+	struct netc_onestep *onestep = container_of(ref, struct netc_onestep,
+						    refcnt);
+
+	/* This may be called from the skb destructor in softirq context
+	 * (napi_consume_skb()), where cancel_work_sync() must not be used.
+	 * Defer the final teardown to process context.
+	 */
+	schedule_work(&onestep->destroy_work);
+}
+
+static void netc_onestep_get(struct netc_onestep *onestep)
+{
+	kref_get(&onestep->refcnt);
+}
+
+void netc_onestep_put(struct netc_onestep *onestep)
+{
+	kref_put(&onestep->refcnt, netc_onestep_release);
+}
+
+static void netc_onestep_skb_destructor(struct sk_buff *skb)
+{
+	struct netc_onestep *onestep = skb_shinfo(skb)->destructor_arg;
+
+	/* skb has been transmitted by hardware. Schedule work to send the next
+	 * queued one-step Sync packet, then release this skb's reference on the
+	 * context. If the port has already been torn down and this is the last
+	 * reference, the context is freed via netc_onestep_release().
+	 */
+	schedule_work(&onestep->work);
+	netc_onestep_put(onestep);
+}
+
+static void netc_port_program_onestep(struct netc_port *np,
+				      struct netc_onestep *onestep,
+				      struct sk_buff *skb,
+				      u64 tstamp)
+{
+	u16 correction_offset = NETC_SKB_CB(skb)->correction_offset;
+	u16 tstamp_offset = NETC_SKB_CB(skb)->timestamp_offset;
+	u8 *hdr = skb_mac_header(skb);
+	bool csum_update = false;
+	__be32 new_sec_l, new_ns;
+	__be16 new_sec_h;
+	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);
+	new_sec_h = htons((sec >> 32) & 0xffff);
+	new_sec_l = htonl(sec & 0xffffffff);
+	new_ns = htonl(ns);
+
+	if (NETC_SKB_CB(skb)->is_udp) {
+		__be32 old_sec_l, old_ns;
+		struct udphdr *uh;
+		__be16 old_sec_h;
+
+		if (skb->ip_summed == CHECKSUM_PARTIAL) {
+			csum_update = true;
+			goto update_timestamp;
+		}
+
+		if (unlikely(!skb_transport_header_was_set(skb)))
+			uh = (struct udphdr *)(hdr + tstamp_offset -
+					       sizeof(struct ptp_header) -
+					       sizeof(struct udphdr));
+		else
+			uh = udp_hdr(skb);
+
+		/* For IPv4, a UDP checksum of zero on the wire means "no
+		 * checksum". For IPv6, its UDP checksum is mandatory and
+		 * never zero.
+		 */
+		if (!uh->check)
+			goto update_timestamp;
+
+		old_sec_h = __get_unaligned_t(__be16, hdr + tstamp_offset);
+		old_sec_l = __get_unaligned_t(__be32, hdr + tstamp_offset + 2);
+		old_ns = __get_unaligned_t(__be32, hdr + tstamp_offset + 6);
+		inet_proto_csum_replace2(&uh->check, skb, old_sec_h,
+					 new_sec_h, false);
+		inet_proto_csum_replace4(&uh->check, skb, old_sec_l,
+					 new_sec_l, false);
+		inet_proto_csum_replace4(&uh->check, skb, old_ns,
+					 new_ns, false);
+		csum_update = true;
+	}
+
+update_timestamp:
+	__put_unaligned_t(__be16, new_sec_h, hdr + tstamp_offset);
+	__put_unaligned_t(__be32, new_sec_l, hdr + tstamp_offset + 2);
+	__put_unaligned_t(__be32, new_ns, hdr + tstamp_offset + 6);
+
+	netc_port_set_onestep_control(np, csum_update, correction_offset);
+
+	/* Orphan the skb to release the socket send buffer quota immediately.
+	 * This is safe because sock_wfree() does not access skb->data or any
+	 * frame content. 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);
+	netc_onestep_get(onestep); /* in-flight reference */
+	skb_shinfo(skb)->destructor_arg = onestep;
+	skb->destructor = netc_onestep_skb_destructor;
+}
+
+static u64 netc_get_phc_time(struct netc_switch *priv)
+{
+	if (unlikely(!priv->tmr_dev))
+		return 0;
+
+	return netc_timer_get_current_time(priv->tmr_dev);
+}
+
+void netc_port_onestep_work(struct work_struct *work)
+{
+	struct netc_onestep *onestep = container_of(work, struct netc_onestep,
+						    work);
+	struct netc_tagger_data *tagger_data;
+	struct netc_switch *priv;
+	struct netc_port *np;
+	struct sk_buff *skb;
+	u64 tstamp;
+
+	/* Serialize the whole hardware access against port disable. work_lock
+	 * is a mutex (this runs in process context and netc_get_phc_time() may
+	 * sleep). If the port has been disabled, bail out immediately; np and
+	 * priv are only dereferenced after the @active check passes, so they
+	 * are always valid here.
+	 */
+	mutex_lock(&onestep->work_lock);
+	if (unlikely(!onestep->active)) {
+		netc_port_purge_onestep_queue(onestep, true);
+		goto unlock_work;
+	}
+
+	/* Send only one queued Sync per run. The shared SINGLE_STEP register
+	 * must match the frame currently being transmitted, so the next frame
+	 * is programmed only after this one completes TX, when its skb
+	 * destructor reschedules this work. Dequeue under onestep->queue_lock,
+	 * and if the queue has drained, release the in-flight slot so a later
+	 * frame from the xmit path kicks the work again.
+	 */
+	spin_lock_bh(&onestep->queue_lock);
+	skb = __skb_dequeue(&onestep->queue);
+	if (!skb) {
+		onestep->in_flight = false;
+		spin_unlock_bh(&onestep->queue_lock);
+		goto unlock_work;
+	}
+	spin_unlock_bh(&onestep->queue_lock);
+
+	np = onestep->np;
+	priv = np->switch_priv;
+	tstamp = netc_get_phc_time(priv);
+	if (unlikely(!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.
+		 *
+		 * 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 the 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(&onestep->work);
+		goto unlock_work;
+	}
+
+	/* 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, onestep, 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.
+	 * And there is no need to check if tagger_data is NULL, because
+	 * dsa_tree_teardown_ports() executes before
+	 * dsa_switch_teardown_tag_protocol(), so tagger_data cannot be
+	 * NULL when onestep->active is set.
+	 */
+	tagger_data = priv->ds->tagger_data;
+	tagger_data->onestep_sync_xmit(skb, np->dp->user);
+
+unlock_work:
+	mutex_unlock(&onestep->work_lock);
+}
+
+static int netc_port_onestep_alloc(struct netc_port *np)
+{
+	struct netc_onestep *onestep;
+
+	onestep = kzalloc_obj(*onestep);
+	if (!onestep)
+		return -ENOMEM;
+
+	kref_init(&onestep->refcnt); /* port (owner) reference */
+	np->onestep = onestep;
+	onestep->np = np;
+	mutex_init(&onestep->work_lock);
+	spin_lock_init(&onestep->queue_lock);
+	__skb_queue_head_init(&onestep->queue);
+	INIT_WORK(&onestep->work, netc_port_onestep_work);
+	INIT_WORK(&onestep->destroy_work, netc_onestep_destroy_work);
+
+	return 0;
+}
 
 int netc_port_ptp_init(struct netc_port *np)
 {
@@ -21,7 +278,7 @@ int netc_port_ptp_init(struct netc_port *np)
 	spin_lock_init(&np->tstamp_lock);
 	__skb_queue_head_init(&np->skb_txtstamp_queue);
 
-	return 0;
+	return netc_port_onestep_alloc(np);
 }
 
 static int netc_get_phc_index(struct netc_switch *priv)
@@ -45,7 +302,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) |
@@ -262,6 +520,22 @@ void netc_port_purge_txtstamp_queue(struct netc_port *np)
 	__skb_queue_purge(&free_list);
 }
 
+void netc_port_purge_onestep_queue(struct netc_onestep *onestep,
+				   bool clear_flight)
+{
+	struct sk_buff_head free_list;
+
+	__skb_queue_head_init(&free_list);
+
+	spin_lock_bh(&onestep->queue_lock);
+	skb_queue_splice_init(&onestep->queue, &free_list);
+	if (clear_flight)
+		onestep->in_flight = false;
+	spin_unlock_bh(&onestep->queue_lock);
+
+	__skb_queue_purge(&free_list);
+}
+
 int netc_port_hwtstamp_set(struct dsa_switch *ds, int port,
 			   struct kernel_hwtstamp_config *config,
 			   struct netlink_ext_ack *extack)
@@ -278,6 +552,7 @@ int netc_port_hwtstamp_set(struct dsa_switch *ds, int port,
 	switch (config->tx_type) {
 	case HWTSTAMP_TX_ON:
 	case HWTSTAMP_TX_OFF:
+	case HWTSTAMP_TX_ONESTEP_SYNC:
 		break;
 	default:
 		return -ERANGE;
@@ -316,6 +591,9 @@ int netc_port_hwtstamp_set(struct dsa_switch *ds, int port,
 	if (config->tx_type == HWTSTAMP_TX_OFF)
 		netc_port_purge_txtstamp_queue(np);
 
+	if (config->tx_type != HWTSTAMP_TX_ONESTEP_SYNC)
+		netc_port_purge_onestep_queue(np->onestep, false);
+
 	config->rx_filter = rx_filter;
 
 	return 0;
@@ -439,9 +717,100 @@ bool netc_port_rxtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb,
 	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, tstamp_offset;
+	struct ptp_header *ptp_hdr;
+	u8 msg_type, twostep_flag;
+	bool is_udp = false;
+	u32 pkt_type;
+	u8 *pkt_hdr;
+
+	if (unlikely(skb_linearize(skb))) {
+		NETC_SKB_CB(skb)->ptp_flag = NETC_PTP_FLAG_DROP;
+		return;
+	}
+
+	ptp_hdr = ptp_parse_header(skb, ptp_class);
+	if (unlikely(!ptp_hdr)) {
+		NETC_SKB_CB(skb)->ptp_flag = NETC_PTP_FLAG_DROP;
+		dev_dbg_ratelimited(priv->dev,
+				    "Port %d failed to parse Sync header\n",
+				    np->dp->index);
+		return;
+	}
+
+	msg_type = ptp_get_msgtype(ptp_hdr, ptp_class);
+	twostep_flag = ptp_hdr->flag_field[0] & 0x2;
+
+	pkt_hdr = skb_mac_header(skb);
+	correction_offset = (u8 *)&ptp_hdr->correction - pkt_hdr;
+	tstamp_offset = (u8 *)ptp_hdr + sizeof(*ptp_hdr) - pkt_hdr;
+
+	/* Ensure that the entire originTimestamp field is present in the
+	 * linear buffer of the skb and the correction_offset must be within
+	 * the hardware capability.
+	 */
+	if (unlikely(tstamp_offset + 10 > skb_headlen(skb) ||
+		     correction_offset > NETC_MAX_STEP_OFFSET)) {
+		NETC_SKB_CB(skb)->ptp_flag = NETC_PTP_FLAG_DROP;
+		dev_dbg_ratelimited(priv->dev,
+				    "Port %d PTP offset check error\n",
+				    np->dp->index);
+		return;
+	}
+
+	/* 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 np->onestep 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)) {
+		NETC_SKB_CB(skb)->ptp_flag = NETC_PTP_FLAG_DROP;
+		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 = tstamp_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);
+	int tx_type = READ_ONCE(np->ptp_tx_type);
+	bool twostep = false;
 	u32 ptp_class;
 
 	NETC_SKB_CB(skb)->ptp_flag = 0;
@@ -449,6 +818,49 @@ void netc_port_txtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb)
 	if (ptp_class == PTP_CLASS_NONE)
 		return;
 
-	if (READ_ONCE(np->ptp_tx_type) == HWTSTAMP_TX_ON)
+	if (tx_type == HWTSTAMP_TX_ONESTEP_SYNC)
+		netc_port_prepare_onestep_sync(np, skb, ptp_class, &twostep);
+
+	if (tx_type == HWTSTAMP_TX_ON || twostep)
 		netc_port_txtstamp_twostep(np, skb);
 }
+
+void netc_port_onestep_sync_enqueue(struct dsa_switch *ds, int port,
+				    struct sk_buff *skb)
+{
+	struct netc_port *np = NETC_PORT(ds, port);
+	struct netc_onestep *onestep = np->onestep;
+	bool kick = false;
+
+	/* This runs in the xmit path (softirq / BH-disabled), so it must not
+	 * sleep: only queue the frame here and let netc_port_onestep_work()
+	 * program the SINGLE_STEP register and transmit it from process
+	 * context. The shared SINGLE_STEP register can describe only one frame
+	 * at a time, so at most one one-step Sync may be in flight. Track that
+	 * with @in_flight under onestep->queue_lock.
+	 *
+	 * Enqueue the frame and, only if no frame is currently in flight, claim
+	 * the in-flight slot and kick the work. When a frame is already in
+	 * flight, just queue: its skb destructor will kick the work to send the
+	 * next one once it completes TX, so the frames are transmitted strictly
+	 * one at a time in order.
+	 *
+	 * PTP Sync frames are periodic, low-rate control-plane frames and only
+	 * reach this TX path when the local socket requested hardware TX
+	 * timestamping on a one-step port, so the queue cannot be flooded and
+	 * needs no depth cap.
+	 */
+	spin_lock_bh(&onestep->queue_lock);
+	__skb_queue_tail(&onestep->queue, skb);
+	if (!onestep->in_flight) {
+		onestep->in_flight = true;
+		kick = true;
+	}
+	spin_unlock_bh(&onestep->queue_lock);
+
+	/* Ownership is transferred to the queue; netc_xmit() stops processing
+	 * this skb. The work will program and transmit it.
+	 */
+	if (kick)
+		schedule_work(&onestep->work);
+}
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
index 86fd15889733..98d4842441df 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -9,6 +9,7 @@
 #include <linux/dsa/tag_netc.h>
 #include <linux/fsl/netc_global.h>
 #include <linux/fsl/ntmp.h>
+#include <linux/mutex.h>
 #include <linux/of_device.h>
 #include <linux/of_net.h>
 #include <linux/pci.h>
@@ -87,6 +88,44 @@ enum netc_host_reason {
 	NETC_HR_PTP_TRAP   = 9,
 };
 
+/* One-step Sync serialization context.
+ *
+ * Its lifetime is decoupled from the devm-allocated netc_port. An in-flight
+ * one-step Sync skb keeps a reference on this context via its skb destructor,
+ * so the context outlives the port teardown until the conduit frees the last
+ * in-flight skb after TX completion. Once the port is disabled, @active is
+ * cleared and the work stops touching any devm memory (netc_port/netc_switch)
+ * or the unregistered user netdev or the tagger_data.
+ */
+struct netc_onestep {
+	struct netc_port *np;
+	struct kref refcnt;
+	/* Process-context lock: serializes the deferred TX work against port
+	 * teardown, so the work never touches the devm-allocated netc_port /
+	 * netc_switch or the unregistered user netdev after teardown. Held
+	 * across netc_get_phc_time(), which may sleep, hence a mutex.
+	 */
+	struct mutex work_lock;
+	/* Serialize access to in_flight and queue */
+	spinlock_t queue_lock;
+	bool active;	/* set when port is enabled, under @work_lock */
+	/* In-flight slot: true while one one-step Sync frame is programmed
+	 * into the shared SINGLE_STEP register and being transmitted. Only one
+	 * frame may be in flight at a time, so the next queued frame is sent
+	 * only after the current one completes TX (its skb destructor kicks
+	 * the work). Accessed under queue_lock, from both the softirq xmit
+	 * path and the process-context work.
+	 */
+	bool in_flight;
+	/* Pending one-step Sync frames. Enqueued from the softirq xmit path and
+	 * dequeued by the process-context work; the list is serialized by
+	 * queue_lock together with @in_flight.
+	 */
+	struct sk_buff_head queue;
+	struct work_struct work;	/* drains @queue */
+	struct work_struct destroy_work; /* frees the context in process ctx */
+};
+
 struct netc_port {
 	void __iomem *iobase;
 	struct netc_switch *switch_priv;
@@ -106,6 +145,8 @@ struct netc_port {
 	spinlock_t tstamp_lock;
 	/* skb queue for two-step timestamp frames */
 	struct sk_buff_head skb_txtstamp_queue;
+	/* one-step Sync serialization context (ref-counted, kzalloc'd) */
+	struct netc_onestep *onestep;
 	int ptp_tx_type;
 	int ptp_rx_filter;
 	u32 ptp_ipft_eid[NETC_PTP_MAX];
@@ -212,6 +253,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,
@@ -243,5 +285,11 @@ void netc_port_twostep_tstamp_handler(struct dsa_switch *ds, int port,
 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, struct sk_buff *skb);
+void netc_onestep_put(struct netc_onestep *onestep);
+void netc_port_purge_onestep_queue(struct netc_onestep *onestep,
+				   bool clear_flight);
+void netc_port_onestep_work(struct work_struct *work);
+void netc_port_onestep_sync_enqueue(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 da200e3ba8ad..5ac5e2e72dff 100644
--- a/include/linux/dsa/tag_netc.h
+++ b/include/linux/dsa/tag_netc.h
@@ -10,13 +10,23 @@
 #include <net/dsa.h>
 
 #define NETC_TAG_MAX_LEN			14
+#define NETC_PTP_FLAG_ONESTEP			BIT(0)
 #define NETC_PTP_FLAG_TWOSTEP			BIT(1)
+#define NETC_PTP_FLAG_DROP			BIT(2)
 
 struct netc_skb_cb {
 	unsigned long ptp_tx_time;
 	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))
@@ -26,10 +36,22 @@ 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_enqueue: Called from the tagger xmit path for a one-step Sync
+ *	frame. The switch driver takes ownership of the skb and queues it for
+ *	deferred transmission from process context, where the shared
+ *	PM_SINGLE_STEP register can be programmed and the PTP timer read
+ *	(which may sleep). The tagger must not touch the skb after this call
+ *	and returns NULL to dsa_user_xmit().
+ * @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);
+	void (*onestep_sync_enqueue)(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 9f9a61d8133b..91548fb7ed1b 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
@@ -44,6 +47,7 @@
 #define NETC_TAG_SWITCH			GENMASK(2, 0)
 #define NETC_TAG_PORT			GENMASK(7, 3)
 #define NETC_TAG_TS_REQ_ID		GENMASK(3, 0)
+#define NETC_TAG_TIMESTAMP		GENMASK(29, 0)
 
 struct netc_tag_cmn {
 	__be16 tpid;
@@ -52,6 +56,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;
@@ -118,6 +128,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,42 @@ 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 void netc_onestep_sync_enqueue(struct sk_buff *skb,
+				      struct net_device *ndev)
+{
+	struct dsa_port *dp = dsa_user_to_port(ndev);
+	struct netc_tagger_data *tagger_data;
+
+	tagger_data = dp->ds->tagger_data;
+	if (unlikely(!tagger_data->onestep_sync_enqueue)) {
+		kfree_skb(skb);
+		return;
+	}
+
+	/* Hand the one-step Sync to the switch driver, which takes ownership
+	 * and queues it for deferred transmission from its work. The tagger
+	 * must not touch the skb after this point.
+	 */
+	tagger_data->onestep_sync_enqueue(dp->ds, dp->index, skb);
+}
+
 static struct sk_buff *netc_xmit(struct sk_buff *skb,
 				 struct net_device *ndev)
 {
@@ -138,9 +195,19 @@ 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) {
+		/* The switch driver takes ownership of the one-step Sync and
+		 * queues it for deferred TX; the deferred work tags it subtype 1
+		 * and transmits it directly to the conduit. Return NULL so
+		 * dsa_user_xmit() stops processing this skb.
+		 */
+		netc_onestep_sync_enqueue(skb, ndev);
+		return NULL;
 	} else if (ptp_flag == NETC_PTP_FLAG_TWOSTEP) {
 		netc_fill_tp_tag_subtype2(skb, ndev);
-	} else {
+	} else { /* NETC_PTP_FLAG_DROP */
 		kfree_skb(skb);
 		return NULL;
 	}
@@ -317,6 +384,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
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.