* [PATCH net-next v4 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver
@ 2026-11-12 18:46 Srinivas Neeli
2026-11-12 18:46 ` [PATCH net-next v4 2/8] net: xilinx: tsn: add TSN endpoint wrapper driver Srinivas Neeli
` (5 more replies)
0 siblings, 6 replies; 7+ 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
Introduce a new network driver for the AMD LogiCORE 100M/1G TSN
Subsystem IP, also known as the TSN Endpoint Ethernet MAC IP, which
implements IEEE 802.1 Time-Sensitive Networking (TSN) features for
deterministic and low-latency Ethernet communication in real-time and
industrial automation use cases.
The v1 posting sent all 20 patches (wrapper, endpoint and the DSA switch)
as one series. Following the review feedback to split it, this series
sends only the wrapper and endpoint side (patches 1-8). The DSA switch
driver, tag protocol, MDIO, phylink, PTP and bridge offloads will follow
as a separate series once this base lands.
IP Core Overview:
The AMD LogiCORE 100M/1G TSN Subsystem IP solution (named as TSN Endpoint
Ethernet MAC IP in the IP catalog) implements IEEE 802.1 Time Sensitive
Networking (TSN) Standards and provides a low latency Bridged Endpoint or
Endpoint only solutions. The bridged endpoint solution consists of a 3-port
switch that connects to an endpoint including Linux software drivers. For
Bridged Endpoint (Switch Endpoint), two ports connects to the network and
one port connects to an internal Endpoint. It supports the use of
GMII/RGMII interfaces connecting to a physical-side interface (PHY) chip
with full duplex 100 Mb/s and 1 Gb/s operations.
Features:
- IP Customizable to generate TSN Endpoint or Bridged Endpoint solution
- Supports interface to network software stack
- Designed to comply with the following IEEE standards
- 802.1AS : Precision Time Protocol (PTP) synchronization
- 802.1Qav : Credit-Based Shaper for traffic shaping
- 802.1Qbv : Time-Aware Shaper for scheduled traffic
- 802.1Qbu : Frame Preemption for ultra-low latency
- 802.1CB : Frame Replication and Elimination for redundancy
- 802.1Qcc : Stream Reservation Protocol (SRP)
- IP is customizable to enable various TSN feature-sets
- Supports 2 to 8 priority queues for scheduled, reserved (IEEE 802.1 Qav
Credit Based Shaper) and best effort traffic classes
- Provides feature rich Ethernet Switch that caters to various network
needs
* 3-port Switch (2-external, 1-internal)
* Programmable cut-through and store-forward operations
* 4-port Switch (2-external, 2-internal) extension through
'Endpoint Extension' and 'Endpoint Packet Switching' features
- Solution validated on ZCU102 & ZC702 AMD Evaluation boards.
Sample hardware architecture diagram for Bridge End Point like below:
+------------------+
| MCDMA |
+---------+--------+
Q0---Q7
|
+------------------------------------------------------------ +
| | TSN sub system(Bridge End Point) |
| | |
| +------+----+ Port 0 +-----------------------+ |
| | EndPoint |<--------->| TSN Switch | |
| | (EP) | +----+-------------+----+ |
| +-----------+ | | |
| | | |
| Port 1 Port 2 |
| | | |
| +-----------+ +-----------+ |
| | MAC-1 | | MAC-2 | |
| | (ETH1) | | (ETH2) | |
| +-----+-----+ +-----+-----+ |
| | | |
| | | |
+-------------------------------------------------------------+
| |
RGMII RGMII
| |
+-----------+ +-----------+
| PHY1 | | PHY2 |
| (Port 0) | | (Port 2) |
+-----------+ +-----------+
The endpoint is capable of Qbv scheduling and is connected to hardware
that implements time-triggered per-stream scheduling as specified in
IEEE 802.1Qcc-2018 Annex U.1.3.
Software Driver Overview:
This series adds the wrapper and endpoint side of the IP. The switch-facing
part is exposed through the Distributed Switch Architecture (DSA) framework
in a follow-up series, so that MAC1 and MAC2 show up to Linux as switch
ports with the endpoint acting as the CPU/conduit port. The endpoint frames
are already classified by source-port id and tagged with DSA port metadata
here, so the switch series can consume them without further endpoint
change.
Since the endpoint must be exposed to users for Qbv and time-triggered
per-stream schedule configuration, it requires a user-facing netdev,
which also aligns well with the DSA model. In addition, the hardware
lacks a destination-port selector and does not provide an independent
per-port forwarding mask. As a result, standalone port isolation is
achieved using a private VLAN that contains only the selected port and
the endpoint. The DSA framework enables reuse of the tag_8021q helpers to
manage these VLANs, insert the destination-port VLAN tag (which is
stripped by the hardware before egress on the wire), and seamlessly
handle transitions between standalone and bridged operation.
Wrapper driver:
- Binds the top-level IP node, acquires and enables the IP-block clocks.
- Spawns the endpoint (and, later, switch) blocks as child platform
devices via devm_of_platform_populate() and carries no netdev or
register access of its own.
Endpoint driver:
- Implements the conduit net_device that Linux DMA traffic flows through
on its way to and from the switch fabric.
- Manages multi-queue TX and RX descriptor rings using the dmaengine
framework for high-performance packet transfer.
- Classifies each received frame by the source-port id carried in the RX
descriptor sideband metadata and attaches DSA port metadata so the DSA
core can demux it to the right user port netdev.
Changes in v4:
- Rebase onto net-next, which now carries the AXI MCDMA dmaengine changes
merged for v7.4-rc1. Commit 5b585cc87781 ("dmaengine: xilinx_dma: Move
descriptors to done list based on completion bit") keeps RX buffers
owned by the engine until their descriptor completes, and
commit 5f677afc9567 ("dmaengine: xilinx_dma: Extend metadata handling
for AXI DMA and MCDMA") provides the RX sideband metadata the endpoint
reads. The series no longer has an external dependency.
- binding: bound the queueN children from above as well as below, so a
device tree with more queues than xlnx,num-priorities is rejected, and
state that each priority queue must use a distinct TX channel. Add the
optional xlnx,max-learning-entries switch property.
- Kconfig: drop the reference to the companion DSA driver, which is not
part of this series.
- endpoint MAC: use alloc_etherdev() so the eth%d name is registered with
NET_NAME_ENUM.
- endpoint DMA parse: use alloc_etherdev_mqs() for the multi-queue
netdev, and reject xlnx,num-priorities outside the 2..8 range the
binding allows.
- endpoint MCDMA bring-up: describe @dma_address as a DMA address in the
kernel-doc.
- endpoint RX: report the ring as armed when the refill submitted at least
one descriptor, so ndo_open no longer fails when a buffer completes
before the ring is tested.
- endpoint TX: call skb_tx_timestamp() before dmaengine_submit(), add
.get_ts_info so ethtool reports software TX timestamps, and terminate
the TX channels before netif_tx_disable() in ndo_stop so a completion
cannot wake a queue while the channels are freed. Extend the @closing
kernel-doc to the TX completion.
- Link to v3: https://patch.msgid.link/20261016-patches_v2_external-v3-0-e33d3960ca1e@amd.com
Changes in v3:
- cover letter: explain why the switch-facing part is implemented as DSA,
what the endpoint netdev provides beyond being the conduit, and how RX
demultiplexing and TX port selection map onto the hardware.
- binding: rename ports to ethernet-ports and port@0..2 to
ethernet-port@0..2, the preferred names for a new binding. Require reg
on the CPU ethernet-port@0 and pin reg to the unit address on the
external ports (literal ethernet-port@1 reg=1 and ethernet-port@2 reg=2
instead of a shared enum). Require queue0 in tx-queues-config, and add
contains rules so dma-names must carry at least one tx_chan and one
rx_chan.
- endpoint MAC: register the netdev with the standard eth%d name
(NET_NAME_UNKNOWN) instead of a custom "ep%d".
- endpoint DMA parse: state the queueN to TX channel mapping in the commit
body rather than only in the changelog. Rename the counting helper to
ep_count_dma_channels() and the RX map to rx_dma_chan_map, keep the RX
map in a probe local, and document the TSN_DMA_CH_INVALID sentinel in
the tx_dma_chan_map kernel-doc.
- endpoint MCDMA bring-up: acquire every TX and RX channel at probe and
hold them until remove, so ndo_open only builds the rings and no
deferral can reach userspace. Reset the controller through the first
held channel. Move the closing flag and the circ_buf.h and spinlock.h
includes to the RX patch that first uses them, and drop the unused io.h
include.
- endpoint RX: check the dmaengine_submit() cookie before advancing
ring_head, and free the buffer if the submit fails. Use scoped_guard()
rather than guard() for the rx_lock, per the netdev cleanup.h guidance.
Introduce the closing flag here, alongside its first reader.
- endpoint TX: free a DMA-error frame with dev_kfree_skb_any() and a
completed one with dev_consume_skb_any(). Add skb_tx_timestamp() at the
hardware handoff. Skip the queue wake while closing, using
__netif_txq_completed_wake() with the closing flag.
- endpoint RX to DSA: reword the commit message to describe the metadata
this patch adds, and drop the claim that RX frames already reach the
swpN netdevs.
- Link to v2: https://patch.msgid.link/20260909-patches_v2_external-v2-0-3a40babaff4c@amd.com
- This series has a dependency on the MCDMA patches, which are currently
not present in net-next:
https://lore.kernel.org/all/20260818174151.1608971-1-srinivas.neeli@amd.com
Changes in v2:
- Split the original 20-patch series. This v2 carries only the wrapper and
endpoint (patches 1-8). The DSA switch driver, tag protocol and offloads
will be posted as a separate follow-up series.
- binding: describe only the Bridged Endpoint configuration, drop reg from
the wrapper node (it has none of its own, ranges covers the child
windows), rename ep-mac@ to ethernet@ and mdio-mac1/2 to mdio-1/2, cap
the DMA channels at 8 (dma-names maxItems 16, tx_chan/rx_chan [0-7],
dma-channel-num max 7), use a ports/ethernet-ports container and require
port@0, port@1 and port@2, add reg (enum [1, 2]) and require reg,
phy-mode and phy-handle on the external ports (gmii/rgmii/rgmii-id), and
add minItems on the switch interrupts.
- Kconfig: depend on OF_ADDRESS instead of OF && HAS_IOMEM, point the help
text at the companion DSA driver rather than a symbol that does not exist
yet, and drop the mod_devicetable.h include (platform_device.h pulls it
in).
- endpoint: name the netdev ep%d instead of a fixed "ep", drop the mapped
but unused register window, use dev_kfree_skb_any() on the drop path, and
drop the mod_devicetable.h include.
- endpoint DMA: check xlnx,dma-channel-num against the channels dma-names
lists, require one queue per tx_chan and num-priorities equal to the TX
channel count, cap TX/RX at 8, error out on an unknown dma-names entry,
and drop the unreachable count guards.
- endpoint MCDMA bring-up: request a channel at probe time so -EPROBE_DEFER
stays in the probe path and never reaches ndo_open, and request channels
by their real dma-names index instead of a dense 0..N-1 sequence.
- endpoint RX: serialise the refill against teardown under a per-channel
lock, recover a drained ring with a refill timer instead of stalling,
fail ndo_open if a channel cannot arm any descriptor, take the ring size
from the channel, shut the refill timer down with timer_shutdown_sync()
at teardown, and return true from the refill helper when it arms a
descriptor.
- endpoint TX: take the scatterlist count from skb_to_sgvec(), check
dmaengine_submit() before advancing the ring and charging BQL, publish
the ring head and tail with WRITE_ONCE(), move tx_lock into the
per-channel struct so independent TX queues do not share one lock, use
the spinlock_bh guard class in the completion callback, and use
dmaengine_prep_slave_sg().
- endpoint RX to DSA: attach the port metadata only when the netdev is a
DSA conduit, take a counted dst reference (dst_hold() + skb_dst_set()) so
the dst survives the __netif_rx() backlog handoff without skb_dst_force()
in softirq, and release it with dst_release() at teardown.
Previous versions:
v1 (full 20-patch series, now split):
https://lore.kernel.org/lkml/20260807104431.157230-1-nagadheeraj.rottela@amd.com/
Test information:
- Validated these changes on the net-next branch with the MCDMA metadata
patches cherry-picked from linux-next, along with the series listed below
https://lore.kernel.org/all/20260713072146.45269-1-srinivas.neeli@amd.com/
- The endpoint RX path depends on AXI MCDMA metadata support in the
dmaengine driver. On plain net-next, xilinx_mcdma_prep_slave_sg() does
not attach metadata_ops and desc_metadata_modes stays 0 for
XDMA_TYPE_AXIMCDMA, so dmaengine_desc_get_metadata_ptr() returns
-ENOTSUPP and ep_dma_rx_cb() would drop every received frame. The series
therefore depends on the MCDMA metadata support that extends metadata_ops
to XDMA_TYPE_AXIMCDMA and returns the descriptor status word (index 0 is
status, index 1 is the sideband word carrying TID/TDEST/TUSER, index 2
onward is the app words). That dmaengine work is a prerequisite for RX to
function and the TSN patches touch no dmaengine code. It will be posted
to the dmaengine tree and this series carries a Depends-on note plus a
lore link once it is on-list.
- Performed ping, iperf3 and multi-queue TX/RX tests on the ZynqMP ZCU102
platform, with the endpoint conduit up and both external MACs cabled to
peer NICs. Also ran teardown and probe-defer stress under KASAN and
lockdep with no use-after-free or locking splats.
Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>
---
Nagadheeraj Rottela (1):
net: xilinx: tsn: deliver endpoint RX frames to DSA user ports
Srinivas Neeli (7):
dt-bindings: net: add Xilinx TSN Endpoint Ethernet MAC
net: xilinx: tsn: add TSN endpoint wrapper driver
net: xilinx: tsn: add endpoint MAC driver skeleton
net: xilinx: tsn: parse endpoint DMA channel configuration
net: xilinx: tsn: bring up the endpoint MCDMA channels
net: xilinx: tsn: add the endpoint RX data path
net: xilinx: tsn: add the endpoint TX data path
.../net/xlnx,tsn-endpoint-ethernet-mac.yaml | 536 ++++++++++
MAINTAINERS | 8 +
drivers/net/ethernet/xilinx/Kconfig | 1 +
drivers/net/ethernet/xilinx/Makefile | 1 +
drivers/net/ethernet/xilinx/tsn/Kconfig | 15 +
drivers/net/ethernet/xilinx/tsn/Makefile | 2 +
drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h | 15 +
drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c | 1094 ++++++++++++++++++++
drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c | 107 ++
9 files changed, 1779 insertions(+)
---
base-commit: 6584acd42820478910a771efb28a355a707e9cb0
change-id: 20260831-patches_v2_external-36421e557430
Best regards,
--
Srinivas Neeli <srinivas.neeli@amd.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net-next v4 2/8] net: xilinx: tsn: add TSN endpoint wrapper driver
2026-11-12 18:46 [PATCH net-next v4 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
@ 2026-11-12 18:46 ` Srinivas Neeli
2026-11-12 18:46 ` [PATCH net-next v4 4/8] net: xilinx: tsn: parse endpoint DMA channel configuration Srinivas Neeli
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ 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] 7+ messages in thread
* [PATCH net-next v4 4/8] net: xilinx: tsn: parse endpoint DMA channel configuration
2026-11-12 18:46 [PATCH net-next v4 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
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 5/8] net: xilinx: tsn: bring up the endpoint MCDMA channels Srinivas Neeli
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ 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 endpoint has one MCDMA channel per TX queue and per RX queue, and
the device tree lists them in "dma-names". The transmit path needs to
know which channel backs each logical TX queue.
Count the TX and RX channels in "dma-names" and allocate the conduit
netdev with that many hardware queues. Parse the "tx-queues-config"
child node to map each logical TX queue to its channel.
The device tree describes one "queueN" child per TX channel under
"tx-queues-config". Each child names its channel with
"xlnx,dma-channel-num", given as the N of the tx_chanN entry in
"dma-names". Every logical TX queue must be described exactly once and
name a distinct channel that "dma-names" lists, and the TX channel count
must equal "xlnx,num-priorities" on the parent IP node. Probe fails with
dev_err_probe() when any of that does not hold.
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:
- Use alloc_etherdev_mqs() so the standard eth%d name is registered with
NET_NAME_ENUM while preserving the TX and RX queue counts.
- Reject xlnx,num-priorities outside the 2..8 range the binding allows.
A value of 1 used to probe with a single TX queue.
Changes in v3:
- State the device tree requirements in the commit body. One "queueN"
child per TX channel naming its channel with "xlnx,dma-channel-num",
every TX queue described exactly once, and the TX channel count equal
to "xlnx,num-priorities". They were only in the v2 changelog before.
- Correct the kernel-doc for tx_dma_chan_map. It holds the N of a tx_chanN
entry in "dma-names", not a hardware channel number.
- Name the counting helper ep_count_dma_channels() and its probe results
num_tx_dma_channels and num_rx_dma_channels. Both count MCDMA channels
listed in "dma-names", which "queues" did not convey.
- Call the RX map rx_dma_chan_map, matching tx_dma_chan_map. Both hold the
N of a chanN entry in "dma-names", so one concept now has one name.
- Keep the RX map in a probe local rather than copying it into the private
struct. Nothing outside probe reads it.
Changes in v2:
- Check xlnx,dma-channel-num against the channels that dma-names actually
lists, using a presence bitmask. Reject duplicates and out-of-range channels,
and index the channel arrays by logical queue.
- Require one queue node per tx_chan and num-priorities equal to the TX channel
count. This also gives xlnx,num-priorities a consumer.
- Cap TX and RX at 8 to match the binding.
- Error out on an unknown dma-names entry instead of skipping it.
- Drop the unreachable TX and RX count guards. The in-loop index and duplicate
checks already bound the count, so the channel arrays cannot be over-indexed.
---
drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c | 203 +++++++++++++++++++++++-
1 file changed, 202 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 b6458bb0ba0f..9458db38255d 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
@@ -5,9 +5,11 @@
* Copyright (C) 2026 Advanced Micro Devices, Inc.
*/
+#include <linux/bitops.h>
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/if_ether.h>
+#include <linux/if_vlan.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netdevice.h>
@@ -21,14 +23,31 @@
#define DRIVER_NAME "xilinx_tsn_ep"
+#define TSN_DMA_CH_INVALID 0xFFU
+#define TSN_MIN_PRIORITIES 2
+#define TSN_MAX_TX_QUEUE 8
+#define TSN_MAX_RX_QUEUE 8
+
+#define TSN_MAX_VLAN_FRAME_SIZE (ETH_DATA_LEN + VLAN_ETH_HLEN + \
+ ETH_FCS_LEN)
+
/**
* struct xlnx_tsn_ep - EP MAC private data, embedded in net_device priv area
* @ndev: the conduit netdev
* @dev: backing device
+ * @num_tx_queues: number of TX DMA channels (one per priority)
+ * @num_rx_queues: number of RX DMA channels
+ * @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
*/
struct xlnx_tsn_ep {
struct net_device *ndev;
struct device *dev;
+ u32 num_tx_queues;
+ u32 num_rx_queues;
+ u32 tx_dma_chan_map[TSN_MAX_TX_QUEUE];
+ u32 max_frm_size;
};
static netdev_tx_t ep_start_xmit(struct sk_buff *skb, struct net_device *ndev)
@@ -69,15 +88,180 @@ static const struct ethtool_ops ep_ethtool_ops = {
.get_drvinfo = ep_get_drvinfo,
};
+/*
+ * 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
+ * depend on the order the child nodes appear in the device tree.
+ */
+static int ep_parse_tx_queue_config(struct xlnx_tsn_ep *ep,
+ struct device_node *txcfg_np, u16 tx_present)
+{
+ DECLARE_BITMAP(queue_seen, TSN_MAX_TX_QUEUE) = {};
+ DECLARE_BITMAP(chan_seen, TSN_MAX_TX_QUEUE) = {};
+ unsigned int count = 0;
+ int ret;
+
+ for_each_child_of_node_scoped(txcfg_np, qnode) {
+ u32 chan, queue;
+
+ if (!str_has_prefix(qnode->name, "queue") ||
+ kstrtou32(qnode->name + strlen("queue"), 10, &queue) ||
+ queue >= ep->num_tx_queues)
+ return dev_err_probe(ep->dev, -EINVAL,
+ "tx-config: invalid queue node %pOFn (have %u queues)\n",
+ qnode, ep->num_tx_queues);
+
+ if (test_and_set_bit(queue, queue_seen))
+ return dev_err_probe(ep->dev, -EINVAL,
+ "tx-config: queue %u described twice\n",
+ queue);
+
+ ret = of_property_read_u32(qnode, "xlnx,dma-channel-num", &chan);
+ if (ret)
+ return dev_err_probe(ep->dev, ret,
+ "tx-config: queue %u missing xlnx,dma-channel-num\n",
+ queue);
+
+ if (chan >= TSN_MAX_TX_QUEUE || !(tx_present & BIT(chan)))
+ return dev_err_probe(ep->dev, -EINVAL,
+ "tx-config: queue %u maps to channel %u not present in dma-names\n",
+ queue, chan);
+
+ if (test_and_set_bit(chan, chan_seen))
+ return dev_err_probe(ep->dev, -EINVAL,
+ "tx-config: channel %u already assigned to another queue\n",
+ chan);
+
+ ep->tx_dma_chan_map[queue] = chan;
+ count++;
+ }
+
+ if (count != ep->num_tx_queues)
+ return dev_err_probe(ep->dev, -EINVAL,
+ "tx-config: described %u queues but expected %u\n",
+ count, ep->num_tx_queues);
+
+ return 0;
+}
+
+static int ep_count_dma_channels(struct device *dev, u32 *out_tx, u32 *out_rx,
+ u16 *tx_present, u32 *rx_dma_chan_map)
+{
+ u32 tx = 0, rx = 0;
+ u16 rx_present = 0;
+ int n, i, ret;
+
+ n = of_property_count_strings(dev->of_node, "dma-names");
+ if (n < 0)
+ return dev_err_probe(dev, n, "failed to read dma-names\n");
+
+ for (i = 0; i < n; i++) {
+ const char *name;
+ size_t plen;
+ u32 idx;
+
+ ret = of_property_read_string_index(dev->of_node, "dma-names",
+ i, &name);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read dma-names[%d]\n", i);
+
+ plen = str_has_prefix(name, "tx_chan");
+ if (plen) {
+ if (kstrtou32(name + plen, 10, &idx) ||
+ idx >= TSN_MAX_TX_QUEUE)
+ return dev_err_probe(dev, -EINVAL,
+ "invalid TX channel name %s\n",
+ name);
+
+ if (*tx_present & BIT(idx))
+ return dev_err_probe(dev, -EINVAL,
+ "duplicate TX channel %s\n",
+ name);
+
+ *tx_present |= BIT(idx);
+ tx++;
+ continue;
+ }
+
+ plen = str_has_prefix(name, "rx_chan");
+ if (plen) {
+ if (kstrtou32(name + plen, 10, &idx) ||
+ idx >= TSN_MAX_RX_QUEUE)
+ return dev_err_probe(dev, -EINVAL,
+ "invalid RX channel name %s\n",
+ name);
+
+ if (rx_present & BIT(idx))
+ return dev_err_probe(dev, -EINVAL,
+ "duplicate RX channel %s\n",
+ name);
+
+ rx_present |= BIT(idx);
+ rx_dma_chan_map[rx] = idx;
+ rx++;
+ continue;
+ }
+
+ return dev_err_probe(dev, -EINVAL,
+ "unrecognised dma-names entry %s\n", name);
+ }
+
+ if (!tx)
+ return dev_err_probe(dev, -EINVAL,
+ "no TX channels in dma-names\n");
+
+ if (!rx)
+ return dev_err_probe(dev, -EINVAL,
+ "no RX channels in dma-names\n");
+
+ *out_tx = tx;
+ *out_rx = rx;
+
+ return 0;
+}
+
static int xlnx_tsn_ep_probe(struct platform_device *pdev)
{
+ u32 num_tx_dma_channels, num_rx_dma_channels, num_prio;
+ u32 rx_dma_chan_map[TSN_MAX_RX_QUEUE];
struct device *dev = &pdev->dev;
+ struct device_node *txcfg_np;
+ struct device_node *ip_np;
struct net_device *ndev;
struct xlnx_tsn_ep *ep;
u8 mac_addr[ETH_ALEN];
+ u16 tx_present = 0;
int ret;
+ int i;
+
+ ret = ep_count_dma_channels(dev, &num_tx_dma_channels,
+ &num_rx_dma_channels, &tx_present,
+ rx_dma_chan_map);
+ if (ret)
+ return ret;
+
+ ip_np = of_get_parent(dev->of_node);
+ if (!ip_np)
+ return dev_err_probe(dev, -EINVAL, "missing parent IP node\n");
+
+ ret = of_property_read_u32(ip_np, "xlnx,num-priorities", &num_prio);
+ of_node_put(ip_np);
+ if (ret)
+ return dev_err_probe(dev, ret, "missing xlnx,num-priorities\n");
+
+ if (num_prio < TSN_MIN_PRIORITIES || num_prio > TSN_MAX_TX_QUEUE)
+ return dev_err_probe(dev, -EINVAL,
+ "invalid xlnx,num-priorities %u\n",
+ num_prio);
- ndev = alloc_etherdev(sizeof(*ep));
+ if (num_tx_dma_channels != num_prio)
+ return dev_err_probe(dev, -EINVAL,
+ "TX channel count %u must equal num-priorities %u\n",
+ num_tx_dma_channels, num_prio);
+
+ ndev = alloc_etherdev_mqs(sizeof(*ep), num_tx_dma_channels,
+ num_rx_dma_channels);
if (!ndev)
return -ENOMEM;
@@ -89,6 +273,23 @@ static int xlnx_tsn_ep_probe(struct platform_device *pdev)
ep = netdev_priv(ndev);
ep->ndev = ndev;
ep->dev = dev;
+ ep->num_tx_queues = num_tx_dma_channels;
+ ep->num_rx_queues = num_rx_dma_channels;
+ ep->max_frm_size = TSN_MAX_VLAN_FRAME_SIZE;
+
+ for (i = 0; i < TSN_MAX_TX_QUEUE; i++)
+ ep->tx_dma_chan_map[i] = TSN_DMA_CH_INVALID;
+
+ txcfg_np = of_get_child_by_name(dev->of_node, "tx-queues-config");
+ if (!txcfg_np) {
+ ret = dev_err_probe(dev, -EINVAL,
+ "missing tx-queues-config node\n");
+ goto err_free_ndev;
+ }
+ ret = ep_parse_tx_queue_config(ep, txcfg_np, tx_present);
+ of_node_put(txcfg_np);
+ if (ret)
+ goto err_free_ndev;
ret = of_get_mac_address(dev->of_node, mac_addr);
if (ret == -EPROBE_DEFER) {
--
2.43.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net-next v4 5/8] net: xilinx: tsn: bring up the endpoint MCDMA channels
2026-11-12 18:46 [PATCH net-next v4 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
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 4/8] net: xilinx: tsn: parse endpoint DMA channel configuration Srinivas Neeli
@ 2026-11-12 18:46 ` Srinivas Neeli
2026-11-12 18:46 ` [PATCH net-next v4 6/8] net: xilinx: tsn: add the endpoint RX data path Srinivas Neeli
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ 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] 7+ messages in thread
* [PATCH net-next v4 6/8] net: xilinx: tsn: add the endpoint RX data path
2026-11-12 18:46 [PATCH net-next v4 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
` (2 preceding siblings ...)
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
2026-11-12 18:46 ` [PATCH net-next v4 7/8] net: xilinx: tsn: add the endpoint TX " 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
5 siblings, 0 replies; 7+ 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
With the RX channels armed, the endpoint can start receiving frames.
Post RX buffers to every RX channel on open and reap them from the
completion callback. Each inbound frame carries a TUSER port_id in the
descriptor sideband. Accept frames from MAC1 (port_id 1) and MAC2
(port_id 2) and pass them up the stack. Drop any other port_id, since
the bridged-only model never produces it. Drop and count frames with
bad descriptor status, an invalid length or no metadata pointer.
Refill each slot from the completion callback unless the interface is
closing, which keeps the ring full without a separate refill worker.
Serialise the RX refill against teardown. The completion callback re-arms
the ring, so ndo_stop() sets a closing flag before teardown and the refill
tests it under the per-channel lock. ep_free_dma_chan() then takes that
lock as a barrier, so any refill already past the closing test finishes
and no later one submits or arms the timer before the channel is
terminated. No descriptor is submitted once teardown has begun, so a single
dmaengine_terminate_sync() is enough to quiesce the channel.
The RX path uses __netif_rx() from the dmaengine completion callback
rather than NAPI. dmaengine owns the RX interrupt and ring and delivers
one descriptor per callback, so there is no poll context to run NAPI or
GRO, and the per-packet counters use the dev->stats atomics.
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:
- Return true from ep_rx_refill() when this call submitted at least one
descriptor. The completion callback advances ring_tail without rx_lock,
so a descriptor could complete before the ring_head != ring_tail test
and make ndo_open() fail with -ENOMEM.
- Use dev_dbg_ratelimited() for the unexpected port_id message, so it does
not consume the shared net_ratelimit() budget, and replace the redundant
CHECKSUM_NONE store with skb_checksum_none_assert().
Changes in v3:
- Check the dmaengine_submit() cookie in ep_rx_submit_desc(). Store the skb
and submit, then advance ring_head only once dma_submit_error() is clear.
On a submit failure clear the slot, unmap and free the buffer and return
the error, so a failed descriptor can no longer pin its buffer or be read
back by a later completion.
- Use scoped_guard() instead of guard() in ep_rx_refill() for the rx_lock,
per the netdev cleanup.h guidance (guard() is discouraged in functions
longer than 20 lines).
- Introduce the closing flag here (field, kernel-doc and the
ndo_open/ndo_stop writes), together with its first reader in the RX
refill path. It was added in the MCDMA bring-up patch before it had a
consumer. Add the circ_buf.h and spinlock.h includes here, where
CIRC_SPACE() and spinlock_t are first used.
- Reword the commit message to describe how the closing flag is published
and tested, rather than stating that ndo_stop() sets it under the
per-channel lock.
Changes in v2:
- Serialise the RX refill against teardown under a per-channel rx_lock. The
completion callback re-arms the ring, so ndo_stop() sets a closing flag under
the same lock and ep_free_dma_chan() takes it as a barrier before terminating.
- Recover a drained ring with a refill timer instead of leaving RX dead after
an allocation failure.
- Fail ndo_open if a channel cannot arm any RX descriptor.
- Set the closing flag on the ndo_open error path too, before tearing the
channels down.
- Take the ring size from the channel instead of a compile-time constant.
- Note in the commit message why RX is not NAPI based. dmaengine owns the RX
interrupt and ring and delivers one descriptor per callback, so there is no
poll context for NAPI or GRO, and the counters use the dev->stats atomics.
- Shut the refill timer down with timer_shutdown_sync() at teardown rather than
relying on timer_delete_sync() plus the closing flag.
- Return true from ep_rx_refill() when at least one descriptor is armed, false
otherwise, following the usual bool convention.
- Init the refill timer and rx_lock only for RX channels.
---
drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c | 230 +++++++++++++++++++++++-
1 file changed, 229 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 52d2da04a5d8..f179062c7d14 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
@@ -5,7 +5,9 @@
* Copyright (C) 2026 Advanced Micro Devices, Inc.
*/
+#include <linux/bitfield.h>
#include <linux/bitops.h>
+#include <linux/circ_buf.h>
#include <linux/dma/xilinx_dma.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
@@ -21,7 +23,9 @@
#include <linux/platform_device.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
+#include <linux/spinlock.h>
#include <linux/string.h>
+#include <linux/timer.h>
#include <linux/types.h>
#include "xilinx_tsn.h"
@@ -39,6 +43,19 @@
#define TX_BD_NUM_DEFAULT 64
#define RX_BD_NUM_DEFAULT 128
+#define EP_RX_REFILL_RETRY msecs_to_jiffies(10)
+
+/*
+ * The DMA descriptor sideband status word packs TID/TDEST/TUSER together;
+ * TUSER occupies the low byte, TID/TDEST sit in the upper bits.
+ */
+#define TSN_TUSER_MASK GENMASK(7, 0)
+/* TUSER Input Port ID field (bits [5:4] of the TUSER field) */
+#define TSN_TUSER_PORT_ID_MASK GENMASK(5, 4)
+#define TSN_TUSER_PORT_EP 0x0
+#define TSN_TUSER_PORT_MAC1 0x1
+#define TSN_TUSER_PORT_MAC2 0x2
+
/**
* struct skbuf_dma_descriptor - skb container for each in-flight DMA descriptor
* @sgl: scatter-gather list backing the DMA mapping
@@ -64,6 +81,8 @@ struct skbuf_dma_descriptor {
* @ring_head: producer index
* @ring_tail: consumer index
* @ring_size: number of slots in @skb_ring
+ * @rx_lock: serialises @ring_head between the RX callback and the refill timer
+ * @rx_refill_timer: retries RX refill after an allocation failure
* @is_tx: true for TX channels, false for RX
*/
struct xlnx_tsn_ep_dma_chan {
@@ -74,6 +93,8 @@ struct xlnx_tsn_ep_dma_chan {
u32 ring_head;
u32 ring_tail;
u32 ring_size;
+ spinlock_t rx_lock; /* serialises @ring_head */
+ struct timer_list rx_refill_timer;
bool is_tx;
};
@@ -90,6 +111,7 @@ struct xlnx_tsn_ep_dma_chan {
* @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)
+ * @closing: set in ndo_stop so the RX completion callback stops re-arming
*/
struct xlnx_tsn_ep {
struct net_device *ndev;
@@ -105,6 +127,7 @@ struct xlnx_tsn_ep {
struct xlnx_tsn_ep_dma_chan **tx_chans;
struct xlnx_tsn_ep_dma_chan **rx_chans;
+ bool closing;
};
static inline struct skbuf_dma_descriptor *
@@ -113,6 +136,181 @@ ep_get_desc(struct xlnx_tsn_ep_dma_chan *xchan, int idx)
return xchan->skb_ring[idx];
}
+static void ep_dma_rx_cb(void *data, const struct dmaengine_result *result);
+
+static int ep_rx_submit_desc(struct xlnx_tsn_ep_dma_chan *xchan)
+{
+ struct dma_async_tx_descriptor *dma_rx_desc;
+ struct skbuf_dma_descriptor *skbuf_dma;
+ struct xlnx_tsn_ep *ep = xchan->ep;
+ struct sk_buff *skb;
+ dma_cookie_t cookie;
+ dma_addr_t addr;
+
+ skbuf_dma = ep_get_desc(xchan, xchan->ring_head & (xchan->ring_size - 1));
+ if (!skbuf_dma)
+ return -ENOSPC;
+
+ skb = dev_alloc_skb(ep->max_frm_size);
+ if (!skb)
+ return -ENOMEM;
+
+ sg_init_table(skbuf_dma->sgl, 1);
+ addr = dma_map_single(xchan->dma_dev, skb->data, ep->max_frm_size,
+ DMA_FROM_DEVICE);
+ if (unlikely(dma_mapping_error(xchan->dma_dev, addr))) {
+ if (net_ratelimit())
+ dev_warn(ep->dev, "DMA mapping error on RX submit\n");
+
+ goto err_free_skb;
+ }
+ sg_dma_address(skbuf_dma->sgl) = addr;
+ sg_dma_len(skbuf_dma->sgl) = ep->max_frm_size;
+ dma_rx_desc = dmaengine_prep_slave_sg(xchan->chan, skbuf_dma->sgl,
+ 1, DMA_DEV_TO_MEM,
+ DMA_PREP_INTERRUPT);
+ if (!dma_rx_desc)
+ goto err_unmap_skb;
+
+ skbuf_dma->dma_address = sg_dma_address(skbuf_dma->sgl);
+ skbuf_dma->desc = dma_rx_desc;
+ dma_rx_desc->callback_param = xchan;
+ dma_rx_desc->callback_result = ep_dma_rx_cb;
+
+ skbuf_dma->skb = skb;
+ cookie = dmaengine_submit(dma_rx_desc);
+ if (dma_submit_error(cookie)) {
+ skbuf_dma->skb = NULL;
+ goto err_unmap_skb;
+ }
+ xchan->ring_head++;
+
+ return 0;
+
+err_unmap_skb:
+ dma_unmap_single(xchan->dma_dev, addr, ep->max_frm_size, DMA_FROM_DEVICE);
+err_free_skb:
+ dev_kfree_skb(skb);
+ return -ENOMEM;
+}
+
+static bool ep_rx_refill(struct xlnx_tsn_ep_dma_chan *xchan, bool arm_timer)
+{
+ int avail, i;
+
+ scoped_guard(spinlock_bh, &xchan->rx_lock) {
+ if (READ_ONCE(xchan->ep->closing))
+ return false;
+
+ avail = CIRC_SPACE(xchan->ring_head, READ_ONCE(xchan->ring_tail),
+ xchan->ring_size);
+ for (i = 0; i < avail; i++) {
+ if (ep_rx_submit_desc(xchan))
+ break;
+ }
+ dma_async_issue_pending(xchan->chan);
+
+ if (i > 0 || xchan->ring_head != READ_ONCE(xchan->ring_tail))
+ return true;
+
+ if (arm_timer)
+ mod_timer(&xchan->rx_refill_timer,
+ jiffies + EP_RX_REFILL_RETRY);
+ }
+
+ return false;
+}
+
+static void ep_rx_refill_timer(struct timer_list *t)
+{
+ struct xlnx_tsn_ep_dma_chan *xchan = timer_container_of(xchan, t,
+ rx_refill_timer);
+
+ ep_rx_refill(xchan, true);
+}
+
+static void ep_dma_rx_cb(void *data, const struct dmaengine_result *result)
+{
+ struct xlnx_tsn_ep_dma_chan *xchan = data;
+ struct skbuf_dma_descriptor *skbuf_dma;
+ size_t meta_len, meta_max_len, rx_len;
+ struct xlnx_tsn_ep *ep = xchan->ep;
+ struct net_device *ndev = ep->ndev;
+ struct sk_buff *skb;
+ u32 port_id, tuser;
+ u32 *metadata;
+
+ skbuf_dma = ep_get_desc(xchan, xchan->ring_tail & (xchan->ring_size - 1));
+ WRITE_ONCE(xchan->ring_tail, xchan->ring_tail + 1);
+ skb = skbuf_dma->skb;
+ skbuf_dma->skb = NULL;
+
+ dma_unmap_single(xchan->dma_dev, skbuf_dma->dma_address,
+ ep->max_frm_size, DMA_FROM_DEVICE);
+
+ if (result->result != DMA_TRANS_NOERROR) {
+ if (net_ratelimit())
+ dev_warn(ep->dev, "RX DMA transfer error %d\n",
+ result->result);
+
+ dev_kfree_skb_any(skb);
+ DEV_STATS_INC(ndev, rx_dropped);
+ DEV_STATS_INC(ndev, rx_errors);
+ goto submit_new;
+ }
+
+ metadata = dmaengine_desc_get_metadata_ptr(skbuf_dma->desc,
+ &meta_len,
+ &meta_max_len);
+ if (IS_ERR_OR_NULL(metadata)) {
+ if (net_ratelimit())
+ dev_warn(ep->dev, "Failed to get RX metadata pointer\n");
+
+ dev_kfree_skb_any(skb);
+ DEV_STATS_INC(ndev, rx_dropped);
+ DEV_STATS_INC(ndev, rx_errors);
+ goto submit_new;
+ }
+
+ /* MCDMA metadata: [0] = status, [1] = sideband (TID/TDEST/TUSER), [2..] = app */
+ tuser = metadata[1] & TSN_TUSER_MASK;
+ rx_len = ep->max_frm_size - result->residue;
+
+ if (rx_len > ep->max_frm_size || rx_len < ETH_HLEN) {
+ if (net_ratelimit())
+ dev_warn(ep->dev, "Invalid RX length %zu (max=%u, min=%u)\n",
+ rx_len, ep->max_frm_size, ETH_HLEN);
+
+ dev_kfree_skb_any(skb);
+ DEV_STATS_INC(ndev, rx_dropped);
+ DEV_STATS_INC(ndev, rx_errors);
+ goto submit_new;
+ }
+
+ port_id = FIELD_GET(TSN_TUSER_PORT_ID_MASK, tuser);
+ if (port_id != TSN_TUSER_PORT_MAC1 && port_id != TSN_TUSER_PORT_MAC2) {
+ dev_dbg_ratelimited(ep->dev,
+ "RX dropping unexpected TUSER port_id=%u\n",
+ port_id);
+
+ dev_kfree_skb_any(skb);
+ DEV_STATS_INC(ndev, rx_dropped);
+ goto submit_new;
+ }
+
+ skb_put(skb, rx_len);
+ skb->dev = ndev;
+ skb->protocol = eth_type_trans(skb, ndev);
+ skb_checksum_none_assert(skb);
+ __netif_rx(skb);
+
+ DEV_STATS_INC(ndev, rx_packets);
+ DEV_STATS_ADD(ndev, rx_bytes, rx_len);
+
+submit_new:
+ ep_rx_refill(xchan, true);
+}
+
static netdev_tx_t ep_start_xmit(struct sk_buff *skb, struct net_device *ndev)
{
dev_kfree_skb_any(skb);
@@ -128,6 +326,8 @@ static int ep_open(struct net_device *ndev)
struct xlnx_tsn_ep *ep = netdev_priv(ndev);
int ret;
+ WRITE_ONCE(ep->closing, false);
+
ret = ep_init_dmaengine(ep);
if (ret) {
netdev_err(ndev, "failed to initialize DMA engine\n");
@@ -145,6 +345,7 @@ static int ep_stop(struct net_device *ndev)
struct xlnx_tsn_ep *ep = netdev_priv(ndev);
netif_tx_disable(ndev);
+ WRITE_ONCE(ep->closing, true);
ep_exit_dmaengine(ep);
return 0;
@@ -201,6 +402,10 @@ ep_alloc_dma_chan(struct xlnx_tsn_ep *ep, struct dma_chan *dma_chan,
chan->ep = ep;
chan->ring_size = ring_size;
chan->dma_dev = dmaengine_get_dma_device(chan->chan);
+ if (!is_tx) {
+ spin_lock_init(&chan->rx_lock);
+ timer_setup(&chan->rx_refill_timer, ep_rx_refill_timer, 0);
+ }
return chan;
}
@@ -212,8 +417,21 @@ static void ep_free_dma_chan(struct xlnx_tsn_ep_dma_chan *chan)
if (!chan)
return;
- if (chan->chan)
+ if (chan->chan) {
+ if (!chan->is_tx) {
+ /* ep_stop() sets closing before teardown. Take rx_lock
+ * so any refill that already passed the closing check
+ * finishes and no later one submits or arms the timer,
+ * then shut down the timer so it cannot be rearmed
+ * before the channel is freed.
+ */
+ spin_lock_bh(&chan->rx_lock);
+ spin_unlock_bh(&chan->rx_lock);
+ timer_shutdown_sync(&chan->rx_refill_timer);
+ }
+
dmaengine_terminate_sync(chan->chan);
+ }
if (chan->is_tx) {
while (chan->ring_tail != chan->ring_head) {
@@ -306,9 +524,19 @@ static int ep_init_dmaengine(struct xlnx_tsn_ep *ep)
rx_allocated++;
}
+ for (i = 0; i < ep->num_rx_queues; i++) {
+ if (!ep_rx_refill(ep->rx_chans[i], false)) {
+ dev_err(ep->dev, "RX channel %d: no descriptors armed\n",
+ i);
+ ret = -ENOMEM;
+ goto err_free_chans;
+ }
+ }
+
return 0;
err_free_chans:
+ WRITE_ONCE(ep->closing, true);
while (--rx_allocated >= 0)
ep_free_dma_chan(ep->rx_chans[rx_allocated]);
while (--tx_allocated >= 0)
--
2.43.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net-next v4 7/8] net: xilinx: tsn: add the endpoint TX data path
2026-11-12 18:46 [PATCH net-next v4 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
` (3 preceding siblings ...)
2026-11-12 18:46 ` [PATCH net-next v4 6/8] net: xilinx: tsn: add the endpoint RX data path 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
5 siblings, 0 replies; 7+ 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
Add the transmit path so the endpoint can send frames.
Select the TX channel by indexing tx_chans[] with the frame's logical
queue. ep_init_dmaengine() fills that array in logical-queue order, so the
index maps to the channel that queue's descriptors were set up on. The
driver consults tx_dma_chan_map[] only to reject a queue with no mapping.
Map the frame and submit it to that channel. Stop the queue when the ring
runs out of space, and wake it from the completion callback once a slot
opens.
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:
- Call skb_tx_timestamp() before dmaengine_submit(). The provider can
start a pending descriptor from the IRQ of an earlier transfer, so the
completion callback could free the skb before the timestamp was taken.
- Add .get_ts_info with ethtool_op_get_ts_info() so ethtool reports the
software TX timestamps the driver now generates.
- Terminate the TX channels in ndo_stop() before netif_tx_disable(). A
completion that read the closing flag as false then finishes before the
queues are stopped and can no longer wake one while the channels are
freed.
- Extend the @closing kernel-doc to the TX completion callback, which
tests it before waking a stopped queue.
Changes in v3:
- Free a DMA-error TX frame with dev_kfree_skb_any() and a completed one
with dev_consume_skb_any(), so dropped frames are not reported as
delivered.
- Add skb_tx_timestamp() at the hardware handoff for software TX
timestamps.
- Skip the queue wake in the completion callback while closing. Use
__netif_txq_completed_wake() with the closing flag, and set ep->closing
before netif_tx_disable().
- Reword the body to state that tx_chans[] is indexed by logical queue, and
drop the description of a runtime channel lookup that no longer exists.
Changes in v2:
- Take the scatterlist entry count from skb_to_sgvec() instead of assuming
nr_frags + 1, so dma_map_sg() cannot walk past the end of the list.
- Check dmaengine_submit() with dma_submit_error() before advancing the ring
head and charging BQL, and drop the frame if the submit fails.
- Publish the ring head with WRITE_ONCE(), paired with the READ_ONCE() on the
completion side.
- Take the ring size from the channel instead of the compile-time constant.
- Use dmaengine_prep_slave_sg() instead of calling the ops member directly.
- Move tx_lock into the per-channel struct, mirroring rx_lock, so independent
TX queues do not serialise on one device-wide lock.
- Narrow the tx_lock kernel-doc to what it actually protects.
- Mark the ring_tail store in the completion callback with WRITE_ONCE() to
match the READ_ONCE() readers.
- Use the spinlock_bh guard class in the completion callback so it matches the
spin_lock_bh() in the xmit path.
- Index tx_chans directly by logical queue in ep_start_xmit() and drop the
phys_chan/dma_dev locals, so the transmit path no longer re-derives the
channel through tx_dma_chan_map.
---
drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c | 161 +++++++++++++++++++++++-
1 file changed, 159 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
index f179062c7d14..6e2721a74f28 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/netdev_queues.h>
+
#include "xilinx_tsn.h"
#define DRIVER_NAME "xilinx_tsn_ep"
@@ -82,6 +84,7 @@ struct skbuf_dma_descriptor {
* @ring_tail: consumer index
* @ring_size: number of slots in @skb_ring
* @rx_lock: serialises @ring_head between the RX callback and the refill timer
+ * @tx_lock: serialises @ring_head and @ring_tail between xmit and TX completion
* @rx_refill_timer: retries RX refill after an allocation failure
* @is_tx: true for TX channels, false for RX
*/
@@ -94,6 +97,7 @@ struct xlnx_tsn_ep_dma_chan {
u32 ring_tail;
u32 ring_size;
spinlock_t rx_lock; /* serialises @ring_head */
+ spinlock_t tx_lock; /* serialises @ring_head and @ring_tail */
struct timer_list rx_refill_timer;
bool is_tx;
};
@@ -112,6 +116,7 @@ struct xlnx_tsn_ep_dma_chan {
* @tx_chans: array of TX channels (size @num_tx_queues)
* @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
*/
struct xlnx_tsn_ep {
struct net_device *ndev;
@@ -311,8 +316,151 @@ static void ep_dma_rx_cb(void *data, const struct dmaengine_result *result)
ep_rx_refill(xchan, true);
}
+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 netdev_queue *txq;
+ struct net_device *ndev;
+ struct scatterlist *sgl;
+ struct sk_buff *skb;
+ int sg_len;
+ int len;
+
+ scoped_guard(spinlock_bh, &xchan->tx_lock) {
+ skbuf_dma = ep_get_desc(xchan,
+ xchan->ring_tail & (xchan->ring_size - 1));
+ 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;
+ WRITE_ONCE(xchan->ring_tail, xchan->ring_tail + 1);
+ }
+
+ 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);
+ dev_kfree_skb_any(skb);
+ } 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),
+ xchan->ring_size), 2,
+ READ_ONCE(xchan->ep->closing));
+}
+
static netdev_tx_t ep_start_xmit(struct sk_buff *skb, struct net_device *ndev)
{
+ struct dma_async_tx_descriptor *dma_tx_desc;
+ struct xlnx_tsn_ep *ep = netdev_priv(ndev);
+ struct skbuf_dma_descriptor *skbuf_dma;
+ int queue = skb_get_queue_mapping(skb);
+ struct xlnx_tsn_ep_dma_chan *xchan;
+ struct netdev_queue *txq;
+ int sg_len, nents, ret;
+ dma_cookie_t cookie;
+
+ if (unlikely(queue >= ep->num_tx_queues)) {
+ if (net_ratelimit())
+ netdev_warn(ndev, "Invalid TX queue %d (max %u)\n",
+ queue, ep->num_tx_queues);
+ goto err_drop_skb;
+ }
+
+ if (ep->tx_dma_chan_map[queue] == TSN_DMA_CH_INVALID) {
+ if (net_ratelimit())
+ netdev_warn(ndev, "Logical TX queue %d has invalid DMA mapping\n",
+ queue);
+ goto err_drop_skb;
+ }
+
+ xchan = ep->tx_chans[queue];
+
+ sg_len = skb_shinfo(skb)->nr_frags + 1;
+ txq = netdev_get_tx_queue(ndev, queue);
+
+ spin_lock_bh(&xchan->tx_lock);
+ if (CIRC_SPACE(xchan->ring_head, READ_ONCE(xchan->ring_tail),
+ xchan->ring_size) <= 1) {
+ netif_txq_try_stop(txq,
+ CIRC_SPACE(xchan->ring_head,
+ READ_ONCE(xchan->ring_tail),
+ xchan->ring_size),
+ 2);
+ spin_unlock_bh(&xchan->tx_lock);
+ if (net_ratelimit())
+ netdev_warn(ndev, "TSN TX ring full\n");
+
+ return NETDEV_TX_BUSY;
+ }
+
+ skbuf_dma = ep_get_desc(xchan, xchan->ring_head & (xchan->ring_size - 1));
+ if (!skbuf_dma) {
+ spin_unlock_bh(&xchan->tx_lock);
+ goto err_drop_skb;
+ }
+ spin_unlock_bh(&xchan->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;
+ sg_len = ret;
+
+ nents = dma_map_sg(xchan->dma_dev, skbuf_dma->sgl, sg_len, DMA_TO_DEVICE);
+ if (!nents)
+ goto err_drop_skb;
+
+ dma_tx_desc = dmaengine_prep_slave_sg(xchan->chan, skbuf_dma->sgl,
+ nents, DMA_MEM_TO_DEV,
+ DMA_PREP_INTERRUPT);
+ if (!dma_tx_desc)
+ goto err_unmap_sg;
+
+ 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;
+
+ skb_tx_timestamp(skb);
+
+ spin_lock_bh(&xchan->tx_lock);
+ cookie = dmaengine_submit(dma_tx_desc);
+ if (dma_submit_error(cookie)) {
+ spin_unlock_bh(&xchan->tx_lock);
+ skbuf_dma->skb = NULL;
+ goto err_unmap_sg;
+ }
+ WRITE_ONCE(xchan->ring_head, xchan->ring_head + 1);
+ netdev_tx_sent_queue(txq, skb->len);
+ netif_txq_maybe_stop(txq,
+ CIRC_SPACE(xchan->ring_head,
+ READ_ONCE(xchan->ring_tail),
+ xchan->ring_size),
+ 2, 2);
+ spin_unlock_bh(&xchan->tx_lock);
+
+ dma_async_issue_pending(xchan->chan);
+
+ 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_any(skb);
DEV_STATS_INC(ndev, tx_dropped);
return NETDEV_TX_OK;
@@ -343,10 +491,16 @@ static int ep_open(struct net_device *ndev)
static int ep_stop(struct net_device *ndev)
{
struct xlnx_tsn_ep *ep = netdev_priv(ndev);
+ unsigned int i;
- netif_tx_disable(ndev);
WRITE_ONCE(ep->closing, true);
+ for (i = 0; i < ep->num_tx_queues; i++)
+ dmaengine_terminate_sync(ep->tx_chans[i]->chan);
+
+ netif_tx_disable(ndev);
ep_exit_dmaengine(ep);
+ for (i = 0; i < ndev->num_tx_queues; i++)
+ netdev_tx_reset_subqueue(ndev, i);
return 0;
}
@@ -366,6 +520,7 @@ static const struct net_device_ops ep_netdev_ops = {
static const struct ethtool_ops ep_ethtool_ops = {
.get_drvinfo = ep_get_drvinfo,
+ .get_ts_info = ethtool_op_get_ts_info,
};
static struct xlnx_tsn_ep_dma_chan *
@@ -402,7 +557,9 @@ ep_alloc_dma_chan(struct xlnx_tsn_ep *ep, struct dma_chan *dma_chan,
chan->ep = ep;
chan->ring_size = ring_size;
chan->dma_dev = dmaengine_get_dma_device(chan->chan);
- if (!is_tx) {
+ if (is_tx) {
+ spin_lock_init(&chan->tx_lock);
+ } else {
spin_lock_init(&chan->rx_lock);
timer_setup(&chan->rx_refill_timer, ep_rx_refill_timer, 0);
}
--
2.43.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net-next v4 8/8] net: xilinx: tsn: deliver endpoint RX frames to DSA user ports
2026-11-12 18:46 [PATCH net-next v4 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
` (4 preceding siblings ...)
2026-11-12 18:46 ` [PATCH net-next v4 7/8] net: xilinx: tsn: add the endpoint TX " Srinivas Neeli
@ 2026-11-12 18:46 ` Srinivas Neeli
5 siblings, 0 replies; 7+ 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] 7+ messages in thread
end of thread, other threads:[~2026-11-13 8:18 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-12 18:46 [PATCH net-next v4 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
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 4/8] net: xilinx: tsn: parse endpoint DMA channel configuration 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 6/8] net: xilinx: tsn: add the endpoint RX data path Srinivas Neeli
2026-11-12 18:46 ` [PATCH net-next v4 7/8] net: xilinx: tsn: add the endpoint TX " 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
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®