From: Jakub Kicinski <kuba@kernel.org>
To: nagadheeraj.rottela@amd.com
Cc: Jakub Kicinski <kuba@kernel.org>,
srinivas.neeli@amd.com, andrew+netdev@lunn.ch,
davem@davemloft.net, edumazet@google.com, pabeni@redhat.com,
robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
richardcochran@gmail.com, michal.simek@amd.com, andrew@lunn.ch,
olteanv@gmail.com, horms@kernel.org, linux@armlinux.org.uk,
netdev@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, git-dev@amd.com
Subject: Re: [PATCH 07/20] net: xilinx: tsn: add the endpoint TX data path
Date: Sat, 8 Aug 2026 12:48:24 -0700 [thread overview]
Message-ID: <20260808194824.132828-1-kuba@kernel.org> (raw)
In-Reply-To: <20260807104431.157230-8-nagadheeraj.rottela@amd.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
net: xilinx: tsn: add the endpoint TX data path
This adds the TX path for the Xilinx TSN endpoint conduit netdev: the
logical queue is translated to a physical DMA channel through the map built
at probe, the frame is mapped and submitted to that channel, and the queue
is stopped/woken based on the software ring occupancy. A few questions
about the new ring accounting and the scatterlist handling below.
> diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> index 207ca3242a12..7615d27f3b36 100644
> --- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
[ ... ]
> @@ -97,6 +99,7 @@ struct xlnx_tsn_ep_dma_chan {
> * @ndev: the conduit netdev ("ep")
> * @dev: backing device
> * @regs: EP MAC register window
> + * @tx_lock: protects TX ring head/tail and SKB ring slots
> * @num_tx_queues: number of TX DMA channels (one per priority)
> * @num_rx_queues: number of RX DMA channels
> * @tx_dma_chan_map: logical TX queue index -> physical DMA channel number
> @@ -110,6 +113,8 @@ struct xlnx_tsn_ep {
> struct device *dev;
> void __iomem *regs;
>
> + spinlock_t tx_lock; /* protects TX ring buffers */
> +
[Severity: Low]
Does the implementation match this documented protection domain? The
kernel-doc says tx_lock protects the "SKB ring slots", but ep_start_xmit()
drops the lock right after ep_get_desc() and then fills the slot unlocked:
skbuf_dma = ep_get_desc(xchan, xchan->ring_head & (TX_BD_NUM_DEFAULT - 1));
...
spin_unlock_bh(&ep->tx_lock);
sg_init_table(skbuf_dma->sgl, sg_len);
ret = skb_to_sgvec(skb, skbuf_dma->sgl, 0, skb->len);
...
skbuf_dma->skb = skb;
skbuf_dma->sg_len = sg_len;
while ep_dma_tx_cb() reads skbuf_dma->skb/sgl/sg_len under the lock. The
pre-existing teardown loop in ep_free_dma_chan() also walks chan->ring_tail
and clears skbuf_dma->skb with no tx_lock held at all.
As far as I can tell there is no race today: ep_parse_tx_queue_config()
rejects duplicate queue and duplicate channel numbers, so each DMA channel
is fed by exactly one netdev txq and the slot writes have a single writer
serialised by __netif_tx_lock, and the drain path only runs after
netif_tx_disable() plus dmaengine_terminate_sync().
Would it be clearer to narrow the comment to what is actually enforced,
e.g. "protects TX ring head/tail; slot payloads are serialised by the txq
xmit lock", or alternatively extend the locked region to cover the slot
writes?
> u32 num_tx_queues;
> u32 num_rx_queues;
> u32 tx_dma_chan_map[TSN_MAX_TX_QUEUE];
> @@ -268,8 +273,145 @@ static void ep_dma_rx_cb(void *data, const struct dmaengine_result *result)
> dma_async_issue_pending(xchan->chan);
> }
>
> +static void ep_dma_tx_cb(void *data, const struct dmaengine_result *result)
> +{
> + struct xlnx_tsn_ep_dma_chan *xchan = data;
> + struct skbuf_dma_descriptor *skbuf_dma;
> + struct xlnx_tsn_ep *ep = xchan->ep;
> + struct netdev_queue *txq;
> + struct net_device *ndev;
> + struct scatterlist *sgl;
> + struct sk_buff *skb;
> + int sg_len;
> + int len;
> +
> + scoped_guard(spinlock, &ep->tx_lock) {
> + skbuf_dma = ep_get_desc(xchan,
> + xchan->ring_tail & (TX_BD_NUM_DEFAULT - 1));
[Severity: Low]
Should the new TX sites use xchan->ring_size instead of the compile-time
constant? struct xlnx_tsn_ep_dma_chan documents ring_size as the "number
of slots in @skb_ring" and ep_free_dma_chan() uses it:
skbuf_dma = chan->skb_ring[chan->ring_tail & (chan->ring_size - 1)];
...
for (i = 0; i < chan->ring_size; i++)
but the index masks in ep_dma_tx_cb() and ep_start_xmit(), and all four
CIRC_SPACE() computations, hardcode TX_BD_NUM_DEFAULT. They agree today
only because ep_init_dmaengine() passes TX_BD_NUM_DEFAULT to
ep_alloc_dma_chan(); if the ring size ever becomes configurable through
ethtool set_ringparam the producer and consumer would mask against the
wrong size while the free path masks correctly.
> + if (!skbuf_dma || !skbuf_dma->skb)
> + return;
> +
> + skb = skbuf_dma->skb;
> + sgl = skbuf_dma->sgl;
> + sg_len = skbuf_dma->sg_len;
> +
> + dma_unmap_sg(xchan->dma_dev, sgl, sg_len, DMA_TO_DEVICE);
> +
> + skbuf_dma->skb = NULL;
> + xchan->ring_tail++;
> + }
> +
> + ndev = skb->dev;
> + txq = netdev_get_tx_queue(ndev, skb_get_queue_mapping(skb));
> + len = skb->len;
> +
> + if (unlikely(result->result != DMA_TRANS_NOERROR)) {
> + DEV_STATS_INC(ndev, tx_errors);
> + } else {
> + DEV_STATS_INC(ndev, tx_packets);
> + DEV_STATS_ADD(ndev, tx_bytes, len);
> + }
> +
> + dev_consume_skb_any(skb);
> + netif_txq_completed_wake(txq, 1, len,
> + CIRC_SPACE(READ_ONCE(xchan->ring_head),
> + READ_ONCE(xchan->ring_tail),
> + TX_BD_NUM_DEFAULT), 2);
> +}
[Severity: Low]
Is the READ_ONCE() here paired with anything on the producer side? This
read happens after the scoped_guard() block has already released
ep->tx_lock, while ep_start_xmit() updates the index with a plain
read-modify-write:
spin_lock_bh(&ep->tx_lock);
xchan->ring_head++;
That is a marked read against a plain write, which KCSAN would report, and
the wake decision is taken on a non-atomic snapshot of the (head, tail)
pair. If the completion observes a stale head, CIRC_SPACE() over-estimates
the free space and the queue may be woken with only one slot free, so the
next ep_start_xmit() takes the "TSN TX ring full" path and returns
NETDEV_TX_BUSY.
Would WRITE_ONCE() on the ring_head update be appropriate here?
Note the stop/wake handshake itself looks fine: include/net/netdev_queues.h
documents these macros as single-producer/single-consumer safe and states
they do not prevent false wake ups when the ring is full, which
ep_start_xmit() re-checks on entry.
> static netdev_tx_t ep_start_xmit(struct sk_buff *skb, struct net_device *ndev)
> {
[ ... ]
> + xchan = ep->tx_chans[phys_chan];
> + dma_dev = xchan->chan->device;
> +
> + sg_len = skb_shinfo(skb)->nr_frags + 1;
> + txq = netdev_get_tx_queue(ndev, queue);
> +
[ ... ]
> + skbuf_dma = ep_get_desc(xchan, xchan->ring_head & (TX_BD_NUM_DEFAULT - 1));
> + if (!skbuf_dma) {
> + spin_unlock_bh(&ep->tx_lock);
> + goto err_drop_skb;
> + }
> + spin_unlock_bh(&ep->tx_lock);
> +
> + sg_init_table(skbuf_dma->sgl, sg_len);
> + ret = skb_to_sgvec(skb, skbuf_dma->sgl, 0, skb->len);
> + if (ret < 0)
> + goto err_drop_skb;
> +
> + nents = dma_map_sg(xchan->dma_dev, skbuf_dma->sgl, sg_len, DMA_TO_DEVICE);
> + if (!nents)
> + goto err_drop_skb;
[Severity: Medium]
Can dma_map_sg() walk past the end of skbuf_dma->sgl here? The return
value of skb_to_sgvec() is only tested for < 0 and then discarded, and the
assumed count sg_len = nr_frags + 1 is what gets passed as nents.
skb_to_sgvec() only emits an entry for a non-zero-length region and marks
the end at sg[nsg - 1]:
net/core/skbuff.c:__skb_to_sgvec() {
...
if ((copy = end - offset) > 0) {
...
}
net/core/skbuff.c:skb_to_sgvec() {
int nsg = __skb_to_sgvec(skb, sg, offset, len, 0);
...
sg_mark_end(&sg[nsg - 1]);
return nsg;
}
So a zero-length page frag, or skb_headlen() == 0, yields nsg < nr_frags +
1 while the caller still hands nents = sg_len to dma_map_sg().
dma_map_sg_attrs() iterates with for_each_sg(sgl, sg, nents, i), and
sg_next() returns NULL once sg_is_last() is true, so the extra iterations
dereference NULL inside the DMA core.
The same value is stored in skbuf_dma->sg_len, which the struct documents
as "number of valid entries in @sgl", and it is later used for the
dma_unmap_sg() in ep_dma_tx_cb() and on the err_unmap_sg path.
Would using skb_to_sgvec()'s return value as the entry count for
dma_map_sg(), the prep call and skbuf_dma->sg_len be more robust? Note
ndev->features sets NETIF_F_SG, so page frags are passed through as-is and
only frag_list skbs get linearized. For what it is worth, the upstream
axienet dmaengine TX path has the same pattern, which is likely why this
has not been observed in practice.
> +
> + dma_tx_desc = dma_dev->device_prep_slave_sg(xchan->chan, skbuf_dma->sgl,
> + nents, DMA_MEM_TO_DEV,
> + DMA_PREP_INTERRUPT, NULL);
> + if (!dma_tx_desc)
> + goto err_unmap_sg;
[Severity: Low]
Any reason to call the ops member directly rather than use
dmaengine_prep_slave_sg(), which is what ep_rx_submit_desc() in this same
file does?
dma_rx_desc = dmaengine_prep_slave_sg(xchan->chan, skbuf_dma->sgl,
1, DMA_DEV_TO_MEM,
DMA_PREP_INTERRUPT);
device_prep_slave_sg is optional: xilinx_dma_probe() installs it only for
XDMA_TYPE_AXIDMA and XDMA_TYPE_AXIMCDMA, while for CDMA it installs only
device_prep_dma_memcpy and for VDMA only device_prep_interleaved_dma. If a
device tree pointed the endpoint dma-names at a CDMA or VDMA node, the RX
path would degrade gracefully (the wrapper returns NULL) but this line
would be a NULL function pointer call in the xmit path. Switching to the
wrapper would also make the extra dma_dev local unnecessary.
> +
> + skbuf_dma->skb = skb;
> + skbuf_dma->sg_len = sg_len;
> + dma_tx_desc->callback_param = xchan;
> + dma_tx_desc->callback_result = ep_dma_tx_cb;
> +
> + spin_lock_bh(&ep->tx_lock);
> + xchan->ring_head++;
> + netdev_tx_sent_queue(txq, skb->len);
> + netif_txq_maybe_stop(txq,
> + CIRC_SPACE(xchan->ring_head,
> + READ_ONCE(xchan->ring_tail),
> + TX_BD_NUM_DEFAULT),
> + 2, 2);
> + spin_unlock_bh(&ep->tx_lock);
> +
> + dmaengine_submit(dma_tx_desc);
> + dma_async_issue_pending(xchan->chan);
[Severity: Medium]
What happens if dmaengine_submit() fails here? The cookie is discarded
without a dma_submit_error() check, and the ring slot plus the BQL byte
charge have already been committed above.
The provider this driver targets can fail the submit:
drivers/dma/xilinx/xilinx_dma.c:xilinx_dma_tx_submit() {
if (chan->err) {
err = xilinx_dma_chan_reset(chan);
if (err < 0)
return err;
}
...
}
xilinx_dma_reset() returns -ETIMEDOUT on reset timeout, and in that path
the descriptor is never appended to the pending list, so ep_dma_tx_cb()
never runs for it.
Because the completion retires strictly by ring_tail:
skbuf_dma = ep_get_desc(xchan,
xchan->ring_tail & (TX_BD_NUM_DEFAULT - 1));
one lost submit leaves a permanent one-slot offset. After that, every
completion unmaps the scatterlist and calls dev_consume_skb_any() on a
frame that may still be in flight in the engine, the buffers the engine
actually finished stay mapped, and the length handed to
netif_txq_completed_wake() belongs to a different frame than the one BQL
was charged for.
The bytes charged for the frame that was never queued also stay outstanding
in the DQL forever. Since ep_netdev_ops registers no .ndo_tx_timeout:
static const struct net_device_ops ep_netdev_ops = {
.ndo_open = ep_open,
.ndo_stop = ep_stop,
.ndo_start_xmit = ep_start_xmit,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = eth_mac_addr,
};
__netdev_watchdog_up() never arms the watchdog, so a queue left stopped by
the skewed accounting is never detected or recovered while the interface
stays up.
Would it make sense to submit first and only advance ring_head and call
netdev_tx_sent_queue() once dma_submit_error() reports success, and to add
an .ndo_tx_timeout handler?
> +
> + return NETDEV_TX_OK;
> +
> +err_unmap_sg:
> + dma_unmap_sg(xchan->dma_dev, skbuf_dma->sgl, sg_len, DMA_TO_DEVICE);
> +err_drop_skb:
> dev_kfree_skb(skb);
> DEV_STATS_INC(ndev, tx_dropped);
> return NETDEV_TX_OK;
[ ... ]
next prev parent reply other threads:[~2026-08-08 19:48 UTC|newest]
Thread overview: 60+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-07 10:44 [PATCH 00/20] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support Nagadheeraj Rottela
2026-08-07 10:44 ` [PATCH 01/20] dt-bindings: net: add Xilinx TSN Endpoint Ethernet MAC Nagadheeraj Rottela
2026-08-08 10:46 ` sashiko-bot
2026-08-08 19:48 ` Jakub Kicinski
2026-08-07 10:44 ` [PATCH 02/20] net: xilinx: tsn: add TSN endpoint wrapper driver Nagadheeraj Rottela
2026-08-07 20:58 ` Uwe Kleine-König
2026-08-08 12:27 ` Neeli, Srinivas
2026-08-08 19:48 ` Jakub Kicinski
2026-08-07 10:44 ` [PATCH 03/20] net: xilinx: tsn: add endpoint MAC driver skeleton Nagadheeraj Rottela
2026-08-07 21:00 ` Uwe Kleine-König
2026-08-08 12:28 ` Neeli, Srinivas
2026-08-08 10:46 ` sashiko-bot
2026-08-08 19:48 ` Jakub Kicinski
2026-08-07 10:44 ` [PATCH 04/20] net: xilinx: tsn: parse endpoint DMA channel configuration Nagadheeraj Rottela
2026-08-08 19:48 ` Jakub Kicinski
2026-08-07 10:44 ` [PATCH 05/20] net: xilinx: tsn: bring up the endpoint MCDMA channels Nagadheeraj Rottela
2026-08-08 10:46 ` sashiko-bot
2026-08-08 19:48 ` Jakub Kicinski
2026-08-07 10:44 ` [PATCH 06/20] net: xilinx: tsn: add the endpoint RX data path Nagadheeraj Rottela
2026-08-08 10:46 ` sashiko-bot
2026-08-08 19:48 ` Jakub Kicinski
2026-08-07 10:44 ` [PATCH 07/20] net: xilinx: tsn: add the endpoint TX " Nagadheeraj Rottela
2026-08-08 10:46 ` sashiko-bot
2026-08-08 19:48 ` Jakub Kicinski [this message]
2026-08-07 10:44 ` [PATCH 08/20] net: xilinx: tsn: deliver endpoint RX frames to DSA user ports Nagadheeraj Rottela
2026-08-08 10:46 ` sashiko-bot
2026-08-08 19:48 ` Jakub Kicinski
2026-08-07 10:44 ` [PATCH 09/20] net: dsa: tag_xlnx_tsn: add skeleton tag protocol Nagadheeraj Rottela
2026-08-08 19:48 ` Jakub Kicinski
2026-08-07 10:44 ` [PATCH 10/20] net: dsa: xilinx: add skeleton driver for TSN switch Nagadheeraj Rottela
2026-08-07 10:44 ` [PATCH 11/20] net: dsa: xilinx: implement port_stp_state_set Nagadheeraj Rottela
2026-08-08 10:46 ` sashiko-bot
2026-08-08 19:48 ` Jakub Kicinski
2026-08-07 10:44 ` [PATCH 12/20] net: dsa: xilinx: register per-MAC MDIO buses Nagadheeraj Rottela
2026-08-08 10:46 ` sashiko-bot
2026-08-08 19:48 ` Jakub Kicinski
2026-08-07 10:44 ` [PATCH 13/20] net: dsa: xilinx: wire up phylink for the switch ports Nagadheeraj Rottela
2026-08-08 19:48 ` Jakub Kicinski
2026-08-07 10:44 ` [PATCH 14/20] net: dsa: xilinx: program MAC frame filter and per-port nibbles Nagadheeraj Rottela
2026-08-08 10:46 ` sashiko-bot
2026-08-08 19:48 ` Jakub Kicinski
2026-08-07 10:44 ` [PATCH 15/20] net: dsa: xilinx: register PHC backed by the RTC timer block Nagadheeraj Rottela
2026-08-08 10:46 ` sashiko-bot
2026-08-08 19:48 ` Jakub Kicinski
2026-08-07 10:44 ` [PATCH 16/20] net: dsa: xilinx: drive per-MAC PTP TX/RX hardware paths Nagadheeraj Rottela
2026-08-08 10:46 ` sashiko-bot
2026-08-08 19:48 ` Jakub Kicinski
2026-08-07 10:44 ` [PATCH 17/20] net: dsa: xilinx: opt into TX forwarding offload on bridge join Nagadheeraj Rottela
2026-08-08 10:46 ` sashiko-bot
2026-08-08 19:48 ` Jakub Kicinski
2026-08-07 10:44 ` [PATCH 18/20] net: dsa: xilinx: offload the bridge FDB to the switch CAM Nagadheeraj Rottela
2026-08-08 10:46 ` sashiko-bot
2026-08-08 19:48 ` Jakub Kicinski
2026-08-07 10:44 ` [PATCH 19/20] net: dsa: xilinx: offload bridge VLAN filtering to the switch Nagadheeraj Rottela
2026-08-08 10:46 ` sashiko-bot
2026-08-08 19:48 ` Jakub Kicinski
2026-08-07 10:44 ` [PATCH 20/20] net: dsa: xilinx: trap link-local control frames to the CPU port Nagadheeraj Rottela
2026-08-08 10:47 ` sashiko-bot
2026-08-08 19:48 ` Jakub Kicinski
2026-08-07 22:28 ` [PATCH 00/20] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support Jakub Kicinski
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260808194824.132828-1-kuba@kernel.org \
--to=kuba@kernel.org \
--cc=andrew+netdev@lunn.ch \
--cc=andrew@lunn.ch \
--cc=conor+dt@kernel.org \
--cc=davem@davemloft.net \
--cc=devicetree@vger.kernel.org \
--cc=edumazet@google.com \
--cc=git-dev@amd.com \
--cc=horms@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@armlinux.org.uk \
--cc=michal.simek@amd.com \
--cc=nagadheeraj.rottela@amd.com \
--cc=netdev@vger.kernel.org \
--cc=olteanv@gmail.com \
--cc=pabeni@redhat.com \
--cc=richardcochran@gmail.com \
--cc=robh@kernel.org \
--cc=srinivas.neeli@amd.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).