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


Reply via email to