On Wed, 4 Feb 2026 13:31:29 +0100 [email protected] wrote: > From: Martin Spinler <[email protected]> > > This series implements real multiport for better user experience. > > The existing driver creates one ethdev/port for one PCI device. > As the CESNET-NDK based cards aren't capable to represent each > Ethernet port by own PCI device, new driver implementation > processes real port configuration from firmware/card and switches > from rte_eth_dev_pci_generic_probe to multiple rte_eth_dev_create calls. > > --- > Depends-on: patch-37245 ("doc/nfb: update release notes for nfb driver") > > v7: > * Rebased to patch-37245 > * Removed timestamp update info from release notes > * Added missing ctype include for isdigit() > * Updated style: removed extra brackets > > Martin Spinler (8): > net/nfb: prepare for indirect queue mapping scheme > net/nfb: create one ethdev per ethernet port > net/nfb: add vdev as alternative device probe method > net/nfb: add device argument "port" to limit used ports > net/nfb: init only MACs associated with device > net/nfb: add compatible cards to driver PCI ID table > net/nfb: report firmware version > doc/nfb: cleanup and update guide > > doc/guides/nics/features/nfb.ini | 4 + > doc/guides/nics/nfb.rst | 187 +++++------ > doc/guides/rel_notes/release_26_03.rst | 6 + > drivers/net/nfb/meson.build | 1 + > drivers/net/nfb/nfb.h | 56 +++- > drivers/net/nfb/nfb_ethdev.c | 445 +++++++++++++++++++------ > drivers/net/nfb/nfb_rx.c | 30 +- > drivers/net/nfb/nfb_rx.h | 9 +- > drivers/net/nfb/nfb_tx.c | 27 +- > drivers/net/nfb/nfb_tx.h | 7 +- > drivers/net/nfb/nfb_vdev.c | 107 ++++++ > 11 files changed, 622 insertions(+), 257 deletions(-) > create mode 100644 drivers/net/nfb/nfb_vdev.c >
Improved the AI review process and it saw this. I think you should fix and retest before merge to next-net. Here's the review. The most significant finding is in patch 2/8: Double TAILQ_REMOVE — nfb_eth_common_remove() explicitly removes the entry from the list, then calls rte_eth_dev_destroy() which invokes nfb_eth_dev_uninit(), which also does TAILQ_REMOVE on the same entry. This will corrupt the linked list. The fix is to remove the TAILQ_REMOVE from nfb_eth_common_remove() and let uninit handle it, since uninit is also called from the dev_close path. Other notable findings include unbounded loop counters when populating queue maps and MAC arrays (patches 2 and 5 — trusting firmware consistency without defensive bounds checks), strtoul with base 0 allowing accidental octal parsing of port numbers (patch 4), and a "DYNANIC" typo in the documentation (patch 8). The overall architecture of the series is solid — the indirect queue mapping, per-interface ethdev creation, and TAILQ-based device tracking are well-designed patterns for multi-port NICs. Full report is: # Deep Dive Review: NFB Multi-Port Patch Series (v7, 8 patches) **Series**: `[PATCH v7 1/8]` through `[PATCH v7 8/8]` **Author**: Martin Spinler `<[email protected]>` **Delegate**: Stephen Hemminger --- ## Series Overview This patch series transforms the NFB PMD from a "one ethdev per PCI device" model to "one ethdev per physical Ethernet port." This is a significant architectural change requiring: 1. An indirect queue mapping layer (DPDK queue index → firmware queue ID) 2. Multiple `rte_eth_dev_create()` calls per PCI probe instead of `rte_eth_dev_pci_generic_probe()` 3. A TAILQ-based device list to track all created ethdevs for proper removal 4. A vdev probe path for non-PCI (simulated/virtual) devices 5. Per-port MAC initialization instead of global 6. New PCI IDs and documentation updates The design is generally sound — the libnfb framework's `nc_ifc_map_info` provides the firmware-side port/queue topology, and this series maps it cleanly into DPDK's model. --- ## Patch-by-Patch Analysis ### Patch 1/8: `net/nfb: prepare for indirect queue mapping scheme` **What it does**: Adds `queue_map_rx` / `queue_map_tx` arrays to `struct pmd_priv`, allocated as a single `rte_calloc` block. The RX/TX queue setup functions now look up the firmware queue ID via these maps instead of using the DPDK queue index directly. **Correctness findings**: **Error: Potential out-of-bounds access on `queue_map_rx` / `queue_map_tx`** In `nfb_eth_rx_queue_setup()`: ```c qid = priv->queue_map_rx[rx_queue_id]; ``` There is no bounds check that `rx_queue_id < max_rx_queues` before indexing into the map array. The same applies to `nfb_eth_tx_queue_setup()` with `queue_map_tx[tx_queue_id]`. While ethdev may enforce queue count limits configured via `dev_info`, a defensive check would prevent corruption if the upper layer has a bug. **Confidence**: ~60%. Ethdev's `rte_eth_rx_queue_setup` does validate `rx_queue_id < dev->data->nb_rx_queues`, but `nb_rx_queues` is set by `rte_eth_dev_configure()` which should match `max_rx_queues`. If a mismatch ever occurs (e.g., bug in configure path), this would silently corrupt memory. **Info: Clean refactoring of error paths** The conversion from: ```c if (ret == 0) dev->data->rx_queues[rx_queue_id] = rxq; else rte_free(rxq); ``` to the `goto err_queue_init` pattern is cleaner and more consistent with DPDK conventions. --- ### Patch 2/8: `net/nfb: create one ethdev per ethernet port` This is the core architectural patch. Key changes: - `nfb_eth_dev_init()` signature changes to accept `void *init_data` (for `rte_eth_dev_create()`) - `nfb_eth_common_probe()` opens the NFB device, gets interface map, iterates interfaces - TAILQ list tracks all created ethdevs for removal - `nfb_eth_common_remove()` iterates the TAILQ to destroy matching ethdevs **Correctness findings**: **Error: Double TAILQ_REMOVE in `nfb_eth_common_remove()`** ```c RTE_TAILQ_FOREACH_SAFE(entry, &nfb_eth_dev_list, eth_dev_list, temp) { if (dev == entry->eth_dev->device) { TAILQ_REMOVE(&nfb_eth_dev_list, entry, eth_dev_list); rte_eth_dev_destroy(entry->eth_dev, nfb_eth_dev_uninit); } } ``` `rte_eth_dev_destroy()` calls `nfb_eth_dev_uninit()`, which also does: ```c TAILQ_REMOVE(&nfb_eth_dev_list, internals, eth_dev_list); ``` This is a **double TAILQ_REMOVE** on the same entry. The first `TAILQ_REMOVE` in `nfb_eth_common_remove()` unlinks the entry, then `nfb_eth_dev_uninit()` attempts to remove the already-unlinked entry again. Depending on the TAILQ implementation, this could corrupt the list or cause undefined behavior. **Confidence**: ~85%. The `nfb_eth_dev_uninit()` always does the TAILQ_REMOVE (it was added in this same patch), and `nfb_eth_common_remove()` also does it before calling destroy. One of these must be removed. The cleanest fix is to remove the TAILQ_REMOVE from `nfb_eth_common_remove()` and let `nfb_eth_dev_uninit()` handle it exclusively — that way `dev_close` → `uninit` also works correctly. **Error: `nfb_eth_common_probe()` opens NFB device twice** `nfb_eth_common_probe()` opens the NFB device with `nfb_open()` to get the interface map, then closes it. But then `nfb_eth_dev_init()` (called via `rte_eth_dev_create()`) opens the **same device again** per-interface: ```c internals->nfb = nfb_open(params->probe_params->path); ``` Each ethdev gets its own `nfb_open()` handle, which is probably intentional (each port needs its own handle for independent NDP queue operations). But the probe-level open is done solely to read the interface map and is then closed. This is not a bug per se, but worth confirming the design intent: **N+1 opens** for N ports seems intentional. **Confidence**: 50% — this is likely intentional but should be confirmed. **Warning: `__rte_internal` in header file** ```c __rte_internal int nfb_eth_common_probe(struct nfb_probe_params *params); __rte_internal int nfb_eth_common_remove(struct rte_device *dev); ``` These are declared in `nfb.h` (a private driver header), which is fine for cross-file use within the driver. However, per AGENTS.md guidelines, `__rte_internal` should be alone on its own line before the function — and it is here, so this is correct. The `RTE_EXPORT_INTERNAL_SYMBOL` macros are present in the .c file. No issue. **Warning: `nfb_eth_dev_create_for_ifc()` queue map population may write beyond allocated bounds** In patch 2, the queue map population loop: ```c cnt = 0; for (i = 0; i < mi->rxq_cnt; i++) { if (mi->rxq[i].ifc == ifc->id) priv->queue_map_rx[cnt++] = mi->rxq[i].id; } ``` The array `queue_map_rx` was allocated with size `max_rx_queues` (which is `ifc->rxq_cnt`). If the firmware map reports more RX queues for this interface than `ifc->rxq_cnt`, `cnt` could exceed the allocation. This depends on the libnfb library's consistency guarantees between `ifc->rxq_cnt` and the actual count of `mi->rxq[i].ifc == ifc->id` matches. **Confidence**: ~55%. If `nc_ifc_map_info_create_ordinary()` guarantees consistency, this is safe. If not, a bounds check on `cnt < max_rx_queues` would be prudent. **Info: Static TAILQ without synchronization** The `nfb_eth_dev_list` TAILQ is a static global with no locking. This is safe as long as probe/remove are only called from the EAL main thread (which is the normal DPDK model), but worth noting. --- ### Patch 3/8: `net/nfb: add vdev as alternative device probe method` Adds a vdev driver (`net_vdev_nfb`) for non-PCI devices (simulated/virtual). **Correctness findings**: **Warning: `nfb_default_dev_path()` used without NULL check** ```c params.path = nfb_default_dev_path(); ``` If `nfb_default_dev_path()` returns NULL (e.g., no NFB device present), this will be passed to `nfb_open()` which will likely segfault. The "dev" argument parsing later may override it, but if no `dev=` argument is given, the default path is used. **Confidence**: ~65%. Depends on `nfb_default_dev_path()` semantics. If it always returns a valid string (even for non-existent devices), this is fine. If it can return NULL, a check is needed. **Info: Mixed `free()` and `rte_kvargs_free()` cleanup** The error paths correctly use `free()` for `strdup()`'d memory and `rte_kvargs_free()` for kvargs. This is correct. --- ### Patch 4/8: `net/nfb: add device argument "port" to limit used ports` Adds ability to select specific ports via `port=N` arguments. **Correctness findings**: **Error: `strtoul` with base 0 allows octal/hex input — potential user confusion** ```c port = strtoul(value, &end, 0); ``` Base 0 means `port=010` is parsed as octal (8), not decimal 10. For a port number argument, base 10 would be more predictable: ```c port = strtoul(value, &end, 10); ``` **Confidence**: 70%. Not a crash bug, but a usability issue that could cause silent misconfiguration. **Info: The `nfb_eth_dev_create_for_ifc_by_port()` validation is good** — it checks for empty string, non-digit start, and out-of-range values. --- ### Patch 5/8: `net/nfb: init only MACs associated with device` Replaces the global "open all MACs" approach with per-interface MAC initialization. **Correctness findings**: **Warning: Uses `calloc()` instead of `rte_calloc()` for rxmac/txmac arrays** ```c intl->rxmac = calloc(ifc->eth_cnt, sizeof(*intl->rxmac)); ``` These are pointer arrays (not DMA-accessible), so `calloc()` is actually appropriate per DPDK guidelines (don't use `rte_malloc` for non-DMA control structures). However, `nfb_eth_dev_uninit()` is freeing the `internals` struct with `rte_free()` while these sub-allocations use plain `free()`. This mixing is fine — they're separate allocations — but worth noting for consistency. **Error: `nfb_nc_eth_init()` doesn't validate `cnt` against allocated array size** ```c intl->rxmac = calloc(ifc->eth_cnt, sizeof(*intl->rxmac)); // ... rxm = 0; for (i = 0; i < mi->eth_cnt; i++) { if (mi->eth[i].ifc != ifc->id) continue; intl->rxmac[rxm] = nc_rxmac_open(intl->nfb, mi->eth[i].node_rxmac); if (intl->rxmac[rxm]) rxm++; // ... } ``` The array is allocated with size `ifc->eth_cnt`, but the loop counts matches of `mi->eth[i].ifc == ifc->id` without bounding `rxm` against `ifc->eth_cnt`. If the firmware map has more eth entries for this interface than `ifc->eth_cnt` indicates, this overflows the allocation. **Confidence**: ~55%. Same trust-the-firmware question as in patch 2's queue map population. **Info: Early return for `ifc->eth_cnt == 0` is good defensive coding.** --- ### Patch 6/8: `net/nfb: add compatible cards to driver PCI ID table` Adds PCI IDs for NFB-200G2QL secondary endpoint, FB2CGHH, COMBO-400G1, and generic CESNET-NDK. No correctness issues. Straightforward PCI ID table additions. --- ### Patch 7/8: `net/nfb: report firmware version` Implements `fw_version_get` ethdev op. **Correctness findings**: **Info: Good NULL handling for `proj_name` / `proj_vers`** — both are defaulted to empty string if NULL. **Info: The return value semantics are correct** — returns 0 on success, the required buffer size on truncation, and negative on error, matching the `fw_version_get` API contract. --- ### Patch 8/8: `doc/nfb: cleanup and update guide` Documentation rewrite and release notes update. **Warning: Typo in documentation** ```rst Ready-to-use demo firmwares can be found on the `DYNANIC page <https://dyna-nic.com/try-now/>`_. ``` "DYNANIC" should be "DYNANIC" or more likely "DyNANIC" or whatever the correct product name is — but the URL suggests it might be "dyna-nic", so the display text appears misspelled. Worth verifying the correct branding. **Warning: Feature matrix additions should match implemented features** The patch adds to `features/nfb.ini`: ```ini Unicast MAC filter = Y Timestamp offload = P FW version = Y Multiprocess aware = Y ``` "Unicast MAC filter" and "Timestamp offload" were presumably already supported but undocumented. "FW version" is added in patch 7. "Multiprocess aware" — the series adds `rte_eal_process_type()` checks in some places but the full multiprocess story should be verified. --- ## Cross-Cutting Concerns ### Thread Safety of Static TAILQ The `nfb_eth_dev_list` TAILQ has no locking. DPDK's device management is typically single-threaded (EAL main thread), but if hotplug events could race with probe/remove, this could be problematic. This is consistent with how other DPDK drivers handle similar lists, so it's acceptable. ### Memory Allocation Strategy The series uses an appropriate mix of `rte_calloc`/`rte_zmalloc` for data in the DPDK-managed dev_private area and plain `calloc`/`malloc` for driver-internal control structures (MAC handle arrays). This follows DPDK best practices. ### Error Path Completeness Error paths are generally well-handled across the series. The `goto` cleanup chains in `nfb_eth_dev_init()` are consistent and cover the allocation sequence. --- ## Summary of Findings | Severity | Patch | Finding | |----------|-------|---------| | **Error** | 2/8 | Double `TAILQ_REMOVE` — `nfb_eth_common_remove()` removes from list, then `nfb_eth_dev_uninit()` (called via `rte_eth_dev_destroy()`) removes again | | **Warning** | 2/8 | Queue map population loop lacks bounds check against allocated size | | **Warning** | 5/8 | `nfb_nc_eth_init()` rxmac/txmac population loop lacks bounds check against `ifc->eth_cnt` | | **Warning** | 4/8 | `strtoul` with base 0 allows unintended octal/hex parsing of port numbers | | **Warning** | 3/8 | `nfb_default_dev_path()` return value not checked for NULL before use | | **Warning** | 8/8 | "DYNANIC" appears to be a typo in documentation | | **Info** | 1/8 | No bounds check on `rx_queue_id` / `tx_queue_id` before indexing queue maps | The **double TAILQ_REMOVE** in patch 2 is the most significant finding and will cause list corruption when removing devices. The fix is straightforward: remove the `TAILQ_REMOVE` call from `nfb_eth_common_remove()` and let `nfb_eth_dev_uninit()` handle list removal exclusively.

