* [PATCH net-next v4 2/8] net: xilinx: tsn: add TSN endpoint wrapper driver
[not found] <20261113-patches_v2_external-v4-0-56bb26f042a4@amd.com>
@ 2026-11-12 18:46 ` Srinivas Neeli
2026-11-12 18:46 ` [PATCH net-next v4 5/8] net: xilinx: tsn: bring up the endpoint MCDMA channels Srinivas Neeli
2026-11-12 18:46 ` [PATCH net-next v4 8/8] net: xilinx: tsn: deliver endpoint RX frames to DSA user ports Srinivas Neeli
2 siblings, 0 replies; 3+ messages in thread
From: Srinivas Neeli @ 2026-11-12 18:46 UTC (permalink / raw)
To: Nagadheeraj Rottela, Andrew Lunn, David S. Miller,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Richard Cochran, Michal Simek,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Eric Dumazet, Eric Dumazet
Cc: netdev, devicetree, linux-kernel, linux-arm-kernel,
linux-rt-devel, Srinivas Neeli, git, neelisrinivas18
The TSN Endpoint Ethernet MAC IP groups several sub-nodes under one IP
node with a set of IP-wide clocks. Each sub-node needs its own driver,
and the shared clocks must be running before any of them probe.
Add a thin wrapper platform driver for the IP node. Enable the IP-wide
clocks from the device tree and bind each sub-node to its own driver.
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 v4:
- Drop the Kconfig reference to a companion DSA driver that is not in
this series.
Changes in v2:
- Depend on OF_ADDRESS instead of OF && HAS_IOMEM.
- Point the Kconfig help text at the companion DSA driver, not a symbol that
does not exist yet at this patch.
- Drop the mod_devicetable.h include, platform_device.h already pulls it in.
---
MAINTAINERS | 1 +
drivers/net/ethernet/xilinx/Kconfig | 1 +
drivers/net/ethernet/xilinx/Makefile | 1 +
drivers/net/ethernet/xilinx/tsn/Kconfig | 14 +++
drivers/net/ethernet/xilinx/tsn/Makefile | 2 +
drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c | 104 ++++++++++++++++++++++
6 files changed, 123 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 6e71049322df..27f42178c12d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -30748,6 +30748,7 @@ M: Srinivas Neeli <srinivas.neeli@amd.com>
L: netdev@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
+F: drivers/net/ethernet/xilinx/tsn/
XILINX UARTLITE SERIAL DRIVER
M: Peter Korsgaard <jacmet@sunsite.dk>
diff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig
index 7502214cc7d5..c6d704c8d3d4 100644
--- a/drivers/net/ethernet/xilinx/Kconfig
+++ b/drivers/net/ethernet/xilinx/Kconfig
@@ -41,4 +41,5 @@ config XILINX_LL_TEMAC
This driver supports the Xilinx 10/100/1000 LocalLink TEMAC
core used in Xilinx Spartan and Virtex FPGAs
+source "drivers/net/ethernet/xilinx/tsn/Kconfig"
endif # NET_VENDOR_XILINX
diff --git a/drivers/net/ethernet/xilinx/Makefile b/drivers/net/ethernet/xilinx/Makefile
index 7d7dc1771423..66dab012650b 100644
--- a/drivers/net/ethernet/xilinx/Makefile
+++ b/drivers/net/ethernet/xilinx/Makefile
@@ -8,3 +8,4 @@ obj-$(CONFIG_XILINX_LL_TEMAC) += ll_temac.o
obj-$(CONFIG_XILINX_EMACLITE) += xilinx_emaclite.o
xilinx_emac-objs := xilinx_axienet_main.o xilinx_axienet_mdio.o
obj-$(CONFIG_XILINX_AXI_EMAC) += xilinx_emac.o
+obj-$(CONFIG_XILINX_TSN) += tsn/
diff --git a/drivers/net/ethernet/xilinx/tsn/Kconfig b/drivers/net/ethernet/xilinx/tsn/Kconfig
new file mode 100644
index 000000000000..3164d1ce7caa
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/Kconfig
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Xilinx TSN device configuration
+#
+
+config XILINX_TSN
+ tristate "Xilinx TSN Ethernet driver"
+ depends on OF_ADDRESS
+ help
+ This driver supports the AMD/Xilinx Time-Sensitive Networking
+ (TSN) Endpoint Ethernet MAC IP. It provides the wrapper device
+ and the endpoint MAC that connects the IP to the host CPU.
+
+ If unsure, say N.
diff --git a/drivers/net/ethernet/xilinx/tsn/Makefile b/drivers/net/ethernet/xilinx/tsn/Makefile
new file mode 100644
index 000000000000..6f99226f3dc8
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_XILINX_TSN) += xilinx_tsn.o
+xilinx_tsn-y := xilinx_tsn_main.o
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
new file mode 100644
index 000000000000..afe7609c67fb
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Time Sensitive Networking (TSN) Ethernet MAC wrapper driver.
+ *
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#define TSN_NUM_CLOCKS 6
+
+/**
+ * struct xlnx_tsn_ip - wrapper-private IP state
+ * @clks: bulk-managed IP clocks
+ */
+struct xlnx_tsn_ip {
+ struct clk_bulk_data clks[TSN_NUM_CLOCKS];
+};
+
+static const char * const tsn_clk_names[TSN_NUM_CLOCKS] = {
+ "gtx",
+ "gtx90",
+ "host_rxfifo",
+ "host_txfifo",
+ "ref",
+ "s_axi",
+};
+
+static void tsn_clk_bulk_disable(void *data)
+{
+ struct xlnx_tsn_ip *w = data;
+
+ clk_bulk_disable_unprepare(TSN_NUM_CLOCKS, w->clks);
+}
+
+static int tsn_ip_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct xlnx_tsn_ip *w;
+ int ret;
+
+ w = devm_kzalloc(dev, sizeof(*w), GFP_KERNEL);
+ if (!w)
+ return -ENOMEM;
+
+ for (int i = 0; i < TSN_NUM_CLOCKS; i++)
+ w->clks[i].id = tsn_clk_names[i];
+
+ ret = devm_clk_bulk_get(dev, TSN_NUM_CLOCKS, w->clks);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get clocks\n");
+
+ ret = clk_bulk_prepare_enable(TSN_NUM_CLOCKS, w->clks);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable clocks\n");
+
+ ret = devm_add_action_or_reset(dev, tsn_clk_bulk_disable, w);
+ if (ret)
+ return ret;
+
+ return devm_of_platform_populate(dev);
+}
+
+static const struct of_device_id tsn_of_match[] = {
+ { .compatible = "xlnx,tsn-endpoint-ethernet-mac-3.0" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tsn_of_match);
+
+static struct platform_driver tsn_driver = {
+ .probe = tsn_ip_probe,
+ .driver = {
+ .name = "xilinx-tsn",
+ .of_match_table = tsn_of_match,
+ },
+};
+
+static struct platform_driver * const tsn_drivers[] = {
+ &tsn_driver,
+};
+
+static int __init xlnx_tsn_init(void)
+{
+ return platform_register_drivers(tsn_drivers, ARRAY_SIZE(tsn_drivers));
+}
+module_init(xlnx_tsn_init);
+
+static void __exit xlnx_tsn_exit(void)
+{
+ platform_unregister_drivers(tsn_drivers, ARRAY_SIZE(tsn_drivers));
+}
+module_exit(xlnx_tsn_exit);
+
+MODULE_AUTHOR("Srinivas Neeli <srinivas.neeli@amd.com>");
+MODULE_DESCRIPTION("AMD/Xilinx TSN Endpoint Ethernet MAC wrapper driver");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH net-next v4 5/8] net: xilinx: tsn: bring up the endpoint MCDMA channels
[not found] <20261113-patches_v2_external-v4-0-56bb26f042a4@amd.com>
2026-11-12 18:46 ` [PATCH net-next v4 2/8] net: xilinx: tsn: add TSN endpoint wrapper driver Srinivas Neeli
@ 2026-11-12 18:46 ` Srinivas Neeli
2026-11-12 18:46 ` [PATCH net-next v4 8/8] net: xilinx: tsn: deliver endpoint RX frames to DSA user ports Srinivas Neeli
2 siblings, 0 replies; 3+ messages in thread
From: Srinivas Neeli @ 2026-11-12 18:46 UTC (permalink / raw)
To: Nagadheeraj Rottela, Andrew Lunn, David S. Miller,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Richard Cochran, Michal Simek,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Eric Dumazet, Eric Dumazet
Cc: netdev, devicetree, linux-kernel, linux-arm-kernel,
linux-rt-devel, Srinivas Neeli, git, neelisrinivas18
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 v4:
- Describe @dma_address as the DMA address of the first sgl entry. It
holds a dma_addr_t from the DMA mapping, not a physical address.
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 3164d1ce7caa..673c86ca99cd 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 9458db38255d..52d2da04a5d8 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>
@@ -31,6 +36,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: DMA 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
@@ -40,6 +86,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;
@@ -48,8 +98,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);
@@ -57,8 +120,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;
@@ -66,7 +142,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;
}
@@ -88,6 +167,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
@@ -291,9 +592,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 {
@@ -307,11 +616,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;
@@ -325,6 +636,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.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH net-next v4 8/8] net: xilinx: tsn: deliver endpoint RX frames to DSA user ports
[not found] <20261113-patches_v2_external-v4-0-56bb26f042a4@amd.com>
2026-11-12 18:46 ` [PATCH net-next v4 2/8] net: xilinx: tsn: add TSN endpoint wrapper driver Srinivas Neeli
2026-11-12 18:46 ` [PATCH net-next v4 5/8] net: xilinx: tsn: bring up the endpoint MCDMA channels Srinivas Neeli
@ 2026-11-12 18:46 ` Srinivas Neeli
2 siblings, 0 replies; 3+ messages in thread
From: Srinivas Neeli @ 2026-11-12 18:46 UTC (permalink / raw)
To: Nagadheeraj Rottela, Andrew Lunn, David S. Miller,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Richard Cochran, Michal Simek,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Eric Dumazet, Eric Dumazet
Cc: netdev, devicetree, linux-kernel, linux-arm-kernel,
linux-rt-devel, Srinivas Neeli, git, neelisrinivas18
From: Nagadheeraj Rottela <nagadheeraj.rottela@amd.com>
The endpoint MAC is the DSA conduit for the three-port TSN switch. DSA
selects an RX frame's user port from the port metadata on the skb, so a
frame from either MAC needs that metadata to reach its swpN netdev.
Allocate a METADATA_HW_PORT_MUX entry per MAC port at probe and attach it
to each RX frame by its TUSER port_id (1 for MAC1, 2 for MAC2), guarded by
netdev_uses_dsa() so it runs only while a switch is bound as the conduit.
The RX callback runs in softirq and hands the frame to __netif_rx(), which
queues it on the backlog, so the attached dst must be a counted reference
(dst_hold() + skb_dst_set()) to survive the handoff.
Signed-off-by: Nagadheeraj Rottela <nagadheeraj.rottela@amd.com>
Co-developed-by: Srinivas Neeli <srinivas.neeli@amd.com>
Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>
---
drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c | 55 ++++++++++++++++++++++++-
1 file changed, 54 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
index 6e2721a74f28..0f4fd965b987 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
@@ -28,6 +28,8 @@
#include <linux/timer.h>
#include <linux/types.h>
+#include <net/dsa.h>
+#include <net/dst_metadata.h>
#include <net/netdev_queues.h>
#include "xilinx_tsn.h"
@@ -58,6 +60,9 @@
#define TSN_TUSER_PORT_MAC1 0x1
#define TSN_TUSER_PORT_MAC2 0x2
+/* Sized to index port_md[] by TUSER port_id (1 or 2, slot 0 unused) */
+#define XLNX_TSN_EP_PORT_MD_SLOTS (TSN_TUSER_PORT_MAC2 + 1)
+
/**
* struct skbuf_dma_descriptor - skb container for each in-flight DMA descriptor
* @sgl: scatter-gather list backing the DMA mapping
@@ -117,6 +122,8 @@ struct xlnx_tsn_ep_dma_chan {
* @rx_chans: array of RX channels (size @num_rx_queues)
* @closing: set in ndo_stop so the RX completion callback stops re-arming
* and the TX completion callback stops waking a stopped queue
+ * @port_md: per-TUSER-port METADATA_HW_PORT_MUX entries attached on RX,
+ * indexed by port_id (1 for MAC1, 2 for MAC2)
*/
struct xlnx_tsn_ep {
struct net_device *ndev;
@@ -133,6 +140,8 @@ struct xlnx_tsn_ep {
struct xlnx_tsn_ep_dma_chan **rx_chans;
bool closing;
+
+ struct metadata_dst *port_md[XLNX_TSN_EP_PORT_MD_SLOTS];
};
static inline struct skbuf_dma_descriptor *
@@ -304,6 +313,10 @@ static void ep_dma_rx_cb(void *data, const struct dmaengine_result *result)
}
skb_put(skb, rx_len);
+ if (netdev_uses_dsa(ndev)) {
+ dst_hold(&ep->port_md[port_id]->dst);
+ skb_dst_set(skb, &ep->port_md[port_id]->dst);
+ }
skb->dev = ndev;
skb->protocol = eth_type_trans(skb, ndev);
skb_checksum_none_assert(skb);
@@ -907,6 +920,37 @@ static int ep_count_dma_channels(struct device *dev, u32 *out_tx, u32 *out_rx,
return 0;
}
+static void ep_free_port_md(struct xlnx_tsn_ep *ep)
+{
+ int i;
+
+ for (i = 0; i < XLNX_TSN_EP_PORT_MD_SLOTS; i++) {
+ if (ep->port_md[i]) {
+ dst_release(&ep->port_md[i]->dst);
+ ep->port_md[i] = NULL;
+ }
+ }
+}
+
+static int ep_alloc_port_md(struct xlnx_tsn_ep *ep)
+{
+ int i;
+
+ for (i = TSN_TUSER_PORT_MAC1; i <= TSN_TUSER_PORT_MAC2; i++) {
+ struct metadata_dst *md;
+
+ md = metadata_dst_alloc(0, METADATA_HW_PORT_MUX, GFP_KERNEL);
+ if (!md) {
+ ep_free_port_md(ep);
+ return -ENOMEM;
+ }
+ md->u.port_info.port_id = i;
+ ep->port_md[i] = md;
+ }
+
+ return 0;
+}
+
static int xlnx_tsn_ep_probe(struct platform_device *pdev)
{
u32 num_tx_dma_channels, num_rx_dma_channels, num_prio;
@@ -996,16 +1040,24 @@ static int xlnx_tsn_ep_probe(struct platform_device *pdev)
ndev->dev_addr);
}
+ ret = ep_alloc_port_md(ep);
+ if (ret) {
+ dev_err_probe(dev, ret, "failed to allocate per-port metadata\n");
+ goto err_release_chans;
+ }
+
platform_set_drvdata(pdev, ep);
ret = register_netdev(ndev);
if (ret) {
dev_err_probe(dev, ret, "failed to register net device\n");
- goto err_release_chans;
+ goto err_free_md;
}
return 0;
+err_free_md:
+ ep_free_port_md(ep);
err_release_chans:
ep_release_dma_chans(ep);
err_free_ndev:
@@ -1021,6 +1073,7 @@ static void xlnx_tsn_ep_remove(struct platform_device *pdev)
return;
unregister_netdev(ep->ndev);
+ ep_free_port_md(ep);
ep_release_dma_chans(ep);
free_netdev(ep->ndev);
}
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread