| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/08 22:17 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"KMSANReasoning": "",
"NeedsKMSAN": false,
"Reasoning": "The patch introduces a new platform driver and Device Tree bindings for the AMD/Xilinx TSN Endpoint Ethernet MAC IP core (drivers/net/ethernet/xilinx/tsn/). This driver is designed for vendor-specific FPGA/SoC hardware and is structurally unreachable in standard virtualized fuzzing environments (QEMU/GCE) on amd64.",
"WorthFuzzing": false
} |
| 1/1 | 2026/09/08 22:17 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit b7dadee0b63edcdc9353e4b61f84fea4d43cb325\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Sep 8 22:17:34 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml b/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml\nnew file mode 100644\nindex 0000000000000..4e65d1710da78\n--- /dev/null\n+++ b/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml\n@@ -0,0 +1,364 @@\n+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)\n+%YAML 1.2\n+---\n+$id: http://devicetree.org/schemas/net/xlnx,tsn-endpoint-ethernet-mac.yaml#\n+$schema: http://devicetree.org/meta-schemas/core.yaml#\n+\n+title: Xilinx TSN Endpoint Ethernet MAC\n+\n+description:\n+ TSN Endpoint Ethernet MAC IP implements IEEE 802.1 Time-Sensitive\n+ Networking (TSN) standards and provides low-latency network connectivity\n+ in the Bridged Endpoint configuration. The IP integrates a three-port\n+ switch, with two ports connected to the external network and one port\n+ connected to an internal endpoint. It also supports GMII/RGMII interfaces\n+ for connection to an external PHY, enabling full-duplex operation at\n+ 100 Mb/s and 1 Gb/s speeds.\n+\n+maintainers:\n+ - Srinivas Neeli \u003csrinivas.neeli@amd.com\u003e\n+ - Nagadheeraj Rottela \u003cnagadheeraj.rottela@amd.com\u003e\n+\n+properties:\n+ compatible:\n+ const: xlnx,tsn-endpoint-ethernet-mac-3.0\n+\n+ clocks:\n+ items:\n+ - description: GTX clock for transmitter logic and RTC\n+ - description: GTX 90-degree phase shifted clock for RGMII\n+ - description: Host RX FIFO clock\n+ - description: Host TX FIFO clock\n+ - description: Reference clock for IDELAY control\n+ - description: AXI-Lite register interface clock\n+\n+ clock-names:\n+ items:\n+ - const: gtx\n+ - const: gtx90\n+ - const: host_rxfifo\n+ - const: host_txfifo\n+ - const: ref\n+ - const: s_axi\n+\n+ ranges: true\n+\n+ '#address-cells':\n+ const: 1\n+\n+ '#size-cells':\n+ const: 1\n+\n+ xlnx,num-priorities:\n+ $ref: /schemas/types.yaml#/definitions/uint32\n+ minimum: 2\n+ maximum: 8\n+ description:\n+ Number of traffic classes (priorities) configured in the IP.\n+ This is an IP configuration parameter that determines the number of\n+ priority queues available for QoS scheduling. Traffic classes map to\n+ IEEE 802.1Q priority levels (0-7).\n+\n+patternProperties:\n+ \"^ethernet@[0-9a-f]+$\":\n+ type: object\n+ $ref: /schemas/net/ethernet-controller.yaml#\n+ unevaluatedProperties: false\n+ description:\n+ Endpoint MAC sub-node. Provides the host-side data path through the\n+ DMA channels described by its dmas / dma-names.\n+ properties:\n+ compatible:\n+ const: xlnx,tsn-ep-mac\n+\n+ reg:\n+ maxItems: 1\n+\n+ dmas:\n+ minItems: 2\n+ maxItems: 16\n+\n+ dma-names:\n+ minItems: 2\n+ maxItems: 16\n+ items:\n+ pattern: \"^(tx|rx)_chan[0-7]$\"\n+ description: |\n+ Names matching the entries in 'dmas', one per DMA channel and in\n+ the same order. TX channel names use \"tx_chan\u003cN\u003e\" and RX channel\n+ names use \"rx_chan\u003cN\u003e\", where N is 0..7. Example: \"tx_chan0\",\n+ \"tx_chan1\", ..., \"rx_chan0\", \"rx_chan1\", ....\n+\n+ tx-queues-config:\n+ type: object\n+ additionalProperties: false\n+ description:\n+ TX queue configuration node that maps IP priority queues to\n+ DMA TX channels. The TSN IP supports multiple priority queues for\n+ QoS scheduling, and each queue can be connected to a specific DMA\n+ channel. This mapping defines which DMA TX channel is used to\n+ transmit packets for each priority queue. For example, queue0\n+ with xlnx,dma-channel-num set to 5 means priority queue 0 uses\n+ tx_chan5 for data transfer.\n+\n+ patternProperties:\n+ \"^queue[0-7]$\":\n+ type: object\n+ additionalProperties: false\n+\n+ description:\n+ Each subnode represents a priority queue. The\n+ xlnx,dma-channel-num property specifies which DMA TX channel\n+ (tx_chan0 to tx_chan7) is connected to this queue for\n+ transmitting packets.\n+ properties:\n+ xlnx,dma-channel-num:\n+ $ref: /schemas/types.yaml#/definitions/uint32\n+ description:\n+ DMA channel that transmits this priority queue, given as\n+ the N of the tx_chanN entry in dma-names.\n+ minimum: 0\n+ maximum: 7\n+\n+ required:\n+ - xlnx,dma-channel-num\n+\n+ required:\n+ - compatible\n+ - reg\n+ - dmas\n+ - dma-names\n+ - tx-queues-config\n+\n+ \"^switch@[0-9a-f]+$\":\n+ type: object\n+ $ref: /schemas/net/dsa/dsa.yaml#/$defs/ethernet-ports\n+ description:\n+ Three-port Ethernet switch sub-node. Contains the switch fabric\n+ register window and the two per-MAC register windows. Each per-MAC\n+ window houses an MDIO controller and a PTP register block at fixed\n+ offsets. The IP's per-MAC and PTP-timer interrupts are exposed here.\n+\n+ properties:\n+ compatible:\n+ const: xlnx,tsn-switch\n+\n+ reg:\n+ items:\n+ - description: Switch fabric register window\n+ - description: MAC1 register window (includes MDIO and PTP)\n+ - description: MAC2 register window (includes MDIO and PTP)\n+\n+ reg-names:\n+ items:\n+ - const: switch\n+ - const: mac1\n+ - const: mac2\n+\n+ interrupts:\n+ minItems: 7\n+ maxItems: 7\n+\n+ interrupt-names:\n+ items:\n+ - const: ptp_rx_mac1\n+ - const: ptp_tx_mac1\n+ - const: mac_irq_mac1\n+ - const: ptp_timer\n+ - const: ptp_rx_mac2\n+ - const: ptp_tx_mac2\n+ - const: mac_irq_mac2\n+\n+ mdio-1:\n+ $ref: /schemas/net/mdio.yaml#\n+ description:\n+ MDIO bus controller for MAC1. Registers reside inside the mac1\n+ reg-names window.\n+ unevaluatedProperties: false\n+\n+ mdio-2:\n+ $ref: /schemas/net/mdio.yaml#\n+ description:\n+ MDIO bus controller for MAC2. Registers reside inside the mac2\n+ reg-names window.\n+ unevaluatedProperties: false\n+\n+ patternProperties:\n+ \"^(ethernet-)?ports$\":\n+ type: object\n+ additionalProperties: false\n+\n+ properties:\n+ '#address-cells':\n+ const: 1\n+\n+ '#size-cells':\n+ const: 0\n+\n+ port@0:\n+ type: object\n+ unevaluatedProperties: true\n+ description:\n+ CPU port. Wired internally to the endpoint MAC sibling.\n+\n+ properties:\n+ reg:\n+ const: 0\n+\n+ phy-mode:\n+ const: internal\n+\n+ required:\n+ - ethernet\n+\n+ patternProperties:\n+ \"^port@[1-2]$\":\n+ type: object\n+ unevaluatedProperties: true\n+ description:\n+ External Ethernet port. port@1 is wired to MAC1 and reached\n+ through the mdio-1 bus, port@2 is wired to MAC2 and reached\n+ through the mdio-2 bus.\n+\n+ properties:\n+ reg:\n+ enum: [1, 2]\n+\n+ phy-mode:\n+ enum:\n+ - gmii\n+ - rgmii\n+ - rgmii-id\n+\n+ required:\n+ - reg\n+ - phy-mode\n+ - phy-handle\n+\n+ required:\n+ - port@0\n+ - port@1\n+ - port@2\n+\n+ required:\n+ - compatible\n+ - reg\n+ - reg-names\n+ - interrupts\n+ - interrupt-names\n+\n+ oneOf:\n+ - required: [ports]\n+ - required: [ethernet-ports]\n+\n+ unevaluatedProperties: false\n+\n+additionalProperties: false\n+\n+required:\n+ - compatible\n+ - clocks\n+ - clock-names\n+ - xlnx,num-priorities\n+ - ranges\n+ - '#address-cells'\n+ - '#size-cells'\n+\n+examples:\n+ - |\n+ tsn@80040000 {\n+ compatible = \"xlnx,tsn-endpoint-ethernet-mac-3.0\";\n+ clocks = \u003c\u0026misc_clk_2\u003e, \u003c\u0026misc_clk_2\u003e, \u003c\u0026misc_clk_1\u003e, \u003c\u0026misc_clk_1\u003e,\n+ \u003c\u0026misc_clk_3\u003e, \u003c\u0026misc_clk_0\u003e;\n+ clock-names = \"gtx\", \"gtx90\", \"host_rxfifo\", \"host_txfifo\",\n+ \"ref\", \"s_axi\";\n+ xlnx,num-priorities = \u003c8\u003e;\n+ #address-cells = \u003c1\u003e;\n+ #size-cells = \u003c1\u003e;\n+ ranges = \u003c0x0 0x80040000 0x40000\u003e;\n+\n+ ep_mac: ethernet@16000 {\n+ compatible = \"xlnx,tsn-ep-mac\";\n+ reg = \u003c0x16000 0xa000\u003e;\n+ dmas = \u003c\u0026axi_mcdma_0 0\u003e, \u003c\u0026axi_mcdma_0 1\u003e, \u003c\u0026axi_mcdma_0 2\u003e,\n+ \u003c\u0026axi_mcdma_0 3\u003e, \u003c\u0026axi_mcdma_0 4\u003e, \u003c\u0026axi_mcdma_0 5\u003e,\n+ \u003c\u0026axi_mcdma_0 6\u003e, \u003c\u0026axi_mcdma_0 7\u003e, \u003c\u0026axi_mcdma_0 16\u003e,\n+ \u003c\u0026axi_mcdma_0 17\u003e, \u003c\u0026axi_mcdma_0 18\u003e, \u003c\u0026axi_mcdma_0 19\u003e,\n+ \u003c\u0026axi_mcdma_0 20\u003e, \u003c\u0026axi_mcdma_0 21\u003e, \u003c\u0026axi_mcdma_0 22\u003e,\n+ \u003c\u0026axi_mcdma_0 23\u003e;\n+ dma-names = \"tx_chan0\", \"tx_chan1\", \"tx_chan2\", \"tx_chan3\",\n+ \"tx_chan4\", \"tx_chan5\", \"tx_chan6\", \"tx_chan7\",\n+ \"rx_chan0\", \"rx_chan1\", \"rx_chan2\", \"rx_chan3\",\n+ \"rx_chan4\", \"rx_chan5\", \"rx_chan6\", \"rx_chan7\";\n+ tx-queues-config {\n+ queue0 { xlnx,dma-channel-num = \u003c0x7\u003e; };\n+ queue1 { xlnx,dma-channel-num = \u003c0x6\u003e; };\n+ queue2 { xlnx,dma-channel-num = \u003c0x5\u003e; };\n+ queue3 { xlnx,dma-channel-num = \u003c0x4\u003e; };\n+ queue4 { xlnx,dma-channel-num = \u003c0x3\u003e; };\n+ queue5 { xlnx,dma-channel-num = \u003c0x2\u003e; };\n+ queue6 { xlnx,dma-channel-num = \u003c0x1\u003e; };\n+ queue7 { xlnx,dma-channel-num = \u003c0x0\u003e; };\n+ };\n+ };\n+\n+ switch@38000 {\n+ compatible = \"xlnx,tsn-switch\";\n+ reg = \u003c0x38000 0x8000\u003e,\n+ \u003c0x00000 0x14000\u003e,\n+ \u003c0x20000 0x14000\u003e;\n+ reg-names = \"switch\", \"mac1\", \"mac2\";\n+\n+ interrupt-parent = \u003c\u0026intc\u003e;\n+ interrupts = \u003c0 2\u003e, \u003c2 2\u003e, \u003c4 2\u003e, \u003c6 2\u003e,\n+ \u003c1 2\u003e, \u003c3 2\u003e, \u003c5 2\u003e;\n+ interrupt-names = \"ptp_rx_mac1\", \"ptp_tx_mac1\",\n+ \"mac_irq_mac1\", \"ptp_timer\",\n+ \"ptp_rx_mac2\", \"ptp_tx_mac2\",\n+ \"mac_irq_mac2\";\n+\n+ mdio-1 {\n+ #address-cells = \u003c1\u003e;\n+ #size-cells = \u003c0\u003e;\n+ phy0: ethernet-phy@0 {\n+ reg = \u003c0\u003e;\n+ };\n+ };\n+\n+ mdio-2 {\n+ #address-cells = \u003c1\u003e;\n+ #size-cells = \u003c0\u003e;\n+ phy1: ethernet-phy@1 {\n+ reg = \u003c1\u003e;\n+ };\n+ };\n+\n+ ethernet-ports {\n+ #address-cells = \u003c1\u003e;\n+ #size-cells = \u003c0\u003e;\n+\n+ port@0 {\n+ reg = \u003c0\u003e;\n+ ethernet = \u003c\u0026ep_mac\u003e;\n+ phy-mode = \"internal\";\n+\n+ fixed-link {\n+ speed = \u003c1000\u003e;\n+ full-duplex;\n+ };\n+ };\n+\n+ port@1 {\n+ reg = \u003c1\u003e;\n+ phy-handle = \u003c\u0026phy0\u003e;\n+ phy-mode = \"rgmii-id\";\n+ };\n+\n+ port@2 {\n+ reg = \u003c2\u003e;\n+ phy-handle = \u003c\u0026phy1\u003e;\n+ phy-mode = \"rgmii-id\";\n+ };\n+ };\n+ };\n+ };\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex b23fb6f2f4ef4..ff94e314eeb1f 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -29911,6 +29911,14 @@ M:\tHarsh Jain \u003ch.jain@amd.com\u003e\n S:\tMaintained\n F:\tdrivers/char/hw_random/xilinx-trng.c\n \n+XILINX TSN ENDPOINT ETHERNET MAC DRIVER\n+M:\tNagadheeraj Rottela \u003cnagadheeraj.rottela@amd.com\u003e\n+M:\tSrinivas Neeli \u003csrinivas.neeli@amd.com\u003e\n+L:\tnetdev@vger.kernel.org\n+S:\tMaintained\n+F:\tDocumentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml\n+F:\tdrivers/net/ethernet/xilinx/tsn/\n+\n XILINX UARTLITE SERIAL DRIVER\n M:\tPeter Korsgaard \u003cjacmet@sunsite.dk\u003e\n L:\tlinux-serial@vger.kernel.org\ndiff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig\nindex 7502214cc7d50..c6d704c8d3d46 100644\n--- a/drivers/net/ethernet/xilinx/Kconfig\n+++ b/drivers/net/ethernet/xilinx/Kconfig\n@@ -41,4 +41,5 @@ config XILINX_LL_TEMAC\n \t This driver supports the Xilinx 10/100/1000 LocalLink TEMAC\n \t core used in Xilinx Spartan and Virtex FPGAs\n \n+source \"drivers/net/ethernet/xilinx/tsn/Kconfig\"\n endif # NET_VENDOR_XILINX\ndiff --git a/drivers/net/ethernet/xilinx/Makefile b/drivers/net/ethernet/xilinx/Makefile\nindex 7d7dc1771423e..66dab012650b3 100644\n--- a/drivers/net/ethernet/xilinx/Makefile\n+++ b/drivers/net/ethernet/xilinx/Makefile\n@@ -8,3 +8,4 @@ obj-$(CONFIG_XILINX_LL_TEMAC) += ll_temac.o\n obj-$(CONFIG_XILINX_EMACLITE) += xilinx_emaclite.o\n xilinx_emac-objs := xilinx_axienet_main.o xilinx_axienet_mdio.o\n obj-$(CONFIG_XILINX_AXI_EMAC) += xilinx_emac.o\n+obj-$(CONFIG_XILINX_TSN) += tsn/\ndiff --git a/drivers/net/ethernet/xilinx/tsn/Kconfig b/drivers/net/ethernet/xilinx/tsn/Kconfig\nnew file mode 100644\nindex 0000000000000..292f176944042\n--- /dev/null\n+++ b/drivers/net/ethernet/xilinx/tsn/Kconfig\n@@ -0,0 +1,16 @@\n+# SPDX-License-Identifier: GPL-2.0-only\n+#\n+# Xilinx TSN device configuration\n+#\n+\n+config XILINX_TSN\n+\ttristate \"Xilinx TSN Ethernet driver\"\n+\tdepends on OF_ADDRESS\n+\tdepends on XILINX_DMA\n+\thelp\n+\t This driver supports the AMD/Xilinx Time-Sensitive Networking\n+\t (TSN) Endpoint Ethernet MAC IP. It provides the wrapper device\n+\t and the endpoint MAC that connects the IP to the host CPU. The\n+\t three-port switch is supported by the companion DSA driver.\n+\n+\t If unsure, say N.\ndiff --git a/drivers/net/ethernet/xilinx/tsn/Makefile b/drivers/net/ethernet/xilinx/tsn/Makefile\nnew file mode 100644\nindex 0000000000000..5886828b386f4\n--- /dev/null\n+++ b/drivers/net/ethernet/xilinx/tsn/Makefile\n@@ -0,0 +1,2 @@\n+obj-$(CONFIG_XILINX_TSN) += xilinx_tsn.o\n+xilinx_tsn-y := xilinx_tsn_main.o xilinx_tsn_ep.o\ndiff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h\nnew file mode 100644\nindex 0000000000000..b0757e22d1fd1\n--- /dev/null\n+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h\n@@ -0,0 +1,15 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * AMD/Xilinx TSN Endpoint Ethernet MAC driver, shared definitions.\n+ *\n+ * Copyright (C) 2026 Advanced Micro Devices, Inc.\n+ */\n+\n+#ifndef _XILINX_TSN_H\n+#define _XILINX_TSN_H\n+\n+#include \u003clinux/platform_device.h\u003e\n+\n+extern struct platform_driver xlnx_tsn_ep_driver;\n+\n+#endif /* _XILINX_TSN_H */\ndiff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c\nnew file mode 100644\nindex 0000000000000..db7da26521d01\n--- /dev/null\n+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c\n@@ -0,0 +1,1034 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * AMD/Xilinx TSN Endpoint MAC driver.\n+ *\n+ * Copyright (C) 2026 Advanced Micro Devices, Inc.\n+ */\n+\n+#include \u003clinux/bitfield.h\u003e\n+#include \u003clinux/bitops.h\u003e\n+#include \u003clinux/circ_buf.h\u003e\n+#include \u003clinux/dma/xilinx_dma.h\u003e\n+#include \u003clinux/dma-mapping.h\u003e\n+#include \u003clinux/dmaengine.h\u003e\n+#include \u003clinux/etherdevice.h\u003e\n+#include \u003clinux/ethtool.h\u003e\n+#include \u003clinux/if_ether.h\u003e\n+#include \u003clinux/if_vlan.h\u003e\n+#include \u003clinux/io.h\u003e\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/of.h\u003e\n+#include \u003clinux/of_net.h\u003e\n+#include \u003clinux/platform_device.h\u003e\n+#include \u003clinux/scatterlist.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/spinlock.h\u003e\n+#include \u003clinux/string.h\u003e\n+#include \u003clinux/timer.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+#include \u003cnet/dsa.h\u003e\n+#include \u003cnet/dst_metadata.h\u003e\n+#include \u003cnet/netdev_queues.h\u003e\n+\n+#include \"xilinx_tsn.h\"\n+\n+#define DRIVER_NAME\t\t\t\"xilinx_tsn_ep\"\n+\n+#define TSN_DMA_CH_INVALID\t\t0xFFU\n+#define TSN_MAX_TX_QUEUE\t\t8\n+#define TSN_MAX_RX_QUEUE\t\t8\n+\n+#define TSN_MAX_VLAN_FRAME_SIZE\t\t(ETH_DATA_LEN + VLAN_ETH_HLEN + \\\n+\t\t\t\t\t ETH_FCS_LEN)\n+\n+#define TX_BD_NUM_DEFAULT\t\t64\n+#define RX_BD_NUM_DEFAULT\t\t128\n+\n+#define EP_RX_REFILL_RETRY\t\tmsecs_to_jiffies(10)\n+\n+/*\n+ * The DMA descriptor sideband status word packs TID/TDEST/TUSER together;\n+ * TUSER occupies the low byte, TID/TDEST sit in the upper bits.\n+ */\n+#define TSN_TUSER_MASK\t\t\tGENMASK(7, 0)\n+/* TUSER Input Port ID field (bits [5:4] of the TUSER field) */\n+#define TSN_TUSER_PORT_ID_MASK\t\tGENMASK(5, 4)\n+#define TSN_TUSER_PORT_EP\t\t0x0\n+#define TSN_TUSER_PORT_MAC1\t\t0x1\n+#define TSN_TUSER_PORT_MAC2\t\t0x2\n+\n+/* Sized to index port_md[] by TUSER port_id (1 or 2, slot 0 unused) */\n+#define XLNX_TSN_EP_PORT_MD_SLOTS\t(TSN_TUSER_PORT_MAC2 + 1)\n+\n+/**\n+ * struct skbuf_dma_descriptor - skb container for each in-flight DMA descriptor\n+ * @sgl: scatter-gather list backing the DMA mapping\n+ * @desc: dmaengine descriptor handle\n+ * @dma_address: physical address of the first sgl entry (RX path)\n+ * @skb: SKB owning the buffer\n+ * @sg_len: number of valid entries in @sgl (TX path)\n+ */\n+struct skbuf_dma_descriptor {\n+\tstruct scatterlist sgl[MAX_SKB_FRAGS + 1];\n+\tstruct dma_async_tx_descriptor *desc;\n+\tdma_addr_t dma_address;\n+\tstruct sk_buff *skb;\n+\tint sg_len;\n+};\n+\n+/**\n+ * struct xlnx_tsn_ep_dma_chan - one DMA channel and its SKB ring\n+ * @skb_ring: per-slot SKB descriptors\n+ * @ep: pointer back to the owning EP instance\n+ * @chan: dmaengine channel handle\n+ * @dma_dev: device used for DMA mapping (the DMA engine, not the EP)\n+ * @ring_head: producer index\n+ * @ring_tail: consumer index\n+ * @ring_size: number of slots in @skb_ring\n+ * @rx_lock: serialises @ring_head between the RX callback and the refill timer\n+ * @tx_lock: serialises @ring_head and @ring_tail between xmit and TX completion\n+ * @rx_refill_timer: retries RX refill after an allocation failure\n+ * @is_tx: true for TX channels, false for RX\n+ */\n+struct xlnx_tsn_ep_dma_chan {\n+\tstruct skbuf_dma_descriptor **skb_ring;\n+\tstruct xlnx_tsn_ep *ep;\n+\tstruct dma_chan *chan;\n+\tstruct device *dma_dev;\n+\tu32 ring_head;\n+\tu32 ring_tail;\n+\tu32 ring_size;\n+\tspinlock_t rx_lock;\t/* serialises @ring_head */\n+\tspinlock_t tx_lock;\t/* serialises @ring_head and @ring_tail */\n+\tstruct timer_list rx_refill_timer;\n+\tbool is_tx;\n+};\n+\n+/**\n+ * struct xlnx_tsn_ep - EP MAC private data, embedded in net_device priv area\n+ * @ndev: the conduit netdev (\"ep0\" for the first IP instance)\n+ * @dev: backing device\n+ * @num_tx_queues: number of TX DMA channels (one per priority)\n+ * @num_rx_queues: number of RX DMA channels\n+ * @tx_dma_chan_map: logical TX queue index -\u003e physical DMA channel number\n+ * @rx_chan_num: RX ring index -\u003e physical DMA channel number\n+ * @max_frm_size: maximum frame size accepted on RX\n+ * @tx_chans: array of TX channels (size @num_tx_queues)\n+ * @rx_chans: array of RX channels (size @num_rx_queues)\n+ * @closing: set in ndo_stop so the RX completion callback stops re-arming\n+ * @port_md: per-TUSER-port METADATA_HW_PORT_MUX entries attached on RX,\n+ *\tindexed by port_id (1 for MAC1, 2 for MAC2)\n+ */\n+struct xlnx_tsn_ep {\n+\tstruct net_device *ndev;\n+\tstruct device *dev;\n+\tu32 num_tx_queues;\n+\tu32 num_rx_queues;\n+\tu32 tx_dma_chan_map[TSN_MAX_TX_QUEUE];\n+\tu32 rx_chan_num[TSN_MAX_RX_QUEUE];\n+\tu32 max_frm_size;\n+\n+\tstruct xlnx_tsn_ep_dma_chan **tx_chans;\n+\tstruct xlnx_tsn_ep_dma_chan **rx_chans;\n+\n+\tbool closing;\n+\n+\tstruct metadata_dst *port_md[XLNX_TSN_EP_PORT_MD_SLOTS];\n+};\n+\n+static inline struct skbuf_dma_descriptor *\n+ep_get_desc(struct xlnx_tsn_ep_dma_chan *xchan, int idx)\n+{\n+\treturn xchan-\u003eskb_ring[idx];\n+}\n+\n+static void ep_dma_rx_cb(void *data, const struct dmaengine_result *result);\n+\n+static int ep_rx_submit_desc(struct xlnx_tsn_ep_dma_chan *xchan)\n+{\n+\tstruct dma_async_tx_descriptor *dma_rx_desc;\n+\tstruct skbuf_dma_descriptor *skbuf_dma;\n+\tstruct xlnx_tsn_ep *ep = xchan-\u003eep;\n+\tstruct sk_buff *skb;\n+\tdma_addr_t addr;\n+\n+\tskbuf_dma = ep_get_desc(xchan, xchan-\u003ering_head \u0026 (xchan-\u003ering_size - 1));\n+\tif (!skbuf_dma)\n+\t\treturn -ENOSPC;\n+\n+\tskb = dev_alloc_skb(ep-\u003emax_frm_size);\n+\tif (!skb)\n+\t\treturn -ENOMEM;\n+\n+\tsg_init_table(skbuf_dma-\u003esgl, 1);\n+\taddr = dma_map_single(xchan-\u003edma_dev, skb-\u003edata, ep-\u003emax_frm_size,\n+\t\t\t DMA_FROM_DEVICE);\n+\tif (unlikely(dma_mapping_error(xchan-\u003edma_dev, addr))) {\n+\t\tif (net_ratelimit())\n+\t\t\tdev_warn(ep-\u003edev, \"DMA mapping error on RX submit\\n\");\n+\n+\t\tgoto err_free_skb;\n+\t}\n+\tsg_dma_address(skbuf_dma-\u003esgl) = addr;\n+\tsg_dma_len(skbuf_dma-\u003esgl) = ep-\u003emax_frm_size;\n+\tdma_rx_desc = dmaengine_prep_slave_sg(xchan-\u003echan, skbuf_dma-\u003esgl,\n+\t\t\t\t\t 1, DMA_DEV_TO_MEM,\n+\t\t\t\t\t DMA_PREP_INTERRUPT);\n+\tif (!dma_rx_desc)\n+\t\tgoto err_unmap_skb;\n+\n+\tskbuf_dma-\u003eskb = skb;\n+\tskbuf_dma-\u003edma_address = sg_dma_address(skbuf_dma-\u003esgl);\n+\tskbuf_dma-\u003edesc = dma_rx_desc;\n+\tdma_rx_desc-\u003ecallback_param = xchan;\n+\tdma_rx_desc-\u003ecallback_result = ep_dma_rx_cb;\n+\txchan-\u003ering_head++;\n+\tdmaengine_submit(dma_rx_desc);\n+\n+\treturn 0;\n+\n+err_unmap_skb:\n+\tdma_unmap_single(xchan-\u003edma_dev, addr, ep-\u003emax_frm_size, DMA_FROM_DEVICE);\n+err_free_skb:\n+\tdev_kfree_skb(skb);\n+\treturn -ENOMEM;\n+}\n+\n+static bool ep_rx_refill(struct xlnx_tsn_ep_dma_chan *xchan, bool arm_timer)\n+{\n+\tint avail, i;\n+\n+\tguard(spinlock_bh)(\u0026xchan-\u003erx_lock);\n+\n+\tif (READ_ONCE(xchan-\u003eep-\u003eclosing))\n+\t\treturn false;\n+\n+\tavail = CIRC_SPACE(xchan-\u003ering_head, READ_ONCE(xchan-\u003ering_tail),\n+\t\t\t xchan-\u003ering_size);\n+\tfor (i = 0; i \u003c avail; i++) {\n+\t\tif (ep_rx_submit_desc(xchan))\n+\t\t\tbreak;\n+\t}\n+\tdma_async_issue_pending(xchan-\u003echan);\n+\n+\tif (xchan-\u003ering_head != READ_ONCE(xchan-\u003ering_tail))\n+\t\treturn true;\n+\n+\tif (arm_timer)\n+\t\tmod_timer(\u0026xchan-\u003erx_refill_timer, jiffies + EP_RX_REFILL_RETRY);\n+\n+\treturn false;\n+}\n+\n+static void ep_rx_refill_timer(struct timer_list *t)\n+{\n+\tstruct xlnx_tsn_ep_dma_chan *xchan = timer_container_of(xchan, t,\n+\t\t\t\t\t\t\t rx_refill_timer);\n+\n+\tep_rx_refill(xchan, true);\n+}\n+\n+static void ep_dma_rx_cb(void *data, const struct dmaengine_result *result)\n+{\n+\tstruct xlnx_tsn_ep_dma_chan *xchan = data;\n+\tstruct skbuf_dma_descriptor *skbuf_dma;\n+\tsize_t meta_len, meta_max_len, rx_len;\n+\tstruct xlnx_tsn_ep *ep = xchan-\u003eep;\n+\tstruct net_device *ndev = ep-\u003endev;\n+\tstruct sk_buff *skb;\n+\tu32 port_id, tuser;\n+\tu32 *metadata;\n+\n+\tskbuf_dma = ep_get_desc(xchan, xchan-\u003ering_tail \u0026 (xchan-\u003ering_size - 1));\n+\tWRITE_ONCE(xchan-\u003ering_tail, xchan-\u003ering_tail + 1);\n+\tskb = skbuf_dma-\u003eskb;\n+\tskbuf_dma-\u003eskb = NULL;\n+\n+\tdma_unmap_single(xchan-\u003edma_dev, skbuf_dma-\u003edma_address,\n+\t\t\t ep-\u003emax_frm_size, DMA_FROM_DEVICE);\n+\n+\tif (result-\u003eresult != DMA_TRANS_NOERROR) {\n+\t\tif (net_ratelimit())\n+\t\t\tdev_warn(ep-\u003edev, \"RX DMA transfer error %d\\n\",\n+\t\t\t\t result-\u003eresult);\n+\n+\t\tdev_kfree_skb_any(skb);\n+\t\tDEV_STATS_INC(ndev, rx_dropped);\n+\t\tDEV_STATS_INC(ndev, rx_errors);\n+\t\tgoto submit_new;\n+\t}\n+\n+\tmetadata = dmaengine_desc_get_metadata_ptr(skbuf_dma-\u003edesc,\n+\t\t\t\t\t\t \u0026meta_len,\n+\t\t\t\t\t\t \u0026meta_max_len);\n+\tif (IS_ERR_OR_NULL(metadata)) {\n+\t\tif (net_ratelimit())\n+\t\t\tdev_warn(ep-\u003edev, \"Failed to get RX metadata pointer\\n\");\n+\n+\t\tdev_kfree_skb_any(skb);\n+\t\tDEV_STATS_INC(ndev, rx_dropped);\n+\t\tDEV_STATS_INC(ndev, rx_errors);\n+\t\tgoto submit_new;\n+\t}\n+\n+\t/* MCDMA metadata: [0] = status, [1] = sideband (TID/TDEST/TUSER), [2..] = app */\n+\ttuser = metadata[1] \u0026 TSN_TUSER_MASK;\n+\trx_len = ep-\u003emax_frm_size - result-\u003eresidue;\n+\n+\tif (rx_len \u003e ep-\u003emax_frm_size || rx_len \u003c ETH_HLEN) {\n+\t\tif (net_ratelimit())\n+\t\t\tdev_warn(ep-\u003edev, \"Invalid RX length %zu (max=%u, min=%u)\\n\",\n+\t\t\t\t rx_len, ep-\u003emax_frm_size, ETH_HLEN);\n+\n+\t\tdev_kfree_skb_any(skb);\n+\t\tDEV_STATS_INC(ndev, rx_dropped);\n+\t\tDEV_STATS_INC(ndev, rx_errors);\n+\t\tgoto submit_new;\n+\t}\n+\n+\tport_id = FIELD_GET(TSN_TUSER_PORT_ID_MASK, tuser);\n+\tif (port_id != TSN_TUSER_PORT_MAC1 \u0026\u0026 port_id != TSN_TUSER_PORT_MAC2) {\n+\t\tif (net_ratelimit())\n+\t\t\tdev_dbg(ep-\u003edev, \"RX dropping unexpected TUSER port_id=%u\\n\",\n+\t\t\t\tport_id);\n+\n+\t\tdev_kfree_skb_any(skb);\n+\t\tDEV_STATS_INC(ndev, rx_dropped);\n+\t\tgoto submit_new;\n+\t}\n+\n+\tskb_put(skb, rx_len);\n+\tif (netdev_uses_dsa(ndev)) {\n+\t\tdst_hold(\u0026ep-\u003eport_md[port_id]-\u003edst);\n+\t\tskb_dst_set(skb, \u0026ep-\u003eport_md[port_id]-\u003edst);\n+\t}\n+\tskb-\u003edev = ndev;\n+\tskb-\u003eprotocol = eth_type_trans(skb, ndev);\n+\tskb-\u003eip_summed = CHECKSUM_NONE;\n+\t__netif_rx(skb);\n+\n+\tDEV_STATS_INC(ndev, rx_packets);\n+\tDEV_STATS_ADD(ndev, rx_bytes, rx_len);\n+\n+submit_new:\n+\tep_rx_refill(xchan, true);\n+}\n+\n+static void ep_dma_tx_cb(void *data, const struct dmaengine_result *result)\n+{\n+\tstruct xlnx_tsn_ep_dma_chan *xchan = data;\n+\tstruct skbuf_dma_descriptor *skbuf_dma;\n+\tstruct netdev_queue *txq;\n+\tstruct net_device *ndev;\n+\tstruct scatterlist *sgl;\n+\tstruct sk_buff *skb;\n+\tint sg_len;\n+\tint len;\n+\n+\tscoped_guard(spinlock_bh, \u0026xchan-\u003etx_lock) {\n+\t\tskbuf_dma = ep_get_desc(xchan,\n+\t\t\t\t\txchan-\u003ering_tail \u0026 (xchan-\u003ering_size - 1));\n+\t\tif (!skbuf_dma || !skbuf_dma-\u003eskb)\n+\t\t\treturn;\n+\n+\t\tskb = skbuf_dma-\u003eskb;\n+\t\tsgl = skbuf_dma-\u003esgl;\n+\t\tsg_len = skbuf_dma-\u003esg_len;\n+\n+\t\tdma_unmap_sg(xchan-\u003edma_dev, sgl, sg_len, DMA_TO_DEVICE);\n+\n+\t\tskbuf_dma-\u003eskb = NULL;\n+\t\tWRITE_ONCE(xchan-\u003ering_tail, xchan-\u003ering_tail + 1);\n+\t}\n+\n+\tndev = skb-\u003edev;\n+\ttxq = netdev_get_tx_queue(ndev, skb_get_queue_mapping(skb));\n+\tlen = skb-\u003elen;\n+\n+\tif (unlikely(result-\u003eresult != DMA_TRANS_NOERROR)) {\n+\t\tDEV_STATS_INC(ndev, tx_errors);\n+\t} else {\n+\t\tDEV_STATS_INC(ndev, tx_packets);\n+\t\tDEV_STATS_ADD(ndev, tx_bytes, len);\n+\t}\n+\n+\tdev_consume_skb_any(skb);\n+\tnetif_txq_completed_wake(txq, 1, len,\n+\t\t\t\t CIRC_SPACE(READ_ONCE(xchan-\u003ering_head),\n+\t\t\t\t\t READ_ONCE(xchan-\u003ering_tail),\n+\t\t\t\t\t xchan-\u003ering_size), 2);\n+}\n+\n+static netdev_tx_t ep_start_xmit(struct sk_buff *skb, struct net_device *ndev)\n+{\n+\tstruct dma_async_tx_descriptor *dma_tx_desc;\n+\tstruct xlnx_tsn_ep *ep = netdev_priv(ndev);\n+\tstruct skbuf_dma_descriptor *skbuf_dma;\n+\tint queue = skb_get_queue_mapping(skb);\n+\tstruct xlnx_tsn_ep_dma_chan *xchan;\n+\tstruct netdev_queue *txq;\n+\tint sg_len, nents, ret;\n+\tdma_cookie_t cookie;\n+\n+\tif (unlikely(queue \u003e= ep-\u003enum_tx_queues)) {\n+\t\tif (net_ratelimit())\n+\t\t\tnetdev_warn(ndev, \"Invalid TX queue %d (max %u)\\n\",\n+\t\t\t\t queue, ep-\u003enum_tx_queues);\n+\t\tgoto err_drop_skb;\n+\t}\n+\n+\tif (ep-\u003etx_dma_chan_map[queue] == TSN_DMA_CH_INVALID) {\n+\t\tif (net_ratelimit())\n+\t\t\tnetdev_warn(ndev, \"Logical TX queue %d has invalid DMA mapping\\n\",\n+\t\t\t\t queue);\n+\t\tgoto err_drop_skb;\n+\t}\n+\n+\txchan = ep-\u003etx_chans[queue];\n+\n+\tsg_len = skb_shinfo(skb)-\u003enr_frags + 1;\n+\ttxq = netdev_get_tx_queue(ndev, queue);\n+\n+\tspin_lock_bh(\u0026xchan-\u003etx_lock);\n+\tif (CIRC_SPACE(xchan-\u003ering_head, READ_ONCE(xchan-\u003ering_tail),\n+\t\t xchan-\u003ering_size) \u003c= 1) {\n+\t\tnetif_txq_try_stop(txq,\n+\t\t\t\t CIRC_SPACE(xchan-\u003ering_head,\n+\t\t\t\t\t READ_ONCE(xchan-\u003ering_tail),\n+\t\t\t\t\t xchan-\u003ering_size),\n+\t\t\t\t 2);\n+\t\tspin_unlock_bh(\u0026xchan-\u003etx_lock);\n+\t\tif (net_ratelimit())\n+\t\t\tnetdev_warn(ndev, \"TSN TX ring full\\n\");\n+\n+\t\treturn NETDEV_TX_BUSY;\n+\t}\n+\n+\tskbuf_dma = ep_get_desc(xchan, xchan-\u003ering_head \u0026 (xchan-\u003ering_size - 1));\n+\tif (!skbuf_dma) {\n+\t\tspin_unlock_bh(\u0026xchan-\u003etx_lock);\n+\t\tgoto err_drop_skb;\n+\t}\n+\tspin_unlock_bh(\u0026xchan-\u003etx_lock);\n+\n+\tsg_init_table(skbuf_dma-\u003esgl, sg_len);\n+\tret = skb_to_sgvec(skb, skbuf_dma-\u003esgl, 0, skb-\u003elen);\n+\tif (ret \u003c 0)\n+\t\tgoto err_drop_skb;\n+\tsg_len = ret;\n+\n+\tnents = dma_map_sg(xchan-\u003edma_dev, skbuf_dma-\u003esgl, sg_len, DMA_TO_DEVICE);\n+\tif (!nents)\n+\t\tgoto err_drop_skb;\n+\n+\tdma_tx_desc = dmaengine_prep_slave_sg(xchan-\u003echan, skbuf_dma-\u003esgl,\n+\t\t\t\t\t nents, DMA_MEM_TO_DEV,\n+\t\t\t\t\t DMA_PREP_INTERRUPT);\n+\tif (!dma_tx_desc)\n+\t\tgoto err_unmap_sg;\n+\n+\tskbuf_dma-\u003eskb = skb;\n+\tskbuf_dma-\u003esg_len = sg_len;\n+\tdma_tx_desc-\u003ecallback_param = xchan;\n+\tdma_tx_desc-\u003ecallback_result = ep_dma_tx_cb;\n+\n+\tspin_lock_bh(\u0026xchan-\u003etx_lock);\n+\tcookie = dmaengine_submit(dma_tx_desc);\n+\tif (dma_submit_error(cookie)) {\n+\t\tspin_unlock_bh(\u0026xchan-\u003etx_lock);\n+\t\tskbuf_dma-\u003eskb = NULL;\n+\t\tgoto err_unmap_sg;\n+\t}\n+\tWRITE_ONCE(xchan-\u003ering_head, xchan-\u003ering_head + 1);\n+\tnetdev_tx_sent_queue(txq, skb-\u003elen);\n+\tnetif_txq_maybe_stop(txq,\n+\t\t\t CIRC_SPACE(xchan-\u003ering_head,\n+\t\t\t\t\tREAD_ONCE(xchan-\u003ering_tail),\n+\t\t\t\t\txchan-\u003ering_size),\n+\t\t\t 2, 2);\n+\tspin_unlock_bh(\u0026xchan-\u003etx_lock);\n+\n+\tdma_async_issue_pending(xchan-\u003echan);\n+\n+\treturn NETDEV_TX_OK;\n+\n+err_unmap_sg:\n+\tdma_unmap_sg(xchan-\u003edma_dev, skbuf_dma-\u003esgl, sg_len, DMA_TO_DEVICE);\n+err_drop_skb:\n+\tdev_kfree_skb_any(skb);\n+\tDEV_STATS_INC(ndev, tx_dropped);\n+\treturn NETDEV_TX_OK;\n+}\n+\n+static int ep_init_dmaengine(struct xlnx_tsn_ep *ep);\n+static void ep_exit_dmaengine(struct xlnx_tsn_ep *ep);\n+\n+static int ep_open(struct net_device *ndev)\n+{\n+\tstruct xlnx_tsn_ep *ep = netdev_priv(ndev);\n+\tint ret;\n+\n+\tWRITE_ONCE(ep-\u003eclosing, false);\n+\n+\tret = ep_init_dmaengine(ep);\n+\tif (ret) {\n+\t\tnetdev_err(ndev, \"failed to initialize DMA engine\\n\");\n+\t\treturn ret;\n+\t}\n+\n+\tnetif_tx_start_all_queues(ndev);\n+\n+\treturn 0;\n+}\n+\n+static int ep_stop(struct net_device *ndev)\n+{\n+\tstruct xlnx_tsn_ep *ep = netdev_priv(ndev);\n+\tunsigned int i;\n+\n+\tnetif_tx_disable(ndev);\n+\tWRITE_ONCE(ep-\u003eclosing, true);\n+\tep_exit_dmaengine(ep);\n+\tfor (i = 0; i \u003c ndev-\u003enum_tx_queues; i++)\n+\t\tnetdev_tx_reset_subqueue(ndev, i);\n+\n+\treturn 0;\n+}\n+\n+static void ep_get_drvinfo(struct net_device *ndev, struct ethtool_drvinfo *ed)\n+{\n+\tstrscpy(ed-\u003edriver, DRIVER_NAME, sizeof(ed-\u003edriver));\n+}\n+\n+static const struct net_device_ops ep_netdev_ops = {\n+\t.ndo_open\t\t= ep_open,\n+\t.ndo_stop\t\t= ep_stop,\n+\t.ndo_start_xmit\t\t= ep_start_xmit,\n+\t.ndo_validate_addr\t= eth_validate_addr,\n+\t.ndo_set_mac_address\t= eth_mac_addr,\n+};\n+\n+static const struct ethtool_ops ep_ethtool_ops = {\n+\t.get_drvinfo\t= ep_get_drvinfo,\n+};\n+\n+static struct xlnx_tsn_ep_dma_chan *\n+ep_alloc_dma_chan(struct xlnx_tsn_ep *ep, const char *name, bool is_tx,\n+\t\t int ring_size)\n+{\n+\tstruct xlnx_tsn_ep_dma_chan *chan;\n+\tstruct dma_chan *err_chan;\n+\tint i;\n+\n+\tchan = kzalloc_obj(*chan);\n+\tif (!chan)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tchan-\u003echan = dma_request_chan(ep-\u003edev, name);\n+\tif (IS_ERR(chan-\u003echan)) {\n+\t\terr_chan = chan-\u003echan;\n+\t\tkfree(chan);\n+\t\treturn ERR_CAST(err_chan);\n+\t}\n+\n+\tchan-\u003eskb_ring = kcalloc(ring_size, sizeof(*chan-\u003eskb_ring), GFP_KERNEL);\n+\tif (!chan-\u003eskb_ring) {\n+\t\tdma_release_channel(chan-\u003echan);\n+\t\tkfree(chan);\n+\t\treturn ERR_PTR(-ENOMEM);\n+\t}\n+\n+\tfor (i = 0; i \u003c ring_size; i++) {\n+\t\tchan-\u003eskb_ring[i] = kzalloc_obj(*chan-\u003eskb_ring[i]);\n+\t\tif (!chan-\u003eskb_ring[i]) {\n+\t\t\twhile (--i \u003e= 0)\n+\t\t\t\tkfree(chan-\u003eskb_ring[i]);\n+\t\t\tkfree(chan-\u003eskb_ring);\n+\t\t\tdma_release_channel(chan-\u003echan);\n+\t\t\tkfree(chan);\n+\t\t\treturn ERR_PTR(-ENOMEM);\n+\t\t}\n+\t}\n+\n+\tchan-\u003eis_tx = is_tx;\n+\tchan-\u003eep = ep;\n+\tchan-\u003ering_size = ring_size;\n+\tchan-\u003edma_dev = dmaengine_get_dma_device(chan-\u003echan);\n+\tif (is_tx) {\n+\t\tspin_lock_init(\u0026chan-\u003etx_lock);\n+\t} else {\n+\t\tspin_lock_init(\u0026chan-\u003erx_lock);\n+\t\ttimer_setup(\u0026chan-\u003erx_refill_timer, ep_rx_refill_timer, 0);\n+\t}\n+\n+\treturn chan;\n+}\n+\n+static void ep_free_dma_chan(struct xlnx_tsn_ep_dma_chan *chan)\n+{\n+\tint i;\n+\n+\tif (!chan)\n+\t\treturn;\n+\n+\tif (chan-\u003echan) {\n+\t\tif (!chan-\u003eis_tx) {\n+\t\t\t/* ep_stop() sets closing before teardown. Take rx_lock\n+\t\t\t * so any refill that already passed the closing check\n+\t\t\t * finishes and no later one submits or arms the timer,\n+\t\t\t * then shut down the timer so it cannot be rearmed\n+\t\t\t * before the channel is freed.\n+\t\t\t */\n+\t\t\tspin_lock_bh(\u0026chan-\u003erx_lock);\n+\t\t\tspin_unlock_bh(\u0026chan-\u003erx_lock);\n+\t\t\ttimer_shutdown_sync(\u0026chan-\u003erx_refill_timer);\n+\t\t}\n+\n+\t\tdmaengine_terminate_sync(chan-\u003echan);\n+\t}\n+\n+\tif (chan-\u003eis_tx) {\n+\t\twhile (chan-\u003ering_tail != chan-\u003ering_head) {\n+\t\t\tstruct skbuf_dma_descriptor *skbuf_dma;\n+\n+\t\t\tskbuf_dma = chan-\u003eskb_ring[chan-\u003ering_tail \u0026\n+\t\t\t\t\t\t (chan-\u003ering_size - 1)];\n+\t\t\tif (skbuf_dma \u0026\u0026 skbuf_dma-\u003eskb) {\n+\t\t\t\tdma_unmap_sg(chan-\u003edma_dev, skbuf_dma-\u003esgl,\n+\t\t\t\t\t skbuf_dma-\u003esg_len, DMA_TO_DEVICE);\n+\t\t\t\tdev_kfree_skb_any(skbuf_dma-\u003eskb);\n+\t\t\t\tskbuf_dma-\u003eskb = NULL;\n+\t\t\t}\n+\t\t\tchan-\u003ering_tail++;\n+\t\t}\n+\t}\n+\n+\tif (chan-\u003eskb_ring) {\n+\t\tfor (i = 0; i \u003c chan-\u003ering_size; i++) {\n+\t\t\tstruct skbuf_dma_descriptor *skbuf_dma = chan-\u003eskb_ring[i];\n+\n+\t\t\tif (skbuf_dma \u0026\u0026 !chan-\u003eis_tx \u0026\u0026 skbuf_dma-\u003eskb) {\n+\t\t\t\tdma_unmap_single(chan-\u003edma_dev,\n+\t\t\t\t\t\t skbuf_dma-\u003edma_address,\n+\t\t\t\t\t\t chan-\u003eep-\u003emax_frm_size,\n+\t\t\t\t\t\t DMA_FROM_DEVICE);\n+\t\t\t\tdev_kfree_skb_any(skbuf_dma-\u003eskb);\n+\t\t\t}\n+\t\t\tkfree(chan-\u003eskb_ring[i]);\n+\t\t}\n+\t\tkfree(chan-\u003eskb_ring);\n+\t}\n+\tif (chan-\u003echan)\n+\t\tdma_release_channel(chan-\u003echan);\n+\n+\tkfree(chan);\n+}\n+\n+static void ep_exit_dmaengine(struct xlnx_tsn_ep *ep)\n+{\n+\tint i;\n+\n+\tif (ep-\u003etx_chans) {\n+\t\tfor (i = 0; i \u003c ep-\u003enum_tx_queues; i++)\n+\t\t\tep_free_dma_chan(ep-\u003etx_chans[i]);\n+\t\tkfree(ep-\u003etx_chans);\n+\t\tep-\u003etx_chans = NULL;\n+\t}\n+\tif (ep-\u003erx_chans) {\n+\t\tfor (i = 0; i \u003c ep-\u003enum_rx_queues; i++)\n+\t\t\tep_free_dma_chan(ep-\u003erx_chans[i]);\n+\t\tkfree(ep-\u003erx_chans);\n+\t\tep-\u003erx_chans = NULL;\n+\t}\n+}\n+\n+static int ep_init_dmaengine(struct xlnx_tsn_ep *ep)\n+{\n+\tint tx_allocated = 0, rx_allocated = 0;\n+\tchar name[16];\n+\tint i, ret;\n+\n+\tep-\u003etx_chans = kcalloc(ep-\u003enum_tx_queues, sizeof(*ep-\u003etx_chans),\n+\t\t\t GFP_KERNEL);\n+\tif (!ep-\u003etx_chans)\n+\t\treturn -ENOMEM;\n+\n+\tep-\u003erx_chans = kcalloc(ep-\u003enum_rx_queues, sizeof(*ep-\u003erx_chans),\n+\t\t\t GFP_KERNEL);\n+\tif (!ep-\u003erx_chans) {\n+\t\tret = -ENOMEM;\n+\t\tgoto err_free_tx;\n+\t}\n+\n+\tfor (i = 0; i \u003c ep-\u003enum_tx_queues; i++) {\n+\t\tsnprintf(name, sizeof(name), \"tx_chan%u\", ep-\u003etx_dma_chan_map[i]);\n+\t\tep-\u003etx_chans[i] = ep_alloc_dma_chan(ep, name, true,\n+\t\t\t\t\t\t TX_BD_NUM_DEFAULT);\n+\t\tif (IS_ERR(ep-\u003etx_chans[i])) {\n+\t\t\tret = PTR_ERR(ep-\u003etx_chans[i]);\n+\t\t\tep-\u003etx_chans[i] = NULL;\n+\t\t\tgoto err_free_chans;\n+\t\t}\n+\t\ttx_allocated++;\n+\t}\n+\n+\tfor (i = 0; i \u003c ep-\u003enum_rx_queues; i++) {\n+\t\tsnprintf(name, sizeof(name), \"rx_chan%u\", ep-\u003erx_chan_num[i]);\n+\t\tep-\u003erx_chans[i] = ep_alloc_dma_chan(ep, name, false,\n+\t\t\t\t\t\t RX_BD_NUM_DEFAULT);\n+\t\tif (IS_ERR(ep-\u003erx_chans[i])) {\n+\t\t\tret = PTR_ERR(ep-\u003erx_chans[i]);\n+\t\t\tep-\u003erx_chans[i] = NULL;\n+\t\t\tgoto err_free_chans;\n+\t\t}\n+\t\trx_allocated++;\n+\t}\n+\n+\tfor (i = 0; i \u003c ep-\u003enum_rx_queues; i++) {\n+\t\tif (!ep_rx_refill(ep-\u003erx_chans[i], false)) {\n+\t\t\tdev_err(ep-\u003edev, \"RX channel %d: no descriptors armed\\n\",\n+\t\t\t\ti);\n+\t\t\tret = -ENOMEM;\n+\t\t\tgoto err_free_chans;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+\n+err_free_chans:\n+\tWRITE_ONCE(ep-\u003eclosing, true);\n+\twhile (--rx_allocated \u003e= 0)\n+\t\tep_free_dma_chan(ep-\u003erx_chans[rx_allocated]);\n+\twhile (--tx_allocated \u003e= 0)\n+\t\tep_free_dma_chan(ep-\u003etx_chans[tx_allocated]);\n+\tkfree(ep-\u003erx_chans);\n+\tep-\u003erx_chans = NULL;\n+err_free_tx:\n+\tkfree(ep-\u003etx_chans);\n+\tep-\u003etx_chans = NULL;\n+\treturn ret;\n+}\n+\n+static int ep_reset_dma_controller(struct xlnx_tsn_ep *ep)\n+{\n+\tstruct xilinx_vdma_config cfg = { .reset = 1 };\n+\tstruct dma_chan *reset_chan;\n+\tchar name[16];\n+\tint ret;\n+\n+\tsnprintf(name, sizeof(name), \"tx_chan%u\", ep-\u003etx_dma_chan_map[0]);\n+\treset_chan = dma_request_chan(ep-\u003edev, name);\n+\tif (IS_ERR(reset_chan))\n+\t\treturn dev_err_probe(ep-\u003edev, PTR_ERR(reset_chan),\n+\t\t\t\t \"failed to request %s for reset\\n\", name);\n+\n+\tret = xilinx_vdma_channel_set_config(reset_chan, \u0026cfg);\n+\tdma_release_channel(reset_chan);\n+\tif (ret \u003c 0)\n+\t\treturn dev_err_probe(ep-\u003edev, ret,\n+\t\t\t\t \"failed to reset DMA controller\\n\");\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Parse the \"tx-queues-config\" child of the EP node. The logical queue\n+ * index is taken from the \"queue\u003cN\u003e\" node name, so the mapping does not\n+ * depend on the order the child nodes appear in the device tree.\n+ */\n+static int ep_parse_tx_queue_config(struct xlnx_tsn_ep *ep,\n+\t\t\t\t struct device_node *txcfg_np, u16 tx_present)\n+{\n+\tDECLARE_BITMAP(queue_seen, TSN_MAX_TX_QUEUE) = {};\n+\tDECLARE_BITMAP(chan_seen, TSN_MAX_TX_QUEUE) = {};\n+\tunsigned int count = 0;\n+\tint ret;\n+\n+\tfor_each_child_of_node_scoped(txcfg_np, qnode) {\n+\t\tu32 chan, queue;\n+\n+\t\tif (!str_has_prefix(qnode-\u003ename, \"queue\") ||\n+\t\t kstrtou32(qnode-\u003ename + strlen(\"queue\"), 10, \u0026queue) ||\n+\t\t queue \u003e= ep-\u003enum_tx_queues)\n+\t\t\treturn dev_err_probe(ep-\u003edev, -EINVAL,\n+\t\t\t\t\t \"tx-config: invalid queue node %pOFn (have %u queues)\\n\",\n+\t\t\t\t\t qnode, ep-\u003enum_tx_queues);\n+\n+\t\tif (test_and_set_bit(queue, queue_seen))\n+\t\t\treturn dev_err_probe(ep-\u003edev, -EINVAL,\n+\t\t\t\t\t \"tx-config: queue %u described twice\\n\",\n+\t\t\t\t\t queue);\n+\n+\t\tret = of_property_read_u32(qnode, \"xlnx,dma-channel-num\", \u0026chan);\n+\t\tif (ret)\n+\t\t\treturn dev_err_probe(ep-\u003edev, ret,\n+\t\t\t\t\t \"tx-config: queue %u missing xlnx,dma-channel-num\\n\",\n+\t\t\t\t\t queue);\n+\n+\t\tif (chan \u003e= TSN_MAX_TX_QUEUE || !(tx_present \u0026 BIT(chan)))\n+\t\t\treturn dev_err_probe(ep-\u003edev, -EINVAL,\n+\t\t\t\t\t \"tx-config: queue %u maps to channel %u not present in dma-names\\n\",\n+\t\t\t\t\t queue, chan);\n+\n+\t\tif (test_and_set_bit(chan, chan_seen))\n+\t\t\treturn dev_err_probe(ep-\u003edev, -EINVAL,\n+\t\t\t\t\t \"tx-config: channel %u already assigned to another queue\\n\",\n+\t\t\t\t\t chan);\n+\n+\t\tep-\u003etx_dma_chan_map[queue] = chan;\n+\t\tcount++;\n+\t}\n+\n+\tif (count != ep-\u003enum_tx_queues)\n+\t\treturn dev_err_probe(ep-\u003edev, -EINVAL,\n+\t\t\t\t \"tx-config: described %u queues but expected %u\\n\",\n+\t\t\t\t count, ep-\u003enum_tx_queues);\n+\n+\treturn 0;\n+}\n+\n+static int ep_count_dma_queues(struct device *dev, u32 *out_tx, u32 *out_rx,\n+\t\t\t u16 *tx_present, u32 *rx_chan_num)\n+{\n+\tu32 tx = 0, rx = 0;\n+\tu16 rx_present = 0;\n+\tint n, i, ret;\n+\n+\tn = of_property_count_strings(dev-\u003eof_node, \"dma-names\");\n+\tif (n \u003c 0)\n+\t\treturn dev_err_probe(dev, n, \"failed to read dma-names\\n\");\n+\n+\tfor (i = 0; i \u003c n; i++) {\n+\t\tconst char *name;\n+\t\tsize_t plen;\n+\t\tu32 idx;\n+\n+\t\tret = of_property_read_string_index(dev-\u003eof_node, \"dma-names\",\n+\t\t\t\t\t\t i, \u0026name);\n+\t\tif (ret)\n+\t\t\treturn dev_err_probe(dev, ret,\n+\t\t\t\t\t \"failed to read dma-names[%d]\\n\", i);\n+\n+\t\tplen = str_has_prefix(name, \"tx_chan\");\n+\t\tif (plen) {\n+\t\t\tif (kstrtou32(name + plen, 10, \u0026idx) ||\n+\t\t\t idx \u003e= TSN_MAX_TX_QUEUE)\n+\t\t\t\treturn dev_err_probe(dev, -EINVAL,\n+\t\t\t\t\t\t \"invalid TX channel name %s\\n\",\n+\t\t\t\t\t\t name);\n+\n+\t\t\tif (*tx_present \u0026 BIT(idx))\n+\t\t\t\treturn dev_err_probe(dev, -EINVAL,\n+\t\t\t\t\t\t \"duplicate TX channel %s\\n\",\n+\t\t\t\t\t\t name);\n+\n+\t\t\t*tx_present |= BIT(idx);\n+\t\t\ttx++;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tplen = str_has_prefix(name, \"rx_chan\");\n+\t\tif (plen) {\n+\t\t\tif (kstrtou32(name + plen, 10, \u0026idx) ||\n+\t\t\t idx \u003e= TSN_MAX_RX_QUEUE)\n+\t\t\t\treturn dev_err_probe(dev, -EINVAL,\n+\t\t\t\t\t\t \"invalid RX channel name %s\\n\",\n+\t\t\t\t\t\t name);\n+\n+\t\t\tif (rx_present \u0026 BIT(idx))\n+\t\t\t\treturn dev_err_probe(dev, -EINVAL,\n+\t\t\t\t\t\t \"duplicate RX channel %s\\n\",\n+\t\t\t\t\t\t name);\n+\n+\t\t\trx_present |= BIT(idx);\n+\t\t\trx_chan_num[rx] = idx;\n+\t\t\trx++;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\treturn dev_err_probe(dev, -EINVAL,\n+\t\t\t\t \"unrecognised dma-names entry %s\\n\", name);\n+\t}\n+\n+\tif (!tx)\n+\t\treturn dev_err_probe(dev, -EINVAL,\n+\t\t\t\t \"no TX channels in dma-names\\n\");\n+\n+\tif (!rx)\n+\t\treturn dev_err_probe(dev, -EINVAL,\n+\t\t\t\t \"no RX channels in dma-names\\n\");\n+\n+\t*out_tx = tx;\n+\t*out_rx = rx;\n+\n+\treturn 0;\n+}\n+\n+static void ep_free_port_md(struct xlnx_tsn_ep *ep)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i \u003c XLNX_TSN_EP_PORT_MD_SLOTS; i++) {\n+\t\tif (ep-\u003eport_md[i]) {\n+\t\t\tdst_release(\u0026ep-\u003eport_md[i]-\u003edst);\n+\t\t\tep-\u003eport_md[i] = NULL;\n+\t\t}\n+\t}\n+}\n+\n+static int ep_alloc_port_md(struct xlnx_tsn_ep *ep)\n+{\n+\tint i;\n+\n+\tfor (i = TSN_TUSER_PORT_MAC1; i \u003c= TSN_TUSER_PORT_MAC2; i++) {\n+\t\tstruct metadata_dst *md;\n+\n+\t\tmd = metadata_dst_alloc(0, METADATA_HW_PORT_MUX, GFP_KERNEL);\n+\t\tif (!md) {\n+\t\t\tep_free_port_md(ep);\n+\t\t\treturn -ENOMEM;\n+\t\t}\n+\t\tmd-\u003eu.port_info.port_id = i;\n+\t\tep-\u003eport_md[i] = md;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int xlnx_tsn_ep_probe(struct platform_device *pdev)\n+{\n+\tu32 rx_chan_num[TSN_MAX_RX_QUEUE];\n+\tstruct device *dev = \u0026pdev-\u003edev;\n+\tstruct device_node *txcfg_np;\n+\tu32 num_tx, num_rx, num_prio;\n+\tstruct device_node *ip_np;\n+\tstruct net_device *ndev;\n+\tstruct xlnx_tsn_ep *ep;\n+\tu8 mac_addr[ETH_ALEN];\n+\tu16 tx_present = 0;\n+\tint ret;\n+\tint i;\n+\n+\tret = ep_count_dma_queues(dev, \u0026num_tx, \u0026num_rx, \u0026tx_present, rx_chan_num);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tip_np = of_get_parent(dev-\u003eof_node);\n+\tif (!ip_np)\n+\t\treturn dev_err_probe(dev, -EINVAL, \"missing parent IP node\\n\");\n+\n+\tret = of_property_read_u32(ip_np, \"xlnx,num-priorities\", \u0026num_prio);\n+\tof_node_put(ip_np);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret, \"missing xlnx,num-priorities\\n\");\n+\n+\tif (num_tx != num_prio)\n+\t\treturn dev_err_probe(dev, -EINVAL,\n+\t\t\t\t \"TX channel count %u must equal num-priorities %u\\n\",\n+\t\t\t\t num_tx, num_prio);\n+\n+\tndev = alloc_netdev_mqs(sizeof(*ep), \"ep%d\", NET_NAME_ENUM,\n+\t\t\t\tether_setup, num_tx, num_rx);\n+\tif (!ndev)\n+\t\treturn -ENOMEM;\n+\n+\tSET_NETDEV_DEV(ndev, dev);\n+\tndev-\u003enetdev_ops = \u0026ep_netdev_ops;\n+\tndev-\u003eethtool_ops = \u0026ep_ethtool_ops;\n+\tndev-\u003efeatures = NETIF_F_SG;\n+\n+\tep = netdev_priv(ndev);\n+\tep-\u003endev = ndev;\n+\tep-\u003edev = dev;\n+\tep-\u003enum_tx_queues = num_tx;\n+\tep-\u003enum_rx_queues = num_rx;\n+\tep-\u003emax_frm_size = TSN_MAX_VLAN_FRAME_SIZE;\n+\tmemcpy(ep-\u003erx_chan_num, rx_chan_num, num_rx * sizeof(*rx_chan_num));\n+\n+\tfor (i = 0; i \u003c TSN_MAX_TX_QUEUE; i++)\n+\t\tep-\u003etx_dma_chan_map[i] = TSN_DMA_CH_INVALID;\n+\n+\ttxcfg_np = of_get_child_by_name(dev-\u003eof_node, \"tx-queues-config\");\n+\tif (!txcfg_np) {\n+\t\tret = dev_err_probe(dev, -EINVAL,\n+\t\t\t\t \"missing tx-queues-config node\\n\");\n+\t\tgoto err_free_ndev;\n+\t}\n+\tret = ep_parse_tx_queue_config(ep, txcfg_np, tx_present);\n+\tof_node_put(txcfg_np);\n+\tif (ret)\n+\t\tgoto err_free_ndev;\n+\n+\t/*\n+\t * Request one DMA channel at probe time to reset the controller and to\n+\t * gate on the MCDMA provider being bound. This keeps -EPROBE_DEFER in\n+\t * the probe path, so the netdev is only registered once the provider is\n+\t * available and ndo_open never sees a deferral.\n+\t */\n+\tret = ep_reset_dma_controller(ep);\n+\tif (ret)\n+\t\tgoto err_free_ndev;\n+\n+\tret = of_get_mac_address(dev-\u003eof_node, mac_addr);\n+\tif (ret == -EPROBE_DEFER) {\n+\t\tgoto err_free_ndev;\n+\t} else if (!ret \u0026\u0026 is_valid_ether_addr(mac_addr)) {\n+\t\teth_hw_addr_set(ndev, mac_addr);\n+\t} else {\n+\t\teth_hw_addr_random(ndev);\n+\t\tdev_info(dev, \"no valid MAC in DT, using random address %pM\\n\",\n+\t\t\t ndev-\u003edev_addr);\n+\t}\n+\n+\tret = ep_alloc_port_md(ep);\n+\tif (ret) {\n+\t\tdev_err_probe(dev, ret, \"failed to allocate per-port metadata\\n\");\n+\t\tgoto err_free_ndev;\n+\t}\n+\n+\tplatform_set_drvdata(pdev, ep);\n+\n+\tret = register_netdev(ndev);\n+\tif (ret) {\n+\t\tdev_err_probe(dev, ret, \"failed to register net device\\n\");\n+\t\tgoto err_free_md;\n+\t}\n+\n+\treturn 0;\n+\n+err_free_md:\n+\tep_free_port_md(ep);\n+err_free_ndev:\n+\tfree_netdev(ndev);\n+\treturn ret;\n+}\n+\n+static void xlnx_tsn_ep_remove(struct platform_device *pdev)\n+{\n+\tstruct xlnx_tsn_ep *ep = platform_get_drvdata(pdev);\n+\n+\tif (!ep)\n+\t\treturn;\n+\n+\tunregister_netdev(ep-\u003endev);\n+\tep_free_port_md(ep);\n+\tfree_netdev(ep-\u003endev);\n+}\n+\n+static const struct of_device_id xlnx_tsn_ep_of_match[] = {\n+\t{ .compatible = \"xlnx,tsn-ep-mac\" },\n+\t{ }\n+};\n+MODULE_DEVICE_TABLE(of, xlnx_tsn_ep_of_match);\n+\n+struct platform_driver xlnx_tsn_ep_driver = {\n+\t.probe\t= xlnx_tsn_ep_probe,\n+\t.remove\t= xlnx_tsn_ep_remove,\n+\t.driver\t= {\n+\t\t.name\t\t= DRIVER_NAME,\n+\t\t.of_match_table\t= xlnx_tsn_ep_of_match,\n+\t},\n+};\ndiff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c\nnew file mode 100644\nindex 0000000000000..d33a00dd6d15e\n--- /dev/null\n+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c\n@@ -0,0 +1,107 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+/*\n+ * Time Sensitive Networking (TSN) Ethernet MAC wrapper driver.\n+ *\n+ * Copyright (C) 2026 Advanced Micro Devices, Inc.\n+ */\n+\n+#include \u003clinux/clk.h\u003e\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/of.h\u003e\n+#include \u003clinux/of_platform.h\u003e\n+#include \u003clinux/platform_device.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+#include \"xilinx_tsn.h\"\n+\n+#define TSN_NUM_CLOCKS\t\t6\n+\n+/**\n+ * struct xlnx_tsn_ip - wrapper-private IP state\n+ * @clks: bulk-managed IP clocks\n+ */\n+struct xlnx_tsn_ip {\n+\tstruct clk_bulk_data clks[TSN_NUM_CLOCKS];\n+};\n+\n+static const char * const tsn_clk_names[TSN_NUM_CLOCKS] = {\n+\t\"gtx\",\n+\t\"gtx90\",\n+\t\"host_rxfifo\",\n+\t\"host_txfifo\",\n+\t\"ref\",\n+\t\"s_axi\",\n+};\n+\n+static void tsn_clk_bulk_disable(void *data)\n+{\n+\tstruct xlnx_tsn_ip *w = data;\n+\n+\tclk_bulk_disable_unprepare(TSN_NUM_CLOCKS, w-\u003eclks);\n+}\n+\n+static int tsn_ip_probe(struct platform_device *pdev)\n+{\n+\tstruct device *dev = \u0026pdev-\u003edev;\n+\tstruct xlnx_tsn_ip *w;\n+\tint ret;\n+\n+\tw = devm_kzalloc(dev, sizeof(*w), GFP_KERNEL);\n+\tif (!w)\n+\t\treturn -ENOMEM;\n+\n+\tfor (int i = 0; i \u003c TSN_NUM_CLOCKS; i++)\n+\t\tw-\u003eclks[i].id = tsn_clk_names[i];\n+\n+\tret = devm_clk_bulk_get(dev, TSN_NUM_CLOCKS, w-\u003eclks);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret, \"failed to get clocks\\n\");\n+\n+\tret = clk_bulk_prepare_enable(TSN_NUM_CLOCKS, w-\u003eclks);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret, \"failed to enable clocks\\n\");\n+\n+\tret = devm_add_action_or_reset(dev, tsn_clk_bulk_disable, w);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn devm_of_platform_populate(dev);\n+}\n+\n+static const struct of_device_id tsn_of_match[] = {\n+\t{ .compatible = \"xlnx,tsn-endpoint-ethernet-mac-3.0\" },\n+\t{ }\n+};\n+MODULE_DEVICE_TABLE(of, tsn_of_match);\n+\n+static struct platform_driver tsn_driver = {\n+\t.probe = tsn_ip_probe,\n+\t.driver = {\n+\t\t.name = \"xilinx-tsn\",\n+\t\t.of_match_table = tsn_of_match,\n+\t},\n+};\n+\n+static struct platform_driver * const tsn_drivers[] = {\n+\t\u0026tsn_driver,\n+\t\u0026xlnx_tsn_ep_driver,\n+};\n+\n+static int __init xlnx_tsn_init(void)\n+{\n+\treturn platform_register_drivers(tsn_drivers, ARRAY_SIZE(tsn_drivers));\n+}\n+module_init(xlnx_tsn_init);\n+\n+static void __exit xlnx_tsn_exit(void)\n+{\n+\tplatform_unregister_drivers(tsn_drivers, ARRAY_SIZE(tsn_drivers));\n+}\n+module_exit(xlnx_tsn_exit);\n+\n+MODULE_AUTHOR(\"Srinivas Neeli \u003csrinivas.neeli@amd.com\u003e\");\n+MODULE_DESCRIPTION(\"AMD/Xilinx TSN Endpoint Ethernet MAC wrapper driver\");\n+MODULE_LICENSE(\"GPL\");\n"
} |
| 2/1 | 2026/09/08 22:17 | agent | patch-evaluator |
0mModel:core Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch introduces a new platform driver and Device Tree bindings for the AMD/Xilinx TSN Endpoint Ethernet MAC IP core (drivers/net/ethernet/xilinx/tsn/). This driver is designed for vendor-specific FPGA/SoC hardware and is structurally unreachable in standard virtualized fuzzing environments (QEMU/GCE) on amd64.",
"WorthFuzzing": false
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit b7dadee0b63edcdc9353e4b61f84fea4d43cb325
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Sep 8 22:17:34 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml b/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
new file mode 100644
index 0000000000000..4e65d1710da78
--- /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 b23fb6f2f4ef4..ff94e314eeb1f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29911,6 +29911,14 @@ 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
+F: drivers/net/ethernet/xilinx/tsn/
+
XILINX UARTLITE SERIAL DRIVER
M: Peter Korsgaard <jacmet@sunsite.dk>
L: linux-serial@vger.kernel.org
diff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig
index 7502214cc7d50..c6d704c8d3d46 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 7d7dc1771423e..66dab012650b3 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 0000000000000..292f176944042
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Xilinx TSN device configuration
+#
+
+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
+ 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 0000000000000..5886828b386f4
--- /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 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 0000000000000..b0757e22d1fd1
--- /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 0000000000000..db7da26521d01
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
@@ -0,0 +1,1034 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD/Xilinx TSN Endpoint MAC driver.
+ *
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/circ_buf.h>
+#include <linux/dma/xilinx_dma.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#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/timer.h>
+#include <linux/types.h>
+
+#include <net/dsa.h>
+#include <net/dst_metadata.h>
+#include <net/netdev_queues.h>
+
+#include "xilinx_tsn.h"
+
+#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)
+
+#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
+
+/* 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
+ * @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
+ * @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
+ */
+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;
+ 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;
+};
+
+/**
+ * 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
+ * @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;
+ 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;
+
+ struct xlnx_tsn_ep_dma_chan **tx_chans;
+ 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 *
+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);
+ 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;
+ __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 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;
+}
+
+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;
+}
+
+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;
+}
+
+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 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);
+ 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);
+ }
+
+ return chan;
+}
+
+static void ep_free_dma_chan(struct xlnx_tsn_ep_dma_chan *chan)
+{
+ int i;
+
+ if (!chan)
+ return;
+
+ 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) {
+ 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++;
+ }
+
+ 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)
+ 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
+ * 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 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];
+ 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_mqs(sizeof(*ep), "ep%d", NET_NAME_ENUM,
+ ether_setup, num_tx, num_rx);
+ 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;
+ 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;
+
+ /*
+ * 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;
+ } 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);
+ }
+
+ 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_md;
+ }
+
+ return 0;
+
+err_free_md:
+ ep_free_port_md(ep);
+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);
+ ep_free_port_md(ep);
+ 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
new file mode 100644
index 0000000000000..d33a00dd6d15e
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
@@ -0,0 +1,107 @@
+// 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>
+
+#include "xilinx_tsn.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,
+ &xlnx_tsn_ep_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");
|
| 3/2 | 2026/09/08 22:17 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 22023 output: 21 thoughts: 705 Thoughts: **Fuzzing Feasibility Assessment: Xilinx TSN Ethernet Driver**
Okay, let's break this down. My initial assessment is focused on whether this patch, introducing the Xilinx TSN Ethernet driver, is a good candidate for syzkaller fuzzing.
First, I need to understand the scope of the changes. I see documentation updates (`.yaml` and `MAINTAINERS`), Kconfig and Makefile additions indicating a new `tsn/` subdirectory, and the core driver files (`.c` and `.h`). This suggests a new functional block, which is promising from a fuzzing perspective.
Now, let's get into the nitty-gritty. This is a platform driver, specifically designed for OpenFirmware/Devicetree ("xlnx,tsn-endpoint-ethernet-mac-3.0" and "xlnx,tsn-ep-mac"). It's *definitely* interesting, but my alarm bells are going off because it depends on `OF_ADDRESS` and `XILINX_DMA`. That's the key.
My immediate question is reachability within standard syzkaller environments. Can I trigger the `xlnx_tsn_ep_driver` or `tsn_driver` in QEMU (amd64) in GCE? Does QEMU emulate Xilinx TSN MAC or Xilinx DMA? Or is this, as I suspect, hardware-dependent, possibly an FPGA/ZynqMP SoC-specific component? If it's the latter, we're likely dealing with a significant hurdle.
I need to explore how platform devices, especially those DT-based, behave in x86_64 environments. Can Device Tree overlays instantiate this? Will I need to emulate the platform or use a dedicated hardware setup? I need to check how other Xilinx Ethernet drivers, especially those OF- or DT-based, are handled. Has anyone successfully fuzzed similar drivers?
I need to apply the negative criteria. Is this Structurally unreachable hardware? I need to know if the hardware isn't emulated or if the hardware is necessary. Looking at the definition, "vendor-specific...requiring physical ASIC/PCIe cards not emulated in standard QEMU" makes me worried. I am concerned this is similar to that. I also need to make sure, this isn't an "unreachable execution path", i.e. that it isn't only called during physical hot-unplug.
My next steps involve grepping the kernel source for `XILINX_DMA`, `OF_ADDRESS`, and other Xilinx Ethernet driver implementations, and then checking what's feasible given the typical syzkaller limitations. Is it a *go* or a *no-go*?
|
| 4/2 | 2026/09/08 22:17 | tool | grepper |
0mArgs:{
"Expression": "config XILINX_DMA"
}Results: {
"Output": "drivers/dma/Kconfig=674=config XGENE_DMA\n--\ndrivers/dma/Kconfig-682-\ndrivers/dma/Kconfig:683:config XILINX_DMA\ndrivers/dma/Kconfig-684-\ttristate \"Xilinx AXI DMAS Engine\"\n"
} |
| 5/2 | 2026/09/08 22:17 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 23434 output: 35 thoughts: 20 Thoughts: Here's my take on those thoughts: **Investigating a DMA Configuration Concern** Okay, time to dig into the DMA configuration. I'm thinking the logical starting point is the `drivers/dma/Kconfig` file. I'm specifically interested in what's going on around line 683. That area likely holds some crucial definitions or dependencies related to whatever DMA issue I'm dealing with. Let's see what's being conditionally compiled or configured there. I bet it will provide the key! |
| 6/2 | 2026/09/08 22:17 | tool | read-file |
0mArgs:{
"File": "drivers/dma/Kconfig",
"FirstLine": 680,
"LineCount": 25
}Results: {
"Contents": " 680:\t\thelp\n 681:\t\t Enable support for the APM X-Gene SoC DMA engine.\n 682:\t\n 683:\tconfig XILINX_DMA\n 684:\t\ttristate \"Xilinx AXI DMAS Engine\"\n 685:\t\tdepends on HAS_IOMEM\n 686:\t\tselect DMA_ENGINE\n 687:\t\thelp\n 688:\t\t Enable support for Xilinx AXI VDMA Soft IP.\n 689:\t\n 690:\t\t AXI VDMA engine provides high-bandwidth direct memory access\n 691:\t\t between memory and AXI4-Stream video type target\n 692:\t\t peripherals including peripherals which support AXI4-\n 693:\t\t Stream Video Protocol. It has two stream interfaces/\n 694:\t\t channels, Memory Mapped to Stream (MM2S) and Stream to\n 695:\t\t Memory Mapped (S2MM) for the data transfers.\n 696:\t\t AXI CDMA engine provides high-bandwidth direct memory access\n 697:\t\t between a memory-mapped source address and a memory-mapped\n 698:\t\t destination address.\n 699:\t\t AXI DMA engine provides high-bandwidth one dimensional direct\n 700:\t\t memory access between memory and AXI4-Stream target peripherals.\n 701:\t\t AXI MCDMA engine provides high-bandwidth direct memory access\n 702:\t\t between memory and AXI4-Stream target peripherals. It provides\n 703:\t\t the scatter gather interface with multiple channels independent\n 704:\t\t configuration support.\n"
} |
| 7/2 | 2026/09/08 22:17 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 24022 output: 18 thoughts: 46 Thoughts: **Checking the Configuration Landscape** Okay, let's dive into this. My initial thought is to confirm the status of `CONFIG_OF_ADDRESS`. Can we realistically enable `CONFIG_OF_ADDRESS` on an x86_64 architecture? That's the core question. To answer that definitively, I need to know where `OF_ADDRESS` is defined within the Kconfig configuration system. I need to trace its origins and dependencies to fully understand its applicability in this context. Let's get to the root of its definition. |
| 8/2 | 2026/09/08 22:17 | tool | grepper |
0mArgs:{
"Expression": "config OF_ADDRESS"
}Results: {
"Output": "drivers/of/Kconfig=83=config OF_DYNAMIC\n--\ndrivers/of/Kconfig-90-\ndrivers/of/Kconfig:91:config OF_ADDRESS\ndrivers/of/Kconfig-92-\tdef_bool y\n"
} |
| 9/2 | 2026/09/08 22:17 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 24270 output: 33 thoughts: 18 Thoughts: Here's a summary of my thought process, as requested: **Navigating the Kconfig Labyrinth** Okay, time to dive in. I need to take a look at the `drivers/of/Kconfig` file. I'm specifically interested in something around line 91, or at least in that vicinity. Given my expertise, I suspect something related to device tree configuration or potentially a specific driver's build options might be defined there. It's a hunch based on common patterns in embedded systems development, but I need to confirm it. Let's see what lies within those lines. |
| 10/2 | 2026/09/08 22:17 | tool | read-file |
0mArgs:{
"File": "drivers/of/Kconfig",
"FirstLine": 80,
"LineCount": 30
}Results: {
"Contents": " 80:\t\n 81:\t# Hardly any platforms need this. It is safe to select, but only do so if you\n 82:\t# need it.\n 83:\tconfig OF_DYNAMIC\n 84:\t\tbool \"Support for dynamic device trees\" if OF_UNITTEST\n 85:\t\tselect OF_KOBJ\n 86:\t\thelp\n 87:\t\t On some platforms, the device tree can be manipulated at runtime.\n 88:\t\t While this option is selected automatically on such platforms, you\n 89:\t\t can enable it manually to improve device tree unit test coverage.\n 90:\t\n 91:\tconfig OF_ADDRESS\n 92:\t\tdef_bool y\n 93:\t\tdepends on !SPARC \u0026\u0026 (HAS_IOMEM || UML)\n 94:\t\n 95:\tconfig OF_IRQ\n 96:\t\tdef_bool y\n 97:\t\tdepends on !SPARC \u0026\u0026 IRQ_DOMAIN\n 98:\t\n 99:\tconfig OF_RESERVED_MEM\n 100:\t\tdef_bool OF_EARLY_FLATTREE\n 101:\t\n 102:\tconfig OF_RESOLVE\n 103:\t\tbool\n 104:\t\n 105:\tconfig OF_OVERLAY\n 106:\t\tbool \"Device Tree overlays\"\n 107:\t\tselect OF_DYNAMIC\n 108:\t\tselect OF_FLATTREE\n 109:\t\tselect OF_RESOLVE\n"
} |
| 11/2 | 2026/09/08 22:17 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 24829 output: 107 thoughts: 1228 Thoughts: **WorthFuzzing Assessment: Negative** Okay, let's break this down. My initial thought is to determine if this `xlnx_tsn` component is something we can effectively fuzz. First, is it a platform driver specific to hardware? Yes, it looks like this is an AMD/Xilinx TSN Endpoint Ethernet MAC IP core on an FPGA or SoC. So, it definitely requires specific hardware: an FPGA and related IP blocks, including Xilinx's MCDMA. I need to understand exactly what the driver does. From the code, it probes for clocks and populates subdevices and also requests and configures DMA channels, interacting with the Xilinx VDMA/MCDMA. Now, the critical question: can this hardware be emulated in our standard syzkaller environments, specifically QEMU or GCE VMs on amd64? No. There's no emulation of this Xilinx-specific hardware, like the TSN FPGA MAC or MCDMA, in those environments. Let's consult the syzkaller criteria regarding when *not* to fuzz. It's clear from the documentation that vendor-specific drivers requiring physical hardware not emulated in QEMU fall under the "Structurally unreachable hardware" category and that such drivers shouldn't be fuzzed. This is a very important point! So, does this driver interact with any virtual buses? No. It's a platform driver that relies on the specific hardware provided by Xilinx. It's not reachable via virtio, USB gadget, loopback, netlink, binder, or sockets. I need to confirm that this is the sole extent of the change. The documentation changes look like documentation, Kconfig, and Makefile updates alongside the new driver files: `xlnx_tsn.h`, `xlnx_tsn_ep.c`, `xlnx_tsn_main.c`. Everything is contained within the `drivers/net/ethernet/xilinx/tsn/` directory, including Device Tree bindings. Therefore, since this driver is for platform-specific hardware unavailable in standard fuzzing environments, it's structurally unreachable. The modified code is vendor-specific and requires an FPGA/SoC. Thus, this code is not worth fuzzing. I will set `WorthFuzzing` to `false` and provide the reasoning. I will set the fields required by `set-results` as: - `WorthFuzzing`: false - `Reasoning`: The patch introduces a new device driver and Device Tree bindings for the AMD/Xilinx TSN (Time-Sensitive Networking) Endpoint Ethernet MAC IP core (`drivers/net/ethernet/xilinx/tsn/`). This driver targets vendor-specific FPGA/SoC hardware that is not emulated in standard virtualized environments (QEMU/GCE) on amd64, making the modified code structurally unreachable for fuzzing. - `FocusSymbols`: [] (empty list) - `EnableConfigs`: [] (empty list) Now, let's invoke `set-results`. |
| 12/2 | 2026/09/08 22:17 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch introduces a new platform driver and Device Tree bindings for the AMD/Xilinx TSN Endpoint Ethernet MAC IP core (drivers/net/ethernet/xilinx/tsn/). This driver is designed for vendor-specific FPGA/SoC hardware and is structurally unreachable in standard virtualized fuzzing environments (QEMU/GCE) on amd64.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch introduces a new platform driver and Device Tree bindings for the AMD/Xilinx TSN Endpoint Ethernet MAC IP core (drivers/net/ethernet/xilinx/tsn/). This driver is designed for vendor-specific FPGA/SoC hardware and is structurally unreachable in standard virtualized fuzzing environments (QEMU/GCE) on amd64.",
"WorthFuzzing": false
} |
| 13/1 | 2026/09/08 22:17 | action | If |
0mArgs:{
"WorthFuzzing": false
}Results: null |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|