[PATCH net-next v2 4/8] net: xilinx: tsn: parse endpoint DMA channel configuration

Srinivas Neeli srinivas.neeli at amd.com
Tue Sep 8 12:19:52 PDT 2026


The endpoint has one MCDMA channel per TX queue and per RX queue, and
the device tree lists them in "dma-names". The transmit path needs to
know which physical DMA channel backs each logical TX queue.

Count the TX and RX channels in "dma-names" and allocate the conduit
netdev with that many hardware queues. Parse the "tx-queues-config"
child node to map each logical TX queue to its physical DMA channel.

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

---
Changes in v2:
- Check xlnx,dma-channel-num against the channels that dma-names actually
  lists, using a presence bitmask. Reject duplicates and out-of-range channels,
  and index the channel arrays by logical queue.
- Require one queue node per tx_chan and num-priorities equal to the TX channel
  count. This also gives xlnx,num-priorities a consumer.
- Cap TX and RX at 8 to match the binding.
- Error out on an unknown dma-names entry instead of skipping it.
- Drop the unreachable TX and RX count guards. The in-loop index and duplicate
  checks already bound the count, so the channel arrays cannot be over-indexed.
---
 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c | 197 +++++++++++++++++++++++-
 1 file changed, 196 insertions(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
index 089f17a126f5..24025b1f6e66 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
@@ -5,9 +5,11 @@
  * Copyright (C) 2026 Advanced Micro Devices, Inc.
  */
 
+#include <linux/bitops.h>
 #include <linux/etherdevice.h>
 #include <linux/ethtool.h>
 #include <linux/if_ether.h>
+#include <linux/if_vlan.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/netdevice.h>
@@ -21,14 +23,31 @@
 
 #define DRIVER_NAME			"xilinx_tsn_ep"
 
+#define TSN_DMA_CH_INVALID		0xFFU
+#define TSN_MAX_TX_QUEUE		8
+#define TSN_MAX_RX_QUEUE		8
+
+#define TSN_MAX_VLAN_FRAME_SIZE		(ETH_DATA_LEN + VLAN_ETH_HLEN + \
+					 ETH_FCS_LEN)
+
 /**
  * struct xlnx_tsn_ep - EP MAC private data, embedded in net_device priv area
  * @ndev: the conduit netdev ("ep0" for the first IP instance)
  * @dev: backing device
+ * @num_tx_queues: number of TX DMA channels (one per priority)
+ * @num_rx_queues: number of RX DMA channels
+ * @tx_dma_chan_map: logical TX queue index -> physical DMA channel number
+ * @rx_chan_num: RX ring index -> physical DMA channel number
+ * @max_frm_size: maximum frame size accepted on RX
  */
 struct xlnx_tsn_ep {
 	struct net_device *ndev;
 	struct device *dev;
+	u32 num_tx_queues;
+	u32 num_rx_queues;
+	u32 tx_dma_chan_map[TSN_MAX_TX_QUEUE];
+	u32 rx_chan_num[TSN_MAX_RX_QUEUE];
+	u32 max_frm_size;
 };
 
 static netdev_tx_t ep_start_xmit(struct sk_buff *skb, struct net_device *ndev)
@@ -69,15 +88,173 @@ static const struct ethtool_ops ep_ethtool_ops = {
 	.get_drvinfo	= ep_get_drvinfo,
 };
 
+/*
+ * Parse the "tx-queues-config" child of the EP node. The logical queue
+ * index is taken from the "queue<N>" node name, so the mapping does not
+ * depend on the order the child nodes appear in the device tree.
+ */
+static int ep_parse_tx_queue_config(struct xlnx_tsn_ep *ep,
+				    struct device_node *txcfg_np, u16 tx_present)
+{
+	DECLARE_BITMAP(queue_seen, TSN_MAX_TX_QUEUE) = {};
+	DECLARE_BITMAP(chan_seen, TSN_MAX_TX_QUEUE) = {};
+	unsigned int count = 0;
+	int ret;
+
+	for_each_child_of_node_scoped(txcfg_np, qnode) {
+		u32 chan, queue;
+
+		if (!str_has_prefix(qnode->name, "queue") ||
+		    kstrtou32(qnode->name + strlen("queue"), 10, &queue) ||
+		    queue >= ep->num_tx_queues)
+			return dev_err_probe(ep->dev, -EINVAL,
+					     "tx-config: invalid queue node %pOFn (have %u queues)\n",
+					     qnode, ep->num_tx_queues);
+
+		if (test_and_set_bit(queue, queue_seen))
+			return dev_err_probe(ep->dev, -EINVAL,
+					     "tx-config: queue %u described twice\n",
+					     queue);
+
+		ret = of_property_read_u32(qnode, "xlnx,dma-channel-num", &chan);
+		if (ret)
+			return dev_err_probe(ep->dev, ret,
+					     "tx-config: queue %u missing xlnx,dma-channel-num\n",
+					     queue);
+
+		if (chan >= TSN_MAX_TX_QUEUE || !(tx_present & BIT(chan)))
+			return dev_err_probe(ep->dev, -EINVAL,
+					     "tx-config: queue %u maps to channel %u not present in dma-names\n",
+					     queue, chan);
+
+		if (test_and_set_bit(chan, chan_seen))
+			return dev_err_probe(ep->dev, -EINVAL,
+					     "tx-config: channel %u already assigned to another queue\n",
+					     chan);
+
+		ep->tx_dma_chan_map[queue] = chan;
+		count++;
+	}
+
+	if (count != ep->num_tx_queues)
+		return dev_err_probe(ep->dev, -EINVAL,
+				     "tx-config: described %u queues but expected %u\n",
+				     count, ep->num_tx_queues);
+
+	return 0;
+}
+
+static int ep_count_dma_queues(struct device *dev, u32 *out_tx, u32 *out_rx,
+			       u16 *tx_present, u32 *rx_chan_num)
+{
+	u32 tx = 0, rx = 0;
+	u16 rx_present = 0;
+	int n, i, ret;
+
+	n = of_property_count_strings(dev->of_node, "dma-names");
+	if (n < 0)
+		return dev_err_probe(dev, n, "failed to read dma-names\n");
+
+	for (i = 0; i < n; i++) {
+		const char *name;
+		size_t plen;
+		u32 idx;
+
+		ret = of_property_read_string_index(dev->of_node, "dma-names",
+						    i, &name);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "failed to read dma-names[%d]\n", i);
+
+		plen = str_has_prefix(name, "tx_chan");
+		if (plen) {
+			if (kstrtou32(name + plen, 10, &idx) ||
+			    idx >= TSN_MAX_TX_QUEUE)
+				return dev_err_probe(dev, -EINVAL,
+						     "invalid TX channel name %s\n",
+						     name);
+
+			if (*tx_present & BIT(idx))
+				return dev_err_probe(dev, -EINVAL,
+						     "duplicate TX channel %s\n",
+						     name);
+
+			*tx_present |= BIT(idx);
+			tx++;
+			continue;
+		}
+
+		plen = str_has_prefix(name, "rx_chan");
+		if (plen) {
+			if (kstrtou32(name + plen, 10, &idx) ||
+			    idx >= TSN_MAX_RX_QUEUE)
+				return dev_err_probe(dev, -EINVAL,
+						     "invalid RX channel name %s\n",
+						     name);
+
+			if (rx_present & BIT(idx))
+				return dev_err_probe(dev, -EINVAL,
+						     "duplicate RX channel %s\n",
+						     name);
+
+			rx_present |= BIT(idx);
+			rx_chan_num[rx] = idx;
+			rx++;
+			continue;
+		}
+
+		return dev_err_probe(dev, -EINVAL,
+				     "unrecognised dma-names entry %s\n", name);
+	}
+
+	if (!tx)
+		return dev_err_probe(dev, -EINVAL,
+				     "no TX channels in dma-names\n");
+
+	if (!rx)
+		return dev_err_probe(dev, -EINVAL,
+				     "no RX channels in dma-names\n");
+
+	*out_tx = tx;
+	*out_rx = rx;
+
+	return 0;
+}
+
 static int xlnx_tsn_ep_probe(struct platform_device *pdev)
 {
+	u32 rx_chan_num[TSN_MAX_RX_QUEUE];
 	struct device *dev = &pdev->dev;
+	struct device_node *txcfg_np;
+	u32 num_tx, num_rx, num_prio;
+	struct device_node *ip_np;
 	struct net_device *ndev;
 	struct xlnx_tsn_ep *ep;
 	u8 mac_addr[ETH_ALEN];
+	u16 tx_present = 0;
 	int ret;
+	int i;
+
+	ret = ep_count_dma_queues(dev, &num_tx, &num_rx, &tx_present, rx_chan_num);
+	if (ret)
+		return ret;
+
+	ip_np = of_get_parent(dev->of_node);
+	if (!ip_np)
+		return dev_err_probe(dev, -EINVAL, "missing parent IP node\n");
+
+	ret = of_property_read_u32(ip_np, "xlnx,num-priorities", &num_prio);
+	of_node_put(ip_np);
+	if (ret)
+		return dev_err_probe(dev, ret, "missing xlnx,num-priorities\n");
+
+	if (num_tx != num_prio)
+		return dev_err_probe(dev, -EINVAL,
+				     "TX channel count %u must equal num-priorities %u\n",
+				     num_tx, num_prio);
 
-	ndev = alloc_netdev(sizeof(*ep), "ep%d", NET_NAME_ENUM, ether_setup);
+	ndev = alloc_netdev_mqs(sizeof(*ep), "ep%d", NET_NAME_ENUM,
+				ether_setup, num_tx, num_rx);
 	if (!ndev)
 		return -ENOMEM;
 
@@ -89,6 +266,24 @@ static int xlnx_tsn_ep_probe(struct platform_device *pdev)
 	ep = netdev_priv(ndev);
 	ep->ndev = ndev;
 	ep->dev = dev;
+	ep->num_tx_queues = num_tx;
+	ep->num_rx_queues = num_rx;
+	ep->max_frm_size = TSN_MAX_VLAN_FRAME_SIZE;
+	memcpy(ep->rx_chan_num, rx_chan_num, num_rx * sizeof(*rx_chan_num));
+
+	for (i = 0; i < TSN_MAX_TX_QUEUE; i++)
+		ep->tx_dma_chan_map[i] = TSN_DMA_CH_INVALID;
+
+	txcfg_np = of_get_child_by_name(dev->of_node, "tx-queues-config");
+	if (!txcfg_np) {
+		ret = dev_err_probe(dev, -EINVAL,
+				    "missing tx-queues-config node\n");
+		goto err_free_ndev;
+	}
+	ret = ep_parse_tx_queue_config(ep, txcfg_np, tx_present);
+	of_node_put(txcfg_np);
+	if (ret)
+		goto err_free_ndev;
 
 	ret = of_get_mac_address(dev->of_node, mac_addr);
 	if (ret == -EPROBE_DEFER) {

-- 
2.43.0




More information about the linux-arm-kernel mailing list