From: Stephen Hemminger <stephen@networkplumber.org>
To: "Subhransu S. Prusty" <subhransu.sekhar.prusty@altera.com>
Cc: dev@dpdk.org, ferruh.yigit@intel.com, rtice@cisco.com,
cheryl.bansal@altera.com, nanditha.jayarajan@altera.com
Subject: Re: [RFC 0/4] net/mcdma: add Altera MCDMA poll mode driver
Date: Mon, 9 Nov 2026 09:25:13 -0800 [thread overview]
Message-ID: <20261109092513.59d8b647@phoenix.local> (raw)
In-Reply-To: <20261109160926.800828-1-subhransu.sekhar.prusty@altera.com>
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.
prev parent reply other threads:[~2026-11-09 17:25 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` Stephen Hemminger [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261109092513.59d8b647@phoenix.local \
--to=stephen@networkplumber.org \
--cc=cheryl.bansal@altera.com \
--cc=dev@dpdk.org \
--cc=ferruh.yigit@intel.com \
--cc=nanditha.jayarajan@altera.com \
--cc=rtice@cisco.com \
--cc=subhransu.sekhar.prusty@altera.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox