On Tue, 10 Nov 2026 00:09:22 +0800
"Subhransu S. Prusty" <[email protected]> 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.

Reply via email to