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 <[email protected]>
Signed-off-by: Cheryl Bansal <[email protected]>
Signed-off-by: Subhransu S. Prusty <[email protected]>
---
 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 <[email protected]>
+M: Cheryl Bansal <[email protected]>
+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

Reply via email to