Netdev List
 help / color / mirror / Atom feed
* [PATCH net-next v3 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver
@ 2026-10-16  9:31 Srinivas Neeli
  2026-10-16  9:32 ` [PATCH net-next v3 1/8] dt-bindings: net: add Xilinx TSN Endpoint Ethernet MAC Srinivas Neeli
                   ` (7 more replies)
  0 siblings, 8 replies; 17+ messages in thread
From: Srinivas Neeli @ 2026-10-16  9:31 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, neelisrinivas18, git

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 Bidge 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 TADMA 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 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        |  491 +++++++++
 MAINTAINERS                                        |    8 +
 drivers/net/ethernet/xilinx/Kconfig                |    1 +
 drivers/net/ethernet/xilinx/Makefile               |    1 +
 drivers/net/ethernet/xilinx/tsn/Kconfig            |   16 +
 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    | 1083 ++++++++++++++++++++
 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c  |  107 ++
 9 files changed, 1724 insertions(+)
---
base-commit: 45ad84d2800e4a092fb8d96006a533b2d0ab13f6
change-id: 20260831-patches_v2_external-36421e557430
prerequisite-message-id: 20260818174151.1608971-1-srinivas.neeli@amd.com
prerequisite-patch-id: abfd1f0259ea7524a5b6a4a7c98b116a213910d0
prerequisite-patch-id: 64bc1fc9008bff333d8cab78d018d93db31c2ee7
prerequisite-patch-id: 3e7fee96146b9a89dbd70f83942e4acf61cfd730
prerequisite-patch-id: 87d626e07a12df2d50163e12e34879868c750253

Best regards,
--  
Srinivas Neeli <srinivas.neeli@amd.com>


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH net-next v3 1/8] dt-bindings: net: add Xilinx TSN Endpoint Ethernet MAC
  2026-10-16  9:31 [PATCH net-next v3 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
@ 2026-10-16  9:32 ` Srinivas Neeli
  2026-10-20 11:58   ` netdev-bot+sashiko
  2026-10-16  9:32 ` [PATCH net-next v3 2/8] net: xilinx: tsn: add TSN endpoint wrapper driver Srinivas Neeli
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 17+ messages in thread
From: Srinivas Neeli @ 2026-10-16  9:32 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, neelisrinivas18, git

Add a binding for the Xilinx TSN Endpoint Ethernet MAC IP. The
top-level node describes the IP wrapper, which owns the six shared
IP clocks and the xlnx,num-priorities parameter, and has two
children:

  - xlnx,tsn-ep-mac: the endpoint MAC, owning the host-side DMA
    channels (dmas / dma-names / tx-queues-config).

  - xlnx,tsn-switch: the three-port Ethernet switch covering the
    switch fabric and the two per-MAC register windows. The MDIO
    controllers and PTP register blocks sit at fixed offsets inside
    the per-MAC windows, accessed through the mdio-1 / mdio-2
    children and per-MAC interrupt-names entries. Each external
    port@N references its PHY through a phy-handle in the matching
    mdio-<N> child.

Co-developed-by: Nagadheeraj Rottela <nagadheeraj.rottela@amd.com>
Signed-off-by: Nagadheeraj Rottela <nagadheeraj.rottela@amd.com>
Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>

---
Changes in v3:
- 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. dsa_switch_parse_ports_of() reads reg
  from every port before classifying it, so a DT that omits reg on the CPU port
  passes dt_binding_check but fails switch registration.
- Pin reg to the unit address on the external ports. Replace the shared
  ^port@[1-2]$ subschema with literal ethernet-port@1 (reg const 1) and
  ethernet-port@2 (reg const 2), so the node name and reg value can no longer
  disagree and each MAC/MDIO binds to the intended port.
- Require queue0 in tx-queues-config. An empty node, or one without queue0,
  is now rejected at dt_binding_check. The logical queue index is dense from
  0, so queue0 is always present. The driver validates the full queue set.
- Add contains rules to dma-names so a TX-only or RX-only list no longer
  validates. At least one tx_chan and one rx_chan are required.
- Tie the queueN children to xlnx,num-priorities. tx-queues-config now
  requires queue0 and queue1, which hold for every allowed value, and for
  each value 3 to 8 the schema requires queue0 through queueN-1. A device
  tree with fewer queues than the IP has priorities is rejected at
  dt_binding_check.

Changes in v2:
- Describe only the Bridged Endpoint configuration. The IP has so far been
  simulated only as a bridged endpoint, so the binding and driver describe
  that configuration alone. Support for an Endpoint-only design will be added
  once that IP variant is simulated.
- Rename ep-mac@ to ethernet@ and mdio-mac1/mdio-mac2 to mdio-1/mdio-2, and
  anchor the node names (^ethernet@[0-9a-f]+$, ^switch@[0-9a-f]+$), per
  ethernet-controller.yaml and mdio.yaml.
- Cap TX at 8 and RX at 8 channels. The IP exposes 8 priorities, so it
  drives at most 8 TX and 8 RX MCDMA channels. dma-names lists both
  directions, so its combined maxItems is 16 (8 TX + 8 RX). V1 allowed up
  to 16 per direction, which the hardware cannot use.
- Set additionalProperties: false on tx-queues-config and its queue nodes.
- Drop reg from the wrapper node. It has no registers of its own, ranges
  already describes the child windows.
- Add minItems: 7 to the switch interrupts to match interrupt-names.
- Use a ^(ethernet-)?ports$ container with additionalProperties: false and its
  own #address-cells/#size-cells, and require either ports or ethernet-ports.
- Require ethernet on the CPU port@0.
- Add reg (enum [1, 2]) on the external port@[1-2] ports and require reg,
  phy-mode and phy-handle. MAC1 and MAC2 use gmii/rgmii/rgmii-id.
- Require port@0, port@1 and port@2. The IP is a fixed three-port bridged
  design (port@0 CPU, port@1 MAC1, port@2 MAC2), so a device tree missing
  any of them cannot describe working hardware.
- Add #address-cells and #size-cells (both const 1) on the wrapper node,
  which the child ethernet@/switch@ windows need now that reg is dropped,
  and set unevaluatedProperties: false on the endpoint node.
---
 .../net/xlnx,tsn-endpoint-ethernet-mac.yaml        | 491 +++++++++++++++++++++
 MAINTAINERS                                        |   7 +
 2 files changed, 498 insertions(+)

diff --git a/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml b/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
new file mode 100644
index 000000000000..bca834120b3b
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
@@ -0,0 +1,491 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/xlnx,tsn-endpoint-ethernet-mac.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Xilinx TSN Endpoint Ethernet MAC
+
+description:
+  TSN Endpoint Ethernet MAC IP implements IEEE 802.1 Time-Sensitive
+  Networking (TSN) standards and provides low-latency network connectivity
+  in the Bridged Endpoint configuration. The IP integrates a three-port
+  switch, with two ports connected to the external network and one port
+  connected to an internal endpoint. It also supports GMII/RGMII interfaces
+  for connection to an external PHY, enabling full-duplex operation at
+  100 Mb/s and 1 Gb/s speeds.
+
+maintainers:
+  - Srinivas Neeli <srinivas.neeli@amd.com>
+  - Nagadheeraj Rottela <nagadheeraj.rottela@amd.com>
+
+properties:
+  compatible:
+    const: xlnx,tsn-endpoint-ethernet-mac-3.0
+
+  clocks:
+    items:
+      - description: GTX clock for transmitter logic and RTC
+      - description: GTX 90-degree phase shifted clock for RGMII
+      - description: Host RX FIFO clock
+      - description: Host TX FIFO clock
+      - description: Reference clock for IDELAY control
+      - description: AXI-Lite register interface clock
+
+  clock-names:
+    items:
+      - const: gtx
+      - const: gtx90
+      - const: host_rxfifo
+      - const: host_txfifo
+      - const: ref
+      - const: s_axi
+
+  ranges: true
+
+  '#address-cells':
+    const: 1
+
+  '#size-cells':
+    const: 1
+
+  xlnx,num-priorities:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    minimum: 2
+    maximum: 8
+    description:
+      Number of traffic classes (priorities) configured in the IP.
+      This is an IP configuration parameter that determines the number of
+      priority queues available for QoS scheduling. Traffic classes map to
+      IEEE 802.1Q priority levels (0-7).
+
+patternProperties:
+  "^ethernet@[0-9a-f]+$":
+    type: object
+    $ref: /schemas/net/ethernet-controller.yaml#
+    unevaluatedProperties: false
+    description:
+      Endpoint MAC sub-node. Provides the host-side data path through the
+      DMA channels described by its dmas / dma-names.
+    properties:
+      compatible:
+        const: xlnx,tsn-ep-mac
+
+      reg:
+        maxItems: 1
+
+      dmas:
+        minItems: 2
+        maxItems: 16
+
+      dma-names:
+        minItems: 2
+        maxItems: 16
+        items:
+          pattern: "^(tx|rx)_chan[0-7]$"
+        allOf:
+          - contains:
+              pattern: "^tx_chan[0-7]$"
+          - contains:
+              pattern: "^rx_chan[0-7]$"
+        description: |
+          Names matching the entries in 'dmas', one per DMA channel and in
+          the same order. TX channel names use "tx_chan<N>" and RX channel
+          names use "rx_chan<N>", where N is 0..7. At least one TX and one RX
+          channel are required. Example: "tx_chan0", "tx_chan1", ...,
+          "rx_chan0", "rx_chan1", ....
+
+      tx-queues-config:
+        type: object
+        additionalProperties: false
+        description:
+          TX queue configuration node that maps IP priority queues to
+          DMA TX channels. The TSN IP supports multiple priority queues for
+          QoS scheduling, and each queue can be connected to a specific DMA
+          channel. This mapping defines which DMA TX channel is used to
+          transmit packets for each priority queue. For example, queue0
+          with xlnx,dma-channel-num set to 5 means priority queue 0 uses
+          tx_chan5 for data transfer.
+
+        patternProperties:
+          "^queue[0-7]$":
+            type: object
+            additionalProperties: false
+
+            description:
+              Each subnode represents a priority queue. The
+              xlnx,dma-channel-num property specifies which DMA TX channel
+              (tx_chan0 to tx_chan7) is connected to this queue for
+              transmitting packets.
+            properties:
+              xlnx,dma-channel-num:
+                $ref: /schemas/types.yaml#/definitions/uint32
+                description:
+                  DMA channel that transmits this priority queue, given as
+                  the N of the tx_chanN entry in dma-names.
+                minimum: 0
+                maximum: 7
+
+            required:
+              - xlnx,dma-channel-num
+
+        required:
+          - queue0
+          - queue1
+
+    required:
+      - compatible
+      - reg
+      - dmas
+      - dma-names
+      - tx-queues-config
+
+  "^switch@[0-9a-f]+$":
+    type: object
+    $ref: /schemas/net/dsa/dsa.yaml#/$defs/ethernet-ports
+    description:
+      Three-port Ethernet switch sub-node. Contains the switch fabric
+      register window and the two per-MAC register windows. Each per-MAC
+      window houses an MDIO controller and a PTP register block at fixed
+      offsets. The IP's per-MAC and PTP-timer interrupts are exposed here.
+
+    properties:
+      compatible:
+        const: xlnx,tsn-switch
+
+      reg:
+        items:
+          - description: Switch fabric register window
+          - description: MAC1 register window (includes MDIO and PTP)
+          - description: MAC2 register window (includes MDIO and PTP)
+
+      reg-names:
+        items:
+          - const: switch
+          - const: mac1
+          - const: mac2
+
+      interrupts:
+        minItems: 7
+        maxItems: 7
+
+      interrupt-names:
+        items:
+          - const: ptp_rx_mac1
+          - const: ptp_tx_mac1
+          - const: mac_irq_mac1
+          - const: ptp_timer
+          - const: ptp_rx_mac2
+          - const: ptp_tx_mac2
+          - const: mac_irq_mac2
+
+      mdio-1:
+        $ref: /schemas/net/mdio.yaml#
+        description:
+          MDIO bus controller for MAC1. Registers reside inside the mac1
+          reg-names window.
+        unevaluatedProperties: false
+
+      mdio-2:
+        $ref: /schemas/net/mdio.yaml#
+        description:
+          MDIO bus controller for MAC2. Registers reside inside the mac2
+          reg-names window.
+        unevaluatedProperties: false
+
+      ethernet-ports:
+        type: object
+        additionalProperties: false
+
+        properties:
+          '#address-cells':
+            const: 1
+
+          '#size-cells':
+            const: 0
+
+          ethernet-port@0:
+            type: object
+            unevaluatedProperties: true
+            description:
+              CPU port. Wired internally to the endpoint MAC sibling.
+
+            properties:
+              reg:
+                const: 0
+
+              phy-mode:
+                const: internal
+
+            required:
+              - reg
+              - ethernet
+
+          ethernet-port@1:
+            type: object
+            unevaluatedProperties: true
+            description:
+              External Ethernet port wired to MAC1, reached through the
+              mdio-1 bus.
+
+            properties:
+              reg:
+                const: 1
+
+              phy-mode:
+                enum:
+                  - gmii
+                  - rgmii
+                  - rgmii-id
+
+            required:
+              - reg
+              - phy-mode
+              - phy-handle
+
+          ethernet-port@2:
+            type: object
+            unevaluatedProperties: true
+            description:
+              External Ethernet port wired to MAC2, reached through the
+              mdio-2 bus.
+
+            properties:
+              reg:
+                const: 2
+
+              phy-mode:
+                enum:
+                  - gmii
+                  - rgmii
+                  - rgmii-id
+
+            required:
+              - reg
+              - phy-mode
+              - phy-handle
+
+        required:
+          - ethernet-port@0
+          - ethernet-port@1
+          - ethernet-port@2
+
+    required:
+      - compatible
+      - reg
+      - reg-names
+      - interrupts
+      - interrupt-names
+      - ethernet-ports
+
+    unevaluatedProperties: false
+
+additionalProperties: false
+
+required:
+  - compatible
+  - clocks
+  - clock-names
+  - xlnx,num-priorities
+  - ranges
+  - '#address-cells'
+  - '#size-cells'
+
+allOf:
+  - if:
+      properties:
+        xlnx,num-priorities:
+          const: 3
+    then:
+      patternProperties:
+        "^ethernet@[0-9a-f]+$":
+          properties:
+            tx-queues-config:
+              required:
+                - queue0
+                - queue1
+                - queue2
+
+  - if:
+      properties:
+        xlnx,num-priorities:
+          const: 4
+    then:
+      patternProperties:
+        "^ethernet@[0-9a-f]+$":
+          properties:
+            tx-queues-config:
+              required:
+                - queue0
+                - queue1
+                - queue2
+                - queue3
+
+  - if:
+      properties:
+        xlnx,num-priorities:
+          const: 5
+    then:
+      patternProperties:
+        "^ethernet@[0-9a-f]+$":
+          properties:
+            tx-queues-config:
+              required:
+                - queue0
+                - queue1
+                - queue2
+                - queue3
+                - queue4
+
+  - if:
+      properties:
+        xlnx,num-priorities:
+          const: 6
+    then:
+      patternProperties:
+        "^ethernet@[0-9a-f]+$":
+          properties:
+            tx-queues-config:
+              required:
+                - queue0
+                - queue1
+                - queue2
+                - queue3
+                - queue4
+                - queue5
+
+  - if:
+      properties:
+        xlnx,num-priorities:
+          const: 7
+    then:
+      patternProperties:
+        "^ethernet@[0-9a-f]+$":
+          properties:
+            tx-queues-config:
+              required:
+                - queue0
+                - queue1
+                - queue2
+                - queue3
+                - queue4
+                - queue5
+                - queue6
+
+  - if:
+      properties:
+        xlnx,num-priorities:
+          const: 8
+    then:
+      patternProperties:
+        "^ethernet@[0-9a-f]+$":
+          properties:
+            tx-queues-config:
+              required:
+                - queue0
+                - queue1
+                - queue2
+                - queue3
+                - queue4
+                - queue5
+                - queue6
+                - queue7
+
+examples:
+  - |
+    tsn@80040000 {
+        compatible = "xlnx,tsn-endpoint-ethernet-mac-3.0";
+        clocks = <&misc_clk_2>, <&misc_clk_2>, <&misc_clk_1>, <&misc_clk_1>,
+                 <&misc_clk_3>, <&misc_clk_0>;
+        clock-names = "gtx", "gtx90", "host_rxfifo", "host_txfifo",
+                      "ref", "s_axi";
+        xlnx,num-priorities = <8>;
+        #address-cells = <1>;
+        #size-cells = <1>;
+        ranges = <0x0 0x80040000 0x40000>;
+
+        ep_mac: ethernet@16000 {
+            compatible = "xlnx,tsn-ep-mac";
+            reg = <0x16000 0xa000>;
+            dmas = <&axi_mcdma_0 0>, <&axi_mcdma_0 1>, <&axi_mcdma_0 2>,
+                   <&axi_mcdma_0 3>, <&axi_mcdma_0 4>, <&axi_mcdma_0 5>,
+                   <&axi_mcdma_0 6>, <&axi_mcdma_0 7>, <&axi_mcdma_0 16>,
+                   <&axi_mcdma_0 17>, <&axi_mcdma_0 18>, <&axi_mcdma_0 19>,
+                   <&axi_mcdma_0 20>, <&axi_mcdma_0 21>, <&axi_mcdma_0 22>,
+                   <&axi_mcdma_0 23>;
+            dma-names = "tx_chan0", "tx_chan1", "tx_chan2", "tx_chan3",
+                        "tx_chan4", "tx_chan5", "tx_chan6", "tx_chan7",
+                        "rx_chan0", "rx_chan1", "rx_chan2", "rx_chan3",
+                        "rx_chan4", "rx_chan5", "rx_chan6", "rx_chan7";
+            tx-queues-config {
+                queue0 { xlnx,dma-channel-num = <0x7>; };
+                queue1 { xlnx,dma-channel-num = <0x6>; };
+                queue2 { xlnx,dma-channel-num = <0x5>; };
+                queue3 { xlnx,dma-channel-num = <0x4>; };
+                queue4 { xlnx,dma-channel-num = <0x3>; };
+                queue5 { xlnx,dma-channel-num = <0x2>; };
+                queue6 { xlnx,dma-channel-num = <0x1>; };
+                queue7 { xlnx,dma-channel-num = <0x0>; };
+            };
+        };
+
+        switch@38000 {
+            compatible = "xlnx,tsn-switch";
+            reg = <0x38000 0x8000>,
+                  <0x00000 0x14000>,
+                  <0x20000 0x14000>;
+            reg-names = "switch", "mac1", "mac2";
+
+            interrupt-parent = <&intc>;
+            interrupts = <0 2>, <2 2>, <4 2>, <6 2>,
+                         <1 2>, <3 2>, <5 2>;
+            interrupt-names = "ptp_rx_mac1", "ptp_tx_mac1",
+                              "mac_irq_mac1", "ptp_timer",
+                              "ptp_rx_mac2", "ptp_tx_mac2",
+                              "mac_irq_mac2";
+
+            mdio-1 {
+                #address-cells = <1>;
+                #size-cells = <0>;
+                phy0: ethernet-phy@0 {
+                    reg = <0>;
+                };
+            };
+
+            mdio-2 {
+                #address-cells = <1>;
+                #size-cells = <0>;
+                phy1: ethernet-phy@1 {
+                    reg = <1>;
+                };
+            };
+
+            ethernet-ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                ethernet-port@0 {
+                    reg = <0>;
+                    ethernet = <&ep_mac>;
+                    phy-mode = "internal";
+
+                    fixed-link {
+                        speed = <1000>;
+                        full-duplex;
+                    };
+                };
+
+                ethernet-port@1 {
+                    reg = <1>;
+                    phy-handle = <&phy0>;
+                    phy-mode = "rgmii-id";
+                };
+
+                ethernet-port@2 {
+                    reg = <2>;
+                    phy-handle = <&phy1>;
+                    phy-mode = "rgmii-id";
+                };
+            };
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 72ca3aab2106..d579df0ad578 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29963,6 +29963,13 @@ M:	Harsh Jain <h.jain@amd.com>
 S:	Maintained
 F:	drivers/char/hw_random/xilinx-trng.c
 
+XILINX TSN ENDPOINT ETHERNET MAC DRIVER
+M:	Nagadheeraj Rottela <nagadheeraj.rottela@amd.com>
+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
+
 XILINX UARTLITE SERIAL DRIVER
 M:	Peter Korsgaard <jacmet@sunsite.dk>
 L:	linux-serial@vger.kernel.org

-- 
2.44.4


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH net-next v3 2/8] net: xilinx: tsn: add TSN endpoint wrapper driver
  2026-10-16  9:31 [PATCH net-next v3 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
  2026-10-16  9:32 ` [PATCH net-next v3 1/8] dt-bindings: net: add Xilinx TSN Endpoint Ethernet MAC Srinivas Neeli
@ 2026-10-16  9:32 ` Srinivas Neeli
  2026-10-20 11:58   ` netdev-bot+sashiko
  2026-10-16  9:32 ` [PATCH net-next v3 3/8] net: xilinx: tsn: add endpoint MAC driver skeleton Srinivas Neeli
                   ` (5 subsequent siblings)
  7 siblings, 1 reply; 17+ messages in thread
From: Srinivas Neeli @ 2026-10-16  9:32 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, neelisrinivas18, git

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 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           |  15 ++++
 drivers/net/ethernet/xilinx/tsn/Makefile          |   2 +
 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c | 104 ++++++++++++++++++++++
 6 files changed, 124 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index d579df0ad578..7cb61c16995d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29969,6 +29969,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..b8939d8a92ef
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/Kconfig
@@ -0,0 +1,15 @@
+# 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. The
+	  three-port switch is supported by the companion DSA driver.
+
+	  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.44.4


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH net-next v3 3/8] net: xilinx: tsn: add endpoint MAC driver skeleton
  2026-10-16  9:31 [PATCH net-next v3 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
  2026-10-16  9:32 ` [PATCH net-next v3 1/8] dt-bindings: net: add Xilinx TSN Endpoint Ethernet MAC Srinivas Neeli
  2026-10-16  9:32 ` [PATCH net-next v3 2/8] net: xilinx: tsn: add TSN endpoint wrapper driver Srinivas Neeli
@ 2026-10-16  9:32 ` Srinivas Neeli
  2026-10-20 11:58   ` netdev-bot+sashiko
  2026-10-16  9:32 ` [PATCH net-next v3 4/8] net: xilinx: tsn: parse endpoint DMA channel configuration Srinivas Neeli
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 17+ messages in thread
From: Srinivas Neeli @ 2026-10-16  9:32 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, neelisrinivas18, git

The TSN Endpoint MAC owns the IP's host-side MCDMA data path and is the
netdev physically wired to the CPU. The DSA switch needs this netdev to
exist as its conduit.

Add a platform driver (compatible "xlnx,tsn-ep-mac") for the endpoint.
Register the netdev with the standard eth%d name, set its MAC address, and
provide minimal netdev and ethtool ops. ndo_open starts the queues and
ndo_start_xmit drops frames.

Co-developed-by: Nagadheeraj Rottela <nagadheeraj.rottela@amd.com>
Signed-off-by: Nagadheeraj Rottela <nagadheeraj.rottela@amd.com>
Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>

---
Changes in v3:
- Register the netdev with the standard eth%d name (NET_NAME_UNKNOWN) instead
  of a custom "ep%d".

Changes in v2:
- Name the netdev ep%d with NET_NAME_ENUM instead of a fixed "ep", so a second
  IP instance does not clash on register_netdev(). The %d enumeration is what
  NET_NAME_ENUM describes, and it matches the eth%d naming in net/dsa/user.c.
- Drop the mapped but unused register window (ep->regs and its ioremap).
  It was intended for QBV support, which is not part of this series, so the
  endpoint driver does not use it. The mapping will be re-added when QBV
  support lands.
- Use dev_kfree_skb_any() on the drop path, not dev_kfree_skb().
- Drop the mod_devicetable.h include, platform_device.h already pulls it in.
---
 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   | 143 ++++++++++++++++++++++
 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c |   3 +
 4 files changed, 162 insertions(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/xilinx/tsn/Makefile b/drivers/net/ethernet/xilinx/tsn/Makefile
index 6f99226f3dc8..5886828b386f 100644
--- a/drivers/net/ethernet/xilinx/tsn/Makefile
+++ b/drivers/net/ethernet/xilinx/tsn/Makefile
@@ -1,2 +1,2 @@
 obj-$(CONFIG_XILINX_TSN) += xilinx_tsn.o
-xilinx_tsn-y := xilinx_tsn_main.o
+xilinx_tsn-y := xilinx_tsn_main.o xilinx_tsn_ep.o
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
new file mode 100644
index 000000000000..b0757e22d1fd
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * AMD/Xilinx TSN Endpoint Ethernet MAC driver, shared definitions.
+ *
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ */
+
+#ifndef _XILINX_TSN_H
+#define _XILINX_TSN_H
+
+#include <linux/platform_device.h>
+
+extern struct platform_driver xlnx_tsn_ep_driver;
+
+#endif /* _XILINX_TSN_H */
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
new file mode 100644
index 000000000000..609d444c6835
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD/Xilinx TSN Endpoint MAC driver.
+ *
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/if_ether.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include "xilinx_tsn.h"
+
+#define DRIVER_NAME			"xilinx_tsn_ep"
+
+/**
+ * struct xlnx_tsn_ep - EP MAC private data, embedded in net_device priv area
+ * @ndev: the conduit netdev
+ * @dev: backing device
+ */
+struct xlnx_tsn_ep {
+	struct net_device *ndev;
+	struct device *dev;
+};
+
+static netdev_tx_t ep_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+	dev_kfree_skb_any(skb);
+	DEV_STATS_INC(ndev, tx_dropped);
+	return NETDEV_TX_OK;
+}
+
+static int ep_open(struct net_device *ndev)
+{
+	netif_tx_start_all_queues(ndev);
+
+	return 0;
+}
+
+static int ep_stop(struct net_device *ndev)
+{
+	netif_tx_disable(ndev);
+
+	return 0;
+}
+
+static void ep_get_drvinfo(struct net_device *ndev, struct ethtool_drvinfo *ed)
+{
+	strscpy(ed->driver, DRIVER_NAME, sizeof(ed->driver));
+}
+
+static const struct net_device_ops ep_netdev_ops = {
+	.ndo_open		= ep_open,
+	.ndo_stop		= ep_stop,
+	.ndo_start_xmit		= ep_start_xmit,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_set_mac_address	= eth_mac_addr,
+};
+
+static const struct ethtool_ops ep_ethtool_ops = {
+	.get_drvinfo	= ep_get_drvinfo,
+};
+
+static int xlnx_tsn_ep_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct net_device *ndev;
+	struct xlnx_tsn_ep *ep;
+	u8 mac_addr[ETH_ALEN];
+	int ret;
+
+	ndev = alloc_netdev(sizeof(*ep), "eth%d", NET_NAME_UNKNOWN, ether_setup);
+	if (!ndev)
+		return -ENOMEM;
+
+	SET_NETDEV_DEV(ndev, dev);
+	ndev->netdev_ops = &ep_netdev_ops;
+	ndev->ethtool_ops = &ep_ethtool_ops;
+	ndev->features = NETIF_F_SG;
+
+	ep = netdev_priv(ndev);
+	ep->ndev = ndev;
+	ep->dev = dev;
+
+	ret = of_get_mac_address(dev->of_node, mac_addr);
+	if (ret == -EPROBE_DEFER) {
+		goto err_free_ndev;
+	} else if (!ret && is_valid_ether_addr(mac_addr)) {
+		eth_hw_addr_set(ndev, mac_addr);
+	} else {
+		eth_hw_addr_random(ndev);
+		dev_info(dev, "no valid MAC in DT, using random address %pM\n",
+			 ndev->dev_addr);
+	}
+
+	platform_set_drvdata(pdev, ep);
+
+	ret = register_netdev(ndev);
+	if (ret) {
+		dev_err_probe(dev, ret, "failed to register net device\n");
+		goto err_free_ndev;
+	}
+
+	return 0;
+
+err_free_ndev:
+	free_netdev(ndev);
+	return ret;
+}
+
+static void xlnx_tsn_ep_remove(struct platform_device *pdev)
+{
+	struct xlnx_tsn_ep *ep = platform_get_drvdata(pdev);
+
+	if (!ep)
+		return;
+
+	unregister_netdev(ep->ndev);
+	free_netdev(ep->ndev);
+}
+
+static const struct of_device_id xlnx_tsn_ep_of_match[] = {
+	{ .compatible = "xlnx,tsn-ep-mac" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, xlnx_tsn_ep_of_match);
+
+struct platform_driver xlnx_tsn_ep_driver = {
+	.probe	= xlnx_tsn_ep_probe,
+	.remove	= xlnx_tsn_ep_remove,
+	.driver	= {
+		.name		= DRIVER_NAME,
+		.of_match_table	= xlnx_tsn_ep_of_match,
+	},
+};
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
index afe7609c67fb..d33a00dd6d15 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
@@ -15,6 +15,8 @@
 #include <linux/slab.h>
 #include <linux/types.h>
 
+#include "xilinx_tsn.h"
+
 #define TSN_NUM_CLOCKS		6
 
 /**
@@ -85,6 +87,7 @@ static struct platform_driver tsn_driver = {
 
 static struct platform_driver * const tsn_drivers[] = {
 	&tsn_driver,
+	&xlnx_tsn_ep_driver,
 };
 
 static int __init xlnx_tsn_init(void)

-- 
2.44.4


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH net-next v3 4/8] net: xilinx: tsn: parse endpoint DMA channel configuration
  2026-10-16  9:31 [PATCH net-next v3 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
                   ` (2 preceding siblings ...)
  2026-10-16  9:32 ` [PATCH net-next v3 3/8] net: xilinx: tsn: add endpoint MAC driver skeleton Srinivas Neeli
@ 2026-10-16  9:32 ` Srinivas Neeli
  2026-10-20 11:58   ` netdev-bot+sashiko
  2026-10-16  9:32 ` [PATCH net-next v3 5/8] net: xilinx: tsn: bring up the endpoint MCDMA channels Srinivas Neeli
                   ` (3 subsequent siblings)
  7 siblings, 1 reply; 17+ messages in thread
From: Srinivas Neeli @ 2026-10-16  9:32 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, neelisrinivas18, git

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 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 | 198 +++++++++++++++++++++++-
 1 file changed, 197 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 609d444c6835..2adee07f6f51 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,30 @@
 
 #define DRIVER_NAME			"xilinx_tsn_ep"
 
+#define TSN_DMA_CH_INVALID		0xFFU
+#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 +87,176 @@ 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_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_netdev(sizeof(*ep), "eth%d", NET_NAME_UNKNOWN, ether_setup);
+	ndev = alloc_netdev_mqs(sizeof(*ep), "eth%d", NET_NAME_UNKNOWN,
+				ether_setup, num_tx_dma_channels,
+				num_rx_dma_channels);
 	if (!ndev)
 		return -ENOMEM;
 
@@ -89,6 +268,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.44.4


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH net-next v3 5/8] net: xilinx: tsn: bring up the endpoint MCDMA channels
  2026-10-16  9:31 [PATCH net-next v3 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
                   ` (3 preceding siblings ...)
  2026-10-16  9:32 ` [PATCH net-next v3 4/8] net: xilinx: tsn: parse endpoint DMA channel configuration Srinivas Neeli
@ 2026-10-16  9:32 ` Srinivas Neeli
  2026-10-20 11:58   ` netdev-bot+sashiko
  2026-10-16  9:32 ` [PATCH net-next v3 6/8] net: xilinx: tsn: add the endpoint RX data path Srinivas Neeli
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 17+ messages in thread
From: Srinivas Neeli @ 2026-10-16  9:32 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, neelisrinivas18, git

Before the endpoint can move frames, each MCDMA channel needs a ring of
buffers and a live dmaengine channel.

Request every TX and RX channel named in the device tree at probe and
hold them for the lifetime of the device. dma_request_chan() returns
-EPROBE_DEFER until the MCDMA provider is bound, and probe is the only
context the driver core retries, so acquiring the channels here keeps
the deferral where it can be handled. Holding them also pins the
provider, so the channels cannot go away while the interface is down.

Reset the controller once at probe through the first held TX channel. An
MCDMA soft reset resets the whole core, so any channel of the instance
reaches it.

Give each channel a ring of preallocated SKB descriptor slots when the
interface opens, and tear the rings down on stop, where
dmaengine_terminate_sync() returns each channel to a clean state. Add
the XILINX_DMA dependency for the dmaengine and Xilinx DMA APIs.

Co-developed-by: Nagadheeraj Rottela <nagadheeraj.rottela@amd.com>
Signed-off-by: Nagadheeraj Rottela <nagadheeraj.rottela@amd.com>
Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>

---
Changes in v3:
- Acquire every TX and RX channel in probe and hold them until remove.
  ndo_open now only builds the rings. dma_request_chan() is the call that
  can return -EPROBE_DEFER and probe is the only context the driver core
  retries, so the deferral no longer has to be translated in ndo_open.
  Holding the channels also pins the MCDMA provider, so it cannot go away
  while the interface is down.
- Reset the controller through the first held TX channel instead of
  requesting and releasing a channel for the reset alone.
- Move the closing flag (field, kernel-doc and its ndo_open/ndo_stop
  writes) to the RX data path patch, where it is first read. Drop the
  unused io.h include and move circ_buf.h and spinlock.h to the RX patch
  that first uses CIRC_SPACE() and spinlock_t.
- Reword the commit description for the probe-time channel acquisition.

Changes in v2:
- Request a channel at probe time so -EPROBE_DEFER stays in the probe path and
  ndo_open never hands a deferral back to userspace.
- Request the channels by their real dma-names index, not a dense 0..N-1
  sequence.
- Reword the commit message so the reset reads as probe-time through the first
  mapped TX channel, not on open.
---
 drivers/net/ethernet/xilinx/tsn/Kconfig         |   1 +
 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c | 316 +++++++++++++++++++++++-
 2 files changed, 315 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/tsn/Kconfig b/drivers/net/ethernet/xilinx/tsn/Kconfig
index b8939d8a92ef..292f17694404 100644
--- a/drivers/net/ethernet/xilinx/tsn/Kconfig
+++ b/drivers/net/ethernet/xilinx/tsn/Kconfig
@@ -6,6 +6,7 @@
 config XILINX_TSN
 	tristate "Xilinx TSN Ethernet driver"
 	depends on OF_ADDRESS
+	depends on XILINX_DMA
 	help
 	  This driver supports the AMD/Xilinx Time-Sensitive Networking
 	  (TSN) Endpoint Ethernet MAC IP. It provides the wrapper device
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
index 2adee07f6f51..ab3d6d8cb944 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
@@ -6,6 +6,9 @@
  */
 
 #include <linux/bitops.h>
+#include <linux/dma/xilinx_dma.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
 #include <linux/etherdevice.h>
 #include <linux/ethtool.h>
 #include <linux/if_ether.h>
@@ -16,6 +19,8 @@
 #include <linux/of.h>
 #include <linux/of_net.h>
 #include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
 #include <linux/string.h>
 #include <linux/types.h>
 
@@ -30,6 +35,47 @@
 #define TSN_MAX_VLAN_FRAME_SIZE		(ETH_DATA_LEN + VLAN_ETH_HLEN + \
 					 ETH_FCS_LEN)
 
+#define TX_BD_NUM_DEFAULT		64
+#define RX_BD_NUM_DEFAULT		128
+
+/**
+ * struct skbuf_dma_descriptor - skb container for each in-flight DMA descriptor
+ * @sgl: scatter-gather list backing the DMA mapping
+ * @desc: dmaengine descriptor handle
+ * @dma_address: physical address of the first sgl entry (RX path)
+ * @skb: SKB owning the buffer
+ * @sg_len: number of valid entries in @sgl (TX path)
+ */
+struct skbuf_dma_descriptor {
+	struct scatterlist sgl[MAX_SKB_FRAGS + 1];
+	struct dma_async_tx_descriptor *desc;
+	dma_addr_t dma_address;
+	struct sk_buff *skb;
+	int sg_len;
+};
+
+/**
+ * struct xlnx_tsn_ep_dma_chan - one DMA channel and its SKB ring
+ * @skb_ring: per-slot SKB descriptors
+ * @ep: pointer back to the owning EP instance
+ * @chan: dmaengine channel handle
+ * @dma_dev: device used for DMA mapping (the DMA engine, not the EP)
+ * @ring_head: producer index
+ * @ring_tail: consumer index
+ * @ring_size: number of slots in @skb_ring
+ * @is_tx: true for TX channels, false for RX
+ */
+struct xlnx_tsn_ep_dma_chan {
+	struct skbuf_dma_descriptor **skb_ring;
+	struct xlnx_tsn_ep *ep;
+	struct dma_chan *chan;
+	struct device *dma_dev;
+	u32 ring_head;
+	u32 ring_tail;
+	u32 ring_size;
+	bool is_tx;
+};
+
 /**
  * struct xlnx_tsn_ep - EP MAC private data, embedded in net_device priv area
  * @ndev: the conduit netdev
@@ -39,6 +85,10 @@
  * @tx_dma_chan_map: TX queue to tx_chanN map, or TSN_DMA_CH_INVALID when
  *	the queue has no channel
  * @max_frm_size: maximum frame size accepted on RX
+ * @tx_dma: dmaengine TX channels, held from probe until remove
+ * @rx_dma: dmaengine RX channels, held from probe until remove
+ * @tx_chans: array of TX channels (size @num_tx_queues)
+ * @rx_chans: array of RX channels (size @num_rx_queues)
  */
 struct xlnx_tsn_ep {
 	struct net_device *ndev;
@@ -47,8 +97,21 @@ struct xlnx_tsn_ep {
 	u32 num_rx_queues;
 	u32 tx_dma_chan_map[TSN_MAX_TX_QUEUE];
 	u32 max_frm_size;
+
+	struct dma_chan *tx_dma[TSN_MAX_TX_QUEUE];
+	struct dma_chan *rx_dma[TSN_MAX_RX_QUEUE];
+
+	struct xlnx_tsn_ep_dma_chan **tx_chans;
+	struct xlnx_tsn_ep_dma_chan **rx_chans;
+
 };
 
+static inline struct skbuf_dma_descriptor *
+ep_get_desc(struct xlnx_tsn_ep_dma_chan *xchan, int idx)
+{
+	return xchan->skb_ring[idx];
+}
+
 static netdev_tx_t ep_start_xmit(struct sk_buff *skb, struct net_device *ndev)
 {
 	dev_kfree_skb_any(skb);
@@ -56,8 +119,21 @@ static netdev_tx_t ep_start_xmit(struct sk_buff *skb, struct net_device *ndev)
 	return NETDEV_TX_OK;
 }
 
+static int ep_init_dmaengine(struct xlnx_tsn_ep *ep);
+static void ep_exit_dmaengine(struct xlnx_tsn_ep *ep);
+
 static int ep_open(struct net_device *ndev)
 {
+	struct xlnx_tsn_ep *ep = netdev_priv(ndev);
+	int ret;
+
+	ret = ep_init_dmaengine(ep);
+	if (ret) {
+		netdev_err(ndev, "failed to initialize DMA engine\n");
+
+		return ret;
+	}
+
 	netif_tx_start_all_queues(ndev);
 
 	return 0;
@@ -65,7 +141,10 @@ static int ep_open(struct net_device *ndev)
 
 static int ep_stop(struct net_device *ndev)
 {
+	struct xlnx_tsn_ep *ep = netdev_priv(ndev);
+
 	netif_tx_disable(ndev);
+	ep_exit_dmaengine(ep);
 
 	return 0;
 }
@@ -87,6 +166,228 @@ static const struct ethtool_ops ep_ethtool_ops = {
 	.get_drvinfo	= ep_get_drvinfo,
 };
 
+static struct xlnx_tsn_ep_dma_chan *
+ep_alloc_dma_chan(struct xlnx_tsn_ep *ep, struct dma_chan *dma_chan,
+		  bool is_tx, int ring_size)
+{
+	struct xlnx_tsn_ep_dma_chan *chan;
+	int i;
+
+	chan = kzalloc_obj(*chan);
+	if (!chan)
+		return ERR_PTR(-ENOMEM);
+
+	chan->chan = dma_chan;
+
+	chan->skb_ring = kcalloc(ring_size, sizeof(*chan->skb_ring), GFP_KERNEL);
+	if (!chan->skb_ring) {
+		kfree(chan);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	for (i = 0; i < ring_size; i++) {
+		chan->skb_ring[i] = kzalloc_obj(*chan->skb_ring[i]);
+		if (!chan->skb_ring[i]) {
+			while (--i >= 0)
+				kfree(chan->skb_ring[i]);
+			kfree(chan->skb_ring);
+			kfree(chan);
+			return ERR_PTR(-ENOMEM);
+		}
+	}
+
+	chan->is_tx = is_tx;
+	chan->ep = ep;
+	chan->ring_size = ring_size;
+	chan->dma_dev = dmaengine_get_dma_device(chan->chan);
+
+	return chan;
+}
+
+static void ep_free_dma_chan(struct xlnx_tsn_ep_dma_chan *chan)
+{
+	int i;
+
+	if (!chan)
+		return;
+
+	if (chan->chan)
+		dmaengine_terminate_sync(chan->chan);
+
+	if (chan->is_tx) {
+		while (chan->ring_tail != chan->ring_head) {
+			struct skbuf_dma_descriptor *skbuf_dma;
+
+			skbuf_dma = chan->skb_ring[chan->ring_tail &
+						  (chan->ring_size - 1)];
+			if (skbuf_dma && skbuf_dma->skb) {
+				dma_unmap_sg(chan->dma_dev, skbuf_dma->sgl,
+					     skbuf_dma->sg_len, DMA_TO_DEVICE);
+				dev_kfree_skb_any(skbuf_dma->skb);
+				skbuf_dma->skb = NULL;
+			}
+			chan->ring_tail++;
+		}
+	}
+
+	if (chan->skb_ring) {
+		for (i = 0; i < chan->ring_size; i++) {
+			struct skbuf_dma_descriptor *skbuf_dma = chan->skb_ring[i];
+
+			if (skbuf_dma && !chan->is_tx && skbuf_dma->skb) {
+				dma_unmap_single(chan->dma_dev,
+						 skbuf_dma->dma_address,
+						 chan->ep->max_frm_size,
+						 DMA_FROM_DEVICE);
+				dev_kfree_skb_any(skbuf_dma->skb);
+			}
+			kfree(chan->skb_ring[i]);
+		}
+		kfree(chan->skb_ring);
+	}
+	kfree(chan);
+}
+
+static void ep_exit_dmaengine(struct xlnx_tsn_ep *ep)
+{
+	int i;
+
+	if (ep->tx_chans) {
+		for (i = 0; i < ep->num_tx_queues; i++)
+			ep_free_dma_chan(ep->tx_chans[i]);
+		kfree(ep->tx_chans);
+		ep->tx_chans = NULL;
+	}
+	if (ep->rx_chans) {
+		for (i = 0; i < ep->num_rx_queues; i++)
+			ep_free_dma_chan(ep->rx_chans[i]);
+		kfree(ep->rx_chans);
+		ep->rx_chans = NULL;
+	}
+}
+
+static int ep_init_dmaengine(struct xlnx_tsn_ep *ep)
+{
+	int tx_allocated = 0, rx_allocated = 0;
+	int i, ret;
+
+	ep->tx_chans = kcalloc(ep->num_tx_queues, sizeof(*ep->tx_chans),
+			       GFP_KERNEL);
+	if (!ep->tx_chans)
+		return -ENOMEM;
+
+	ep->rx_chans = kcalloc(ep->num_rx_queues, sizeof(*ep->rx_chans),
+			       GFP_KERNEL);
+	if (!ep->rx_chans) {
+		ret = -ENOMEM;
+		goto err_free_tx;
+	}
+
+	for (i = 0; i < ep->num_tx_queues; i++) {
+		ep->tx_chans[i] = ep_alloc_dma_chan(ep, ep->tx_dma[i], true,
+						    TX_BD_NUM_DEFAULT);
+		if (IS_ERR(ep->tx_chans[i])) {
+			ret = PTR_ERR(ep->tx_chans[i]);
+			ep->tx_chans[i] = NULL;
+			goto err_free_chans;
+		}
+		tx_allocated++;
+	}
+
+	for (i = 0; i < ep->num_rx_queues; i++) {
+		ep->rx_chans[i] = ep_alloc_dma_chan(ep, ep->rx_dma[i], false,
+						    RX_BD_NUM_DEFAULT);
+		if (IS_ERR(ep->rx_chans[i])) {
+			ret = PTR_ERR(ep->rx_chans[i]);
+			ep->rx_chans[i] = NULL;
+			goto err_free_chans;
+		}
+		rx_allocated++;
+	}
+
+	return 0;
+
+err_free_chans:
+	while (--rx_allocated >= 0)
+		ep_free_dma_chan(ep->rx_chans[rx_allocated]);
+	while (--tx_allocated >= 0)
+		ep_free_dma_chan(ep->tx_chans[tx_allocated]);
+	kfree(ep->rx_chans);
+	ep->rx_chans = NULL;
+err_free_tx:
+	kfree(ep->tx_chans);
+	ep->tx_chans = NULL;
+	return ret;
+}
+
+static void ep_release_dma_chans(struct xlnx_tsn_ep *ep)
+{
+	unsigned int i;
+
+	for (i = 0; i < ep->num_tx_queues; i++) {
+		if (ep->tx_dma[i]) {
+			dma_release_channel(ep->tx_dma[i]);
+			ep->tx_dma[i] = NULL;
+		}
+	}
+
+	for (i = 0; i < ep->num_rx_queues; i++) {
+		if (ep->rx_dma[i]) {
+			dma_release_channel(ep->rx_dma[i]);
+			ep->rx_dma[i] = NULL;
+		}
+	}
+}
+
+static int ep_request_dma_chans(struct xlnx_tsn_ep *ep,
+				const u32 *rx_dma_chan_map)
+{
+	struct dma_chan *chan;
+	char name[16];
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < ep->num_tx_queues; i++) {
+		snprintf(name, sizeof(name), "tx_chan%u", ep->tx_dma_chan_map[i]);
+		chan = dma_request_chan(ep->dev, name);
+		if (IS_ERR(chan)) {
+			ret = PTR_ERR(chan);
+			goto err_release;
+		}
+		ep->tx_dma[i] = chan;
+	}
+
+	for (i = 0; i < ep->num_rx_queues; i++) {
+		snprintf(name, sizeof(name), "rx_chan%u", rx_dma_chan_map[i]);
+		chan = dma_request_chan(ep->dev, name);
+		if (IS_ERR(chan)) {
+			ret = PTR_ERR(chan);
+			goto err_release;
+		}
+		ep->rx_dma[i] = chan;
+	}
+
+	return 0;
+
+err_release:
+	ep_release_dma_chans(ep);
+
+	return dev_err_probe(ep->dev, ret, "failed to request %s\n", name);
+}
+
+static int ep_reset_dma_controller(struct xlnx_tsn_ep *ep)
+{
+	struct xilinx_vdma_config cfg = { .reset = 1 };
+	int ret;
+
+	ret = xilinx_vdma_channel_set_config(ep->tx_dma[0], &cfg);
+	if (ret < 0)
+		return dev_err_probe(ep->dev, ret,
+				     "failed to reset DMA controller\n");
+
+	return 0;
+}
+
 /*
  * Parse the "tx-queues-config" child of the EP node. The logical queue
  * index is taken from the "queue<N>" node name, so the mapping does not
@@ -286,9 +587,17 @@ static int xlnx_tsn_ep_probe(struct platform_device *pdev)
 	if (ret)
 		goto err_free_ndev;
 
+	ret = ep_request_dma_chans(ep, rx_dma_chan_map);
+	if (ret)
+		goto err_free_ndev;
+
+	ret = ep_reset_dma_controller(ep);
+	if (ret)
+		goto err_release_chans;
+
 	ret = of_get_mac_address(dev->of_node, mac_addr);
 	if (ret == -EPROBE_DEFER) {
-		goto err_free_ndev;
+		goto err_release_chans;
 	} else if (!ret && is_valid_ether_addr(mac_addr)) {
 		eth_hw_addr_set(ndev, mac_addr);
 	} else {
@@ -302,11 +611,13 @@ static int xlnx_tsn_ep_probe(struct platform_device *pdev)
 	ret = register_netdev(ndev);
 	if (ret) {
 		dev_err_probe(dev, ret, "failed to register net device\n");
-		goto err_free_ndev;
+		goto err_release_chans;
 	}
 
 	return 0;
 
+err_release_chans:
+	ep_release_dma_chans(ep);
 err_free_ndev:
 	free_netdev(ndev);
 	return ret;
@@ -320,6 +631,7 @@ static void xlnx_tsn_ep_remove(struct platform_device *pdev)
 		return;
 
 	unregister_netdev(ep->ndev);
+	ep_release_dma_chans(ep);
 	free_netdev(ep->ndev);
 }
 

-- 
2.44.4


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH net-next v3 6/8] net: xilinx: tsn: add the endpoint RX data path
  2026-10-16  9:31 [PATCH net-next v3 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
                   ` (4 preceding siblings ...)
  2026-10-16  9:32 ` [PATCH net-next v3 5/8] net: xilinx: tsn: bring up the endpoint MCDMA channels Srinivas Neeli
@ 2026-10-16  9:32 ` Srinivas Neeli
  2026-10-20 11:58   ` netdev-bot+sashiko
  2026-10-16  9:32 ` [PATCH net-next v3 7/8] net: xilinx: tsn: add the endpoint TX " Srinivas Neeli
  2026-10-16  9:32 ` [PATCH net-next v3 8/8] net: xilinx: tsn: deliver endpoint RX frames to DSA user ports Srinivas Neeli
  7 siblings, 1 reply; 17+ messages in thread
From: Srinivas Neeli @ 2026-10-16  9:32 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, neelisrinivas18, git

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 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 ab3d6d8cb944..0a13e04d4057 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"
@@ -38,6 +42,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
@@ -63,6 +80,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 {
@@ -73,6 +92,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;
 };
 
@@ -89,6 +110,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;
@@ -104,6 +126,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 *
@@ -112,6 +135,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 (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) {
+		if (net_ratelimit())
+			dev_dbg(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->ip_summed = CHECKSUM_NONE;
+	__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);
@@ -127,6 +325,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");
@@ -144,6 +344,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;
@@ -200,6 +401,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;
 }
@@ -211,8 +416,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) {
@@ -305,9 +523,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.44.4


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH net-next v3 7/8] net: xilinx: tsn: add the endpoint TX data path
  2026-10-16  9:31 [PATCH net-next v3 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
                   ` (5 preceding siblings ...)
  2026-10-16  9:32 ` [PATCH net-next v3 6/8] net: xilinx: tsn: add the endpoint RX data path Srinivas Neeli
@ 2026-10-16  9:32 ` Srinivas Neeli
  2026-10-20 11:58   ` netdev-bot+sashiko
  2026-10-16  9:32 ` [PATCH net-next v3 8/8] net: xilinx: tsn: deliver endpoint RX frames to DSA user ports Srinivas Neeli
  7 siblings, 1 reply; 17+ messages in thread
From: Srinivas Neeli @ 2026-10-16  9:32 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, neelisrinivas18, git

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 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 | 155 +++++++++++++++++++++++-
 1 file changed, 153 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 0a13e04d4057..bd8cd504cefb 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"
@@ -81,6 +83,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
  */
@@ -93,6 +96,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;
 };
@@ -310,8 +314,150 @@ 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;
+
+	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);
+
+	skb_tx_timestamp(skb);
+	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;
@@ -342,10 +488,13 @@ 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);
+	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;
 }
@@ -401,7 +550,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.44.4


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH net-next v3 8/8] net: xilinx: tsn: deliver endpoint RX frames to DSA user ports
  2026-10-16  9:31 [PATCH net-next v3 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
                   ` (6 preceding siblings ...)
  2026-10-16  9:32 ` [PATCH net-next v3 7/8] net: xilinx: tsn: add the endpoint TX " Srinivas Neeli
@ 2026-10-16  9:32 ` Srinivas Neeli
  2026-10-20 11:58   ` netdev-bot+sashiko
  7 siblings, 1 reply; 17+ messages in thread
From: Srinivas Neeli @ 2026-10-16  9:32 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, neelisrinivas18, git

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 bd8cd504cefb..5103a3b4b018 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"
@@ -57,6 +59,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
@@ -115,6 +120,8 @@ 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
+ * @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;
@@ -131,6 +138,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 *
@@ -302,6 +311,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->ip_summed = CHECKSUM_NONE;
@@ -900,6 +913,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;
@@ -985,16 +1029,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:
@@ -1010,6 +1062,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.44.4


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [PATCH net-next v3 1/8] dt-bindings: net: add Xilinx TSN Endpoint Ethernet MAC
  2026-10-16  9:32 ` [PATCH net-next v3 1/8] dt-bindings: net: add Xilinx TSN Endpoint Ethernet MAC Srinivas Neeli
@ 2026-10-20 11:58   ` netdev-bot+sashiko
  0 siblings, 0 replies; 17+ messages in thread
From: netdev-bot+sashiko @ 2026-10-20 11:58 UTC (permalink / raw)
  To: srinivas.neeli
  Cc: nagadheeraj.rottela, andrew+netdev, davem, kuba, pabeni, robh,
	krzk+dt, conor+dt, richardcochran, michal.simek, bigeasy,
	clrkwllms, rostedt, edumazet, netdev, devicetree, linux-kernel,
	linux-arm-kernel, linux-rt-devel, neelisrinivas18, git

Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.

Low priority / nit picks for your consideration:

- [Low] In Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-
  ethernet-mac.yaml, the ethernet@ node's dma-names (lines 81-96) does…
- [Low] Concern: tx-queues-config only enforces a lower bound tied to
  xlnx,num-priorities, and the rest of the checking is left to a driver…
- [Low] The child compatibles in
  Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-…

--

dt-bindings: net: add Xilinx TSN Endpoint Ethernet MAC

This adds a YAML binding for the Xilinx TSN Endpoint Ethernet MAC IP in
its Bridged Endpoint configuration. The wrapper node owns the shared
clocks and xlnx,num-priorities. It has two children: an xlnx,tsn-ep-mac
endpoint that holds the DMA channels, and an xlnx,tsn-switch three-port
switch with its MDIO buses and ports.

> diff --git a/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml b/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
> new file mode 100644
> index 0000000000000..bca834120b3b1
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
> @@ -0,0 +1,491 @@

[ ... ]

> +properties:
> +  compatible:
> +    const: xlnx,tsn-endpoint-ethernet-mac-3.0

[ ... ]

> +patternProperties:
> +  "^ethernet@[0-9a-f]+$":
> +    type: object
> +    $ref: /schemas/net/ethernet-controller.yaml#
> +    unevaluatedProperties: false
> +    description:
> +      Endpoint MAC sub-node. Provides the host-side data path through the
> +      DMA channels described by its dmas / dma-names.
> +    properties:
> +      compatible:
> +        const: xlnx,tsn-ep-mac

[Severity: Low]
The wrapper compatible is versioned (xlnx,tsn-endpoint-ethernet-mac-3.0).
The child compatibles here and on the switch node (xlnx,tsn-switch) are
generic and carry no version.

writing-bindings.rst asks for specific compatibles, with no device-family
names. For sub-blocks of a bigger device, it asks for device-based
compatibles.

If a later IP revision, or the planned Endpoint-only variant, changes the
endpoint or switch register layout, how would these child nodes be told
apart? The endpoint driver later in the series matches only on
"xlnx,tsn-ep-mac" in xlnx_tsn_ep_of_match.

Should the child compatibles be tied to the device or version, the same
way the wrapper compatible is?

[ ... ]

> +      dma-names:
> +        minItems: 2
> +        maxItems: 16
> +        items:
> +          pattern: "^(tx|rx)_chan[0-7]$"
> +        allOf:
> +          - contains:
> +              pattern: "^tx_chan[0-7]$"
> +          - contains:
> +              pattern: "^rx_chan[0-7]$"
> +        description: |
> +          Names matching the entries in 'dmas', one per DMA channel and in
> +          the same order. TX channel names use "tx_chan<N>" and RX channel
> +          names use "rx_chan<N>", where N is 0..7. At least one TX and one RX
> +          channel are required. Example: "tx_chan0", "tx_chan1", ...,
> +          "rx_chan0", "rx_chan1", ....

[Severity: Low]
The description says the names are "one per DMA channel". The v3
changelog says "Cap TX at 8 and RX at 8 channels". Does the schema
enforce either of these?

Without uniqueItems: true, dt_binding_check appears to accept a 16-entry
list of fifteen "tx_chan0" plus one "rx_chan0". It also appears to accept
"tx_chan0".."tx_chan7", then "tx_chan0".."tx_chan6", then "rx_chan0".

dtschema doesn't add uniqueness to -names properties by itself. For
example, renesas,drif.yaml lists "rx", "rx", while renesas,sdhi.yaml sets
uniqueItems: true on its clock-names.

Could uniqueItems: true be added here? The pattern only allows eight
names per direction, so that would also enforce the 8 TX / 8 RX limit.

Later in the series, ep_count_dma_channels() rejects duplicate TX and RX
names at probe time. So the runtime result is a DT that passes schema
validation and then fails probe.

[ ... ]

> +        patternProperties:
> +          "^queue[0-7]$":
> +            type: object
> +            additionalProperties: false

[ ... ]

> +        required:
> +          - queue0
> +          - queue1

[ ... ]

> +allOf:
> +  - if:
> +      properties:
> +        xlnx,num-priorities:
> +          const: 3
> +    then:
> +      patternProperties:
> +        "^ethernet@[0-9a-f]+$":
> +          properties:
> +            tx-queues-config:
> +              required:
> +                - queue0
> +                - queue1
> +                - queue2

[Severity: Low]
With xlnx,num-priorities = <2>, can a tx-queues-config that contains
queue0 through queue7 still pass dt_binding_check?

The ^queue[0-7]$ pattern accepts every index from 0 to 7. The if/then
blocks for num-priorities 3..8 only add entries to required. So the
schema has a lower bound tied to xlnx,num-priorities, but no upper bound.

Later in the series, ep_parse_tx_queue_config() rejects queueN with
N >= num_tx_queues at probe time, so only schema validation is affected.

Could each if/then block also set queueK: false for K >= N, so the
schema rejects these nodes too?

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261016-patches_v2_external-v3-0-e33d3960ca1e%40amd.com

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH net-next v3 2/8] net: xilinx: tsn: add TSN endpoint wrapper driver
  2026-10-16  9:32 ` [PATCH net-next v3 2/8] net: xilinx: tsn: add TSN endpoint wrapper driver Srinivas Neeli
@ 2026-10-20 11:58   ` netdev-bot+sashiko
  0 siblings, 0 replies; 17+ messages in thread
From: netdev-bot+sashiko @ 2026-10-20 11:58 UTC (permalink / raw)
  To: srinivas.neeli
  Cc: nagadheeraj.rottela, andrew+netdev, davem, kuba, pabeni, robh,
	krzk+dt, conor+dt, richardcochran, michal.simek, bigeasy,
	clrkwllms, rostedt, edumazet, netdev, devicetree, linux-kernel,
	linux-arm-kernel, linux-rt-devel, neelisrinivas18, git

Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.

Low priority / nit picks for your consideration:

- [Low] The XILINX_TSN Kconfig help text says "The three-port switch is
  supported by the companion DSA driver." That driver does not exist in…

--

net: xilinx: tsn: add TSN endpoint wrapper driver

This adds a thin platform driver for the AMD/Xilinx TSN Endpoint
Ethernet MAC IP node. It turns on the six IP-wide clocks and then calls
devm_of_platform_populate(), so each sub-node can bind to its own
driver.

> diff --git a/drivers/net/ethernet/xilinx/tsn/Kconfig b/drivers/net/ethernet/xilinx/tsn/Kconfig
> new file mode 100644
> index 0000000000000..b8939d8a92ef7
> --- /dev/null
> +++ b/drivers/net/ethernet/xilinx/tsn/Kconfig
> @@ -0,0 +1,15 @@

[ ... ]

> +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. The
> +	  three-port switch is supported by the companion DSA driver.

[Severity: Low]
Which DSA driver is this sentence about? I couldn't find one in the
tree, either at this commit or at the end of the series.

The binding in this series defines the "xlnx,tsn-switch" compatible for
the switch@ sub-node, and nothing under drivers/ matches it.
tsn_ip_probe() calls devm_of_platform_populate(), which creates a
platform device for switch@, but no driver ever binds to it.

The v2 changelog says the help text was reworded so it no longer points
at "a symbol that does not exist yet at this patch". The new wording
still names a driver that isn't in the tree and has no Kconfig symbol.

Could this sentence be dropped or reworded until the switch driver is
posted?

Two other claims do become true later in the series: "endpoint MAC" in
the help text, and "bind each sub-node to its own driver" in the commit
message. That happens when "net: xilinx: tsn: add endpoint MAC driver
skeleton" adds xlnx_tsn_ep_driver to tsn_drivers[]. Only the DSA driver
reference is still wrong once the whole series is applied.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261016-patches_v2_external-v3-0-e33d3960ca1e%40amd.com

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH net-next v3 3/8] net: xilinx: tsn: add endpoint MAC driver skeleton
  2026-10-16  9:32 ` [PATCH net-next v3 3/8] net: xilinx: tsn: add endpoint MAC driver skeleton Srinivas Neeli
@ 2026-10-20 11:58   ` netdev-bot+sashiko
  0 siblings, 0 replies; 17+ messages in thread
From: netdev-bot+sashiko @ 2026-10-20 11:58 UTC (permalink / raw)
  To: srinivas.neeli
  Cc: nagadheeraj.rottela, andrew+netdev, davem, kuba, pabeni, robh,
	krzk+dt, conor+dt, richardcochran, michal.simek, bigeasy,
	clrkwllms, rostedt, edumazet, netdev, devicetree, linux-kernel,
	linux-arm-kernel, linux-rt-devel, neelisrinivas18, git

Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.

Low priority / nit picks for your consideration:

- [Low] xlnx_tsn_ep_probe() in
  drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c (line 80) open-codes…

--

net: xilinx: tsn: add endpoint MAC driver skeleton

This adds a platform driver for the "xlnx,tsn-ep-mac" endpoint MAC. The
endpoint MAC provides the conduit netdev for the TSN DSA switch. The driver
registers an eth%d netdev, sets its MAC address from DT or a random one,
and provides minimal netdev/ethtool ops where xmit just drops frames.

> diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> new file mode 100644
> index 0000000000000..609d444c68356
> --- /dev/null
> +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c

[ ... ]

> +static int xlnx_tsn_ep_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct net_device *ndev;
> +	struct xlnx_tsn_ep *ep;
> +	u8 mac_addr[ETH_ALEN];
> +	int ret;
> +
> +	ndev = alloc_netdev(sizeof(*ep), "eth%d", NET_NAME_UNKNOWN, ether_setup);

[Severity: Low]
Should this be NET_NAME_ENUM rather than NET_NAME_UNKNOWN?

With the "eth%d" template the kernel chooses the number. The standard
helper for that case uses NET_NAME_ENUM:

net/ethernet/eth.c:alloc_etherdev_mqs() {
	return alloc_netdev_mqs(sizeof_priv, "eth%d", NET_NAME_ENUM,
				ether_setup, txqs, rxqs);
}

The DSA user port fallback in dsa_user_create() in net/dsa/user.c also
pairs "eth%d" with NET_NAME_ENUM. The v2 changelog gave that as its reason.

With NET_NAME_UNKNOWN, reading /sys/class/net/ethX/name_assign_type for
this device returns -EINVAL:

net/core/net-sysfs.c:name_assign_type_show() {
	...
	if (READ_ONCE(ndev->name_assign_type) != NET_NAME_UNKNOWN)
		ret = netdev_show(dev, attr, buf, format_name_assign_type);
	...
}

Netdevs from alloc_etherdev() and the DSA user netdevs stacked on top of
this conduit report 1 there.

The v3 changelog says:

  "Register the netdev with the standard eth%d name (NET_NAME_UNKNOWN)"

That doesn't match what the standard helper does. Would alloc_etherdev()
work here instead? It would also drop the open-coded ether_setup.

Later in the series, "net: xilinx: tsn: parse endpoint DMA channel
configuration" changes this call to alloc_netdev_mqs(sizeof(*ep), "eth%d",
NET_NAME_UNKNOWN, ether_setup, ...) and still uses NET_NAME_UNKNOWN.
alloc_etherdev_mqs() would cover that multi-queue version.

> +	if (!ndev)
> +		return -ENOMEM;

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261016-patches_v2_external-v3-0-e33d3960ca1e%40amd.com

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH net-next v3 4/8] net: xilinx: tsn: parse endpoint DMA channel configuration
  2026-10-16  9:32 ` [PATCH net-next v3 4/8] net: xilinx: tsn: parse endpoint DMA channel configuration Srinivas Neeli
@ 2026-10-20 11:58   ` netdev-bot+sashiko
  0 siblings, 0 replies; 17+ messages in thread
From: netdev-bot+sashiko @ 2026-10-20 11:58 UTC (permalink / raw)
  To: srinivas.neeli
  Cc: nagadheeraj.rottela, andrew+netdev, davem, kuba, pabeni, robh,
	krzk+dt, conor+dt, richardcochran, michal.simek, bigeasy,
	clrkwllms, rostedt, edumazet, netdev, devicetree, linux-kernel,
	linux-arm-kernel, linux-rt-devel, neelisrinivas18, git

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 1 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Medium] The driver enforces DT rules that the in-tree binding
  (Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-…

--

net: xilinx: tsn: parse endpoint DMA channel configuration

This commit counts the TX and RX MCDMA channels listed in dma-names and
allocates the conduit netdev with that many queues. It also parses the
tx-queues-config child node to map each logical TX queue to a tx_chanN
entry. Probe fails if the mapping is not one-to-one, or if the TX
channel count differs from xlnx,num-priorities.

> diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> index 609d444c68356..2adee07f6f51c 100644
> --- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c

[ ... ]

>  static int xlnx_tsn_ep_probe(struct platform_device *pdev)
>  {

[ ... ]

> +	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_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);

[Severity: Medium]
Do these rules match what the binding in
Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
allows?

Here xlnx_tsn_ep_probe() requires the number of tx_chanN entries in
dma-names to equal xlnx,num-priorities. ep_parse_tx_queue_config() adds
three more rules:

	if (!str_has_prefix(qnode->name, "queue") ||
	    kstrtou32(qnode->name + strlen("queue"), 10, &queue) ||
	    queue >= ep->num_tx_queues)
	...
	if (test_and_set_bit(chan, chan_seen))
	...
	if (count != ep->num_tx_queues)

The binding only has:

	dma-names:
	  items:
	    pattern: "^(tx|rx)_chan[0-7]$"
	  allOf:
	    - contains:
	        pattern: "^tx_chan[0-7]$"
	    - contains:
	        pattern: "^rx_chan[0-7]$"

	tx-queues-config:
	  patternProperties:
	    "^queue[0-7]$":

The allOf blocks at the end of the binding only require queue0 through
queue(num-priorities - 1). The binding does not tie the tx_chanN count to
xlnx,num-priorities. It does not forbid queueN nodes at or above
num-priorities, and it does not require each queue to use a distinct
channel.

Its description reads as a general mapping:

	For example, queue0 with xlnx,dma-channel-num set to 5 means
	priority queue 0 uses tx_chan5 for data transfer.

So some device trees pass schema validation but would make probe fail
with -EINVAL. For example:

  - xlnx,num-priorities = <4> with all eight tx_chanN entries listed and
    queue0..queue3 mapped to channels 4..7
  - xlnx,num-priorities = <2> with queue0, queue1 and queue2 nodes
  - xlnx,num-priorities = <2> with two tx channels and both queues
    mapped to channel 0

The binding also sets minimum: 2 for xlnx,num-priorities, and the driver
does not check that.

Later patches in the series do not change the binding, so the mismatch
is still there at the end of the series.

Should the binding describe and enforce the one-to-one queue/channel rule
and the num-priorities rule? Or should the driver accept the general
mapping that the binding describes?

>  
> -	ndev = alloc_netdev(sizeof(*ep), "eth%d", NET_NAME_UNKNOWN, ether_setup);
> +	ndev = alloc_netdev_mqs(sizeof(*ep), "eth%d", NET_NAME_UNKNOWN,
> +				ether_setup, num_tx_dma_channels,
> +				num_rx_dma_channels);

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261016-patches_v2_external-v3-0-e33d3960ca1e%40amd.com

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH net-next v3 5/8] net: xilinx: tsn: bring up the endpoint MCDMA channels
  2026-10-16  9:32 ` [PATCH net-next v3 5/8] net: xilinx: tsn: bring up the endpoint MCDMA channels Srinivas Neeli
@ 2026-10-20 11:58   ` netdev-bot+sashiko
  0 siblings, 0 replies; 17+ messages in thread
From: netdev-bot+sashiko @ 2026-10-20 11:58 UTC (permalink / raw)
  To: srinivas.neeli
  Cc: nagadheeraj.rottela, andrew+netdev, davem, kuba, pabeni, robh,
	krzk+dt, conor+dt, richardcochran, michal.simek, bigeasy,
	clrkwllms, rostedt, edumazet, netdev, devicetree, linux-kernel,
	linux-arm-kernel, linux-rt-devel, neelisrinivas18, git

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 1 · Low: 1

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Medium] ep_reset_dma_controller() resets the whole MCDMA core at probe
  through one channel handle, but the driver does not own every channel…
- [Low] In drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c, the kernel-
  doc for struct skbuf_dma_descriptor calls @dma_address the "physical…

--

net: xilinx: tsn: bring up the endpoint MCDMA channels

At probe, this patch requests every TX and RX MCDMA channel named in the
device tree and holds them until remove. It also resets the MCDMA
controller once, through the first TX channel. Each channel gets a ring of
preallocated SKB descriptor slots in ndo_open, and the rings are torn down
in ndo_stop.

> diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> index 2adee07f6f51c..ab3d6d8cb9440 100644
> --- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c

[ ... ]

> @@ -30,6 +35,47 @@
>  #define TSN_MAX_VLAN_FRAME_SIZE		(ETH_DATA_LEN + VLAN_ETH_HLEN + \
>  					 ETH_FCS_LEN)
>  
> +#define TX_BD_NUM_DEFAULT		64
> +#define RX_BD_NUM_DEFAULT		128
> +
> +/**
> + * struct skbuf_dma_descriptor - skb container for each in-flight DMA descriptor
> + * @sgl: scatter-gather list backing the DMA mapping
> + * @desc: dmaengine descriptor handle
> + * @dma_address: physical address of the first sgl entry (RX path)

[Severity: Low]

This isn't a bug, but is "physical address" the right description for
@dma_address?

The field is a dma_addr_t. ep_free_dma_chan() passes it to
dma_unmap_single() as a mapping handle, and in the RX data path patch
ep_rx_submit_desc() fills it from sg_dma_address() after dma_map_single().
If an IOMMU sits in front of the MCDMA, the value is an IOVA, not a CPU
physical address.

The same wording is still there at the end of the series. Would something
like "DMA address of the first sgl entry" be more accurate?

> + * @skb: SKB owning the buffer
> + * @sg_len: number of valid entries in @sgl (TX path)
> + */

[ ... ]

> +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);

[Severity: Medium]

Can this reset stall channels on the same MCDMA instance that belong to
another dmaengine client?

The commit message says:

  "An MCDMA soft reset resets the whole core, so any channel of the
  instance reaches it."

For MCDMA, every MM2S channel uses ctrl_offset 0, which is the common
control register. The call chain is:

xlnx_tsn_ep_probe()
  ep_reset_dma_controller()
    xilinx_vdma_channel_set_config()
      xilinx_dma_chan_reset()
        xilinx_dma_reset()

drivers/dma/xilinx/xilinx_dma.c:xilinx_dma_reset() {
	dma_ctrl_set(chan, XILINX_DMA_REG_DMACR, XILINX_DMA_DMACR_RESET);
	...
	chan->err = false;
	chan->idle = true;
	chan->desc_pendingcount = 0;
	chan->desc_submitcount = 0;
	...
}

The hardware reset hits the whole core, but software state is only reset
for tx_dma[0].

The endpoint only requests the tx_chanN/rx_chanN entries in its own
dma-names. Nothing in the binding or the driver stops the other channels of
the instance from going to another consumer. Suppose one of those channels
is mid-transfer when the endpoint probes, for example after a probe
deferral or a module load. It is still marked idle == false and has
descriptors on active_list. Its next submit then goes through:

drivers/dma/xilinx/xilinx_dma.c:xilinx_mcdma_start_transfer() {
	...
	if (chan->has_sg && list_empty(&chan->active_list))
		xilinx_write(chan, XILINX_MCDMA_CHAN_CDESC_OFFSET(chan->tdest),
			     head_desc->async_tx.phys);
	...
	if (chan->idle)
		xilinx_dma_start(chan);
	...
}

CDESC is not reprogrammed and xilinx_dma_start() is skipped. Doesn't that
leave the engine halted, with no error reported to that client?

I could not confirm that shared MCDMA designs exist in practice. The
binding example gives the whole instance to the endpoint.

Is the reset needed at all? The provider already resets each channel in
xilinx_dma_chan_probe(). The MCDMA path of
xilinx_dma_alloc_chan_resources() only allocates BD memory and does not
program the hardware. So the endpoint's own channels look clean already,
and the only visible effect of this reset is on channels the endpoint does
not own.

Calling dmaengine_terminate_sync() from ep_free_dma_chan() on ndo_stop also
resets the whole core, through the provider's xilinx_dma_terminate_all().
Any MCDMA client that terminates a channel causes this, so that part comes
from the provider and not from this patch.

> +	if (ret < 0)
> +		return dev_err_probe(ep->dev, ret,
> +				     "failed to reset DMA controller\n");
> +
> +	return 0;
> +}

[ ... ]

> @@ -286,9 +587,17 @@ static int xlnx_tsn_ep_probe(struct platform_device *pdev)
>  	if (ret)
>  		goto err_free_ndev;
>  
> +	ret = ep_request_dma_chans(ep, rx_dma_chan_map);
> +	if (ret)
> +		goto err_free_ndev;
> +
> +	ret = ep_reset_dma_controller(ep);
> +	if (ret)
> +		goto err_release_chans;
> +

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261016-patches_v2_external-v3-0-e33d3960ca1e%40amd.com

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH net-next v3 6/8] net: xilinx: tsn: add the endpoint RX data path
  2026-10-16  9:32 ` [PATCH net-next v3 6/8] net: xilinx: tsn: add the endpoint RX data path Srinivas Neeli
@ 2026-10-20 11:58   ` netdev-bot+sashiko
  0 siblings, 0 replies; 17+ messages in thread
From: netdev-bot+sashiko @ 2026-10-20 11:58 UTC (permalink / raw)
  To: srinivas.neeli
  Cc: nagadheeraj.rottela, andrew+netdev, davem, kuba, pabeni, robh,
	krzk+dt, conor+dt, richardcochran, michal.simek, bigeasy,
	clrkwllms, rostedt, edumazet, netdev, devicetree, linux-kernel,
	linux-arm-kernel, linux-rt-devel, neelisrinivas18, git

Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider.

Critical: 1 · High: 1 · Medium: 2 · Low: 1

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Critical] RX buffers are freed and unmapped while the MCDMA engine
  still owns them, so incoming DMA lands in freed memory.
- [High] The RX path cannot deliver any frame on the in-tree Xilinx AXI
  MCDMA provider.
- [Medium] After an MCDMA RX channel error, RX on that queue stops
  permanently while the interface stays up.
- [Medium] A partially armed RX batch can hold received frames for an
  unbounded time under sparse traffic.
- [Low] ep_rx_refill() decides whether 'at least one descriptor is armed'
  by testing ring_head != READ_ONCE(ring_tail) after…

--

net: xilinx: tsn: add the endpoint RX data path

This commit posts RX buffers to every MCDMA RX channel on open. It reaps
them from the dmaengine completion callback and passes MAC1/MAC2 frames up
the stack with __netif_rx(). Refill runs from the completion callback, with
a retry timer for a drained ring and a closing flag plus a per-channel lock
to serialise refill against teardown.

> diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> index ab3d6d8cb9440..0a13e04d40575 100644
> --- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c

[ ... ]

> @@ -112,6 +135,181 @@ ep_get_desc(struct xlnx_tsn_ep_dma_chan *xchan, int idx)

[ ... ]

> +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);

[Severity: Critical]
Can this give RX buffers back to the allocator while the MCDMA engine still
owns them?

On open, this loop posts CIRC_SPACE(0, 0, 128) = 127 descriptors in one
batch. xilinx_mcdma_start_transfer() only reprograms the IRQ coalescing
threshold when the batch is small enough:

drivers/dma/xilinx/xilinx_dma.c:xilinx_mcdma_start_transfer() {
    ...
	if (chan->desc_pendingcount <= XILINX_MCDMA_COALESCE_MAX) {
		reg &= ~XILINX_MCDMA_COALESCE_MASK;
		reg |= chan->desc_pendingcount <<
			XILINX_MCDMA_COALESCE_SHIFT;
	}
    ...
}

XILINX_MCDMA_COALESCE_MAX is 24. With 127 pending, the threshold keeps its
previous or reset value, which is well below 127.

When the first IOC fires, xilinx_mcdma_irq_handler() calls
xilinx_dma_complete_descriptor(). That function only checks per-BD
completion for AXIDMA:

drivers/dma/xilinx/xilinx_dma.c:xilinx_dma_complete_descriptor() {
    ...
	list_for_each_entry_safe(desc, next, &chan->active_list, node) {
		if (chan->xdev->dma_config->dmatype == XDMA_TYPE_AXIDMA) {
			...
			if (!(seg->hw.status & XILINX_DMA_BD_COMP_MASK) && chan->has_sg)
				break;
		}
		...
		list_add_tail(&desc->node, &chan->done_list);
	}
}

For MCDMA, every active descriptor goes to done_list, and the tasklet runs
ep_dma_rx_cb() for each of them. For every BD the hardware has not filled
yet, ep_dma_rx_cb() unconditionally calls dma_unmap_single() and then frees
the skb.

The S2MM engine's CURDESC..TAILDESC chain still points at those BDs and
buffer addresses. Would the next received frames then be written into
freed skb memory?

The descriptor segments are also recycled, and the next refill rewrites
them while the engine is running. active_list is empty by then, so
xilinx_mcdma_start_transfer() also rewrites CDESC on a channel that already
has RUNSTOP set:

	if (chan->has_sg && list_empty(&chan->active_list))
		xilinx_write(chan, XILINX_MCDMA_CHAN_CDESC_OFFSET(chan->tdest),
			     head_desc->async_tx.phys);

The same thing looks possible in steady state, whenever a small refill
sets the threshold to 1 while other descriptors are still active.

The commit message says dmaengine "delivers one descriptor per callback".
That assumes per-BD completion, and this provider doesn't appear to
provide it for MCDMA.

[Severity: Medium]
Could a partially armed batch hold received frames for an unbounded time
when traffic is sparse?

This loop can post between 2 and 24 descriptors in one call. That happens
if dev_alloc_skb() fails partway through, or if a callback refills several
slots at once. xilinx_mcdma_start_transfer() then sets the coalescing
threshold to that count, and no IOC is raised until that many frames have
arrived.

xilinx_mcdma_irq_handler() only logs the delay interrupt and reaps nothing:

	if (status & XILINX_MCDMA_IRQ_DELAY_MASK) {
		...
		dev_dbg(chan->dev, "Inter-packet latency too long\n");
	}

No delay timer appears to be programmed for MCDMA. ep_rx_refill() returns
true because the ring is non-empty, so the refill timer isn't armed either.

Would low-rate traffic such as ARP, PTP or control frames sit in host
memory until enough further frames arrive, or indefinitely if traffic
stops?

> +
> +		if (xchan->ring_head != READ_ONCE(xchan->ring_tail))
> +			return true;

[Severity: Medium]
What recovers this RX queue after an MCDMA channel error?

On XILINX_MCDMA_IRQ_ERR_MASK, xilinx_mcdma_irq_handler() sets
chan->err = true. An error-only interrupt completes no descriptor, because
completion only happens under XILINX_MCDMA_IRQ_IOC_MASK. So no
ep_dma_rx_cb() runs, and xilinx_mcdma_start_transfer() returns early while
chan->err is set.

The outstanding descriptors keep ring_head != ring_tail here. The refill
timer is never armed and nothing new is submitted. The only automatic
reset is in xilinx_dma_tx_submit():

	if (chan->err) {
		...
		err = xilinx_dma_chan_reset(chan);

That reset needs a new submit, and none ever happens.

Does the queue then stay halted until ifdown/ifup, with the interface up
and no driver-level counter showing the problem?

> +
> +		if (arm_timer)
> +			mod_timer(&xchan->rx_refill_timer,
> +				  jiffies + EP_RX_REFILL_RETRY);
> +	}
> +
> +	return false;
> +}

[ ... ]

> +static void ep_dma_rx_cb(void *data, const struct dmaengine_result *result)
> +{

[ ... ]

> +	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;

[Severity: High]
Can any frame reach __netif_rx() with the in-tree MCDMA provider?

For XDMA_TYPE_AXIMCDMA, xilinx_dma_probe() installs
xilinx_mcdma_prep_slave_sg(), and that function never sets
async_tx.metadata_ops. DESC_METADATA_ENGINE is only set when
has_axistream_connected is true, and that flag is only read for AXIDMA:

drivers/dma/xilinx/xilinx_dma.c:xilinx_dma_probe() {
    ...
	if (xdev->dma_config->dmatype == XDMA_TYPE_AXIDMA) {
		xdev->has_axistream_connected =
			of_property_read_bool(node, "xlnx,axistream-connected");
	}
    ...
	if (xdev->has_axistream_connected)
		xdev->common.desc_metadata_modes = DESC_METADATA_ENGINE;
    ...
}

So dmaengine_desc_get_metadata_ptr() looks like it always returns an
ERR_PTR for these descriptors. The error comes either from
desc_check_and_set_metadata_mode() or from the NULL metadata_ops check.

If so, every DMA_TRANS_NOERROR completion is dropped here with "Failed to
get RX metadata pointer". ndo_open still succeeds, so the interface comes
up with RX dead.

Wiring up the existing hook wouldn't fix this on its own.
xilinx_dma_get_metadata_ptr() casts to the AXIDMA segment type and returns
the app words, which start at BD offset 0x20:

	seg = list_first_entry(&desc->segments,
			       struct xilinx_axidma_tx_segment, node);
	return seg->hw.app;

In struct xilinx_aximcdma_desc_hw, status is at 0x18 and sideband_status
(TID/TDEST/TUSER) is at 0x1C. The "[0] = status, [1] = sideband" layout
assumed here doesn't match any get_ptr in the tree. metadata[1] would be
an app word, and the TUSER port_id filter would act on unrelated bits.

metadata[1] is also read without checking meta_len.

No patch in the series touches drivers/dma/. The same code is still there
at the end of the series, in "net: xilinx: tsn: deliver endpoint RX frames
to DSA user ports".

[ ... ]

> @@ -305,9 +523,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;
> +		}
> +	}

[Severity: Low]
Can ep_rx_refill() return false here even though a descriptor was armed?

ep_rx_refill() decides success by testing ring_head !=
READ_ONCE(ring_tail) after dma_async_issue_pending(). ep_dma_rx_cb()
advances ring_tail outside rx_lock, before it calls ep_rx_refill() and
blocks on the lock:

	WRITE_ONCE(xchan->ring_tail, xchan->ring_tail + 1);

The callback runs in the provider tasklet, possibly on another CPU, and
spin_lock_bh() only blocks BH locally.

Suppose only one slot gets armed, because dev_alloc_skb() (GFP_ATOMIC) or
prep fails on the second slot. If that frame completes before the
head/tail test, head == tail and ep_rx_refill() returns false. ndo_open
then fails with -ENOMEM and "no descriptors armed", even though the frame
has already gone up through __netif_rx().

Teardown after that failure looks safe. closing is set before
ep_free_dma_chan() takes the lock and shuts down the timer.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261016-patches_v2_external-v3-0-e33d3960ca1e%40amd.com

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH net-next v3 7/8] net: xilinx: tsn: add the endpoint TX data path
  2026-10-16  9:32 ` [PATCH net-next v3 7/8] net: xilinx: tsn: add the endpoint TX " Srinivas Neeli
@ 2026-10-20 11:58   ` netdev-bot+sashiko
  0 siblings, 0 replies; 17+ messages in thread
From: netdev-bot+sashiko @ 2026-10-20 11:58 UTC (permalink / raw)
  To: srinivas.neeli
  Cc: nagadheeraj.rottela, andrew+netdev, davem, kuba, pabeni, robh,
	krzk+dt, conor+dt, richardcochran, michal.simek, bigeasy,
	clrkwllms, rostedt, edumazet, netdev, devicetree, linux-kernel,
	linux-arm-kernel, linux-rt-devel, neelisrinivas18, git

Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider.

Critical: 1 · High: 1 · Medium: 2 · Low: 1

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Critical] Use-after-free in ep_start_xmit()
  (drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c).
- [High] Shutdown race: the closing flag does not stop a TX completion
  that is already running from waking a queue after ep_stop() has called…
- [Medium] The TX path cannot recover from an MCDMA channel error.
- [Medium] TX flow control counts frames, but the DMA engine runs out of
  segments.
- [Low] The patch now produces software TX timestamps (skb_tx_timestamp()
  in ep_start_xmit), but ep_ethtool_ops (line 515) only has .get_drvinfo…

--

net: xilinx: tsn: add the endpoint TX data path

This adds the transmit path to the Xilinx TSN endpoint driver. Each frame
is mapped into a scatterlist and submitted to the DMA channel in
tx_chans[] for the frame's logical queue. The queue is stopped when the
ring fills and woken from the DMA completion callback.

> diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> index 0a13e04d40575..bd8cd504cefb4 100644
> --- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c

[ ... ]

> @@ -310,8 +314,150 @@ 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)
> +{

[ ... ]

> +	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);

[Severity: Medium]
How does the TX path recover if the MCDMA channel reports an error?

In drivers/dma/xilinx/xilinx_dma.c, xilinx_mcdma_irq_handler() sets
chan->err on an error. After that, xilinx_mcdma_start_transfer() stops
handing BDs to the hardware:

xilinx_mcdma_start_transfer() {
    ...
	if (chan->err)
		return;
    ...
}

The flag is only cleared by xilinx_dma_chan_reset(). That runs from
xilinx_dma_tx_submit() on the next submit, or from terminate.

Suppose the error arrives while the TX queue is stopped, either by
netif_txq_maybe_stop() or by BQL through netdev_tx_sent_queue(). Then
ep_start_xmit() is never called again. No reset happens, ring_tail never
advances and __netif_txq_completed_wake() never runs. The queue stays
stopped until ifdown/ifup.

ep_netdev_ops has no .ndo_tx_timeout either. So the watchdog never
terminates the channel or resets ring_head/ring_tail and the BQL state.

A related point: xilinx_dma_complete_descriptor() checks per-BD
completion only for XDMA_TYPE_AXIDMA. Can descriptors abandoned across a
submit-triggered reset later complete here as DMA_TRANS_NOERROR and be
counted as tx_packets?

This looks unchanged at the end of the series.

> +	} 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));

[Severity: High]
Does the closing flag really prevent a wake after ep_stop() has disabled
the queues?

__netif_txq_completed_wake() checks netif_tx_queue_stopped(txq), then
checks !closing, then calls netif_tx_wake_queue(). None of these steps
is serialised against ep_stop():

CPU A (TX completion)               CPU B (ep_stop)
__netif_txq_completed_wake()
  queue stopped, closing == false
                                    WRITE_ONCE(ep->closing, true);
                                    netif_tx_disable(ndev);
  netif_tx_wake_queue(txq)
                                    ep_exit_dmaengine(ep);
                                      frees TX channels
                                      ep->tx_chans = NULL

netif_tx_disable() takes and drops each txq lock, then returns. It does
not wait for a completion that has already passed the closing check.

The qdisc path is covered by dev_deactivate_many(). generic_xdp_tx() is
not: it only checks netif_xmit_frozen_or_drv_stopped(txq) under
HARD_TX_LOCK.

It can be reached through xdp_do_generic_redirect(). xdp_ok_fwd_dev()
checks only IFF_UP, which is cleared after ndo_stop returns. It can also
be reached through generic XDP_TX on this device's own RX, whose
channels are freed after the TX channels.

Could ep_start_xmit() then dereference a NULL ep->tx_chans or a freed
xchan on the re-woken queue?

The v3 changelog says the patch will "Skip the queue wake in the
completion callback while closing". A lockless flag test doesn't seem
to give that guarantee. This also looks unchanged at the end of the
series.

> +}
> +
>  static netdev_tx_t ep_start_xmit(struct sk_buff *skb, struct net_device *ndev)
>  {

[ ... ]

> +	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;

[Severity: Medium]
Can this path drop frames under load once the DMA segment pool runs out?

With ndev->features = NETIF_F_SG, one skb can need up to
MAX_SKB_FRAGS + 1 scatterlist entries. xilinx_mcdma_prep_slave_sg()
takes at least one segment per entry. The segments come from a fixed
per-channel pool of XILINX_DMA_NUM_DESCS (512):

xilinx_mcdma_prep_slave_sg() {
    ...
			segment = xilinx_aximcdma_alloc_tx_segment(chan);
			if (!segment)
				goto error;
    ...
}

The driver ring holds TX_BD_NUM_DEFAULT (64) frames. The stop/wake
thresholds of 2 count only frame slots.

Suppose in-flight frames average more than 8 segments each, for example
MSG_ZEROCOPY with many small frags. The pool then empties while
CIRC_SPACE() still reports room. prep returns NULL and the frame is
counted as tx_dropped, instead of the queue being stopped.

This looks unchanged at the end of the series.

> +
> +	skbuf_dma->skb = skb;
> +	skbuf_dma->sg_len = sg_len;
> +	dma_tx_desc->callback_param = xchan;
> +	dma_tx_desc->callback_result = ep_dma_tx_cb;
> +
> +	spin_lock_bh(&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);
> +
> +	skb_tx_timestamp(skb);
> +	dma_async_issue_pending(xchan->chan);

[Severity: Critical]
Can the skb already be freed when skb_tx_timestamp() runs here?

Once dmaengine_submit() has returned and tx_lock is dropped,
ep_dma_tx_cb() can take skbuf_dma->skb and free it with
dev_kfree_skb_any() or dev_consume_skb_any().

This ordering seems to assume the transfer can't start before
dma_async_issue_pending(). However, the Xilinx MCDMA provider starts
pending descriptors from its IRQ handler when an earlier transfer
finishes:

xilinx_mcdma_irq_handler() {
    ...
		if (list_empty(&chan->active_list)) {
			chan->idle = true;
			chan->start_transfer(chan);
		}
    ...
}

xilinx_mcdma_start_transfer() then moves the whole pending list onto the
hardware:

	list_splice_tail_init(&chan->pending_list, &chan->active_list);

So with an earlier frame in flight on the same channel:

CPU A (ep_start_xmit)               CPU B
dmaengine_submit(frame B)
spin_unlock_bh(&xchan->tx_lock)
  <delayed by irq/preemption>
                                    IRQ for earlier frame
                                      start_transfer() starts frame B
                                    frame B completes
                                    ep_dma_tx_cb() frees frame B
skb_tx_timestamp(frame B)

skb_tx_timestamp() reads skb->sk, skb->dev and skb_shinfo()->tx_flags.
With SOF_TIMESTAMPING_TX_SOFTWARE or a PHY timestamper, it also clones
the skb or queues it to the socket error queue.

The skb_tx_timestamp() kernel-doc says it must be called "before TX
completion of this packet can trigger. Otherwise the packet could
potentially already be freed." The v3 note says the call was placed "at
the hardware handoff", but here it runs after the handoff.

Would moving skb_tx_timestamp() before dmaengine_submit() avoid this?
The later patches in the series don't touch the TX path, so this is
still present at the end of the series.

[Severity: Low]
The driver now generates software TX timestamps. Should ep_ethtool_ops
also get a .get_ts_info, such as ethtool_op_get_ts_info()?

ep_ethtool_ops only has .get_drvinfo. The default in
__ethtool_get_ts_info() reports only SOF_TIMESTAMPING_RX_SOFTWARE |
SOF_TIMESTAMPING_SOFTWARE. ethtool_op_get_ts_info() is what adds
SOF_TIMESTAMPING_TX_SOFTWARE.

As a result, ethtool -T won't list software-transmit for this device.
Tools such as linuxptp, which check TSINFO first, will treat it as
unsupported.

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261016-patches_v2_external-v3-0-e33d3960ca1e%40amd.com

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH net-next v3 8/8] net: xilinx: tsn: deliver endpoint RX frames to DSA user ports
  2026-10-16  9:32 ` [PATCH net-next v3 8/8] net: xilinx: tsn: deliver endpoint RX frames to DSA user ports Srinivas Neeli
@ 2026-10-20 11:58   ` netdev-bot+sashiko
  0 siblings, 0 replies; 17+ messages in thread
From: netdev-bot+sashiko @ 2026-10-20 11:58 UTC (permalink / raw)
  To: srinivas.neeli
  Cc: nagadheeraj.rottela, andrew+netdev, davem, kuba, pabeni, robh,
	krzk+dt, conor+dt, richardcochran, michal.simek, bigeasy,
	clrkwllms, rostedt, edumazet, netdev, devicetree, linux-kernel,
	linux-arm-kernel, linux-rt-devel, neelisrinivas18, git

Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.

Low priority / nit picks for your consideration:

- [Low] The new RX demux in ep_dma_rx_cb()
  (drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c) relies on a DSA…

--

net: xilinx: tsn: deliver endpoint RX frames to DSA user ports

At probe time this commit allocates one METADATA_HW_PORT_MUX metadata dst
for each of the two MAC ports. In ep_dma_rx_cb() it then attaches the
matching dst to each RX frame based on its TUSER port_id, gated on
netdev_uses_dsa(), so DSA can deliver the frame to its swpN user port.

> diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> index bd8cd504cefb4..5103a3b4b018a 100644
> --- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c

[ ... ]

> @@ -302,6 +311,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);
> +	}

[Severity: Low]
Can this branch be reached at all with the tree as submitted?

As far as I can tell, nothing under drivers/net/dsa/ binds
"xlnx,tsn-switch", and net/dsa/ has no TSN tagger. The only references
are the binding in xlnx,tsn-endpoint-ethernet-mac.yaml and this Kconfig
help text:

    The three-port switch is supported by the companion DSA driver.

Without that driver, nothing sets dsa_ptr on this netdev, so
netdev_uses_dsa() is always false here. That leaves three pieces of dead
code for now: this branch, the port_md[] allocation in
xlnx_tsn_ep_probe(), and ep_free_port_md(). It also means RX delivery to
swp1/swp2 can't be tested from this tree.

Will the companion DSA driver be posted along with this series?

The commit message also says:

    guarded by netdev_uses_dsa() so it runs only while a switch is bound
    as the conduit.

netdev_uses_dsa() checks a bit more than that:

include/net/dsa.h:netdev_uses_dsa() {
    ...
	return dev->dsa_ptr && dev->dsa_ptr->rcv;
    ...
}

dsa_port_set_tag_protocol() copies cpu_dp->rcv directly from the
tagger's ops.

Every frame accepted here carries a METADATA_HW_PORT_MUX dst, so
dsa_switch_rcv() always takes the md_dst branch and never calls
cpu_dp->rcv. The tagger still has to provide a non-NULL .rcv, though,
or this code never runs.

With a tagger that has no .rcv, such as none_ops in net/dsa/tag_none.c,
every frame would quietly stay on the conduit eth%d. tag_none.c warns
against using it for new switches, so this mainly applies to whatever
tagger the companion driver adds.

Could the commit message, or a comment here, say that the tagger must
provide an .rcv callback, even though it won't be called for these
frames?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261016-patches_v2_external-v3-0-e33d3960ca1e%40amd.com

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2026-10-20 11:58 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-16  9:31 [PATCH net-next v3 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
2026-10-16  9:32 ` [PATCH net-next v3 1/8] dt-bindings: net: add Xilinx TSN Endpoint Ethernet MAC Srinivas Neeli
2026-10-20 11:58   ` netdev-bot+sashiko
2026-10-16  9:32 ` [PATCH net-next v3 2/8] net: xilinx: tsn: add TSN endpoint wrapper driver Srinivas Neeli
2026-10-20 11:58   ` netdev-bot+sashiko
2026-10-16  9:32 ` [PATCH net-next v3 3/8] net: xilinx: tsn: add endpoint MAC driver skeleton Srinivas Neeli
2026-10-20 11:58   ` netdev-bot+sashiko
2026-10-16  9:32 ` [PATCH net-next v3 4/8] net: xilinx: tsn: parse endpoint DMA channel configuration Srinivas Neeli
2026-10-20 11:58   ` netdev-bot+sashiko
2026-10-16  9:32 ` [PATCH net-next v3 5/8] net: xilinx: tsn: bring up the endpoint MCDMA channels Srinivas Neeli
2026-10-20 11:58   ` netdev-bot+sashiko
2026-10-16  9:32 ` [PATCH net-next v3 6/8] net: xilinx: tsn: add the endpoint RX data path Srinivas Neeli
2026-10-20 11:58   ` netdev-bot+sashiko
2026-10-16  9:32 ` [PATCH net-next v3 7/8] net: xilinx: tsn: add the endpoint TX " Srinivas Neeli
2026-10-20 11:58   ` netdev-bot+sashiko
2026-10-16  9:32 ` [PATCH net-next v3 8/8] net: xilinx: tsn: deliver endpoint RX frames to DSA user ports Srinivas Neeli
2026-10-20 11:58   ` netdev-bot+sashiko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox