Hi all, I accidentally included this patch in the series ― it was left over in my local branch and should not have been sent. Please disregard it.
Sorry for the noise. Best Regards, Wei Fang > -----Original Message----- > From: Wei Fang (OSS) <[email protected]> > Sent: 2026年8月8日 11:22 > To: Clark Wang <[email protected]>; [email protected]; > [email protected]; [email protected]; [email protected]; > [email protected]; [email protected]; [email protected]; > [email protected]; [email protected] > Cc: Wei Fang <[email protected]>; [email protected]; > [email protected]; [email protected]; > [email protected]; [email protected] > Subject: [PATCH] net: dsa: netc: add PTP one-step timestamping support > > 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 | 410 > +++++++++++++++++++++++++- > drivers/net/dsa/netc/netc_switch.h | 48 +++ > drivers/net/dsa/netc/netc_switch_hw.h | 5 + > include/linux/dsa/tag_netc.h | 21 ++ > net/dsa/tag_netc.c | 68 +++++ > 6 files changed, 605 insertions(+), 8 deletions(-) > > diff --git a/drivers/net/dsa/netc/netc_main.c > b/drivers/net/dsa/netc/netc_main.c > index 4e139ffc2f76..967f20a94d99 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..881b07b616ca 100644 > --- a/drivers/net/dsa/netc/netc_ptp.c > +++ b/drivers/net/dsa/netc/netc_ptp.c > @@ -4,14 +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) > > +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) > { > /* Initialize to invalid entry IDs */ > @@ -21,7 +277,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 +301,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 +519,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 +551,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 +590,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 +716,93 @@ 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))) > + return; > + > + ptp_hdr = ptp_parse_header(skb, ptp_class); > + if (unlikely(!ptp_hdr)) { > + 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. > + */ > + if (unlikely(tstamp_offset + 10 > skb_headlen(skb))) { > + dev_dbg_ratelimited(priv->dev, > + "Port %d Sync header not in linear area\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)) { > + 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 +810,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..8aeb865cecab 100644 > --- a/include/linux/dsa/tag_netc.h > +++ b/include/linux/dsa/tag_netc.h > @@ -10,6 +10,7 @@ > #include <net/dsa.h> > > #define NETC_TAG_MAX_LEN 14 > +#define NETC_PTP_FLAG_ONESTEP BIT(0) > #define NETC_PTP_FLAG_TWOSTEP BIT(1) > > struct netc_skb_cb { > @@ -17,6 +18,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)) > @@ -26,10 +35,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..0c36aeaade6a 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,6 +195,16 @@ 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 { > @@ -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
