Stephen Hemminger <[email protected]> writes:

> Provide structured guidelines for AI tools reviewing DPDK
> patches. Focuses on correctness bug detection (resource leaks,
> use-after-free, race conditions), C coding style, forbidden
> tokens, API conventions, and severity classifications.
>
> Mechanical checks already handled by checkpatches.sh (SPDX
> format, commit message formatting, tag ordering) are excluded
> to avoid redundant and potentially contradictory findings.
>
> Signed-off-by: Stephen Hemminger <[email protected]>
> ---

Hi Stephen,

I did some testing with this and the sonnet model looks like it costs
roughly 11 cents US per patch.  This seems acceptable for us, so I think
it's getting close enough to accept.

I am not a prompt optimization engineer, but I've been told by one that
we should try to avoid 'do not' type orders in favor of 'do' orders.
That's maybe a good second pass optimization, because we have a mix.  On
the plus side, there are also lots of examples of good behavior in here,
which I was also told is a good thing to include.

Given that,

Acked-by: Aaron Conole <[email protected]>

>  AGENTS.md | 2162 +++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 2162 insertions(+)
>  create mode 100644 AGENTS.md
>
> diff --git a/AGENTS.md b/AGENTS.md
> new file mode 100644
> index 0000000000..d49ed859f1
> --- /dev/null
> +++ b/AGENTS.md
> @@ -0,0 +1,2162 @@
> +# AGENTS.md - DPDK Code Review Guidelines for AI Tools
> +
> +## CRITICAL INSTRUCTION - READ FIRST
> +
> +This document has two categories of review rules with different
> +confidence thresholds:
> +
> +### 1. Correctness Bugs -- HIGHEST PRIORITY (report at >=50% confidence)
> +
> +**Always report potential correctness bugs.** These are the most
> +valuable findings. When in doubt, report them with a note about
> +your confidence level. A possible use-after-free or resource leak
> +is worth mentioning even if you are not certain.
> +
> +Correctness bugs include:
> +- Use-after-free (accessing memory after `free`/`rte_free`)
> +- Resource leaks on error paths (memory, file descriptors, locks)
> +- Double-free or double-close
> +- NULL pointer dereference
> +- Buffer overflows or out-of-bounds access
> +- Uninitialized variable use in a reachable code path
> +- Race conditions (unsynchronized shared state)
> +- `volatile` used instead of atomic operations for inter-thread shared 
> variables
> +- `__atomic_load_n()`/`__atomic_store_n()`/`__atomic_*()` GCC built-ins 
> instead of `rte_atomic_*_explicit()`
> +- `rte_smp_mb()`/`rte_smp_rmb()`/`rte_smp_wmb()` legacy barriers instead of 
> `rte_atomic_thread_fence()`
> +- Missing error checks on functions that can fail
> +- Error paths that skip cleanup (goto labels, missing free/close)
> +- Incorrect error propagation (wrong return value, lost errno)
> +- Logic errors in conditionals (wrong operator, inverted test)
> +- Integer overflow/truncation in size calculations
> +- Missing bounds checks on user-supplied sizes or indices
> +- `mmap()` return checked against `NULL` instead of `MAP_FAILED`
> +- Statistics accumulation using `=` instead of `+=`
> +- Integer multiply without widening cast losing upper bits (16×16, 32×32, 
> etc.)
> +- Unbounded descriptor chain traversal on guest/API-supplied data
> +- `1 << n` on 64-bit bitmask (must use `1ULL << n` or `RTE_BIT64()`)
> +- Left shift of narrow unsigned (`uint8_t`/`uint16_t`) used as 64-bit value 
> (sign extension via implicit `int` promotion)
> +- Variable assigned then overwritten before being read (dead store)
> +- Same variable used as loop counter in nested loops
> +- `memcpy`/`memcmp`/`memset` with same pointer for source and destination 
> (no-op or undefined)
> +- `rte_mbuf_raw_free_bulk()` called on mbufs that may originate from 
> different mempools (Tx burst, ring dequeue)
> +- MTU confused with frame length (MTU is L3 payload; frame length = MTU + L2 
> overhead)
> +- Using `dev_conf.rxmode.mtu` after configure instead of `dev->data->mtu`
> +- Hardcoded Ethernet overhead instead of per-device calculation
> +- MTU set without enabling `RTE_ETH_RX_OFFLOAD_SCATTER` when frame size 
> exceeds mbuf data room
> +- `mtu_set` callback rejects valid MTU when scatter Rx is already enabled
> +- Rx queue setup silently drops oversized packets instead of enabling 
> scatter or returning an error
> +- Rx function selection ignores `scattered_rx` flag or MTU-vs-mbuf-size check
> +
> +**Do NOT self-censor correctness bugs.** If you identify a code
> +path where a resource could leak or memory could be used after
> +free, report it. Do not talk yourself out of it.
> +
> +### 2. Style, Process, and Formatting -- suppress false positives
> +
> +**NEVER list a style/process item under "Errors" or "Warnings" if
> +you conclude it is correct.**
> +
> +Before outputting any style, formatting, or process error/warning,
> +verify it is actually wrong. If your analysis concludes with
> +phrases like "there's no issue here", "which is fine", "appears
> +correct", "is acceptable", or "this is actually correct" -- then
> +DO NOT INCLUDE IT IN YOUR OUTPUT AT ALL. Delete it. Omit it
> +entirely.
> +
> +This suppression rule applies to: naming conventions,
> +code style, and process compliance. It does NOT apply to
> +correctness bugs listed above. (SPDX/copyright format and
> +commit message formatting are handled by checkpatch and are
> +excluded from AI review entirely.)
> +
> +---
> +
> +This document provides guidelines for AI-powered code review tools
> +when reviewing contributions to the Data Plane Development Kit
> +(DPDK). It is derived from the official DPDK contributor guidelines
> +and validation scripts.
> +
> +## Overview
> +
> +DPDK follows a development process modeled on the Linux Kernel. All
> +patches are reviewed publicly on the mailing list before being
> +merged. AI review tools should verify compliance with the standards
> +outlined below.
> +
> +## Review Philosophy
> +
> +**Correctness bugs are the primary goal of AI review.** Style and
> +formatting checks are secondary. A review that catches a
> +use-after-free but misses a style nit is far more valuable than
> +one that catches every style issue but misses the bug.
> +
> +**BEFORE OUTPUTTING YOUR REVIEW**: Re-read each item.
> +- For correctness bugs: keep them. If you have reasonable doubt
> +  that a code path is safe, report it.
> +- For style/process items: if ANY item contains phrases like "is
> +  fine", "no issue", "appears correct", "is acceptable",
> +  "actually correct" -- DELETE THAT ITEM. Do not include it.
> +
> +### Correctness review guidelines
> +- Trace error paths: for every function that allocates a resource
> +  or acquires a lock, verify that ALL error paths after that point
> +  release it
> +- Check every `goto error` and early `return`: does it clean up
> +  everything allocated so far?
> +- Look for use-after-free: after `free(p)`, is `p` accessed again?
> +- Check that error codes are propagated, not silently dropped
> +- Report at >=50% confidence; note uncertainty if appropriate
> +- It is better to report a potential bug that turns out to be safe
> +  than to miss a real bug
> +
> +### Style and process review guidelines
> +- Only comment on style/process issues when you have HIGH CONFIDENCE (>80%) 
> that an issue exists
> +- Be concise: one sentence per comment when possible
> +- Focus on actionable feedback, not observations
> +- When reviewing text, only comment on clarity issues if the text is 
> genuinely
> +  confusing or could lead to errors.
> +- Do NOT comment on copyright years, SPDX format, or copyright holders - not 
> subject to AI review
> +- Do NOT report an issue then contradict yourself - if something is 
> acceptable, do not mention it at all
> +- Do NOT include items in Errors/Warnings that you then say are "acceptable" 
> or "correct"
> +- Do NOT mention things that are correct or "not an issue" - only report 
> actual problems
> +- Do NOT speculate about contributor circumstances (employment, company 
> policies, etc.)
> +- Before adding any style item to your review, ask: "Is this actually 
> wrong?" If no, omit it entirely.
> +- NEVER write "(Correction: ...)" - if you need to correct yourself, simply 
> omit the item entirely
> +- Do NOT add vague suggestions like "should be verified" or "should be 
> checked" - either it's wrong or don't mention it
> +- Do NOT flag something as an Error then say "which is correct" in the same 
> item
> +- Do NOT say "no issue here" or "this is actually correct" - if there's no 
> issue, do not include it in your review
> +- Do NOT analyze cross-patch dependencies or compilation order - you cannot 
> reliably determine this from patch review
> +- Do NOT claim a patch "would cause compilation failure" based on symbols 
> used in other patches in the series
> +- Review each patch individually for its own correctness; assume the patch 
> author ordered them correctly
> +- When reviewing a patch series, OMIT patches that have no issues. Do not 
> include a patch in your output just to say "no issues found" or to summarize 
> what the patch does. Only include patches where you have actual findings to 
> report.
> +
> +## Priority Areas (Review These)
> +
> +### Security & Safety
> +- Unsafe code blocks without justification
> +- Command injection risks (shell commands, user input)
> +- Path traversal vulnerabilities
> +- Credential exposure or hard coded secrets
> +- Missing input validation on external data
> +- Improper error handling that could leak sensitive info
> +
> +### Correctness Issues
> +- Logic errors that could cause panics or incorrect behavior
> +- Buffer overflows
> +- Race conditions
> +- **`volatile` for inter-thread synchronization**: `volatile` does not
> +  provide atomicity or memory ordering between threads. Use
> +  `rte_atomic_load_explicit()`/`rte_atomic_store_explicit()` with
> +  appropriate `rte_memory_order_*` instead. See the Shared Variable
> +  Access section under Forbidden Tokens for details.
> +- Resource leaks (files, connections, memory)
> +- Off-by-one errors or boundary conditions
> +- Incorrect error propagation
> +- **Use-after-free** (any access to memory after it has been freed)
> +- **Error path resource leaks**: For every allocation or fd open,
> +  trace each error path (`goto`, early `return`, conditional) to
> +  verify the resource is released. Common patterns to check:
> +  - `malloc`/`rte_malloc` followed by a failure that does `return -1`
> +    instead of `goto cleanup`
> +  - `open()`/`socket()` fd not closed on a later error
> +  - Lock acquired but not released on an error branch
> +  - Partially initialized structure where early fields are allocated
> +    but later allocation fails without freeing the early ones
> +- **Double-free / double-close**: resource freed in both a normal
> +  path and an error path, or fd closed but not set to -1 allowing
> +  a second close
> +- **Missing error checks**: functions that can fail (malloc, open,
> +  ioctl, etc.) whose return value is not checked
> +- Changes to API without release notes
> +- Changes to ABI on non-LTS release
> +- Usage of deprecated APIs when replacements exist
> +- Overly defensive code that adds unnecessary checks
> +- Unnecessary comments that just restate what the code already shows (remove 
> them)
> +- **Process-shared synchronization errors** (pthread mutexes in shared 
> memory without `PTHREAD_PROCESS_SHARED`)
> +- **`mmap()` checked against NULL instead of `MAP_FAILED`**: `mmap()` returns
> +  `MAP_FAILED` (i.e., `(void *)-1`) on failure, NOT `NULL`. Checking
> +  `== NULL` or `!= NULL` will miss the error and use an invalid pointer.
> +  ```c
> +  /* BAD - mmap never returns NULL on failure */
> +  p = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
> +  if (p == NULL)       /* WRONG - will not catch MAP_FAILED */
> +      return -1;
> +
> +  /* GOOD */
> +  p = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
> +  if (p == MAP_FAILED)
> +      return -1;
> +  ```
> +- **Statistics accumulation using `=` instead of `+=`**: When accumulating
> +  statistics (counters, byte totals, packet counts), using `=` overwrites
> +  the running total with only the latest value. This silently produces
> +  wrong results.
> +  ```c
> +  /* BAD - overwrites instead of accumulating */
> +  stats->rx_packets = nb_rx;
> +  stats->rx_bytes = total_bytes;
> +
> +  /* GOOD - accumulates over time */
> +  stats->rx_packets += nb_rx;
> +  stats->rx_bytes += total_bytes;
> +  ```
> +  Note: `=` is correct for gauge-type values (e.g., queue depth, link
> +  status) and for initial assignment. Only flag when the context is
> +  clearly incremental accumulation (loop bodies, per-burst counters,
> +  callback tallies).
> +- **Integer multiply without widening cast**: When multiplying integers
> +  to produce a result wider than the operands (sizes, offsets, byte
> +  counts), the multiplication is performed at the operand width and
> +  the upper bits are silently lost before the assignment. This applies
> +  to any narrowing scenario: 16×16 assigned to a 32-bit variable,
> +  32×32 assigned to a 64-bit variable, etc.
> +  ```c
> +  /* BAD - 32×32 overflows before widening to 64 */
> +  uint64_t total_size = num_entries * entry_size;  /* both are uint32_t */
> +  size_t offset = ring->idx * ring->desc_size;     /* 32×32 → truncated */
> +
> +  /* BAD - 16×16 overflows before widening to 32 */
> +  uint32_t byte_count = pkt_len * nb_segs;         /* both are uint16_t */
> +
> +  /* GOOD - widen before multiply */
> +  uint64_t total_size = (uint64_t)num_entries * entry_size;
> +  size_t offset = (size_t)ring->idx * ring->desc_size;
> +  uint32_t byte_count = (uint32_t)pkt_len * nb_segs;
> +  ```
> +- **Unbounded descriptor chain traversal**: When walking a chain of
> +  descriptors (virtio, DMA, NIC Rx/Tx rings) where the chain length
> +  or next-index comes from guest memory or an untrusted API caller,
> +  the traversal MUST have a bounds check or loop counter to prevent
> +  infinite loops or out-of-bounds access from malicious/corrupt data.
> +  ```c
> +  /* BAD - guest controls desc[idx].next with no bound */
> +  while (desc[idx].flags & VRING_DESC_F_NEXT) {
> +      idx = desc[idx].next;          /* guest-supplied, unbounded */
> +      process(desc[idx]);
> +  }
> +
> +  /* GOOD - cap iterations to descriptor ring size */
> +  for (i = 0; i < ring_size; i++) {
> +      if (!(desc[idx].flags & VRING_DESC_F_NEXT))
> +          break;
> +      idx = desc[idx].next;
> +      if (idx >= ring_size)          /* bounds check */
> +          return -EINVAL;
> +      process(desc[idx]);
> +  }
> +  ```
> +  This applies to any chain/linked-list traversal where indices or
> +  pointers originate from untrusted input (guest VMs, user-space
> +  callers, network packets).
> +- **Bitmask shift using `1` instead of `1ULL` on 64-bit masks**: The
> +  literal `1` is `int` (32 bits). Shifting it by 32 or more is
> +  undefined behavior; shifting it by less than 32 but assigning to a
> +  `uint64_t` silently zeroes the upper 32 bits. Use `1ULL << n`,
> +  `UINT64_C(1) << n`, or the DPDK `RTE_BIT64(n)` macro.
> +  ```c
> +  /* BAD - 1 is int, UB if n >= 32, wrong if result used as uint64_t */
> +  uint64_t mask = 1 << bit_pos;
> +  if (features & (1 << VIRTIO_NET_F_MRG_RXBUF))  /* bit 15 OK, bit 32+ UB */
> +
> +  /* GOOD */
> +  uint64_t mask = UINT64_C(1) << bit_pos;
> +  uint64_t mask = 1ULL << bit_pos;
> +  uint64_t mask = RTE_BIT64(bit_pos);        /* preferred in DPDK */
> +  if (features & RTE_BIT64(VIRTIO_NET_F_MRG_RXBUF))
> +  ```
> +  Note: `1U << n` is acceptable when the mask is known to be 32-bit
> +  (e.g., `uint32_t` register fields with `n < 32`). Only flag when
> +  the result is stored in, compared against, or returned as a 64-bit
> +  type, or when `n` could be >= 32.
> +- **Left shift of narrow unsigned type sign-extends to 64-bit**: When
> +  a `uint8_t` or `uint16_t` value is left-shifted, C integer promotion
> +  converts it to `int` (signed 32-bit) before the shift. If the result
> +  has bit 31 set, implicit conversion to `uint64_t`, `size_t`, or use
> +  in pointer arithmetic sign-extends the upper 32 bits to all-1s,
> +  producing a wrong address or value. This is Coverity SIGN_EXTENSION.
> +  The fix is to cast the narrow operand to an unsigned type at least as
> +  wide as the target before shifting.
> +  ```c
> +  /* BAD - uint16_t promotes to signed int, bit 31 may set,
> +   * then sign-extends when converted to 64-bit for pointer math */
> +  uint16_t idx = get_index();
> +  void *addr = base + (idx << wqebb_shift);      /* SIGN_EXTENSION */
> +  uint64_t off = (uint64_t)(idx << shift);        /* too late: shift already 
> in int */
> +
> +  /* BAD - uint8_t shift with result used as size_t */
> +  uint8_t page_order = get_order();
> +  size_t size = page_order << PAGE_SHIFT;          /* promotes to int first 
> */
> +
> +  /* GOOD - cast before shift */
> +  void *addr = base + ((uint64_t)idx << wqebb_shift);
> +  uint64_t off = (uint64_t)idx << shift;
> +  size_t size = (size_t)page_order << PAGE_SHIFT;
> +
> +  /* GOOD - intermediate unsigned variable */
> +  uint32_t offset = (uint32_t)idx << wqebb_shift;  /* OK if result fits 32 
> bits */
> +  ```
> +  Note: This is distinct from the `1 << n` pattern (where the literal
> +  `1` is the problem) and from the integer-multiply pattern (where
> +  the operation is `*` not `<<`). The mechanism is the same C integer
> +  promotion rule, but the code patterns and Coverity checker names
> +  differ. Only flag when the shift result is used in a context wider
> +  than 32 bits (64-bit assignment, pointer arithmetic, function
> +  argument expecting `uint64_t`/`size_t`). A shift whose result is
> +  stored in a `uint32_t` or narrower variable is not affected.
> +- **Variable overwrite before read (dead store)**: A variable is
> +  assigned a value that is unconditionally overwritten before it is
> +  ever read. This usually indicates a logic error (wrong variable
> +  name, missing `if`, copy-paste mistake) or at minimum is dead code.
> +  ```c
> +  /* BAD - first assignment is never read */
> +  ret = validate_input(cfg);
> +  ret = apply_config(cfg);     /* overwrites without checking first ret */
> +  if (ret != 0)
> +      return ret;
> +
> +  /* GOOD - check each return value */
> +  ret = validate_input(cfg);
> +  if (ret != 0)
> +      return ret;
> +  ret = apply_config(cfg);
> +  if (ret != 0)
> +      return ret;
> +  ```
> +  Do NOT flag cases where the initial value is intentionally a default
> +  that may or may not be overwritten (e.g., `int ret = 0;` followed
> +  by a conditional assignment). Only flag unconditional overwrites
> +  where the first value can never be observed.
> +- **Shared loop counter in nested loops**: Using the same variable as
> +  the loop counter in both an outer and inner loop causes the outer
> +  loop to malfunction because the inner loop modifies its counter.
> +  ```c
> +  /* BAD - inner loop clobbers outer loop counter */
> +  int i;
> +  for (i = 0; i < nb_queues; i++) {
> +      setup_queue(i);
> +      for (i = 0; i < nb_descs; i++)    /* BUG: reuses i */
> +          init_desc(i);
> +  }
> +
> +  /* GOOD - distinct loop counters */
> +  for (int i = 0; i < nb_queues; i++) {
> +      setup_queue(i);
> +      for (int j = 0; j < nb_descs; j++)
> +          init_desc(j);
> +  }
> +  ```
> +- **`memcpy`/`memcmp`/`memset` self-argument (same pointer as both
> +  operands)**: Passing the same pointer as both source and destination
> +  to `memcpy()` is undefined behavior per C99. Passing the same
> +  pointer to both arguments of `memcmp()` is a no-op that always
> +  returns 0, indicating a logic error (usually a copy-paste mistake
> +  with the wrong variable name). The same applies to `rte_memcpy()`
> +  and `memmove()` with identical arguments.
> +  ```c
> +  /* BAD - memcpy with same src and dst is undefined behavior */
> +  memcpy(buf, buf, len);
> +  rte_memcpy(dst, dst, len);
> +
> +  /* BAD - memcmp with same pointer always returns 0 (logic error) */
> +  if (memcmp(key, key, KEY_LEN) == 0)  /* always true, wrong variable? */
> +
> +  /* BAD - likely copy-paste: should be comparing two different MACs */
> +  if (memcmp(&eth->src_addr, &eth->src_addr, RTE_ETHER_ADDR_LEN) == 0)
> +
> +  /* GOOD - comparing two different things */
> +  memcpy(dst, src, len);
> +  if (memcmp(&eth->src_addr, &eth->dst_addr, RTE_ETHER_ADDR_LEN) == 0)
> +  ```
> +  This pattern almost always indicates a copy-paste bug where one of
> +  the arguments should be a different variable.
> +- **`rte_mbuf_raw_free_bulk()` on mixed-pool mbuf arrays**: Tx burst 
> functions
> +  and ring/queue dequeue paths receive mbufs that may originate from 
> different
> +  mempools (applications are free to send mbufs from any pool).
> +  `rte_mbuf_raw_free_bulk()` takes an explicit mempool parameter and calls
> +  `rte_mempool_put_bulk()` directly — ALL mbufs in the array must come from
> +  that single pool. If mbufs come from different pools, they are returned to
> +  the wrong pool, corrupting pool accounting and causing hard-to-debug 
> failures.
> +  Note: `rte_pktmbuf_free_bulk()` is safe for mixed pools — it batches mbufs
> +  by pool internally and flushes whenever the pool changes.
> +  ```c
> +  /* BAD - assumes all mbufs are from the same pool */
> +  /* (in tx_burst completion or ring dequeue error path) */
> +  rte_mbuf_raw_free_bulk(mp, mbufs, nb_mbufs);
> +
> +  /* GOOD - rte_pktmbuf_free_bulk handles mixed pools correctly */
> +  rte_pktmbuf_free_bulk(mbufs, nb_mbufs);
> +
> +  /* GOOD - free individually (each mbuf returned to its own pool) */
> +  for (i = 0; i < nb_mbufs; i++)
> +      rte_pktmbuf_free(mbufs[i]);
> +  ```
> +  This applies to any path that frees mbufs submitted by the application:
> +  Tx completion, Tx error cleanup, and ring/queue drain paths.
> +  `rte_mbuf_raw_free_bulk()` is an optimization for the fast-free case
> +  (`RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE`) where the application guarantees
> +  all mbufs come from a single pool with refcnt=1.
> +- **MTU confused with Ethernet frame length**: Maximum Transmission Unit
> +  (MTU) is the maximum L3 payload size (e.g., 1500 bytes for standard
> +  Ethernet). The maximum Ethernet *frame length* includes L2 overhead:
> +  Ethernet header (14 bytes) + optional VLAN tags (4 bytes each) + CRC
> +  (4 bytes). The overhead varies per device depending on supported
> +  encapsulations (VLAN, QinQ, etc.). Confusing MTU with frame length
> +  produces off-by-14-to-22-byte errors in packet size limits, buffer
> +  sizing, and scattered Rx decisions.
> +
> +  **VLAN tag accounting:** The outer VLAN tag is L2 overhead and does
> +  NOT count toward MTU (matching Linux and FreeBSD). A 1522-byte
> +  single-tagged frame is valid at MTU 1500. However, in QinQ the
> +  inner (customer) tag DOES consume MTU — it is part of the customer
> +  frame. So QinQ with MTU 1500 allows only 1496 bytes of L3 payload
> +  unless the port MTU is raised to 1504.
> +
> +  **Using `rxmode.mtu` after configure:** After `rte_eth_dev_configure()`
> +  completes, the canonical MTU is stored in `dev->data->mtu`. The
> +  `dev->data->dev_conf.rxmode.mtu` field is the user's *request* and
> +  must not be read after configure — it becomes stale if
> +  `rte_eth_dev_set_mtu()` is called later. Both configure and set_mtu
> +  write to `dev->data->mtu`; PMDs should always read from there.
> +
> +  **Overhead calculation:** Do not hardcode a single overhead constant.
> +  Use the device's own overhead calculation (typically available via
> +  `dev_info.max_rx_pktlen - dev_info.max_mtu` or an internal
> +  `eth_overhead` field). Different devices support different
> +  encapsulations, so the overhead is not a universal constant.
> +
> +  **Scattered Rx decision:** PMDs compare maximum frame length
> +  (MTU + per-device overhead) against Rx buffer size to decide
> +  whether scattered Rx is needed. Comparing raw MTU against buffer
> +  size is wrong — it underestimates the actual frame size by the
> +  overhead.
> +  ```c
> +  /* BAD - MTU used where frame length is needed */
> +  if (dev->data->mtu > rxq->buf_size)
> +      enable_scattered_rx();
> +
> +  /* BAD - hardcoded overhead, wrong for QinQ-capable devices */
> +  #define ETHER_OVERHEAD 18  /* may be 22 or 26 for VLAN/QinQ */
> +  max_frame = mtu + ETHER_OVERHEAD;
> +
> +  /* BAD - reading rxmode.mtu after configure (stale if set_mtu called) */
> +  static int
> +  mydrv_rx_queue_setup(...) {
> +      mtu = dev->data->dev_conf.rxmode.mtu;  /* WRONG - may be stale */
> +      ...
> +  }
> +
> +  /* GOOD - use dev->data->mtu, the canonical post-configure value */
> +  static int
> +  mydrv_rx_queue_setup(...) {
> +      uint16_t mtu = dev->data->mtu;
> +      ...
> +  }
> +
> +  /* GOOD - use per-device overhead for frame length calculation */
> +  uint32_t frame_overhead = dev_info.max_rx_pktlen - dev_info.max_mtu;
> +  uint32_t max_frame_len = dev->data->mtu + frame_overhead;
> +  if (max_frame_len > rxq->buf_size)
> +      enable_scattered_rx();
> +
> +  /* GOOD - device-specific overhead constant derived from capabilities */
> +  static uint32_t
> +  mydrv_eth_overhead(struct rte_eth_dev *dev) {
> +      uint32_t overhead = RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN;
> +      if (dev->data->dev_conf.rxmode.offloads & RTE_ETH_RX_OFFLOAD_VLAN)
> +          overhead += RTE_VLAN_HLEN;
> +      if (dev->data->dev_conf.rxmode.offloads & RTE_ETH_RX_OFFLOAD_QINQ)
> +          overhead += RTE_VLAN_HLEN;
> +      return overhead;
> +  }
> +  ```
> +  Note: In `rte_eth_dev_configure()` itself, reading `rxmode.mtu` is
> +  correct — that is where the user's request is consumed and written
> +  to `dev->data->mtu`. Only flag reads of `rxmode.mtu` *outside*
> +  configure (queue setup, start, link update, MTU set, etc.).
> +- **Missing scatter Rx for large MTU**: When the configured MTU
> +  produces a frame size (MTU + Ethernet overhead) larger than the mbuf
> +  data buffer size (`rte_pktmbuf_data_room_size(mp) - RTE_PKTMBUF_HEADROOM`),
> +  the PMD MUST either enable scatter Rx (multi-segment receive) or reject
> +  the configuration. Silently accepting the MTU and then truncating or
> +  dropping oversized packets is a correctness bug.
> +  ```c
> +  /* BAD - accepts MTU but will truncate packets that don't fit */
> +  static int
> +  mydrv_mtu_set(struct rte_eth_dev *dev, uint16_t mtu)
> +  {
> +      /* No check against mbuf size or scatter capability */
> +      dev->data->mtu = mtu;
> +      return 0;
> +  }
> +
> +  /* BAD - rejects valid MTU even though scatter is enabled */
> +  if (frame_size > mbuf_data_size)
> +      return -EINVAL;  /* wrong: should allow if scatter is on */
> +
> +  /* GOOD - check scatter and mbuf size */
> +  if (!dev->data->scattered_rx &&
> +      frame_size > dev->data->min_rx_buf_size - RTE_PKTMBUF_HEADROOM)
> +      return -EINVAL;
> +
> +  /* GOOD - auto-enable scatter when needed */
> +  if (frame_size > mbuf_data_size) {
> +      if (!(dev_info.rx_offload_capa & RTE_ETH_RX_OFFLOAD_SCATTER))
> +          return -EINVAL;
> +      dev->data->dev_conf.rxmode.offloads |=
> +          RTE_ETH_RX_OFFLOAD_SCATTER;
> +      dev->data->scattered_rx = 1;
> +  }
> +  ```
> +  Key relationships:
> +  - `dev_info.max_rx_pktlen`: maximum frame the hardware can receive
> +  - `dev_info.max_mtu`: maximum MTU = `max_rx_pktlen` - overhead
> +  - `dev_info.min_rx_bufsize`: minimum Rx buffer the HW requires
> +  - `dev_info.max_rx_bufsize`: maximum single-descriptor buffer size
> +  - `mbuf data size = rte_pktmbuf_data_room_size(mp) - RTE_PKTMBUF_HEADROOM`
> +  - When scatter is off: frame length must fit in a single mbuf
> +  - When scatter is on: frame length can span multiple mbufs;
> +    the PMD selects a scattered Rx function
> +
> +  This pattern should be checked in three places:
> +  1. `dev_configure()` -- validate MTU against mbuf size / scatter
> +  2. `rx_queue_setup()` -- select scattered vs non-scattered Rx path
> +  3. `mtu_set()` -- runtime MTU change must re-validate
> +- **Rx queue function selection ignoring scatter**: When a PMD has
> +  separate fast-path Rx functions for scalar (single-segment) and
> +  scattered (multi-segment) modes, it must select the scattered
> +  variant whenever `dev->data->scattered_rx` is set OR when the
> +  configured frame length exceeds the single mbuf data size.
> +  Failing to do so causes the scalar Rx function to silently drop
> +  or corrupt multi-segment packets.
> +  ```c
> +  /* BAD - only checks offload flag, ignores actual need */
> +  if (rxmode->offloads & RTE_ETH_RX_OFFLOAD_SCATTER)
> +      rx_func = mydrv_recv_scattered;
> +  else
> +      rx_func = mydrv_recv_single;  /* will drop oversized pkts */
> +
> +  /* GOOD - check both the flag and the size */
> +  mbuf_size = rte_pktmbuf_data_room_size(rxq->mp) -
> +              RTE_PKTMBUF_HEADROOM;
> +  max_pkt = dev->data->mtu + overhead;
> +  if ((rxmode->offloads & RTE_ETH_RX_OFFLOAD_SCATTER) ||
> +      max_pkt > mbuf_size) {
> +      dev->data->scattered_rx = 1;
> +      rx_func = mydrv_recv_scattered;
> +  } else {
> +      rx_func = mydrv_recv_single;
> +  }
> +  ```
> +
> +### Architecture & Patterns
> +- Code that violates existing patterns in the code base
> +- Missing error handling
> +- Code that is not safe against signals
> +- **Environment variables used for driver configuration instead of devargs**:
> +  Drivers must use DPDK device arguments (`devargs`) for runtime
> +  configuration, not environment variables. Devargs are preferred because
> +  they are obviously device-specific rather than having global impact,
> +  some launch methods strip all environment variables, and devargs can
> +  be associated on a per-device basis rather than per-device-type.
> +  Use `rte_kvargs_parse()` on the devargs string instead.
> +  ```c
> +  /* BAD - environment variable for driver tuning */
> +  val = getenv("MYDRV_RX_BURST_SIZE");
> +  if (val != NULL)
> +      burst = atoi(val);
> +
> +  /* GOOD - devargs parsed at probe time */
> +  static const char * const valid_args[] = { "rx_burst_size", NULL };
> +  kvlist = rte_kvargs_parse(devargs->args, valid_args);
> +  rte_kvargs_process(kvlist, "rx_burst_size", &parse_uint, &burst);
> +  ```
> +  Note: `getenv()` in EAL itself or in test/example code is acceptable.
> +  This rule applies to libraries under `lib/` and drivers under `drivers/`.
> +
> +### New Library API Design
> +
> +When a patch adds a new library under `lib/`, review API design in
> +addition to correctness and style.
> +
> +**API boundary.** A library should be a compiler, not a framework.
> +The model is `rte_acl`: create a context, feed input, get structured
> +output, caller decides what to do with it. No callbacks needed. If
> +the library requires callers to implement a callback table to
> +function, the boundary is wrong — the library is asking the caller
> +to be its backend.
> +
> +**Callback structs** (Warning / Error). Any function-pointer struct
> +in an installed header is an ABI break waiting to happen. Adding or
> +reordering a member breaks all consumers.
> +- Prefer a single callback parameter over an ops table.
> +- \>5 callbacks: **Warning** — likely needs redesign.
> +- \>20 callbacks: **Error** — this is an app plugin API, not a library.
> +- All callbacks must have Doxygen (contract, return values, ownership).
> +- Void-returning callbacks for failable operations swallow errors —
> +  flag as **Error**.
> +- Callbacks serving app-specific needs (e.g. `verbose_level_get`)
> +  indicate wrong code was extracted into the library.
> +
> +**Extensible structures.** Prefer TLV / tagged-array patterns over
> +enum + union, following `rte_flow_item` and `rte_flow_action` as
> +the model. Type tag + pointer to type-specific data allows adding
> +types without ABI breaks. Flag as **Warning**:
> +- Large enums (100+) consumers must switch on.
> +- Unions that grow with every new feature.
> +- Ask: "What changes when a feature is added next release?" If
> +  "add an enum value and union arm" — should be TLV.
> +
> +**Installed headers.** If it's in `headers` or `indirect_headers`
> +in meson.build, it's public API. Don't call it "private." If truly
> +internal, don't install it.
> +
> +**Global state.** Prefer handle-based APIs (`create`/`destroy`)
> +over singletons. `rte_acl` allows multiple independent classifier
> +instances; new libraries should do the same.
> +
> +**Output ownership.** Prefer caller-allocated or library-allocated-
> +caller-freed over internal static buffers. If static buffers are
> +used, document lifetime and ensure Doxygen examples don't show
> +stale-pointer usage.
> +
> +---
> +
> +## C Coding Style
> +
> +### General Formatting
> +
> +- **Tab width**: 8 characters (hard tabs for indentation, spaces for 
> alignment)
> +- **No trailing whitespace** on lines or at end of files
> +- Files must end with a new line
> +- Code style should be consistent within each file
> +
> +
> +### Comments
> +
> +```c
> +/* Most single-line comments look like this. */
> +
> +/*
> + * VERY important single-line comments look like this.
> + */
> +
> +/*
> + * Multi-line comments look like this. Make them real sentences. Fill
> + * them so they look like real paragraphs.
> + */
> +```
> +
> +### Header File Organization
> +
> +Include order (each group separated by blank line):
> +1. System/libc includes
> +2. DPDK EAL includes
> +3. DPDK misc library includes
> +4. Application-specific includes
> +
> +```c
> +#include <stdio.h>
> +#include <stdlib.h>
> +
> +#include <rte_eal.h>
> +
> +#include <rte_ring.h>
> +#include <rte_mempool.h>
> +
> +#include "application.h"
> +```
> +
> +### Header Guards
> +
> +```c
> +#ifndef _FILE_H_
> +#define _FILE_H_
> +
> +/* Code */
> +
> +#endif /* _FILE_H_ */
> +```
> +
> +### Naming Conventions
> +
> +- **All external symbols** must have `RTE_` or `rte_` prefix
> +- **Macros**: ALL_UPPERCASE with `RTE_` prefix
> +- **Functions**: lowercase with underscores only (no CamelCase)
> +- **Variables**: lowercase with underscores only
> +- **Enum values**: ALL_UPPERCASE with `RTE_<ENUM>_` prefix
> +
> +**Exception**: Driver base directories (`drivers/*/base/`) may use different
> +naming conventions when sharing code across platforms or with upstream 
> vendor code.
> +
> +#### Symbol Naming for Static Linking
> +
> +Drivers and libraries must not expose global variables that could
> +clash when statically linked with other DPDK components or
> +applications. Use consistent and unique prefixes for all exported
> +symbols to avoid namespace collisions.
> +
> +**Good practice**: Use a driver-specific or library-specific prefix for all 
> global variables:
> +
> +```c
> +/* Good - virtio driver uses consistent "virtio_" prefix */
> +const struct virtio_ops virtio_legacy_ops = {
> +     .read = virtio_legacy_read,
> +     .write = virtio_legacy_write,
> +     .configure = virtio_legacy_configure,
> +};
> +
> +const struct virtio_ops virtio_modern_ops = {
> +     .read = virtio_modern_read,
> +     .write = virtio_modern_write,
> +     .configure = virtio_modern_configure,
> +};
> +
> +/* Good - mlx5 driver uses consistent "mlx5_" prefix */
> +struct mlx5_flow_driver_ops mlx5_flow_dv_ops;
> +```
> +
> +**Bad practice**: Generic names that may clash:
> +
> +```c
> +/* Bad - "ops" is too generic, will clash with other drivers */
> +const struct virtio_ops ops = { ... };
> +
> +/* Bad - "legacy_ops" could clash with other legacy implementations */
> +const struct virtio_ops legacy_ops = { ... };
> +
> +/* Bad - "driver_config" is not unique */
> +struct driver_config config;
> +```
> +
> +**Guidelines**:
> +- Prefix all global variables with the driver or library name (e.g., 
> `virtio_`, `mlx5_`, `ixgbe_`)
> +- Prefix all global functions similarly unless they use the `rte_` namespace
> +- Internal static variables do not require prefixes as they have file scope
> +- Consider using the `RTE_` or `rte_` prefix only for symbols that are part 
> of the public DPDK API
> +
> +#### Prohibited Terminology
> +
> +Do not use non-inclusive naming including:
> +- `master/slave` -> Use: primary/secondary, controller/worker, 
> leader/follower
> +- `blacklist/whitelist` -> Use: denylist/allowlist, blocklist/passlist
> +- `cripple` -> Use: impacted, degraded, restricted, immobilized
> +- `tribe` -> Use: team, squad
> +- `sanity check` -> Use: coherence check, test, verification
> +
> +
> +### Comparisons and Boolean Logic
> +
> +```c
> +/* Pointers - compare explicitly with NULL */
> +if (p == NULL)      /* Good */
> +if (p != NULL)      /* Good */
> +if (likely(p != NULL))   /* Good - likely/unlikely don't change this */
> +if (unlikely(p == NULL)) /* Good - likely/unlikely don't change this */
> +if (!p)             /* Bad - don't use ! on pointers */
> +
> +/* Integers - compare explicitly with zero */
> +if (a == 0)         /* Good */
> +if (a != 0)         /* Good */
> +if (errno != 0)     /* Good - this IS explicit */
> +if (likely(a != 0)) /* Good - likely/unlikely don't change this */
> +if (!a)             /* Bad - don't use ! on integers */
> +if (a)              /* Bad - implicit, should be a != 0 */
> +
> +/* Characters - compare with character constant */
> +if (*p == '\0')     /* Good */
> +
> +/* Booleans - direct test is acceptable */
> +if (flag)           /* Good for actual bool types */
> +if (!flag)          /* Good for actual bool types */
> +```
> +
> +**Explicit comparison** means using `==` or `!=` operators (e.g., `x != 0`, 
> `p == NULL`).
> +**Implicit comparison** means relying on truthiness without an operator 
> (e.g., `if (x)`, `if (!p)`).
> +**Note**: `likely()` and `unlikely()` macros do NOT affect whether a 
> comparison is explicit or implicit.
> +
> +### Boolean Usage
> +
> +Prefer `bool` (from `<stdbool.h>`) over `int` for variables,
> +parameters, and return values that are purely true/false. Using
> +`bool` makes intent explicit, enables compiler diagnostics for
> +misuse, and is self-documenting.
> +
> +```c
> +/* Bad - int used as boolean flag */
> +int verbose = 0;
> +int is_enabled = 1;
> +
> +int
> +check_valid(struct item *item)
> +{
> +     if (item->flags & ITEM_VALID)
> +             return 1;
> +     return 0;
> +}
> +
> +/* Good - bool communicates intent */
> +bool verbose = false;
> +bool is_enabled = true;
> +
> +bool
> +check_valid(struct item *item)
> +{
> +     return item->flags & ITEM_VALID;
> +}
> +```
> +
> +**Guidelines:**
> +- Use `bool` for variables that only hold true/false values
> +- Use `bool` return type for predicate functions (functions that
> +  answer a yes/no question, often named `is_*`, `has_*`, `can_*`)
> +- Use `true`/`false` rather than `1`/`0` for boolean assignments
> +- Boolean variables and parameters should not use explicit
> +  comparison: `if (verbose)` is correct, not `if (verbose == true)`
> +- `int` is still appropriate when a value can be negative, is an
> +  error code, or carries more than two states
> +
> +**Structure fields:**
> +- `bool` occupies 1 byte. In packed or cache-critical structures,
> +  consider using a bitfield or flags word instead
> +- For configuration structures and non-hot-path data, `bool` is
> +  preferred over `int` for flag fields
> +
> +```c
> +/* Bad - int flags waste space and obscure intent */
> +struct port_config {
> +     int promiscuous;     /* 0 or 1 */
> +     int link_up;         /* 0 or 1 */
> +     int autoneg;         /* 0 or 1 */
> +     uint16_t mtu;
> +};
> +
> +/* Good - bool for flag fields */
> +struct port_config {
> +     bool promiscuous;
> +     bool link_up;
> +     bool autoneg;
> +     uint16_t mtu;
> +};
> +
> +/* Also good - bitfield for cache-critical structures */
> +struct fast_path_config {
> +     uint32_t flags;      /* bitmask of CONFIG_F_* */
> +     /* ... hot-path fields ... */
> +};
> +```
> +
> +**Do NOT flag:**
> +- `int` return type for functions that return error codes (0 for
> +  success, negative for error) — these are NOT boolean
> +- `int` used for tri-state or multi-state values
> +- `int` flags in existing code where changing the type would be a
> +  large, unrelated refactor
> +- Bitfield or flags-word approaches in performance-critical
> +  structures
> +
> +### Indentation and Braces
> +
> +```c
> +/* Control statements - no braces for single statements */
> +if (val != NULL)
> +     val = realloc(val, newsize);
> +
> +/* Braces on same line as else */
> +if (test)
> +     stmt;
> +else if (bar) {
> +     stmt;
> +     stmt;
> +} else
> +     stmt;
> +
> +/* Switch statements - don't indent case */
> +switch (ch) {
> +case 'a':
> +     aflag = 1;
> +     /* FALLTHROUGH */
> +case 'b':
> +     bflag = 1;
> +     break;
> +default:
> +     usage();
> +}
> +
> +/* Long conditions - double indent continuation */
> +if (really_long_variable_name_1 == really_long_variable_name_2 &&
> +             really_long_variable_name_3 == really_long_variable_name_4)
> +     stmt;
> +```
> +
> +### Variable Declarations
> +
> +- Prefer declaring variables inside the basic block where they are used
> +- Variables may be declared either at the start of the block, or at point of 
> first use (C99 style)
> +- Both declaration styles are acceptable; consistency within a function is 
> preferred
> +- Initialize variables only when a meaningful value exists at declaration 
> time
> +- Use C99 designated initializers for structures
> +
> +```c
> +/* Good - declaration at start of block */
> +int ret;
> +ret = some_function();
> +
> +/* Also good - declaration at point of use (C99 style) */
> +for (int i = 0; i < count; i++)
> +     process(i);
> +
> +/* Good - declaration in inner block where variable is used */
> +if (condition) {
> +     int local_val = compute();
> +     use(local_val);
> +}
> +
> +/* Bad - unnecessary initialization defeats compiler warnings */
> +int ret = 0;
> +ret = some_function();    /* Compiler won't warn if assignment removed */
> +```
> +
> +### Function Format
> +
> +- Return type on its own line
> +- Opening brace on its own line
> +- Place an empty line between declarations and statements
> +
> +```c
> +static char *
> +function(int a1, int b1)
> +{
> +     char *p;
> +
> +     p = do_something(a1, b1);
> +     return p;
> +}
> +```
> +
> +---
> +
> +## Unnecessary Code Patterns
> +
> +The following patterns add unnecessary code, hide bugs, or reduce 
> performance. Avoid them.
> +
> +### Unnecessary Variable Initialization
> +
> +Do not initialize variables that will be assigned before use. This defeats 
> the compiler's uninitialized variable warnings, hiding potential bugs.
> +
> +```c
> +/* Bad - initialization defeats -Wuninitialized */
> +int ret = 0;
> +if (condition)
> +     ret = func_a();
> +else
> +     ret = func_b();
> +
> +/* Good - compiler will warn if any path misses assignment */
> +int ret;
> +if (condition)
> +     ret = func_a();
> +else
> +     ret = func_b();
> +
> +/* Good - meaningful initial value */
> +int count = 0;
> +for (i = 0; i < n; i++)
> +     if (test(i))
> +             count++;
> +```
> +
> +### Unnecessary Casts of void *
> +
> +In C, `void *` converts implicitly to any pointer type. Casting the result 
> of `malloc()`, `calloc()`, `rte_malloc()`, or similar functions is 
> unnecessary and can hide the error of a missing `#include <stdlib.h>`.
> +
> +```c
> +/* Bad - unnecessary cast */
> +struct foo *p = (struct foo *)malloc(sizeof(*p));
> +struct bar *q = (struct bar *)rte_malloc(NULL, sizeof(*q), 0);
> +
> +/* Good - no cast needed in C */
> +struct foo *p = malloc(sizeof(*p));
> +struct bar *q = rte_malloc(NULL, sizeof(*q), 0);
> +```
> +
> +Note: Casts are required in C++ but DPDK is a C project.
> +
> +### Zero-Length Arrays vs Variable-Length Arrays
> +
> +Zero-length arrays (`int arr[0]`) are a GCC extension. Use C99 flexible 
> array members instead.
> +
> +```c
> +/* Bad - GCC extension */
> +struct msg {
> +     int len;
> +     char data[0];
> +};
> +
> +/* Good - C99 flexible array member */
> +struct msg {
> +     int len;
> +     char data[];
> +};
> +```
> +
> +### Unnecessary NULL Checks Before free()
> +
> +Functions like `free()`, `rte_free()`, and similar deallocation functions 
> accept NULL pointers safely. Do not add redundant NULL checks.
> +
> +```c
> +/* Bad - unnecessary check */
> +if (ptr != NULL)
> +     free(ptr);
> +
> +if (rte_ptr != NULL)
> +     rte_free(rte_ptr);
> +
> +/* Good - free handles NULL */
> +free(ptr);
> +rte_free(rte_ptr);
> +```
> +
> +### memset Before free() (CWE-14)
> +
> +Do not call `memset()` to zero memory before freeing it. The compiler may 
> optimize away the `memset()` as a dead store (CWE-14: Compiler Removal of 
> Code to Clear Buffers). For security-sensitive data, use `explicit_bzero()`, 
> `rte_memset_sensitive()`, or `rte_free_sensitive()` which the compiler is not 
> permitted to eliminate.
> +
> +```c
> +/* Bad - compiler may eliminate memset */
> +memset(secret_key, 0, sizeof(secret_key));
> +free(secret_key);
> +
> +/* Good - for non-sensitive data, just free */
> +free(ptr);
> +
> +/* Good - explicit_bzero cannot be optimized away */
> +explicit_bzero(secret_key, sizeof(secret_key));
> +free(secret_key);
> +
> +/* Good - DPDK wrapper for clearing sensitive data */
> +rte_memset_sensitive(secret_key, 0, sizeof(secret_key));
> +free(secret_key);
> +
> +/* Good - for rte_malloc'd sensitive data, combined clear+free */
> +rte_free_sensitive(secret_key);
> +```
> +
> +### Appropriate Use of rte_malloc()
> +
> +`rte_malloc()` allocates from hugepage memory. Use it only when required:
> +
> +- Memory that will be accessed by DMA (NIC descriptors, packet buffers)
> +- Memory shared between primary and secondary DPDK processes
> +- Memory requiring specific NUMA node placement
> +
> +For general allocations, use standard `malloc()` which is faster and does 
> not consume limited hugepage resources.
> +
> +```c
> +/* Bad - rte_malloc for ordinary data structure */
> +struct config *cfg = rte_malloc(NULL, sizeof(*cfg), 0);
> +
> +/* Good - standard malloc for control structures */
> +struct config *cfg = malloc(sizeof(*cfg));
> +
> +/* Good - rte_malloc for DMA-accessible memory */
> +struct rte_mbuf *mbufs = rte_malloc(NULL, n * sizeof(*mbufs), 
> RTE_CACHE_LINE_SIZE);
> +```
> +
> +### Appropriate Use of rte_memcpy()
> +
> +`rte_memcpy()` is optimized for bulk data transfer in the fast path. For 
> general use, standard `memcpy()` is preferred because:
> +
> +- Modern compilers optimize `memcpy()` effectively
> +- `memcpy()` includes bounds checking with `_FORTIFY_SOURCE`
> +- `memcpy()` handles small fixed-size copies efficiently
> +
> +```c
> +/* Bad - rte_memcpy in control path */
> +rte_memcpy(&config, &default_config, sizeof(config));
> +
> +/* Good - standard memcpy for control path */
> +memcpy(&config, &default_config, sizeof(config));
> +
> +/* Good - rte_memcpy for packet data in fast path */
> +rte_memcpy(rte_pktmbuf_mtod(m, void *), payload, len);
> +```
> +
> +### Non-const Function Pointer Arrays
> +
> +Arrays of function pointers (ops tables, dispatch tables, callback arrays)
> +should be declared `const` when their contents are fixed at compile time.
> +A non-`const` function pointer array can be overwritten by bugs or exploits,
> +and prevents the compiler from placing the table in read-only memory.
> +
> +```c
> +/* Bad - mutable when it doesn't need to be */
> +static rte_rx_burst_t rx_functions[] = {
> +     rx_burst_scalar,
> +     rx_burst_vec_avx2,
> +     rx_burst_vec_avx512,
> +};
> +
> +/* Good - immutable dispatch table */
> +static const rte_rx_burst_t rx_functions[] = {
> +     rx_burst_scalar,
> +     rx_burst_vec_avx2,
> +     rx_burst_vec_avx512,
> +};
> +```
> +
> +**Exceptions** (do NOT flag):
> +- Arrays modified at runtime for CPU feature detection or capability probing
> +  (e.g., selecting a burst function based on `rte_cpu_get_flag_enabled()`)
> +- Arrays containing mutable state (e.g., entries that are linked into lists)
> +- Arrays populated dynamically via registration APIs
> +- `dev_ops` or similar structures assigned per-device at init time
> +
> +Only flag when the array is fully initialized at declaration with constant
> +values and never modified thereafter.
> +
> +---
> +
> +## Forbidden Tokens
> +
> +### Functions
> +
> +| Forbidden | Preferred | Context |
> +|-----------|-----------|---------|
> +| `rte_panic()` | Return error codes | lib/, drivers/ |
> +| `rte_exit()` | Return error codes | lib/, drivers/ |
> +| `perror()` | `RTE_LOG()` with `strerror(errno)` | lib/, drivers/ (allowed 
> in examples/, app/test/) |
> +| `printf()` | `RTE_LOG()` | lib/, drivers/ (allowed in examples/, 
> app/test/) |
> +| `fprintf()` | `RTE_LOG()` | lib/, drivers/ (allowed in examples/, 
> app/test/) |
> +| `getenv()` | `rte_kvargs_parse()` / devargs | drivers/ (allowed in EAL, 
> examples/, app/test/) |
> +
> +### Atomics and Memory Barriers
> +
> +| Forbidden | Preferred |
> +|-----------|-----------|
> +| `rte_atomic16/32/64_xxx()` | C11 atomics via `rte_atomic_xxx()` |
> +| `rte_smp_mb()` | `rte_atomic_thread_fence()` |
> +| `rte_smp_rmb()` | `rte_atomic_thread_fence()` |
> +| `rte_smp_wmb()` | `rte_atomic_thread_fence()` |
> +| `__sync_xxx()` | `rte_atomic_xxx()` |
> +| `__atomic_xxx()` | `rte_atomic_xxx()` |
> +| `__ATOMIC_RELAXED` etc. | `rte_memory_order_xxx` |
> +| `__rte_atomic_thread_fence()` | `rte_atomic_thread_fence()` |
> +
> +#### Shared Variable Access: volatile vs Atomics
> +
> +Variables shared between threads or between a thread and a signal
> +handler **must** use atomic operations. The C `volatile` keyword is
> +NOT a substitute for atomics — it prevents compiler optimization
> +of accesses but provides no atomicity guarantees and no memory
> +ordering between threads. On some architectures, `volatile` reads
> +and writes may tear on unaligned or multi-word values.
> +
> +DPDK provides C11 atomic wrappers that are portable across all
> +supported compilers and architectures. Always use these for shared
> +state.
> +
> +**Reading shared variables:**
> +
> +```c
> +/* BAD - volatile provides no atomicity or ordering guarantee */
> +volatile int stop_flag;
> +if (stop_flag)           /* data race, compiler/CPU can reorder */
> +    return;
> +
> +/* BAD - direct access to shared variable without atomic */
> +if (shared->running)     /* undefined behavior if another thread writes */
> +    process();
> +
> +/* GOOD - DPDK C11 atomic wrapper */
> +if (rte_atomic_load_explicit(&shared->stop_flag, rte_memory_order_acquire))
> +    return;
> +
> +/* GOOD - relaxed is fine for statistics or polling a flag where
> + * you don't need to synchronize other memory accesses */
> +count = rte_atomic_load_explicit(&shared->count, rte_memory_order_relaxed);
> +```
> +
> +**Writing shared variables:**
> +
> +```c
> +/* BAD - volatile write */
> +volatile int *flag = &shared->ready;
> +*flag = 1;
> +
> +/* GOOD - atomic store with appropriate ordering */
> +rte_atomic_store_explicit(&shared->ready, 1, rte_memory_order_release);
> +```
> +
> +**Read-modify-write operations:**
> +
> +```c
> +/* BAD - not atomic even with volatile */
> +volatile uint64_t *counter = &stats->packets;
> +*counter += nb_rx;       /* TOCTOU: load, add, store is 3 operations */
> +
> +/* GOOD - atomic add */
> +rte_atomic_fetch_add_explicit(&stats->packets, nb_rx,
> +    rte_memory_order_relaxed);
> +```
> +
> +#### Forbidden Atomic APIs in New Code
> +
> +New code **must not** use GCC/Clang `__atomic_*` built-ins or the
> +legacy DPDK `rte_smp_*mb()` barriers. These are deprecated and
> +will be removed. Use the DPDK C11 atomic wrappers instead.
> +
> +**GCC/Clang `__atomic_*` built-ins — do not use:**
> +
> +```c
> +/* BAD - GCC built-in, not portable, not DPDK API */
> +val = __atomic_load_n(&shared->count, __ATOMIC_RELAXED);
> +__atomic_store_n(&shared->flag, 1, __ATOMIC_RELEASE);
> +__atomic_fetch_add(&shared->counter, 1, __ATOMIC_RELAXED);
> +__atomic_compare_exchange_n(&shared->state, &expected, desired,
> +    0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE);
> +__atomic_thread_fence(__ATOMIC_SEQ_CST);
> +
> +/* GOOD - DPDK C11 atomic wrappers */
> +val = rte_atomic_load_explicit(&shared->count, rte_memory_order_relaxed);
> +rte_atomic_store_explicit(&shared->flag, 1, rte_memory_order_release);
> +rte_atomic_fetch_add_explicit(&shared->counter, 1, rte_memory_order_relaxed);
> +rte_atomic_compare_exchange_strong_explicit(&shared->state, &expected, 
> desired,
> +    rte_memory_order_acq_rel, rte_memory_order_acquire);
> +rte_atomic_thread_fence(rte_memory_order_seq_cst);
> +```
> +
> +Similarly, do not use `__sync_*` built-ins (`__sync_fetch_and_add`,
> +`__sync_bool_compare_and_swap`, etc.) — these are the older GCC
> +atomics with implicit full barriers and are even less appropriate
> +than `__atomic_*`.
> +
> +**Legacy DPDK barriers — do not use:**
> +
> +```c
> +/* BAD - legacy DPDK barriers, deprecated */
> +rte_smp_mb();            /* full memory barrier */
> +rte_smp_rmb();           /* read memory barrier */
> +rte_smp_wmb();           /* write memory barrier */
> +
> +/* GOOD - C11 fence with explicit ordering */
> +rte_atomic_thread_fence(rte_memory_order_seq_cst);   /* replaces 
> rte_smp_mb() */
> +rte_atomic_thread_fence(rte_memory_order_acquire);    /* replaces 
> rte_smp_rmb() */
> +rte_atomic_thread_fence(rte_memory_order_release);    /* replaces 
> rte_smp_wmb() */
> +
> +/* BETTER - use ordering on the atomic operation itself when possible */
> +val = rte_atomic_load_explicit(&shared->flag, rte_memory_order_acquire);
> +rte_atomic_store_explicit(&shared->flag, 1, rte_memory_order_release);
> +```
> +
> +The legacy `rte_atomic16/32/64_*()` type-specific functions (e.g.,
> +`rte_atomic32_inc()`, `rte_atomic64_read()`) are also deprecated.
> +Use `rte_atomic_fetch_add_explicit()`, `rte_atomic_load_explicit()`,
> +etc. with standard C integer types.
> +
> +| Deprecated API | Replacement |
> +|----------------|-------------|
> +| `__atomic_load_n()` | `rte_atomic_load_explicit()` |
> +| `__atomic_store_n()` | `rte_atomic_store_explicit()` |
> +| `__atomic_fetch_add()` | `rte_atomic_fetch_add_explicit()` |
> +| `__atomic_compare_exchange_n()` | 
> `rte_atomic_compare_exchange_strong_explicit()` |
> +| `__atomic_thread_fence()` | `rte_atomic_thread_fence()` |
> +| `__ATOMIC_RELAXED` | `rte_memory_order_relaxed` |
> +| `__ATOMIC_ACQUIRE` | `rte_memory_order_acquire` |
> +| `__ATOMIC_RELEASE` | `rte_memory_order_release` |
> +| `__ATOMIC_ACQ_REL` | `rte_memory_order_acq_rel` |
> +| `__ATOMIC_SEQ_CST` | `rte_memory_order_seq_cst` |
> +| `rte_smp_mb()` | `rte_atomic_thread_fence(rte_memory_order_seq_cst)` |
> +| `rte_smp_rmb()` | `rte_atomic_thread_fence(rte_memory_order_acquire)` |
> +| `rte_smp_wmb()` | `rte_atomic_thread_fence(rte_memory_order_release)` |
> +| `rte_atomic32_inc(&v)` | `rte_atomic_fetch_add_explicit(&v, 1, 
> rte_memory_order_relaxed)` |
> +| `rte_atomic64_read(&v)` | `rte_atomic_load_explicit(&v, 
> rte_memory_order_relaxed)` |
> +
> +#### Memory Ordering Guide
> +
> +Use the weakest ordering that is correct. Stronger ordering
> +constrains hardware and compiler optimization unnecessarily.
> +
> +| DPDK Ordering | When to Use |
> +|---------------|-------------|
> +| `rte_memory_order_relaxed` | Statistics counters, polling flags where no 
> other data depends on the value. Most common for simple counters. |
> +| `rte_memory_order_acquire` | **Load** side of a flag/pointer that guards 
> access to other shared data. Ensures subsequent reads see data published by 
> the releasing thread. |
> +| `rte_memory_order_release` | **Store** side of a flag/pointer that 
> publishes shared data. Ensures all prior writes are visible to a thread that 
> does an acquire load. |
> +| `rte_memory_order_acq_rel` | Read-modify-write operations (e.g., 
> `fetch_add`) that both consume and publish shared state in one operation. |
> +| `rte_memory_order_seq_cst` | Rarely needed. Only when multiple independent 
> atomic variables must be observed in a globally consistent total order. Avoid 
> unless required. |
> +
> +**Common pattern — producer/consumer flag:**
> +
> +```c
> +/* Producer thread: fill buffer, then signal ready */
> +fill_buffer(buf, data, len);
> +rte_atomic_store_explicit(&shared->ready, 1, rte_memory_order_release);
> +
> +/* Consumer thread: wait for flag, then read buffer */
> +while (!rte_atomic_load_explicit(&shared->ready, rte_memory_order_acquire))
> +    rte_pause();
> +process_buffer(buf, len);  /* guaranteed to see producer's writes */
> +```
> +
> +**Common pattern — statistics counter (no ordering needed):**
> +
> +```c
> +rte_atomic_fetch_add_explicit(&port_stats->rx_packets, nb_rx,
> +    rte_memory_order_relaxed);
> +```
> +
> +#### Standalone Fences
> +
> +Prefer ordering on the atomic operation itself (acquire load,
> +release store) over standalone fences. Standalone fences
> +(`rte_atomic_thread_fence()`) are a blunt instrument that
> +orders ALL memory accesses around the fence, not just the
> +atomic variable you care about.
> +
> +```c
> +/* Acceptable but less precise - standalone fence */
> +rte_atomic_store_explicit(&shared->flag, 1, rte_memory_order_relaxed);
> +rte_atomic_thread_fence(rte_memory_order_release);
> +
> +/* Preferred - ordering on the operation itself */
> +rte_atomic_store_explicit(&shared->flag, 1, rte_memory_order_release);
> +```
> +
> +Standalone fences are appropriate when synchronizing multiple
> +non-atomic writes (e.g., filling a structure before publishing
> +a pointer to it) where annotating each write individually is
> +impractical.
> +
> +#### When volatile Is Still Acceptable
> +
> +`volatile` remains correct for:
> +- Memory-mapped I/O registers (hardware MMIO)
> +- Variables shared with signal handlers in single-threaded contexts
> +- Interaction with `setjmp`/`longjmp`
> +
> +`volatile` is NOT correct for:
> +- Any variable accessed by multiple threads
> +- Polling flags between lcores
> +- Statistics counters updated from multiple threads
> +- Flags set by one thread and read by another
> +
> +**Do NOT flag** `volatile` used for MMIO or hardware register access
> +(common in drivers under `drivers/*/base/`).
> +
> +### Threading
> +
> +| Forbidden | Preferred |
> +|-----------|-----------|
> +| `pthread_create()` | `rte_thread_create()` |
> +| `pthread_join()` | `rte_thread_join()` |
> +| `pthread_detach()` | EAL thread functions |
> +| `pthread_setaffinity_np()` | `rte_thread_set_affinity()` |
> +| `rte_thread_set_name()` | `rte_thread_set_prefixed_name()` |
> +| `rte_thread_create_control()` | `rte_thread_create_internal_control()` |
> +
> +### Process-Shared Synchronization
> +
> +When placing synchronization primitives in shared memory (memory accessible 
> by multiple processes, such as DPDK primary/secondary processes or `mmap`'d 
> regions), they **must** be initialized with process-shared attributes. 
> Failure to do so causes **undefined behavior** that may appear to work in 
> testing but fail unpredictably in production.
> +
> +#### pthread Mutexes in Shared Memory
> +
> +**This is an error** - mutex in shared memory without 
> `PTHREAD_PROCESS_SHARED`:
> +
> +```c
> +/* BAD - undefined behavior when used across processes */
> +struct shared_data {
> +     pthread_mutex_t lock;
> +     int counter;
> +};
> +
> +void init_shared(struct shared_data *shm) {
> +     pthread_mutex_init(&shm->lock, NULL);  /* ERROR: missing pshared 
> attribute */
> +}
> +```
> +
> +**Correct implementation**:
> +
> +```c
> +/* GOOD - properly initialized for cross-process use */
> +struct shared_data {
> +     pthread_mutex_t lock;
> +     int counter;
> +};
> +
> +int init_shared(struct shared_data *shm) {
> +     pthread_mutexattr_t attr;
> +     int ret;
> +
> +     ret = pthread_mutexattr_init(&attr);
> +     if (ret != 0)
> +             return -ret;
> +
> +     ret = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
> +     if (ret != 0) {
> +             pthread_mutexattr_destroy(&attr);
> +             return -ret;
> +     }
> +
> +     ret = pthread_mutex_init(&shm->lock, &attr);
> +     pthread_mutexattr_destroy(&attr);
> +
> +     return -ret;
> +}
> +```
> +
> +#### pthread Condition Variables in Shared Memory
> +
> +Condition variables also require the process-shared attribute:
> +
> +```c
> +/* BAD - will not work correctly across processes */
> +pthread_cond_init(&shm->cond, NULL);
> +
> +/* GOOD */
> +pthread_condattr_t cattr;
> +pthread_condattr_init(&cattr);
> +pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED);
> +pthread_cond_init(&shm->cond, &cattr);
> +pthread_condattr_destroy(&cattr);
> +```
> +
> +#### pthread Read-Write Locks in Shared Memory
> +
> +```c
> +/* BAD */
> +pthread_rwlock_init(&shm->rwlock, NULL);
> +
> +/* GOOD */
> +pthread_rwlockattr_t rwattr;
> +pthread_rwlockattr_init(&rwattr);
> +pthread_rwlockattr_setpshared(&rwattr, PTHREAD_PROCESS_SHARED);
> +pthread_rwlock_init(&shm->rwlock, &rwattr);
> +pthread_rwlockattr_destroy(&rwattr);
> +```
> +
> +#### When to Flag This Issue
> +
> +Flag as an **Error** when ALL of the following are true:
> +1. A `pthread_mutex_t`, `pthread_cond_t`, `pthread_rwlock_t`, or 
> `pthread_barrier_t` is initialized
> +2. The primitive is stored in shared memory (identified by context such as: 
> structure in `rte_malloc`/`rte_memzone`, `mmap`'d memory, memory passed to 
> secondary processes, or structures documented as shared)
> +3. The initialization uses `NULL` attributes or attributes without 
> `PTHREAD_PROCESS_SHARED`
> +
> +**Do NOT flag** when:
> +- The mutex is in thread-local or process-private heap memory (`malloc`)
> +- The mutex is a local/static variable not in shared memory
> +- The code already uses `pthread_mutexattr_setpshared()` with 
> `PTHREAD_PROCESS_SHARED`
> +- The synchronization uses DPDK primitives (`rte_spinlock_t`, 
> `rte_rwlock_t`) which are designed for shared memory
> +
> +#### Preferred Alternatives
> +
> +For DPDK code, prefer DPDK's own synchronization primitives which are 
> designed for shared memory:
> +
> +| pthread Primitive | DPDK Alternative |
> +|-------------------|------------------|
> +| `pthread_mutex_t` | `rte_spinlock_t` (busy-wait) or properly initialized 
> pthread mutex |
> +| `pthread_rwlock_t` | `rte_rwlock_t` |
> +| `pthread_spinlock_t` | `rte_spinlock_t` |
> +
> +Note: `rte_spinlock_t` and `rte_rwlock_t` work correctly in shared memory 
> without special initialization, but they are spinning locks unsuitable for 
> long wait times.
> +
> +### Compiler Built-ins and Attributes
> +
> +| Forbidden | Preferred | Notes |
> +|-----------|-----------|-------|
> +| `__attribute__` | RTE macros in `rte_common.h` | Except in 
> `lib/eal/include/rte_common.h` |
> +| `__alignof__` | C11 `alignof` | |
> +| `__typeof__` | `typeof` | |
> +| `__builtin_*` | EAL macros | Except in `lib/eal/` and `drivers/*/base/` |
> +| `__reserved` | Different name | Reserved in Windows headers |
> +| `#pragma` / `_Pragma` | Avoid | Except in `rte_common.h` |
> +
> +### Format Specifiers
> +
> +| Forbidden | Preferred |
> +|-----------|-----------|
> +| `%lld`, `%llu`, `%llx` | `%PRId64`, `%PRIu64`, `%PRIx64` |
> +
> +### Headers and Build
> +
> +| Forbidden | Preferred | Context |
> +|-----------|-----------|---------|
> +| `#include <linux/pci_regs.h>` | `#include <rte_pci.h>` | |
> +| `install_headers()` | Meson `headers` variable | meson.build |
> +| `-DALLOW_EXPERIMENTAL_API` | Not in lib/drivers/app | Build flags |
> +| `allow_experimental_apis` | Not in lib/drivers/app | Meson |
> +| `#undef XXX` | `// XXX is not set` | config/rte_config.h |
> +| Driver headers (`*_driver.h`, `*_pmd.h`) | Public API headers | app/, 
> examples/ |
> +
> +### Testing
> +
> +| Forbidden | Preferred |
> +|-----------|-----------|
> +| `REGISTER_TEST_COMMAND` | `REGISTER_<suite_name>_TEST` |
> +
> +### Documentation
> +
> +| Forbidden | Preferred |
> +|-----------|-----------|
> +| `http://...dpdk.org` | `https://...dpdk.org` |
> +| `//doc.dpdk.org/guides/...` | `:ref:` or `:doc:` Sphinx references |
> +| `::  file.svg` | `::  file.*` (wildcard extension) |
> +
> +---
> +
> +## Deprecated API Usage
> +
> +New patches must not introduce usage of deprecated APIs, macros, or 
> functions.
> +Deprecated items are marked with `RTE_DEPRECATED` or documented in the
> +deprecation notices section of the release notes.
> +
> +### Rules for New Code
> +
> +- Do not call functions marked with `RTE_DEPRECATED` or `__rte_deprecated`
> +- Do not use macros that have been superseded by newer alternatives
> +- Do not use data structures or enum values marked as deprecated
> +- Check `doc/guides/rel_notes/deprecation.rst` for planned deprecations
> +- When a deprecated API has a replacement, use the replacement
> +
> +### Deprecating APIs
> +
> +A patch may mark an API as deprecated provided:
> +
> +- No remaining usages exist in the current DPDK codebase
> +- The deprecation is documented in the release notes
> +- A migration path or replacement API is documented
> +- The `RTE_DEPRECATED` macro is used to generate compiler warnings
> +
> +```c
> +/* Marking a function as deprecated */
> +__rte_deprecated
> +int
> +rte_old_function(void);
> +
> +/* With a message pointing to the replacement */
> +__rte_deprecated_msg("use rte_new_function() instead")
> +int
> +rte_old_function(void);
> +```
> +
> +### Common Deprecated Patterns
> +
> +| Deprecated | Replacement | Notes |
> +|-----------|-------------|-------|
> +| `rte_atomic*_t` types | C11 atomics | Use `rte_atomic_xxx()` wrappers |
> +| `rte_smp_*mb()` barriers | `rte_atomic_thread_fence()` | See Atomics 
> section |
> +| `pthread_*()` in portable code | `rte_thread_*()` | See Threading section |
> +
> +When reviewing patches that add new code, flag any usage of deprecated APIs
> +as requiring change to use the modern replacement.
> +
> +---
> +
> +## API Tag Requirements
> +
> +### `__rte_experimental`
> +
> +- Must appear **alone on the line** immediately preceding the return type
> +- Only allowed in **header files** (not `.c` files)
> +
> +```c
> +/* Correct */
> +__rte_experimental
> +int
> +rte_new_feature(void);
> +
> +/* Wrong - not alone on line */
> +__rte_experimental int rte_new_feature(void);
> +
> +/* Wrong - in .c file */
> +```
> +
> +### `__rte_internal`
> +
> +- Must appear **alone on the line** immediately preceding the return type
> +- Only allowed in **header files** (not `.c` files)
> +
> +```c
> +/* Correct */
> +__rte_internal
> +int
> +internal_function(void);
> +```
> +
> +### Alignment Attributes
> +
> +`__rte_aligned`, `__rte_cache_aligned`, `__rte_cache_min_aligned` may only 
> be used with `struct` or `union` types:
> +
> +```c
> +/* Correct */
> +struct __rte_cache_aligned my_struct {
> +     /* ... */
> +};
> +
> +/* Wrong */
> +int __rte_cache_aligned my_variable;
> +```
> +
> +### Packed Attributes
> +
> +- `__rte_packed_begin` must follow `struct`, `union`, or alignment attributes
> +- `__rte_packed_begin` and `__rte_packed_end` must be used in pairs
> +- Cannot use `__rte_packed_begin` with `enum`
> +
> +```c
> +/* Correct */
> +struct __rte_packed_begin my_packed_struct {
> +     /* ... */
> +} __rte_packed_end;
> +
> +/* Wrong - with enum */
> +enum __rte_packed_begin my_enum {
> +     /* ... */
> +};
> +```
> +
> +---
> +
> +## Code Quality Requirements
> +
> +### Compilation
> +
> +- Each commit must compile independently (for `git bisect`)
> +- No forward dependencies within a patchset
> +- Test with multiple targets, compilers, and options
> +- Use `devtools/test-meson-builds.sh`
> +
> +**Note for AI reviewers**: You cannot verify compilation order or 
> cross-patch dependencies from patch review alone. Do NOT flag patches 
> claiming they "would fail to compile" based on symbols used in other patches 
> in the series. Assume the patch author has ordered them correctly.
> +
> +### Testing
> +
> +- Add tests to `app/test` unit test framework
> +- New API functions must be used in `/app` test directory
> +- New device APIs require at least one driver implementation
> +
> +#### Functional Test Infrastructure
> +
> +Standalone functional tests should use the `TEST_ASSERT` macros and 
> `unit_test_suite_runner` infrastructure for consistency and proper 
> integration with the DPDK test framework.
> +
> +```c
> +#include <rte_test.h>
> +
> +static int
> +test_feature_basic(void)
> +{
> +     int ret;
> +
> +     ret = rte_feature_init();
> +     TEST_ASSERT_SUCCESS(ret, "Failed to initialize feature");
> +
> +     ret = rte_feature_operation();
> +     TEST_ASSERT_EQUAL(ret, 0, "Operation returned unexpected value");
> +
> +     TEST_ASSERT_NOT_NULL(rte_feature_get_ptr(),
> +             "Feature pointer should not be NULL");
> +
> +     return TEST_SUCCESS;
> +}
> +
> +static struct unit_test_suite feature_testsuite = {
> +     .suite_name = "feature_autotest",
> +     .setup = test_feature_setup,
> +     .teardown = test_feature_teardown,
> +     .unit_test_cases = {
> +             TEST_CASE(test_feature_basic),
> +             TEST_CASE(test_feature_advanced),
> +             TEST_CASES_END()
> +     }
> +};
> +
> +static int
> +test_feature(void)
> +{
> +     return unit_test_suite_runner(&feature_testsuite);
> +}
> +
> +REGISTER_FAST_TEST(feature_autotest, NOHUGE_OK, ASAN_OK, test_feature);
> +```
> +
> +The `REGISTER_FAST_TEST` macro parameters are:
> +- Test name (e.g., `feature_autotest`)
> +- `NOHUGE_OK` or `HUGEPAGES_REQUIRED` - whether test can run without 
> hugepages
> +- `ASAN_OK` or `ASAN_FAILS` - whether test is compatible with Address 
> Sanitizer
> +- Test function name
> +
> +Common `TEST_ASSERT` macros:
> +- `TEST_ASSERT(cond, msg, ...)` - Assert condition is true
> +- `TEST_ASSERT_SUCCESS(val, msg, ...)` - Assert value equals 0
> +- `TEST_ASSERT_FAIL(val, msg, ...)` - Assert value is non-zero
> +- `TEST_ASSERT_EQUAL(a, b, msg, ...)` - Assert two values are equal
> +- `TEST_ASSERT_NOT_EQUAL(a, b, msg, ...)` - Assert two values differ
> +- `TEST_ASSERT_NULL(val, msg, ...)` - Assert value is NULL
> +- `TEST_ASSERT_NOT_NULL(val, msg, ...)` - Assert value is not NULL
> +
> +### Documentation
> +
> +- Add Doxygen comments for public APIs
> +- Update release notes in `doc/guides/rel_notes/` for important changes
> +- Code and documentation must be updated atomically in same patch
> +- Only update the **current release** notes file
> +- Documentation must match the code
> +- PMD features must match the features matrix in `doc/guides/nics/features/`
> +- Documentation must match device operations (see 
> `doc/guides/nics/features.rst` for the mapping between features, 
> `eth_dev_ops`, and related APIs)
> +- Release notes are NOT required for:
> +  - Test-only changes (unit tests, functional tests)
> +  - Internal APIs and helper functions (not exported to applications)
> +  - Internal implementation changes that don't affect public API
> +
> +### RST Documentation Style
> +
> +When reviewing `.rst` documentation files, prefer **definition lists**
> +over simple bullet lists where each item has a term and a description.
> +Definition lists produce better-structured HTML/PDF output and are
> +easier to scan.
> +
> +**When to suggest a definition list:**
> +- A bullet list where each item starts with a bold or emphasized term
> +  followed by a dash, colon, or long explanation
> +- Lists of options, parameters, configuration values, or features
> +  where each entry has a name and a description
> +- Glossary-style enumerations
> +
> +**When a simple list is fine (do NOT flag):**
> +- Short lists of items without descriptions (e.g., file names, steps)
> +- Lists where items are single phrases or sentences with no term/definition 
> structure
> +- Enumerated steps in a procedure
> +
> +**RST definition list syntax:**
> +
> +```rst
> +term 1
> +   Description of term 1.
> +
> +term 2
> +   Description of term 2.
> +   Can span multiple lines.
> +```
> +
> +**Example — flag this pattern:**
> +
> +```rst
> +* **error** - Fail with error (default)
> +* **truncate** - Truncate content to fit token limit
> +* **summary** - Request high-level summary review
> +```
> +
> +**Suggest rewriting as:**
> +
> +```rst
> +error
> +   Fail with error (default).
> +
> +truncate
> +   Truncate content to fit token limit.
> +
> +summary
> +   Request high-level summary review.
> +```
> +
> +This is a **Warning**-level suggestion, not an Error. Do not flag it
> +when the existing list structure is appropriate (see "when a simple
> +list is fine" above).
> +
> +### API and Driver Changes
> +
> +- New APIs must be marked as `__rte_experimental`
> +- New APIs must have hooks in `app/testpmd` and tests in the functional test 
> suite
> +- Changes to existing APIs require release notes
> +- New drivers or subsystems must have release notes
> +- Internal APIs (used only within DPDK, not exported to applications) do NOT 
> require release notes
> +
> +### ABI Compatibility and Symbol Exports
> +
> +**IMPORTANT**: DPDK uses automatic symbol map generation. Do **NOT** 
> recommend
> +manually editing `version.map` files - they are auto-generated from source 
> code
> +annotations.
> +
> +#### Symbol Export Macros
> +
> +New public functions must be annotated with export macros (defined in
> +`rte_export.h`). Place the macro on the line immediately before the function
> +definition in the `.c` file:
> +
> +```c
> +/* For stable ABI symbols */
> +RTE_EXPORT_SYMBOL(rte_foo_create)
> +int
> +rte_foo_create(struct rte_foo_config *config)
> +{
> +    /* ... */
> +}
> +
> +/* For experimental symbols (include version when first added) */
> +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_foo_new_feature, 25.03)
> +__rte_experimental
> +int
> +rte_foo_new_feature(void)
> +{
> +    /* ... */
> +}
> +
> +/* For internal symbols (shared between DPDK components only) */
> +RTE_EXPORT_INTERNAL_SYMBOL(rte_foo_internal_helper)
> +int
> +rte_foo_internal_helper(void)
> +{
> +    /* ... */
> +}
> +```
> +
> +#### Symbol Export Rules
> +
> +- `RTE_EXPORT_SYMBOL` - Use for stable ABI functions
> +- `RTE_EXPORT_EXPERIMENTAL_SYMBOL(name, ver)` - Use for new experimental APIs
> +  (version is the DPDK release, e.g., `25.03`)
> +- `RTE_EXPORT_INTERNAL_SYMBOL` - Use for functions shared between DPDK 
> libs/drivers
> +  but not part of public API
> +- Export macros go in `.c` files, not headers
> +- The build system generates linker version maps automatically
> +
> +#### What NOT to Review
> +
> +- Do **NOT** flag missing `version.map` updates - maps are auto-generated
> +- Do **NOT** suggest adding symbols to `lib/*/version.map` files
> +
> +#### ABI Versioning for Changed Functions
> +
> +When changing the signature of an existing stable function, use versioning 
> macros
> +from `rte_function_versioning.h`:
> +
> +- `RTE_VERSION_SYMBOL` - Create versioned symbol for backward compatibility
> +- `RTE_DEFAULT_SYMBOL` - Mark the new default version
> +
> +Follow ABI policy and versioning guidelines in the contributor documentation.
> +Enable ABI checks with `DPDK_ABI_REF_VERSION` environment variable.
> +
> +---
> +
> +## LTS (Long Term Stable) Release Review
> +
> +LTS releases are DPDK versions ending in `.11` (e.g., 23.11, 22.11,
> +21.11, 20.11, 19.11). When reviewing patches targeting an LTS branch,
> +apply stricter criteria:
> +
> +### LTS-Specific Rules
> +
> +- **Only bug fixes allowed** -- no new features
> +- **No new APIs** (experimental or stable)
> +- **ABI must remain unchanged** -- no symbol additions, removals,
> +  or signature changes
> +- Backported fixes should reference the original commit with a
> +  `Fixes:` tag
> +- Copyright years should reflect when the code was originally
> +  written
> +- Be conservative: reject changes that are not clearly bug fixes
> +
> +### What to Flag on LTS Branches
> +
> +**Error:**
> +- New feature code (new functions, new driver capabilities)
> +- New experimental or stable API additions
> +- ABI changes (new or removed symbols, changed function signatures)
> +- Changes that add new configuration options or parameters
> +
> +**Warning:**
> +- Large refactoring that goes beyond what is needed for a fix
> +- Missing `Fixes:` tag on a backported bug fix
> +- Missing `Cc: [email protected]`
> +
> +### When LTS Rules Apply
> +
> +LTS rules apply when the reviewer is told the target release is an
> +LTS version (via the `--release` option or equivalent). If no
> +release is specified, assume the patch targets the main development
> +branch where new features and APIs are allowed.
> +
> +---
> +
> +## Patch Validation Checklist
> +
> +### Commit Message and License
> +
> +Checked by `devtools/checkpatches.sh` -- not duplicated here.
> +
> +### Code Style
> +
> +- [ ] Lines <=100 characters
> +- [ ] Hard tabs for indentation, spaces for alignment
> +- [ ] No trailing whitespace
> +- [ ] Proper include order
> +- [ ] Header guards present
> +- [ ] `rte_`/`RTE_` prefix on external symbols
> +- [ ] Driver/library global variables use unique prefixes (e.g., `virtio_`, 
> `mlx5_`)
> +- [ ] No prohibited terminology
> +- [ ] Proper brace style
> +- [ ] Function return type on own line
> +- [ ] Explicit comparisons: `== NULL`, `== 0`, `!= NULL`, `!= 0`
> +- [ ] No forbidden tokens (see table above)
> +- [ ] No unnecessary code patterns (see section above)
> +- [ ] No usage of deprecated APIs, macros, or functions
> +- [ ] Process-shared primitives in shared memory use `PTHREAD_PROCESS_SHARED`
> +- [ ] `mmap()` return checked against `MAP_FAILED`, not `NULL`
> +- [ ] Statistics use `+=` not `=` for accumulation
> +- [ ] Integer multiplies widened before operation when result is 64-bit
> +- [ ] Descriptor chain traversals bounded by ring size or loop counter
> +- [ ] 64-bit bitmasks use `1ULL <<` or `RTE_BIT64()`, not `1 <<`
> +- [ ] Left shifts of `uint8_t`/`uint16_t` cast to unsigned target width 
> before shift when result is 64-bit
> +- [ ] No unconditional variable overwrites before read
> +- [ ] Nested loops use distinct counter variables
> +- [ ] No `memcpy`/`memcmp` with identical source and destination pointers
> +- [ ] `rte_mbuf_raw_free_bulk()` not used on mixed-pool mbuf arrays (Tx 
> paths, ring dequeue, error paths)
> +- [ ] MTU not confused with frame length (MTU = L3 payload, frame = MTU + L2 
> overhead)
> +- [ ] PMDs read `dev->data->mtu` after configure, not `dev_conf.rxmode.mtu`
> +- [ ] Ethernet overhead not hardcoded -- derived from device capabilities
> +- [ ] Scatter Rx enabled or error returned when frame length exceeds single 
> mbuf data size
> +- [ ] `mtu_set` allows large MTU when scatter Rx is active; re-selects Rx 
> burst function
> +- [ ] Rx queue setup selects scattered Rx function when frame length exceeds 
> mbuf
> +- [ ] Static function pointer arrays declared `const` when contents are 
> compile-time fixed
> +- [ ] `bool` used for pure true/false variables, parameters, and predicate 
> return types
> +- [ ] Shared variables use `rte_atomic_*_explicit()`, not `volatile` or bare 
> access
> +- [ ] No `__atomic_*()` GCC built-ins or `__ATOMIC_*` ordering constants 
> (use `rte_atomic_*_explicit()` and `rte_memory_order_*`)
> +- [ ] No `rte_smp_mb()`/`rte_smp_rmb()`/`rte_smp_wmb()` (use 
> `rte_atomic_thread_fence()`)
> +- [ ] Memory ordering is the weakest correct choice (`relaxed` for counters, 
> `acquire`/`release` for publish/consume)
> +- [ ] Sensitive data cleared with `explicit_bzero()`/`rte_free_sensitive()`, 
> not `memset()`
> +
> +### API Tags
> +
> +- [ ] `__rte_experimental` alone on line, only in headers
> +- [ ] `__rte_internal` alone on line, only in headers
> +- [ ] Alignment attributes only on struct/union
> +- [ ] Packed attributes properly paired
> +- [ ] New public functions have `RTE_EXPORT_*` macro in `.c` file
> +- [ ] Experimental functions use `RTE_EXPORT_EXPERIMENTAL_SYMBOL(name, 
> version)`
> +
> +### Structure
> +
> +- [ ] Each commit compiles independently
> +- [ ] Code and docs updated together
> +- [ ] Documentation matches code behavior
> +- [ ] RST docs use definition lists for term/description patterns
> +- [ ] PMD features match `doc/guides/nics/features/` matrix
> +- [ ] Device operations match documentation (per `features.rst` mappings)
> +- [ ] Tests added/updated as needed
> +- [ ] Functional tests use TEST_ASSERT macros and unit_test_suite_runner
> +- [ ] New APIs marked as `__rte_experimental`
> +- [ ] New APIs have testpmd hooks and functional tests
> +- [ ] Current release notes updated for significant changes
> +- [ ] Release notes updated for API changes
> +- [ ] Release notes updated for new drivers or subsystems
> +
> +---
> +
> +## Meson Build Files
> +
> +### Style Requirements
> +
> +- 4-space indentation (no tabs)
> +- Line continuations double-indented
> +- Lists alphabetically ordered
> +- Short lists (<=3 items): single line, no trailing comma
> +- Long lists: one item per line, trailing comma on last item
> +- No strict line length limit for meson files; lines under 100 characters 
> are acceptable
> +
> +```python
> +# Short list
> +sources = files('file1.c', 'file2.c')
> +
> +# Long list
> +headers = files(
> +     'header1.h',
> +     'header2.h',
> +     'header3.h',
> +)
> +```
> +
> +---
> +
> +## Python Code
> +
> +- Must comply with formatting standards
> +- Use **`black`** for code formatting validation
> +- Line length acceptable up to 100 characters
> +
> +---
> +
> +## Validation Tools
> +
> +Run these before submitting:
> +
> +```bash
> +# Check commit messages
> +devtools/check-git-log.sh -n1
> +
> +# Check patch format and forbidden tokens
> +devtools/checkpatches.sh -n1
> +
> +# Check maintainers coverage
> +devtools/check-maintainers.sh
> +
> +# Build validation
> +devtools/test-meson-builds.sh
> +
> +# Find maintainers for your patch
> +devtools/get-maintainer.sh <patch-file>
> +```
> +
> +---
> +
> +## Severity Levels for AI Review
> +
> +**Error** (must fix):
> +
> +*Correctness bugs (highest value findings):*
> +- Use-after-free
> +- Resource leaks on error paths (memory, file descriptors, locks)
> +- Double-free or double-close
> +- NULL pointer dereference on reachable code path
> +- Buffer overflow or out-of-bounds access
> +- Missing error check on a function that can fail, leading to undefined 
> behavior
> +- Race condition on shared mutable state without synchronization
> +- `volatile` used instead of atomics for inter-thread shared variables
> +- `__atomic_*()` GCC built-ins in new code (must use 
> `rte_atomic_*_explicit()`)
> +- `rte_smp_mb()`/`rte_smp_rmb()`/`rte_smp_wmb()` in new code (must use 
> `rte_atomic_thread_fence()`)
> +- Error path that skips necessary cleanup
> +- `mmap()` return value checked against NULL instead of `MAP_FAILED`
> +- Statistics accumulation using `=` instead of `+=` (overwrite vs increment)
> +- Integer multiply without widening cast losing upper bits (16×16, 32×32, 
> etc.)
> +- Unbounded descriptor chain traversal on guest/API-supplied indices
> +- `1 << n` used for 64-bit bitmask (undefined behavior if n >= 32)
> +- Left shift of `uint8_t`/`uint16_t` used in 64-bit context without widening 
> cast (sign extension)
> +- Variable assigned then unconditionally overwritten before read
> +- Same variable used as counter in nested loops
> +- `memcpy`/`memcmp` with same pointer as both arguments (UB or no-op logic 
> error)
> +- `rte_mbuf_raw_free_bulk()` on mbuf array where mbufs may come from 
> different pools (Tx burst, ring dequeue)
> +- MTU used where frame length is needed or vice versa (off by L2 overhead)
> +- `dev_conf.rxmode.mtu` read after configure instead of `dev->data->mtu` 
> (stale value)
> +- MTU accepted without scatter Rx when frame size exceeds single mbuf 
> capacity (silent truncation/drop)
> +- `mtu_set` rejects valid MTU when scatter Rx is already enabled
> +- Rx function selection ignores `scattered_rx` flag or MTU-vs-mbuf-size 
> comparison
> +
> +*Process and format errors:*
> +- Forbidden tokens in code
> +- `__rte_experimental`/`__rte_internal` in .c files or not alone on line
> +- Compilation failures
> +- ABI breaks without proper versioning
> +- pthread mutex/cond/rwlock in shared memory without `PTHREAD_PROCESS_SHARED`
> +
> +*API design errors (new libraries only):*
> +- Ops/callback struct with 20+ function pointers in an installed header
> +- Callback struct members with no Doxygen documentation
> +- Void-returning callbacks for failable operations (errors silently 
> swallowed)
> +
> +**Warning** (should fix):
> +- Missing Cc: [email protected] for fixes
> +- Documentation gaps
> +- Documentation does not match code behavior
> +- PMD features missing from `doc/guides/nics/features/` matrix
> +- Device operations not documented per `features.rst` mappings
> +- Missing tests
> +- Functional tests not using TEST_ASSERT macros or unit_test_suite_runner
> +- New API not marked as `__rte_experimental`
> +- New API without testpmd hooks or functional tests
> +- New public function missing `RTE_EXPORT_*` macro
> +- API changes without release notes
> +- New drivers or subsystems without release notes
> +- Implicit comparisons (`!ptr` instead of `ptr == NULL`)
> +- Unnecessary variable initialization
> +- Unnecessary casts of `void *`
> +- Unnecessary NULL checks before free
> +- Inappropriate use of `rte_malloc()` or `rte_memcpy()`
> +- Use of `perror()`, `printf()`, `fprintf()` in libraries or drivers 
> (allowed in examples and test code)
> +- Driver/library global variables without unique prefixes (static linking 
> clash risk)
> +- Usage of deprecated APIs, macros, or functions in new code
> +- RST documentation using bullet lists where definition lists would be more 
> appropriate
> +- Ops/callback struct with >5 function pointers in an installed header (ABI 
> risk)
> +- New API using fixed enum+union where TLV pattern would be more extensible
> +- Installed header labeled "private" or "internal" in meson.build
> +- New library using global singleton instead of handle-based API
> +- Static function pointer array not declared `const` when contents are 
> compile-time constant
> +- `int` used instead of `bool` for variables or return values that are 
> purely true/false
> +- `rte_memory_order_seq_cst` used where weaker ordering (`relaxed`, 
> `acquire`/`release`) suffices
> +- Standalone `rte_atomic_thread_fence()` where ordering on the atomic 
> operation itself would be clearer
> +- `getenv()` used in a driver or library for runtime configuration instead 
> of devargs
> +- Hardcoded Ethernet overhead constant instead of per-device overhead 
> calculation
> +- PMD does not advertise `RTE_ETH_RX_OFFLOAD_SCATTER` in `rx_offload_capa` 
> but hardware supports multi-segment Rx
> +- PMD `dev_info` reports `max_rx_pktlen` or `max_mtu` inconsistent with each 
> other or with the Ethernet overhead
> +- `mtu_set` callback does not re-select the Rx burst function after changing 
> MTU
> +
> +**Do NOT flag** (common false positives):
> +- Missing `version.map` updates (maps are auto-generated from `RTE_EXPORT_*` 
> macros)
> +- Suggesting manual edits to any `version.map` file
> +- SPDX/copyright format, copyright years, copyright holders (not subject to 
> AI review)
> +- Commit message formatting (subject length, punctuation, tag order, 
> case-sensitive terms) -- checked by checkpatch
> +- Meson file lines under 100 characters
> +- Comparisons using `== 0`, `!= 0`, `== NULL`, `!= NULL` as "implicit" 
> (these ARE explicit)
> +- Comparisons wrapped in `likely()` or `unlikely()` macros - these are still 
> explicit if using == or !=
> +- Anything you determine is correct (do not mention non-issues or say "No 
> issue here")
> +- `REGISTER_FAST_TEST` using `NOHUGE_OK`/`ASAN_OK` macros (this is the 
> correct current format)
> +- Missing release notes for test-only changes (unit tests do not require 
> release notes)
> +- Missing release notes for internal APIs or helper functions (only public 
> APIs need release notes)
> +- Any item you later correct with "(Correction: ...)" or "actually 
> acceptable" - just omit it
> +- Vague concerns ("should be verified", "should be checked") - if you're not 
> sure it's wrong, don't flag it
> +- Items where you say "which is correct" or "this is correct" - if it's 
> correct, don't mention it at all
> +- Items where you conclude "no issue here" or "this is actually correct" - 
> omit these entirely
> +- Clean patches in a series - do not include a patch just to say "no issues" 
> or describe what it does
> +- Cross-patch compilation dependencies - you cannot determine patch ordering 
> correctness from review
> +- Claims that a symbol "was removed in patch N" causing issues in patch M - 
> assume author ordered correctly
> +- Any speculation about whether patches will compile when applied in sequence
> +- Mutexes/locks in process-private memory (standard `malloc`, stack, static 
> non-shared) - these don't need `PTHREAD_PROCESS_SHARED`
> +- Use of `rte_spinlock_t` or `rte_rwlock_t` in shared memory (these work 
> correctly without special init)
> +- `volatile` used for MMIO/hardware register access in drivers (this is 
> correct usage)
> +- Left shift of `uint8_t`/`uint16_t` where the result is stored in a 
> `uint32_t` or narrower variable and not used in pointer arithmetic or 64-bit 
> context (sign extension cannot occur)
> +- `getenv()` used in EAL, examples, app/test, or build/config scripts (only 
> flag in drivers/ and lib/)
> +- Reading `rxmode.mtu` inside `rte_eth_dev_configure()` implementation (that 
> is where the user request is consumed)
> +- `=` assignment to MTU or frame length fields during initial setup (only 
> flag stale reads of `rxmode.mtu` outside configure)
> +- PMDs that auto-enable scatter when MTU exceeds mbuf size (this is the 
> correct pattern)
> +- Hardcoded `RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN` as overhead when the PMD 
> does not support VLAN and device info is consistent
> +- Tagged frames exceeding 1518 bytes at standard MTU — a single-tagged frame 
> of 1522 bytes is valid at MTU 1500 (the outer VLAN header is L2 overhead, not 
> payload). Note: inner VLAN tags in QinQ *do* consume MTU; see the MTU section 
> for details.
> +
> +**Info** (consider):
> +- Minor style preferences
> +- Optimization suggestions
> +- Alternative approaches
> +
> +---
> +
> +# Response Format
> +
> +When you identify an issue:
> +1. **State the problem** (1 sentence)
> +2. **Why it matters** (1 sentence, only if not obvious)
> +3. **Suggested fix** (code snippet or specific action)
> +
> +Example:
> +This could panic if the string is NULL.
> +
> +---
> +
> +## FINAL CHECK BEFORE SUBMITTING REVIEW
> +
> +Before outputting your review, do two separate passes:
> +
> +### Pass 1: Verify correctness bugs are included
> +
> +Ask: "Did I trace every error path for resource leaks? Did I check
> +for use-after-free? Did I verify error codes are propagated?"
> +
> +If you identified a potential correctness bug but talked yourself
> +out of it, **add it back**. It is better to report a possible bug
> +than to miss a real one.
> +
> +### Pass 2: Remove style/process false positives
> +
> +For EACH style/process item, ask: "Did I conclude this is actually
> +fine/correct/acceptable/no issue?"
> +
> +If YES, DELETE THAT ITEM. It should not be in your output.
> +
> +An item that says "X is wrong... actually this is correct" is a
> +FALSE POSITIVE and must be removed. This applies to style, format,
> +and process items only.
> +
> +**If your Errors section would be empty after this check, that's
> +fine -- it means the patches are good.**


Reply via email to