* [RFC 0/4] net/mcdma: add Altera MCDMA poll mode driver
@ 2026-11-09 16:09 Subhransu S. Prusty
2026-11-09 16:09 ` [RFC 1/4] net/mcdma: add Altera MCDMA poll mode driver skeleton Subhransu S. Prusty
` (4 more replies)
0 siblings, 5 replies; 6+ messages in thread
From: Subhransu S. Prusty @ 2026-11-09 16:09 UTC (permalink / raw)
To: dev
Cc: ferruh.yigit, stephen, rtice, cheryl.bansal, nanditha.jayarajan,
Subhransu S. Prusty
This RFC series introduces a poll mode driver for Altera Multi-Channel
Direct Memory Access (MCDMA) PCI devices. The driver exposes
host-to-device and device-to-host descriptor queues as standard DPDK
Ethernet ports for custom FPGA datapaths.
While the MCDMA IP supports multiple PFs and VFs per PF, this RFC
implements single PF with multiple VFs; each function exposes multiple
Rx/Tx queue pairs backed by H2D/D2H descriptor rings and per-queue
QCSR.
User guide reference:
https://docs.altera.com/r/docs/683821/25.3.1/multi-channel-dma-ip-for-pci-express-user-guide/before-you-begin
The series is split for review as follows:
1. Core driver skeleton, queue management, documentation, and features
matrix, excluding datapath bursts, debug dumps, and PIO APIs.
2. Rx and Tx packet burst handlers.
3. Experimental PIO BAR access APIs.
4. Experimental QCSR inspection and dump APIs.
Functional testing was performed on Linux with vfio-pci, using DPDK
pktgen on a single PF and VF configuration. The attached user logic
was adapted as needed for validation.
We would appreciate feedback on the ethdev design, the experimental
PIO APIs, and PF/VF devargs.
Subhransu S. Prusty (4):
net/mcdma: add Altera MCDMA poll mode driver skeleton
net/mcdma: add Rx and Tx packet burst paths
net/mcdma: add experimental PIO access APIs
net/mcdma: add experimental QCSR dump APIs
MAINTAINERS | 4 +
doc/guides/nics/features/mcdma.ini | 17 +
doc/guides/nics/index.rst | 1 +
doc/guides/nics/mcdma.rst | 147 +++
drivers/net/altera/mcdma/mcdma_config_csr.h | 201 +++++
drivers/net/altera/mcdma/mcdma_eth.c | 323 +++++++
drivers/net/altera/mcdma/mcdma_eth.h | 335 +++++++
drivers/net/altera/mcdma/mcdma_eth_ops.c | 933 ++++++++++++++++++++
drivers/net/altera/mcdma/mcdma_eth_rxtx.c | 401 +++++++++
drivers/net/altera/mcdma/meson.build | 16 +
drivers/net/altera/mcdma/rte_pmd_mcdma.c | 284 ++++++
drivers/net/altera/mcdma/rte_pmd_mcdma.h | 140 +++
drivers/net/meson.build | 1 +
usertools/dpdk-devbind.py | 1 +
14 files changed, 2804 insertions(+)
create mode 100644 doc/guides/nics/features/mcdma.ini
create mode 100644 doc/guides/nics/mcdma.rst
create mode 100644 drivers/net/altera/mcdma/mcdma_config_csr.h
create mode 100644 drivers/net/altera/mcdma/mcdma_eth.c
create mode 100644 drivers/net/altera/mcdma/mcdma_eth.h
create mode 100644 drivers/net/altera/mcdma/mcdma_eth_ops.c
create mode 100644 drivers/net/altera/mcdma/mcdma_eth_rxtx.c
create mode 100644 drivers/net/altera/mcdma/meson.build
create mode 100644 drivers/net/altera/mcdma/rte_pmd_mcdma.c
create mode 100644 drivers/net/altera/mcdma/rte_pmd_mcdma.h
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread
* [RFC 1/4] net/mcdma: add Altera MCDMA poll mode driver skeleton
2026-11-09 16:09 [RFC 0/4] net/mcdma: add Altera MCDMA poll mode driver Subhransu S. Prusty
@ 2026-11-09 16:09 ` Subhransu S. Prusty
2026-11-09 16:09 ` [RFC 2/4] net/mcdma: add Rx and Tx packet burst paths Subhransu S. Prusty
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Subhransu S. Prusty @ 2026-11-09 16:09 UTC (permalink / raw)
To: dev
Cc: ferruh.yigit, stephen, rtice, cheryl.bansal, nanditha.jayarajan,
Subhransu S. Prusty
Add the initial poll mode driver for Altera Multi-Channel Direct Memory
Access (MCDMA). The PMD registers with the ethdev layer, maps
configuration (and optional PIO) BARs, parses PF/VF devargs, and
implements port configuration, Rx/Tx queue setup and start/stop,
link status, MTU, and statistics.
Introduce DMA CSR and QCSR register definitions, device- and queue-level
private data structures, and driver documentation with the features matrix.
Packet burst handlers and experimental APIs to access user logic and
debug dump are added in follow-up patches.
Co-developed-by: Cheryl Bansal <cheryl.bansal@altera.com>
Signed-off-by: Cheryl Bansal <cheryl.bansal@altera.com>
Signed-off-by: Subhransu S. Prusty <subhransu.sekhar.prusty@altera.com>
---
MAINTAINERS | 4 +
doc/guides/nics/features/mcdma.ini | 17 +
doc/guides/nics/index.rst | 1 +
doc/guides/nics/mcdma.rst | 128 +++
drivers/net/altera/mcdma/mcdma_config_csr.h | 201 +++++
drivers/net/altera/mcdma/mcdma_eth.c | 323 +++++++
drivers/net/altera/mcdma/mcdma_eth.h | 333 +++++++
drivers/net/altera/mcdma/mcdma_eth_ops.c | 931 ++++++++++++++++++++
drivers/net/altera/mcdma/meson.build | 12 +
drivers/net/meson.build | 1 +
usertools/dpdk-devbind.py | 1 +
11 files changed, 1952 insertions(+)
create mode 100644 doc/guides/nics/features/mcdma.ini
create mode 100644 doc/guides/nics/mcdma.rst
create mode 100644 drivers/net/altera/mcdma/mcdma_config_csr.h
create mode 100644 drivers/net/altera/mcdma/mcdma_eth.c
create mode 100644 drivers/net/altera/mcdma/mcdma_eth.h
create mode 100644 drivers/net/altera/mcdma/mcdma_eth_ops.c
create mode 100644 drivers/net/altera/mcdma/meson.build
diff --git a/MAINTAINERS b/MAINTAINERS
index 1d865ffd4670..9298ea085ec8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1166,6 +1166,10 @@ F: doc/guides/nics/memif.rst
F: doc/guides/nics/features/memif.ini
F: app/test/test_pmd_memif.c
+Altera MCDMA PMD
+M: Subhransu S. Prusty <subhransu.sekhar.prusty@altera.com>
+M: Cheryl Bansal <cheryl.bansal@altera.com>
+F: drivers/net/altera/mcdma/
Crypto Drivers
--------------
diff --git a/doc/guides/nics/features/mcdma.ini b/doc/guides/nics/features/mcdma.ini
new file mode 100644
index 000000000000..08435fb2fce3
--- /dev/null
+++ b/doc/guides/nics/features/mcdma.ini
@@ -0,0 +1,17 @@
+;
+; Supported features of the 'mcdma' network poll mode driver.
+;
+; Refer to default.ini for the full list of available PMD features.
+;
+[Features]
+Link status = P
+Queue start/stop = Y
+Runtime Rx queue setup = Y
+Runtime Tx queue setup = Y
+MTU update = Y
+Basic stats = Y
+Stats per queue = Y
+Multiprocess aware = Y
+Linux = Y
+x86-64 = Y
+Usage doc = Y
diff --git a/doc/guides/nics/index.rst b/doc/guides/nics/index.rst
index e20be478f837..8bb3f490178f 100644
--- a/doc/guides/nics/index.rst
+++ b/doc/guides/nics/index.rst
@@ -47,6 +47,7 @@ Network Interface Controller Drivers
ixgbe
mana
memif
+ mcdma
mlx4
mlx5
mvneta
diff --git a/doc/guides/nics/mcdma.rst b/doc/guides/nics/mcdma.rst
new file mode 100644
index 000000000000..e31fac2efe71
--- /dev/null
+++ b/doc/guides/nics/mcdma.rst
@@ -0,0 +1,128 @@
+.. SPDX-License-Identifier: BSD-3-Clause
+ Copyright(c) 2020-2026 Altera Corporation
+
+MCDMA Poll Mode Driver
+======================
+
+The MCDMA PMD (**librte_net_mcdma**) provides poll-mode driver support for
+Altera Multi-Channel Direct Memory Access (MCDMA) PCI devices. The driver
+exposes host-to-device (H2D) and device-to-host (D2H) descriptor queues as
+standard DPDK Ethernet ports and is intended for custom FPGA datapaths where
+packet movement is controlled through descriptor rings and queue control/status
+registers (QCSR).
+
+The PMD binds to Altera PCI functions (vendor ID ``0x1172``) and maps the
+configuration BAR for DMA CSR access. A PIO BAR is optionally mapped for
+application-specific control registers in user logic.
+
+Prerequisites
+-------------
+
+Follow the DPDK :doc:`/linux_gsg/index` to set up the basic DPDK environment.
+
+The device must be bound to a DPDK-compatible kernel driver such as
+``vfio-pci`` or ``igb_uio``.
+
+.. note::
+
+ The MCDMA PMD is supported on Linux only.
+
+Device parameters
+-----------------
+
+Device parameters are passed through the EAL ``-a`` allowlist, for example::
+
+ -a 0000:03:00.0,pf_index=0
+
+pf_index
+ Physical function index (0–7). When set, the port is treated as a PF and
+ PF-only parameters below become available.
+
+parent_pf_idx
+ Parent PF index for a virtual function. Required when probing a VF to locate
+ channel capacity in the PF configuration registers.
+
+config_bam_bar
+ PCI BAR index used for the BAM (bus address mapper) region on a PF. Valid
+ values are BAR indices other than the configuration BAR (BAR 0).
+
+vf_weights
+ Comma-separated list of 16 VF scheduler weights (1–8). Each weight is
+ programmed into the DMA CSR VF weight registers.
+
+Queue model
+-----------
+
+Each Ethernet port corresponds to one PCI function (PF or VF). The number of
+Rx and Tx queues available is reported through ``rte_eth_dev_info`` and
+depends on the IP configuration (up to 512 channels per function).
+
+For each queue the driver allocates:
+
+* A hardware descriptor ring in IOVA-contiguous memory (memzone).
+* A software ring tracking in-flight ``rte_mbuf`` pointers.
+
+Descriptor ring size must be a power of two and a multiple of 128 (4096-byte
+pages with 32-byte descriptors). The hardware supports up to 65536 descriptors
+per ring; the driver accepts at most 32768 (``rte_eth_dev_info`` and queue
+setup limits).
+
+H2D queues are used for Tx (``rte_eth_tx_burst()``) and D2H queues for Rx
+(``rte_eth_rx_burst()``). Multi-segment mbufs are supported on Tx when
+``RTE_ETH_TX_OFFLOAD_MULTI_SEGS`` is enabled. Rx delivers one mbuf per
+completed packet; the driver uses an mbuf dynamic field to track
+start-of-frame and end-of-frame descriptor flags internally.
+
+Queues can be started and stopped independently, and Rx/Tx queue setup is
+supported while the device is running
+(``RTE_ETH_DEV_CAPA_RUNTIME_RX_QUEUE_SETUP`` and
+``RTE_ETH_DEV_CAPA_RUNTIME_TX_QUEUE_SETUP``).
+
+Link and statistics
+-------------------
+
+Link status is only partially supported. ``link_update`` always reports the
+port as up and full duplex; there is no carrier detection, link-down state,
+or link speed reporting. Applications can call ``rte_eth_link_get()``, but the
+returned values are fixed stubs rather than hardware link state.
+
+Port and per-queue statistics are maintained in software and augmented with
+hardware drop and pointer-error counters read from QCSR. Use
+``rte_eth_stats_get()`` to retrieve them.
+
+Limitations
+-----------
+
+The MCDMA PMD is a DMA channel driver rather than a full Ethernet NIC PMD.
+The following standard ethdev features are not implemented:
+
+* MAC address programming, promiscuous mode, and multicast filtering.
+* RSS, ``rte_flow``, and interrupt-driven Rx/Tx.
+* Checksum, TSO, VLAN, and other L2/L3 hardware offloads.
+* ``stats_reset`` and extended statistics (xstats).
+
+Traffic acceptance and any Ethernet semantics beyond descriptor DMA are
+defined by the FPGA application connected to the MCDMA IP.
+
+Building and testing
+--------------------
+
+Refer to :doc:`build_and_test` for general PMD build instructions. Enable the
+driver in the Meson build (``-Denable_drivers=net/mcdma`` or a full default
+driver build).
+
+The driver has been tested with `DPDK pktgen <https://github.com/pktgen/Pktgen-DPDK>`_.
+It has not been tested with testpmd.
+
+Example: pktgen on a bound MCDMA device
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Bind the PCI function and start pktgen with one port mapped to a logical core::
+
+ # ./pktgen -l 0-1 -n 4 -a 0000:03:00.0 -- -P -m 1.0
+
+Use the pktgen interactive commands to configure packet size, rate, and
+forwarding. Port statistics are shown in the pktgen UI.
+
+For PF-specific setup (VF weights, BAM BAR), add the corresponding allowlist
+options to the EAL command line.
diff --git a/drivers/net/altera/mcdma/mcdma_config_csr.h b/drivers/net/altera/mcdma/mcdma_config_csr.h
new file mode 100644
index 000000000000..a7cb27e85f2f
--- /dev/null
+++ b/drivers/net/altera/mcdma/mcdma_config_csr.h
@@ -0,0 +1,201 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020-2026 Altera Corporation
+ */
+#ifndef __MCDMA_CONFIG_CSR_H_
+#define __MCDMA_CONFIG_CSR_H_
+
+#include <rte_bitops.h>
+
+typedef enum {
+ MCDMA_QCSR_Q_CTRL = 0x00U,
+ /* QCSR_Reserved = 0x04U, */
+ MCDMA_QCSR_Q_START_ADDR_L = 0x08U,
+ MCDMA_QCSR_Q_START_ADDR_H = 0x0CU,
+ MCDMA_QCSR_Q_SIZE = 0x10U,
+ MCDMA_QCSR_Q_TAIL_POINTER = 0x14U,
+ MCDMA_QCSR_Q_HEAD_POINTER = 0x18U,
+ MCDMA_QCSR_Q_COMPLETED_POINTER = 0x1CU,
+ MCDMA_QCSR_Q_CONSUMED_HEAD_ADDR_L = 0x20U,
+ MCDMA_QCSR_Q_CONSUMED_HEAD_ADDR_H = 0x24U,
+ MCDMA_QCSR_Q_BATCH_DELAY = 0x28U,
+ MCDMA_QCSR_Q_DEBUG_STATUS_1 = 0x38U,
+ MCDMA_QCSR_Q_DEBUG_STATUS_2 = 0x3CU,
+ MCDMA_QCSR_Q_DATA_DRP_ERR_CTR = 0x40U,
+ MCDMA_QCSR_Q_PAYLOAD_COUNT = 0x44U,
+ MCDMA_QCSR_Q_RESET = 0x48U,
+ MCDMA_QCSR_Q_CPL_TIMEOUT = 0x4CU,
+ MCDMA_QCSR_Q_REG_MAX = 0xFFFFFFFFU
+} mcdma_qcsr_reg_offset_t;
+
+#define MCDMA_QCSR_Q(reg) MCDMA_QCSR_Q##_##reg
+#define MCDMA_QCSR_Q_RESET_MASK RTE_GENMASK32(0, 0)
+#define MCDMA_QCSR_Q_RESET_SHIFT 0
+
+/* Q_DATA_DRP_ERR_CTR: valid in bit 20, drop count in bits [15:0] */
+#define MCDMA_QCSR_DRP_CTR_VALID RTE_BIT32(20)
+#define MCDMA_QCSR_DRP_CTR_CNT_MASK RTE_GENMASK32(15, 0)
+#define MCDMA_QCSR_DRP_CTR_CLR_MASK (MCDMA_QCSR_DRP_CTR_VALID | \
+ MCDMA_QCSR_DRP_CTR_CNT_MASK)
+/* Error sticky bit in head / completed pointer registers */
+#define MCDMA_QCSR_PTR_ERR_MASK RTE_BIT32(24)
+
+typedef union {
+ struct {
+ /*
+ * Enable. Once it is enabled, the DMA starts fetching pending
+ * descriptors and executing them.
+ */
+ uint32_t q_en : 1;
+ uint32_t rsv : 7; /* Reserved. */
+ uint32_t q_wb_en : 1; /* If set, upon completion, do a write back. */
+ uint32_t q_intr_en : 1; /* If set, upon completion generate MSI-X interrupt. */
+ uint32_t rsvd : 22; /* Reserved. */
+ } field;
+ uint32_t val;
+} mcdma_qcsr_q_ctrl_t;
+
+/*
+ * The following "read only" registers are available for all functions, not only PF0:
+ * VER_NUM (offset 9'h070)
+ * P0_<H2D, D2H>_TPTR_AVL (offset 9'h100, 110)
+ * PF<0,1,2,3,4,5,6,7>_PARAM_2 (Offset 9'h084, 094, 0A4, 0B4, 0C4, 0D4, 0E4, 0F4)
+ */
+typedef enum {
+ MCDMA_CSR_CTRL = 0x000U,
+ /* Reserved: 0x004 */
+ MCDMA_CSR_WB_INTR_DELAY = 0x008U,
+ /* Reserved: 0x00C - 0x01F */
+ /* VF Weight registers */
+ MCDMA_CSR_VF_WEIGHT_0 = 0x020U,
+ MCDMA_CSR_VF_WEIGHT_1 = 0x024U,
+ MCDMA_CSR_VF_WEIGHT_2 = 0x028U,
+ MCDMA_CSR_VF_WEIGHT_3 = 0x02CU,
+ /* Reserved: 0x030 - 0x06F */
+
+ /* Version / IP params */
+ MCDMA_CSR_VER_NUM = 0x070U,
+ MCDMA_CSR_IP_PARAM_1 = 0x074U,
+
+ /* PF IP PARAM */
+ MCDMA_CSR_PF0_IP_PARAM_1 = 0x080U,
+ MCDMA_CSR_PF0_IP_PARAM_2 = 0x084U,
+ /* Reserved: 0x088 - 0x08F */
+ MCDMA_CSR_PF1_IP_PARAM_1 = 0x090U,
+ MCDMA_CSR_PF1_IP_PARAM_2 = 0x094U,
+ /* Reserved: 0x098 - 0x09F */
+ MCDMA_CSR_PF2_IP_PARAM_1 = 0x0A0U,
+ MCDMA_CSR_PF2_IP_PARAM_2 = 0x0A4U,
+ /* Reserved: 0x0A8 - 0x0AF */
+ MCDMA_CSR_PF3_IP_PARAM_1 = 0x0B0U,
+ MCDMA_CSR_PF3_IP_PARAM_2 = 0x0B4U,
+ /* Reserved: 0x0B8 - 0x0BF */
+ MCDMA_CSR_PF4_IP_PARAM_1 = 0x0C0U,
+ MCDMA_CSR_PF4_IP_PARAM_2 = 0x0C4U,
+ /* Reserved: 0x0C8 - 0x0CF */
+ MCDMA_CSR_PF5_IP_PARAM_1 = 0x0D0U,
+ MCDMA_CSR_PF5_IP_PARAM_2 = 0x0D4U,
+ /* Reserved: 0x0D8 - 0x0DF */
+ MCDMA_CSR_PF6_IP_PARAM_1 = 0x0E0U,
+ MCDMA_CSR_PF6_IP_PARAM_2 = 0x0E4U,
+ /* Reserved: 0x0E8 - 0x0EF */
+ MCDMA_CSR_PF7_IP_PARAM_1 = 0x0F0U,
+ MCDMA_CSR_PF7_IP_PARAM_2 = 0x0F4U,
+ /* Reserved: 0x0F8 - 0x0FF */
+
+ /* Port H2D tail pointers */
+ MCDMA_CSR_P0_H2D_TPTR_AVL = 0x100U,
+ MCDMA_CSR_P1_H2D_TPTR_AVL = 0x104U,
+ MCDMA_CSR_P2_H2D_TPTR_AVL = 0x108U,
+ MCDMA_CSR_P3_H2D_TPTR_AVL = 0x10CU,
+
+ /* Port D2H tail pointers */
+ MCDMA_CSR_P0_D2H_TPTR_AVL = 0x110U,
+ MCDMA_CSR_P1_D2H_TPTR_AVL = 0x114U,
+ MCDMA_CSR_P2_D2H_TPTR_AVL = 0x118U,
+ MCDMA_CSR_P3_D2H_TPTR_AVL = 0x11CU,
+
+ /* Misc */
+ MCDMA_CSR_SW_RESET = 0x120U,
+ /* Reserved: 0x125 - 0x127 */
+ MCDMA_CSR_MSIX_WB_FIFO_STS = 0x128U,
+ /* Reserved: 0x12C - 0x14F */
+
+ /* Debug bank 1 */
+ MCDMA_CSR_DBG1_REG00 = 0x150U,
+ MCDMA_CSR_DBG1_REG01 = 0x154U,
+ MCDMA_CSR_DBG1_REG02 = 0x158U,
+ MCDMA_CSR_DBG1_REG03 = 0x15CU,
+ MCDMA_CSR_DBG1_REG04 = 0x160U,
+ MCDMA_CSR_DBG1_REG05 = 0x164U,
+ MCDMA_CSR_DBG1_REG06 = 0x168U,
+ MCDMA_CSR_DBG1_REG07 = 0x16CU,
+ MCDMA_CSR_DBG1_REG08 = 0x170U,
+ MCDMA_CSR_DBG1_REG09 = 0x174U,
+ MCDMA_CSR_DBG1_REG10 = 0x178U,
+ MCDMA_CSR_DBG1_REG11 = 0x17CU,
+ MCDMA_CSR_DBG1_REG12 = 0x180U,
+ MCDMA_CSR_DBG1_REG13 = 0x184U,
+ MCDMA_CSR_DBG1_REG14 = 0x188U,
+ MCDMA_CSR_DBG1_REG15 = 0x18CU,
+
+ /* Debug bank 2 */
+ MCDMA_CSR_DBG2_REG00 = 0x190U,
+ MCDMA_CSR_DBG2_REG01 = 0x194U,
+ MCDMA_CSR_DBG2_REG02 = 0x198U,
+ MCDMA_CSR_DBG2_REG03 = 0x19CU,
+ MCDMA_CSR_DBG2_REG04 = 0x1A0U,
+ MCDMA_CSR_DBG2_REG05 = 0x1A4U,
+ MCDMA_CSR_DBG2_REG06 = 0x1A8U,
+ MCDMA_CSR_DBG2_REG07 = 0x1ACU,
+ MCDMA_CSR_DBG2_REG08 = 0x1B0U,
+ MCDMA_CSR_DBG2_REG09 = 0x1B4U,
+ MCDMA_CSR_DBG2_REG10 = 0x1B8U,
+ MCDMA_CSR_DBG2_REG11 = 0x1BCU,
+ MCDMA_CSR_DBG2_REG12 = 0x1C0U,
+ MCDMA_CSR_DBG2_REG13 = 0x1C4U,
+ MCDMA_CSR_DBG2_REG14 = 0x1C8U,
+ MCDMA_CSR_DBG2_REG15 = 0x1CCU,
+
+ MCDMA_CSR_OFFSET_MAX = 0xFFFFFFFFU /* force 32-bit backing */
+} mcdma_csr_offset_t;
+
+#define MCDMA_CSR_PF_PARAM_STEP 0x10
+#define MCDMA_CSR_PF_PARAM2_OFFSET(pf) \
+ (MCDMA_CSR_PF0_IP_PARAM_2 + ((pf) * MCDMA_CSR_PF_PARAM_STEP))
+
+/* NUM_DMA_CHAN_PER_PF : bits [31:16] */
+#define MCDMA_NUM_CHAN_PER_PF_SHIFT 16U
+#define MCDMA_NUM_CHAN_PER_PF_MASK (0xFFFFU << MCDMA_NUM_CHAN_PER_PF_SHIFT)
+
+/* NUM_DMA_CHAN_PER_VF : bits [15:0] */
+#define MCDMA_NUM_CHAN_PER_VF_SHIFT 0U
+#define MCDMA_NUM_CHAN_PER_VF_MASK RTE_GENMASK32(15, 0)
+
+#define MCDMA_GET_NUM_CHAN_PER_PF(reg_val) \
+ (((reg_val) & MCDMA_NUM_CHAN_PER_PF_MASK) >> MCDMA_NUM_CHAN_PER_PF_SHIFT)
+
+#define MCDMA_GET_NUM_CHAN_PER_VF(reg_val) \
+ (((reg_val) & MCDMA_NUM_CHAN_PER_VF_MASK) >> MCDMA_NUM_CHAN_PER_VF_SHIFT)
+
+static inline uint32_t mcdma_reg_readl(volatile void *addr)
+{
+ return *(volatile uint32_t *)addr;
+}
+
+static inline void mcdma_reg_writel(volatile void *addr, uint32_t value)
+{
+ *(volatile uint32_t *)addr = value;
+}
+
+static inline void mcdma_reg_updatel(volatile void *base, uint32_t offset,
+ uint32_t value, uint32_t shift, uint32_t mask)
+{
+ volatile void *addr = (volatile uint8_t *)base + offset;
+ uint32_t tmp;
+
+ tmp = mcdma_reg_readl(addr);
+ tmp = (tmp & ~mask) | ((value << shift) & mask);
+ mcdma_reg_writel(addr, tmp);
+}
+
+#endif /* __MCDMA_CONFIG_CSR_H_ */
diff --git a/drivers/net/altera/mcdma/mcdma_eth.c b/drivers/net/altera/mcdma/mcdma_eth.c
new file mode 100644
index 000000000000..003b7c0e7d72
--- /dev/null
+++ b/drivers/net/altera/mcdma/mcdma_eth.c
@@ -0,0 +1,323 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020-2026 Altera Corporation
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <ethdev_pci.h>
+#include <rte_kvargs.h>
+
+#include "mcdma_eth.h"
+
+RTE_LOG_REGISTER_SUFFIX(mcdma_logtype_driver, driver, NOTICE);
+
+size_t config_csr_offsets[MCDMA_MAX_CONFIGS] = {
+ 0, 0x100000, 0x200000, 0x300000, 0x380000,
+};
+
+void *mcdma_get_csraddr(void *base, unsigned int idx)
+{
+ if (idx >= MCDMA_MAX_CONFIGS)
+ return NULL;
+
+ return (uint8_t *)base + config_csr_offsets[idx];
+}
+
+#define MCDMA_ARGS_CFG_BAM_BAR "config_bam_bar"
+#define MCDMA_ARGS_VF_WEIGHTS "vf_weights"
+#define MCDMA_ARGS_PF_IDX "pf_index"
+#define MCDMA_ARGS_PARENT_PF_IDX "parent_pf_idx"
+
+#define NUM_VF_WEIGHT 16
+#define VF_WEIGHT_LO 1
+#define VF_WEIGHT_HI 8
+
+static const char *const mcdma_kvargs_keys[] = {
+ MCDMA_ARGS_CFG_BAM_BAR,
+ MCDMA_ARGS_VF_WEIGHTS,
+ MCDMA_ARGS_PF_IDX,
+ MCDMA_ARGS_PARENT_PF_IDX,
+ NULL,
+};
+
+static int vf_weight_parse_handler(__rte_unused const char *key,
+ const char *value, void *data)
+{
+ int *weights = data;
+ char *token, *str, *tofree;
+ int i = 0;
+
+ str = strdup(value);
+ tofree = str;
+ if (!str) {
+ PMD_DRV_LOG(ERR, "failed to parse vf_weights: out of memory");
+ return -ENOMEM;
+ }
+
+ while ((token = strsep(&str, ",")) != NULL) {
+ uint8_t weight_val = atoi(token);
+ if (weight_val < VF_WEIGHT_LO || weight_val > VF_WEIGHT_HI) {
+ PMD_DRV_LOG(WARNING,
+ "weight value not in range; defaulting to %d",
+ VF_WEIGHT_LO);
+ weight_val = VF_WEIGHT_LO;
+ }
+
+ if (i >= NUM_VF_WEIGHT) {
+ free(tofree);
+ PMD_DRV_LOG(ERR, "vf_weights expects %d values, got more",
+ NUM_VF_WEIGHT);
+ return -EINVAL;
+ }
+
+ weights[i++] = weight_val;
+ }
+
+ free(tofree);
+
+ if (i != NUM_VF_WEIGHT) {
+ PMD_DRV_LOG(ERR, "vf_weights expects %d values, got %d",
+ NUM_VF_WEIGHT, i);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int id_parse_handler(__rte_unused const char *key,
+ const char *value, void *data)
+{
+ unsigned long val;
+ char *end = NULL;
+
+ val = strtoul(value, &end, 10);
+ if (end == value || val > UINT8_MAX)
+ return -EINVAL;
+
+ *(uint8_t *)data = (uint8_t)val;
+ return 0;
+}
+
+static int bar_idx_parse_handler(__rte_unused const char *key,
+ const char *value, void *data)
+{
+ unsigned long val;
+ char *end = NULL;
+
+ val = strtoul(value, &end, 10);
+ if (end == value || val >= MCDMA_BAR_MAX)
+ return -EINVAL;
+
+ *(int *)data = (int)val;
+ return 0;
+}
+
+#define MCDMA_BAM_BAR_KEY_IDX 0
+#define MCDMA_VF_WEIGHT_KEY_IDX 1
+#define MCDMA_PF_IDX 2
+#define MCDMA_PARENT_PF_IDX 3
+
+static int mcmda_parse_args(struct mcdma_pci_device *priv,
+ struct rte_pci_device *pci_dev)
+{
+ struct rte_devargs *devargs = priv->dev->device->devargs;
+ struct rte_kvargs *kvlist;
+ int bam_bar_idx = 0;
+ int ret;
+
+ if (!devargs)
+ return -EINVAL;
+
+ kvlist = rte_kvargs_parse(devargs->args, mcdma_kvargs_keys);
+ if (!kvlist)
+ return 0;
+
+ priv->is_pf = false;
+
+ /* Process pf_idx */
+ if (rte_kvargs_count(kvlist, mcdma_kvargs_keys[MCDMA_PF_IDX])) {
+ ret = rte_kvargs_process(kvlist, mcdma_kvargs_keys[MCDMA_PF_IDX],
+ id_parse_handler, &priv->pf_idx);
+ if (ret) {
+ PMD_DRV_LOG(WARNING, "PF index parse failed");
+ rte_kvargs_free(kvlist);
+ return ret;
+ }
+
+ if (priv->pf_idx >= MCDMA_MAX_PF) {
+ PMD_DRV_LOG(ERR, "Invalid PF index: %u", priv->pf_idx);
+ rte_kvargs_free(kvlist);
+ return -EINVAL;
+ }
+
+ priv->is_pf = true;
+ }
+
+ if (priv->is_pf) {
+ /* Process weight priority for VFs */
+ if (rte_kvargs_count(kvlist, mcdma_kvargs_keys[MCDMA_VF_WEIGHT_KEY_IDX])) {
+ int vf_weights[NUM_VF_WEIGHT];
+ int i;
+
+ ret = rte_kvargs_process(kvlist,
+ mcdma_kvargs_keys[MCDMA_VF_WEIGHT_KEY_IDX],
+ vf_weight_parse_handler, vf_weights);
+ if (ret) {
+ rte_kvargs_free(kvlist);
+ return ret;
+ }
+
+ /* Program vf weights */
+ for (i = 0; i < NUM_VF_WEIGHT; i++) {
+ /* 4 vf weight configuration in 1 register */
+ mcdma_csr_offset_t offset = MCDMA_CSR_VF_WEIGHT_0 + 4 * (i / 4);
+ uint32_t shift = 8 * (unsigned int)(i % 4);
+ uint32_t mask = 0xFFu << shift;
+
+ mcdma_dmacsr_reg_updatel(priv->bar_base, offset,
+ vf_weights[i], shift, mask);
+ }
+ }
+
+ /* Process BAM_bar index */
+ if (rte_kvargs_count(kvlist, mcdma_kvargs_keys[MCDMA_BAM_BAR_KEY_IDX])) {
+ ret = rte_kvargs_process(kvlist,
+ mcdma_kvargs_keys[MCDMA_BAM_BAR_KEY_IDX],
+ bar_idx_parse_handler, &bam_bar_idx);
+ if (ret) {
+ PMD_DRV_LOG(WARNING, "BAM BAR index parse failed");
+ rte_kvargs_free(kvlist);
+ return ret;
+ }
+
+ if (bam_bar_idx <= MCDMA_CONFIG_BAR ||
+ bam_bar_idx >= MCDMA_BAR_MAX) {
+ PMD_DRV_LOG(ERR, "BAM BAR index not valid: %d",
+ bam_bar_idx);
+ rte_kvargs_free(kvlist);
+ return -EINVAL;
+ }
+
+ priv->bam = pci_dev->mem_resource[bam_bar_idx].addr;
+ }
+ }
+
+ /* Process parent_pf idx */
+ if (rte_kvargs_count(kvlist, mcdma_kvargs_keys[MCDMA_PARENT_PF_IDX])) {
+ ret = rte_kvargs_process(kvlist,
+ mcdma_kvargs_keys[MCDMA_PARENT_PF_IDX],
+ id_parse_handler, &priv->parent_pf_idx);
+ if (ret) {
+ PMD_DRV_LOG(WARNING, "parent PF index parse failed");
+ rte_kvargs_free(kvlist);
+ return ret;
+ }
+
+ if (priv->parent_pf_idx >= MCDMA_MAX_PF) {
+ PMD_DRV_LOG(ERR, "Invalid parent PF index: %u",
+ priv->parent_pf_idx);
+ rte_kvargs_free(kvlist);
+ return -EINVAL;
+ }
+ }
+
+ rte_kvargs_free(kvlist);
+ return 0;
+}
+
+static int mcdma_eth_dev_init(struct rte_eth_dev *dev)
+{
+ struct mcdma_pci_device *priv = dev->data->dev_private;
+ struct rte_pci_device *pci_dev = RTE_CLASS_TO_BUS_DEVICE(dev, *pci_dev);
+ uint32_t reg_val;
+ int ret;
+
+ dev->data->dev_private = priv;
+ priv->dev = dev;
+ priv->bar_base = pci_dev->mem_resource[MCDMA_CONFIG_BAR].addr;
+
+ ret = mcmda_parse_args(priv, pci_dev);
+ if (ret) {
+ PMD_DRV_LOG(ERR, "parse args failed with error: %d", ret);
+ return ret;
+ }
+
+ PMD_DRV_LOG(INFO, "MCDMA RTL VERSION : 0x%x",
+ mcdma_dmacsr_reg_readl(priv->bar_base, MCDMA_CSR_VER_NUM));
+
+ priv->pio = pci_dev->mem_resource[MCDMA_PIO_BAR].addr; /* user logic */
+ priv->pio_bar_len = pci_dev->mem_resource[MCDMA_PIO_BAR].len;
+ if (priv->pio == NULL) {
+ priv->pio_bar_len = 0;
+ PMD_DRV_LOG(WARNING, "PIO BAR %d is not mapped", MCDMA_PIO_BAR);
+ }
+
+ if (priv->is_pf) {
+ reg_val = mcdma_dmacsr_reg_readl(priv->bar_base,
+ MCDMA_CSR_PF_PARAM2_OFFSET(priv->pf_idx));
+ priv->ip_num_chan = MCDMA_GET_NUM_CHAN_PER_PF(reg_val);
+ } else {
+ reg_val = mcdma_dmacsr_reg_readl(priv->bar_base,
+ MCDMA_CSR_PF_PARAM2_OFFSET(priv->parent_pf_idx));
+ priv->ip_num_chan = MCDMA_GET_NUM_CHAN_PER_VF(reg_val);
+ }
+
+ /* Register eth_dev_ops */
+ mcdma_ethdev_ops_init(dev);
+
+ dev->data->dev_flags |= RTE_ETH_DEV_NOLIVE_MAC_ADDR;
+
+ dev->data->mac_addrs = rte_zmalloc("mcdma",
+ RTE_ETHER_ADDR_LEN * MCDMA_MAX_MAC_ADDRS, 0);
+ if (dev->data->mac_addrs == NULL)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int mcdma_eth_dev_uninit(struct rte_eth_dev *dev)
+{
+ rte_free(dev->data->mac_addrs);
+ dev->data->mac_addrs = NULL;
+
+ return 0;
+}
+
+static struct rte_pci_id mcdma_pci_id_tbl[] = {
+#define RTE_PCI_DEV_ID_DECL_XNIC(vend, dev) {RTE_PCI_DEVICE(vend, dev)},
+#ifndef PCI_VENDOR_ID_ALTERA
+#define PCI_VENDOR_ID_ALTERA 0x1172
+#endif
+ RTE_PCI_DEV_ID_DECL_XNIC(PCI_VENDOR_ID_ALTERA, 0)
+ { .vendor_id = 0, },
+};
+
+static int mcdma_eth_pci_probe(struct rte_pci_driver *pci_drv __rte_unused,
+ struct rte_pci_device *pci_dev)
+{
+ return rte_eth_dev_pci_generic_probe(pci_dev,
+ sizeof(struct mcdma_pci_device),
+ mcdma_eth_dev_init);
+}
+
+static int mcdma_eth_pci_remove(struct rte_pci_device *pci_dev)
+{
+ return rte_eth_dev_pci_generic_remove(pci_dev, mcdma_eth_dev_uninit);
+}
+
+static struct rte_pci_driver rte_mcdma_pmd = {
+ .id_table = mcdma_pci_id_tbl,
+ .drv_flags = RTE_PCI_DRV_NEED_MAPPING,
+ .probe = mcdma_eth_pci_probe,
+ .remove = mcdma_eth_pci_remove,
+};
+
+RTE_PMD_REGISTER_PCI(net_mcdma, rte_mcdma_pmd);
+RTE_PMD_REGISTER_PCI_TABLE(net_mcdma, mcdma_pci_id_tbl);
+RTE_PMD_REGISTER_KMOD_DEP(net_mcdma, "* igb_uio | vfio-pci");
+RTE_PMD_REGISTER_PARAM_STRING(net_mcdma,
+ MCDMA_ARGS_PF_IDX "=<int> "
+ MCDMA_ARGS_PARENT_PF_IDX "=<int> "
+ MCDMA_ARGS_CFG_BAM_BAR "=<int> "
+ MCDMA_ARGS_VF_WEIGHTS "=<string> ");
diff --git a/drivers/net/altera/mcdma/mcdma_eth.h b/drivers/net/altera/mcdma/mcdma_eth.h
new file mode 100644
index 000000000000..c3b74474c10c
--- /dev/null
+++ b/drivers/net/altera/mcdma/mcdma_eth.h
@@ -0,0 +1,333 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020-2026 Altera Corporation
+ */
+#ifndef __MCDMA_ETH_H_
+#define __MCDMA_ETH_H_
+
+#include <stddef.h>
+#include <stdbool.h>
+
+#include <ethdev_driver.h>
+#include <rte_ether.h>
+#include <rte_log.h>
+#include <rte_mbuf.h>
+#include <rte_mempool.h>
+
+#include "mcdma_config_csr.h"
+
+extern int mcdma_logtype_driver;
+#define RTE_LOGTYPE_MCDMA_DRIVER mcdma_logtype_driver
+#define PMD_DRV_LOG(level, ...) \
+ RTE_LOG_LINE_PREFIX(level, MCDMA_DRIVER, "%s(): ", __func__, __VA_ARGS__)
+
+#define MCDMA_LOG_ENABLE 1
+#if MCDMA_LOG_ENABLE
+#define PMD_DRV_LOG_DBG(level, ...) PMD_DRV_LOG(level, __VA_ARGS__)
+#else
+#define PMD_DRV_LOG_DBG(level, ...) RTE_SET_USED(level)
+#endif
+/* PCI resource */
+#define MCDMA_BAR_MAX 6
+#define MCDMA_CONFIG_BAR 0
+#define MCDMA_PIO_BAR 2
+
+/* SW resource and flags - start */
+#define MCDMA_COMPL_WB 1 /* compl status: Write back */
+#define MCDMA_MAX_PYLD_CNT 1048576
+#define MCDMA_MAX_BURST 32
+#define MCDMA_MAX_MAC_ADDRS 1
+#define MCDMA_MIN_MTU 0x40
+#define MCDMA_DIR_D2H 0
+#define MCDMA_DIR_H2D 1
+#define MCDMA_BATCH_DESC_DELAY 1
+#define MCDMA_IP_MAX_FRAME_LEN 0x1FFFFF /* HW descriptor len (20-bit) */
+#define MCDMA_RX_ETH_OVERHEAD (RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN)
+#define MCDMA_MAX_RX_FRAME_LEN MCDMA_IP_MAX_FRAME_LEN
+#define MCDMA_MAX_MTU \
+ ((uint16_t)RTE_MIN((uint32_t)(MCDMA_MAX_RX_FRAME_LEN - \
+ MCDMA_RX_ETH_OVERHEAD), (uint32_t)UINT16_MAX))
+#define MCDMA_TX_MZONE_NAME "TxHwRn"
+#define MCDMA_RX_MZONE_NAME "RxHwRn"
+#define MCDMA_Q_RESET_TO 300000
+#define MCDMA_SW_RESET_DELAY_MS 1000
+#define MCDMA_DESC_SOF RTE_BIT64(0)
+#define MCDMA_DESC_EOF RTE_BIT64(1)
+
+#define MCDMA_CNT_HEAD_MOVE 100000
+/* SW resource and flags - end */
+
+/* HW resource - start */
+#define MCDMA_MAX_PF 8
+#define MCDMA_MAX_VF_PER_PF 2048
+#define MCDMA_DESC_PAGE_SIZE 4096
+#define MCDMA_DESC_LEN 32
+#define MCDMA_NUM_DESC_PER_PAGE (MCDMA_DESC_PAGE_SIZE / MCDMA_DESC_LEN)
+#define MCDMA_NUM_DESC_BITS 16
+/* HW ring capacity: 16-bit didx index space (1 << 16 descriptors) */
+#define MCDMA_MAX_DESCS (1U << MCDMA_NUM_DESC_BITS)
+/*
+ * Max ring depth exposed via ethdev (nb_*_desc is uint16_t): power of 2,
+ * MCDMA_NUM_DESC_PER_PAGE-aligned; largest value the PMD accepts.
+ */
+#define MCDMA_MAX_RING_DESCS (1U << (MCDMA_NUM_DESC_BITS - 1))
+#define MCDMA_IFC_DATA_WIDTH 512
+
+/* tail/head/cmpl ptr: [15:0] */
+#define MCDMA_PTR_MASK 0xFFFF
+/* HW resource - end */
+
+/* Num QCSR per direction per device. 2048 for H2D and 2048 for D2H */
+#define MCDMA_QCSR_NUM_MAX_PER_DIR 2048
+#define MCDMA_QCSR_SIZE 256
+#define MCDMA_QCSR_H2D_OFFSET (MCDMA_QCSR_NUM_MAX_PER_DIR * MCDMA_QCSR_SIZE)
+
+/* Max DMA channel per function */
+#define MCDMA_MAX_CHAN_PER_FUNC 512
+#define MCDMA_MAX_CHAN 2048
+
+/* Config space CSR offset */
+typedef enum {
+ MCDMA_CONFIG_QCSR,
+ MCDMA_CONFIG_MSIX,
+ MCDMA_CONFIG_CSR,
+ MCDMA_CONFIG_MBOX_CSR,
+ MCDMA_CONFIG_DCA_CSR,
+ MCDMA_MAX_CONFIGS,
+} mcdma_config_idx_t;
+
+struct mcdma_config_map {
+ unsigned int idx;
+ size_t offset;
+};
+
+struct mcdma_desc {
+ uint64_t src;
+ uint64_t dest;
+ uint32_t len:20;
+ uint32_t rsvd:12;
+ uint32_t didx:16;
+ uint32_t msix_en:1;
+ uint32_t wb_en:1;
+ uint32_t rsvd2:14;
+ uint32_t rx_pyld_cnt:20;
+ uint32_t rsvd3:10;
+ uint32_t sof:1;
+ uint32_t eof:1;
+ uint32_t rsvd5:28;
+ uint32_t pad_len:2;
+ uint32_t desc_invalid:1;
+ uint32_t link:1;
+};
+
+struct mcdma_queue {
+ struct rte_eth_dev *ethdev;
+ void *qcsr;
+ struct rte_mbuf **sw_ring;
+ struct rte_mempool *mb_pool;
+ void *qbuf;
+ uint64_t qbuf_dma;
+ uint32_t qid;
+ int socket_id;
+ uint32_t dir;
+ uint32_t num_desc_pages;
+ uint32_t num_descs;
+ uint32_t wbe;
+
+ /* SW shadow of last freed HW didx (1 .. num_descs), 0 = none freed */
+ volatile uint16_t freed_cons_didx;
+ /* Last filled descriptor HW didx (1 .. num_descs), 0 = none */
+ uint16_t prod_didx;
+ /* HW writeback: last completed didx (1 .. num_descs), 0 = none */
+ volatile uint16_t cons_didx;
+ /* Last tail submit HW didx (1 .. num_descs), 0 = none */
+ uint16_t submitted_didx;
+ uint16_t ring_avail;
+ uint16_t ring_to_submit;
+ uint16_t processed_cur;
+ /* 16 bits - Monotonic index and IP requires it to start from 1 */
+ uint16_t didx;
+ uint16_t burst_size;
+ /** D2H: DMA length per buffer descriptor (full frame, <= mbuf data room). */
+ uint32_t rx_seg_len;
+
+ uint64_t rx_pkts;
+ uint64_t rx_bytes;
+ uint64_t tx_pkts;
+ uint64_t tx_bytes;
+ uint64_t drop_pkts;
+ uint64_t rx_nombuf;
+ uint64_t complete;
+};
+
+#define MCDMA_DESC_PTR(q, prod) \
+ ((struct mcdma_desc *)((uint8_t *)(q)->qbuf + \
+ sizeof(struct mcdma_desc) * (prod)))
+#define MCDMA_NEXT_IDX(prod, qlen) ((((uint32_t)(prod)) + 1) % (qlen))
+#define MCDMA_PREV_IDX(cur, qlen) \
+ ({ \
+ uint32_t _cur = (uint32_t)(cur); \
+ uint32_t _qlen = (uint32_t)(qlen); \
+ (_cur + _qlen - 1) % _qlen; \
+ })
+#define MCDMA_RING_AVAIL_COUNT(cur, base, size) \
+ ({ \
+ uint32_t _cur = (uint32_t)(cur); \
+ uint32_t _base = (uint32_t)(base); \
+ uint32_t _size = (uint32_t)(size); \
+ _cur < _base ? _base - _cur : _size - _cur + _base; \
+ })
+#define MCDMA_RING_USED_COUNT(cur, base, size) \
+ ({ \
+ uint32_t _cur = (uint32_t)(cur); \
+ uint32_t _base = (uint32_t)(base); \
+ uint32_t _size = (uint32_t)(size); \
+ _cur >= _base ? _cur - _base : _size - _base + _cur; \
+ })
+
+/* Monotonic descriptor didx assigned to d->didx; wraps at MCDMA_MAX_DESCS. */
+#define MCDMA_DIDX_NEXT(cur, num_descs) \
+ ((uint16_t)((((uint16_t)(cur) + 1) % (num_descs))))
+
+/* HW descriptor didx is 1-based (1 .. num_descs); ring slots are 0-based. */
+#define MCDMA_RING_TO_HW_DIDX(ring_idx, num_descs) \
+ ((uint16_t)(((uint16_t)(ring_idx) % (uint16_t)(num_descs)) + 1))
+
+static inline uint16_t
+mcdma_hw_didx_to_ring(uint16_t hw_didx, uint16_t num_descs)
+{
+ if (hw_didx == 0)
+ return num_descs - 1;
+
+ return hw_didx - 1;
+}
+
+static inline uint16_t
+mcdma_hw_didx_nudge(uint16_t hw_didx, uint16_t num_descs)
+{
+ hw_didx++;
+ if (hw_didx > num_descs)
+ return 1;
+
+ return hw_didx;
+}
+
+static inline uint16_t
+mcdma_qcsr_ptr_val(uint32_t reg)
+{
+ return (uint16_t)(reg & MCDMA_PTR_MASK);
+}
+
+static inline uint16_t
+mcdma_queue_cons_ring(const struct mcdma_queue *q)
+{
+ return mcdma_hw_didx_to_ring(q->cons_didx, q->num_descs);
+}
+
+static inline uint16_t
+mcdma_queue_freed_ring(const struct mcdma_queue *q)
+{
+ return mcdma_hw_didx_to_ring(q->freed_cons_didx, q->num_descs);
+}
+
+static inline uint16_t
+mcdma_queue_prod_ring(const struct mcdma_queue *q)
+{
+ return mcdma_hw_didx_to_ring(q->prod_didx, q->num_descs);
+}
+
+struct mcdma_pci_device {
+ struct rte_eth_dev *dev;
+ /* config */
+ void *bar_base;
+ /* BAR region for user logic */
+ uint64_t *pio;
+ /** Size in bytes of the mapped PIO BAR (0 if unmapped). */
+ uint64_t pio_bar_len;
+ /* BAR region Bursting AVMM master */
+ uint64_t *bam;
+ /* num channels configured in IP */
+ int ip_num_chan;
+ bool is_pf;
+ uint8_t pf_idx;
+ uint8_t parent_pf_idx;
+};
+
+void *mcdma_get_csraddr(void *base, unsigned int idx);
+
+static inline uint32_t mcdma_qcsr_reg_readl(void *base, uint32_t offset)
+{
+ void *addr = (uint8_t *)base + offset;
+
+ return mcdma_reg_readl(addr);
+}
+
+static inline void mcdma_qcsr_reg_writel(void *base, uint32_t offset, uint32_t val)
+{
+ void *addr = (uint8_t *)base + offset;
+
+ mcdma_reg_writel(addr, val);
+}
+
+static inline void mcdma_qcsr_reg_updatel(void *base, uint32_t offset,
+ uint32_t val, uint32_t shift, uint32_t mask)
+{
+ mcdma_reg_updatel(base, offset, val, shift, mask);
+}
+
+/* Except few registers all register access are restricted only to pf0 */
+static inline uint32_t mcdma_dmacsr_reg_readl(void *base, mcdma_csr_offset_t offset)
+{
+ void *addr = (uint8_t *)mcdma_get_csraddr(base, MCDMA_CONFIG_CSR) + offset;
+
+ return mcdma_reg_readl(addr);
+}
+
+static inline void mcdma_dmacsr_reg_writel(void *base, mcdma_csr_offset_t offset,
+ uint32_t val)
+{
+ void *addr = (uint8_t *)mcdma_get_csraddr(base, MCDMA_CONFIG_CSR) + offset;
+
+ mcdma_reg_writel(addr, val);
+}
+
+static inline void mcdma_dmacsr_reg_updatel(void *base, mcdma_csr_offset_t offset,
+ uint32_t val, uint32_t shift, uint32_t mask)
+{
+ void *csr = mcdma_get_csraddr(base, MCDMA_CONFIG_CSR);
+
+ mcdma_reg_updatel(csr, offset, val, shift, mask);
+}
+
+static inline uint32_t mcdma_pio_reg_readl(void *pio, uint32_t offset)
+{
+ void *addr = (uint8_t *)pio + offset;
+
+ return mcdma_reg_readl(addr);
+}
+
+static inline void mcdma_pio_reg_writel(void *pio, uint32_t offset, uint32_t val)
+{
+ void *addr = (uint8_t *)pio + offset;
+
+ mcdma_reg_writel(addr, val);
+}
+
+static inline uint64_t mcdma_pio_reg_read64(void *pio, uint64_t offset)
+{
+ volatile void *addr = (uint8_t *)pio + offset;
+
+ return *(volatile uint64_t *)addr;
+}
+
+static inline void mcdma_pio_reg_write64(void *pio, uint64_t offset, uint64_t val)
+{
+ volatile void *addr = (uint8_t *)pio + offset;
+
+ *(volatile uint64_t *)addr = val;
+}
+
+void mcdma_ethdev_ops_init(struct rte_eth_dev *dev);
+int mcdma_dev_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
+ struct eth_queue_stats *qstats);
+
+#endif /* __MCDMA_ETH_H_ */
diff --git a/drivers/net/altera/mcdma/mcdma_eth_ops.c b/drivers/net/altera/mcdma/mcdma_eth_ops.c
new file mode 100644
index 000000000000..8e7624312fe0
--- /dev/null
+++ b/drivers/net/altera/mcdma/mcdma_eth_ops.c
@@ -0,0 +1,931 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020-2026 Altera Corporation
+ */
+
+#include <errno.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <rte_cycles.h>
+#include <rte_errno.h>
+#include <rte_malloc.h>
+#include <rte_memzone.h>
+
+#include "mcdma_eth.h"
+
+static int mcdma_dev_tx_queue_stop(struct rte_eth_dev *dev, uint16_t qid);
+static int mcdma_dev_rx_queue_stop(struct rte_eth_dev *dev, uint16_t qid);
+
+static uint16_t
+mcdma_dev_mtu_get(const struct rte_eth_dev *dev)
+{
+ if (dev->data->dev_conf.rxmode.mtu != 0)
+ return dev->data->dev_conf.rxmode.mtu;
+ if (dev->data->mtu != 0)
+ return dev->data->mtu;
+
+ return RTE_ETHER_MTU;
+}
+
+static uint32_t
+mcdma_rx_frame_len_from_mtu(uint16_t mtu)
+{
+ return (uint32_t)mtu + MCDMA_RX_ETH_OVERHEAD;
+}
+
+static int
+mcdma_rx_frame_len_check(struct rte_eth_dev *dev, uint32_t frame_len)
+{
+ if (frame_len == 0 || frame_len > MCDMA_MAX_RX_FRAME_LEN) {
+ PMD_DRV_LOG(ERR,
+ "port %u: Rx frame length %u invalid (HW max %u)",
+ dev->data->port_id, frame_len,
+ MCDMA_MAX_RX_FRAME_LEN);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int
+mcdma_rx_seg_len_for_pool(struct rte_eth_dev *dev, struct rte_mempool *mb_pool,
+ uint32_t *rx_seg_len)
+{
+ uint32_t frame_len = mcdma_rx_frame_len_from_mtu(mcdma_dev_mtu_get(dev));
+ uint32_t buf_len = rte_pktmbuf_data_room_size(mb_pool);
+ int ret;
+
+ ret = mcdma_rx_frame_len_check(dev, frame_len);
+ if (ret != 0)
+ return ret;
+
+ if (buf_len <= RTE_PKTMBUF_HEADROOM) {
+ PMD_DRV_LOG(ERR, "port %u: mbuf pool data room %u too small",
+ dev->data->port_id, buf_len);
+ return -EINVAL;
+ }
+
+ buf_len -= RTE_PKTMBUF_HEADROOM;
+ if (frame_len > buf_len) {
+ PMD_DRV_LOG(ERR,
+ "port %u: Rx frame %u B exceeds mbuf data room %u B (MTU %u)",
+ dev->data->port_id, frame_len, buf_len,
+ mcdma_dev_mtu_get(dev));
+ return -EINVAL;
+ }
+
+ *rx_seg_len = frame_len;
+ return 0;
+}
+
+static int mcdma_wait_queue_drain(struct mcdma_queue *q)
+{
+ uint32_t retry;
+
+ for (retry = 0; retry < MCDMA_CNT_HEAD_MOVE; retry++) {
+ rte_rmb();
+ if (q->cons_didx == q->prod_didx)
+ return 0;
+ rte_delay_us_block(10);
+ }
+
+ PMD_DRV_LOG(ERR,
+ "queue %u drain timeout (cons_didx=%u prod_didx=%u)",
+ q->qid, q->cons_didx, q->prod_didx);
+ return -ETIMEDOUT;
+}
+
+/* Free mbufs still held between freed_cons_didx and prod_didx */
+static void
+mcdma_sw_ring_free_inflight(struct mcdma_queue *q)
+{
+ uint16_t nb_to_free;
+
+ nb_to_free = MCDMA_RING_USED_COUNT(mcdma_queue_prod_ring(q),
+ mcdma_queue_freed_ring(q), q->num_descs);
+ while (nb_to_free--) {
+ struct mcdma_desc *d;
+ uint16_t idx = MCDMA_NEXT_IDX(mcdma_queue_freed_ring(q), q->num_descs);
+ d = MCDMA_DESC_PTR(q, idx);
+ if (q->sw_ring[idx] != NULL &&
+ (q->dir != MCDMA_DIR_H2D || d->eof))
+ rte_pktmbuf_free(q->sw_ring[idx]);
+ q->sw_ring[idx] = NULL;
+ q->freed_cons_didx = d->didx;
+ }
+}
+
+/* Caller to ensure value fits inside the mask */
+static int mcdma_reg_write_verify(struct mcdma_queue *q, uint32_t reg,
+ uint32_t mask, uint32_t val, unsigned int shift,
+ uint64_t timeout_us, uint32_t poll_val)
+{
+ uint32_t reg_val;
+ uint64_t start_tsc;
+ uint64_t timeout_tsc;
+
+ if (!mask)
+ return -EINVAL;
+
+ reg_val = mcdma_qcsr_reg_readl(q->qcsr, reg);
+ reg_val = (reg_val & ~mask) | ((val << shift) & mask);
+ mcdma_qcsr_reg_writel(q->qcsr, reg, reg_val);
+
+ start_tsc = rte_get_timer_cycles();
+ timeout_tsc = (timeout_us * rte_get_timer_hz()) / 1000000;
+
+ for (;;) {
+ uint32_t readback = mcdma_qcsr_reg_readl(q->qcsr, reg);
+ if (((readback & mask) >> shift) == poll_val)
+ return 0;
+
+ if (rte_get_timer_cycles() - start_tsc >= timeout_tsc)
+ return -ETIMEDOUT;
+
+ rte_delay_us_block(10);
+ }
+}
+
+static void mcdma_dev_descq_disable(struct mcdma_queue *q)
+{
+ mcdma_qcsr_reg_writel(q->qcsr, MCDMA_QCSR_Q_CTRL, 0);
+}
+
+static void mcdma_dev_descq_free(struct mcdma_queue *q)
+{
+ const struct rte_memzone *qzone;
+ char z_name[RTE_MEMZONE_NAMESIZE];
+ const char *ring_name = (q->dir == MCDMA_DIR_H2D) ?
+ MCDMA_TX_MZONE_NAME : MCDMA_RX_MZONE_NAME;
+ struct rte_eth_dev *dev = q->ethdev;
+
+ snprintf(z_name, sizeof(z_name), "%s%s%u_%u",
+ dev->device->driver->name, ring_name,
+ dev->data->port_id, q->qid);
+
+ qzone = rte_memzone_lookup(z_name);
+ if (qzone)
+ rte_memzone_free(qzone);
+
+ rte_free(q->sw_ring);
+}
+
+/* Allocate and initialize software queue structure */
+static int mcdma_sw_descq_alloc(struct rte_eth_dev *dev, struct mcdma_queue **q_ptr,
+ uint32_t dir, uint16_t q_id, uint16_t nb_desc,
+ unsigned int socket_id)
+{
+ struct mcdma_pci_device *mcdma_dev = dev->data->dev_private;
+ struct mcdma_queue *q;
+ const char *type = (dir == MCDMA_DIR_H2D) ? "MCDMA_TxQ" : "MCDMA_RxQ";
+ uint32_t qcsr_offset = (dir == MCDMA_DIR_H2D) ?
+ (MCDMA_QCSR_H2D_OFFSET + (q_id * MCDMA_QCSR_SIZE)) :
+ (q_id * MCDMA_QCSR_SIZE);
+
+ if (nb_desc == 0 || nb_desc > MCDMA_MAX_RING_DESCS ||
+ (nb_desc % MCDMA_NUM_DESC_PER_PAGE) != 0 ||
+ !rte_is_power_of_2(nb_desc)) {
+ PMD_DRV_LOG(ERR,
+ "Invalid descriptor ring size %u (power of 2, align %u, max %u)",
+ nb_desc, MCDMA_NUM_DESC_PER_PAGE, MCDMA_MAX_RING_DESCS);
+ return -EINVAL;
+ }
+
+ q = rte_zmalloc_socket(type, sizeof(*q), RTE_CACHE_LINE_SIZE, socket_id);
+ if (!q) {
+ PMD_DRV_LOG(ERR, "Failed to allocate %s queue", type);
+ return -ENOMEM;
+ }
+
+ *q_ptr = q;
+
+ q->socket_id = (int)socket_id;
+ q->qcsr = (uint8_t *)mcdma_get_csraddr(mcdma_dev->bar_base,
+ MCDMA_CONFIG_QCSR) + qcsr_offset;
+ q->qid = q_id;
+ q->ethdev = dev;
+ q->dir = dir;
+ q->num_descs = nb_desc;
+ q->num_desc_pages = (nb_desc * MCDMA_DESC_LEN) / MCDMA_DESC_PAGE_SIZE;
+
+ q->sw_ring = rte_zmalloc_socket("desc_sw_ctx",
+ q->num_descs * sizeof(struct rte_mbuf *), 0,
+ socket_id);
+ if (!q->sw_ring) {
+ PMD_DRV_LOG(ERR, "Failed to allocate sw ring");
+ rte_free(q);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+/* Allocate HW descriptor ring */
+static int mcdma_dev_descq_alloc(struct rte_eth_dev *dev,
+ struct mcdma_queue *q)
+{
+ const struct rte_memzone *qzone;
+ struct mcdma_desc *desc;
+ size_t len = MCDMA_DESC_PAGE_SIZE * q->num_desc_pages;
+ char z_name[RTE_MEMZONE_NAMESIZE];
+ const char *ring_name = (q->dir == MCDMA_DIR_H2D) ?
+ MCDMA_TX_MZONE_NAME : MCDMA_RX_MZONE_NAME;
+ unsigned int flags = RTE_MEMZONE_IOVA_CONTIG |
+ RTE_MEMZONE_2MB |
+ RTE_MEMZONE_SIZE_HINT_ONLY;
+ uint32_t i;
+
+ snprintf(z_name, sizeof(z_name), "%s%s%u_%u",
+ dev->device->driver->name, ring_name,
+ dev->data->port_id, q->qid);
+
+ qzone = rte_memzone_reserve_bounded(z_name, len, q->socket_id,
+ flags, RTE_CACHE_LINE_SIZE, len);
+ if (!qzone) {
+ PMD_DRV_LOG(ERR,
+ "qbuf zone allocation failed with err: %s name: %s len: %zu",
+ rte_strerror(rte_errno), z_name, len);
+ return -rte_errno;
+ }
+
+ if (qzone->iova == RTE_BAD_IOVA) {
+ PMD_DRV_LOG(ERR, "qbuf IOVA mapping failure");
+ return -EIO;
+ }
+
+ q->qbuf = qzone->addr;
+ memset(q->qbuf, 0, len);
+ /* VFIO mode */
+ q->qbuf_dma = rte_mem_virt2iova(q->qbuf);
+ desc = (struct mcdma_desc *)q->qbuf;
+
+ /* Initialize link bit of all descs to zero */
+ for (i = 0; i < q->num_descs; i++)
+ desc[i].link = 0;
+
+ /* For every desc at the page boundary src points to the addrss of next desc */
+ for (i = MCDMA_NUM_DESC_PER_PAGE; i <= q->num_descs; i += MCDMA_NUM_DESC_PER_PAGE) {
+ desc[i - 1].link = 1;
+ /*
+ * src for last descriptor points the address for desc ring,
+ * else next page
+ */
+ desc[i - 1].src = (i == q->num_descs) ?
+ rte_mem_virt2iova(q->qbuf) : rte_mem_virt2iova(desc + i);
+ }
+
+ q->wbe = 1;
+ return 0;
+}
+
+static int mcdma_reset_queue(struct mcdma_queue *q)
+{
+ return mcdma_reg_write_verify(q, MCDMA_QCSR_Q_RESET, MCDMA_QCSR_Q_RESET_MASK,
+ 1, MCDMA_QCSR_Q_RESET_SHIFT, MCDMA_Q_RESET_TO, 0);
+}
+
+/* Stop DMA before releasing mbufs still posted on the ring. */
+static int mcdma_quiesce_queue(struct mcdma_queue *q)
+{
+ int ret;
+
+ ret = mcdma_wait_queue_drain(q);
+ if (ret != 0)
+ return ret;
+
+ mcdma_dev_descq_disable(q);
+
+ ret = mcdma_reset_queue(q);
+ if (ret != 0) {
+ PMD_DRV_LOG(ERR, "queue %u reset failed (%d)", q->qid, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mcdma_dev_queue_init(struct mcdma_queue *q)
+{
+ struct rte_eth_dev *dev = q->ethdev;
+ mcdma_qcsr_q_ctrl_t q_ctrl;
+ uint32_t payload;
+ uint64_t cons_idx;
+
+ /* Empty queue: all HW didx counters at 0; first fill is didx 1. */
+ q->cons_didx = 0;
+ q->freed_cons_didx = 0;
+ q->submitted_didx = 0;
+ q->prod_didx = 0;
+ q->didx = 0;
+ q->burst_size = MCDMA_MAX_BURST;
+
+ mcdma_qcsr_reg_writel(q->qcsr, MCDMA_QCSR_Q_START_ADDR_L, q->qbuf_dma);
+ mcdma_qcsr_reg_writel(q->qcsr, MCDMA_QCSR_Q_START_ADDR_H, q->qbuf_dma >> 32);
+ mcdma_qcsr_reg_writel(q->qcsr, MCDMA_QCSR_Q_SIZE, (uint32_t)log2(q->num_descs));
+ cons_idx = rte_mem_virt2iova((const void *)(uintptr_t)&q->cons_didx);
+ mcdma_qcsr_reg_writel(q->qcsr, MCDMA_QCSR_Q_CONSUMED_HEAD_ADDR_L, cons_idx);
+ mcdma_qcsr_reg_writel(q->qcsr, MCDMA_QCSR_Q_CONSUMED_HEAD_ADDR_H,
+ cons_idx >> 32);
+ mcdma_qcsr_reg_writel(q->qcsr, MCDMA_QCSR_Q_BATCH_DELAY,
+ MCDMA_BATCH_DESC_DELAY);
+
+ if (q->dir == MCDMA_DIR_D2H) {
+ if (q->rx_seg_len == 0) {
+ PMD_DRV_LOG(ERR, "Rx queue %u: invalid segment length",
+ q->qid);
+ return -EINVAL;
+ }
+ payload = RTE_ALIGN(q->rx_seg_len, RTE_CACHE_LINE_SIZE);
+ } else {
+ payload = RTE_ALIGN(mcdma_rx_frame_len_from_mtu(mcdma_dev_mtu_get(dev)),
+ RTE_CACHE_LINE_SIZE);
+ }
+
+ mcdma_qcsr_reg_writel(q->qcsr, MCDMA_QCSR_Q_CPL_TIMEOUT, 0);
+ mcdma_qcsr_reg_writel(q->qcsr, MCDMA_QCSR_Q_PAYLOAD_COUNT, payload);
+
+ q_ctrl.val = 0;
+ q_ctrl.field.q_en = 1;
+ if (q->wbe)
+ q_ctrl.field.q_wb_en = 1;
+
+ mcdma_qcsr_reg_writel(q->qcsr, MCDMA_QCSR_Q_CTRL, q_ctrl.val);
+ return 0;
+}
+
+static int mcdma_dev_tx_queue_setup(struct rte_eth_dev *dev, uint16_t tx_queue_id,
+ uint16_t nb_tx_desc,
+ unsigned int socket_id,
+ const struct rte_eth_txconf *tx_conf __rte_unused)
+{
+ struct mcdma_pci_device *mcdma_dev = dev->data->dev_private;
+ struct mcdma_queue *txq = NULL;
+ int ret;
+
+ if (tx_queue_id >= mcdma_dev->ip_num_chan) {
+ PMD_DRV_LOG(ERR, "TX queue id %u >= max channels %u",
+ tx_queue_id, mcdma_dev->ip_num_chan);
+ return -EINVAL;
+ }
+
+ ret = mcdma_sw_descq_alloc(dev, &txq, MCDMA_DIR_H2D, tx_queue_id,
+ nb_tx_desc, socket_id);
+ if (ret < 0)
+ return ret;
+
+ ret = mcdma_dev_descq_alloc(dev, txq);
+ if (ret < 0) {
+ mcdma_dev_descq_free(txq);
+ rte_free(txq);
+ return ret;
+ }
+
+ ret = mcdma_reset_queue(txq);
+ if (ret) {
+ mcdma_dev_descq_free(txq);
+ rte_free(txq);
+ return ret;
+ }
+
+ dev->data->tx_queues[tx_queue_id] = txq;
+ return 0;
+}
+
+static int mcdma_dev_rx_queue_setup(struct rte_eth_dev *dev, uint16_t rx_queue_id,
+ uint16_t nb_rx_desc,
+ unsigned int socket_id,
+ const struct rte_eth_rxconf *rx_conf __rte_unused,
+ struct rte_mempool *mb_pool)
+{
+ struct mcdma_pci_device *mcdma_dev = dev->data->dev_private;
+ struct mcdma_queue *rxq = NULL;
+ int ret;
+
+ if (mb_pool)
+ PMD_DRV_LOG_DBG(ERR,
+ "qid: %u, mbuf_avail_count = %u, mbuf_in_use_count = %u",
+ rx_queue_id, rte_mempool_avail_count(mb_pool),
+ rte_mempool_in_use_count(mb_pool));
+ if (rx_queue_id >= mcdma_dev->ip_num_chan) {
+ PMD_DRV_LOG(ERR, "RX queue id %u >= max channels %u",
+ rx_queue_id, mcdma_dev->ip_num_chan);
+ return -EINVAL;
+ }
+
+ if (mb_pool == NULL) {
+ PMD_DRV_LOG(ERR, "Rx queue %u: mbuf pool required", rx_queue_id);
+ return -EINVAL;
+ }
+
+ ret = mcdma_sw_descq_alloc(dev, &rxq, MCDMA_DIR_D2H, rx_queue_id,
+ nb_rx_desc, socket_id);
+ if (ret < 0)
+ return ret;
+
+ rxq->mb_pool = mb_pool;
+
+ ret = mcdma_rx_seg_len_for_pool(dev, mb_pool, &rxq->rx_seg_len);
+ if (ret != 0) {
+ mcdma_dev_descq_free(rxq);
+ rte_free(rxq);
+ return ret;
+ }
+
+ ret = mcdma_dev_descq_alloc(dev, rxq);
+ if (ret < 0) {
+ mcdma_dev_descq_free(rxq);
+ rte_free(rxq);
+ return ret;
+ }
+
+ ret = mcdma_reset_queue(rxq);
+ if (ret) {
+ mcdma_dev_descq_free(rxq);
+ rte_free(rxq);
+ return ret;
+ }
+
+ dev->data->rx_queues[rx_queue_id] = rxq;
+ return 0;
+}
+
+static int mcdma_dev_tx_queue_start(struct rte_eth_dev *dev, uint16_t qid)
+{
+ struct mcdma_queue *txq = dev->data->tx_queues[qid];
+ int ret;
+
+ if (txq == NULL)
+ return -EINVAL;
+
+ ret = mcdma_dev_queue_init(txq);
+ if (ret)
+ return ret;
+
+ dev->data->tx_queue_state[qid] = RTE_ETH_QUEUE_STATE_STARTED;
+ return 0;
+}
+
+static int mcdma_dev_rx_queue_start(struct rte_eth_dev *dev, uint16_t qid)
+{
+ struct mcdma_queue *rxq = dev->data->rx_queues[qid];
+ int ret;
+
+ if (rxq == NULL)
+ return -EINVAL;
+
+ ret = mcdma_dev_queue_init(rxq);
+ if (ret)
+ return ret;
+
+ dev->data->rx_queue_state[qid] = RTE_ETH_QUEUE_STATE_STARTED;
+ return 0;
+}
+
+static int mcdma_dev_start(struct rte_eth_dev *dev)
+{
+ uint16_t qid;
+ int err;
+
+ for (qid = 0; qid < dev->data->nb_tx_queues; qid++) {
+ if (dev->data->tx_queues[qid] == NULL)
+ continue;
+ err = mcdma_dev_tx_queue_start(dev, qid);
+ if (err != 0)
+ goto stop_started;
+ }
+
+ for (qid = 0; qid < dev->data->nb_rx_queues; qid++) {
+ if (dev->data->rx_queues[qid] == NULL)
+ continue;
+ err = mcdma_dev_rx_queue_start(dev, qid);
+ if (err != 0)
+ goto stop_started;
+ }
+
+ return 0;
+
+stop_started:
+ for (qid = 0; qid < dev->data->nb_rx_queues; qid++) {
+ if (dev->data->rx_queues[qid] == NULL)
+ continue;
+ (void)mcdma_dev_rx_queue_stop(dev, qid);
+ }
+ for (qid = 0; qid < dev->data->nb_tx_queues; qid++) {
+ if (dev->data->tx_queues[qid] == NULL)
+ continue;
+ (void)mcdma_dev_tx_queue_stop(dev, qid);
+ }
+
+ return err;
+}
+
+static int mcdma_dev_stop(struct rte_eth_dev *dev)
+{
+ uint16_t qid;
+ int ret;
+ int err = 0;
+
+ for (qid = 0; qid < dev->data->nb_tx_queues; qid++) {
+ ret = mcdma_dev_tx_queue_stop(dev, qid);
+ if (ret != 0 && err == 0)
+ err = ret;
+ }
+ for (qid = 0; qid < dev->data->nb_rx_queues; qid++) {
+ ret = mcdma_dev_rx_queue_stop(dev, qid);
+ if (ret != 0 && err == 0)
+ err = ret;
+ }
+
+ return err;
+}
+
+static int mcdma_dev_link_update(struct rte_eth_dev *dev,
+ __rte_unused int wait_to_complete)
+{
+ dev->data->dev_link.link_status = RTE_ETH_LINK_UP;
+ dev->data->dev_link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+
+ return 0;
+}
+
+static void
+mcdma_dev_queue_release_common(struct rte_eth_dev *dev, struct mcdma_queue *q,
+ uint16_t qid, bool is_rx)
+{
+ if (q == NULL)
+ return;
+
+ if (is_rx) {
+ if (dev->data->rx_queue_state[qid] == RTE_ETH_QUEUE_STATE_STARTED) {
+ if (mcdma_quiesce_queue(q) != 0)
+ PMD_DRV_LOG(WARNING,
+ "Rx queue %u quiesce failed on release",
+ qid);
+ mcdma_sw_ring_free_inflight(q);
+ dev->data->rx_queue_state[qid] =
+ RTE_ETH_QUEUE_STATE_STOPPED;
+ }
+ dev->data->rx_queues[qid] = NULL;
+ } else {
+ if (dev->data->tx_queue_state[qid] == RTE_ETH_QUEUE_STATE_STARTED) {
+ if (mcdma_quiesce_queue(q) != 0)
+ PMD_DRV_LOG(WARNING,
+ "Tx queue %u quiesce failed on release",
+ qid);
+ mcdma_sw_ring_free_inflight(q);
+ dev->data->tx_queue_state[qid] =
+ RTE_ETH_QUEUE_STATE_STOPPED;
+ }
+ dev->data->tx_queues[qid] = NULL;
+ }
+
+ mcdma_dev_descq_disable(q);
+ mcdma_dev_descq_free(q);
+ rte_free(q);
+}
+
+static void mcdma_dev_tx_queue_release(struct rte_eth_dev *dev, uint16_t qid)
+{
+ mcdma_dev_queue_release_common(dev, dev->data->tx_queues[qid], qid,
+ false);
+}
+
+static void mcdma_dev_rx_queue_release(struct rte_eth_dev *dev, uint16_t qid)
+{
+ mcdma_dev_queue_release_common(dev, dev->data->rx_queues[qid], qid,
+ true);
+}
+
+static int mcdma_dev_tx_queue_stop(struct rte_eth_dev *dev, uint16_t qid)
+{
+ struct mcdma_queue *q;
+ int ret;
+
+ if (!dev->data->tx_queue_state[qid])
+ return 0;
+
+ q = dev->data->tx_queues[qid];
+ if (q == NULL)
+ return 0;
+
+ ret = mcdma_quiesce_queue(q);
+ if (ret != 0)
+ return ret;
+
+ mcdma_sw_ring_free_inflight(q);
+
+ dev->data->tx_queue_state[qid] = RTE_ETH_QUEUE_STATE_STOPPED;
+ return 0;
+}
+
+static int mcdma_dev_rx_queue_stop(struct rte_eth_dev *dev, uint16_t qid)
+{
+ struct mcdma_queue *q;
+ int ret;
+
+ if (!dev->data->rx_queue_state[qid])
+ return 0;
+
+ q = dev->data->rx_queues[qid];
+ if (q == NULL)
+ return 0;
+
+ ret = mcdma_quiesce_queue(q);
+ if (ret != 0)
+ return ret;
+
+ mcdma_sw_ring_free_inflight(q);
+
+ dev->data->rx_queue_state[qid] = RTE_ETH_QUEUE_STATE_STOPPED;
+ return 0;
+}
+
+static int mcdma_dev_infos_get(struct rte_eth_dev *dev,
+ struct rte_eth_dev_info *dev_info)
+{
+ struct mcdma_pci_device *mcdma_dev = dev->data->dev_private;
+
+ dev_info->max_rx_queues = mcdma_dev->ip_num_chan;
+ dev_info->max_tx_queues = mcdma_dev->ip_num_chan;
+ dev_info->max_rx_pktlen = MCDMA_MAX_RX_FRAME_LEN;
+ dev_info->max_mac_addrs = MCDMA_MAX_MAC_ADDRS;
+ dev_info->default_rxportconf.burst_size = MCDMA_MAX_BURST;
+ dev_info->dev_capa =
+ RTE_ETH_DEV_CAPA_RUNTIME_RX_QUEUE_SETUP |
+ RTE_ETH_DEV_CAPA_RUNTIME_TX_QUEUE_SETUP;
+ dev_info->dev_capa &= ~RTE_ETH_DEV_CAPA_FLOW_RULE_KEEP;
+ /* Multi-seg Tx: one sw_ring owner (head on EOF desc); see H2D prepare. */
+ dev_info->tx_offload_capa = RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
+ dev_info->min_mtu = MCDMA_MIN_MTU;
+ dev_info->max_mtu = MCDMA_MAX_MTU;
+ dev_info->rx_desc_lim.nb_max = MCDMA_MAX_RING_DESCS;
+ dev_info->rx_desc_lim.nb_min = MCDMA_NUM_DESC_PER_PAGE;
+ dev_info->rx_desc_lim.nb_align = MCDMA_NUM_DESC_PER_PAGE;
+ dev_info->tx_desc_lim.nb_max = MCDMA_MAX_RING_DESCS;
+ dev_info->tx_desc_lim.nb_min = MCDMA_NUM_DESC_PER_PAGE;
+ dev_info->tx_desc_lim.nb_align = MCDMA_NUM_DESC_PER_PAGE;
+
+ return 0;
+}
+
+static int mcdma_dev_configure(struct rte_eth_dev *dev)
+{
+ struct mcdma_pci_device *mcdma_dev = dev->data->dev_private;
+ int ret;
+
+ if (RTE_MAX(dev->data->nb_rx_queues, dev->data->nb_tx_queues) >
+ (uint16_t)mcdma_dev->ip_num_chan) {
+ PMD_DRV_LOG(ERR, "Max number of channels supported %u",
+ mcdma_dev->ip_num_chan);
+ return -EINVAL;
+ }
+
+ ret = mcdma_rx_frame_len_check(dev,
+ mcdma_rx_frame_len_from_mtu(mcdma_dev_mtu_get(dev)));
+ if (ret != 0)
+ return ret;
+
+ if (dev->data->dev_conf.rxmode.offloads & RTE_ETH_RX_OFFLOAD_SCATTER) {
+ PMD_DRV_LOG(ERR, "Rx scattered/multi-segment mbufs not supported");
+ return -EINVAL;
+ }
+
+ dev->data->scattered_rx = 0;
+
+ /* perform IP Reset - only from PF0 */
+ if (mcdma_dev->is_pf && !mcdma_dev->pf_idx) {
+ mcdma_dmacsr_reg_writel(mcdma_dev->bar_base,
+ MCDMA_CSR_SW_RESET, 0x1);
+ rte_delay_ms(MCDMA_SW_RESET_DELAY_MS);
+ }
+
+ return 0;
+}
+
+static int mcdma_dev_mtu_set(struct rte_eth_dev *dev, uint16_t mtu)
+{
+ uint32_t frame_len;
+ uint16_t qid;
+ int ret;
+
+ if (mtu < MCDMA_MIN_MTU || mtu > MCDMA_MAX_MTU)
+ return -EINVAL;
+
+ frame_len = mcdma_rx_frame_len_from_mtu(mtu);
+ ret = mcdma_rx_frame_len_check(dev, frame_len);
+ if (ret != 0)
+ return ret;
+
+ for (qid = 0; qid < dev->data->nb_rx_queues; qid++) {
+ struct mcdma_queue *rxq = dev->data->rx_queues[qid];
+ uint32_t buf_len;
+
+ if (rxq == NULL || rxq->mb_pool == NULL)
+ continue;
+
+ buf_len = rte_pktmbuf_data_room_size(rxq->mb_pool);
+ if (buf_len <= RTE_PKTMBUF_HEADROOM) {
+ PMD_DRV_LOG(ERR, "port %u: mbuf pool data room %u too small",
+ dev->data->port_id, buf_len);
+ return -EINVAL;
+ }
+ buf_len -= RTE_PKTMBUF_HEADROOM;
+ if (frame_len > buf_len) {
+ PMD_DRV_LOG(ERR,
+ "port %u: Rx frame %u B exceeds mbuf data room %u B (MTU %u)",
+ dev->data->port_id, frame_len, buf_len, mtu);
+ return -EINVAL;
+ }
+
+ rxq->rx_seg_len = frame_len;
+ }
+
+ dev->data->dev_conf.rxmode.mtu = mtu;
+ dev->data->mtu = mtu;
+
+ return 0;
+}
+
+static uint64_t
+mcdma_qcsr_read_drop_count(struct mcdma_queue *q)
+{
+ uint32_t reg;
+
+ reg = mcdma_qcsr_reg_readl(q->qcsr, MCDMA_QCSR_Q_DATA_DRP_ERR_CTR);
+ if (reg & MCDMA_QCSR_DRP_CTR_VALID) {
+ q->drop_pkts += reg & MCDMA_QCSR_DRP_CTR_CNT_MASK;
+ reg &= ~MCDMA_QCSR_DRP_CTR_CLR_MASK;
+ mcdma_qcsr_reg_writel(q->qcsr, MCDMA_QCSR_Q_DATA_DRP_ERR_CTR, reg);
+ }
+
+ return q->drop_pkts;
+}
+
+static uint64_t
+mcdma_qcsr_read_ptr_errors(struct mcdma_queue *q)
+{
+ uint32_t reg;
+ uint64_t errs = 0;
+
+ reg = mcdma_qcsr_reg_readl(q->qcsr, MCDMA_QCSR_Q_HEAD_POINTER);
+ if (reg & MCDMA_QCSR_PTR_ERR_MASK)
+ errs++;
+
+ reg = mcdma_qcsr_reg_readl(q->qcsr, MCDMA_QCSR_Q_COMPLETED_POINTER);
+ if (reg & MCDMA_QCSR_PTR_ERR_MASK)
+ errs++;
+
+ return errs;
+}
+
+int mcdma_dev_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
+ struct eth_queue_stats *qstats)
+{
+ struct mcdma_queue *q;
+ uint16_t i;
+ uint64_t drops;
+ uint64_t q_errs;
+
+ if (stats == NULL)
+ return -EINVAL;
+
+ memset(stats, 0, sizeof(*stats));
+
+ for (i = 0; i < dev->data->nb_rx_queues; i++) {
+ q = dev->data->rx_queues[i];
+ if (q == NULL)
+ continue;
+
+ drops = mcdma_qcsr_read_drop_count(q);
+ q_errs = mcdma_qcsr_read_ptr_errors(q);
+ stats->ipackets += q->rx_pkts;
+ stats->ibytes += q->rx_bytes;
+ stats->imissed += drops;
+ stats->ierrors += q_errs;
+ stats->rx_nombuf += q->rx_nombuf;
+
+ if (qstats != NULL) {
+ qstats[i].q_ipackets = q->rx_pkts;
+ qstats[i].q_ibytes = q->rx_bytes;
+ }
+ }
+
+ for (i = 0; i < dev->data->nb_tx_queues; i++) {
+ q = dev->data->tx_queues[i];
+ if (q == NULL)
+ continue;
+
+ (void)mcdma_qcsr_read_drop_count(q);
+ q_errs = mcdma_qcsr_read_ptr_errors(q);
+ stats->opackets += q->tx_pkts;
+ stats->obytes += q->tx_bytes;
+ stats->oerrors += q_errs;
+
+ if (qstats != NULL) {
+ qstats[i].q_opackets = q->tx_pkts;
+ qstats[i].q_obytes = q->tx_bytes;
+ }
+ }
+
+ return 0;
+}
+
+static unsigned int
+mcdma_xstats_count(const struct rte_eth_dev *dev)
+{
+ return dev->data->nb_rx_queues + dev->data->nb_tx_queues;
+}
+
+static int
+mcdma_dev_xstats_get_names(struct rte_eth_dev *dev,
+ struct rte_eth_xstat_name *xstats_names,
+ unsigned int n)
+{
+ unsigned int i;
+ unsigned int count = 0;
+ unsigned int nstats = mcdma_xstats_count(dev);
+
+ if (xstats_names == NULL || n < nstats)
+ return nstats;
+
+ for (i = 0; i < dev->data->nb_rx_queues; i++) {
+ snprintf(xstats_names[count].name,
+ sizeof(xstats_names[count].name),
+ "rx_q%u_errors", i);
+ count++;
+ }
+
+ for (i = 0; i < dev->data->nb_tx_queues; i++) {
+ snprintf(xstats_names[count].name,
+ sizeof(xstats_names[count].name),
+ "tx_q%u_errors", i);
+ count++;
+ }
+
+ return count;
+}
+
+static int
+mcdma_dev_xstats_get(struct rte_eth_dev *dev, struct rte_eth_xstat *xstats,
+ unsigned int n)
+{
+ unsigned int i;
+ unsigned int count = 0;
+ unsigned int nstats = mcdma_xstats_count(dev);
+
+ if (xstats == NULL || n < nstats)
+ return nstats;
+
+ for (i = 0; i < dev->data->nb_rx_queues; i++) {
+ struct mcdma_queue *q = dev->data->rx_queues[i];
+ uint64_t errors = 0;
+
+ if (q != NULL)
+ errors = mcdma_qcsr_read_drop_count(q) +
+ mcdma_qcsr_read_ptr_errors(q);
+
+ xstats[count].id = count;
+ xstats[count].value = errors;
+ count++;
+ }
+
+ for (i = 0; i < dev->data->nb_tx_queues; i++) {
+ struct mcdma_queue *q = dev->data->tx_queues[i];
+ uint64_t errors = 0;
+
+ if (q != NULL)
+ errors = mcdma_qcsr_read_ptr_errors(q);
+
+ xstats[count].id = count;
+ xstats[count].value = errors;
+ count++;
+ }
+
+ return count;
+}
+
+static struct eth_dev_ops mcdma_ethdev_ops = {
+ .dev_configure = mcdma_dev_configure,
+ .dev_infos_get = mcdma_dev_infos_get,
+ .dev_start = mcdma_dev_start,
+ .dev_stop = mcdma_dev_stop,
+ .link_update = mcdma_dev_link_update,
+ .rx_queue_setup = mcdma_dev_rx_queue_setup,
+ .tx_queue_setup = mcdma_dev_tx_queue_setup,
+ .rx_queue_release = mcdma_dev_rx_queue_release,
+ .tx_queue_release = mcdma_dev_tx_queue_release,
+ .rx_queue_start = mcdma_dev_rx_queue_start,
+ .rx_queue_stop = mcdma_dev_rx_queue_stop,
+ .tx_queue_start = mcdma_dev_tx_queue_start,
+ .tx_queue_stop = mcdma_dev_tx_queue_stop,
+ .stats_get = mcdma_dev_stats_get,
+ .xstats_get = mcdma_dev_xstats_get,
+ .xstats_get_names = mcdma_dev_xstats_get_names,
+ .mtu_set = mcdma_dev_mtu_set,
+};
+
+void mcdma_ethdev_ops_init(struct rte_eth_dev *dev)
+{
+ dev->dev_ops = &mcdma_ethdev_ops;
+}
diff --git a/drivers/net/altera/mcdma/meson.build b/drivers/net/altera/mcdma/meson.build
new file mode 100644
index 000000000000..9195a5d34982
--- /dev/null
+++ b/drivers/net/altera/mcdma/meson.build
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2020-2026 Altera Corporation
+
+if not is_linux
+ build = false
+ reason = 'only supported on Linux'
+endif
+
+sources += files(
+ 'mcdma_eth.c',
+ 'mcdma_eth_ops.c',
+)
diff --git a/drivers/net/meson.build b/drivers/net/meson.build
index 4e8ccb945fa7..aef8af277088 100644
--- a/drivers/net/meson.build
+++ b/drivers/net/meson.build
@@ -68,6 +68,7 @@ drivers = [
'vmxnet3',
'xsc',
'zxdh',
+ 'altera/mcdma',
]
std_deps = ['ethdev', 'kvargs'] # 'ethdev' also pulls in mbuf, net, eal etc
std_deps += ['bus_pci'] # very many PMDs depend on PCI, so make std
diff --git a/usertools/dpdk-devbind.py b/usertools/dpdk-devbind.py
index 15b19acb2781..8edfde33d832 100755
--- a/usertools/dpdk-devbind.py
+++ b/usertools/dpdk-devbind.py
@@ -30,6 +30,7 @@
{'Class': '05', 'Vendor': '1af4', 'Device': '1110'}, # AVP vNIC
{'Class': '08', 'Vendor': '177d', 'Device': 'a0dd,a049'}, # Cavium PKX
{'Class': '12', 'Vendor': '8086', 'Device': '0b30'}, # Intel FPGA
+ {'Class': '06', 'Vendor': '8086', 'Device': '1172'}, # Altera FPGA
]
},
'baseband': {
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [RFC 2/4] net/mcdma: add Rx and Tx packet burst paths
2026-11-09 16:09 [RFC 0/4] net/mcdma: add Altera MCDMA poll mode driver Subhransu S. Prusty
2026-11-09 16:09 ` [RFC 1/4] net/mcdma: add Altera MCDMA poll mode driver skeleton Subhransu S. Prusty
@ 2026-11-09 16:09 ` Subhransu S. Prusty
2026-11-09 16:09 ` [RFC 3/4] net/mcdma: add experimental PIO access APIs Subhransu S. Prusty
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Subhransu S. Prusty @ 2026-11-09 16:09 UTC (permalink / raw)
To: dev
Cc: ferruh.yigit, stephen, rtice, cheryl.bansal, nanditha.jayarajan,
Subhransu S. Prusty
Implement the datapath burst handlers that drive H2D and D2H descriptor
queues. Tx prepares multi-segment mbuf chains on the software ring,
programs data and link descriptors (including page-boundary links),
updates the QCSR tail pointer, and completes finished requests. Rx
reaps completed descriptors, refills buffers, and delivers mbufs to
the application.
Wire the burst callbacks into the ethdev operations table and build
mcdma_eth_rxtx.c as part of librte_net_mcdma.
Co-developed-by: Cheryl Bansal <cheryl.bansal@altera.com>
Signed-off-by: Cheryl Bansal <cheryl.bansal@altera.com>
Signed-off-by: Subhransu S. Prusty <subhransu.sekhar.prusty@altera.com>
---
drivers/net/altera/mcdma/mcdma_eth.h | 2 +
drivers/net/altera/mcdma/mcdma_eth_ops.c | 2 +
drivers/net/altera/mcdma/mcdma_eth_rxtx.c | 401 ++++++++++++++++++++++
drivers/net/altera/mcdma/meson.build | 1 +
4 files changed, 406 insertions(+)
create mode 100644 drivers/net/altera/mcdma/mcdma_eth_rxtx.c
diff --git a/drivers/net/altera/mcdma/mcdma_eth.h b/drivers/net/altera/mcdma/mcdma_eth.h
index c3b74474c10c..07e5d39aa1f4 100644
--- a/drivers/net/altera/mcdma/mcdma_eth.h
+++ b/drivers/net/altera/mcdma/mcdma_eth.h
@@ -327,6 +327,8 @@ static inline void mcdma_pio_reg_write64(void *pio, uint64_t offset, uint64_t va
}
void mcdma_ethdev_ops_init(struct rte_eth_dev *dev);
+uint16_t mcdma_xmit_pkts(void *txq, struct rte_mbuf **tx_pkts, uint16_t nb_pkts);
+uint16_t mcdma_recv_pkts(void *rxq, struct rte_mbuf **rx_pkts, uint16_t nb_pkts);
int mcdma_dev_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
struct eth_queue_stats *qstats);
diff --git a/drivers/net/altera/mcdma/mcdma_eth_ops.c b/drivers/net/altera/mcdma/mcdma_eth_ops.c
index 8e7624312fe0..ac57eb26531e 100644
--- a/drivers/net/altera/mcdma/mcdma_eth_ops.c
+++ b/drivers/net/altera/mcdma/mcdma_eth_ops.c
@@ -928,4 +928,6 @@ static struct eth_dev_ops mcdma_ethdev_ops = {
void mcdma_ethdev_ops_init(struct rte_eth_dev *dev)
{
dev->dev_ops = &mcdma_ethdev_ops;
+ dev->rx_pkt_burst = &mcdma_recv_pkts;
+ dev->tx_pkt_burst = &mcdma_xmit_pkts;
}
diff --git a/drivers/net/altera/mcdma/mcdma_eth_rxtx.c b/drivers/net/altera/mcdma/mcdma_eth_rxtx.c
new file mode 100644
index 000000000000..05c01d7f2150
--- /dev/null
+++ b/drivers/net/altera/mcdma/mcdma_eth_rxtx.c
@@ -0,0 +1,401 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020-2026 Altera Corporation
+ */
+
+#include <string.h>
+
+#include <rte_malloc.h>
+
+#include "mcdma_eth.h"
+
+static uint16_t mcdma_descq_ring_avail(struct mcdma_queue *q)
+{
+ uint16_t used;
+
+ rte_rmb();
+ used = MCDMA_RING_USED_COUNT(q->prod_didx, q->freed_cons_didx, q->num_descs);
+ if (used >= q->num_descs - 1)
+ return 0;
+
+ /* Reserve one slot to distinguish full from empty. */
+ return q->num_descs - 1 - used;
+}
+
+/* Ring slots consumed by nb_segs data descriptors, including link slots. */
+static uint16_t
+mcdma_desc_slots_for_segs(struct mcdma_queue *q, uint16_t nb_segs)
+{
+ uint16_t prod = MCDMA_NEXT_IDX(mcdma_queue_prod_ring(q), q->num_descs);
+ uint16_t slots = 0;
+ uint16_t i;
+
+ for (i = 0; i < nb_segs; i++) {
+ struct mcdma_desc *d = MCDMA_DESC_PTR(q, prod);
+
+ if (d->link) {
+ slots++;
+ prod = MCDMA_NEXT_IDX(prod, q->num_descs);
+ }
+ slots++;
+ prod = MCDMA_NEXT_IDX(prod, q->num_descs);
+ }
+ return slots;
+}
+
+/*
+ * Link descriptors sit at every page boundary (idx 127, 255, ...).
+ * Jump to each link slot instead of walking every ring position.
+ */
+static uint16_t
+mcdma_desc_segs_for_slots(struct mcdma_queue *q, uint16_t slots)
+{
+ uint16_t prod = MCDMA_NEXT_IDX(mcdma_queue_prod_ring(q), q->num_descs);
+ uint16_t links = 0;
+ uint16_t remaining = slots;
+ uint16_t pos = prod;
+
+ while (remaining > 0) {
+ uint16_t off = pos & (MCDMA_NUM_DESC_PER_PAGE - 1);
+ uint16_t to_link;
+
+ if (off == MCDMA_NUM_DESC_PER_PAGE - 1) {
+ links++;
+ pos = MCDMA_NEXT_IDX(pos, q->num_descs);
+ remaining--;
+ continue;
+ }
+
+ to_link = (MCDMA_NUM_DESC_PER_PAGE - 1) - off;
+ if (remaining <= to_link)
+ break;
+
+ links++;
+ remaining -= to_link + 1;
+ pos = (pos + to_link + 1) % q->num_descs;
+ }
+
+ return slots - links;
+}
+
+static uint16_t mcdma_complete_requests(struct mcdma_queue *q,
+ struct rte_mbuf **pkts, uint16_t nb_pkts,
+ int dir)
+{
+ struct mcdma_desc *d;
+ struct rte_mbuf *mb;
+ uint16_t nb_to_free;
+ uint16_t idx;
+
+ rte_rmb();
+ nb_to_free = MCDMA_RING_USED_COUNT(q->cons_didx,
+ q->freed_cons_didx, q->num_descs);
+
+ if (dir == MCDMA_DIR_H2D) {
+ uint16_t i;
+
+ for (i = 0; i < nb_to_free; i++) {
+ idx = MCDMA_NEXT_IDX(mcdma_queue_freed_ring(q),
+ q->num_descs);
+ d = MCDMA_DESC_PTR(q, idx);
+ if (!d->link && d->eof && q->sw_ring[idx] != NULL)
+ rte_pktmbuf_free(q->sw_ring[idx]);
+
+ q->sw_ring[idx] = NULL;
+ q->freed_cons_didx = d->didx;
+ if (!d->link)
+ q->complete++;
+ }
+
+ } else {
+ uint16_t nb_returned = 0;
+ uint16_t nb_avail = nb_to_free;
+
+ while (nb_avail > 0) {
+ idx = MCDMA_NEXT_IDX(mcdma_queue_freed_ring(q),
+ q->num_descs);
+ d = MCDMA_DESC_PTR(q, idx);
+
+ if (d->link) {
+ q->freed_cons_didx = d->didx;
+ nb_avail--;
+ continue;
+ }
+
+ mb = q->sw_ring[idx];
+ /*
+ * Non-scatter Rx: one buffer holds the full frame.
+ * Deliver only on EOF with SOF on the same descriptor.
+ */
+ if (mb != NULL && d->eof && d->sof && !d->desc_invalid &&
+ nb_returned >= nb_pkts)
+ break;
+
+ q->freed_cons_didx = d->didx;
+ nb_avail--;
+ q->complete++;
+
+ q->sw_ring[idx] = NULL;
+ if (mb == NULL) {
+ q->drop_pkts++;
+ continue;
+ }
+
+ if (!d->eof || !d->sof || d->desc_invalid) {
+ rte_pktmbuf_free(mb);
+ q->drop_pkts++;
+ continue;
+ }
+
+ mb->pkt_len = d->rx_pyld_cnt;
+ rte_pktmbuf_data_len(mb) = d->rx_pyld_cnt;
+
+ pkts[nb_returned++] = mb;
+ q->rx_pkts++;
+ q->rx_bytes += mb->pkt_len;
+ }
+ nb_to_free = nb_returned;
+ }
+
+ rte_wmb();
+
+ /* return number of packets processed */
+ return nb_to_free;
+}
+
+static int mcdma_descq_queue_prepare(struct mcdma_queue *q,
+ struct rte_mbuf *mb, int dir)
+{
+ struct mcdma_desc *d;
+ uint16_t prod_nxt = MCDMA_NEXT_IDX(mcdma_queue_prod_ring(q), q->num_descs);
+ uint16_t prod_didx = q->prod_didx;
+ uint16_t prod;
+ uint8_t first_desc = 1;
+ struct rte_mbuf *seg = mb;
+ int cnt = 0;
+
+ if (dir == MCDMA_DIR_D2H &&
+ (mb->nb_segs != 1 || mb->next != NULL)) {
+ PMD_DRV_LOG(ERR,
+ "Rx queue %u: multi-segment mbuf not supported (nb_segs=%u)",
+ q->qid, mb->nb_segs);
+ return -EINVAL;
+ }
+
+ for (cnt = 0; cnt < mb->nb_segs; cnt++) {
+ prod = prod_nxt;
+ d = MCDMA_DESC_PTR(q, prod);
+ if (d->link) { /* skip the link desc */
+ q->didx = MCDMA_DIDX_NEXT(q->didx, q->num_descs);
+ d->didx = q->didx;
+ prod = MCDMA_NEXT_IDX(prod, q->num_descs);
+ d = MCDMA_DESC_PTR(q, prod);
+ }
+
+ memset(d, 0, sizeof(*d));
+ if (dir == MCDMA_DIR_D2H) {
+ /* Only ST supported for now */
+ d->dest = rte_pktmbuf_iova(seg);
+ d->len = RTE_MIN(q->rx_seg_len, rte_pktmbuf_tailroom(seg));
+ } else {
+ uint32_t seg_len = rte_pktmbuf_data_len(seg);
+
+ if (seg_len > MCDMA_IP_MAX_FRAME_LEN)
+ seg_len = MCDMA_IP_MAX_FRAME_LEN;
+
+ d->len = seg_len;
+ /* Only ST supported for now */
+ d->src = rte_pktmbuf_iova(seg);
+
+ /* Default sof on the first mbuf and eof on last */
+ if (first_desc) {
+ d->sof = 1;
+ first_desc = 0;
+ }
+ if (seg->next == NULL)
+ d->eof = 1;
+ }
+
+ if (dir == MCDMA_DIR_H2D) {
+ /*
+ * One sw_ring owner per packet: head mbuf on the EOF
+ * descriptor so completion frees the whole chain once.
+ */
+ q->sw_ring[prod] = d->eof ? mb : NULL;
+ } else {
+ q->sw_ring[prod] = seg;
+ }
+ q->didx = MCDMA_DIDX_NEXT(q->didx, q->num_descs);
+ d->didx = q->didx;
+ prod_didx = q->didx;
+ seg = seg->next;
+ prod_nxt = MCDMA_NEXT_IDX(prod, q->num_descs);
+ }
+
+ q->prod_didx = prod_didx;
+
+ return 0;
+}
+
+static void mcdma_descq_queue_submit(struct mcdma_queue *q)
+{
+ uint16_t hw_tail = q->prod_didx;
+ uint16_t hw_submitted = q->submitted_didx;
+ uint16_t prod_ring = mcdma_queue_prod_ring(q);
+ struct mcdma_desc *d = MCDMA_DESC_PTR(q, prod_ring);
+
+ /* Only writeback supported as of now */
+ d->wb_en = MCDMA_COMPL_WB;
+
+ /*
+ * resubmitting the same tail ptr update indicates no data to transfer,
+ * So first submit 1 desc and then rest.
+ */
+ if (hw_tail == hw_submitted) {
+ uint16_t nudge = MCDMA_NEXT_IDX(hw_submitted, q->num_descs);
+ uint32_t count = 0;
+
+ mcdma_qcsr_reg_writel(q->qcsr, MCDMA_QCSR_Q_TAIL_POINTER, nudge);
+ do {
+ uint32_t head = mcdma_qcsr_reg_readl(q->qcsr,
+ MCDMA_QCSR_Q_HEAD_POINTER);
+ volatile uint16_t qhead = mcdma_qcsr_ptr_val(head);
+
+ if (qhead == nudge)
+ break;
+ count++;
+ } while (count < MCDMA_CNT_HEAD_MOVE);
+ }
+
+ rte_wmb();
+ mcdma_qcsr_reg_writel(q->qcsr, MCDMA_QCSR_Q_TAIL_POINTER, hw_tail);
+ q->submitted_didx = hw_tail;
+}
+
+uint16_t mcdma_xmit_pkts(void *txq, struct rte_mbuf **tx_pkts, uint16_t nb_pkts)
+{
+ struct mcdma_queue *q = txq;
+ uint16_t count;
+ uint16_t i;
+
+ mcdma_complete_requests(q, NULL, 0, MCDMA_DIR_H2D);
+
+ for (count = 0; count < nb_pkts; count++) {
+ uint16_t need;
+ uint16_t nb_avail;
+ struct rte_mbuf *mb;
+
+ mb = tx_pkts[count];
+ /* return check not required for H2D */
+
+ if (mb->nb_segs == 0)
+ break;
+
+ need = mcdma_desc_slots_for_segs(q, mb->nb_segs);
+ nb_avail = mcdma_descq_ring_avail(q);
+ if (nb_avail < need)
+ break;
+
+ if (mcdma_descq_queue_prepare(q, mb, MCDMA_DIR_H2D) != 0)
+ break;
+ }
+
+ rte_wmb();
+ if (count)
+ mcdma_descq_queue_submit(q);
+
+ for (i = 0; i < count; i++) {
+ q->tx_pkts++;
+ q->tx_bytes += rte_pktmbuf_pkt_len(tx_pkts[i]);
+ }
+
+ return count;
+}
+
+uint16_t mcdma_recv_pkts(void *rxq, struct rte_mbuf **rx_pkts, uint16_t nb_pkts)
+{
+ struct mcdma_queue *q = rxq;
+ struct rte_mbuf *mb;
+ struct rte_mbuf **repl;
+ uint32_t mb_avail;
+ uint16_t cnt, nb_avail, to_submit, can_submit;
+ uint16_t i;
+
+ cnt = mcdma_complete_requests(q, rx_pkts, nb_pkts, MCDMA_DIR_D2H);
+ q->processed_cur = cnt;
+
+ /* 1 - How much ring avail */
+ nb_avail = mcdma_descq_ring_avail(q);
+ if (!nb_avail)
+ return cnt;
+
+ /* 2 - Limit slot scan to 4 descriptor pages per poll (HW/latency bound). */
+ can_submit = mcdma_desc_segs_for_slots(q,
+ RTE_MIN(nb_avail, MCDMA_NUM_DESC_PER_PAGE * 4));
+ if (can_submit < q->burst_size)
+ return cnt;
+
+ to_submit = (can_submit / q->burst_size) * q->burst_size;
+
+ /* 3 - Limit to max mbuf available */
+ mb_avail = rte_mempool_avail_count(q->mb_pool);
+ if (to_submit > mb_avail)
+ to_submit = (mb_avail / q->burst_size) * q->burst_size;
+
+ /* 4 - Support submission only in batches */
+ if (to_submit < q->burst_size)
+ return cnt;
+
+ repl = rte_malloc_socket(NULL, q->burst_size * sizeof(*repl), 0,
+ q->socket_id);
+ if (repl == NULL) {
+ PMD_DRV_LOG(ERR, "Failed to allocate repl, queue id = %u, burst_size = %u",
+ q->qid, q->burst_size);
+ return cnt;
+ }
+
+ q->ring_avail = nb_avail;
+ q->ring_to_submit = to_submit;
+
+ while (to_submit > 0) {
+ uint16_t batch = RTE_MIN(to_submit, q->burst_size);
+ int err;
+
+ err = rte_mempool_get_bulk(q->mb_pool, (void **)repl, batch);
+ if (err) {
+ q->rx_nombuf += batch;
+ PMD_DRV_LOG(ERR, "No MBUFS, queue id = %u, nb_avail = %u, batch: %u, "
+ "mbuf_avail_count = %u, mbuf_in_use_count = %u",
+ q->qid, nb_avail, batch,
+ rte_mempool_avail_count(q->mb_pool),
+ rte_mempool_in_use_count(q->mb_pool));
+ break;
+ }
+
+ for (i = 0; i < batch; i++) {
+ mb = repl[i];
+ rte_pktmbuf_reset(mb);
+ if (mcdma_descq_queue_prepare(q, mb, MCDMA_DIR_D2H) != 0) {
+ rte_mempool_put_bulk(q->mb_pool,
+ (void **)&repl[i],
+ batch - i);
+ break;
+ }
+ }
+
+ if (i < batch) {
+ if (i == 0)
+ break;
+ to_submit -= i;
+ break;
+ }
+
+ to_submit -= batch;
+ }
+
+ rte_wmb();
+ if (q->prod_didx != q->submitted_didx)
+ mcdma_descq_queue_submit(q);
+
+ rte_free(repl);
+ return cnt;
+}
diff --git a/drivers/net/altera/mcdma/meson.build b/drivers/net/altera/mcdma/meson.build
index 9195a5d34982..65f442310f07 100644
--- a/drivers/net/altera/mcdma/meson.build
+++ b/drivers/net/altera/mcdma/meson.build
@@ -9,4 +9,5 @@ endif
sources += files(
'mcdma_eth.c',
'mcdma_eth_ops.c',
+ 'mcdma_eth_rxtx.c',
)
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [RFC 3/4] net/mcdma: add experimental PIO access APIs
2026-11-09 16:09 [RFC 0/4] net/mcdma: add Altera MCDMA poll mode driver Subhransu S. Prusty
2026-11-09 16:09 ` [RFC 1/4] net/mcdma: add Altera MCDMA poll mode driver skeleton Subhransu S. Prusty
2026-11-09 16:09 ` [RFC 2/4] net/mcdma: add Rx and Tx packet burst paths Subhransu S. Prusty
@ 2026-11-09 16:09 ` Subhransu S. Prusty
2026-11-09 16:09 ` [RFC 4/4] net/mcdma: add experimental QCSR dump APIs Subhransu S. Prusty
2026-11-09 17:25 ` [RFC 0/4] net/mcdma: add Altera MCDMA poll mode driver Stephen Hemminger
4 siblings, 0 replies; 6+ messages in thread
From: Subhransu S. Prusty @ 2026-11-09 16:09 UTC (permalink / raw)
To: dev
Cc: ferruh.yigit, stephen, rtice, cheryl.bansal, nanditha.jayarajan,
Subhransu S. Prusty
Expose __rte_experimental helpers to read and write 64-bit registers in
the optional PIO BAR mapped for an MCDMA port. Applications use these to
reach user logic behind the PCIe function.
Co-developed-by: Cheryl Bansal <cheryl.bansal@altera.com>
Signed-off-by: Cheryl Bansal <cheryl.bansal@altera.com>
Signed-off-by: Subhransu S. Prusty <subhransu.sekhar.prusty@altera.com>
---
doc/guides/nics/mcdma.rst | 13 +++
drivers/net/altera/mcdma/meson.build | 3 +
drivers/net/altera/mcdma/rte_pmd_mcdma.c | 106 +++++++++++++++++++++++
drivers/net/altera/mcdma/rte_pmd_mcdma.h | 61 +++++++++++++
4 files changed, 183 insertions(+)
create mode 100644 drivers/net/altera/mcdma/rte_pmd_mcdma.c
create mode 100644 drivers/net/altera/mcdma/rte_pmd_mcdma.h
diff --git a/doc/guides/nics/mcdma.rst b/doc/guides/nics/mcdma.rst
index e31fac2efe71..3bbd345d575c 100644
--- a/doc/guides/nics/mcdma.rst
+++ b/doc/guides/nics/mcdma.rst
@@ -104,6 +104,19 @@ The following standard ethdev features are not implemented:
Traffic acceptance and any Ethernet semantics beyond descriptor DMA are
defined by the FPGA application connected to the MCDMA IP.
+Experimental driver API
+-----------------------
+
+The header ``rte_pmd_mcdma.h`` provides experimental helpers to access
+ the PIO BAR:
+
+rte_pmd_mcdma_pio_read64() / rte_pmd_mcdma_pio_write64()
+ Read or write 64-bit registers in the mapped PIO BAR. ``pio_read64`` returns
+ 0 on success and stores the value via an output pointer so a valid register
+ read of zero is not confused with an error.
+
+These symbols are experimental and may change without notice.
+
Building and testing
--------------------
diff --git a/drivers/net/altera/mcdma/meson.build b/drivers/net/altera/mcdma/meson.build
index 65f442310f07..df1bbc495c73 100644
--- a/drivers/net/altera/mcdma/meson.build
+++ b/drivers/net/altera/mcdma/meson.build
@@ -10,4 +10,7 @@ sources += files(
'mcdma_eth.c',
'mcdma_eth_ops.c',
'mcdma_eth_rxtx.c',
+ 'rte_pmd_mcdma.c',
)
+
+headers = files('rte_pmd_mcdma.h')
diff --git a/drivers/net/altera/mcdma/rte_pmd_mcdma.c b/drivers/net/altera/mcdma/rte_pmd_mcdma.c
new file mode 100644
index 000000000000..49d7d97b90bc
--- /dev/null
+++ b/drivers/net/altera/mcdma/rte_pmd_mcdma.c
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020-2026 Altera Corporation
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <eal_export.h>
+#include <ethdev_driver.h>
+#include <rte_compat.h>
+
+#include "mcdma_eth.h"
+#include "rte_pmd_mcdma.h"
+
+#define MCDMA_PMD_NAME "net_mcdma"
+
+static int
+mcdma_pmd_port_get(uint16_t port_id, struct rte_eth_dev **eth_dev)
+{
+ struct rte_eth_dev *dev;
+
+ if (eth_dev == NULL)
+ return -EINVAL;
+
+ if (!rte_eth_dev_is_valid_port(port_id))
+ return -EINVAL;
+
+ dev = &rte_eth_devices[port_id];
+ if (dev->state != RTE_ETH_DEV_ATTACHED)
+ return -ENODEV;
+
+ if (dev->device == NULL || dev->device->driver == NULL ||
+ strcmp(dev->device->driver->name, MCDMA_PMD_NAME) != 0)
+ return -ENODEV;
+
+ *eth_dev = dev;
+ return 0;
+}
+
+static int
+mcdma_pio_offset_check(const struct mcdma_pci_device *priv, uint64_t offset)
+{
+ if (priv->pio == NULL || priv->pio_bar_len == 0)
+ return -ENODEV;
+
+ if ((offset & (sizeof(uint64_t) - 1)) != 0)
+ return -EINVAL;
+
+ if (priv->pio_bar_len < sizeof(uint64_t) ||
+ offset > priv->pio_bar_len - sizeof(uint64_t))
+ return -EINVAL;
+
+ return 0;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_pmd_mcdma_pio_read64, 26.11)
+int
+rte_pmd_mcdma_pio_read64(uint16_t port_id, uint64_t offset, uint64_t *val)
+{
+ struct rte_eth_dev *dev;
+ struct mcdma_pci_device *priv;
+ int ret;
+
+ if (val == NULL)
+ return -EINVAL;
+
+ ret = mcdma_pmd_port_get(port_id, &dev);
+ if (ret != 0)
+ return ret;
+
+ priv = dev->data->dev_private;
+ if (priv == NULL)
+ return -EINVAL;
+
+ ret = mcdma_pio_offset_check(priv, offset);
+ if (ret != 0)
+ return ret;
+
+ *val = mcdma_pio_reg_read64(priv->pio, offset);
+ return 0;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_pmd_mcdma_pio_write64, 26.11)
+int
+rte_pmd_mcdma_pio_write64(uint16_t port_id, uint64_t offset, uint64_t val)
+{
+ struct rte_eth_dev *dev;
+ struct mcdma_pci_device *priv;
+ int ret;
+
+ ret = mcdma_pmd_port_get(port_id, &dev);
+ if (ret != 0)
+ return ret;
+
+ priv = dev->data->dev_private;
+ if (priv == NULL)
+ return -EINVAL;
+
+ ret = mcdma_pio_offset_check(priv, offset);
+ if (ret != 0)
+ return ret;
+
+ mcdma_pio_reg_write64(priv->pio, offset, val);
+ return 0;
+}
diff --git a/drivers/net/altera/mcdma/rte_pmd_mcdma.h b/drivers/net/altera/mcdma/rte_pmd_mcdma.h
new file mode 100644
index 000000000000..d76babbdf7e0
--- /dev/null
+++ b/drivers/net/altera/mcdma/rte_pmd_mcdma.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020-2026 Altera Corporation
+ */
+
+#ifndef RTE_PMD_MCDMA_H
+#define RTE_PMD_MCDMA_H
+
+/**
+ * @file rte_pmd_mcdma.h
+ *
+ * Altera MCDMA PMD specific functions.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include <rte_compat.h>
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change, or be removed, without prior notice
+ *
+ * Read a 64-bit value from the mapped PIO BAR at @p offset.
+ *
+ * @param port_id
+ * Port identifier.
+ * @param offset
+ * Byte offset from the PIO BAR base (must be 8-byte aligned and fit a
+ * 64-bit access within the mapped PIO BAR).
+ * @param val
+ * On success, the 64-bit register value is stored here. Untouched on failure.
+ *
+ * @return
+ * 0 on success, negative errno on failure (-EINVAL, -ENODEV).
+ */
+__rte_experimental
+int
+rte_pmd_mcdma_pio_read64(uint16_t port_id, uint64_t offset, uint64_t *val);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change, or be removed, without prior notice
+ *
+ * Write a 64-bit value to the mapped PIO BAR at @p offset.
+ *
+ * @param port_id
+ * Port identifier.
+ * @param offset
+ * Byte offset from the PIO BAR base (must be 8-byte aligned and fit a
+ * 64-bit access within the mapped PIO BAR).
+ * @param val
+ * Value to write.
+ *
+ * @return
+ * 0 on success, negative errno on failure (-EINVAL, -ENODEV).
+ */
+__rte_experimental
+int
+rte_pmd_mcdma_pio_write64(uint16_t port_id, uint64_t offset, uint64_t val);
+
+#endif /* RTE_PMD_MCDMA_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [RFC 4/4] net/mcdma: add experimental QCSR dump APIs
2026-11-09 16:09 [RFC 0/4] net/mcdma: add Altera MCDMA poll mode driver Subhransu S. Prusty
` (2 preceding siblings ...)
2026-11-09 16:09 ` [RFC 3/4] net/mcdma: add experimental PIO access APIs Subhransu S. Prusty
@ 2026-11-09 16:09 ` Subhransu S. Prusty
2026-11-09 17:25 ` [RFC 0/4] net/mcdma: add Altera MCDMA poll mode driver Stephen Hemminger
4 siblings, 0 replies; 6+ messages in thread
From: Subhransu S. Prusty @ 2026-11-09 16:09 UTC (permalink / raw)
To: dev
Cc: ferruh.yigit, stephen, rtice, cheryl.bansal, nanditha.jayarajan,
Subhransu S. Prusty
Add rte_pmd_mcdma_qcsr_read() and rte_pmd_mcdma_qcsr_dump() to snapshot
and print queue control/status registers together with driver software
indices. Document the QCSR helpers in mcdma.rst.
Co-developed-by: Cheryl Bansal <cheryl.bansal@altera.com>
Signed-off-by: Cheryl Bansal <cheryl.bansal@altera.com>
Signed-off-by: Subhransu S. Prusty <subhransu.sekhar.prusty@altera.com>
---
doc/guides/nics/mcdma.rst | 10 +-
drivers/net/altera/mcdma/rte_pmd_mcdma.c | 178 +++++++++++++++++++++++
drivers/net/altera/mcdma/rte_pmd_mcdma.h | 79 ++++++++++
3 files changed, 265 insertions(+), 2 deletions(-)
diff --git a/doc/guides/nics/mcdma.rst b/doc/guides/nics/mcdma.rst
index 3bbd345d575c..a41997f1618d 100644
--- a/doc/guides/nics/mcdma.rst
+++ b/doc/guides/nics/mcdma.rst
@@ -107,8 +107,14 @@ defined by the FPGA application connected to the MCDMA IP.
Experimental driver API
-----------------------
-The header ``rte_pmd_mcdma.h`` provides experimental helpers to access
- the PIO BAR:
+The header ``rte_pmd_mcdma.h`` provides experimental helpers to inspect queue
+state and access the PIO BAR:
+
+rte_pmd_mcdma_qcsr_read()
+ Read QCSR registers and software queue indices for one queue.
+
+rte_pmd_mcdma_qcsr_dump()
+ Dump decoded QCSR state to a ``FILE`` stream.
rte_pmd_mcdma_pio_read64() / rte_pmd_mcdma_pio_write64()
Read or write 64-bit registers in the mapped PIO BAR. ``pio_read64`` returns
diff --git a/drivers/net/altera/mcdma/rte_pmd_mcdma.c b/drivers/net/altera/mcdma/rte_pmd_mcdma.c
index 49d7d97b90bc..3736c7b45913 100644
--- a/drivers/net/altera/mcdma/rte_pmd_mcdma.c
+++ b/drivers/net/altera/mcdma/rte_pmd_mcdma.c
@@ -38,6 +38,82 @@ mcdma_pmd_port_get(uint16_t port_id, struct rte_eth_dev **eth_dev)
return 0;
}
+static struct mcdma_queue *
+mcdma_pmd_queue_get(struct rte_eth_dev *dev, uint16_t queue_id,
+ enum rte_pmd_mcdma_queue_dir dir)
+{
+ if (dir == RTE_PMD_MCDMA_QUEUE_TX) {
+ if (queue_id >= dev->data->nb_tx_queues)
+ return NULL;
+ return dev->data->tx_queues[queue_id];
+ }
+
+ if (queue_id >= dev->data->nb_rx_queues)
+ return NULL;
+
+ return dev->data->rx_queues[queue_id];
+}
+
+static void
+mcdma_qcsr_regs_read(struct mcdma_queue *q, struct rte_pmd_mcdma_qcsr *regs)
+{
+ regs->q_ctrl = mcdma_qcsr_reg_readl(q->qcsr, MCDMA_QCSR_Q_CTRL);
+ regs->q_start_addr_l = mcdma_qcsr_reg_readl(q->qcsr,
+ MCDMA_QCSR_Q_START_ADDR_L);
+ regs->q_start_addr_h = mcdma_qcsr_reg_readl(q->qcsr,
+ MCDMA_QCSR_Q_START_ADDR_H);
+ regs->q_size = mcdma_qcsr_reg_readl(q->qcsr, MCDMA_QCSR_Q_SIZE);
+ regs->q_tail_pointer = mcdma_qcsr_reg_readl(q->qcsr,
+ MCDMA_QCSR_Q_TAIL_POINTER);
+ regs->q_head_pointer = mcdma_qcsr_reg_readl(q->qcsr,
+ MCDMA_QCSR_Q_HEAD_POINTER);
+ regs->q_completed_pointer = mcdma_qcsr_reg_readl(q->qcsr,
+ MCDMA_QCSR_Q_COMPLETED_POINTER);
+ regs->q_consumed_head_addr_l = mcdma_qcsr_reg_readl(q->qcsr,
+ MCDMA_QCSR_Q_CONSUMED_HEAD_ADDR_L);
+ regs->q_consumed_head_addr_h = mcdma_qcsr_reg_readl(q->qcsr,
+ MCDMA_QCSR_Q_CONSUMED_HEAD_ADDR_H);
+ regs->q_batch_delay = mcdma_qcsr_reg_readl(q->qcsr,
+ MCDMA_QCSR_Q_BATCH_DELAY);
+ regs->q_debug_status_1 = mcdma_qcsr_reg_readl(q->qcsr,
+ MCDMA_QCSR_Q_DEBUG_STATUS_1);
+ regs->q_debug_status_2 = mcdma_qcsr_reg_readl(q->qcsr,
+ MCDMA_QCSR_Q_DEBUG_STATUS_2);
+ regs->q_data_drp_err_ctr = mcdma_qcsr_reg_readl(q->qcsr,
+ MCDMA_QCSR_Q_DATA_DRP_ERR_CTR);
+ regs->q_payload_count = mcdma_qcsr_reg_readl(q->qcsr,
+ MCDMA_QCSR_Q_PAYLOAD_COUNT);
+ regs->q_reset = mcdma_qcsr_reg_readl(q->qcsr, MCDMA_QCSR_Q_RESET);
+ regs->q_cpl_timeout = mcdma_qcsr_reg_readl(q->qcsr,
+ MCDMA_QCSR_Q_CPL_TIMEOUT);
+
+ rte_rmb();
+ regs->prod_didx = q->prod_didx;
+ regs->cons_didx = q->cons_didx;
+ regs->freed_cons_didx = q->freed_cons_didx;
+ regs->submitted_didx = q->submitted_didx;
+ regs->num_descs = q->num_descs;
+}
+
+static void
+mcdma_qcsr_dump_decoded(FILE *f, const struct rte_pmd_mcdma_qcsr *regs)
+{
+ mcdma_qcsr_q_ctrl_t q_ctrl;
+
+ q_ctrl.val = regs->q_ctrl;
+ fprintf(f, " Q_CTRL decoded: q_en=%u q_wb_en=%u q_intr_en=%u\n",
+ q_ctrl.field.q_en, q_ctrl.field.q_wb_en, q_ctrl.field.q_intr_en);
+
+ if (regs->q_head_pointer & MCDMA_QCSR_PTR_ERR_MASK)
+ fprintf(f, " Q_HEAD_POINTER error bit set\n");
+ if (regs->q_completed_pointer & MCDMA_QCSR_PTR_ERR_MASK)
+ fprintf(f, " Q_COMPLETED_POINTER error bit set\n");
+ if (regs->q_data_drp_err_ctr & MCDMA_QCSR_DRP_CTR_VALID) {
+ fprintf(f, " Q_DATA_DRP_ERR_CTR drop_count=%u\n",
+ regs->q_data_drp_err_ctr & MCDMA_QCSR_DRP_CTR_CNT_MASK);
+ }
+}
+
static int
mcdma_pio_offset_check(const struct mcdma_pci_device *priv, uint64_t offset)
{
@@ -104,3 +180,105 @@ rte_pmd_mcdma_pio_write64(uint16_t port_id, uint64_t offset, uint64_t val)
mcdma_pio_reg_write64(priv->pio, offset, val);
return 0;
}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_pmd_mcdma_qcsr_read, 26.11)
+int
+rte_pmd_mcdma_qcsr_read(uint16_t port_id, uint16_t queue_id,
+ enum rte_pmd_mcdma_queue_dir dir,
+ struct rte_pmd_mcdma_qcsr *regs)
+{
+ struct rte_eth_dev *dev;
+ struct mcdma_queue *q;
+ int ret;
+
+ if (regs == NULL)
+ return -EINVAL;
+
+ if (dir != RTE_PMD_MCDMA_QUEUE_RX && dir != RTE_PMD_MCDMA_QUEUE_TX)
+ return -EINVAL;
+
+ ret = mcdma_pmd_port_get(port_id, &dev);
+ if (ret != 0)
+ return ret;
+
+ q = mcdma_pmd_queue_get(dev, queue_id, dir);
+ if (q == NULL || q->qcsr == NULL)
+ return -EINVAL;
+
+ memset(regs, 0, sizeof(*regs));
+ mcdma_qcsr_regs_read(q, regs);
+ return 0;
+}
+
+static void
+mcdma_qcsr_dump_reg(FILE *f, const char *name, uint32_t offset, uint32_t val)
+{
+ fprintf(f, " %s @ 0x%03x = 0x%08x (%u)\n", name, offset, val, val);
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_pmd_mcdma_qcsr_dump, 26.11)
+int
+rte_pmd_mcdma_qcsr_dump(uint16_t port_id, uint16_t queue_id,
+ enum rte_pmd_mcdma_queue_dir dir, FILE *f)
+{
+ struct rte_pmd_mcdma_qcsr regs;
+ uint64_t qbuf_dma;
+ int ret;
+
+ if (f == NULL)
+ f = stdout;
+
+ ret = rte_pmd_mcdma_qcsr_read(port_id, queue_id, dir, ®s);
+ if (ret != 0)
+ return ret;
+
+ qbuf_dma = ((uint64_t)regs.q_start_addr_h << 32) | regs.q_start_addr_l;
+
+ fprintf(f, "MCDMA QCSR dump port %u queue %u %s\n",
+ port_id, queue_id,
+ dir == RTE_PMD_MCDMA_QUEUE_TX ? "TX" : "RX");
+ fprintf(f, " ring_dma=0x%016llx ring_size_log2=%u\n",
+ (unsigned long long)qbuf_dma, regs.q_size);
+
+ mcdma_qcsr_dump_reg(f, "Q_CTRL", MCDMA_QCSR_Q_CTRL, regs.q_ctrl);
+ mcdma_qcsr_dump_reg(f, "Q_START_ADDR_L", MCDMA_QCSR_Q_START_ADDR_L,
+ regs.q_start_addr_l);
+ mcdma_qcsr_dump_reg(f, "Q_START_ADDR_H", MCDMA_QCSR_Q_START_ADDR_H,
+ regs.q_start_addr_h);
+ mcdma_qcsr_dump_reg(f, "Q_SIZE", MCDMA_QCSR_Q_SIZE, regs.q_size);
+ mcdma_qcsr_dump_reg(f, "Q_TAIL_POINTER", MCDMA_QCSR_Q_TAIL_POINTER,
+ regs.q_tail_pointer);
+ mcdma_qcsr_dump_reg(f, "Q_HEAD_POINTER", MCDMA_QCSR_Q_HEAD_POINTER,
+ regs.q_head_pointer);
+ mcdma_qcsr_dump_reg(f, "Q_COMPLETED_POINTER",
+ MCDMA_QCSR_Q_COMPLETED_POINTER,
+ regs.q_completed_pointer);
+ mcdma_qcsr_dump_reg(f, "Q_CONSUMED_HEAD_ADDR_L",
+ MCDMA_QCSR_Q_CONSUMED_HEAD_ADDR_L,
+ regs.q_consumed_head_addr_l);
+ mcdma_qcsr_dump_reg(f, "Q_CONSUMED_HEAD_ADDR_H",
+ MCDMA_QCSR_Q_CONSUMED_HEAD_ADDR_H,
+ regs.q_consumed_head_addr_h);
+ mcdma_qcsr_dump_reg(f, "Q_BATCH_DELAY", MCDMA_QCSR_Q_BATCH_DELAY,
+ regs.q_batch_delay);
+ mcdma_qcsr_dump_reg(f, "Q_DEBUG_STATUS_1", MCDMA_QCSR_Q_DEBUG_STATUS_1,
+ regs.q_debug_status_1);
+ mcdma_qcsr_dump_reg(f, "Q_DEBUG_STATUS_2", MCDMA_QCSR_Q_DEBUG_STATUS_2,
+ regs.q_debug_status_2);
+ mcdma_qcsr_dump_reg(f, "Q_DATA_DRP_ERR_CTR",
+ MCDMA_QCSR_Q_DATA_DRP_ERR_CTR,
+ regs.q_data_drp_err_ctr);
+ mcdma_qcsr_dump_reg(f, "Q_PAYLOAD_COUNT", MCDMA_QCSR_Q_PAYLOAD_COUNT,
+ regs.q_payload_count);
+ mcdma_qcsr_dump_reg(f, "Q_RESET", MCDMA_QCSR_Q_RESET, regs.q_reset);
+ mcdma_qcsr_dump_reg(f, "Q_CPL_TIMEOUT", MCDMA_QCSR_Q_CPL_TIMEOUT,
+ regs.q_cpl_timeout);
+
+ fprintf(f, " SW prod_didx=%u cons_didx=%u freed_cons_didx=%u "
+ "submitted_didx=%u num_descs=%u\n",
+ regs.prod_didx, regs.cons_didx, regs.freed_cons_didx,
+ regs.submitted_didx, regs.num_descs);
+
+ mcdma_qcsr_dump_decoded(f, ®s);
+ return 0;
+}
diff --git a/drivers/net/altera/mcdma/rte_pmd_mcdma.h b/drivers/net/altera/mcdma/rte_pmd_mcdma.h
index d76babbdf7e0..7b4ad83c07a2 100644
--- a/drivers/net/altera/mcdma/rte_pmd_mcdma.h
+++ b/drivers/net/altera/mcdma/rte_pmd_mcdma.h
@@ -16,6 +16,85 @@
#include <rte_compat.h>
+/** MCDMA queue direction for PMD APIs. */
+enum rte_pmd_mcdma_queue_dir {
+ RTE_PMD_MCDMA_QUEUE_RX = 0, /**< Device-to-host (RX). */
+ RTE_PMD_MCDMA_QUEUE_TX = 1, /**< Host-to-device (TX). */
+};
+
+/** Snapshot of one queue's QCSR registers and driver state. */
+struct rte_pmd_mcdma_qcsr {
+ uint32_t q_ctrl;
+ uint32_t q_start_addr_l;
+ uint32_t q_start_addr_h;
+ uint32_t q_size;
+ uint32_t q_tail_pointer;
+ uint32_t q_head_pointer;
+ uint32_t q_completed_pointer;
+ uint32_t q_consumed_head_addr_l;
+ uint32_t q_consumed_head_addr_h;
+ uint32_t q_batch_delay;
+ uint32_t q_debug_status_1;
+ uint32_t q_debug_status_2;
+ uint32_t q_data_drp_err_ctr;
+ uint32_t q_payload_count;
+ uint32_t q_reset;
+ uint32_t q_cpl_timeout;
+
+ uint16_t prod_didx;
+ uint16_t cons_didx;
+ uint16_t freed_cons_didx;
+ uint16_t submitted_didx;
+ uint32_t num_descs;
+};
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change, or be removed, without prior notice
+ *
+ * Read QCSR registers and software queue state for one queue.
+ *
+ * @param port_id
+ * Port identifier.
+ * @param queue_id
+ * Queue identifier.
+ * @param dir
+ * Queue direction (RX or TX).
+ * @param regs
+ * Output register snapshot.
+ *
+ * @return
+ * 0 on success, negative errno on failure.
+ */
+__rte_experimental
+int
+rte_pmd_mcdma_qcsr_read(uint16_t port_id, uint16_t queue_id,
+ enum rte_pmd_mcdma_queue_dir dir,
+ struct rte_pmd_mcdma_qcsr *regs);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change, or be removed, without prior notice
+ *
+ * Dump QCSR registers and software queue state to a stream.
+ *
+ * @param port_id
+ * Port identifier.
+ * @param queue_id
+ * Queue identifier.
+ * @param dir
+ * Queue direction (RX or TX).
+ * @param f
+ * Output stream. Uses stdout if NULL.
+ *
+ * @return
+ * 0 on success, negative errno on failure.
+ */
+__rte_experimental
+int
+rte_pmd_mcdma_qcsr_dump(uint16_t port_id, uint16_t queue_id,
+ enum rte_pmd_mcdma_queue_dir dir, FILE *f);
+
/**
* @warning
* @b EXPERIMENTAL: this API may change, or be removed, without prior notice
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [RFC 0/4] net/mcdma: add Altera MCDMA poll mode driver
2026-11-09 16:09 [RFC 0/4] net/mcdma: add Altera MCDMA poll mode driver Subhransu S. Prusty
` (3 preceding siblings ...)
2026-11-09 16:09 ` [RFC 4/4] net/mcdma: add experimental QCSR dump APIs Subhransu S. Prusty
@ 2026-11-09 17:25 ` Stephen Hemminger
4 siblings, 0 replies; 6+ messages in thread
From: Stephen Hemminger @ 2026-11-09 17:25 UTC (permalink / raw)
To: Subhransu S. Prusty
Cc: dev, ferruh.yigit, rtice, cheryl.bansal, nanditha.jayarajan
On Tue, 10 Nov 2026 00:09:22 +0800
"Subhransu S. Prusty" <subhransu.sekhar.prusty@altera.com> wrote:
> This RFC series introduces a poll mode driver for Altera Multi-Channel
> Direct Memory Access (MCDMA) PCI devices. The driver exposes
> host-to-device and device-to-host descriptor queues as standard DPDK
> Ethernet ports for custom FPGA datapaths.
>
> While the MCDMA IP supports multiple PFs and VFs per PF, this RFC
> implements single PF with multiple VFs; each function exposes multiple
> Rx/Tx queue pairs backed by H2D/D2H descriptor rings and per-queue
> QCSR.
>
> User guide reference:
> https://docs.altera.com/r/docs/683821/25.3.1/multi-channel-dma-ip-for-pci-express-user-guide/before-you-begin
>
> The series is split for review as follows:
> 1. Core driver skeleton, queue management, documentation, and features
> matrix, excluding datapath bursts, debug dumps, and PIO APIs.
> 2. Rx and Tx packet burst handlers.
> 3. Experimental PIO BAR access APIs.
> 4. Experimental QCSR inspection and dump APIs.
>
> Functional testing was performed on Linux with vfio-pci, using DPDK
> pktgen on a single PF and VF configuration. The attached user logic
> was adapted as needed for validation.
>
> We would appreciate feedback on the ethdev design, the experimental
> PIO APIs, and PF/VF devargs.
Lots of feedback from AI review. Mostly it needs to be tested with testpmd
before review consideration. And you are too late for 26.11
Review: [RFC 0/4] net/mcdma: add Altera MCDMA poll mode driver
Applied on 26.11-rc2, builds clean with -Dwerror at each commit.
Post-apply sources read, not just diffs.
Series-level
------------
Target release. 26.11 is at rc2; a new driver lands in 27.03.
RTE_EXPORT_EXPERIMENTAL_SYMBOL(..., 26.11) in patches 3/4 should say
27.03, and the series needs a release notes entry in the 27.03 file.
PCI ID and PF/VF model (you asked about devargs). The ID table is
vendor 0x1172, device 0x0000, i.e. the Altera default IDs. Any
Altera design left at default IDs will probe as MCDMA. Whether a
function is PF or VF is decided by the presence of pf_index=, and a
VF needs parent_pf_idx= to find its channel count. The usual way to
do this in DPDK is distinct device IDs for PF and VF (and a real
device ID for the MCDMA IP); then is_pf comes from the match entry
and both devargs go away. A VF probed with no devargs at all
currently fails (see patch 1).
PMD-specific API (you asked about it). Patch 4 reimplements what
ethdev already has: eth_dev_ops.get_reg / rte_eth_dev_get_reg_info_ext()
gives named register dumps that testpmd and dpdk-proc-info already
consume. Implement .get_reg and drop rte_pmd_mcdma_qcsr_read/dump.
Patch 3 (PIO) has no generic equivalent, see notes there.
testpmd. mcdma.rst says the driver has not been tested with testpmd.
It should be before a non-RFC submission; it will exercise close,
stop, mtu set and secondary process paths that pktgen does not.
Patch 1/4: net/mcdma: add Altera MCDMA poll mode driver skeleton
----------------------------------------------------------------
Errors
1. No .dev_close in mcdma_ethdev_ops. rte_eth_dev_close() returns
-ENOTSUP, so queues are never released; on remove,
mcdma_eth_dev_uninit() only frees mac_addrs:
static int mcdma_eth_dev_uninit(struct rte_eth_dev *dev)
{
rte_free(dev->data->mac_addrs);
Every queue's memzone, sw_ring and struct mcdma_queue leak, and
the HW queues stay enabled. Add:
static int mcdma_dev_close(struct rte_eth_dev *dev)
{
uint16_t i;
if (rte_eal_process_type() != RTE_PROC_PRIMARY)
return 0;
for (i = 0; i < dev->data->nb_rx_queues; i++)
mcdma_dev_rx_queue_release(dev, i);
for (i = 0; i < dev->data->nb_tx_queues; i++)
mcdma_dev_tx_queue_release(dev, i);
return 0;
}
and have uninit call it. rte_eth_dev_release_port() already frees
mac_addrs, so uninit need not.
2. MTU read from dev_conf after configure:
static uint16_t
mcdma_dev_mtu_get(const struct rte_eth_dev *dev)
{
if (dev->data->dev_conf.rxmode.mtu != 0)
return dev->data->dev_conf.rxmode.mtu;
rxmode.mtu is the request consumed by configure; after that
dev->data->mtu is authoritative and rte_eth_dev_set_mtu() only
updates dev->data->mtu. mtu_set() works around this by writing
dev->data->dev_conf.rxmode.mtu = mtu itself (line 744), which a
PMD must not do. Delete mcdma_dev_mtu_get() and the rxmode.mtu
store; use dev->data->mtu everywhere (ethdev initialises it to
RTE_ETHER_MTU, so no fallback is needed).
3. Rx queue stop cannot succeed on an idle queue, and the release
path frees buffers the HW still owns.
static int mcdma_wait_queue_drain(struct mcdma_queue *q)
{
for (retry = 0; retry < MCDMA_CNT_HEAD_MOVE; retry++) {
rte_rmb();
if (q->cons_didx == q->prod_didx)
return 0;
For D2H, cons_didx only advances when packets arrive, so with
buffers posted and no traffic this spins 1 s and returns
-ETIMEDOUT. mcdma_quiesce_queue() returns before disabling or
resetting the queue, rx_queue_stop() returns the error with the
queue still STARTED, and dev_stop() fails. In
mcdma_dev_queue_release_common() the failure is only logged and
execution continues:
mcdma_sw_ring_free_inflight(q);
...
mcdma_dev_descq_disable(q);
so mbufs are freed while the queue is still enabled, then the
queue is disabled. Fix: disable (Q_CTRL=0) and reset the queue
first, then reclaim freed..prod unconditionally; only H2D needs
a bounded drain wait before that, and it should fall through to
disable+reset on timeout rather than return.
4. No secondary-process handling while mcdma.ini claims
"Multiprocess aware = Y". mcdma_eth_dev_init() runs the same
code in every process:
priv->dev = dev;
...
ret = mcmda_parse_args(priv, pci_dev);
...
dev->data->mac_addrs = rte_zmalloc("mcdma", ...);
A secondary overwrites the shared priv->dev with its own local
rte_eth_dev pointer (dereferenced later by the primary in
descq_free/queue_init), reprograms the VF weight CSRs, and
replaces the shared mac_addrs pointer (leaking the primary's).
Either add the standard early return for RTE_PROC_SECONDARY
after setting burst pointers, or change the .ini to N. Also
drop the dead `dev->data->dev_private = priv;`.
5. dpdk-devbind.py entry does not match the device the driver
binds:
+ {'Class': '06', 'Vendor': '8086', 'Device': '1172'}, # Altera FPGA
The driver matches vendor 0x1172 device 0x0000; here 0x1172 is in
the Device field with Intel's vendor ID, and class 06 is a bridge.
Should be {'Class': <device class>, 'Vendor': '1172', 'Device':
'0000'} (or the real device ID, see cover).
Warnings
6. BAR 0 is used before it is checked:
priv->bar_base = pci_dev->mem_resource[MCDMA_CONFIG_BAR].addr;
ret = mcmda_parse_args(priv, pci_dev); /* writes VF weight CSRs */
...
PMD_DRV_LOG(INFO, "MCDMA RTL VERSION : 0x%x",
mcdma_dmacsr_reg_readl(priv->bar_base, MCDMA_CSR_VER_NUM));
Return -ENODEV if mem_resource[0].addr == NULL (as is done for the
PIO BAR).
7. Devargs handling is inverted:
if (!devargs)
return -EINVAL;
kvlist = rte_kvargs_parse(devargs->args, mcdma_kvargs_keys);
if (!kvlist)
return 0;
A device probed without -a (no devargs) fails probe; a device with
a malformed or unknown key (rte_kvargs_parse() returns NULL) is
accepted silently. Swap: no devargs is the VF default, NULL kvlist
is -EINVAL.
8. ip_num_chan is taken from a 16-bit register field and never
bounded, but drives the QCSR address:
uint32_t qcsr_offset = (dir == MCDMA_DIR_H2D) ?
(MCDMA_QCSR_H2D_OFFSET + (q_id * MCDMA_QCSR_SIZE)) :
(q_id * MCDMA_QCSR_SIZE);
For q_id >= 2048 a D2H queue's QCSR lands in the H2D region.
MCDMA_MAX_CHAN_PER_FUNC (512) and MCDMA_QCSR_NUM_MAX_PER_DIR
(2048) exist but are unused; clamp ip_num_chan to
MCDMA_MAX_CHAN_PER_FUNC in dev_init.
9. Writeback target is a 16-bit field in the middle of the hot
software state:
volatile uint16_t freed_cons_didx;
uint16_t prod_didx;
volatile uint16_t cons_didx;
uint16_t submitted_didx;
...
cons_idx = rte_mem_virt2iova((const void *)(uintptr_t)&q->cons_didx);
Q_HEAD_POINTER / Q_COMPLETED_POINTER are 32-bit registers (error
flag in bit 24). If the IP writes back the 32-bit register image,
submitted_didx is clobbered on every completion. Independent of
width, DMA writes into the same cache line as prod_didx and the
stats cause ping-pong with the lcore polling the queue. Give the
writeback its own cache-line-aligned uint32_t (or a slot in the
descriptor memzone) and read the 16-bit index from it. The
volatile on freed_cons_didx (SW-only) is noise.
10. Whole-IP reset from dev_configure():
if (mcdma_dev->is_pf && !mcdma_dev->pf_idx) {
mcdma_dmacsr_reg_writel(mcdma_dev->bar_base,
MCDMA_CSR_SW_RESET, 0x1);
rte_delay_ms(MCDMA_SW_RESET_DELAY_MS);
dev_configure can be called repeatedly (every reconfigure), it
blocks 1 s, and MCDMA_CSR_SW_RESET resets every function's queues,
including VFs owned by other processes or VMs. Do this once at
probe, or not at all; configure is not the place.
11. MMIO accessors reinvent rte_io.h and then pay for full fences:
static inline uint32_t mcdma_reg_readl(volatile void *addr)
{
return *(volatile uint32_t *)addr;
}
...
rte_wmb();
mcdma_qcsr_reg_writel(q->qcsr, MCDMA_QCSR_Q_TAIL_POINTER, hw_tail);
Use rte_read32()/rte_write32()/rte_read64()/rte_write64(); they
carry the required rte_io_*mb() (a compiler barrier on x86) and
the explicit rte_wmb()/rte_rmb() (sfence/lfence on x86, in every
burst) go away. Same for mcdma_pio_reg_read64/write64.
12. Const and namespace:
size_t config_csr_offsets[MCDMA_MAX_CONFIGS] = {
static struct rte_pci_id mcdma_pci_id_tbl[] = {
static struct eth_dev_ops mcdma_ethdev_ops = {
config_csr_offsets is an unprefixed exported global (static
linking clash); make all three `static const`.
13. Debug output at ERR level in rx_queue_setup:
if (mb_pool)
PMD_DRV_LOG_DBG(ERR,
"qid: %u, mbuf_avail_count = %u, mbuf_in_use_count = %u",
MCDMA_LOG_ENABLE is hard-wired to 1 so this prints at ERR on every
queue setup. Drop it, or use DEBUG level with plain PMD_DRV_LOG
and remove the PMD_DRV_LOG_DBG/MCDMA_LOG_ENABLE pair.
14. mtu_set on a started queue updates rxq->rx_seg_len but
Q_PAYLOAD_COUNT is only written from mcdma_dev_queue_init():
mcdma_qcsr_reg_writel(q->qcsr, MCDMA_QCSR_Q_PAYLOAD_COUNT, payload);
If that register bounds the D2H transfer, a larger MTU set at
runtime is not applied until the queue is restarted. Either
reprogram it in mtu_set or return -EBUSY when any Rx queue is
started.
15. didx numbering is inconsistent with its own comments:
/* 16 bits - Monotonic index and IP requires it to start from 1 */
uint16_t didx;
...
#define MCDMA_DIDX_NEXT(cur, num_descs) \
((uint16_t)((((uint16_t)(cur) + 1) % (num_descs))))
MCDMA_DIDX_NEXT yields 0..num_descs-1, so after num_descs
descriptors the didx (and the tail pointer written to HW) is 0,
which the comments and mcdma_hw_didx_to_ring() treat as "none".
The unused mcdma_hw_didx_nudge() and MCDMA_RING_TO_HW_DIDX()
implement 1..num_descs. One of the two schemes is wrong; pick
one, document it, delete the other.
16. dev_info inconsistency:
dev_info->max_rx_pktlen = MCDMA_MAX_RX_FRAME_LEN; /* 0x1FFFFF */
dev_info->max_mtu = MCDMA_MAX_MTU; /* 65535 */
max_rx_pktlen should be max_mtu + MCDMA_RX_ETH_OVERHEAD.
17. Documentation does not match code:
- mcdma.ini: "Runtime Rx queue setup" / "Runtime Tx queue setup"
are not feature keys; default.ini has "Runtime queue setup".
"Extended stats" is missing although xstats_get is implemented.
"Multiprocess aware = Y" (see 4).
- mcdma.rst: build option is -Denable_drivers=net/altera/mcdma,
not net/mcdma; says xstats are not implemented (they are);
says SOF/EOF are tracked in an mbuf dynamic field (nothing in
the code does; MCDMA_DESC_SOF/EOF are unused).
- index.rst: mcdma inserted after memif; alphabetical is before.
Info
18. (uint32_t)log2(q->num_descs) uses libm on an integer power of
two; rte_log2_u32(q->num_descs).
19. mac_addrs is left all-zero. Use rte_eth_random_addr() as the
other DMA/virtual PMDs do; RTE_ETH_DEV_NOLIVE_MAC_ADDR does
nothing without a mac_addr_set op.
20. mcdma_qcsr_read_ptr_errors() returns the state of two sticky
bits (0..2) and stats_get adds it to ierrors/oerrors on each
call, so the "counter" is a gauge that never clears.
21. vf_weight_parse_handler(): uint8_t weight_val = atoi(token)
truncates (257 -> 1 passes the range check); use strtoul.
Function name mcmda_parse_args() is a typo.
22. mcdma_dev_descq_free() looks the memzone up by name; keep the
memzone pointer in struct mcdma_queue instead. q->qbuf_dma can
be qzone->iova.
23. Leftovers, all unused: MCDMA_QCSR_Q(), MCDMA_MAX_PYLD_CNT,
MCDMA_MAX_VF_PER_PF, MCDMA_IFC_DATA_WIDTH, MCDMA_MAX_CHAN,
MCDMA_PREV_IDX, MCDMA_RING_AVAIL_COUNT, mcdma_queue_cons_ring(),
mcdma_qcsr_reg_updatel(), mcdma_pio_reg_readl/writel(),
priv->bam (assigned, never read), RTE_PCI_DEV_ID_DECL_XNIC
macro defined inside the initialiser.
Patch 2/4: net/mcdma: add Rx and Tx packet burst paths
------------------------------------------------------
Errors
1. Heap allocation on every rx_burst:
repl = rte_malloc_socket(NULL, q->burst_size * sizeof(*repl), 0,
q->socket_id);
...
rte_free(repl);
rte_malloc takes the heap lock and this runs on every poll that
refills. burst_size is fixed at MCDMA_MAX_BURST (32):
struct rte_mbuf *repl[MCDMA_MAX_BURST];
Warnings
2. rte_mempool_avail_count() per poll:
mb_avail = rte_mempool_avail_count(q->mb_pool);
It walks every lcore cache (O(RTE_MAX_LCORE)) and is documented
as not for the datapath. Just call rte_pktmbuf_alloc_bulk() (which
also replaces the get_bulk + per-mbuf rte_pktmbuf_reset loop) and
handle failure.
3. ERR log in the Rx hot path on mbuf exhaustion:
q->rx_nombuf += batch;
PMD_DRV_LOG(ERR, "No MBUFS, queue id = %u, nb_avail = %u, batch: %u, "
Pool exhaustion is a steady state under load; this is a log
storm. The counter is enough; if a message is wanted use
RTE_LOG_DP_LINE.
4. Rx mbufs never get m->port:
mb->pkt_len = d->rx_pyld_cnt;
rte_pktmbuf_data_len(mb) = d->rx_pyld_cnt;
Add mb->port = q->ethdev->data->port_id (cache port_id in the
queue rather than chasing ethdev).
Info
5. The "nudge" branch in mcdma_descq_queue_submit() is unreachable:
both callers only submit when prod_didx != submitted_didx, and
ring_avail() reserves a slot so a full wrap cannot make them
equal. Remove it (and the `volatile uint16_t qhead` local, and
the 100000-iteration busy loop without delay).
6. mcdma_desc_slots_for_segs()/mcdma_desc_segs_for_slots() read
d->link from the descriptor ring to locate link slots. Link slots
are at (idx & 127) == 127 by construction; compute instead of
touching DMA memory.
7. Write-only fields: q->processed_cur, q->ring_avail,
q->ring_to_submit, q->complete. Remove.
8. In mcdma_descq_queue_prepare(): the seg_len > MCDMA_IP_MAX_FRAME_LEN
clamp is dead (data_len is uint16_t); `if (mb->nb_segs == 0) break;`
returns short on a corrupt mbuf, which the caller will resend
forever. Drop both.
Patch 3/4: net/mcdma: add experimental PIO access APIs
------------------------------------------------------
Info
1. Each 64-bit access does rte_eth_dev_is_valid_port() + strcmp on
the driver name + bounds check. An application polling user-logic
registers will not want that per access. Consider instead a
one-time rte_pmd_mcdma_pio_bar_get(port_id, void **addr, size_t
*len) and let the caller use rte_read64/rte_write64, or at least
add 32-bit variants; most AVMM user logic is 32-bit.
2. MCDMA_PIO_BAR is hard-wired to BAR 2 while the BAM BAR is a
devarg. Either both configurable or both fixed.
3. Symbol version 26.11 -> 27.03 (see cover).
Patch 4/4: net/mcdma: add experimental QCSR dump APIs
-----------------------------------------------------
Warnings
1. Replace rte_pmd_mcdma_qcsr_read()/dump() with eth_dev_ops.get_reg.
rte_eth_dev_get_reg_info_ext() supports a filter string and
register names, so "show port <n> regs" in testpmd and
dpdk-proc-info give the same dump with no PMD-specific header.
The SW indices (prod/cons/freed/submitted) can be exposed as
pseudo-registers or dropped; exposing struct mcdma_queue internals
through an installed header (struct rte_pmd_mcdma_qcsr) ties ABI
to driver internals.
Info
2. mcdma_qcsr_regs_read() reads the SW indices unsynchronised with
the polling lcore; fine for a debug dump, but the rte_rmb() there
buys nothing.
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-11-09 17:25 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-09 16:09 [RFC 0/4] net/mcdma: add Altera MCDMA poll mode driver Subhransu S. Prusty
2026-11-09 16:09 ` [RFC 1/4] net/mcdma: add Altera MCDMA poll mode driver skeleton Subhransu S. Prusty
2026-11-09 16:09 ` [RFC 2/4] net/mcdma: add Rx and Tx packet burst paths Subhransu S. Prusty
2026-11-09 16:09 ` [RFC 3/4] net/mcdma: add experimental PIO access APIs Subhransu S. Prusty
2026-11-09 16:09 ` [RFC 4/4] net/mcdma: add experimental QCSR dump APIs Subhransu S. Prusty
2026-11-09 17:25 ` [RFC 0/4] net/mcdma: add Altera MCDMA poll mode driver Stephen Hemminger
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox