* [PATCH net] net: lan966x: release the in-flight slot of a stale TX skb
@ 2026-10-26 13:46 Daniel Machon
2026-10-27 13:48 ` netdev-bot+sashiko
2026-10-27 22:30 ` patchwork-bot+netdevbpf
0 siblings, 2 replies; 4+ messages in thread
From: Daniel Machon @ 2026-10-26 13:46 UTC (permalink / raw)
To: Horatiu Vultur, UNGLinuxDriver, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Richard Cochran
Cc: netdev, linux-kernel
lan966x_ptp_txtstamp_request() counts every two step TX skb waiting for
its timestamp in lan966x->ptp_skbs, and refuses new ones once
LAN966X_MAX_PTP_ID are in flight. lan966x_ptp_txtstamp_old_release()
frees the skbs that have waited longer than LAN966X_PTP_TIMEOUT.
Before this patch, old_release() does not decrement ptp_skbs, so every
skb whose timestamp never arrives, for example because the switch
discarded the frame, keeps its slot forever. Once LAN966X_MAX_PTP_ID
slots are lost, two step frames fail to transmit on every port of the
switch.
Return the number of released skbs from old_release() and subtract it
from ptp_skbs in txtstamp_request(), under the ptp_ts_id_lock it already
takes.
This fixes the accounting only. A port's stale skbs are still aged out
only when that port sends a two step frame, so a port that stops sending
them keeps its stale skbs, and their slots, until it sends one again.
That behaviour is unchanged by this patch.
Found by an AI code review agent. Reproduced on a lan9668 board.
Fixes: 77eecf25bd9d ("net: lan966x: Update extraction/injection for timestamping")
Signed-off-by: Daniel Machon <daniel.machon@microchip.com>
---
drivers/net/ethernet/microchip/lan966x/lan966x_ptp.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_ptp.c b/drivers/net/ethernet/microchip/lan966x/lan966x_ptp.c
index 8c40db90ee8f6..9be2cfe6feeb7 100644
--- a/drivers/net/ethernet/microchip/lan966x/lan966x_ptp.c
+++ b/drivers/net/ethernet/microchip/lan966x/lan966x_ptp.c
@@ -376,9 +376,10 @@ static void lan966x_ptp_classify(struct lan966x_port *port, struct sk_buff *skb,
*rew_op = IFH_REW_OP_TWO_STEP_PTP;
}
-static void lan966x_ptp_txtstamp_old_release(struct lan966x_port *port)
+static unsigned int lan966x_ptp_txtstamp_old_release(struct lan966x_port *port)
{
struct sk_buff *skb, *skb_tmp;
+ unsigned int purged = 0;
unsigned long flags;
spin_lock_irqsave(&port->tx_skbs.lock, flags);
@@ -389,14 +390,18 @@ static void lan966x_ptp_txtstamp_old_release(struct lan966x_port *port)
__skb_unlink(skb, &port->tx_skbs);
dev_kfree_skb_any(skb);
+ purged++;
}
spin_unlock_irqrestore(&port->tx_skbs.lock, flags);
+
+ return purged;
}
int lan966x_ptp_txtstamp_request(struct lan966x_port *port,
struct sk_buff *skb)
{
struct lan966x *lan966x = port->lan966x;
+ unsigned int purged;
unsigned long flags;
u8 pdu_type;
u8 rew_op;
@@ -408,9 +413,10 @@ int lan966x_ptp_txtstamp_request(struct lan966x_port *port,
if (rew_op != IFH_REW_OP_TWO_STEP_PTP)
return 0;
- lan966x_ptp_txtstamp_old_release(port);
+ purged = lan966x_ptp_txtstamp_old_release(port);
spin_lock_irqsave(&lan966x->ptp_ts_id_lock, flags);
+ lan966x->ptp_skbs -= purged;
if (lan966x->ptp_skbs == LAN966X_MAX_PTP_ID) {
spin_unlock_irqrestore(&lan966x->ptp_ts_id_lock, flags);
return -EBUSY;
---
base-commit: 6430fb7f1cc4e329e386521b7b208df443de92f2
change-id: 20261023-lan966x-ptp-stale-tx-slot-95dffa0df0dd
Best regards,
--
Daniel Machon <daniel.machon@microchip.com>
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [PATCH net] net: lan966x: release the in-flight slot of a stale TX skb 2026-10-26 13:46 [PATCH net] net: lan966x: release the in-flight slot of a stale TX skb Daniel Machon @ 2026-10-27 13:48 ` netdev-bot+sashiko 2026-10-27 14:09 ` Daniel Machon 2026-10-27 22:30 ` patchwork-bot+netdevbpf 1 sibling, 1 reply; 4+ messages in thread From: netdev-bot+sashiko @ 2026-10-27 13:48 UTC (permalink / raw) To: daniel.machon Cc: horatiu.vultur, UNGLinuxDriver, andrew+netdev, davem, edumazet, kuba, pabeni, richardcochran, netdev, linux-kernel Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider. Critical: 0 (1) · High: 0 (5) · Medium: 1 (1) · Low: 0 If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough). - [Medium] After this patch, slots are reclaimed only by lan966x_ptp_txtstamp_old_release(), which ages only the queue of the… Pre-existing issues: - [Critical] This problem wasn't introduced by this patch, but when the shared 512-entry FDMA TX ring is full, lan966x_fdma_xmit() returns… - [High] This problem wasn't introduced by this patch, but the sparx5/lan969x driver has the same leak this patch fixes for lan966x. - [High] This problem wasn't introduced by this patch, but lan966x_ptp_txtstamp_request() makes the skb visible on port->tx_skbs… - [High] This problem wasn't introduced by this patch, but when lan966x_ptp_txtstamp_request() returns -EBUSY, lan966x_port_xmit()… - [High] This problem wasn't introduced by this patch, but in FDMA mode lan966x_fdma_xmit() leaves a two-step skb DMA-mapped and referenced by… - [High] This problem wasn't introduced by this patch, but lan966x_fdma_xmit() can free a two-step skb that is still on… - [Medium] This problem wasn't introduced by this patch, but lan966x_ptp_txtstamp_old_release() frees two-step skbs after… -- net: lan966x: release the in-flight slot of a stale TX skb lan966x_ptp_txtstamp_old_release() now returns how many stale two-step TX skbs it freed. lan966x_ptp_txtstamp_request() subtracts that count from lan966x->ptp_skbs under ptp_ts_id_lock. Before this, a slot whose timestamp never arrived was never released, so two-step TX eventually failed on every port. > diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_ptp.c b/drivers/net/ethernet/microchip/lan966x/lan966x_ptp.c > index 8c40db90ee8f..9be2cfe6feeb 100644 > --- a/drivers/net/ethernet/microchip/lan966x/lan966x_ptp.c > +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_ptp.c [ ... ] > @@ -389,14 +390,18 @@ static void lan966x_ptp_txtstamp_old_release(struct lan966x_port *port) > > __skb_unlink(skb, &port->tx_skbs); > dev_kfree_skb_any(skb); > + purged++; > } [Severity: High] This isn't a bug introduced by this patch, but can this free a two-step skb that the FDMA engine is still reading? In FDMA mode, lan966x_fdma_xmit() leaves the skb DMA-mapped and referenced by the shared TX ring, and hands its lifetime to the PTP code: drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c:lan966x_fdma_xmit() { ... next_dcb_buf->data.skb = skb; ... if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP && LAN966X_SKB_CB(skb)->rew_op == IFH_REW_OP_TWO_STEP_PTP) next_dcb_buf->ptp = true; ... } On completion, lan966x_fdma_tx_clear_buf() only unmaps it: if (!dcb_buf->ptp) napi_consume_skb(dcb_buf->data.skb, weight); lan966x_ptp_txtstamp_old_release() frees by age alone. LAN966X_PTP_TIMEOUT is msecs_to_jiffies(10), which is a single jiffy at HZ=100. Nothing checks whether the FDMA descriptor has completed. The PTP frame can sit behind other frames in the 512-entry ring that all ports share, or egress can be slow (10/100 link, PAUSE, shapers). In either case, the next two-step request on the same port can hand the skb data back to the allocator while the device can still DMA-read it. Could the device then send freed or reused memory onto the wire? And would the late stamp then hit WARN_ON(!skb_match)? [Severity: Medium] This is also a pre-existing issue, but because aging here is based only on time, what happens to a stamp that arrives after its skb has been aged out? lan966x_ptp_irq_handler() matches FIFO entries only by the per-port 0..511 id: skb_queue_walk_safe(&port->tx_skbs, skb, skb_tmp) { if (LAN966X_SKB_CB(skb)->ts_id != id) continue; ... if (WARN_ON(!skb_match)) continue; A late stamp either hits the WARN_ON(), or it gets attributed to a newer skb that reused the id, if the port's ts_id has wrapped in the meantime. Can userspace then receive a wrong TX hardware timestamp, with the newer skb's own stamp tripping the WARN later? Frames held in egress past the timeout would trigger this, for example with taprio/tbf/cbs offload, PAUSE or a slow link. ptp_skbs never prevented id reuse, so this patch does not change the behaviour. > spin_unlock_irqrestore(&port->tx_skbs.lock, flags); > + > + return purged; > } [Severity: High] This isn't a bug introduced by this patch, but does sparx5/lan969x need the same fix? sparx5_ptp_txtstamp_old_release() in drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c matches the pre-patch lan966x version, and it never decrements sparx5->ptp_skbs: skb_queue_walk_safe(&port->tx_skbs, skb, skb_tmp) { if time_after(SPARX5_SKB_CB(skb)->jiffies + SPARX5_PTP_TIMEOUT, jiffies) break; __skb_unlink(skb, &port->tx_skbs); dev_kfree_skb_any(skb); } There are only two other decrements. The IRQ handlers in sparx5_ptp.c and lan969x.c decrement it for a matched skb, and sparx5_ptp_txtstamp_release() does so on the xmit busy path. After SPARX5_MAX_PTP_ID (512) lost stamps, sparx5_ptp_txtstamp_request() rejects every two-step request: if (sparx5->ptp_skbs == SPARX5_MAX_PTP_ID) { spin_unlock_irqrestore(&sparx5->ptp_ts_id_lock, flags); return -EBUSY; } sparx5_port_xmit_impl() then returns NETDEV_TX_BUSY without stopping the queue: if (sparx5_ptp_txtstamp_request(port, skb) < 0) return NETDEV_TX_BUSY; Wouldn't the qdisc then requeue and retry the same PTP skb forever, and block all traffic behind it on that TX queue? A companion patch for sparx5 seems needed. It would return the purged count and subtract it under ptp_ts_id_lock. [ ... ] > @@ -408,9 +413,10 @@ int lan966x_ptp_txtstamp_request(struct lan966x_port *port, > if (rew_op != IFH_REW_OP_TWO_STEP_PTP) > return 0; > > - lan966x_ptp_txtstamp_old_release(port); > + purged = lan966x_ptp_txtstamp_old_release(port); > > spin_lock_irqsave(&lan966x->ptp_ts_id_lock, flags); > + lan966x->ptp_skbs -= purged; > if (lan966x->ptp_skbs == LAN966X_MAX_PTP_ID) { [Severity: Medium] The ptp_skbs limit is switch-wide. Slots are only reclaimed by aging the transmitting port's queue, and only when that port sends a two-step frame. Can stale skbs on one port still use up the limit for every other port? The commit message says idle ports keep their stale skbs. Nothing else releases them either. lan966x_port_stop() does not purge port->tx_skbs, and HWTSTAMP_TX_OFF in lan966x_ptp_hwtstamp_set() only does: case HWTSTAMP_TX_OFF: port->ptp_tx_cmd = IFH_REW_OP_NOOP; break; After that the port can never send a two-step frame again, so its stale skbs are never aged. Only lan966x_ptp_deinit() purges tx_skbs. Lost stamps can pile up on a port that then goes quiet: ifdown, TX timestamping turned off, ptp4l stopped, or a boundary clock port in passive state. Two-step TX on the other ports is then throttled, and returns -EBUSY once the total reaches LAN966X_MAX_PTP_ID. That is the failure this patch sets out to fix. Would any of these close the gap: aging all ports before rejecting, aging from a timer or worker, or purging and uncounting on ndo_stop and HWTSTAMP_TX_OFF? > spin_unlock_irqrestore(&lan966x->ptp_ts_id_lock, flags); > return -EBUSY; [Severity: High] This isn't a bug introduced by this patch, but does this -EBUSY path leak the skb? lan966x_port_xmit() returns the errno directly as its netdev_tx_t: err = lan966x_ptp_txtstamp_request(port, skb); if (err) return err; dev_xmit_complete() treats any rc < NET_XMIT_MASK, negative values included, as meaning the driver consumed the skb. The core therefore neither requeues nor frees it. The skb is not on port->tx_skbs either, because this return happens before skb_queue_tail(). So every two-step frame sent while the slot table is full would leak an skb, and its sk_wmem_alloc charge would keep the sending socket pinned. Once an admin has enabled HW TX timestamping on a port, could an unprivileged user fill the table and keep leaking memory? Sending UDP to port 319 with SOF_TIMESTAMPING_TX_HARDWARE would be enough. [Severity: High] This is a pre-existing issue, but further down in lan966x_ptp_txtstamp_request() the skb is published on port->tx_skbs before its control block is filled in: skb_queue_tail(&port->tx_skbs, skb); LAN966X_SKB_CB(skb)->ts_id = port->ts_id; LAN966X_SKB_CB(skb)->jiffies = jiffies; Only ptp_ts_id_lock covers these writes. lan966x_ptp_irq_handler() walks the list holding only tx_skbs.lock and matches on LAN966X_SKB_CB(skb)->ts_id. On PREEMPT_RT, ptp_ts_id_lock is a sleeping lock and the PTP handler is a threaded IRQ (devm_request_threaded_irq() in lan966x_probe()). The handler can therefore preempt the sender right after skb_queue_tail(). Suppose the FIFO holds an unmatched stamp, for example a late one for an aged-out skb, whose id equals the leftover ts_id bytes in the cb. The handler then unlinks the new skb, and once request() drops the lock it timestamps and frees it. Wouldn't lan966x_port_xmit() then keep reading the cb and transmit the freed skb? Setting ts_id and jiffies before skb_queue_tail() would avoid this. [Severity: High] This is another pre-existing issue. After request() has queued and counted the skb, lan966x_fdma_xmit() can free it without unlinking it from port->tx_skbs: drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c:lan966x_fdma_xmit() { ... if (skb_put_padto(skb, ETH_ZLEN)) { dev->stats.tx_dropped++; return NETDEV_TX_OK; } ... } skb_put_padto() passes free_on_error=true, so __skb_pad() frees the skb when pskb_expand_head(GFP_ATOMIC) or skb_linearize() fails. This return also skips the lan966x_ptp_txtstamp_release() call that the release: label makes. L2 PTP event messages are 58 bytes, which is below ETH_ZLEN. They are cloned when an AF_PACKET tap is active, and that forces pskb_expand_head(). Under memory pressure, could a later old_release() or IRQ handler walk read the freed skb, __skb_unlink() it, and call dev_kfree_skb_any() on it a second time? [Severity: Critical] This isn't introduced by this patch either, but the ring-full path in lan966x_fdma_xmit() also returns without undoing the reservation made here: next_to_use = lan966x_fdma_get_next_dcb(tx); if (next_to_use < 0) { netif_stop_queue(dev); return NETDEV_TX_BUSY; } The register injection path does undo it, in lan966x_port_ifh_xmit(): err: if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP && LAN966X_SKB_CB(skb)->rew_op == IFH_REW_OP_TWO_STEP_PTP) lan966x_ptp_txtstamp_release(port, skb); return NETDEV_TX_BUSY; On NETDEV_TX_BUSY, dev_hard_start_xmit() overwrites skb->next, and dev_requeue_skb() links the skb into q->gso_skb. At that point the skb is still linked on port->tx_skbs and still counted in ptp_skbs. Can this corrupt port->tx_skbs? A later old_release() or IRQ walk could follow the overwritten pointers, or free an skb the stack still owns. When the qdisc retries, request() calls skb_queue_tail() on the same skb again. If that skb was the tail, skb->next ends up pointing to itself, and skb_queue_walk_safe() in lan966x_ptp_irq_handler() would loop forever with tx_skbs.lock held. The 512-entry ring is shared by all ports, and netif_stop_queue() only stops TX queue 0 of the current netdev. Ordinary TX congestion plus two-step traffic on another port or queue looks like enough to reach this. -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261026-lan966x-ptp-stale-tx-slot-v1-1-9f7c895eb1ff%40microchip.com ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH net] net: lan966x: release the in-flight slot of a stale TX skb 2026-10-27 13:48 ` netdev-bot+sashiko @ 2026-10-27 14:09 ` Daniel Machon 0 siblings, 0 replies; 4+ messages in thread From: Daniel Machon @ 2026-10-27 14:09 UTC (permalink / raw) To: netdev-bot+sashiko Cc: horatiu.vultur, UNGLinuxDriver, andrew+netdev, davem, edumazet, kuba, pabeni, richardcochran, netdev, linux-kernel > Thank you for your contribution! Sashiko AI review found 1 potential > issue(s) to consider. > > Critical: 0 (1) · High: 0 (5) · Medium: 1 (1) · Low: 0 > > If you believe none of the issues are legitimate, please reply to each > issue of Medium severity or higher and explain why the AI is wrong. > If any of them are legitimate and you plan to respin, please let patchwork > know by including "pw-bot: cr" as a separate line at the end of your reply > (one such reply per series is enough). > > - [Medium] After this patch, slots are reclaimed only by > lan966x_ptp_txtstamp_old_release(), which ages only the queue of the… > > Pre-existing issues: > - [Critical] This problem wasn't introduced by this patch, but when the > shared 512-entry FDMA TX ring is full, lan966x_fdma_xmit() returns… > - [High] This problem wasn't introduced by this patch, but the > sparx5/lan969x driver has the same leak this patch fixes for lan966x. > - [High] This problem wasn't introduced by this patch, but > lan966x_ptp_txtstamp_request() makes the skb visible on port->tx_skbs… > - [High] This problem wasn't introduced by this patch, but when > lan966x_ptp_txtstamp_request() returns -EBUSY, lan966x_port_xmit()… > - [High] This problem wasn't introduced by this patch, but in FDMA mode > lan966x_fdma_xmit() leaves a two-step skb DMA-mapped and referenced by… > - [High] This problem wasn't introduced by this patch, but > lan966x_fdma_xmit() can free a two-step skb that is still on… > - [Medium] This problem wasn't introduced by this patch, but > lan966x_ptp_txtstamp_old_release() frees two-step skbs after… > > -- > > net: lan966x: release the in-flight slot of a stale TX skb > > lan966x_ptp_txtstamp_old_release() now returns how many stale two-step TX > skbs it freed. lan966x_ptp_txtstamp_request() subtracts that count from > lan966x->ptp_skbs under ptp_ts_id_lock. Before this, a slot whose > timestamp never arrived was never released, so two-step TX eventually > failed on every port. > > > diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_ptp.c b/drivers/net/ethernet/microchip/lan966x/lan966x_ptp.c > > index 8c40db90ee8f..9be2cfe6feeb 100644 > > --- a/drivers/net/ethernet/microchip/lan966x/lan966x_ptp.c > > +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_ptp.c > > [ ... ] > > > @@ -389,14 +390,18 @@ static void lan966x_ptp_txtstamp_old_release(struct lan966x_port *port) > > > > __skb_unlink(skb, &port->tx_skbs); > > dev_kfree_skb_any(skb); > > + purged++; > > } > > [Severity: High] > This isn't a bug introduced by this patch, but can this free a two-step > skb that the FDMA engine is still reading? > > In FDMA mode, lan966x_fdma_xmit() leaves the skb DMA-mapped and > referenced by the shared TX ring, and hands its lifetime to the PTP code: > > drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c:lan966x_fdma_xmit() { > ... > next_dcb_buf->data.skb = skb; > ... > if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP && > LAN966X_SKB_CB(skb)->rew_op == IFH_REW_OP_TWO_STEP_PTP) > next_dcb_buf->ptp = true; > ... > } > > On completion, lan966x_fdma_tx_clear_buf() only unmaps it: > > if (!dcb_buf->ptp) > napi_consume_skb(dcb_buf->data.skb, weight); > > lan966x_ptp_txtstamp_old_release() frees by age alone. > LAN966X_PTP_TIMEOUT is msecs_to_jiffies(10), which is a single jiffy at > HZ=100. Nothing checks whether the FDMA descriptor has completed. > > The PTP frame can sit behind other frames in the 512-entry ring that all > ports share, or egress can be slow (10/100 link, PAUSE, shapers). In > either case, the next two-step request on the same port can hand the skb > data back to the allocator while the device can still DMA-read it. > > Could the device then send freed or reused memory onto the wire? And > would the late stamp then hit WARN_ON(!skb_match)? > > [Severity: Medium] > This is also a pre-existing issue, but because aging here is based only > on time, what happens to a stamp that arrives after its skb has been > aged out? > > lan966x_ptp_irq_handler() matches FIFO entries only by the per-port > 0..511 id: > > skb_queue_walk_safe(&port->tx_skbs, skb, skb_tmp) { > if (LAN966X_SKB_CB(skb)->ts_id != id) > continue; > ... > if (WARN_ON(!skb_match)) > continue; > > A late stamp either hits the WARN_ON(), or it gets attributed to a newer > skb that reused the id, if the port's ts_id has wrapped in the meantime. > > Can userspace then receive a wrong TX hardware timestamp, with the newer > skb's own stamp tripping the WARN later? Frames held in egress past the > timeout would trigger this, for example with taprio/tbf/cbs offload, > PAUSE or a slow link. > > ptp_skbs never prevented id reuse, so this patch does not change the > behaviour. > > > spin_unlock_irqrestore(&port->tx_skbs.lock, flags); > > + > > + return purged; > > } > > [Severity: High] > This isn't a bug introduced by this patch, but does sparx5/lan969x need > the same fix? sparx5_ptp_txtstamp_old_release() in > drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c matches the pre-patch > lan966x version, and it never decrements sparx5->ptp_skbs: > > skb_queue_walk_safe(&port->tx_skbs, skb, skb_tmp) { > if time_after(SPARX5_SKB_CB(skb)->jiffies + SPARX5_PTP_TIMEOUT, > jiffies) > break; > > __skb_unlink(skb, &port->tx_skbs); > dev_kfree_skb_any(skb); > } > > There are only two other decrements. The IRQ handlers in sparx5_ptp.c > and lan969x.c decrement it for a matched skb, and > sparx5_ptp_txtstamp_release() does so on the xmit busy path. > > After SPARX5_MAX_PTP_ID (512) lost stamps, > sparx5_ptp_txtstamp_request() rejects every two-step request: > > if (sparx5->ptp_skbs == SPARX5_MAX_PTP_ID) { > spin_unlock_irqrestore(&sparx5->ptp_ts_id_lock, flags); > return -EBUSY; > } > > sparx5_port_xmit_impl() then returns NETDEV_TX_BUSY without stopping the > queue: > > if (sparx5_ptp_txtstamp_request(port, skb) < 0) > return NETDEV_TX_BUSY; > > Wouldn't the qdisc then requeue and retry the same PTP skb forever, and > block all traffic behind it on that TX queue? > > A companion patch for sparx5 seems needed. It would return the purged > count and subtract it under ptp_ts_id_lock. > > [ ... ] > > > @@ -408,9 +413,10 @@ int lan966x_ptp_txtstamp_request(struct lan966x_port *port, > > if (rew_op != IFH_REW_OP_TWO_STEP_PTP) > > return 0; > > > > - lan966x_ptp_txtstamp_old_release(port); > > + purged = lan966x_ptp_txtstamp_old_release(port); > > > > spin_lock_irqsave(&lan966x->ptp_ts_id_lock, flags); > > + lan966x->ptp_skbs -= purged; > > if (lan966x->ptp_skbs == LAN966X_MAX_PTP_ID) { > > [Severity: Medium] > The ptp_skbs limit is switch-wide. Slots are only reclaimed by aging the > transmitting port's queue, and only when that port sends a two-step > frame. Can stale skbs on one port still use up the limit for every other > port? Yes, but this is a separate, pre-existing limitation, and the commit message even says so. > > The commit message says idle ports keep their stale skbs. Nothing else > releases them either. lan966x_port_stop() does not purge port->tx_skbs, > and HWTSTAMP_TX_OFF in lan966x_ptp_hwtstamp_set() only does: > > case HWTSTAMP_TX_OFF: > port->ptp_tx_cmd = IFH_REW_OP_NOOP; > break; > > After that the port can never send a two-step frame again, so its stale > skbs are never aged. Only lan966x_ptp_deinit() purges tx_skbs. > > Lost stamps can pile up on a port that then goes quiet: ifdown, TX > timestamping turned off, ptp4l stopped, or a boundary clock port in > passive state. Two-step TX on the other ports is then throttled, and > returns -EBUSY once the total reaches LAN966X_MAX_PTP_ID. That is the > failure this patch sets out to fix. > > Would any of these close the gap: aging all ports before rejecting, > aging from a timer or worker, or purging and uncounting on ndo_stop and > HWTSTAMP_TX_OFF? The bug fixed here is that every lost timestamp leaked its slot for good, so the limit was eventually hit on any system that lost timestamps. Aging the queues of all ports on each request, so an idle port cannot hold slots, I believe is a change for net-next. > > > spin_unlock_irqrestore(&lan966x->ptp_ts_id_lock, flags); > > return -EBUSY; > > [Severity: High] > This isn't a bug introduced by this patch, but does this -EBUSY path > leak the skb? lan966x_port_xmit() returns the errno directly as its > netdev_tx_t: > > err = lan966x_ptp_txtstamp_request(port, skb); > if (err) > return err; > > dev_xmit_complete() treats any rc < NET_XMIT_MASK, negative values > included, as meaning the driver consumed the skb. The core therefore > neither requeues nor frees it. The skb is not on port->tx_skbs either, > because this return happens before skb_queue_tail(). > > So every two-step frame sent while the slot table is full would leak an > skb, and its sk_wmem_alloc charge would keep the sending socket pinned. > > Once an admin has enabled HW TX timestamping on a port, could an > unprivileged user fill the table and keep leaking memory? Sending UDP to > port 319 with SOF_TIMESTAMPING_TX_HARDWARE would be enough. > > [Severity: High] > This is a pre-existing issue, but further down in > lan966x_ptp_txtstamp_request() the skb is published on port->tx_skbs > before its control block is filled in: > > skb_queue_tail(&port->tx_skbs, skb); > LAN966X_SKB_CB(skb)->ts_id = port->ts_id; > LAN966X_SKB_CB(skb)->jiffies = jiffies; > > Only ptp_ts_id_lock covers these writes. lan966x_ptp_irq_handler() walks > the list holding only tx_skbs.lock and matches on > LAN966X_SKB_CB(skb)->ts_id. > > On PREEMPT_RT, ptp_ts_id_lock is a sleeping lock and the PTP handler is > a threaded IRQ (devm_request_threaded_irq() in lan966x_probe()). The > handler can therefore preempt the sender right after skb_queue_tail(). > > Suppose the FIFO holds an unmatched stamp, for example a late one for an > aged-out skb, whose id equals the leftover ts_id bytes in the cb. The > handler then unlinks the new skb, and once request() drops the lock it > timestamps and frees it. > > Wouldn't lan966x_port_xmit() then keep reading the cb and transmit the > freed skb? Setting ts_id and jiffies before skb_queue_tail() would avoid > this. > > [Severity: High] > This is another pre-existing issue. After request() has queued and > counted the skb, lan966x_fdma_xmit() can free it without unlinking it > from port->tx_skbs: > > drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c:lan966x_fdma_xmit() { > ... > if (skb_put_padto(skb, ETH_ZLEN)) { > dev->stats.tx_dropped++; > return NETDEV_TX_OK; > } > ... > } > > skb_put_padto() passes free_on_error=true, so __skb_pad() frees the skb > when pskb_expand_head(GFP_ATOMIC) or skb_linearize() fails. This return > also skips the lan966x_ptp_txtstamp_release() call that the release: > label makes. > > L2 PTP event messages are 58 bytes, which is below ETH_ZLEN. They are > cloned when an AF_PACKET tap is active, and that forces > pskb_expand_head(). > > Under memory pressure, could a later old_release() or IRQ handler walk > read the freed skb, __skb_unlink() it, and call dev_kfree_skb_any() on > it a second time? > > [Severity: Critical] > This isn't introduced by this patch either, but the ring-full path in > lan966x_fdma_xmit() also returns without undoing the reservation made > here: > > next_to_use = lan966x_fdma_get_next_dcb(tx); > if (next_to_use < 0) { > netif_stop_queue(dev); > return NETDEV_TX_BUSY; > } > > The register injection path does undo it, in lan966x_port_ifh_xmit(): > > err: > if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP && > LAN966X_SKB_CB(skb)->rew_op == IFH_REW_OP_TWO_STEP_PTP) > lan966x_ptp_txtstamp_release(port, skb); > > return NETDEV_TX_BUSY; > > On NETDEV_TX_BUSY, dev_hard_start_xmit() overwrites skb->next, and > dev_requeue_skb() links the skb into q->gso_skb. At that point the skb > is still linked on port->tx_skbs and still counted in ptp_skbs. > > Can this corrupt port->tx_skbs? A later old_release() or IRQ walk could > follow the overwritten pointers, or free an skb the stack still owns. > > When the qdisc retries, request() calls skb_queue_tail() on the same skb > again. If that skb was the tail, skb->next ends up pointing to itself, > and skb_queue_walk_safe() in lan966x_ptp_irq_handler() would loop > forever with tx_skbs.lock held. > > The 512-entry ring is shared by all ports, and netif_stop_queue() only > stops TX queue 0 of the current netdev. Ordinary TX congestion plus > two-step traffic on another port or queue looks like enough to reach > this. > > -- > Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261026-lan966x-ptp-stale-tx-slot-v1-1-9f7c895eb1ff%40microchip.com ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH net] net: lan966x: release the in-flight slot of a stale TX skb 2026-10-26 13:46 [PATCH net] net: lan966x: release the in-flight slot of a stale TX skb Daniel Machon 2026-10-27 13:48 ` netdev-bot+sashiko @ 2026-10-27 22:30 ` patchwork-bot+netdevbpf 1 sibling, 0 replies; 4+ messages in thread From: patchwork-bot+netdevbpf @ 2026-10-27 22:30 UTC (permalink / raw) To: Daniel Machon Cc: horatiu.vultur, UNGLinuxDriver, andrew+netdev, davem, edumazet, kuba, pabeni, richardcochran, netdev, linux-kernel Hello: This patch was applied to netdev/net.git (main) by Jakub Kicinski <kuba@kernel.org>: On Mon, 26 Oct 2026 14:46:54 +0100 you wrote: > lan966x_ptp_txtstamp_request() counts every two step TX skb waiting for > its timestamp in lan966x->ptp_skbs, and refuses new ones once > LAN966X_MAX_PTP_ID are in flight. lan966x_ptp_txtstamp_old_release() > frees the skbs that have waited longer than LAN966X_PTP_TIMEOUT. > > Before this patch, old_release() does not decrement ptp_skbs, so every > skb whose timestamp never arrives, for example because the switch > discarded the frame, keeps its slot forever. Once LAN966X_MAX_PTP_ID > slots are lost, two step frames fail to transmit on every port of the > switch. > > [...] Here is the summary with links: - [net] net: lan966x: release the in-flight slot of a stale TX skb https://git.kernel.org/netdev/net/c/074caa30a627 You are awesome, thank you! -- Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-27 22:30 UTC | newest] Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-10-26 13:46 [PATCH net] net: lan966x: release the in-flight slot of a stale TX skb Daniel Machon 2026-10-27 13:48 ` netdev-bot+sashiko 2026-10-27 14:09 ` Daniel Machon 2026-10-27 22:30 ` patchwork-bot+netdevbpf
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®