Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH net-next v2 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver
@ 2026-09-08 19:19 Srinivas Neeli
  2026-09-08 19:19 ` [PATCH net-next v2 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-09-08 19:19 UTC (permalink / raw)
  To: Nagadheeraj Rottela, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Richard Cochran, Michal Simek,
	Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt
  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 v2 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)  |
                                      +-----------+  +-----------+

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.

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 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        |  364 +++++++
 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    | 1034 ++++++++++++++++++++
 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c  |  107 ++
 9 files changed, 1548 insertions(+)
---
base-commit: bc6fe9d301d59fe5cb236d646abe792bffae5fa6
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 v2 1/8] dt-bindings: net: add Xilinx TSN Endpoint Ethernet MAC
  2026-09-08 19:19 [PATCH net-next v2 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
@ 2026-09-08 19:19 ` Srinivas Neeli
  2026-09-12 20:36   ` netdev-bot+sashiko
  2026-09-08 19:19 ` [PATCH net-next v2 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-09-08 19:19 UTC (permalink / raw)
  To: Nagadheeraj Rottela, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Richard Cochran, Michal Simek,
	Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt
  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 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        | 364 +++++++++++++++++++++
 MAINTAINERS                                        |   7 +
 2 files changed, 371 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..4e65d1710da7
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
@@ -0,0 +1,364 @@
+# 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]$"
+        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. 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:
+      - 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
+
+    patternProperties:
+      "^(ethernet-)?ports$":
+        type: object
+        additionalProperties: false
+
+        properties:
+          '#address-cells':
+            const: 1
+
+          '#size-cells':
+            const: 0
+
+          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:
+              - ethernet
+
+        patternProperties:
+          "^port@[1-2]$":
+            type: object
+            unevaluatedProperties: true
+            description:
+              External Ethernet port. port@1 is wired to MAC1 and reached
+              through the mdio-1 bus, port@2 is wired to MAC2 and reached
+              through the mdio-2 bus.
+
+            properties:
+              reg:
+                enum: [1, 2]
+
+              phy-mode:
+                enum:
+                  - gmii
+                  - rgmii
+                  - rgmii-id
+
+            required:
+              - reg
+              - phy-mode
+              - phy-handle
+
+        required:
+          - port@0
+          - port@1
+          - port@2
+
+    required:
+      - compatible
+      - reg
+      - reg-names
+      - interrupts
+      - interrupt-names
+
+    oneOf:
+      - required: [ports]
+      - required: [ethernet-ports]
+
+    unevaluatedProperties: false
+
+additionalProperties: false
+
+required:
+  - compatible
+  - clocks
+  - clock-names
+  - xlnx,num-priorities
+  - ranges
+  - '#address-cells'
+  - '#size-cells'
+
+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>;
+
+                port@0 {
+                    reg = <0>;
+                    ethernet = <&ep_mac>;
+                    phy-mode = "internal";
+
+                    fixed-link {
+                        speed = <1000>;
+                        full-duplex;
+                    };
+                };
+
+                port@1 {
+                    reg = <1>;
+                    phy-handle = <&phy0>;
+                    phy-mode = "rgmii-id";
+                };
+
+                port@2 {
+                    reg = <2>;
+                    phy-handle = <&phy1>;
+                    phy-mode = "rgmii-id";
+                };
+            };
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index b23fb6f2f4ef..8f93f165a744 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29911,6 +29911,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.43.0



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

* [PATCH net-next v2 2/8] net: xilinx: tsn: add TSN endpoint wrapper driver
  2026-09-08 19:19 [PATCH net-next v2 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
  2026-09-08 19:19 ` [PATCH net-next v2 1/8] dt-bindings: net: add Xilinx TSN Endpoint Ethernet MAC Srinivas Neeli
@ 2026-09-08 19:19 ` Srinivas Neeli
  2026-09-12 20:36   ` netdev-bot+sashiko
  2026-09-08 19:19 ` [PATCH net-next v2 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-09-08 19:19 UTC (permalink / raw)
  To: Nagadheeraj Rottela, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Richard Cochran, Michal Simek,
	Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt
  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 8f93f165a744..ff94e314eeb1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29917,6 +29917,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.43.0



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

* [PATCH net-next v2 3/8] net: xilinx: tsn: add endpoint MAC driver skeleton
  2026-09-08 19:19 [PATCH net-next v2 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
  2026-09-08 19:19 ` [PATCH net-next v2 1/8] dt-bindings: net: add Xilinx TSN Endpoint Ethernet MAC Srinivas Neeli
  2026-09-08 19:19 ` [PATCH net-next v2 2/8] net: xilinx: tsn: add TSN endpoint wrapper driver Srinivas Neeli
@ 2026-09-08 19:19 ` Srinivas Neeli
  2026-09-12 20:36   ` netdev-bot+sashiko
  2026-09-08 19:19 ` [PATCH net-next v2 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-09-08 19:19 UTC (permalink / raw)
  To: Nagadheeraj Rottela, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Richard Cochran, Michal Simek,
	Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt
  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 named "ep", 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 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..089f17a126f5
--- /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 ("ep0" for the first IP instance)
+ * @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), "ep%d", NET_NAME_ENUM, 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.43.0



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

* [PATCH net-next v2 4/8] net: xilinx: tsn: parse endpoint DMA channel configuration
  2026-09-08 19:19 [PATCH net-next v2 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
                   ` (2 preceding siblings ...)
  2026-09-08 19:19 ` [PATCH net-next v2 3/8] net: xilinx: tsn: add endpoint MAC driver skeleton Srinivas Neeli
@ 2026-09-08 19:19 ` Srinivas Neeli
  2026-09-12 20:36   ` netdev-bot+sashiko
  2026-09-08 19:19 ` [PATCH net-next v2 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-09-08 19:19 UTC (permalink / raw)
  To: Nagadheeraj Rottela, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Richard Cochran, Michal Simek,
	Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt
  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 physical DMA 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 physical DMA channel.

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:
- 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 | 197 +++++++++++++++++++++++-
 1 file changed, 196 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 089f17a126f5..24025b1f6e66 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
@@ -5,9 +5,11 @@
  * Copyright (C) 2026 Advanced Micro Devices, Inc.
  */
 
+#include <linux/bitops.h>
 #include <linux/etherdevice.h>
 #include <linux/ethtool.h>
 #include <linux/if_ether.h>
+#include <linux/if_vlan.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/netdevice.h>
@@ -21,14 +23,31 @@
 
 #define DRIVER_NAME			"xilinx_tsn_ep"
 
+#define TSN_DMA_CH_INVALID		0xFFU
+#define TSN_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 ("ep0" for the first IP instance)
  * @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: logical TX queue index -> physical DMA channel number
+ * @rx_chan_num: RX ring index -> physical DMA channel number
+ * @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 rx_chan_num[TSN_MAX_RX_QUEUE];
+	u32 max_frm_size;
 };
 
 static netdev_tx_t ep_start_xmit(struct sk_buff *skb, struct net_device *ndev)
@@ -69,15 +88,173 @@ 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_queues(struct device *dev, u32 *out_tx, u32 *out_rx,
+			       u16 *tx_present, u32 *rx_chan_num)
+{
+	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_chan_num[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 rx_chan_num[TSN_MAX_RX_QUEUE];
 	struct device *dev = &pdev->dev;
+	struct device_node *txcfg_np;
+	u32 num_tx, num_rx, num_prio;
+	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_queues(dev, &num_tx, &num_rx, &tx_present, rx_chan_num);
+	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 != num_prio)
+		return dev_err_probe(dev, -EINVAL,
+				     "TX channel count %u must equal num-priorities %u\n",
+				     num_tx, num_prio);
 
-	ndev = alloc_netdev(sizeof(*ep), "ep%d", NET_NAME_ENUM, ether_setup);
+	ndev = alloc_netdev_mqs(sizeof(*ep), "ep%d", NET_NAME_ENUM,
+				ether_setup, num_tx, num_rx);
 	if (!ndev)
 		return -ENOMEM;
 
@@ -89,6 +266,24 @@ 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;
+	ep->num_rx_queues = num_rx;
+	ep->max_frm_size = TSN_MAX_VLAN_FRAME_SIZE;
+	memcpy(ep->rx_chan_num, rx_chan_num, num_rx * sizeof(*rx_chan_num));
+
+	for (i = 0; i < TSN_MAX_TX_QUEUE; i++)
+		ep->tx_dma_chan_map[i] = TSN_DMA_CH_INVALID;
+
+	txcfg_np = of_get_child_by_name(dev->of_node, "tx-queues-config");
+	if (!txcfg_np) {
+		ret = dev_err_probe(dev, -EINVAL,
+				    "missing tx-queues-config node\n");
+		goto err_free_ndev;
+	}
+	ret = ep_parse_tx_queue_config(ep, txcfg_np, tx_present);
+	of_node_put(txcfg_np);
+	if (ret)
+		goto err_free_ndev;
 
 	ret = of_get_mac_address(dev->of_node, mac_addr);
 	if (ret == -EPROBE_DEFER) {

-- 
2.43.0



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

* [PATCH net-next v2 5/8] net: xilinx: tsn: bring up the endpoint MCDMA channels
  2026-09-08 19:19 [PATCH net-next v2 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
                   ` (3 preceding siblings ...)
  2026-09-08 19:19 ` [PATCH net-next v2 4/8] net: xilinx: tsn: parse endpoint DMA channel configuration Srinivas Neeli
@ 2026-09-08 19:19 ` Srinivas Neeli
  2026-09-12 20:36   ` netdev-bot+sashiko
  2026-09-08 19:19 ` [PATCH net-next v2 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-09-08 19:19 UTC (permalink / raw)
  To: Nagadheeraj Rottela, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Richard Cochran, Michal Simek,
	Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt
  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 when the
interface opens, and give each one a ring of preallocated SKB descriptor
slots. Reset the controller once at probe time through the first mapped
TX channel, which also gates probe on the MCDMA provider being bound so
-EPROBE_DEFER stays in the probe path and never reaches ndo_open. Release
the channels on stop, where dmaengine_terminate_sync() returns them 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 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 | 281 ++++++++++++++++++++++++
 2 files changed, 282 insertions(+)

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 24025b1f6e66..23a15df71baa 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
@@ -6,16 +6,24 @@
  */
 
 #include <linux/bitops.h>
+#include <linux/circ_buf.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>
 #include <linux/if_vlan.h>
+#include <linux/io.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/scatterlist.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
 #include <linux/string.h>
 #include <linux/types.h>
 
@@ -30,6 +38,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 ("ep0" for the first IP instance)
@@ -39,6 +88,9 @@
  * @tx_dma_chan_map: logical TX queue index -> physical DMA channel number
  * @rx_chan_num: RX ring index -> physical DMA channel number
  * @max_frm_size: maximum frame size accepted on RX
+ * @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;
@@ -48,8 +100,19 @@ struct xlnx_tsn_ep {
 	u32 tx_dma_chan_map[TSN_MAX_TX_QUEUE];
 	u32 rx_chan_num[TSN_MAX_RX_QUEUE];
 	u32 max_frm_size;
+
+	struct xlnx_tsn_ep_dma_chan **tx_chans;
+	struct xlnx_tsn_ep_dma_chan **rx_chans;
+
+	bool closing;
 };
 
+static inline struct skbuf_dma_descriptor *
+ep_get_desc(struct xlnx_tsn_ep_dma_chan *xchan, int idx)
+{
+	return xchan->skb_ring[idx];
+}
+
 static netdev_tx_t ep_start_xmit(struct sk_buff *skb, struct net_device *ndev)
 {
 	dev_kfree_skb_any(skb);
@@ -57,8 +120,22 @@ 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;
+
+	WRITE_ONCE(ep->closing, false);
+
+	ret = ep_init_dmaengine(ep);
+	if (ret) {
+		netdev_err(ndev, "failed to initialize DMA engine\n");
+		return ret;
+	}
+
 	netif_tx_start_all_queues(ndev);
 
 	return 0;
@@ -66,7 +143,11 @@ 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);
+	WRITE_ONCE(ep->closing, true);
+	ep_exit_dmaengine(ep);
 
 	return 0;
 }
@@ -88,6 +169,196 @@ 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, const char *name, bool is_tx,
+		  int ring_size)
+{
+	struct xlnx_tsn_ep_dma_chan *chan;
+	struct dma_chan *err_chan;
+	int i;
+
+	chan = kzalloc_obj(*chan);
+	if (!chan)
+		return ERR_PTR(-ENOMEM);
+
+	chan->chan = dma_request_chan(ep->dev, name);
+	if (IS_ERR(chan->chan)) {
+		err_chan = chan->chan;
+		kfree(chan);
+		return ERR_CAST(err_chan);
+	}
+
+	chan->skb_ring = kcalloc(ring_size, sizeof(*chan->skb_ring), GFP_KERNEL);
+	if (!chan->skb_ring) {
+		dma_release_channel(chan->chan);
+		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);
+			dma_release_channel(chan->chan);
+			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);
+	}
+	if (chan->chan)
+		dma_release_channel(chan->chan);
+
+	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;
+	char name[16];
+	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++) {
+		snprintf(name, sizeof(name), "tx_chan%u", ep->tx_dma_chan_map[i]);
+		ep->tx_chans[i] = ep_alloc_dma_chan(ep, name, 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++) {
+		snprintf(name, sizeof(name), "rx_chan%u", ep->rx_chan_num[i]);
+		ep->rx_chans[i] = ep_alloc_dma_chan(ep, name, 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 int ep_reset_dma_controller(struct xlnx_tsn_ep *ep)
+{
+	struct xilinx_vdma_config cfg = { .reset = 1 };
+	struct dma_chan *reset_chan;
+	char name[16];
+	int ret;
+
+	snprintf(name, sizeof(name), "tx_chan%u", ep->tx_dma_chan_map[0]);
+	reset_chan = dma_request_chan(ep->dev, name);
+	if (IS_ERR(reset_chan))
+		return dev_err_probe(ep->dev, PTR_ERR(reset_chan),
+				     "failed to request %s for reset\n", name);
+
+	ret = xilinx_vdma_channel_set_config(reset_chan, &cfg);
+	dma_release_channel(reset_chan);
+	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
@@ -285,6 +556,16 @@ static int xlnx_tsn_ep_probe(struct platform_device *pdev)
 	if (ret)
 		goto err_free_ndev;
 
+	/*
+	 * Request one DMA channel at probe time to reset the controller and to
+	 * gate on the MCDMA provider being bound. This keeps -EPROBE_DEFER in
+	 * the probe path, so the netdev is only registered once the provider is
+	 * available and ndo_open never sees a deferral.
+	 */
+	ret = ep_reset_dma_controller(ep);
+	if (ret)
+		goto err_free_ndev;
+
 	ret = of_get_mac_address(dev->of_node, mac_addr);
 	if (ret == -EPROBE_DEFER) {
 		goto err_free_ndev;

-- 
2.43.0



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

* [PATCH net-next v2 6/8] net: xilinx: tsn: add the endpoint RX data path
  2026-09-08 19:19 [PATCH net-next v2 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
                   ` (4 preceding siblings ...)
  2026-09-08 19:19 ` [PATCH net-next v2 5/8] net: xilinx: tsn: bring up the endpoint MCDMA channels Srinivas Neeli
@ 2026-09-08 19:19 ` Srinivas Neeli
  2026-09-12 20:36   ` netdev-bot+sashiko
  2026-09-08 19:19 ` [PATCH net-next v2 7/8] net: xilinx: tsn: add the endpoint TX " Srinivas Neeli
  2026-09-08 19:19 ` [PATCH net-next v2 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-09-08 19:19 UTC (permalink / raw)
  To: Nagadheeraj Rottela, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Richard Cochran, Michal Simek,
	Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt
  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 under the same per-channel
lock the refill takes, and ep_free_dma_chan() takes that lock as a barrier
before it terminates the channel. 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 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 | 216 +++++++++++++++++++++++-
 1 file changed, 215 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 23a15df71baa..9ed1170794e0 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
@@ -5,6 +5,7 @@
  * 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>
@@ -25,6 +26,7 @@
 #include <linux/slab.h>
 #include <linux/spinlock.h>
 #include <linux/string.h>
+#include <linux/timer.h>
 #include <linux/types.h>
 
 #include "xilinx_tsn.h"
@@ -41,6 +43,19 @@
 #define TX_BD_NUM_DEFAULT		64
 #define RX_BD_NUM_DEFAULT		128
 
+#define EP_RX_REFILL_RETRY		msecs_to_jiffies(10)
+
+/*
+ * The DMA descriptor sideband status word packs TID/TDEST/TUSER together;
+ * TUSER occupies the low byte, TID/TDEST sit in the upper bits.
+ */
+#define TSN_TUSER_MASK			GENMASK(7, 0)
+/* TUSER Input Port ID field (bits [5:4] of the TUSER field) */
+#define TSN_TUSER_PORT_ID_MASK		GENMASK(5, 4)
+#define TSN_TUSER_PORT_EP		0x0
+#define TSN_TUSER_PORT_MAC1		0x1
+#define TSN_TUSER_PORT_MAC2		0x2
+
 /**
  * struct skbuf_dma_descriptor - skb container for each in-flight DMA descriptor
  * @sgl: scatter-gather list backing the DMA mapping
@@ -66,6 +81,8 @@ struct skbuf_dma_descriptor {
  * @ring_head: producer index
  * @ring_tail: consumer index
  * @ring_size: number of slots in @skb_ring
+ * @rx_lock: serialises @ring_head between the RX callback and the refill timer
+ * @rx_refill_timer: retries RX refill after an allocation failure
  * @is_tx: true for TX channels, false for RX
  */
 struct xlnx_tsn_ep_dma_chan {
@@ -76,6 +93,8 @@ struct xlnx_tsn_ep_dma_chan {
 	u32 ring_head;
 	u32 ring_tail;
 	u32 ring_size;
+	spinlock_t rx_lock;	/* serialises @ring_head */
+	struct timer_list rx_refill_timer;
 	bool is_tx;
 };
 
@@ -113,6 +132,174 @@ 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_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->skb = 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;
+	xchan->ring_head++;
+	dmaengine_submit(dma_rx_desc);
+
+	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;
+
+	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);
@@ -211,6 +398,10 @@ ep_alloc_dma_chan(struct xlnx_tsn_ep *ep, const char *name, bool is_tx,
 	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;
 }
@@ -222,8 +413,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) {
@@ -322,9 +526,19 @@ static int ep_init_dmaengine(struct xlnx_tsn_ep *ep)
 		rx_allocated++;
 	}
 
+	for (i = 0; i < ep->num_rx_queues; i++) {
+		if (!ep_rx_refill(ep->rx_chans[i], false)) {
+			dev_err(ep->dev, "RX channel %d: no descriptors armed\n",
+				i);
+			ret = -ENOMEM;
+			goto err_free_chans;
+		}
+	}
+
 	return 0;
 
 err_free_chans:
+	WRITE_ONCE(ep->closing, true);
 	while (--rx_allocated >= 0)
 		ep_free_dma_chan(ep->rx_chans[rx_allocated]);
 	while (--tx_allocated >= 0)

-- 
2.43.0



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

* [PATCH net-next v2 7/8] net: xilinx: tsn: add the endpoint TX data path
  2026-09-08 19:19 [PATCH net-next v2 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
                   ` (5 preceding siblings ...)
  2026-09-08 19:19 ` [PATCH net-next v2 6/8] net: xilinx: tsn: add the endpoint RX data path Srinivas Neeli
@ 2026-09-08 19:19 ` Srinivas Neeli
  2026-09-12 20:36   ` netdev-bot+sashiko
  2026-09-08 19:19 ` [PATCH net-next v2 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-09-08 19:19 UTC (permalink / raw)
  To: Nagadheeraj Rottela, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Richard Cochran, Michal Simek,
	Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt
  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.

Look up the physical DMA channel for the frame's queue through the map
built at probe, 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 frees up.

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:
- 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 | 150 +++++++++++++++++++++++-
 1 file changed, 149 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 9ed1170794e0..9e9a45169681 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
@@ -29,6 +29,8 @@
 #include <linux/timer.h>
 #include <linux/types.h>
 
+#include <net/netdev_queues.h>
+
 #include "xilinx_tsn.h"
 
 #define DRIVER_NAME			"xilinx_tsn_ep"
@@ -82,6 +84,7 @@ struct skbuf_dma_descriptor {
  * @ring_tail: consumer index
  * @ring_size: number of slots in @skb_ring
  * @rx_lock: serialises @ring_head between the RX callback and the refill timer
+ * @tx_lock: serialises @ring_head and @ring_tail between xmit and TX completion
  * @rx_refill_timer: retries RX refill after an allocation failure
  * @is_tx: true for TX channels, false for RX
  */
@@ -94,6 +97,7 @@ struct xlnx_tsn_ep_dma_chan {
 	u32 ring_tail;
 	u32 ring_size;
 	spinlock_t rx_lock;	/* serialises @ring_head */
+	spinlock_t tx_lock;	/* serialises @ring_head and @ring_tail */
 	struct timer_list rx_refill_timer;
 	bool is_tx;
 };
@@ -300,8 +304,147 @@ 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);
+	} 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);
+}
+
 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);
+
+	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;
@@ -331,10 +474,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);
 	ep_exit_dmaengine(ep);
+	for (i = 0; i < ndev->num_tx_queues; i++)
+		netdev_tx_reset_subqueue(ndev, i);
 
 	return 0;
 }
@@ -398,7 +544,9 @@ ep_alloc_dma_chan(struct xlnx_tsn_ep *ep, const char *name, bool is_tx,
 	chan->ep = ep;
 	chan->ring_size = ring_size;
 	chan->dma_dev = dmaengine_get_dma_device(chan->chan);
-	if (!is_tx) {
+	if (is_tx) {
+		spin_lock_init(&chan->tx_lock);
+	} else {
 		spin_lock_init(&chan->rx_lock);
 		timer_setup(&chan->rx_refill_timer, ep_rx_refill_timer, 0);
 	}

-- 
2.43.0



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

* [PATCH net-next v2 8/8] net: xilinx: tsn: deliver endpoint RX frames to DSA user ports
  2026-09-08 19:19 [PATCH net-next v2 0/8] Add Xilinx TSN Endpoint Ethernet MAC driver Srinivas Neeli
                   ` (6 preceding siblings ...)
  2026-09-08 19:19 ` [PATCH net-next v2 7/8] net: xilinx: tsn: add the endpoint TX " Srinivas Neeli
@ 2026-09-08 19:19 ` Srinivas Neeli
  2026-09-12 20:36   ` netdev-bot+sashiko
  7 siblings, 1 reply; 17+ messages in thread
From: Srinivas Neeli @ 2026-09-08 19:19 UTC (permalink / raw)
  To: Nagadheeraj Rottela, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Richard Cochran, Michal Simek,
	Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt
  Cc: netdev, devicetree, linux-kernel, linux-arm-kernel,
	linux-rt-devel, Srinivas Neeli, neelisrinivas18, git

From: Nagadheeraj Rottela <nagadheeraj.rottela@amd.com>

The DSA core routes an RX frame to a user port from the port metadata
attached to the skb. Without that metadata, frames from the two MACs
cannot reach their swpN netdevs.

Allocate a METADATA_HW_PORT_MUX entry per MAC port at probe and attach
the matching entry to each RX frame by its TUSER port_id. Frames from
MAC1 and MAC2 now reach the correct user netdev, which completes the
conduit data path.

The RX callback runs in softirq and hands the frame to __netif_rx(),
which queues it on the backlog instead of consuming it inline, 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>

---
Changes in v2:
- Attach the RX port metadata only when the netdev is a DSA conduit
  (netdev_uses_dsa()), so no dev-less dst leaks into the stack when no switch
  is bound.
- Take a real reference on the per-port metadata_dst with dst_hold() +
  skb_dst_set() before the __netif_rx() backlog handoff, instead of
  skb_dst_set_noref() + skb_dst_force(). The entries stay live for the driver
  lifetime, so this drops the rcu_read_lock() that skb_dst_force() would need
  in the softirq callback.
- Release the per-port metadata_dst with dst_release() at teardown, not
  metadata_dst_free(), so an skb still referencing it is not freed early.
---
 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 9e9a45169681..db7da26521d0 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
@@ -29,6 +29,8 @@
 #include <linux/timer.h>
 #include <linux/types.h>
 
+#include <net/dsa.h>
+#include <net/dst_metadata.h>
 #include <net/netdev_queues.h>
 
 #include "xilinx_tsn.h"
@@ -58,6 +60,9 @@
 #define TSN_TUSER_PORT_MAC1		0x1
 #define TSN_TUSER_PORT_MAC2		0x2
 
+/* Sized to index port_md[] by TUSER port_id (1 or 2, slot 0 unused) */
+#define XLNX_TSN_EP_PORT_MD_SLOTS	(TSN_TUSER_PORT_MAC2 + 1)
+
 /**
  * struct skbuf_dma_descriptor - skb container for each in-flight DMA descriptor
  * @sgl: scatter-gather list backing the DMA mapping
@@ -114,6 +119,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;
@@ -128,6 +135,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 *
@@ -292,6 +301,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;
@@ -854,6 +867,37 @@ static int ep_count_dma_queues(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 rx_chan_num[TSN_MAX_RX_QUEUE];
@@ -939,16 +983,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_free_ndev;
+	}
+
 	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;
+		goto err_free_md;
 	}
 
 	return 0;
 
+err_free_md:
+	ep_free_port_md(ep);
 err_free_ndev:
 	free_netdev(ndev);
 	return ret;
@@ -962,6 +1014,7 @@ static void xlnx_tsn_ep_remove(struct platform_device *pdev)
 		return;
 
 	unregister_netdev(ep->ndev);
+	ep_free_port_md(ep);
 	free_netdev(ep->ndev);
 }
 

-- 
2.43.0



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

* Re: [PATCH net-next v2 1/8] dt-bindings: net: add Xilinx TSN Endpoint Ethernet MAC
  2026-09-08 19:19 ` [PATCH net-next v2 1/8] dt-bindings: net: add Xilinx TSN Endpoint Ethernet MAC Srinivas Neeli
@ 2026-09-12 20:36   ` netdev-bot+sashiko
  0 siblings, 0 replies; 17+ messages in thread
From: netdev-bot+sashiko @ 2026-09-12 20:36 UTC (permalink / raw)
  To: srinivas.neeli
  Cc: nagadheeraj.rottela, andrew+netdev, davem, edumazet, kuba, pabeni,
	robh, krzk+dt, conor+dt, richardcochran, michal.simek, bigeasy,
	clrkwllms, rostedt, 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

- [Medium] Inconsistent/incomplete port constraints in the new switch
  `^(ethernet-)?ports$` container.
- [Low] The endpoint MAC child's DMA/queue description is under-
  constrained and, more importantly, duplicates information that is…

--

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

Adds a new binding for the Xilinx TSN Endpoint Ethernet MAC IP in the
Bridged Endpoint configuration.  The wrapper node owns the six IP clocks
and xlnx,num-priorities, and has an ethernet@ child for the endpoint MAC
and a switch@ child for the three-port switch plus its two per-MAC
windows.

Two questions below about the schema constraints.

> 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..4e65d1710da7
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
> @@ -0,0 +1,364 @@

[ ... ]

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

[Severity: Low]

Is xlnx,num-priorities derivable from the properties already present in
the ethernet@ child?

The driver added later in this series only accepts one value for it.  In
xlnx_tsn_ep_probe() in drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c:

	if (num_tx != num_prio)
		return dev_err_probe(dev, -EINVAL,
				     "TX channel count %u must equal num-priorities %u\n",
				     num_tx, num_prio);

so the only legal value is the number of tx_chanN entries in the child's
dma-names.

Since this is a required ABI property, would it be better to drop it and
count the tx_chanN entries, or, if it must stay, to state in the
description which of the two is authoritative?

[ ... ]

> +      dma-names:
> +        minItems: 2
> +        maxItems: 16
> +        items:
> +          pattern: "^(tx|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. Example: "tx_chan0",
> +          "tx_chan1", ..., "rx_chan0", "rx_chan1", ....

[Severity: Low]

A plain items pattern here also accepts lists the description rules out:
a TX-only or RX-only list, the same name repeated twice, or RX names
before TX names.  Could the entries be spelled out as an ordered items
list (or split with minItems/maxItems per direction) so the "TX first,
then RX" ordering is actually enforced?

> +      tx-queues-config:
> +        type: object
> +        additionalProperties: false
[ ... ]
> +        patternProperties:
> +          "^queue[0-7]$":
> +            type: object
> +            additionalProperties: false
[ ... ]
> +            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

[Severity: Low]

tx-queues-config is required but has no required queueN child, so an
empty tx-queues-config node validates.  xlnx,dma-channel-num is also
bounded only by 0..7, so a queue may point at a channel that is absent
from dma-names, or at a channel another queue already claims.  Was at
least a required queue0 intended here?

[ ... ]

> +    patternProperties:
> +      "^(ethernet-)?ports$":
> +        type: object
> +        additionalProperties: false
> +
> +        properties:
> +          '#address-cells':
> +            const: 1
> +
> +          '#size-cells':
> +            const: 0
> +
> +          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:
> +              - ethernet

[Severity: Medium]

Should reg be required on port@0 as well?

The value is constrained with reg: const: 0, but the required list only
names ethernet, and nothing in the inherited chain makes reg mandatory:
dsa.yaml#/$defs/ethernet-ports -> dsa-port.yaml only defines it,

  properties:
    reg:
      items:
        - description: Port number

and its if/then on ethernet/link requires phy-mode plus one of
fixed-link/phy-handle/managed.  ethernet-switch.yaml's ports container
requires only #address-cells/#size-cells.

So this node passes dt_binding_check:

    port@0 {
        ethernet = <&ep_mac>;
        phy-mode = "internal";
        fixed-link { speed = <1000>; full-duplex; };
    };

but dsa_switch_parse_ports_of() in net/dsa/dsa.c bails out on it:

	for_each_available_child_of_node(ports, port) {
		err = of_property_read_u32(port, "reg", &reg);
		if (err) {
			of_node_put(port);
			goto out_put_node;
		}

That is also asymmetric with the sibling ^port@[1-2]$ subschema below,
which does require reg.

> +        patternProperties:
> +          "^port@[1-2]$":
> +            type: object
> +            unevaluatedProperties: true
> +            description:
> +              External Ethernet port. port@1 is wired to MAC1 and reached
> +              through the mdio-1 bus, port@2 is wired to MAC2 and reached
> +              through the mdio-2 bus.
> +
> +            properties:
> +              reg:
> +                enum: [1, 2]

[Severity: Medium]

Since both external node names share this one subschema, reg is
decoupled from the unit address.  This validates:

    port@1 { reg = <2>; phy-handle = <&phy0>; phy-mode = "rgmii-id"; };
    port@2 { reg = <1>; phy-handle = <&phy1>; phy-mode = "rgmii-id"; };

as does giving both ports the same reg value.  The description keys the
MAC and MDIO association by node name, while the DSA core selects the
port by the reg value, so a swapped DT passes the schema and then
associates the wrong PHY/MDIO bus with each MAC.

Would per-port subschemas with reg: const: 1 and reg: const: 2, in the
same style used for port@0, work better here?

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260909-patches_v2_external-v2-0-3a40babaff4c%40amd.com


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

* Re: [PATCH net-next v2 2/8] net: xilinx: tsn: add TSN endpoint wrapper driver
  2026-09-08 19:19 ` [PATCH net-next v2 2/8] net: xilinx: tsn: add TSN endpoint wrapper driver Srinivas Neeli
@ 2026-09-12 20:36   ` netdev-bot+sashiko
  0 siblings, 0 replies; 17+ messages in thread
From: netdev-bot+sashiko @ 2026-09-12 20:36 UTC (permalink / raw)
  To: srinivas.neeli
  Cc: nagadheeraj.rottela, andrew+netdev, davem, edumazet, kuba, pabeni,
	robh, krzk+dt, conor+dt, richardcochran, michal.simek, bigeasy,
	clrkwllms, rostedt, 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: 0 · Low: 2

- [Low] Kconfig help text for CONFIG_XILINX_TSN claims the option
  "provides the wrapper device and the endpoint MAC that connects the IP…
- [Low] The binding Documentation/devicetree/bindings/net/xlnx,tsn-
  endpoint-ethernet-mac.yaml puts 'xlnx,num-priorities' (uint32, 2..8)…

--

net: xilinx: tsn: add TSN endpoint wrapper driver

This adds a thin wrapper platform driver for the AMD/Xilinx TSN Endpoint
Ethernet MAC IP node. The wrapper enables the six IP-wide clocks
described in the device tree and then populates the DT sub-nodes so each
one can bind to its own driver.

A couple of questions on the new Kconfig text and on the DT properties
the wrapper consumes.

[ ... ]

> 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.

[Severity: Low]
Is the help text accurate at this point in the series?  It says the
option "provides the wrapper device and the endpoint MAC that connects
the IP to the host CPU", but the Makefile added by this patch builds only
the wrapper object:

  xilinx_tsn-y := xilinx_tsn_main.o

and tsn_drivers[] registers only &tsn_driver, which matches
"xlnx,tsn-endpoint-ethernet-mac-3.0".  No driver in the tree matches the
sub-node compatibles at this commit, so devm_of_platform_populate() in
tsn_ip_probe() creates child platform devices that cannot bind, and
enabling the symbol here yields enabled clocks but no netdev.

The endpoint MAC part is resolved by later patches in this same series:
by the end of the series the Makefile also builds xilinx_tsn_ep.o,
tsn_drivers[] gains &xlnx_tsn_ep_driver, and xilinx_tsn_ep.c matches
".compatible = \"xlnx,tsn-ep-mac\"", so this is only a transient
mismatch between patches.

The remaining sentence about the "companion DSA driver" refers to
something that is not in-tree at any point of this series.  Could that
sentence be dropped or reworded until the DSA driver lands?

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

[ ... ]

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

[Severity: Low]
The binding
Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
lists xlnx,num-priorities (uint32, 2..8) in the required: list for the
"xlnx,tsn-endpoint-ethernet-mac-3.0" node:

  xlnx,num-priorities:
    $ref: /schemas/types.yaml#/definitions/uint32
    minimum: 2
    maximum: 8

tsn_ip_probe() is the only driver matching that compatible here, and it
does devm_kzalloc(), devm_clk_bulk_get(), clk_bulk_prepare_enable(),
devm_add_action_or_reset() and devm_of_platform_populate() without any
of_property_read_u32(), so the mandatory property is neither read nor
range-checked.

A later patch in this series ("net: xilinx: tsn: parse endpoint DMA
channel configuration") does consume it from the parent IP node in
xlnx_tsn_ep_probe():

  ret = of_property_read_u32(ip_np, "xlnx,num-priorities", &num_prio);
  ...
  if (num_tx != num_prio)
          return dev_err_probe(dev, -EINVAL, ...);

so the binding/driver mismatch only exists between patches of this
series.  Was leaving the property unconsumed by the wrapper itself
intentional?

> +
> +static const struct of_device_id tsn_of_match[] = {
> +	{ .compatible = "xlnx,tsn-endpoint-ethernet-mac-3.0" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, tsn_of_match);

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260909-patches_v2_external-v2-0-3a40babaff4c%40amd.com


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

* Re: [PATCH net-next v2 3/8] net: xilinx: tsn: add endpoint MAC driver skeleton
  2026-09-08 19:19 ` [PATCH net-next v2 3/8] net: xilinx: tsn: add endpoint MAC driver skeleton Srinivas Neeli
@ 2026-09-12 20:36   ` netdev-bot+sashiko
  0 siblings, 0 replies; 17+ messages in thread
From: netdev-bot+sashiko @ 2026-09-12 20:36 UTC (permalink / raw)
  To: srinivas.neeli
  Cc: nagadheeraj.rottela, andrew+netdev, davem, edumazet, kuba, pabeni,
	robh, krzk+dt, conor+dt, richardcochran, michal.simek, bigeasy,
	clrkwllms, rostedt, 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: 0 · Low: 2

- [Low] Commit message contradicts the code: the message body states
  'Register the netdev named "ep"', but xlnx_tsn_ep_probe() calls…
- [Low] Reported concern (validated as NOT a defect at the end of the
  series): the binding Documentation/devicetree/bindings/net/xlnx,tsn-…

--

net: xilinx: tsn: add endpoint MAC driver skeleton

This adds a platform driver for the "xlnx,tsn-ep-mac" endpoint MAC that
registers an enumerated netdev with minimal netdev and ethtool ops, sets
the MAC address from DT or a random one, and drops all transmitted
frames.  The new driver is registered alongside the existing TSN
platform driver.

Two questions below, both minor.

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

[Severity: Low]
Should "the netdev named "ep"" say ep%d here?  xlnx_tsn_ep_probe() uses:

	ndev = alloc_netdev(sizeof(*ep), "ep%d", NET_NAME_ENUM, ether_setup);

so the interface shows up as ep0, and ep1 for a second IP instance.  The
kernel-doc for struct xlnx_tsn_ep in the same file already says '("ep0"
for the first IP instance)'.

The rename is described in the v2 changelog below the "---" separator,
which is dropped when the patch is applied, so the permanent log would
keep the old name.  Can the message body be updated to match?

> 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..089f17a126f5
> --- /dev/null
> +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> @@ -0,0 +1,143 @@

[ ... ]

> +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), "ep%d", NET_NAME_ENUM, 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);

[Severity: Low]
This isn't a bug, but of_get_mac_address() is the only DT property read
here, while the binding
Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
lists reg, dmas, dma-names and tx-queues-config as required for this
node.

The later patches in this series ("net: xilinx: tsn: parse endpoint DMA
channel configuration" and the MCDMA/RX/TX data path patches) do add
ep_count_dma_queues() and ep_parse_tx_queue_config(), so dmas,
dma-names, tx-queues-config and xlnx,num-priorities all end up parsed
and enforced with dev_err_probe() by the end of the series.

The one property that is still never consumed at the end of the series
is the required reg region, since the v2 changelog notes the ioremap
comes back with QBV support.  Is reg intended to stay documentation-only
for the node unit address until then?

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260909-patches_v2_external-v2-0-3a40babaff4c%40amd.com


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

* Re: [PATCH net-next v2 4/8] net: xilinx: tsn: parse endpoint DMA channel configuration
  2026-09-08 19:19 ` [PATCH net-next v2 4/8] net: xilinx: tsn: parse endpoint DMA channel configuration Srinivas Neeli
@ 2026-09-12 20:36   ` netdev-bot+sashiko
  0 siblings, 0 replies; 17+ messages in thread
From: netdev-bot+sashiko @ 2026-09-12 20:36 UTC (permalink / raw)
  To: srinivas.neeli
  Cc: nagadheeraj.rottela, andrew+netdev, davem, edumazet, kuba, pabeni,
	robh, krzk+dt, conor+dt, richardcochran, michal.simek, bigeasy,
	clrkwllms, rostedt, 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

- [Medium] xlnx_tsn_ep_probe() and ep_parse_tx_queue_config() enforce two
  new mandatory device-tree constraints that the in-tree binding does…

--

net: xilinx: tsn: parse endpoint DMA channel configuration

This patch counts the TX and RX MCDMA channels listed in the endpoint's
"dma-names" property, allocates the conduit netdev with that many hardware
queues, and parses the "tx-queues-config" child node to map each logical TX
queue to a physical DMA channel.

> diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> index 089f17a126f5..24025b1f6e66 100644
> --- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c

[ ... ]

> @@ -69,15 +88,173 @@ 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);

[ ... ]

> +		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 xlnx_tsn_ep_probe(struct platform_device *pdev)
>  {
> +	u32 rx_chan_num[TSN_MAX_RX_QUEUE];
>  	struct device *dev = &pdev->dev;
> +	struct device_node *txcfg_np;
> +	u32 num_tx, num_rx, num_prio;
> +	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_queues(dev, &num_tx, &num_rx, &tx_present, rx_chan_num);
> +	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 != num_prio)
> +		return dev_err_probe(dev, -EINVAL,
> +				     "TX channel count %u must equal num-priorities %u\n",
> +				     num_tx, num_prio);

[Severity: Medium]
Can this equality check reject device trees that the in-tree binding
accepts?  Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
describes the two properties completely independently:

  xlnx,num-priorities:
    $ref: /schemas/types.yaml#/definitions/uint32
    minimum: 2
    maximum: 8

      dma-names:
        minItems: 2
        maxItems: 16
        items:
          pattern: "^(tx|rx)_chan[0-7]$"

With that schema, dma-names = "tx_chan0", "rx_chan0" together with
xlnx,num-priorities = <2> validates, but xlnx_tsn_ep_probe() now fails with
"TX channel count 1 must equal num-priorities 2".  A 4 TX + 4 RX endpoint
under xlnx,num-priorities = <8> fails the same way.

The queue node checks in ep_parse_tx_queue_config() add a second constraint
that the schema does not express: exactly one queue<N> node per TX channel,
with dense indices 0..num_tx-1.  The schema has no required queue children
under tx-queues-config, so an empty

  tx-queues-config {
  };

is schema-valid and gets "tx-config: described 0 queues but expected N",
and sparse names such as queue0/queue1/queue2/queue7 with 4 TX channels get
"tx-config: invalid queue node queue7 (have 4 queues)".

Because the endpoint MAC is the DSA conduit referenced by
port@0 { ethernet = <&ep_mac>; }, a probe failure here also takes away the
three-port switch, not just the ep0 netdev.

Semantically these look like different things: xlnx,num-priorities is an
IP-wide traffic class count on the parent node, while dma-names describes
host-side MCDMA wiring on the endpoint node, and the binding's own example
uses an arbitrary queueN -> tx_chanM permutation.  num_prio is read here for
no other purpose than this comparison.

Should the binding encode the coupling (required queue children and a
dma-names/num-priorities relation), or should the driver accept every
topology the binding advertises?

The YAML is unchanged by this patch and by the remainder of the series, and
only the 8 TX / 8 RX, xlnx,num-priorities = <8> example in the binding
satisfies the driver, so dt_binding_check does not surface the mismatch.

Could the commit message body state these new device tree requirements?
Right now they appear only in the below-the-line v2 changelog:

  - Require one queue node per tx_chan and num-priorities equal to the TX
    channel count.

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260909-patches_v2_external-v2-0-3a40babaff4c%40amd.com


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

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

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

Critical: 0 · High: 1 · Medium: 1 · Low: 2 (1)

- [High] The stated purpose of the v2 change - "Request a channel at
  probe time so -EPROBE_DEFER stays in the probe path and ndo_open never…
- [Medium] The driver assumes the MCDMA reset and
  dmaengine_terminate_sync() act on a single logical channel, but for…
- [Low] The commit adds infrastructure that has no consumer in this patch
  and that the commit message does not disclose: `ep->closing` is only…
- [Low] ep_reset_dma_controller() hands a channel obtained from the
  generic DT dmaengine API (dma_request_chan(ep->dev, "tx_chanN")) to…

Pre-existing issues:
- [Low] The DT binding and the driver disagree about what a valid EP node
  is, and this patch makes the disagreement load-bearing at run time.

--

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

This patch requests every TX and RX MCDMA channel named in the device tree
when the interface opens and gives each one a ring of preallocated SKB
descriptor slots.  It also resets the controller at probe time through the
first mapped TX channel and releases the channels again in ndo_stop, plus
adds a XILINX_DMA Kconfig dependency.

No Link: tags in the commit header.

> 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 24025b1f6e66..23a15df71baa 100644
> --- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> @@ -6,16 +6,24 @@
>   */
>  
>  #include <linux/bitops.h>
> +#include <linux/circ_buf.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>
>  #include <linux/if_vlan.h>
> +#include <linux/io.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/scatterlist.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
>  #include <linux/string.h>
>  #include <linux/types.h>
>  
> @@ -30,6 +38,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 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 ("ep0" for the first IP instance)
> @@ -39,6 +88,9 @@
>   * @tx_dma_chan_map: logical TX queue index -> physical DMA channel number
>   * @rx_chan_num: RX ring index -> physical DMA channel number
>   * @max_frm_size: maximum frame size accepted on RX
> + * @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;
> @@ -48,8 +100,19 @@ struct xlnx_tsn_ep {
>  	u32 tx_dma_chan_map[TSN_MAX_TX_QUEUE];
>  	u32 rx_chan_num[TSN_MAX_RX_QUEUE];
>  	u32 max_frm_size;
> +
> +	struct xlnx_tsn_ep_dma_chan **tx_chans;
> +	struct xlnx_tsn_ep_dma_chan **rx_chans;
> +
> +	bool closing;
>  };
>  
> +static inline struct skbuf_dma_descriptor *
> +ep_get_desc(struct xlnx_tsn_ep_dma_chan *xchan, int idx)
> +{
> +	return xchan->skb_ring[idx];
> +}
> +

[Severity: Low]

Several of the pieces added here have no consumer yet at this commit.

The closing field is only ever written, in ep_open() and ep_stop(), and
nothing reads it, but the kernel-doc already describes a contract with an
RX completion callback that this patch does not add:

 * @closing: set in ndo_stop so the RX completion callback stops re-arming

Similarly, ep_get_desc() has no caller, and the TX drain loop in
ep_free_dma_chan() walks [ring_tail, ring_head) although nothing advances
ring_head until the TX data path lands.

On the includes, circ_buf.h and spinlock.h have no CIRC_* or spinlock_t user
in this patch, and io.h has no readl()/writel() user here or at the end of
the series.

Could these move to the patches that first use them, or could the commit
message say they are groundwork for the later data-path patches?

>  static netdev_tx_t ep_start_xmit(struct sk_buff *skb, struct net_device *ndev)
>  {
>  	dev_kfree_skb_any(skb);
> @@ -57,8 +120,22 @@ 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;
> +
> +	WRITE_ONCE(ep->closing, false);
> +
> +	ret = ep_init_dmaengine(ep);
> +	if (ret) {
> +		netdev_err(ndev, "failed to initialize DMA engine\n");
> +		return ret;
> +	}
> +

[Severity: High]

Can -EPROBE_DEFER still be returned to userspace from ndo_open here?

The changelog says the v2 change means "-EPROBE_DEFER stays in the probe
path and ndo_open never hands a deferral back to userspace", but
ep_reset_dma_controller() requests only "tx_chan<tx_dma_chan_map[0]>" and
drops the reference before probe returns:

	reset_chan = dma_request_chan(ep->dev, name);
	...
	ret = xilinx_vdma_channel_set_config(reset_chan, &cfg);
	dma_release_channel(reset_chan);

Every other TX channel of tx_dma_chan_map[] and every RX channel of
rx_chan_num[] is first requested from the open path:

ep_open() -> ep_init_dmaengine() -> ep_alloc_dma_chan() -> dma_request_chan()

and dma_request_chan() still yields a deferral when the controller behind
the phandle is not registered:

drivers/dma/of-dma.c:of_dma_request_slave_channel() {
	...
		} else {
			ret_no_channel = -EPROBE_DEFER;
			chan = NULL;
		}
	...
}

ep_alloc_dma_chan() passes that up with ERR_CAST(), ep_open() returns it
unchanged, and the core does not translate ndo_open return values:

net/core/dev.c:__dev_open() {
	...
	if (!ret && ops->ndo_open)
		ret = ops->ndo_open(dev);
	...
}

so "ip link set ep0 up" would fail with errno 517 and no deferred probe
retry is queued.

Two ways to reach it: the MCDMA provider is unbound or unloaded after probe,
since nothing holds a channel reference once the reset channel is released;
or the dmas entries name more than one provider, which the binding permits
because it constrains dmas only by count and name pattern, so only the
provider of the first mapped TX channel is known to be bound at probe.

Would acquiring all channels in probe and keeping them, or explicitly
refusing or translating -EPROBE_DEFER in ndo_open, match what the changelog
describes?

>  	netif_tx_start_all_queues(ndev);
>  
>  	return 0;
> @@ -66,7 +143,11 @@ 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);
> +	WRITE_ONCE(ep->closing, true);
> +	ep_exit_dmaengine(ep);
>  
>  	return 0;
>  }
> @@ -88,6 +169,196 @@ 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, const char *name, bool is_tx,
> +		  int ring_size)
> +{
> +	struct xlnx_tsn_ep_dma_chan *chan;
> +	struct dma_chan *err_chan;
> +	int i;
> +
> +	chan = kzalloc_obj(*chan);
> +	if (!chan)
> +		return ERR_PTR(-ENOMEM);
> +
> +	chan->chan = dma_request_chan(ep->dev, name);
> +	if (IS_ERR(chan->chan)) {
> +		err_chan = chan->chan;
> +		kfree(chan);
> +		return ERR_CAST(err_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);
> +

[Severity: Medium]

Is dmaengine_terminate_sync() really per logical channel on MCDMA?

For XDMA_TYPE_AXIMCDMA, xilinx_dma_chan_probe() sets chan->ctrl_offset to
XILINX_DMA_MM2S_CTRL_OFFSET for every MM2S channel and to
XILINX_MCDMA_S2MM_CTRL_OFFSET for every S2MM channel, so that register is
shared by all channels of a direction; the per-channel control register is
XILINX_MCDMA_CHAN_CR_OFFSET(tdest).

xilinx_dma_terminate_all() clears the shared RUNSTOP via
xilinx_dma_stop_transfer() and then resets:

drivers/dma/xilinx/xilinx_dma.c:xilinx_dma_reset() {
	...
	dma_ctrl_set(chan, XILINX_DMA_REG_DMACR, XILINX_DMA_DMACR_RESET);
	...
}

Given that, doesn't the first ep_free_dma_chan() call in the
ep_exit_dmaengine() loop reset the whole direction, so the sibling channels'
in-flight descriptors are aborted without any completion callback?  And does
ep_reset_dma_controller() at probe likewise reset every channel of that
MCDMA instance, including channels owned by another client of the same
instance?

If per-channel quiescing is what is intended, would clearing the
RUNSTOP/CHEN bit in the per-tdest CR be the right primitive here?

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

[ ... ]

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

[ ... ]

> +	for (i = 0; i < ep->num_tx_queues; i++) {
> +		snprintf(name, sizeof(name), "tx_chan%u", ep->tx_dma_chan_map[i]);
> +		ep->tx_chans[i] = ep_alloc_dma_chan(ep, name, 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++;
> +	}
> +

[Severity: Low]

This is a pre-existing mismatch and was not introduced by this patch - both
the driver checks and the binding come from earlier patches - but requesting
one live channel per tx_dma_chan_map[] entry here makes the undocumented
invariant matter at ndo_open time.

Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
constrains dma-names only by count (minItems 2, maxItems 16) and the pattern
^(tx|rx)_chan[0-7]$, constrains xlnx,num-priorities only to 2..8, and places
no completeness or uniqueness requirement on the ^queue[0-7]$ children of
tx-queues-config.

The driver is stricter: xlnx_tsn_ep_probe() returns -EINVAL when the TX
channel count differs from xlnx,num-priorities, and ep_parse_tx_queue_config()
returns -EINVAL unless the queue children describe every logical queue
0..num_tx-1 exactly once.

So a DTB with xlnx,num-priorities = <8> but only "tx_chan0" and "tx_chan1"
in dma-names validates against the schema and is then rejected at probe.
Should the binding state the relationship between dma-names,
xlnx,num-priorities and the queue<N> children?

[ ... ]

> +static int ep_reset_dma_controller(struct xlnx_tsn_ep *ep)
> +{
> +	struct xilinx_vdma_config cfg = { .reset = 1 };
> +	struct dma_chan *reset_chan;
> +	char name[16];
> +	int ret;
> +
> +	snprintf(name, sizeof(name), "tx_chan%u", ep->tx_dma_chan_map[0]);
> +	reset_chan = dma_request_chan(ep->dev, name);
> +	if (IS_ERR(reset_chan))
> +		return dev_err_probe(ep->dev, PTR_ERR(reset_chan),
> +				     "failed to request %s for reset\n", name);
> +
> +	ret = xilinx_vdma_channel_set_config(reset_chan, &cfg);

[Severity: Low]

reset_chan comes from the generic DT dmaengine API, but the callee casts it
to a Xilinx-private object without any check:

drivers/dma/xilinx/xilinx_dma.c:xilinx_vdma_channel_set_config() {
	struct xilinx_dma_chan *chan = to_xilinx_chan(dchan);
	u32 dmacr;

	if (cfg->reset)
		return xilinx_dma_chan_reset(chan);
	...
}

and with cfg.reset = 1 the reset path performs MMIO through
chan->xdev->regs + chan->ctrl_offset.

If the dmas phandle resolves to some other dmaengine provider, does this
reinterpret that provider's channel object and then write registers through
pointers read out of it?  depends on XILINX_DMA only makes the Xilinx driver
buildable, it does not tie the phandle to it.

axienet_probe() already does the same thing with cfg.reset = 1, so the
pattern is not new, but would a provider identity check before the call, or
a generic dmaengine reset, avoid adding a second unvalidated cast?

> +	dma_release_channel(reset_chan);
> +	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
> @@ -285,6 +556,16 @@ static int xlnx_tsn_ep_probe(struct platform_device *pdev)
>  	if (ret)
>  		goto err_free_ndev;
>  
> +	/*
> +	 * Request one DMA channel at probe time to reset the controller and to
> +	 * gate on the MCDMA provider being bound. This keeps -EPROBE_DEFER in
> +	 * the probe path, so the netdev is only registered once the provider is
> +	 * available and ndo_open never sees a deferral.
> +	 */
> +	ret = ep_reset_dma_controller(ep);
> +	if (ret)
> +		goto err_free_ndev;
> +

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260909-patches_v2_external-v2-0-3a40babaff4c%40amd.com


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

* Re: [PATCH net-next v2 6/8] net: xilinx: tsn: add the endpoint RX data path
  2026-09-08 19:19 ` [PATCH net-next v2 6/8] net: xilinx: tsn: add the endpoint RX data path Srinivas Neeli
@ 2026-09-12 20:36   ` netdev-bot+sashiko
  0 siblings, 0 replies; 17+ messages in thread
From: netdev-bot+sashiko @ 2026-09-12 20:36 UTC (permalink / raw)
  To: srinivas.neeli
  Cc: nagadheeraj.rottela, andrew+netdev, davem, edumazet, kuba, pabeni,
	robh, krzk+dt, conor+dt, richardcochran, michal.simek, bigeasy,
	clrkwllms, rostedt, 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: 0 · High: 2 · Medium: 3 · Low: 0

- [High] The new RX path cannot deliver a single frame on the hardware it
  targets.
- [High] ep_rx_submit_desc()
  (drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c:169-178) records the…
- [Medium] ep_dma_rx_cb() documents and indexes the dmaengine metadata
  window as "[0] = status, [1] = sideband (TID/TDEST/TUSER), [2..] =…
- [Medium] ep_rx_refill() holds the per-channel spinlock with softirqs
  disabled (guard(spinlock_bh)(&xchan->rx_lock), line 191) across a loop…
- [Medium] The commit message states "ndo_stop() sets a closing flag
  under the same per-channel lock the refill takes", but ep_stop() (line…

--

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

This adds the RX side of the Xilinx TSN endpoint: RX buffers are posted to
every RX channel at ndo_open and reaped from the dmaengine completion
callback, which decodes a TUSER port_id out of the descriptor metadata and
passes MAC1/MAC2 frames up with __netif_rx(). A per-channel rx_lock plus a
refill timer are added to serialise refill against teardown.

[Severity: Medium]
The commit message says:

  "Serialise the RX refill against teardown. The completion callback re-arms
   the ring, so ndo_stop() sets a closing flag under the same per-channel
   lock the refill takes, and ep_free_dma_chan() takes that lock as a barrier
   before it terminates the channel."

Is the "under the same per-channel lock" part accurate?  ep_stop() looks
like this:

	netif_tx_disable(ndev);
	WRITE_ONCE(ep->closing, true);
	ep_exit_dmaengine(ep);

The store is not done under any rx_lock; the per-channel lock only shows up
later in ep_free_dma_chan() as a bare lock/unlock barrier, after the flag
has already been published.  Since this claim is the stated reason a single
dmaengine_terminate_sync() suffices, could the wording be adjusted to
describe what the code actually does?

> diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> index 23a15df71baa..9ed1170794e0 100644
> --- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c

[ ... ]

> @@ -41,6 +43,19 @@
>  #define TX_BD_NUM_DEFAULT		64
>  #define RX_BD_NUM_DEFAULT		128
>  
> +#define EP_RX_REFILL_RETRY		msecs_to_jiffies(10)
> +
> +/*
> + * The DMA descriptor sideband status word packs TID/TDEST/TUSER together;
> + * TUSER occupies the low byte, TID/TDEST sit in the upper bits.
> + */
> +#define TSN_TUSER_MASK			GENMASK(7, 0)
> +/* TUSER Input Port ID field (bits [5:4] of the TUSER field) */
> +#define TSN_TUSER_PORT_ID_MASK		GENMASK(5, 4)
> +#define TSN_TUSER_PORT_EP		0x0
> +#define TSN_TUSER_PORT_MAC1		0x1
> +#define TSN_TUSER_PORT_MAC2		0x2
> +

[ ... ]

> @@ -113,6 +132,174 @@ ep_get_desc(struct xlnx_tsn_ep_dma_chan *xchan, int idx)

[ ... ]

> +	skbuf_dma->skb = 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;
> +	xchan->ring_head++;
> +	dmaengine_submit(dma_rx_desc);
> +
> +	return 0;

[Severity: High]
Should ep_rx_submit_desc() check the cookie returned by dmaengine_submit()?
ring_head is advanced and the slot is marked armed before the submit, and
the return value is discarded, so 0 is returned even when the descriptor was
never queued.  The provider can fail:

drivers/dma/xilinx/xilinx_dma.c:xilinx_dma_tx_submit() {
	...
	if (chan->err) {
		err = xilinx_dma_chan_reset(chan);
		if (err < 0)
			return err;
	}
	...
}

If that happens, three things seem to follow.

The skb and its DMA_FROM_DEVICE mapping stay pinned in the slot with no
completion ever arriving for it.

ep_dma_rx_cb() consumes slots strictly in ring_tail order with no
correlation to the completing descriptor:

	skbuf_dma = ep_get_desc(xchan, xchan->ring_tail & (xchan->ring_size - 1));
	WRITE_ONCE(xchan->ring_tail, xchan->ring_tail + 1);

so every later completion is attributed to the wrong slot.  Can this hand a
buffer the engine never wrote, i.e. freshly allocated dev_alloc_skb() memory
with another descriptor's length applied by skb_put(), to __netif_rx()?

And since ep_rx_refill() judges success only by ring_head != ring_tail, the
falsely advanced head makes refill report success, no retry timer is armed,
and the new ep_init_dmaengine() arming check passes with nothing queued.

The TX path in this series does check dma_submit_error() and rolls the slot
back; would the same handling fit here?

> +
> +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;
> +
> +	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: Medium]
Does rx_lock need to cover the whole submit loop?  The guard is taken at
function scope, so with an empty ring avail is ring_size - 1 (127 with
RX_BD_NUM_DEFAULT), and each iteration of ep_rx_submit_desc() does
dev_alloc_skb(), dma_map_single(), dmaengine_prep_slave_sg() and
dmaengine_submit() with softirqs disabled.

Two consequences worth considering.  On the ndo_open path
(ep_init_dmaengine -> ep_rx_refill(chan, false)) this is sleepable process
context, yet every allocation is forced into the GFP_ATOMIC reserve and can
fail ndo_open with -ENOMEM.  A concurrent RX completion on another CPU spins
on rx_lock for the whole burst.

Would moving the skb allocation and mapping outside rx_lock, or dropping and
retaking it per descriptor, work here?

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

[ ... ]

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

[Severity: High]
Can this drop every received frame on the AXI MCDMA channels the endpoint
binding uses?  dmaengine_desc_get_metadata_ptr() goes through:

drivers/dma/dmaengine.c:
	ret = desc_check_and_set_metadata_mode(desc, DESC_METADATA_ENGINE);
	if (ret)
		return ERR_PTR(ret);

and xilinx_dma.c only advertises DESC_METADATA_ENGINE for
XDMA_TYPE_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;
	...
}

xilinx_mcdma_prep_slave_sg() also never assigns
desc->async_tx.metadata_ops, unlike the two AXIDMA prep paths.  With
desc_metadata_modes unset, desc_check_and_set_metadata_mode() returns
-ENOTSUPP for every MCDMA completion, so IS_ERR_OR_NULL(metadata) is taken
and the skb is freed with rx_dropped/rx_errors incremented, making the
port_id filtering and __netif_rx() below unreachable.

Would a one-time capability check at open (for example
dmaengine_is_metadata_mode_supported()) be better than a ratelimited
per-packet warning here, and does the MCDMA side need metadata support added
first?

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

[Severity: Medium]
Is metadata[1] really the sideband word?  The in-tree provider hands out the
APP window, not the status word:

drivers/dma/xilinx/xilinx_dma.c:xilinx_dma_get_metadata_ptr() {
	*max_len = *payload_len = sizeof(u32) * XILINX_DMA_NUM_APP_WORDS;
	seg = list_first_entry(&desc->segments,
			       struct xilinx_axidma_tx_segment, node);
	return seg->hw.app;
}

and in the MCDMA hardware descriptor the status and sideband words sit
before app[]:

struct xilinx_aximcdma_desc_hw {
	...
	u32 control;		/* @0x14 */
	u32 status;		/* @0x18 */
	u32 sideband_status;	/* @0x1C */
	u32 app[XILINX_DMA_NUM_APP_WORDS]; /* @0x20 */
};

So metadata[0] is APP0 and metadata[1] is APP1, which would make the decoded
port_id below unrelated to TUSER: a zero APP1 gives port_id 0
(TSN_TUSER_PORT_EP) and every frame is dropped as an unexpected port_id,
while non-zero garbage can accept a frame and later select the wrong
ep->port_md[port_id] for DSA delivery.  The peer consumer of this same ABI,
axienet_dma_rx_cb(), indexes app_metadata[XILINX_DMA_NUM_APP_WORDS - 1],
which matches the APP-window interpretation.

Also, meta_len and meta_max_len are fetched and then never used.  Should
this check meta_len >= 2 * sizeof(u32) before dereferencing metadata[1]?

> +
> +	if (rx_len > ep->max_frm_size || rx_len < ETH_HLEN) {

[ ... ]

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

[ ... ]

> @@ -322,9 +526,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;
> +		}
> +	}
> +

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260909-patches_v2_external-v2-0-3a40babaff4c%40amd.com


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

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

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

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

- [High] With the AXI MCDMA provider, TX descriptors can be reported
  complete before their hardware transfer has finished, so…
- [Medium] This patch introduces a real TX ring with lockless queue
  stop/wake (netif_txq_maybe_stop()/netif_txq_try_stop() at…
- [Medium] A TX DMA transfer error is turned only into a statistic; there
  is no channel recovery and no way for the driver's ring bookkeeping to…
- [Low] Commit message no longer describes the code.
- [Low] The new transmit path never calls skb_tx_timestamp().
- [Low] ep_dma_tx_cb() violates the BQL completion-reporting contract by
  reporting once per packet.
- [Low] The dma-names schema is under-constrained relative to
  ep_count_dma_queues().
- [Low] The new TX completion callback re-enables the netdev TX queue
  unconditionally: ep_dma_tx_cb() ends in `netif_txq_completed_wake(txq,…

--

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

This adds ep_start_xmit() and ep_dma_tx_cb() to the Xilinx TSN endpoint
driver, mapping each skb with dma_map_sg() and submitting it to the
per-queue dmaengine channel. Queue stop/wake is driven from the software
ring indexes with the netif_txq_* helpers, and BQL accounting is added.

No Link: tags in the commit header.

A few questions below.

>     Add the transmit path so the endpoint can send frames.
>     
>     Look up the physical DMA channel for the frame's queue through the map
>     built at probe, 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 frees up.

[Severity: Low]
Does the commit message still match the code?  The sentence "Look up the
physical DMA channel for the frame's queue through the map built at probe"
describes a runtime lookup, but ep_start_xmit() indexes the channel array
by the logical queue:

	xchan = ep->tx_chans[queue];

and reads ep->tx_dma_chan_map[queue] only as a validity test against
TSN_DMA_CH_INVALID.  The v2 changelog says this lookup was deliberately
removed ("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"), so the body seems to
have been left at v1.

What actually makes tx_chans[queue] correct is ep_init_dmaengine()
populating the array in logical-queue order:

	snprintf(name, sizeof(name), "tx_chan%u", ep->tx_dma_chan_map[i]);
	ep->tx_chans[i] = ep_alloc_dma_chan(ep, name, true, TX_BD_NUM_DEFAULT);

Could the message be reworded to state that invariant instead?

> diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> index 9ed1170794e0..9e9a45169681 100644
> --- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c

[ ... ]

> @@ -300,8 +304,147 @@ 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;

[Severity: Medium]
Is there anything that can recover the queue if this early return is
taken?  It leaves the scope before advancing ring_tail and before
netif_txq_completed_wake(), so CIRC_SPACE stays below the start
threshold, the BQL bytes charged by netdev_tx_sent_queue() are never
returned, and the queue stays DRV_XOFF.

ep_netdev_ops registers no .ndo_tx_timeout:

static const struct net_device_ops ep_netdev_ops = {
	.ndo_open		= ep_open,
	.ndo_stop		= ep_stop,
	.ndo_start_xmit		= ep_start_xmit,
	.ndo_validate_addr	= eth_validate_addr,
	.ndo_set_mac_address	= eth_mac_addr,
};

and net/sched/sch_generic.c:netdev_watchdog_up() then never arms the
timer:

void netdev_watchdog_up(struct net_device *dev)
{
	if (!dev->netdev_ops->ndo_tx_timeout)
		return;

so the txq->trans_start bookkeeping that the newly added
netdev_tx_sent_queue() performs has no consumer either.

The same stuck state is reachable from the provider side:
xilinx_mcdma_irq_handler() sets chan->err on an error IRQ and only calls
xilinx_dma_complete_descriptor() when the IOC bit is also set, and
xilinx_mcdma_start_transfer() returns immediately on chan->err, so
descriptors already on active_list never complete and no further
callbacks arrive.

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

[Severity: High]
Can this unmap (and the dev_consume_skb_any() below) run while the engine
is still reading the buffer?  With the AXI MCDMA provider each frame is
submitted and issued on its own:

	cookie = dmaengine_submit(dma_tx_desc);
	...
	dma_async_issue_pending(xchan->chan);

and xilinx_mcdma_start_transfer() has no idle guard, unlike
xilinx_dma_start_transfer():

	if (chan->err)
		return;

	if (list_empty(&chan->pending_list))
		return;
	...
	list_splice_tail_init(&chan->pending_list, &chan->active_list);

so a new descriptor is appended and TDESC written while an earlier
transfer is still in flight, with the coalesce threshold programmed from
chan->desc_pendingcount alone.

When the first IOC arrives, xilinx_dma_complete_descriptor() tests the
hardware completion bit only for AXI DMA:

		if (chan->xdev->dma_config->dmatype == XDMA_TYPE_AXIDMA) {
			...
			if (!(seg->hw.status & XILINX_DMA_BD_COMP_MASK) && chan->has_sg)
				break;
		}

For XDMA_TYPE_AXIMCDMA every descriptor on active_list is moved to
done_list and dma_cookie_complete()d, and xilinx_dma_chan_desc_cleanup()
then invokes a callback for each.  So ep_dma_tx_cb() would unmap and free
skbs whose descriptors the engine has not finished, which on IOMMU
systems means a translation fault and otherwise means transmitting
whatever now occupies those pages.

This provider behaviour predates the series (the axienet dmaengine client
shares it), but this is the code doing the unmap and free.  Should the
client keep a single transaction outstanding, or is this meant to depend
on a provider-side fix?

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

[Severity: Medium]
Should a TX DMA error do more than bump a counter here?  There is no
channel termination or reset, and no way for ring_head/ring_tail to
resynchronise with the hardware.

On the MCDMA side the error leaves the channel wedged:

xilinx_mcdma_irq_handler() sets chan->err on XILINX_MCDMA_IRQ_ERR_MASK and
only calls xilinx_dma_complete_descriptor() under
XILINX_MCDMA_IRQ_IOC_MASK, and xilinx_mcdma_start_transfer() starts with
if (chan->err) return;

Since ep_start_xmit() derives all of its flow control from the ring
indexes:

	if (CIRC_SPACE(xchan->ring_head, READ_ONCE(xchan->ring_tail),
		       xchan->ring_size) <= 1) {

a channel that stops delivering completions freezes ring_tail, so the ring
looks permanently full, the queue stays DRV_XOFF, the BQL bytes stay
charged and the queued skbs stay dma_map_sg()-mapped until
ep_free_dma_chan() runs from ep_stop().

The driver already has ep_reset_dma_controller() (which programs
struct xilinx_vdma_config cfg = { .reset = 1 }) but only uses it at probe
time.  Would an .ndo_tx_timeout that terminates/resets the channel and
resets the ring indexes cover both this and the missing watchdog above?

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

[Severity: Low]
This reports one packet per descriptor callback.
netif_txq_completed_wake() reaches netdev_tx_completed_queue(), whose
kernel-doc in include/linux/netdevice.h says:

 *	Must be called at most once per TX completion round (and not per
 *	individual packet), so that BQL can adjust its limits appropriately.

With this provider one hardware completion round yields many callbacks:
xilinx_dma_complete_descriptor() moves every descriptor on
chan->active_list to done_list on a single IOC, and
xilinx_dma_chan_desc_cleanup() then walks the list:

	list_for_each_entry_safe(desc, next, &chan->done_list, node) {
		...
		dmaengine_desc_callback_invoke(&desc->async_tx, &result);

so dql_completed() is called N times with per-packet byte counts instead
of once with the round total.  Could the completions be batched into a
single report?

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

[ ... ]

> +	spin_lock_bh(&xchan->tx_lock);
> +	cookie = dmaengine_submit(dma_tx_desc);
> +	if (dma_submit_error(cookie)) {
> +		spin_unlock_bh(&xchan->tx_lock);
> +		skbuf_dma->skb = NULL;
> +		goto err_unmap_sg;
> +	}
> +	WRITE_ONCE(xchan->ring_head, xchan->ring_head + 1);
> +	netdev_tx_sent_queue(txq, skb->len);
> +	netif_txq_maybe_stop(txq,
> +			     CIRC_SPACE(xchan->ring_head,
> +					READ_ONCE(xchan->ring_tail),
> +					xchan->ring_size),
> +			     2, 2);
> +	spin_unlock_bh(&xchan->tx_lock);
> +
> +	dma_async_issue_pending(xchan->chan);
> +
> +	return NETDEV_TX_OK;

[Severity: Low]
Is a skb_tx_timestamp(skb) call missing around the hardware handoff here?
Neither ep_start_xmit() nor ep_dma_tx_cb() calls it (there is no
occurrence of skb_tx_timestamp anywhere in the file), so
SOF_TIMESTAMPING_TX_SOFTWARE requests on this netdev never produce a
timestamp.  Documentation/networking/timestamping.rst asks drivers to
generate it as close as possible to the handoff.

> +
> +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;
> @@ -331,10 +474,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);
>  	ep_exit_dmaengine(ep);
> +	for (i = 0; i < ndev->num_tx_queues; i++)
> +		netdev_tx_reset_subqueue(ndev, i);
>  
>  	return 0;
>  }

[Severity: Low]
Can a TX completion undo the stop that netif_tx_disable() performs here?
ep_free_dma_chan() calls dmaengine_terminate_sync(), so callbacks can
still run while the channels are being freed, and ep_dma_tx_cb() ends in:

	netif_txq_completed_wake(txq, 1, len, CIRC_SPACE(...), 2);

which expands to __netif_txq_completed_wake() with down_cond == false, so
it cannot tell a stop done by the xmit path from the one done by ndo_stop.
Neither ep_dma_tx_cb() nor ep_start_xmit() consults ep->closing, unlike
the RX path.  The visible effect looks limited to leaving
__QUEUE_STATE_DRV_XOFF cleared after ndo_stop plus one spurious
__netif_schedule() on the already-installed noop_qdisc, since
dev_deactivate_many() runs before ndo_stop and noop_qdisc has
.enqueue = noop_enqueue, so ep_start_xmit() is not reachable at that
point.

Would using __netif_txq_completed_wake() with a READ_ONCE(ep->closing)
down condition, and publishing ep->closing before netif_tx_disable(), be
cleaner?

One more item, on the binding added earlier in the series rather than on
this patch:

[Severity: Low]
The dma-names schema in
Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
is looser than what ep_count_dma_queues() accepts:

      dma-names:
        minItems: 2
        maxItems: 16
        items:
          pattern: "^(tx|rx)_chan[0-7]$"

There is no uniqueItems: true and no contains rule requiring an entry of
each direction, so a list of only tx_chanN names, only rx_chanN names, or
a repeated name validates.  ep_count_dma_queues() rejects all of those:

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

plus the -EINVAL returns on the *tx_present & BIT(idx) and
rx_present & BIT(idx) duplicate checks.  Could the schema gain
uniqueItems and two contains rules so it does not advertise device trees
that cannot probe?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260909-patches_v2_external-v2-0-3a40babaff4c%40amd.com


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

* Re: [PATCH net-next v2 8/8] net: xilinx: tsn: deliver endpoint RX frames to DSA user ports
  2026-09-08 19:19 ` [PATCH net-next v2 8/8] net: xilinx: tsn: deliver endpoint RX frames to DSA user ports Srinivas Neeli
@ 2026-09-12 20:36   ` netdev-bot+sashiko
  0 siblings, 0 replies; 17+ messages in thread
From: netdev-bot+sashiko @ 2026-09-12 20:36 UTC (permalink / raw)
  To: srinivas.neeli
  Cc: nagadheeraj.rottela, andrew+netdev, davem, edumazet, kuba, pabeni,
	robh, krzk+dt, conor+dt, richardcochran, michal.simek, bigeasy,
	clrkwllms, rostedt, 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

- [Medium] The new RX metadata attach in ep_dma_rx_cb()
  (drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c:303-307) is gated on…

--

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

This patch allocates one METADATA_HW_PORT_MUX metadata_dst per MAC port at
probe time and attaches the matching entry to each RX frame based on its
TUSER port_id, so that DSA can route frames to the swpN user netdevs. The
entries are freed with dst_release() on probe failure and at remove.

> diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> index 9e9a45169681..db7da26521d0 100644
> --- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
> +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c

[ ... ]

> @@ -292,6 +301,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: Medium]

Can this branch ever be taken with the current tree?  netdev_uses_dsa()
requires a DSA switch to have claimed this netdev as its conduit:

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

drivers/net/ethernet/xilinx/tsn/ contains only Kconfig, Makefile,
xilinx_tsn.h, xilinx_tsn_ep.c and xilinx_tsn_main.c, and xilinx_tsn_main.c
registers only the wrapper and endpoint platform drivers:

static struct platform_driver * const tsn_drivers[] = {
	&tsn_driver,
	&xlnx_tsn_ep_driver,
};

There is no Xilinx/xlnx TSN switch driver under drivers/net/dsa/ and no
"xlnx,tsn-switch" compatible anywhere in the tree, so nothing sets
dev->dsa_ptr for this netdev.  The Kconfig help text points at an
out-of-tree component:

	  and the endpoint MAC that connects the IP to the host CPU. The
	  three-port switch is supported by the companion DSA driver.

The commit message states as fact:

    Frames from MAC1 and MAC2 now reach the correct user netdev, which
    completes the conduit data path.

Since the switch-side DSA driver is not part of this submission, is that
result reachable today?  Would it make sense to either submit the DSA
switch driver in the same series or state the dependency in the commit
message, so the port_id 1/2 -> swp1/swp2 contract can be reviewed against
its consumer?

>  	skb->dev = ndev;
>  	skb->protocol = eth_type_trans(skb, ndev);
>  	skb->ip_summed = CHECKSUM_NONE;

[ ... ]

> @@ -854,6 +867,37 @@ static int ep_count_dma_queues(struct device *dev, u32 *out_tx, u32 *out_rx,
> +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);

Related to the above: these two metadata_dst objects are allocated
unconditionally in xlnx_tsn_ep_probe() and kept for the device lifetime,
including on CONFIG_NET_DSA=n builds where netdev_uses_dsa() folds to a
constant false and the attach site in ep_dma_rx_cb() compiles away.  Should
the allocation be tied to the conduit actually being used, or is holding
them unconditionally intended?

> +		if (!md) {
> +			ep_free_port_md(ep);
> +			return -ENOMEM;
> +		}
> +		md->u.port_info.port_id = i;
> +		ep->port_md[i] = md;
> +	}
> +
> +	return 0;
> +}

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260909-patches_v2_external-v2-0-3a40babaff4c%40amd.com


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

end of thread, other threads:[~2026-09-12 20:36 UTC | newest]

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