From: Srinivas Neeli <srinivas.neeli@amd.com>
To: Nagadheeraj Rottela <nagadheeraj.rottela@amd.com>,
Andrew Lunn <andrew+netdev@lunn.ch>,
"David S. Miller" <davem@davemloft.net>,
"Jakub Kicinski" <kuba@kernel.org>,
Paolo Abeni <pabeni@redhat.com>, Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Richard Cochran <richardcochran@gmail.com>,
"Michal Simek" <michal.simek@amd.com>,
Sebastian Andrzej Siewior <bigeasy@linutronix.de>,
Clark Williams <clrkwllms@kernel.org>,
"Steven Rostedt" <rostedt@goodmis.org>,
Eric Dumazet <edumazet@kernel.org>,
"Eric Dumazet" <edumazet@kernel.org>
Cc: <netdev@vger.kernel.org>, <devicetree@vger.kernel.org>,
<linux-kernel@vger.kernel.org>,
<linux-arm-kernel@lists.infradead.org>,
<linux-rt-devel@lists.linux.dev>,
Srinivas Neeli <srinivas.neeli@amd.com>,
<neelisrinivas18@gmail.com>, <git@amd.com>
Subject: [PATCH net-next v3 5/8] net: xilinx: tsn: bring up the endpoint MCDMA channels
Date: Fri, 16 Oct 2026 15:02:04 +0530 [thread overview]
Message-ID: <20261016-patches_v2_external-v3-5-e33d3960ca1e@amd.com> (raw)
In-Reply-To: <20261016-patches_v2_external-v3-0-e33d3960ca1e@amd.com>
Before the endpoint can move frames, each MCDMA channel needs a ring of
buffers and a live dmaengine channel.
Request every TX and RX channel named in the device tree at probe and
hold them for the lifetime of the device. dma_request_chan() returns
-EPROBE_DEFER until the MCDMA provider is bound, and probe is the only
context the driver core retries, so acquiring the channels here keeps
the deferral where it can be handled. Holding them also pins the
provider, so the channels cannot go away while the interface is down.
Reset the controller once at probe through the first held TX channel. An
MCDMA soft reset resets the whole core, so any channel of the instance
reaches it.
Give each channel a ring of preallocated SKB descriptor slots when the
interface opens, and tear the rings down on stop, where
dmaengine_terminate_sync() returns each channel to a clean state. Add
the XILINX_DMA dependency for the dmaengine and Xilinx DMA APIs.
Co-developed-by: Nagadheeraj Rottela <nagadheeraj.rottela@amd.com>
Signed-off-by: Nagadheeraj Rottela <nagadheeraj.rottela@amd.com>
Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>
---
Changes in v3:
- Acquire every TX and RX channel in probe and hold them until remove.
ndo_open now only builds the rings. dma_request_chan() is the call that
can return -EPROBE_DEFER and probe is the only context the driver core
retries, so the deferral no longer has to be translated in ndo_open.
Holding the channels also pins the MCDMA provider, so it cannot go away
while the interface is down.
- Reset the controller through the first held TX channel instead of
requesting and releasing a channel for the reset alone.
- Move the closing flag (field, kernel-doc and its ndo_open/ndo_stop
writes) to the RX data path patch, where it is first read. Drop the
unused io.h include and move circ_buf.h and spinlock.h to the RX patch
that first uses CIRC_SPACE() and spinlock_t.
- Reword the commit description for the probe-time channel acquisition.
Changes in v2:
- Request a channel at probe time so -EPROBE_DEFER stays in the probe path and
ndo_open never hands a deferral back to userspace.
- Request the channels by their real dma-names index, not a dense 0..N-1
sequence.
- Reword the commit message so the reset reads as probe-time through the first
mapped TX channel, not on open.
---
drivers/net/ethernet/xilinx/tsn/Kconfig | 1 +
drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c | 316 +++++++++++++++++++++++-
2 files changed, 315 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/xilinx/tsn/Kconfig b/drivers/net/ethernet/xilinx/tsn/Kconfig
index b8939d8a92ef..292f17694404 100644
--- a/drivers/net/ethernet/xilinx/tsn/Kconfig
+++ b/drivers/net/ethernet/xilinx/tsn/Kconfig
@@ -6,6 +6,7 @@
config XILINX_TSN
tristate "Xilinx TSN Ethernet driver"
depends on OF_ADDRESS
+ depends on XILINX_DMA
help
This driver supports the AMD/Xilinx Time-Sensitive Networking
(TSN) Endpoint Ethernet MAC IP. It provides the wrapper device
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
index 2adee07f6f51..ab3d6d8cb944 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
@@ -6,6 +6,9 @@
*/
#include <linux/bitops.h>
+#include <linux/dma/xilinx_dma.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/if_ether.h>
@@ -16,6 +19,8 @@
#include <linux/of.h>
#include <linux/of_net.h>
#include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
#include <linux/string.h>
#include <linux/types.h>
@@ -30,6 +35,47 @@
#define TSN_MAX_VLAN_FRAME_SIZE (ETH_DATA_LEN + VLAN_ETH_HLEN + \
ETH_FCS_LEN)
+#define TX_BD_NUM_DEFAULT 64
+#define RX_BD_NUM_DEFAULT 128
+
+/**
+ * struct skbuf_dma_descriptor - skb container for each in-flight DMA descriptor
+ * @sgl: scatter-gather list backing the DMA mapping
+ * @desc: dmaengine descriptor handle
+ * @dma_address: physical address of the first sgl entry (RX path)
+ * @skb: SKB owning the buffer
+ * @sg_len: number of valid entries in @sgl (TX path)
+ */
+struct skbuf_dma_descriptor {
+ struct scatterlist sgl[MAX_SKB_FRAGS + 1];
+ struct dma_async_tx_descriptor *desc;
+ dma_addr_t dma_address;
+ struct sk_buff *skb;
+ int sg_len;
+};
+
+/**
+ * struct xlnx_tsn_ep_dma_chan - one DMA channel and its SKB ring
+ * @skb_ring: per-slot SKB descriptors
+ * @ep: pointer back to the owning EP instance
+ * @chan: dmaengine channel handle
+ * @dma_dev: device used for DMA mapping (the DMA engine, not the EP)
+ * @ring_head: producer index
+ * @ring_tail: consumer index
+ * @ring_size: number of slots in @skb_ring
+ * @is_tx: true for TX channels, false for RX
+ */
+struct xlnx_tsn_ep_dma_chan {
+ struct skbuf_dma_descriptor **skb_ring;
+ struct xlnx_tsn_ep *ep;
+ struct dma_chan *chan;
+ struct device *dma_dev;
+ u32 ring_head;
+ u32 ring_tail;
+ u32 ring_size;
+ bool is_tx;
+};
+
/**
* struct xlnx_tsn_ep - EP MAC private data, embedded in net_device priv area
* @ndev: the conduit netdev
@@ -39,6 +85,10 @@
* @tx_dma_chan_map: TX queue to tx_chanN map, or TSN_DMA_CH_INVALID when
* the queue has no channel
* @max_frm_size: maximum frame size accepted on RX
+ * @tx_dma: dmaengine TX channels, held from probe until remove
+ * @rx_dma: dmaengine RX channels, held from probe until remove
+ * @tx_chans: array of TX channels (size @num_tx_queues)
+ * @rx_chans: array of RX channels (size @num_rx_queues)
*/
struct xlnx_tsn_ep {
struct net_device *ndev;
@@ -47,8 +97,21 @@ struct xlnx_tsn_ep {
u32 num_rx_queues;
u32 tx_dma_chan_map[TSN_MAX_TX_QUEUE];
u32 max_frm_size;
+
+ struct dma_chan *tx_dma[TSN_MAX_TX_QUEUE];
+ struct dma_chan *rx_dma[TSN_MAX_RX_QUEUE];
+
+ struct xlnx_tsn_ep_dma_chan **tx_chans;
+ struct xlnx_tsn_ep_dma_chan **rx_chans;
+
};
+static inline struct skbuf_dma_descriptor *
+ep_get_desc(struct xlnx_tsn_ep_dma_chan *xchan, int idx)
+{
+ return xchan->skb_ring[idx];
+}
+
static netdev_tx_t ep_start_xmit(struct sk_buff *skb, struct net_device *ndev)
{
dev_kfree_skb_any(skb);
@@ -56,8 +119,21 @@ static netdev_tx_t ep_start_xmit(struct sk_buff *skb, struct net_device *ndev)
return NETDEV_TX_OK;
}
+static int ep_init_dmaengine(struct xlnx_tsn_ep *ep);
+static void ep_exit_dmaengine(struct xlnx_tsn_ep *ep);
+
static int ep_open(struct net_device *ndev)
{
+ struct xlnx_tsn_ep *ep = netdev_priv(ndev);
+ int ret;
+
+ ret = ep_init_dmaengine(ep);
+ if (ret) {
+ netdev_err(ndev, "failed to initialize DMA engine\n");
+
+ return ret;
+ }
+
netif_tx_start_all_queues(ndev);
return 0;
@@ -65,7 +141,10 @@ static int ep_open(struct net_device *ndev)
static int ep_stop(struct net_device *ndev)
{
+ struct xlnx_tsn_ep *ep = netdev_priv(ndev);
+
netif_tx_disable(ndev);
+ ep_exit_dmaengine(ep);
return 0;
}
@@ -87,6 +166,228 @@ static const struct ethtool_ops ep_ethtool_ops = {
.get_drvinfo = ep_get_drvinfo,
};
+static struct xlnx_tsn_ep_dma_chan *
+ep_alloc_dma_chan(struct xlnx_tsn_ep *ep, struct dma_chan *dma_chan,
+ bool is_tx, int ring_size)
+{
+ struct xlnx_tsn_ep_dma_chan *chan;
+ int i;
+
+ chan = kzalloc_obj(*chan);
+ if (!chan)
+ return ERR_PTR(-ENOMEM);
+
+ chan->chan = dma_chan;
+
+ chan->skb_ring = kcalloc(ring_size, sizeof(*chan->skb_ring), GFP_KERNEL);
+ if (!chan->skb_ring) {
+ kfree(chan);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ for (i = 0; i < ring_size; i++) {
+ chan->skb_ring[i] = kzalloc_obj(*chan->skb_ring[i]);
+ if (!chan->skb_ring[i]) {
+ while (--i >= 0)
+ kfree(chan->skb_ring[i]);
+ kfree(chan->skb_ring);
+ kfree(chan);
+ return ERR_PTR(-ENOMEM);
+ }
+ }
+
+ chan->is_tx = is_tx;
+ chan->ep = ep;
+ chan->ring_size = ring_size;
+ chan->dma_dev = dmaengine_get_dma_device(chan->chan);
+
+ return chan;
+}
+
+static void ep_free_dma_chan(struct xlnx_tsn_ep_dma_chan *chan)
+{
+ int i;
+
+ if (!chan)
+ return;
+
+ if (chan->chan)
+ dmaengine_terminate_sync(chan->chan);
+
+ if (chan->is_tx) {
+ while (chan->ring_tail != chan->ring_head) {
+ struct skbuf_dma_descriptor *skbuf_dma;
+
+ skbuf_dma = chan->skb_ring[chan->ring_tail &
+ (chan->ring_size - 1)];
+ if (skbuf_dma && skbuf_dma->skb) {
+ dma_unmap_sg(chan->dma_dev, skbuf_dma->sgl,
+ skbuf_dma->sg_len, DMA_TO_DEVICE);
+ dev_kfree_skb_any(skbuf_dma->skb);
+ skbuf_dma->skb = NULL;
+ }
+ chan->ring_tail++;
+ }
+ }
+
+ if (chan->skb_ring) {
+ for (i = 0; i < chan->ring_size; i++) {
+ struct skbuf_dma_descriptor *skbuf_dma = chan->skb_ring[i];
+
+ if (skbuf_dma && !chan->is_tx && skbuf_dma->skb) {
+ dma_unmap_single(chan->dma_dev,
+ skbuf_dma->dma_address,
+ chan->ep->max_frm_size,
+ DMA_FROM_DEVICE);
+ dev_kfree_skb_any(skbuf_dma->skb);
+ }
+ kfree(chan->skb_ring[i]);
+ }
+ kfree(chan->skb_ring);
+ }
+ kfree(chan);
+}
+
+static void ep_exit_dmaengine(struct xlnx_tsn_ep *ep)
+{
+ int i;
+
+ if (ep->tx_chans) {
+ for (i = 0; i < ep->num_tx_queues; i++)
+ ep_free_dma_chan(ep->tx_chans[i]);
+ kfree(ep->tx_chans);
+ ep->tx_chans = NULL;
+ }
+ if (ep->rx_chans) {
+ for (i = 0; i < ep->num_rx_queues; i++)
+ ep_free_dma_chan(ep->rx_chans[i]);
+ kfree(ep->rx_chans);
+ ep->rx_chans = NULL;
+ }
+}
+
+static int ep_init_dmaengine(struct xlnx_tsn_ep *ep)
+{
+ int tx_allocated = 0, rx_allocated = 0;
+ int i, ret;
+
+ ep->tx_chans = kcalloc(ep->num_tx_queues, sizeof(*ep->tx_chans),
+ GFP_KERNEL);
+ if (!ep->tx_chans)
+ return -ENOMEM;
+
+ ep->rx_chans = kcalloc(ep->num_rx_queues, sizeof(*ep->rx_chans),
+ GFP_KERNEL);
+ if (!ep->rx_chans) {
+ ret = -ENOMEM;
+ goto err_free_tx;
+ }
+
+ for (i = 0; i < ep->num_tx_queues; i++) {
+ ep->tx_chans[i] = ep_alloc_dma_chan(ep, ep->tx_dma[i], true,
+ TX_BD_NUM_DEFAULT);
+ if (IS_ERR(ep->tx_chans[i])) {
+ ret = PTR_ERR(ep->tx_chans[i]);
+ ep->tx_chans[i] = NULL;
+ goto err_free_chans;
+ }
+ tx_allocated++;
+ }
+
+ for (i = 0; i < ep->num_rx_queues; i++) {
+ ep->rx_chans[i] = ep_alloc_dma_chan(ep, ep->rx_dma[i], false,
+ RX_BD_NUM_DEFAULT);
+ if (IS_ERR(ep->rx_chans[i])) {
+ ret = PTR_ERR(ep->rx_chans[i]);
+ ep->rx_chans[i] = NULL;
+ goto err_free_chans;
+ }
+ rx_allocated++;
+ }
+
+ return 0;
+
+err_free_chans:
+ while (--rx_allocated >= 0)
+ ep_free_dma_chan(ep->rx_chans[rx_allocated]);
+ while (--tx_allocated >= 0)
+ ep_free_dma_chan(ep->tx_chans[tx_allocated]);
+ kfree(ep->rx_chans);
+ ep->rx_chans = NULL;
+err_free_tx:
+ kfree(ep->tx_chans);
+ ep->tx_chans = NULL;
+ return ret;
+}
+
+static void ep_release_dma_chans(struct xlnx_tsn_ep *ep)
+{
+ unsigned int i;
+
+ for (i = 0; i < ep->num_tx_queues; i++) {
+ if (ep->tx_dma[i]) {
+ dma_release_channel(ep->tx_dma[i]);
+ ep->tx_dma[i] = NULL;
+ }
+ }
+
+ for (i = 0; i < ep->num_rx_queues; i++) {
+ if (ep->rx_dma[i]) {
+ dma_release_channel(ep->rx_dma[i]);
+ ep->rx_dma[i] = NULL;
+ }
+ }
+}
+
+static int ep_request_dma_chans(struct xlnx_tsn_ep *ep,
+ const u32 *rx_dma_chan_map)
+{
+ struct dma_chan *chan;
+ char name[16];
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < ep->num_tx_queues; i++) {
+ snprintf(name, sizeof(name), "tx_chan%u", ep->tx_dma_chan_map[i]);
+ chan = dma_request_chan(ep->dev, name);
+ if (IS_ERR(chan)) {
+ ret = PTR_ERR(chan);
+ goto err_release;
+ }
+ ep->tx_dma[i] = chan;
+ }
+
+ for (i = 0; i < ep->num_rx_queues; i++) {
+ snprintf(name, sizeof(name), "rx_chan%u", rx_dma_chan_map[i]);
+ chan = dma_request_chan(ep->dev, name);
+ if (IS_ERR(chan)) {
+ ret = PTR_ERR(chan);
+ goto err_release;
+ }
+ ep->rx_dma[i] = chan;
+ }
+
+ return 0;
+
+err_release:
+ ep_release_dma_chans(ep);
+
+ return dev_err_probe(ep->dev, ret, "failed to request %s\n", name);
+}
+
+static int ep_reset_dma_controller(struct xlnx_tsn_ep *ep)
+{
+ struct xilinx_vdma_config cfg = { .reset = 1 };
+ int ret;
+
+ ret = xilinx_vdma_channel_set_config(ep->tx_dma[0], &cfg);
+ if (ret < 0)
+ return dev_err_probe(ep->dev, ret,
+ "failed to reset DMA controller\n");
+
+ return 0;
+}
+
/*
* Parse the "tx-queues-config" child of the EP node. The logical queue
* index is taken from the "queue<N>" node name, so the mapping does not
@@ -286,9 +587,17 @@ static int xlnx_tsn_ep_probe(struct platform_device *pdev)
if (ret)
goto err_free_ndev;
+ ret = ep_request_dma_chans(ep, rx_dma_chan_map);
+ if (ret)
+ goto err_free_ndev;
+
+ ret = ep_reset_dma_controller(ep);
+ if (ret)
+ goto err_release_chans;
+
ret = of_get_mac_address(dev->of_node, mac_addr);
if (ret == -EPROBE_DEFER) {
- goto err_free_ndev;
+ goto err_release_chans;
} else if (!ret && is_valid_ether_addr(mac_addr)) {
eth_hw_addr_set(ndev, mac_addr);
} else {
@@ -302,11 +611,13 @@ static int xlnx_tsn_ep_probe(struct platform_device *pdev)
ret = register_netdev(ndev);
if (ret) {
dev_err_probe(dev, ret, "failed to register net device\n");
- goto err_free_ndev;
+ goto err_release_chans;
}
return 0;
+err_release_chans:
+ ep_release_dma_chans(ep);
err_free_ndev:
free_netdev(ndev);
return ret;
@@ -320,6 +631,7 @@ static void xlnx_tsn_ep_remove(struct platform_device *pdev)
return;
unregister_netdev(ep->ndev);
+ ep_release_dma_chans(ep);
free_netdev(ep->ndev);
}
--
2.44.4
next prev parent reply other threads:[~2026-10-16 9:33 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-16 9:31 [PATCH net-next v3 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
2026-10-16 9:32 ` [PATCH net-next v3 1/8] dt-bindings: net: add Xilinx TSN Endpoint Ethernet MAC Srinivas Neeli
2026-10-16 9:32 ` [PATCH net-next v3 2/8] net: xilinx: tsn: add TSN endpoint wrapper driver Srinivas Neeli
2026-10-16 9:32 ` [PATCH net-next v3 3/8] net: xilinx: tsn: add endpoint MAC driver skeleton Srinivas Neeli
2026-10-16 9:32 ` [PATCH net-next v3 4/8] net: xilinx: tsn: parse endpoint DMA channel configuration Srinivas Neeli
2026-10-16 9:32 ` Srinivas Neeli [this message]
2026-10-16 9:32 ` [PATCH net-next v3 6/8] net: xilinx: tsn: add the endpoint RX data path Srinivas Neeli
2026-10-16 9:32 ` [PATCH net-next v3 7/8] net: xilinx: tsn: add the endpoint TX " Srinivas Neeli
2026-10-16 9:32 ` [PATCH net-next v3 8/8] net: xilinx: tsn: deliver endpoint RX frames to DSA user ports Srinivas Neeli
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=20261016-patches_v2_external-v3-5-e33d3960ca1e@amd.com \
--to=srinivas.neeli@amd.com \
--cc=andrew+netdev@lunn.ch \
--cc=bigeasy@linutronix.de \
--cc=clrkwllms@kernel.org \
--cc=conor+dt@kernel.org \
--cc=davem@davemloft.net \
--cc=devicetree@vger.kernel.org \
--cc=edumazet@kernel.org \
--cc=git@amd.com \
--cc=krzk+dt@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-rt-devel@lists.linux.dev \
--cc=michal.simek@amd.com \
--cc=nagadheeraj.rottela@amd.com \
--cc=neelisrinivas18@gmail.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=richardcochran@gmail.com \
--cc=robh@kernel.org \
--cc=rostedt@goodmis.org \
/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