DPDK-dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v15 6/7] doc: add AI-assisted patch review to contributing guide
From: Stephen Hemminger @ 2026-05-21 22:44 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Aaron Conole
In-Reply-To: <20260521224550.927338-1-stephen@networkplumber.org>

Add a new section to the contributing guide describing the
analyze-patch.py script which uses AI providers to review patches
against DPDK coding standards before submission to the mailing list.

The new section covers basic usage, provider selection, patch series
handling, LTS release review, and output format options. A note
clarifies that AI review supplements but does not replace human
review.

Also add a reference to the script in the new driver guide's
test tools checklist.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Acked-by: Aaron Conole <aconole@redhat.com>
---
 doc/guides/contributing/new_driver.rst |  2 +
 doc/guides/contributing/patches.rst    | 59 ++++++++++++++++++++++++++
 2 files changed, 61 insertions(+)

diff --git a/doc/guides/contributing/new_driver.rst b/doc/guides/contributing/new_driver.rst
index 555e875329..6c0d356cfd 100644
--- a/doc/guides/contributing/new_driver.rst
+++ b/doc/guides/contributing/new_driver.rst
@@ -210,3 +210,5 @@ Be sure to run the following test tools per patch in a patch series:
 * `check-doc-vs-code.sh`
 * `check-spdx-tag.sh`
 * Build documentation and validate how output looks
+* Optionally run ``analyze-patch.py`` for AI-assisted review
+  (see :ref:`ai_assisted_review` in the Contributing Guide)
diff --git a/doc/guides/contributing/patches.rst b/doc/guides/contributing/patches.rst
index 5f554d47e6..52001570c6 100644
--- a/doc/guides/contributing/patches.rst
+++ b/doc/guides/contributing/patches.rst
@@ -183,6 +183,10 @@ Make your planned changes in the cloned ``dpdk`` repo. Here are some guidelines
 
 * Code and related documentation must be updated atomically in the same patch.
 
+* Consider running the :ref:`AI-assisted review <ai_assisted_review>` tool
+  before submitting to catch common issues early.
+  This is encouraged but not required.
+
 Once the changes have been made you should commit them to your local repo.
 
 For small changes, that do not require specific explanations, it is better to keep things together in the
@@ -503,6 +507,61 @@ Additionally, when contributing to the DTS tool, patches should also be checked
 the ``dts-check-format.sh`` script in the ``devtools`` directory of the DPDK repo.
 To run the script, extra :ref:`Python dependencies <dts_deps>` are needed.
 
+
+.. _ai_assisted_review:
+
+AI-Assisted Patch Review
+------------------------
+
+Contributors may optionally use the ``analyze-patch.py`` script
+to get an AI-assisted review of patches before submitting them to the mailing list.
+The script checks patches against the DPDK coding standards and contribution
+guidelines documented in ``AGENTS.md``.
+
+The script supports multiple AI providers (Anthropic Claude, OpenAI ChatGPT,
+xAI Grok, Google Gemini).  An API key for the chosen provider must be set
+in the corresponding environment variable (see ``--list-providers``).
+
+Basic usage::
+
+   # Review a single patch (default provider: Anthropic Claude)
+   devtools/ai/analyze-patch.py my-patch.patch
+
+   # Use a different provider
+   devtools/ai/analyze-patch.py -p openai my-patch.patch
+
+   # Review for an LTS branch (enables stricter rules)
+   devtools/ai/analyze-patch.py -r 24.11 my-patch.patch
+
+   # List available providers and their API key variables
+   devtools/ai/analyze-patch.py --list-providers
+
+For a patch series in an mbox file, the ``--split-patches`` option reviews
+each patch individually::
+
+   devtools/ai/analyze-patch.py --split-patches series.mbox
+
+   # Review only a range of patches
+   devtools/ai/analyze-patch.py --split-patches --patch-range 1-5 series.mbox
+
+When reviewing for a Long Term Stable (LTS) release, use the ``-r`` option
+with the target version.  Any DPDK release with minor version ``.11``
+(e.g., 23.11, 24.11) is automatically recognized as LTS,
+and the script will enforce stricter rules: bug fixes only, no new features or APIs.
+
+Output can be formatted as plain text (default), Markdown, HTML, or JSON::
+
+   devtools/ai/analyze-patch.py -f markdown -o review.md my-patch.patch
+
+The review guidelines in ``AGENTS.md`` focus on correctness bug detection
+and other DPDK-specific requirements. Commit message formatting and
+SPDX/copyright compliance are checked by ``checkpatches.sh`` and are
+not duplicated in the AI review.
+
+.. note::
+
+   Always verify AI suggestions before acting on them.
+
 .. _contrib_check_compilation:
 
 Checking Compilation
-- 
2.53.0


^ permalink raw reply related

* [PATCH v15 5/7] devtools: add multi-provider AI documentation review script
From: Stephen Hemminger @ 2026-05-21 22:44 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Aaron Conole
In-Reply-To: <20260521224550.927338-1-stephen@networkplumber.org>

Add review-doc.py script that reviews DPDK documentation files for
spelling, grammar, technical correctness, and clarity using AI
language models. Supports batch processing of multiple files.

Supported AI providers:
  - Anthropic Claude (default)
  - OpenAI ChatGPT
  - xAI Grok
  - Google Gemini

Output formats (-f/--format):
  - text: plain text with extractable diff/msg markers (default)
  - markdown: formatted review document
  - html: complete HTML document with styling
  - json: structured data with metadata

For each input file, the script produces:
  - <basename>.{txt,md,html,json}: review in selected format
  - <basename>.diff: unified diff (text/json, or with -d flag)
  - <basename>.msg: commit message (text/json, or with -d flag)

The commit message prefix is automatically determined from the
file path (e.g., doc/guides/prog_guide: for programmer's guide).

Features:
  - Multiple file processing with glob support
  - Provider selection via -p/--provider option
  - Custom model selection via -m/--model option
  - Configurable output directory via -o/--output-dir option
  - Output format selection via -f/--format option
  - Force diff/msg generation via -d/--diff option
  - Quiet mode (-q) suppresses stdout output
  - Verbose mode (-v) shows token usage and API details
  - Email integration using git sendemail configuration
  - Prompt caching support for Anthropic to reduce costs

Usage:
  ./devtools/ai/review-doc.py doc/guides/prog_guide/mempool_lib.rst
  ./devtools/ai/review-doc.py doc/guides/nics/*.rst
  ./devtools/ai/review-doc.py -f html -d -o /tmp doc/guides/nics/*.rst
  ./devtools/ai/review-doc.py --send-email --to dev@dpdk.org file.rst

Requires the appropriate API key environment variable to be set
for the chosen provider (ANTHROPIC_API_KEY, OPENAI_API_KEY,
XAI_API_KEY, or GOOGLE_API_KEY).

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 devtools/ai/review-doc.py | 1013 +++++++++++++++++++++++++++++++++++++
 1 file changed, 1013 insertions(+)
 create mode 100755 devtools/ai/review-doc.py

diff --git a/devtools/ai/review-doc.py b/devtools/ai/review-doc.py
new file mode 100755
index 0000000000..22b5614415
--- /dev/null
+++ b/devtools/ai/review-doc.py
@@ -0,0 +1,1013 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026 Stephen Hemminger
+
+"""
+Review DPDK documentation files using AI providers.
+
+Produces a diff file and commit message compliant with DPDK standards.
+Accepts multiple documentation files and generates output for each.
+Supported providers: Anthropic Claude, OpenAI ChatGPT, xAI Grok, Google Gemini
+"""
+
+import argparse
+import getpass
+import json
+import os
+import re
+import smtplib
+import ssl
+import sys
+from email.message import EmailMessage
+from pathlib import Path
+from typing import Any
+
+from _common import (
+    PROVIDERS,
+    TokenUsage,
+    add_token_args,
+    error,
+    get_git_config,
+    list_providers,
+    print_token_summary,
+    send_request,
+)
+
+# Map output format to file extension
+FORMAT_EXTENSIONS = {
+    "text": ".txt",
+    "markdown": ".md",
+    "html": ".html",
+    "json": ".json",
+}
+
+# Additional markers for extracting diff/msg (used with --diff flag)
+DIFF_MARKERS_INSTRUCTION = """
+
+ADDITIONALLY, at the end of your response, include these exact markers for automated extraction:
+---COMMIT_MESSAGE_START---
+(same commit message as above)
+---COMMIT_MESSAGE_END---
+
+---UNIFIED_DIFF_START---
+(same unified diff as above)
+---UNIFIED_DIFF_END---
+"""
+
+# Commit prefix mappings based on file path
+COMMIT_PREFIX_MAP = [
+    ("doc/guides/prog_guide/", "doc/guides/prog_guide:"),
+    ("doc/guides/sample_app_ug/", "doc/guides/sample_app:"),
+    ("doc/guides/nics/", "doc/guides/nics:"),
+    ("doc/guides/cryptodevs/", "doc/guides/cryptodevs:"),
+    ("doc/guides/compressdevs/", "doc/guides/compressdevs:"),
+    ("doc/guides/eventdevs/", "doc/guides/eventdevs:"),
+    ("doc/guides/rawdevs/", "doc/guides/rawdevs:"),
+    ("doc/guides/bbdevs/", "doc/guides/bbdevs:"),
+    ("doc/guides/gpus/", "doc/guides/gpus:"),
+    ("doc/guides/dmadevs/", "doc/guides/dmadevs:"),
+    ("doc/guides/regexdevs/", "doc/guides/regexdevs:"),
+    ("doc/guides/mldevs/", "doc/guides/mldevs:"),
+    ("doc/guides/rel_notes/", "doc/guides/rel_notes:"),
+    ("doc/guides/linux_gsg/", "doc/guides/linux_gsg:"),
+    ("doc/guides/freebsd_gsg/", "doc/guides/freebsd_gsg:"),
+    ("doc/guides/windows_gsg/", "doc/guides/windows_gsg:"),
+    ("doc/guides/tools/", "doc/guides/tools:"),
+    ("doc/guides/testpmd_app_ug/", "doc/guides/testpmd:"),
+    ("doc/guides/howto/", "doc/guides/howto:"),
+    ("doc/guides/contributing/", "doc/guides/contributing:"),
+    ("doc/guides/platform/", "doc/guides/platform:"),
+    ("doc/guides/", "doc:"),
+    ("doc/api/", "doc/api:"),
+    ("doc/", "doc:"),
+]
+
+SYSTEM_PROMPT = """\
+You are an expert technical documentation reviewer for DPDK.
+Your task is to review documentation files and suggest improvements for:
+- Spelling errors
+- Grammar issues
+- Technical correctness
+- Clarity and readability
+- Consistency with DPDK terminology
+
+IMPORTANT COMMIT MESSAGE RULES (from check-git-log.sh):
+- Subject line MUST be <=60 characters
+- Format: "prefix: lowercase description"
+- First word after colon must be lowercase (except acronyms like Rx, Tx, VF, MAC, API)
+- Use imperative mood (e.g., "fix typo" not "fixed typo" or "fixes typo")
+- NO trailing period on subject line
+- NO punctuation marks: , ; ! ? & |
+- NO underscores in subject after colon
+- Body lines wrapped at 75 characters
+- Body must NOT start with "It"
+- Do NOT include Signed-off-by (user adds via git commit --sign)
+- Only use "Fixes:" tag for actual errors in documentation, not style improvements
+
+Case-sensitive terms (must use exact case):
+- Rx, Tx (not RX, TX, rx, tx)
+- VF, PF (not vf, pf)
+- MAC, VLAN, RSS, API
+- Linux, Windows, FreeBSD
+
+For style/clarity improvements, do NOT use Fixes tag.
+For actual errors (wrong information, broken examples), include Fixes tag \
+if you can identify the commit."""
+
+FORMAT_INSTRUCTIONS = {
+    "text": """
+OUTPUT FORMAT:
+You must output exactly two sections:
+
+1. COMMIT_MESSAGE section containing the complete commit message
+2. UNIFIED_DIFF section containing the unified diff
+
+Use these exact markers:
+---COMMIT_MESSAGE_START---
+(commit message here)
+---COMMIT_MESSAGE_END---
+
+---UNIFIED_DIFF_START---
+(unified diff here)
+---UNIFIED_DIFF_END---
+
+The diff should be in unified format that can be applied with "git apply".
+If no changes are needed, output empty sections with a note.""",
+    "markdown": """
+OUTPUT FORMAT:
+Provide your review in Markdown format with:
+
+## Summary
+Brief description of changes
+
+## Commit Message
+```
+(complete commit message here, ready to use)
+```
+
+## Changes
+For each change:
+### Issue N: Brief title
+- **Location**: file path and line
+- **Problem**: description
+- **Fix**: suggested correction
+
+## Unified Diff
+```diff
+(unified diff here)
+```""",
+    "html": """
+OUTPUT FORMAT:
+Provide your review in HTML format with:
+- <h2> for sections (Summary, Commit Message, Changes, Diff)
+- <pre><code> for commit message and diff
+- <ul>/<li> for individual issues
+- Do NOT include <html>, <head>, or <body> tags - just the content
+
+Include sections for: Summary, Commit Message, Changes, Unified Diff""",
+    "json": """
+OUTPUT FORMAT:
+Provide your review as JSON with this structure:
+{
+  "summary": "Brief description of changes",
+  "commit_message": "Complete commit message ready to use",
+  "changes": [
+    {
+      "type": "spelling|grammar|technical|clarity|style",
+      "location": "line number or section",
+      "original": "original text",
+      "suggested": "corrected text",
+      "reason": "why this change"
+    }
+  ],
+  "diff": "unified diff as a string",
+  "stats": {
+    "total_issues": 0,
+    "spelling": 0,
+    "grammar": 0,
+    "technical": 0,
+    "clarity": 0
+  }
+}
+Output ONLY valid JSON, no markdown code fences or other text.""",
+}
+
+USER_PROMPT = """\
+Review the following DPDK documentation file and provide improvements.
+
+File path: {doc_file}
+Commit message prefix to use: {commit_prefix}
+
+{format_instruction}
+
+---DOCUMENT CONTENT---
+"""
+
+
+def get_smtp_config() -> dict[str, Any]:
+    """Get SMTP configuration from git config sendemail settings."""
+    config = {
+        "server": get_git_config("sendemail.smtpserver"),
+        "port": get_git_config("sendemail.smtpserverport"),
+        "user": get_git_config("sendemail.smtpuser"),
+        "encryption": get_git_config("sendemail.smtpencryption"),
+        "password": get_git_config("sendemail.smtppass"),
+    }
+
+    # Set defaults
+    if not config["port"]:
+        if config["encryption"] == "ssl":
+            config["port"] = "465"
+        else:
+            config["port"] = "587"
+
+    # Convert port to int
+    try:
+        config["port"] = int(config["port"])
+    except (TypeError, ValueError):
+        error(
+            f"Invalid sendemail.smtpserverport: {config['port']!r} "
+            "(must be a number)"
+        )
+
+    return config
+
+
+def get_commit_prefix(filepath: str) -> str:
+    """Determine commit message prefix from file path."""
+    for prefix_path, prefix in COMMIT_PREFIX_MAP:
+        if filepath.startswith(prefix_path):
+            return prefix
+    return "doc:"
+
+
+def build_user_prompt(
+    doc_file: str,
+    commit_prefix: str,
+    output_format: str = "text",
+    include_diff_markers: bool = False,
+) -> str:
+    """Build the user prompt with format instructions."""
+    format_instruction = FORMAT_INSTRUCTIONS.get(output_format, "")
+    if include_diff_markers and output_format not in ("text", "json"):
+        format_instruction += DIFF_MARKERS_INSTRUCTION
+    return USER_PROMPT.format(
+        doc_file=doc_file,
+        commit_prefix=commit_prefix,
+        format_instruction=format_instruction,
+    )
+
+
+def build_anthropic_request(
+    model: str,
+    max_tokens: int,
+    agents_content: str,
+    doc_content: str,
+    doc_file: str,
+    commit_prefix: str,
+    output_format: str = "text",
+    include_diff_markers: bool = False,
+) -> dict[str, Any]:
+    """Build request payload for Anthropic API."""
+    user_prompt = build_user_prompt(
+        doc_file, commit_prefix, output_format, include_diff_markers
+    )
+    return {
+        "model": model,
+        "max_tokens": max_tokens,
+        "system": [
+            {"type": "text", "text": SYSTEM_PROMPT},
+            {
+                "type": "text",
+                "text": agents_content,
+                "cache_control": {"type": "ephemeral"},
+            },
+        ],
+        "messages": [
+            {
+                "role": "user",
+                "content": user_prompt + doc_content,
+            }
+        ],
+    }
+
+
+def build_openai_request(
+    model: str,
+    max_tokens: int,
+    agents_content: str,
+    doc_content: str,
+    doc_file: str,
+    commit_prefix: str,
+    output_format: str = "text",
+    include_diff_markers: bool = False,
+) -> dict[str, Any]:
+    """Build request payload for OpenAI-compatible APIs."""
+    user_prompt = build_user_prompt(
+        doc_file, commit_prefix, output_format, include_diff_markers
+    )
+    return {
+        "model": model,
+        "max_tokens": max_tokens,
+        "messages": [
+            {"role": "system", "content": SYSTEM_PROMPT},
+            {"role": "system", "content": agents_content},
+            {
+                "role": "user",
+                "content": user_prompt + doc_content,
+            },
+        ],
+    }
+
+
+def build_google_request(
+    max_tokens: int,
+    agents_content: str,
+    doc_content: str,
+    doc_file: str,
+    commit_prefix: str,
+    output_format: str = "text",
+    include_diff_markers: bool = False,
+) -> dict[str, Any]:
+    """Build request payload for Google Gemini API."""
+    user_prompt = build_user_prompt(
+        doc_file, commit_prefix, output_format, include_diff_markers
+    )
+    return {
+        "systemInstruction": {
+            "parts": [
+                {"text": SYSTEM_PROMPT},
+                {"text": agents_content},
+            ],
+        },
+        "contents": [
+            {
+                "role": "user",
+                "parts": [{"text": user_prompt + doc_content}],
+            },
+        ],
+        "generationConfig": {"maxOutputTokens": max_tokens},
+    }
+
+
+def call_api(
+    provider: str,
+    api_key: str,
+    model: str,
+    max_tokens: int,
+    agents_content: str,
+    doc_content: str,
+    doc_file: str,
+    commit_prefix: str,
+    output_format: str = "text",
+    include_diff_markers: bool = False,
+    verbose: bool = False,
+    timeout: int = 120,
+) -> tuple[str, TokenUsage]:
+    """Build the per-provider request body and dispatch via _common."""
+    if provider == "anthropic":
+        request_data = build_anthropic_request(
+            model,
+            max_tokens,
+            agents_content,
+            doc_content,
+            doc_file,
+            commit_prefix,
+            output_format,
+            include_diff_markers,
+        )
+    elif provider == "google":
+        request_data = build_google_request(
+            max_tokens,
+            agents_content,
+            doc_content,
+            doc_file,
+            commit_prefix,
+            output_format,
+            include_diff_markers,
+        )
+    else:  # openai, xai
+        request_data = build_openai_request(
+            model,
+            max_tokens,
+            agents_content,
+            doc_content,
+            doc_file,
+            commit_prefix,
+            output_format,
+            include_diff_markers,
+        )
+    return send_request(
+        provider,
+        api_key,
+        model,
+        request_data,
+        timeout=timeout,
+        verbose=verbose,
+    )
+
+
+def parse_review_text(review_text: str) -> tuple[str, str]:
+    """Extract commit message and diff from text format response."""
+    commit_msg = ""
+    diff = ""
+
+    # Extract commit message
+    msg_match = re.search(
+        r"---COMMIT_MESSAGE_START---\s*\n(.*?)\n---COMMIT_MESSAGE_END---",
+        review_text,
+        re.DOTALL,
+    )
+    if msg_match:
+        commit_msg = msg_match.group(1).strip()
+
+    # Extract unified diff
+    diff_match = re.search(
+        r"---UNIFIED_DIFF_START---\s*\n(.*?)\n---UNIFIED_DIFF_END---",
+        review_text,
+        re.DOTALL,
+    )
+    if diff_match:
+        diff = diff_match.group(1).strip()
+        # Clean up any markdown code fence if present
+        diff = re.sub(r"^```diff\s*\n?", "", diff)
+        diff = re.sub(r"\n?```\s*$", "", diff)
+
+    return commit_msg, diff
+
+
+def strip_diff_markers(text: str) -> str:
+    """Remove the diff/msg extraction markers from text."""
+    # Remove commit message markers and content
+    text = re.sub(
+        r"\n*---COMMIT_MESSAGE_START---\s*\n.*?\n---COMMIT_MESSAGE_END---\s*",
+        "",
+        text,
+        flags=re.DOTALL,
+    )
+    # Remove unified diff markers and content
+    text = re.sub(
+        r"\n*---UNIFIED_DIFF_START---\s*\n.*?\n---UNIFIED_DIFF_END---\s*",
+        "",
+        text,
+        flags=re.DOTALL,
+    )
+    return text.strip()
+
+
+def send_email(
+    to_addrs: list[str],
+    cc_addrs: list[str],
+    from_addr: str,
+    subject: str,
+    in_reply_to: str | None,
+    body: str,
+    dry_run: bool = False,
+    verbose: bool = False,
+) -> None:
+    """Send review email via SMTP using git sendemail config.
+
+    TODO: This duplicates send_email in analyze-patch.py, which uses the
+    git send-email / sendmail / msmtp fallback chain instead of direct
+    smtplib. Both approaches work; pick one and move it to _common.py.
+    """
+    # Build email message
+    msg = EmailMessage()
+    msg["From"] = from_addr
+    msg["To"] = ", ".join(to_addrs)
+    if cc_addrs:
+        msg["Cc"] = ", ".join(cc_addrs)
+    msg["Subject"] = subject
+    if in_reply_to:
+        msg["In-Reply-To"] = in_reply_to
+        msg["References"] = in_reply_to
+    msg.set_content(body)
+
+    if dry_run:
+        print("=== Email Preview (dry-run) ===", file=sys.stderr)
+        print(msg.as_string(), file=sys.stderr)
+        print("=== End Preview ===", file=sys.stderr)
+        return
+
+    # Get SMTP configuration from git config
+    smtp_config = get_smtp_config()
+
+    if not smtp_config["server"]:
+        error("No SMTP server configured. Set git config sendemail.smtpserver")
+
+    server = smtp_config["server"]
+    port = smtp_config["port"]
+    user = smtp_config["user"]
+    encryption = smtp_config["encryption"]
+
+    # Get password from environment or git config, or prompt
+    password = os.environ.get("SMTP_PASSWORD") or smtp_config["password"]
+    if user and not password:
+        password = getpass.getpass(f"SMTP password for {user}@{server}: ")
+
+    if verbose:
+        print(f"SMTP server: {server}:{port}", file=sys.stderr)
+        print(f"SMTP user: {user or '(none)'}", file=sys.stderr)
+        print(f"Encryption: {encryption or 'starttls'}", file=sys.stderr)
+
+    # Collect all recipients
+    all_recipients = list(to_addrs)
+    if cc_addrs:
+        all_recipients.extend(cc_addrs)
+
+    try:
+        if encryption == "ssl":
+            # SSL/TLS connection from the start (port 465)
+            context = ssl.create_default_context()
+            with smtplib.SMTP_SSL(server, port, context=context) as smtp:
+                if user and password:
+                    smtp.login(user, password)
+                smtp.send_message(msg, from_addr, all_recipients)
+        else:
+            # STARTTLS (port 587) or plain (port 25)
+            with smtplib.SMTP(server, port) as smtp:
+                smtp.ehlo()
+                if encryption == "tls" or port == 587:
+                    context = ssl.create_default_context()
+                    smtp.starttls(context=context)
+                    smtp.ehlo()
+                if user and password:
+                    smtp.login(user, password)
+                smtp.send_message(msg, from_addr, all_recipients)
+
+        print(f"Email sent via SMTP ({server}:{port})", file=sys.stderr)
+
+    except smtplib.SMTPAuthenticationError as e:
+        error(f"SMTP authentication failed: {e}")
+    except smtplib.SMTPException as e:
+        error(f"SMTP error: {e}")
+    except OSError as e:
+        error(f"Connection error to {server}:{port}: {e}")
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser(
+        description="Review DPDK documentation files using AI providers. "
+        "Accepts multiple files and generates output for each.",
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        epilog="""
+Examples:
+    %(prog)s doc/guides/prog_guide/mempool_lib.rst
+    %(prog)s doc/guides/nics/*.rst              # Review all NIC docs
+    %(prog)s -p openai -o /tmp doc/guides/nics/ixgbe.rst doc/guides/nics/i40e.rst
+    %(prog)s -f html -d -o /tmp/reviews doc/guides/nics/*.rst  # HTML + diff files
+    %(prog)s -f json -o /tmp doc/guides/howto/flow_bifurcation.rst
+    %(prog)s --send-email --to dev@dpdk.org doc/guides/nics/ixgbe.rst
+
+Output files (in output-dir):
+    <basename>.txt|.md|.html|.json  Review in selected format
+    <basename>.diff                  Unified diff (text/json, or with --diff)
+    <basename>.msg                   Commit message (text/json, or with --diff)
+
+After review:
+    git apply <basename>.diff
+    git commit -sF <basename>.msg
+
+SMTP Configuration (from git config):
+    sendemail.smtpserver      SMTP server hostname
+    sendemail.smtpserverport  SMTP port (default: 587 for TLS, 465 for SSL)
+    sendemail.smtpuser        SMTP username
+    sendemail.smtpencryption  'tls' for STARTTLS, 'ssl' for SSL/TLS
+    sendemail.smtppass        SMTP password (or set SMTP_PASSWORD env var)
+
+Example git config:
+    git config --global sendemail.smtpserver smtp.gmail.com
+    git config --global sendemail.smtpserverport 587
+    git config --global sendemail.smtpuser yourname@gmail.com
+    git config --global sendemail.smtpencryption tls
+
+Token Usage:
+    Use --show-tokens (or -v/--verbose) to print a token usage summary
+    on stderr after the run. Off by default.
+        """,
+    )
+
+    parser.add_argument(
+        "doc_files",
+        nargs="+",
+        metavar="doc_file",
+        help="Documentation file(s) to review",
+    )
+    parser.add_argument(
+        "-p",
+        "--provider",
+        choices=PROVIDERS.keys(),
+        default="anthropic",
+        help="AI provider (default: anthropic)",
+    )
+    parser.add_argument(
+        "-a",
+        "--agents",
+        default="AGENTS.md",
+        help="Path to AGENTS.md file (default: AGENTS.md)",
+    )
+    parser.add_argument(
+        "-m",
+        "--model",
+        help="Model to use (default: provider-specific)",
+    )
+    parser.add_argument(
+        "-t",
+        "--tokens",
+        type=int,
+        default=8192,
+        help="Max tokens for response (default: 8192)",
+    )
+    parser.add_argument(
+        "-o",
+        "--output-dir",
+        default=".",
+        help="Output directory for all output files (default: .)",
+    )
+    parser.add_argument(
+        "-v",
+        "--verbose",
+        action="store_true",
+        help="Show API request details",
+    )
+    add_token_args(parser)
+    parser.add_argument(
+        "-q",
+        "--quiet",
+        action="store_true",
+        help="Suppress review output to stdout (only write files)",
+    )
+    parser.add_argument(
+        "-f",
+        "--format",
+        choices=FORMAT_EXTENSIONS,
+        default="text",
+        dest="output_format",
+        help="Output format: text, markdown, html, json (default: text)",
+    )
+    parser.add_argument(
+        "-d",
+        "--diff",
+        action="store_true",
+        help="Always produce .diff and .msg files (automatic for text/json)",
+    )
+    parser.add_argument(
+        "-l",
+        "--list-providers",
+        action="store_true",
+        help="List available providers and exit",
+    )
+    parser.add_argument(
+        "--timeout",
+        type=int,
+        default=120,
+        metavar="SECONDS",
+        help="API request timeout in seconds (default: 120)",
+    )
+
+    # Email options
+    email_group = parser.add_argument_group("Email Options")
+    email_group.add_argument(
+        "--send-email",
+        action="store_true",
+        help="Send review via email",
+    )
+    email_group.add_argument(
+        "--to",
+        action="append",
+        dest="to_addrs",
+        default=[],
+        metavar="ADDRESS",
+        help="Email recipient (can be specified multiple times)",
+    )
+    email_group.add_argument(
+        "--cc",
+        action="append",
+        dest="cc_addrs",
+        default=[],
+        metavar="ADDRESS",
+        help="CC recipient (can be specified multiple times)",
+    )
+    email_group.add_argument(
+        "--from",
+        dest="from_addr",
+        metavar="ADDRESS",
+        help="From address (default: from git config)",
+    )
+    email_group.add_argument(
+        "--dry-run",
+        action="store_true",
+        help="Show email without sending",
+    )
+
+    args = parser.parse_args()
+
+    if args.list_providers:
+        list_providers()
+
+    # Get provider config
+    config = PROVIDERS[args.provider]
+    model = args.model or config["default_model"]
+
+    # Get API key
+    api_key = os.environ.get(config["env_var"])
+    if not api_key:
+        error(f"{config['env_var']} environment variable not set")
+
+    # Validate files
+    agents_path = Path(args.agents)
+    if not agents_path.exists():
+        error(f"AGENTS.md not found: {args.agents}")
+
+    # Validate all doc files exist before processing
+    doc_paths = []
+    for doc_file in args.doc_files:
+        doc_path = Path(doc_file)
+        if not doc_path.exists():
+            error(f"Documentation file not found: {doc_file}")
+        doc_paths.append((doc_file, doc_path))
+
+    # Validate email options
+    if args.send_email and not args.to_addrs:
+        error("--send-email requires at least one --to address")
+
+    # Get from address for email
+    from_addr = args.from_addr
+    if args.send_email and not from_addr:
+        git_name = get_git_config("user.name")
+        git_email = get_git_config("user.email")
+        if git_email:
+            from_addr = f"{git_name} <{git_email}>" if git_name else git_email
+        else:
+            error("No --from specified and git user.email not configured")
+
+    # Read AGENTS.md once
+    # AGENTS.md should always be UTF-8; replace bad bytes rather than crash.
+    agents_content = agents_path.read_text(encoding="utf-8", errors="replace")
+    output_dir = Path(args.output_dir)
+    output_dir.mkdir(parents=True, exist_ok=True)
+    provider_name = config["name"]
+
+    # Accumulate token usage across all API calls
+    total_usage = TokenUsage()
+
+    # Process each file
+    num_files = len(doc_paths)
+    for file_idx, (doc_file, doc_path) in enumerate(doc_paths, 1):
+        if num_files > 1:
+            print(
+                f"\n{'=' * 60}",
+                file=sys.stderr,
+            )
+            print(
+                f"Processing file {file_idx}/{num_files}: {doc_file}",
+                file=sys.stderr,
+            )
+            print(
+                f"{'=' * 60}",
+                file=sys.stderr,
+            )
+
+        # Determine output filenames
+        doc_basename = doc_path.stem
+        diff_file = output_dir / f"{doc_basename}.diff"
+        msg_file = output_dir / f"{doc_basename}.msg"
+
+        # Get commit prefix
+        commit_prefix = get_commit_prefix(doc_file)
+
+        # Read doc content
+        # Docs should be UTF-8 (.rst, .md, etc.); be defensive about bad bytes.
+        doc_content = doc_path.read_text(encoding="utf-8", errors="replace")
+
+        if args.verbose:
+            print("=== Request ===", file=sys.stderr)
+            print(f"Provider: {args.provider}", file=sys.stderr)
+            print(f"Model: {model}", file=sys.stderr)
+            print(f"Output format: {args.output_format}", file=sys.stderr)
+            print(f"AGENTS file: {args.agents}", file=sys.stderr)
+            print(f"Doc file: {doc_file}", file=sys.stderr)
+            print(f"Commit prefix: {commit_prefix}", file=sys.stderr)
+            print(f"Output dir: {args.output_dir}", file=sys.stderr)
+            if args.send_email:
+                print("Send email: yes", file=sys.stderr)
+                print(f"To: {', '.join(args.to_addrs)}", file=sys.stderr)
+                if args.cc_addrs:
+                    print(f"Cc: {', '.join(args.cc_addrs)}", file=sys.stderr)
+                print(f"From: {from_addr}", file=sys.stderr)
+            print("===============", file=sys.stderr)
+
+        # Call API
+        review_text, call_usage = call_api(
+            args.provider,
+            api_key,
+            model,
+            args.tokens,
+            agents_content,
+            doc_content,
+            doc_file,
+            commit_prefix,
+            args.output_format,
+            args.diff,
+            args.verbose,
+            args.timeout,
+        )
+        total_usage.add(call_usage)
+
+        if not review_text:
+            print(
+                f"Warning: No response received for {doc_file}",
+                file=sys.stderr,
+            )
+            continue
+
+        # Determine review output file
+        format_ext = FORMAT_EXTENSIONS[args.output_format]
+        review_file = output_dir / f"{doc_basename}{format_ext}"
+
+        # Determine if we should write diff/msg files
+        write_diff_msg = args.diff or args.output_format in ("text", "json")
+
+        # Extract commit message and diff first (before stripping markers)
+        commit_msg, diff = "", ""
+        if write_diff_msg:
+            if args.output_format == "json":
+                # Will extract from JSON below
+                pass
+            else:
+                # Parse from text format markers
+                commit_msg, diff = parse_review_text(review_text)
+
+        # For non-text formats with --diff, strip the markers from display output
+        display_text = review_text
+        if args.diff and args.output_format in ("markdown", "html"):
+            display_text = strip_diff_markers(review_text)
+
+        # Build formatted output text
+        if args.output_format == "text":
+            output_text = review_text
+        elif args.output_format == "json":
+            # Try to parse JSON response
+            try:
+                review_data = json.loads(review_text)
+            except json.JSONDecodeError:
+                print("Warning: Response is not valid JSON", file=sys.stderr)
+                review_data = {"raw_response": review_text}
+
+            # Extract diff/msg from JSON if present
+            if write_diff_msg:
+                if isinstance(review_data, dict) and "raw_response" not in review_data:
+                    commit_msg = review_data.get("commit_message", "")
+                    diff = review_data.get("diff", "")
+
+            # Add metadata
+            usage_data = {
+                "api_calls": call_usage.api_calls,
+                "input_tokens": call_usage.input_tokens,
+                "output_tokens": call_usage.output_tokens,
+                "total_tokens": call_usage.input_tokens + call_usage.output_tokens,
+            }
+            if call_usage.cache_creation_tokens:
+                usage_data["cache_creation_tokens"] = call_usage.cache_creation_tokens
+            if call_usage.cache_read_tokens:
+                usage_data["cache_read_tokens"] = call_usage.cache_read_tokens
+
+            output_data = {
+                "metadata": {
+                    "doc_file": doc_file,
+                    "provider": args.provider,
+                    "provider_name": provider_name,
+                    "model": model,
+                    "commit_prefix": commit_prefix,
+                    "token_usage": usage_data,
+                },
+                "review": review_data,
+            }
+            output_text = json.dumps(output_data, indent=2)
+        elif args.output_format == "markdown":
+            output_text = f"""# Documentation Review: {doc_path.name}
+
+*Reviewed by {provider_name} ({model})*
+
+{display_text}
+"""
+        elif args.output_format == "html":
+            output_text = f"""<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Review: {doc_path.name}</title>
+<style>
+body {{ font-family: system-ui, sans-serif; max-width: 900px; margin: 2em auto; padding: 0 1em; }}
+h1 {{ color: #333; }}
+.review-meta {{ color: #666; font-style: italic; }}
+pre {{ background: #f5f5f5; padding: 1em; overflow-x: auto; }}
+</style>
+</head>
+<body>
+<h1>Documentation Review: {doc_path.name}</h1>
+<p class="review-meta">Reviewed by {provider_name} ({model})</p>
+<div class="review-content">
+{display_text}
+</div>
+</body>
+</html>
+"""
+
+        # Write formatted review to file
+        review_file.write_text(output_text)
+        print(f"Review written to: {review_file}", file=sys.stderr)
+
+        # Write diff/msg files
+        if write_diff_msg:
+            if commit_msg:
+                msg_file.write_text(commit_msg + "\n")
+                print(f"Commit message written to: {msg_file}", file=sys.stderr)
+            else:
+                msg_file.write_text("# No commit message generated\n")
+                print("Warning: Could not extract commit message", file=sys.stderr)
+
+            if diff:
+                diff_file.write_text(diff + "\n")
+                print(f"Diff written to: {diff_file}", file=sys.stderr)
+            else:
+                diff_file.write_text("# No changes suggested\n")
+                print("Warning: Could not extract diff", file=sys.stderr)
+
+        # Print to stdout unless quiet (or multiple files without verbose)
+        show_stdout = not args.quiet and (num_files == 1 or args.verbose)
+        if show_stdout:
+            print(
+                f"\n=== Documentation Review: {doc_path.name} "
+                f"(via {provider_name}) ==="
+            )
+            print(output_text)
+
+            # Print usage instructions for text format
+            if args.output_format == "text":
+                print("\n=== Output Files ===")
+                print(f"Commit message: {msg_file}")
+                print(f"Diff file:      {diff_file}")
+                print("\nTo apply changes:")
+                print(f"  git apply {diff_file}")
+                print(f"  git commit -sF {msg_file}")
+
+        # Send email if requested
+        if args.send_email:
+            if args.output_format != "text":
+                print(
+                    f"Note: Email will be sent as plain text regardless of "
+                    f"--format={args.output_format}",
+                    file=sys.stderr,
+                )
+
+            review_subject = f"[REVIEW] {commit_prefix} {doc_path.name}"
+
+            # Build email body
+            email_body = f"""AI-generated documentation review of {doc_file}
+Reviewed using {provider_name} ({model})
+
+This is an automated review. Please verify all suggestions.
+
+---
+
+{review_text}
+"""
+
+            if args.verbose:
+                print("", file=sys.stderr)
+                print("=== Email Details ===", file=sys.stderr)
+                print(f"Subject: {review_subject}", file=sys.stderr)
+                print("=====================", file=sys.stderr)
+
+            send_email(
+                args.to_addrs,
+                args.cc_addrs,
+                from_addr,
+                review_subject,
+                None,
+                email_body,
+                args.dry_run,
+                args.verbose,
+            )
+
+            if not args.dry_run:
+                print("", file=sys.stderr)
+                print(f"Review sent to: {', '.join(args.to_addrs)}", file=sys.stderr)
+
+    # Print summary for multiple files
+    if num_files > 1:
+        print(f"\n{'=' * 60}", file=sys.stderr)
+        print(f"Processed {num_files} files", file=sys.stderr)
+        print(f"Output directory: {output_dir}", file=sys.stderr)
+
+    print_token_summary(
+        total_usage,
+        args.provider,
+        model,
+        args.show_tokens or args.verbose,
+    )
+
+
+if __name__ == "__main__":
+    main()
-- 
2.53.0


^ permalink raw reply related

* [PATCH v15 4/7] devtools: add compare-patch-reviews.sh for multi-provider analysis
From: Stephen Hemminger @ 2026-05-21 22:44 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Aaron Conole
In-Reply-To: <20260521224550.927338-1-stephen@networkplumber.org>

Add script to run patch reviews across multiple AI providers for
comparison purposes.

The script automatically detects which providers have API keys
configured and runs analyze-patch.py for each one. This allows
users to compare review quality and feedback across different
AI models.

Features:
  - Auto-detects available providers based on environment variables
  - Optional provider selection via -p/--providers option
  - Saves individual reviews to separate files with -o/--output
  - Verbose mode passes through to underlying analyze-patch.py

Usage:
  ./devtools/ai/compare-patch-reviews.sh my-patch.patch
  ./devtools/ai/compare-patch-reviews.sh -p anthropic,xai my-patch.patch
  ./devtools/ai/compare-patch-reviews.sh -o ./reviews my-patch.patch

Output files are named <patch>-<provider>.txt when using the
output directory option.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Acked-by: Aaron Conole <aconole@redhat.com>
---
 devtools/ai/compare-patch-reviews.sh | 306 +++++++++++++++++++++++++++
 1 file changed, 306 insertions(+)
 create mode 100755 devtools/ai/compare-patch-reviews.sh

diff --git a/devtools/ai/compare-patch-reviews.sh b/devtools/ai/compare-patch-reviews.sh
new file mode 100755
index 0000000000..ba6d3a3eba
--- /dev/null
+++ b/devtools/ai/compare-patch-reviews.sh
@@ -0,0 +1,306 @@
+#!/bin/bash
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026 Stephen Hemminger
+
+# Compare DPDK patch reviews across multiple AI providers
+# Runs analyze-patch.py with each available provider
+
+set -o pipefail
+
+SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
+ANALYZE_SCRIPT="${SCRIPT_DIR}/analyze-patch.py"
+AGENTS_FILE="AGENTS.md"
+OUTPUT_DIR=""
+PROVIDERS=""
+FORMAT="text"
+VERBOSE=""
+EXTRA_ARGS=()
+
+usage() {
+    cat <<EOF
+Usage: $(basename "$0") [OPTIONS] <patch-file>
+
+Compare DPDK patch reviews across multiple AI providers.
+
+Options:
+    -a, --agents FILE      Path to AGENTS.md file (default: AGENTS.md)
+    -o, --output DIR       Save individual reviews to directory
+    -p, --providers LIST   Comma-separated list of providers to use
+                           (default: all providers with API keys set)
+    -f, --format FORMAT    Output format: text, markdown, html, json
+                           (default: text)
+    -t, --tokens N         Max tokens for response
+    -D, --date DATE        Review date context (YYYY-MM-DD)
+    -r, --release VERSION  Target DPDK release (e.g., 24.11, 23.11-lts)
+    --split-patches        Split mbox into individual patches
+    --patch-range N-M      Review only patches N through M
+    --large-file MODE      Handle large files: error, truncate, chunk,
+                           commits-only, summary
+    --max-tokens N         Max input tokens
+    -v, --verbose          Show verbose output from each provider
+    -h, --help             Show this help message
+
+Environment Variables:
+    Set API keys for providers you want to use:
+    ANTHROPIC_API_KEY, OPENAI_API_KEY, XAI_API_KEY, GOOGLE_API_KEY
+
+Examples:
+    $(basename "$0") my-patch.patch
+    $(basename "$0") -p anthropic,openai my-patch.patch
+    $(basename "$0") -o ./reviews -f markdown my-patch.patch
+    $(basename "$0") -r 24.11 --split-patches series.mbox
+
+Exit code from analyze-patch.py is interpreted as:
+    0    clean review
+    2    review found warnings (output preserved)
+    3    review found errors   (output preserved)
+    1/*  operational failure   (output removed, counted as failure)
+EOF
+    exit "${1:-0}"
+}
+
+error() {
+    echo "Error: $1" >&2
+    exit 1
+}
+
+# Check which providers have API keys configured
+get_available_providers() {
+    local available=""
+
+    [[ -n "$ANTHROPIC_API_KEY" ]] && available="${available}anthropic,"
+    [[ -n "$OPENAI_API_KEY" ]] && available="${available}openai,"
+    [[ -n "$XAI_API_KEY" ]] && available="${available}xai,"
+    [[ -n "$GOOGLE_API_KEY" ]] && available="${available}google,"
+
+    # Remove trailing comma
+    echo "${available%,}"
+}
+
+# Get file extension for format
+get_extension() {
+    case "$1" in
+        text)     echo "txt" ;;
+        markdown) echo "md" ;;
+        html)     echo "html" ;;
+        json)     echo "json" ;;
+        *)        echo "txt" ;;
+    esac
+}
+
+# Parse command line options
+while [[ $# -gt 0 ]]; do
+    case "$1" in
+        -a|--agents)
+            [[ -z "${2:-}" || "$2" == -* ]] && error "$1 requires an argument"
+            AGENTS_FILE="$2"
+            shift 2
+            ;;
+        -o|--output)
+            [[ -z "${2:-}" || "$2" == -* ]] && error "$1 requires an argument"
+            OUTPUT_DIR="$2"
+            shift 2
+            ;;
+        -p|--providers)
+            [[ -z "${2:-}" || "$2" == -* ]] && error "$1 requires an argument"
+            PROVIDERS="$2"
+            shift 2
+            ;;
+        -f|--format)
+            [[ -z "${2:-}" || "$2" == -* ]] && error "$1 requires an argument"
+            FORMAT="$2"
+            shift 2
+            ;;
+        -t|--tokens)
+            [[ -z "${2:-}" || "$2" == -* ]] && error "$1 requires an argument"
+            EXTRA_ARGS+=("-t" "$2")
+            shift 2
+            ;;
+        -D|--date)
+            [[ -z "${2:-}" || "$2" == -* ]] && error "$1 requires an argument"
+            EXTRA_ARGS+=("-D" "$2")
+            shift 2
+            ;;
+        -r|--release)
+            [[ -z "${2:-}" || "$2" == -* ]] && error "$1 requires an argument"
+            EXTRA_ARGS+=("-r" "$2")
+            shift 2
+            ;;
+        --split-patches)
+            EXTRA_ARGS+=("--split-patches")
+            shift
+            ;;
+        --patch-range)
+            [[ -z "${2:-}" || "$2" == -* ]] && error "$1 requires an argument"
+            EXTRA_ARGS+=("--patch-range" "$2")
+            shift 2
+            ;;
+        --large-file)
+            [[ -z "${2:-}" || "$2" == -* ]] && error "$1 requires an argument"
+            EXTRA_ARGS+=("--large-file" "$2")
+            shift 2
+            ;;
+        --large-file=*)
+            EXTRA_ARGS+=("$1")
+            shift
+            ;;
+        --max-tokens)
+            [[ -z "${2:-}" || "$2" == -* ]] && error "$1 requires an argument"
+            EXTRA_ARGS+=("--max-tokens" "$2")
+            shift 2
+            ;;
+        -v|--verbose)
+            VERBOSE="-v"
+            shift
+            ;;
+        -h|--help)
+            usage 0
+            ;;
+        -*)
+            error "Unknown option: $1"
+            ;;
+        *)
+            break
+            ;;
+    esac
+done
+
+# Check for required arguments
+if [[ $# -lt 1 ]]; then
+    echo "Error: No patch file specified" >&2
+    usage 1
+fi
+
+PATCH_FILE="$1"
+
+if [[ ! -f "$PATCH_FILE" ]]; then
+    error "Patch file not found: $PATCH_FILE"
+fi
+
+if [[ ! -f "$ANALYZE_SCRIPT" ]]; then
+    error "analyze-patch.py not found: $ANALYZE_SCRIPT"
+fi
+
+if [[ ! -f "$AGENTS_FILE" ]]; then
+    error "AGENTS.md not found: $AGENTS_FILE"
+fi
+
+# Validate format
+case "$FORMAT" in
+    text|markdown|html|json) ;;
+    *) error "Invalid format: $FORMAT (must be text, markdown, html, or json)" ;;
+esac
+
+# Get providers to use
+if [[ -z "$PROVIDERS" ]]; then
+    PROVIDERS=$(get_available_providers)
+fi
+
+if [[ -z "$PROVIDERS" ]]; then
+    error "No API keys configured. Set at least one of: "\
+"ANTHROPIC_API_KEY, OPENAI_API_KEY, XAI_API_KEY, GOOGLE_API_KEY"
+fi
+
+# Create output directory if specified
+if [[ -n "$OUTPUT_DIR" ]]; then
+    mkdir -p "$OUTPUT_DIR"
+fi
+
+PATCH_BASENAME=$(basename "$PATCH_FILE")
+PATCH_STEM="${PATCH_BASENAME%.*}"
+EXT=$(get_extension "$FORMAT")
+
+echo "Reviewing patch: $PATCH_BASENAME"
+echo "Providers: $PROVIDERS"
+echo "Format: $FORMAT"
+echo "========================================"
+echo ""
+
+# Run review for each provider; continue on failure of any one provider.
+#
+# analyze-patch.py exit codes:
+#   0 - clean review (no issues)
+#   1 - operational failure (missing key, file not found, network error, etc.)
+#   2 - review found warnings
+#   3 - review found errors
+# Exit 2 and 3 mean the review SUCCEEDED and produced output the user wants
+# to read. Only 1 (and unexpected codes) indicate a failed run.
+IFS=',' read -ra PROVIDER_LIST <<< "$PROVIDERS"
+failures=0
+warnings_count=0
+errors_count=0
+for provider in "${PROVIDER_LIST[@]}"; do
+    echo ">>> Running review with: $provider"
+    echo ""
+
+    OUTPUT_FILE=""
+    if [[ -n "$OUTPUT_DIR" ]]; then
+        OUTPUT_FILE="${OUTPUT_DIR}/${PATCH_STEM}-${provider}.${EXT}"
+        python3 "$ANALYZE_SCRIPT" \
+            -p "$provider" \
+            -a "$AGENTS_FILE" \
+            -f "$FORMAT" \
+            ${VERBOSE:+"$VERBOSE"} \
+            "${EXTRA_ARGS[@]}" \
+            "$PATCH_FILE" | tee "$OUTPUT_FILE"
+        rc=${PIPESTATUS[0]}
+    else
+        python3 "$ANALYZE_SCRIPT" \
+            -p "$provider" \
+            -a "$AGENTS_FILE" \
+            -f "$FORMAT" \
+            ${VERBOSE:+"$VERBOSE"} \
+            "${EXTRA_ARGS[@]}" \
+            "$PATCH_FILE"
+        rc=$?
+    fi
+
+    case "$rc" in
+        0)
+            if [[ -n "$OUTPUT_FILE" ]]; then
+                echo ""
+                echo "Saved to: $OUTPUT_FILE"
+            fi
+            ;;
+        2)
+            ((warnings_count++)) || true
+            echo "($provider: review reported warnings)" >&2
+            if [[ -n "$OUTPUT_FILE" ]]; then
+                echo ""
+                echo "Saved to: $OUTPUT_FILE"
+            fi
+            ;;
+        3)
+            ((errors_count++)) || true
+            echo "($provider: review reported errors)" >&2
+            if [[ -n "$OUTPUT_FILE" ]]; then
+                echo ""
+                echo "Saved to: $OUTPUT_FILE"
+            fi
+            ;;
+        *)
+            echo "FAILED: $provider review failed (exit $rc)" >&2
+            [[ -n "$OUTPUT_FILE" ]] && rm -f "$OUTPUT_FILE"
+            ((failures++)) || true
+            ;;
+    esac
+
+    echo ""
+    echo "========================================"
+    echo ""
+done
+
+total=${#PROVIDER_LIST[@]}
+clean_count=$((total - warnings_count - errors_count - failures))
+
+echo "Review comparison complete."
+echo "Summary across $total provider(s): clean=$clean_count" \
+     "warnings=$warnings_count errors=$errors_count failed=$failures"
+
+if [[ -n "$OUTPUT_DIR" ]]; then
+    echo "Reviews saved to: $OUTPUT_DIR"
+fi
+
+if [[ $failures -gt 0 ]]; then
+    exit 1
+fi
-- 
2.53.0


^ permalink raw reply related

* [PATCH v15 3/7] devtools: add multi-provider AI patch review script
From: Stephen Hemminger @ 2026-05-21 22:44 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Aaron Conole
In-Reply-To: <20260521224550.927338-1-stephen@networkplumber.org>

This is an AI generated script to review DPDK patches against
the AGENTS.md coding guidelines using AI language models.

Supported AI providers:
  - Anthropic Claude (default)
  - OpenAI ChatGPT
  - xAI Grok
  - Google Gemini

The script reads a patch file and the AGENTS.md guidelines, then
submits them to the selected AI provider for review. Results are
organized by severity level (Error, Warning, Info) as defined in
the guidelines.

Features:
  - Provider selection via -p/--provider option
  - Custom model selection via -m/--model option
  - Verbose mode shows token usage statistics
  - Uses temporary files for API requests to handle large patches
  - Prompt caching support for Anthropic to reduce costs

Usage:
  ./devtools/ai/analyze-patch.py 0001-net-ixgbe-fix-something.patch
  ./devtools/ai/analyze-patch.py -p xai my-patch.patch
  ./devtools/ai/analyze-patch.py -l  # list providers

Requires the appropriate API key environment variable to be set
for the chosen provider (ANTHROPIC_API_KEY, OPENAI_API_KEY,
XAI_API_KEY, or GOOGLE_API_KEY).

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 devtools/ai/analyze-patch.py | 1330 ++++++++++++++++++++++++++++++++++
 1 file changed, 1330 insertions(+)
 create mode 100755 devtools/ai/analyze-patch.py

diff --git a/devtools/ai/analyze-patch.py b/devtools/ai/analyze-patch.py
new file mode 100755
index 0000000000..89e6384f99
--- /dev/null
+++ b/devtools/ai/analyze-patch.py
@@ -0,0 +1,1330 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026 Stephen Hemminger
+
+"""
+Analyze DPDK patches using AI providers.
+
+Supported providers: Anthropic Claude, OpenAI ChatGPT, xAI Grok, Google Gemini
+"""
+
+import argparse
+import json
+import os
+import re
+import subprocess
+import sys
+import tempfile
+from datetime import date
+from email.message import EmailMessage
+from pathlib import Path
+from typing import Any, Iterator
+
+from _common import (
+    PROVIDERS,
+    TokenUsage,
+    add_token_args,
+    error,
+    get_git_config,
+    list_providers,
+    print_token_summary,
+    send_request,
+)
+
+# Output formats
+OUTPUT_FORMATS = ["text", "markdown", "html", "json"]
+
+# Large file handling modes
+LARGE_FILE_MODES = ["error", "truncate", "chunk", "commits-only", "summary"]
+
+# Approximate characters per token (conservative: fewer chars = higher estimate)
+CHARS_PER_TOKEN = 3.5
+
+# Default token limits by provider (leaving room for system prompt and response)
+PROVIDER_INPUT_LIMITS = {
+    "anthropic": 180000,  # 200K context, reserve for system/response
+    "openai": 900000,  # GPT-4.1 has 1M context
+    "xai": 1800000,  # Grok 4.1 Fast has 2M context
+    "google": 900000,  # Gemini 3 Flash has 1M context
+}
+
+# LTS releases: any DPDK release with minor version .11
+# (e.g., 19.11, 20.11, 21.11, 22.11, 23.11, 24.11, 25.11, ...)
+
+SYSTEM_PROMPT_BASE = """\
+You are an expert DPDK code reviewer. Analyze patches for compliance with \
+DPDK coding standards and contribution guidelines. Provide clear, actionable \
+feedback organized by severity (Error, Warning, Info) as defined in the \
+guidelines."""
+
+LTS_RULES = """
+LTS (Long Term Stable) branch rules apply:
+- Only bug fixes allowed, no new features
+- No new APIs (experimental or stable)
+- ABI must remain unchanged
+- Backported fixes should reference the original commit with Fixes: tag
+- Copyright years should reflect when the code was originally written
+- Be conservative: reject changes that aren't clearly bug fixes"""
+
+FORMAT_INSTRUCTIONS = {
+    "text": """Provide your review in plain text format.""",
+    "markdown": """Provide your review in Markdown format with:
+- Headers (##) for each severity level (Errors, Warnings, Info)
+- Bullet points for individual issues
+- Code blocks (```) for code references
+- Bold (**) for emphasis on key points""",
+    "html": """Provide your review in HTML format with:
+- <h2> tags for each severity level (Errors, Warnings, Info)
+- <ul>/<li> for individual issues
+- <pre><code> for code references
+- <strong> for emphasis on key points
+- Use appropriate semantic HTML tags
+- Do NOT include <html>, <head>, or <body> tags - just the content""",
+    "json": """Provide your review in JSON format with this structure:
+{
+  "summary": "Brief one-line summary of the review",
+  "errors": [
+    {"issue": "description", "location": "file:line", "suggestion": "fix"}
+  ],
+  "warnings": [
+    {"issue": "description", "location": "file:line", "suggestion": "fix"}
+  ],
+  "info": [
+    {"issue": "description", "location": "file:line", "suggestion": "fix"}
+  ],
+  "passed_checks": ["list of checks that passed"],
+  "overall_status": "PASS|WARN|FAIL"
+}
+Output ONLY valid JSON, no markdown code fences or other text.""",
+}
+
+USER_PROMPT = """Please review the following DPDK patch file '{patch_name}' \
+against the AGENTS.md guidelines. Focus on:
+
+1. Correctness bugs (resource leaks, use-after-free, race conditions, etc.)
+2. C coding style (forbidden tokens, implicit comparisons, unnecessary patterns)
+3. API and documentation requirements
+4. Any other guideline violations
+
+Note: commit message formatting and SPDX/copyright compliance are checked \
+by checkpatches.sh and should NOT be flagged here.
+
+{format_instruction}
+
+--- PATCH CONTENT ---
+"""
+
+# Exit codes for review results
+EXIT_CLEAN = 0
+EXIT_WARNINGS = 2
+EXIT_ERRORS = 3
+
+
+def classify_review(review_text: str, output_format: str) -> int:
+    """Classify review result and return appropriate exit code.
+
+    Returns:
+        0 - clean (no errors or warnings)
+        2 - warnings found (no errors)
+        3 - errors found
+    """
+    has_errors = False
+    has_warnings = False
+
+    if output_format == "json":
+        try:
+            data = json.loads(review_text)
+            if data.get("errors"):
+                has_errors = True
+            if data.get("warnings"):
+                has_warnings = True
+            status = data.get("overall_status", "").upper()
+            if status == "FAIL":
+                has_errors = True
+            elif status == "WARN":
+                has_warnings = True
+        except (json.JSONDecodeError, AttributeError):
+            pass  # Fall through to text scanning
+
+    if not has_errors and not has_warnings:
+        # Scan review text for severity indicators.
+        # Match section headers and inline markers across text/markdown/html.
+        for line in review_text.splitlines():
+            stripped = line.strip().lower()
+            # Skip quoted patch context lines
+            if stripped.startswith(">") or stripped.startswith("diff --git"):
+                continue
+            if re.match(r"^(#{1,3}\s+)?(\*{0,2})error", stripped) or re.match(
+                r"^<h[1-3]>\s*error", stripped
+            ):
+                has_errors = True
+            elif re.match(r"^(#{1,3}\s+)?(\*{0,2})warning", stripped) or re.match(
+                r"^<h[1-3]>\s*warning", stripped
+            ):
+                has_warnings = True
+
+    if has_errors:
+        return EXIT_ERRORS
+    if has_warnings:
+        return EXIT_WARNINGS
+    return EXIT_CLEAN
+
+
+def is_lts_release(release: str | None) -> bool:
+    """Check if a release is an LTS release.
+
+    Per DPDK project guidelines, any release with minor version .11
+    is an LTS release (e.g., 19.11, 21.11, 23.11, 24.11, 25.11).
+    """
+    if not release:
+        return False
+    # Check for explicit -lts suffix
+    if "-lts" in release.lower():
+        return True
+    # Extract base version (e.g., "23.11" from "23.11.1" or "23.11-rc1")
+    version = release.split("-")[0]
+    parts = version.split(".")
+    if len(parts) >= 2:
+        try:
+            minor = int(parts[1])
+            return minor == 11
+        except ValueError:
+            pass
+    return False
+
+
+def estimate_tokens(text: str) -> int:
+    """Estimate token count from text length."""
+    return int(len(text) / CHARS_PER_TOKEN)
+
+
+def split_mbox_patches(content: str) -> list[str]:
+    """Split an mbox file into individual patches."""
+    patches = []
+    current_patch = []
+    in_patch = False
+
+    for line in content.split("\n"):
+        # Detect start of new message in mbox format
+        # git-format-patch: "From <40-char-hex> Mon Sep 17 00:00:00 2001"
+        # general mbox: "From <addr> <day-of-week> ..."
+        if line.startswith("From ") and (
+            re.match(r"^From [0-9a-f]{40} ", line)
+            or " Mon " in line
+            or " Tue " in line
+            or " Wed " in line
+            or " Thu " in line
+            or " Fri " in line
+            or " Sat " in line
+            or " Sun " in line
+        ):
+            if current_patch:
+                patches.append("\n".join(current_patch))
+            current_patch = [line]
+            in_patch = True
+        elif in_patch:
+            current_patch.append(line)
+
+    # Don't forget the last patch
+    if current_patch:
+        patches.append("\n".join(current_patch))
+
+    return patches if patches else [content]
+
+
+def extract_commit_messages(content: str) -> str:
+    """Extract only commit messages from patch content."""
+    patches = split_mbox_patches(content)
+    messages = []
+
+    for patch in patches:
+        lines = patch.split("\n")
+        msg_lines = []
+        in_headers = True
+        in_body = False
+        found_subject = False
+
+        for line in lines:
+            # Collect headers we care about
+            if in_headers:
+                if line.startswith("Subject:"):
+                    msg_lines.append(line)
+                    found_subject = True
+                elif line.startswith(("From:", "Date:")):
+                    msg_lines.append(line)
+                elif line.startswith((" ", "\t")) and found_subject:
+                    # Subject continuation
+                    msg_lines.append(line)
+                elif line == "":
+                    if found_subject:
+                        in_headers = False
+                        in_body = True
+                        msg_lines.append("")
+            elif in_body:
+                # Stop at the diffstat separator or diff
+                if line.rstrip() == "---":
+                    break
+                if line.startswith("diff --git"):
+                    break
+                msg_lines.append(line)
+
+        if msg_lines:
+            messages.append("\n".join(msg_lines))
+
+    return "\n\n---\n\n".join(messages)
+
+
+def truncate_content(content: str, max_tokens: float) -> tuple[str, bool]:
+    """Truncate content to fit within token limit."""
+    max_chars = int(max_tokens * CHARS_PER_TOKEN)
+
+    if len(content) <= max_chars:
+        return content, False
+
+    # Try to truncate at a reasonable boundary
+    truncated = content[:max_chars]
+
+    # Find last complete diff hunk or patch boundary
+    last_diff = truncated.rfind("\ndiff --git")
+    last_patch = truncated.rfind("\nFrom ")
+
+    if last_diff > max_chars * 0.5:
+        truncated = truncated[:last_diff]
+    elif last_patch > max_chars * 0.5:
+        truncated = truncated[:last_patch]
+
+    truncated += "\n\n[... Content truncated due to size limits ...]\n"
+    return truncated, True
+
+
+def chunk_content(content: str, max_tokens: int) -> Iterator[tuple[str, int, int]]:
+    """Split content into chunks that fit within token limit.
+
+    Yields tuples of (chunk_content, chunk_number, total_chunks).
+    """
+    patches = split_mbox_patches(content)
+
+    if len(patches) == 1:
+        # Single large patch - split by diff sections
+        yield from chunk_single_patch(content, max_tokens)
+        return
+
+    # Multiple patches - group them to fit within limits
+    chunks = []
+    current_chunk = []
+    current_size = 0
+    max_chars = int(max_tokens * CHARS_PER_TOKEN * 0.9)  # 90% to leave margin
+
+    for patch in patches:
+        patch_size = len(patch)
+        if current_size + patch_size > max_chars and current_chunk:
+            chunks.append("\n".join(current_chunk))
+            current_chunk = []
+            current_size = 0
+
+        if patch_size > max_chars:
+            # Single patch too large, truncate it
+            if current_chunk:
+                chunks.append("\n".join(current_chunk))
+                current_chunk = []
+                current_size = 0
+            truncated, _ = truncate_content(patch, max_tokens * 0.9)
+            chunks.append(truncated)
+        else:
+            current_chunk.append(patch)
+            current_size += patch_size
+
+    if current_chunk:
+        chunks.append("\n".join(current_chunk))
+
+    total = len(chunks)
+    for i, chunk in enumerate(chunks, 1):
+        yield chunk, i, total
+
+
+def chunk_single_patch(content: str, max_tokens: int) -> Iterator[tuple[str, int, int]]:
+    """Split a single large patch by diff sections."""
+    max_chars = int(max_tokens * CHARS_PER_TOKEN * 0.9)
+
+    # Extract header (everything before first diff)
+    first_diff = content.find("\ndiff --git")
+    if first_diff == -1:
+        # No diff sections, just truncate
+        truncated, _ = truncate_content(content, max_tokens * 0.9)
+        yield truncated, 1, 1
+        return
+
+    header = content[: first_diff + 1]
+    diff_content = content[first_diff + 1 :]
+
+    # Split by diff sections
+    diffs = []
+    current_diff = []
+    for line in diff_content.split("\n"):
+        if line.startswith("diff --git") and current_diff:
+            diffs.append("\n".join(current_diff))
+            current_diff = []
+        current_diff.append(line)
+    if current_diff:
+        diffs.append("\n".join(current_diff))
+
+    # Group diffs into chunks
+    chunks = []
+    current_chunk_diffs = []
+    current_size = len(header)
+
+    for diff in diffs:
+        diff_size = len(diff)
+        if current_size + diff_size > max_chars and current_chunk_diffs:
+            chunks.append(header + "\n".join(current_chunk_diffs))
+            current_chunk_diffs = []
+            current_size = len(header)
+
+        if diff_size + len(header) > max_chars:
+            # Single diff too large
+            if current_chunk_diffs:
+                chunks.append(header + "\n".join(current_chunk_diffs))
+                current_chunk_diffs = []
+            truncated_diff = diff[: max_chars - len(header) - 100]
+            truncated_diff += "\n[... diff truncated ...]\n"
+            chunks.append(header + truncated_diff)
+            current_size = len(header)
+        else:
+            current_chunk_diffs.append(diff)
+            current_size += diff_size
+
+    if current_chunk_diffs:
+        chunks.append(header + "\n".join(current_chunk_diffs))
+
+    total = len(chunks)
+    for i, chunk in enumerate(chunks, 1):
+        yield chunk, i, total
+
+
+def get_summary_prompt() -> str:
+    """Get prompt modifications for summary mode."""
+    return """
+NOTE: This is a LARGE patch series. Provide a HIGH-LEVEL summary review only:
+- Focus on overall architecture and design concerns
+- Check commit message formatting across the series
+- Identify any obvious policy violations
+- Do NOT attempt detailed line-by-line code review
+- Summarize the scope and purpose of the changes
+"""
+
+
+def format_combined_reviews(
+    reviews: list[tuple[str, str]], output_format: str, patch_name: str
+) -> str:
+    """Combine multiple chunk/patch reviews into a single output."""
+    if output_format == "json":
+        combined = {
+            "patch_file": patch_name,
+            "sections": [
+                {"label": label, "review": review} for label, review in reviews
+            ],
+        }
+        return json.dumps(combined, indent=2)
+    elif output_format == "html":
+        sections = []
+        for label, review in reviews:
+            sections.append(f"<h2>{label}</h2>\n{review}")
+        return "\n<hr>\n".join(sections)
+    elif output_format == "markdown":
+        sections = []
+        for label, review in reviews:
+            sections.append(f"## {label}\n\n{review}")
+        return "\n\n---\n\n".join(sections)
+    else:  # text
+        sections = []
+        for label, review in reviews:
+            sections.append(f"=== {label} ===\n\n{review}")
+        separator = "\n\n" + "=" * 60 + "\n\n"
+        return separator.join(sections)
+
+
+def build_system_prompt(review_date: str, release: str | None) -> str:
+    """Build system prompt with date and release context."""
+    prompt = SYSTEM_PROMPT_BASE
+    prompt += f"\n\nCurrent date: {review_date}."
+
+    if release:
+        prompt += f"\nTarget DPDK release: {release}."
+        if is_lts_release(release):
+            prompt += LTS_RULES
+        else:
+            prompt += "\nThis is a main branch or standard release."
+            prompt += "\nNew features and experimental APIs are allowed."
+
+    return prompt
+
+
+def build_anthropic_request(
+    model: str,
+    max_tokens: int,
+    system_prompt: str,
+    agents_content: str,
+    patch_content: str,
+    patch_name: str,
+    output_format: str = "text",
+) -> dict[str, Any]:
+    """Build request payload for Anthropic API."""
+    format_instruction = FORMAT_INSTRUCTIONS.get(output_format, "")
+    user_prompt = USER_PROMPT.format(
+        patch_name=patch_name, format_instruction=format_instruction
+    )
+    return {
+        "model": model,
+        "max_tokens": max_tokens,
+        "system": [
+            {"type": "text", "text": system_prompt},
+            {
+                "type": "text",
+                "text": agents_content,
+                "cache_control": {"type": "ephemeral"},
+            },
+        ],
+        "messages": [
+            {
+                "role": "user",
+                "content": user_prompt + patch_content,
+            }
+        ],
+    }
+
+
+def build_openai_request(
+    model: str,
+    max_tokens: int,
+    system_prompt: str,
+    agents_content: str,
+    patch_content: str,
+    patch_name: str,
+    output_format: str = "text",
+) -> dict[str, Any]:
+    """Build request payload for OpenAI-compatible APIs."""
+    format_instruction = FORMAT_INSTRUCTIONS.get(output_format, "")
+    user_prompt = USER_PROMPT.format(
+        patch_name=patch_name, format_instruction=format_instruction
+    )
+    return {
+        "model": model,
+        "max_tokens": max_tokens,
+        "messages": [
+            {"role": "system", "content": system_prompt},
+            {"role": "system", "content": agents_content},
+            {
+                "role": "user",
+                "content": user_prompt + patch_content,
+            },
+        ],
+    }
+
+
+def build_google_request(
+    max_tokens: int,
+    system_prompt: str,
+    agents_content: str,
+    patch_content: str,
+    patch_name: str,
+    output_format: str = "text",
+) -> dict[str, Any]:
+    """Build request payload for Google Gemini API."""
+    format_instruction = FORMAT_INSTRUCTIONS.get(output_format, "")
+    user_prompt = USER_PROMPT.format(
+        patch_name=patch_name, format_instruction=format_instruction
+    )
+    return {
+        "systemInstruction": {
+            "parts": [
+                {"text": system_prompt},
+                {"text": agents_content},
+            ]
+        },
+        "contents": [
+            {
+                "role": "user",
+                "parts": [{"text": user_prompt + patch_content}],
+            },
+        ],
+        "generationConfig": {"maxOutputTokens": max_tokens},
+    }
+
+
+def call_api(
+    provider: str,
+    api_key: str,
+    model: str,
+    max_tokens: int,
+    system_prompt: str,
+    agents_content: str,
+    patch_content: str,
+    patch_name: str,
+    output_format: str = "text",
+    verbose: bool = False,
+    timeout: int = 300,
+) -> tuple[str, TokenUsage]:
+    """Build the per-provider request body and dispatch via _common."""
+    if provider == "anthropic":
+        request_data = build_anthropic_request(
+            model,
+            max_tokens,
+            system_prompt,
+            agents_content,
+            patch_content,
+            patch_name,
+            output_format,
+        )
+    elif provider == "google":
+        request_data = build_google_request(
+            max_tokens,
+            system_prompt,
+            agents_content,
+            patch_content,
+            patch_name,
+            output_format,
+        )
+    else:  # openai, xai
+        request_data = build_openai_request(
+            model,
+            max_tokens,
+            system_prompt,
+            agents_content,
+            patch_content,
+            patch_name,
+            output_format,
+        )
+    return send_request(
+        provider,
+        api_key,
+        model,
+        request_data,
+        timeout=timeout,
+        verbose=verbose,
+    )
+
+
+def get_last_message_id(patch_content: str) -> str | None:
+    """Extract Message-ID from the last patch in an mbox."""
+    msg_ids = re.findall(
+        r"^Message-I[Dd]:\s*(.+)$", patch_content, re.MULTILINE | re.IGNORECASE
+    )
+    if msg_ids:
+        msg_id = msg_ids[-1].strip()
+        # Normalize: remove < > and add them back
+        msg_id = msg_id.strip("<>")
+        return f"<{msg_id}>"
+    return None
+
+
+def get_last_subject(patch_content: str) -> str | None:
+    """Extract subject from the last patch in an mbox."""
+    # Find all Subject lines with potential continuations
+    subjects = []
+    lines = patch_content.split("\n")
+    i = 0
+    while i < len(lines):
+        if lines[i].lower().startswith("subject:"):
+            subject = lines[i][8:].strip()
+            i += 1
+            # Handle continuation lines (RFC 2822 folding)
+            while i < len(lines) and lines[i].startswith((" ", "\t")):
+                subject += " " + lines[i].strip()
+                i += 1
+            subjects.append(subject)
+        else:
+            i += 1
+    return subjects[-1] if subjects else None
+
+
+def send_email(
+    to_addrs: list[str],
+    cc_addrs: list[str],
+    from_addr: str,
+    subject: str,
+    in_reply_to: str | None,
+    body: str,
+    dry_run: bool = False,
+) -> bool:
+    """Send review email using git send-email, sendmail, or msmtp.
+
+    TODO: This duplicates send_email in review-doc.py, which uses direct
+    smtplib with git sendemail.* config instead of shelling out. Both
+    approaches work; pick one and move it to _common.py.
+    """
+    msg = EmailMessage()
+    msg["From"] = from_addr
+    msg["To"] = ", ".join(to_addrs)
+    if cc_addrs:
+        msg["Cc"] = ", ".join(cc_addrs)
+    msg["Subject"] = subject
+    if in_reply_to:
+        msg["In-Reply-To"] = in_reply_to
+        msg["References"] = in_reply_to
+    msg.set_content(body)
+
+    email_text = msg.as_string()
+
+    if dry_run:
+        print("=== Email Preview (dry-run) ===", file=sys.stderr)
+        print(email_text, file=sys.stderr)
+        print("=== End Preview ===", file=sys.stderr)
+        return True
+
+    # Write to temp file for git send-email
+    with tempfile.NamedTemporaryFile(mode="w", suffix=".eml", delete=False) as f:
+        f.write(email_text)
+        temp_file = f.name
+
+    try:
+        # Try git send-email first
+        if get_git_config("sendemail.smtpserver"):
+            # Build command with all arguments
+            flat_cmd = ["git", "send-email", "--confirm=never", "--quiet"]
+            for addr in to_addrs:
+                flat_cmd.extend(["--to", addr])
+            for addr in cc_addrs:
+                flat_cmd.extend(["--cc", addr])
+            if from_addr:
+                flat_cmd.extend(["--from", from_addr])
+            if in_reply_to:
+                flat_cmd.extend(["--in-reply-to", in_reply_to])
+            flat_cmd.append(temp_file)
+
+            try:
+                subprocess.run(flat_cmd, check=True, capture_output=True)
+                print("Email sent via git send-email", file=sys.stderr)
+                return True
+            except (subprocess.CalledProcessError, FileNotFoundError):
+                pass
+
+        # Try sendmail
+        try:
+            subprocess.run(
+                ["sendmail", "-t"],
+                input=email_text,
+                text=True,
+                capture_output=True,
+                check=True,
+            )
+            print("Email sent via sendmail", file=sys.stderr)
+            return True
+        except (subprocess.CalledProcessError, FileNotFoundError):
+            pass
+
+        # Try msmtp
+        try:
+            subprocess.run(
+                ["msmtp", "-t"],
+                input=email_text,
+                text=True,
+                capture_output=True,
+                check=True,
+            )
+            print("Email sent via msmtp", file=sys.stderr)
+            return True
+        except (subprocess.CalledProcessError, FileNotFoundError):
+            pass
+
+        error("Could not send email. Configure git send-email, sendmail, or msmtp.")
+
+    finally:
+        os.unlink(temp_file)
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser(
+        description="Analyze DPDK patches using AI providers",
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        epilog="""
+Examples:
+    %(prog)s patch.patch                    # Review with default settings
+    %(prog)s -p openai my-patch.patch       # Use OpenAI ChatGPT
+    %(prog)s -f markdown patch.patch        # Output as Markdown
+    %(prog)s -f json -o review.json patch.patch  # Save JSON to file
+    %(prog)s -f html -o review.html patch.patch  # Save HTML to file
+    %(prog)s -r 24.11 patch.patch           # Review for specific release
+    %(prog)s -r 24.11-lts patch.patch       # Review for LTS branch
+    %(prog)s --send-email --to dev@dpdk.org series.mbox
+    %(prog)s --send-email --to dev@dpdk.org --dry-run series.mbox
+
+Large File Handling:
+    %(prog)s --split-patches series.mbox    # Review each patch separately
+    %(prog)s --split-patches --patch-range 1-5 series.mbox  # Review patches 1-5
+    %(prog)s --large-file=truncate patch.mbox   # Truncate to fit limit
+    %(prog)s --large-file=commits-only series.mbox  # Review commit messages only
+    %(prog)s --large-file=summary series.mbox   # High-level summary only
+    %(prog)s --large-file=chunk series.mbox     # Split and review in chunks
+
+Large File Modes:
+    error       - Fail with error (default)
+    truncate    - Truncate content to fit token limit
+    chunk       - Split into chunks and review each
+    commits-only - Extract and review only commit messages
+    summary     - Request high-level summary review
+
+LTS Releases:
+    Use -r/--release with LTS version (e.g., 24.11-lts, 23.11) to enable
+    stricter review rules: bug fixes only, no new features or APIs.
+    Any DPDK release with minor version .11 is an LTS release.
+
+Token Usage:
+    Use --show-tokens (or -v/--verbose) to print a token usage summary
+    on stderr after the run. Off by default.
+
+Exit Codes:
+    0 - Clean review (no errors or warnings)
+    1 - Operational failure (missing API key, file not found, etc.)
+    2 - Review found warnings (no errors)
+    3 - Review found errors
+        """,
+    )
+
+    parser.add_argument("patch_file", nargs="?", help="Patch file to analyze")
+    parser.add_argument(
+        "-p",
+        "--provider",
+        choices=PROVIDERS.keys(),
+        default="anthropic",
+        help="AI provider (default: anthropic)",
+    )
+    parser.add_argument(
+        "-a",
+        "--agents",
+        default="AGENTS.md",
+        help="Path to AGENTS.md file (default: AGENTS.md)",
+    )
+    parser.add_argument(
+        "-m",
+        "--model",
+        help="Model to use (default: provider-specific)",
+    )
+    parser.add_argument(
+        "-t",
+        "--tokens",
+        type=int,
+        default=4096,
+        help="Max tokens for response (default: 4096)",
+    )
+    parser.add_argument(
+        "-v",
+        "--verbose",
+        action="store_true",
+        help="Show API request details",
+    )
+    add_token_args(parser)
+    parser.add_argument(
+        "-f",
+        "--format",
+        choices=OUTPUT_FORMATS,
+        default="text",
+        dest="output_format",
+        help="Output format: text, markdown, html, json (default: text)",
+    )
+    parser.add_argument(
+        "-o",
+        "--output",
+        metavar="FILE",
+        help="Write output to file instead of stdout",
+    )
+    parser.add_argument(
+        "-l",
+        "--list-providers",
+        action="store_true",
+        help="List available providers and exit",
+    )
+    parser.add_argument(
+        "--timeout",
+        type=int,
+        default=300,
+        metavar="SECONDS",
+        help="API request timeout in seconds (default: 300)",
+    )
+
+    # Date and release options
+    parser.add_argument(
+        "-D",
+        "--date",
+        metavar="YYYY-MM-DD",
+        help="Review date context (default: today)",
+    )
+    parser.add_argument(
+        "-r",
+        "--release",
+        metavar="VERSION",
+        help="Target DPDK release (e.g., 24.11, 23.11-lts)",
+    )
+
+    # Large file handling options
+    large_group = parser.add_argument_group("Large File Handling")
+    large_group.add_argument(
+        "--large-file",
+        choices=LARGE_FILE_MODES,
+        default="error",
+        metavar="MODE",
+        help="How to handle large files: error (default), truncate, "
+        "chunk, commits-only, summary",
+    )
+    large_group.add_argument(
+        "--max-tokens",
+        type=int,
+        metavar="N",
+        help="Max input tokens (default: provider-specific)",
+    )
+    large_group.add_argument(
+        "--split-patches",
+        action="store_true",
+        help="Split mbox into individual patches and review each separately",
+    )
+    large_group.add_argument(
+        "--patch-range",
+        metavar="N-M",
+        help="Review only patches N through M (1-indexed, use with --split-patches)",
+    )
+
+    # Email options
+    email_group = parser.add_argument_group("Email Options")
+    email_group.add_argument(
+        "--send-email",
+        action="store_true",
+        help="Send review via email",
+    )
+    email_group.add_argument(
+        "--to",
+        action="append",
+        dest="to_addrs",
+        default=[],
+        metavar="ADDRESS",
+        help="Email recipient (can be specified multiple times)",
+    )
+    email_group.add_argument(
+        "--cc",
+        action="append",
+        dest="cc_addrs",
+        default=[],
+        metavar="ADDRESS",
+        help="CC recipient (can be specified multiple times)",
+    )
+    email_group.add_argument(
+        "--from",
+        dest="from_addr",
+        metavar="ADDRESS",
+        help="From address (default: from git config)",
+    )
+    email_group.add_argument(
+        "--dry-run",
+        action="store_true",
+        help="Show email without sending",
+    )
+
+    args = parser.parse_args()
+
+    if args.list_providers:
+        list_providers()
+
+    # Check patch file is provided
+    if not args.patch_file:
+        parser.error("patch_file is required")
+
+    # Get provider config
+    config = PROVIDERS[args.provider]
+    model = args.model or config["default_model"]
+
+    # Get API key
+    api_key = os.environ.get(config["env_var"])
+    if not api_key:
+        error(f"{config['env_var']} environment variable not set")
+
+    # Validate files
+    agents_path = Path(args.agents)
+    if not agents_path.exists():
+        error(f"AGENTS.md not found: {args.agents}")
+
+    patch_path = Path(args.patch_file)
+    if not patch_path.exists():
+        error(f"Patch file not found: {args.patch_file}")
+
+    # Validate email options
+    if args.send_email and not args.to_addrs:
+        error("--send-email requires at least one --to address")
+
+    # Get from address for email
+    from_addr = args.from_addr
+    if args.send_email and not from_addr:
+        git_name = get_git_config("user.name")
+        git_email = get_git_config("user.email")
+        if git_email:
+            from_addr = f"{git_name} <{git_email}>" if git_name else git_email
+        else:
+            error("No --from specified and git user.email not configured")
+
+    # Determine review date
+    review_date = args.date or date.today().isoformat()
+
+    # Build system prompt with date and release context
+    system_prompt = build_system_prompt(review_date, args.release)
+
+    # Read files
+    # Files may have non-UTF-8 bytes (binary patches, unusual filenames); replace
+    # rather than crashing so the review can still run.
+    agents_content = agents_path.read_text(encoding="utf-8", errors="replace")
+    patch_content = patch_path.read_text(encoding="utf-8", errors="replace")
+    patch_name = patch_path.name
+
+    # Determine max tokens for this provider
+    max_input_tokens = args.max_tokens or PROVIDER_INPUT_LIMITS.get(
+        args.provider, 100000
+    )
+
+    # Estimate token count
+    estimated_tokens = estimate_tokens(patch_content + agents_content)
+
+    # Accumulate token usage across all API calls
+    total_usage = TokenUsage()
+
+    # Parse patch range if specified
+    patch_start, patch_end = None, None
+    if args.patch_range:
+        try:
+            if "-" in args.patch_range:
+                start, end = args.patch_range.split("-", 1)
+                patch_start = int(start)
+                patch_end = int(end)
+            else:
+                patch_start = patch_end = int(args.patch_range)
+        except ValueError:
+            error(f"Invalid --patch-range format: {args.patch_range}")
+        if not args.split_patches:
+            print(
+                "Warning: --patch-range has no effect without --split-patches",
+                file=sys.stderr,
+            )
+
+    # Handle --split-patches mode
+    review_text = ""
+    if args.split_patches:
+        patches = split_mbox_patches(patch_content)
+        total_patches = len(patches)
+
+        if total_patches == 1:
+            print(
+                "Note: Only 1 patch found in mbox, --split-patches has no effect",
+                file=sys.stderr,
+            )
+        else:
+            print(
+                f"Found {total_patches} patches in mbox",
+                file=sys.stderr,
+            )
+
+            # Apply patch range filter
+            if patch_start is not None:
+                if patch_start < 1 or patch_start > total_patches:
+                    error(
+                        f"Patch range start {patch_start} out of range (1-{total_patches})"
+                    )
+                if patch_end < patch_start or patch_end > total_patches:
+                    error(
+                        f"Patch range end {patch_end} out of range ({patch_start}-{total_patches})"
+                    )
+                patches = patches[patch_start - 1 : patch_end]
+                print(
+                    f"Reviewing patches {patch_start}-{patch_end} ({len(patches)} patches)",
+                    file=sys.stderr,
+                )
+
+            # Review each patch separately
+            all_reviews = []
+            for i, patch in enumerate(patches, patch_start or 1):
+                patch_label = f"Patch {i}/{total_patches}"
+                print(f"\nReviewing {patch_label}...", file=sys.stderr)
+
+                review_text, call_usage = call_api(
+                    args.provider,
+                    api_key,
+                    model,
+                    args.tokens,
+                    system_prompt,
+                    agents_content,
+                    patch,
+                    f"{patch_name} ({patch_label})",
+                    args.output_format,
+                    args.verbose,
+                    args.timeout,
+                )
+                total_usage.add(call_usage)
+                all_reviews.append((patch_label, review_text))
+
+            # Combine reviews
+            review_text = format_combined_reviews(
+                all_reviews, args.output_format, patch_name
+            )
+
+            # Skip the normal API call
+            estimated_tokens = 0  # Bypass size check since we've already processed
+
+    # Check if content is too large
+    is_large = estimated_tokens > max_input_tokens
+
+    if is_large:
+        print(
+            f"Warning: Estimated {estimated_tokens:,} tokens exceeds limit of "
+            f"{max_input_tokens:,}",
+            file=sys.stderr,
+        )
+
+        if args.large_file == "error":
+            error(
+                f"Patch file too large ({estimated_tokens:,} tokens). "
+                f"Use --large-file=truncate|chunk|commits-only|summary to handle, "
+                f"or --split-patches to review patches individually."
+            )
+        elif args.large_file == "truncate":
+            print("Truncating content to fit token limit...", file=sys.stderr)
+            patch_content, was_truncated = truncate_content(
+                patch_content, max_input_tokens
+            )
+            if was_truncated:
+                print("Content was truncated.", file=sys.stderr)
+        elif args.large_file == "commits-only":
+            print("Extracting commit messages only...", file=sys.stderr)
+            patch_content = extract_commit_messages(patch_content)
+            new_estimate = estimate_tokens(patch_content + agents_content)
+            print(
+                f"Reduced to ~{new_estimate:,} tokens (commit messages only)",
+                file=sys.stderr,
+            )
+            if new_estimate > max_input_tokens:
+                patch_content, _ = truncate_content(patch_content, max_input_tokens)
+        elif args.large_file == "summary":
+            print("Using summary mode for large patch...", file=sys.stderr)
+            system_prompt += get_summary_prompt()
+            patch_content, _ = truncate_content(patch_content, max_input_tokens)
+        elif args.large_file == "chunk":
+            print("Processing in chunks...", file=sys.stderr)
+            all_reviews = []
+            for chunk, chunk_num, total_chunks in chunk_content(
+                patch_content, max_input_tokens
+            ):
+                chunk_label = f"Chunk {chunk_num}/{total_chunks}"
+                print(f"Reviewing {chunk_label}...", file=sys.stderr)
+
+                review_text, call_usage = call_api(
+                    args.provider,
+                    api_key,
+                    model,
+                    args.tokens,
+                    system_prompt,
+                    agents_content,
+                    chunk,
+                    f"{patch_name} ({chunk_label})",
+                    args.output_format,
+                    args.verbose,
+                    args.timeout,
+                )
+                total_usage.add(call_usage)
+                all_reviews.append((chunk_label, review_text))
+
+            # Combine chunk reviews
+            review_text = format_combined_reviews(
+                all_reviews, args.output_format, patch_name
+            )
+
+            # Skip the normal single API call below
+            estimated_tokens = 0
+
+    if args.verbose:
+        print("=== Request ===", file=sys.stderr)
+        print(f"Provider: {args.provider}", file=sys.stderr)
+        print(f"Model: {model}", file=sys.stderr)
+        print(f"Review date: {review_date}", file=sys.stderr)
+        if args.release:
+            lts_status = " (LTS)" if is_lts_release(args.release) else ""
+            print(f"Target release: {args.release}{lts_status}", file=sys.stderr)
+        print(f"Output format: {args.output_format}", file=sys.stderr)
+        print(f"AGENTS file: {args.agents}", file=sys.stderr)
+        print(f"Patch file: {args.patch_file}", file=sys.stderr)
+        print(f"Estimated tokens: {estimated_tokens:,}", file=sys.stderr)
+        print(f"Max input tokens: {max_input_tokens:,}", file=sys.stderr)
+        if args.large_file != "error":
+            print(f"Large file mode: {args.large_file}", file=sys.stderr)
+        if args.split_patches:
+            print("Split patches: yes", file=sys.stderr)
+        if args.output:
+            print(f"Output file: {args.output}", file=sys.stderr)
+        if args.send_email:
+            print("Send email: yes", file=sys.stderr)
+            print(f"To: {', '.join(args.to_addrs)}", file=sys.stderr)
+            if args.cc_addrs:
+                print(f"Cc: {', '.join(args.cc_addrs)}", file=sys.stderr)
+            print(f"From: {from_addr}", file=sys.stderr)
+        print("===============", file=sys.stderr)
+
+    # Call API (unless already processed via chunks/split)
+    if estimated_tokens > 0:  # Not already processed
+        review_text, call_usage = call_api(
+            args.provider,
+            api_key,
+            model,
+            args.tokens,
+            system_prompt,
+            agents_content,
+            patch_content,
+            patch_name,
+            args.output_format,
+            args.verbose,
+            args.timeout,
+        )
+        total_usage.add(call_usage)
+
+    if not review_text:
+        error(f"No response received from {args.provider}")
+
+    # Format output based on requested format
+    provider_name = config["name"]
+
+    if args.output_format == "json":
+        # For JSON, try to parse and add metadata
+        try:
+            review_data = json.loads(review_text)
+        except json.JSONDecodeError:
+            # If AI didn't return valid JSON, wrap the text
+            review_data = {"raw_review": review_text}
+
+        usage_data = {
+            "api_calls": total_usage.api_calls,
+            "input_tokens": total_usage.input_tokens,
+            "output_tokens": total_usage.output_tokens,
+            "total_tokens": total_usage.input_tokens + total_usage.output_tokens,
+        }
+        if total_usage.cache_creation_tokens:
+            usage_data["cache_creation_tokens"] = total_usage.cache_creation_tokens
+        if total_usage.cache_read_tokens:
+            usage_data["cache_read_tokens"] = total_usage.cache_read_tokens
+
+        output_data = {
+            "metadata": {
+                "patch_file": patch_name,
+                "provider": args.provider,
+                "provider_name": provider_name,
+                "model": model,
+                "review_date": review_date,
+                "target_release": args.release,
+                "is_lts": is_lts_release(args.release) if args.release else False,
+                "token_usage": usage_data,
+            },
+            "review": review_data,
+        }
+        output_text = json.dumps(output_data, indent=2)
+    elif args.output_format == "html":
+        # Wrap HTML content with header
+        release_info = ""
+        if args.release:
+            lts_badge = " (LTS)" if is_lts_release(args.release) else ""
+            release_info = f"<br>Target release: {args.release}{lts_badge}"
+        output_text = f"""<!-- AI-generated review of {patch_name} -->
+<!-- Reviewed using {provider_name} ({model}) on {review_date} -->
+<div class="patch-review">
+<h1>Patch Review: {patch_name}</h1>
+<p class="review-meta">Reviewed by {provider_name} ({model}) on {review_date}{release_info}</p>
+{review_text}
+</div>
+"""
+    elif args.output_format == "markdown":
+        release_info = ""
+        if args.release:
+            lts_badge = " (LTS)" if is_lts_release(args.release) else ""
+            release_info = f"\n*Target release: {args.release}{lts_badge}*\n"
+        output_text = f"""# Patch Review: {patch_name}
+
+*Reviewed by {provider_name} ({model}) on {review_date}*
+{release_info}
+{review_text}
+"""
+    else:  # text
+        release_info = ""
+        if args.release:
+            lts_badge = " (LTS)" if is_lts_release(args.release) else ""
+            release_info = f"Target release: {args.release}{lts_badge}\n"
+        output_text = f"=== Patch Review: {patch_name} (via {provider_name}) ===\n"
+        output_text += f"Review date: {review_date}\n"
+        output_text += release_info
+        output_text += "\n" + review_text
+
+    # Write output
+    if args.output:
+        Path(args.output).write_text(output_text)
+        print(f"Review written to: {args.output}", file=sys.stderr)
+    else:
+        print(output_text)
+
+    print_token_summary(
+        total_usage,
+        args.provider,
+        model,
+        args.show_tokens or args.verbose,
+    )
+
+    # Send email if requested
+    if args.send_email:
+        # Email always uses plain text - warn if different format requested
+        if args.output_format != "text":
+            print(
+                f"Note: Email will be sent as plain text regardless of "
+                f"--format={args.output_format}",
+                file=sys.stderr,
+            )
+
+        in_reply_to = get_last_message_id(patch_content)
+        orig_subject = get_last_subject(patch_content)
+
+        if orig_subject:
+            # Remove [PATCH n/m] prefix
+            review_subject = re.sub(r"^\[PATCH[^\]]*\]\s*", "", orig_subject)
+            review_subject = f"[REVIEW] {review_subject}"
+        else:
+            review_subject = f"[REVIEW] {patch_name}"
+
+        # Build email body - always use plain text version
+        release_info = ""
+        if args.release:
+            lts_badge = " (LTS)" if is_lts_release(args.release) else ""
+            release_info = f"Target release: {args.release}{lts_badge}\n"
+
+        email_body = f"""AI-generated review of {patch_name}
+Reviewed using {provider_name} ({model}) on {review_date}
+{release_info}
+This is an automated review. Please verify all suggestions.
+
+---
+
+{review_text}
+"""
+
+        if args.verbose:
+            print("", file=sys.stderr)
+            print("=== Email Details ===", file=sys.stderr)
+            print(f"Subject: {review_subject}", file=sys.stderr)
+            print(f"In-Reply-To: {in_reply_to}", file=sys.stderr)
+            print("=====================", file=sys.stderr)
+
+        send_email(
+            args.to_addrs,
+            args.cc_addrs,
+            from_addr,
+            review_subject,
+            in_reply_to,
+            email_body,
+            args.dry_run,
+        )
+
+        if not args.dry_run:
+            print("", file=sys.stderr)
+            print(f"Review sent to: {', '.join(args.to_addrs)}", file=sys.stderr)
+
+    # Exit with code based on review severity
+    sys.exit(classify_review(review_text, args.output_format))
+
+
+if __name__ == "__main__":
+    main()
-- 
2.53.0


^ permalink raw reply related

* [PATCH v15 1/7] doc: add AGENTS.md for AI code review tools
From: Stephen Hemminger @ 2026-05-21 22:44 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Aaron Conole
In-Reply-To: <20260521224550.927338-1-stephen@networkplumber.org>

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 <stephen@networkplumber.org>
Acked-by: Aaron Conole <aconole@redhat.com>
---
 AGENTS.md | 1826 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1826 insertions(+)
 create mode 100644 AGENTS.md

diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000000..8401f11c39
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,1826 @@
+<!-- SPDX-License-Identifier: BSD-3-Clause -->
+<!-- Copyright(c) 2026 Stephen Hemminger -->
+
+# 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
+- 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
+- Statistics accumulation using `=` instead of `+=`
+- Integer multiply without widening cast losing upper bits (16x16, 32x32, 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
+- `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
+- **Do NOT flag unchecked return values from functions
+  that always succeed on Linux.** Some POSIX functions are documented to always return zero on Linux
+  when called with valid arguments.
+  Flagging unchecked returns from these is a false positive.
+  The list includes:
+  - `pthread_mutex_init()`, `pthread_cond_init()`
+  - `pthread_cond_signal()`, `pthread_cond_broadcast()`, `pthread_cond_wait()`
+  - `pthread_condattr_init()`, `pthread_condattr_destroy()`
+  - `pthread_attr_init()`, `pthread_attr_destroy()`
+- 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`)
+- **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: 16x16 assigned to a 32-bit variable,
+  32x32 assigned to a 64-bit variable, etc.
+  ```c
+  /* BAD - 32x32 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;     /* 32x32 -> truncated */
+
+  /* BAD - 16x16 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);
+  }
+  ```
+- **`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
+
+### 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
+
+DPDK style requires explicit comparison against `NULL`, `0`, or `'\0'` rather than
+truthiness on pointers, integers, and characters: write `if (p == NULL)` not `if (!p)`,
+and `if (a != 0)` not `if (a)`.
+Direct truthiness is acceptable only on actual `bool` types.
+`likely()` and `unlikely()` wrappers do not change whether a comparison is explicit
+(`if (likely(p != NULL))` is still explicit).
+
+Mechanical rewrites for these are handled by coccinelle in `devtools/cocci/`;
+AI review need only catch cases cocci can't see (e.g. macro-expanded conditions).
+
+### 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.
+Several closely related patterns are caught by coccinelle scripts in `devtools/cocci/`
+(unnecessary `void *` casts, NULL checks before `free()`/`rte_free()`,
+`memset()` before `free()`, zero-length array members, dead variable initialization);
+those are not duplicated here.
+
+### 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.
+
+Queue-related buffers (descriptor rings, Rx/Tx queue control structures) should use `rte_zmalloc_socket()` rather
+than plain `rte_malloc()`: zero-initialization avoids stale descriptor bugs,
+the `_socket` variant ensures NUMA-local allocation,
+and hugepage backing makes the memory visible to secondary processes.
+
+```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_zmalloc_socket for descriptor rings / queue structures */
+struct my_rx_desc *ring = rte_zmalloc_socket("rx_ring",
+    n * sizeof(*ring), RTE_CACHE_LINE_SIZE, socket_id);
+```
+
+### 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 function additions (`rte_panic()`, `rte_exit()`, `perror()`, `printf()`,
+`fprintf()`, `getenv()` in `lib/` and `drivers/`, with appropriate per-function
+exceptions for EAL, `examples/`, and `app/test/`) are caught by `devtools/checkpatches.sh`.
+
+### Atomics and Memory Barriers
+
+Substitutions from the deprecated atomic APIs to the C11 DPDK wrappers
+(`__atomic_*`/`__ATOMIC_*` -> `rte_atomic_*_explicit()`/`rte_memory_order_*`,
+`__sync_*` -> `rte_atomic_*`, `rte_smp_{r,w,}mb()` -> `rte_atomic_thread_fence()`,
+`rte_atomic{16,32,64}_*()` -> `rte_atomic_*_explicit()`)
+are handled by coccinelle scripts in `devtools/cocci/`.
+AI review only needs to flag conceptual misuse, covered in the sections below.
+
+#### 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);
+```
+
+#### 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
+
+Substitutions from `pthread_*()` to the DPDK `rte_thread_*()` wrappers,
+and from `rte_thread_set_name()`/`rte_thread_create_control()` to their
+`_prefixed_name`/`_internal_control` replacements,
+are handled by coccinelle in `devtools/cocci/`.
+
+### 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;
+};
+
+void init_shared(struct shared_data *shm) {
+	pthread_mutexattr_t attr;
+
+	pthread_mutexattr_init(&attr);
+	pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+	pthread_mutex_init(&shm->lock, &attr);
+	pthread_mutexattr_destroy(&attr);
+}
+```
+
+#### 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, Attributes, Format Specifiers, and Test Macros
+
+Mechanical substitutions handled by coccinelle in `devtools/cocci/`:
+`__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` identifier -> renamed (reserved in Windows headers);
+`%lld`/`%llu`/`%llx` -> `%PRId64`/`%PRIu64`/`%PRIx64`;
+`REGISTER_TEST_COMMAND` -> `REGISTER_<suite_name>_TEST`.
+
+Also avoid `#pragma` / `_Pragma` outside `rte_common.h`.
+
+### 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/ |
+
+### 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
+
+`rte_atomic*_t` types, `rte_smp_*mb()` barriers, and `pthread_*()` in portable code
+all have modern replacements covered in the Atomics and Threading sections above
+and are rewritten by coccinelle.
+
+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: stable@dpdk.org`
+
+### 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
+- [ ] No forbidden tokens (see table above)
+- [ ] No usage of deprecated APIs, macros, or functions
+- [ ] Process-shared primitives in shared memory use `PTHREAD_PROCESS_SHARED`
+- [ ] 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
+- [ ] `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
+- [ ] Memory ordering is the weakest correct choice (`relaxed` for counters, `acquire`/`release` for publish/consume)
+
+### 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
+- Error path that skips necessary cleanup
+- Statistics accumulation using `=` instead of `+=` (overwrite vs increment)
+- Integer multiply without widening cast losing upper bits (16x16, 32x32, 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
+- `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: stable@dpdk.org 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
+- Inappropriate use of `rte_malloc()` or `rte_memcpy()`
+- Queue-related buffers (descriptor rings, Rx/Tx queue structures) allocated with `malloc()` instead of `rte_zmalloc_socket()` (breaks secondary process access, loses NUMA locality)
+- 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
+- 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
+- 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)
+- 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.**
-- 
2.53.0


^ permalink raw reply related

* [PATCH v15 2/7] devtools: add common infrastructure for AI review scripts
From: Stephen Hemminger @ 2026-05-21 22:44 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Aaron Conole
In-Reply-To: <20260521224550.927338-1-stephen@networkplumber.org>

The AI review scripts share similar options and infrastructure.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 devtools/ai/_common.py | 246 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 246 insertions(+)
 create mode 100644 devtools/ai/_common.py

diff --git a/devtools/ai/_common.py b/devtools/ai/_common.py
new file mode 100644
index 0000000000..69982cbda5
--- /dev/null
+++ b/devtools/ai/_common.py
@@ -0,0 +1,246 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026 Stephen Hemminger
+
+"""Common utilities shared by the DPDK AI review scripts."""
+
+import argparse
+import json
+import subprocess
+import sys
+from dataclasses import dataclass
+from typing import Any, NoReturn
+from urllib.error import HTTPError, URLError
+from urllib.request import Request, urlopen
+
+# Provider configurations (model defaults; override with --model).
+PROVIDERS: dict[str, dict[str, str]] = {
+    "anthropic": {
+        "name": "Claude",
+        "endpoint": "https://api.anthropic.com/v1/messages",
+        "default_model": "claude-sonnet-4-5-20250929",
+        "env_var": "ANTHROPIC_API_KEY",
+    },
+    "openai": {
+        "name": "ChatGPT",
+        "endpoint": "https://api.openai.com/v1/chat/completions",
+        "default_model": "gpt-4.1",
+        "env_var": "OPENAI_API_KEY",
+    },
+    "xai": {
+        "name": "Grok",
+        "endpoint": "https://api.x.ai/v1/chat/completions",
+        "default_model": "grok-4-1-fast-non-reasoning",
+        "env_var": "XAI_API_KEY",
+    },
+    "google": {
+        "name": "Gemini",
+        "endpoint": "https://generativelanguage.googleapis.com/v1beta/models",
+        "default_model": "gemini-3-flash-preview",
+        "env_var": "GOOGLE_API_KEY",
+    },
+}
+
+
+def error(msg: str) -> NoReturn:
+    """Print error message to stderr and exit with status 1."""
+    print(f"Error: {msg}", file=sys.stderr)
+    sys.exit(1)
+
+
+def get_git_config(key: str) -> str | None:
+    """Return the value of a git config key, or None if unset/git missing."""
+    try:
+        result = subprocess.run(
+            ["git", "config", "--get", key],
+            capture_output=True,
+            text=True,
+            check=True,
+        )
+        return result.stdout.strip()
+    except (subprocess.CalledProcessError, FileNotFoundError):
+        return None
+
+
+def list_providers() -> NoReturn:
+    """Print available providers and exit."""
+    print("Available AI Providers:\n")
+    print(f"{'Provider':<12} {'Default Model':<30} {'API Key Variable'}")
+    print(f"{'--------':<12} {'-------------':<30} {'----------------'}")
+    for name, config in PROVIDERS.items():
+        print(f"{name:<12} {config['default_model']:<30} {config['env_var']}")
+    sys.exit(0)
+
+
+@dataclass
+class TokenUsage:
+    """Accumulated token usage across API calls."""
+
+    input_tokens: int = 0
+    output_tokens: int = 0
+    cache_creation_tokens: int = 0
+    cache_read_tokens: int = 0
+    api_calls: int = 0
+
+    def add(self, other: "TokenUsage") -> None:
+        """Accumulate usage from another TokenUsage."""
+        self.input_tokens += other.input_tokens
+        self.output_tokens += other.output_tokens
+        self.cache_creation_tokens += other.cache_creation_tokens
+        self.cache_read_tokens += other.cache_read_tokens
+        self.api_calls += other.api_calls
+
+
+def format_token_summary(usage: TokenUsage, provider: str, model: str) -> str:
+    """Format a token usage summary string."""
+    provider_label = PROVIDERS.get(provider, {}).get("name", provider)
+    lines = ["=== Token Usage Summary ==="]
+    lines.append(f"Provider:      {provider_label} ({model})")
+    lines.append(f"API calls:     {usage.api_calls}")
+    lines.append(f"Input tokens:  {usage.input_tokens:,}")
+    lines.append(f"Output tokens: {usage.output_tokens:,}")
+    if usage.cache_creation_tokens:
+        lines.append(f"Cache write:   {usage.cache_creation_tokens:,}")
+    if usage.cache_read_tokens:
+        lines.append(f"Cache read:    {usage.cache_read_tokens:,}")
+    total = usage.input_tokens + usage.output_tokens
+    lines.append(f"Total tokens:  {total:,}")
+    lines.append("=" * 27)
+    return "\n".join(lines)
+
+
+def add_token_args(parser: argparse.ArgumentParser) -> None:
+    """Add the --show-tokens flag to an ArgumentParser."""
+    parser.add_argument(
+        "--show-tokens",
+        action="store_true",
+        help="Show token usage summary on stderr after the run",
+    )
+
+
+def print_token_summary(
+    usage: TokenUsage, provider: str, model: str, show: bool
+) -> None:
+    """Print token usage summary to stderr if requested and any calls were made."""
+    if not show or usage.api_calls == 0:
+        return
+    print("", file=sys.stderr)
+    print(format_token_summary(usage, provider, model), file=sys.stderr)
+
+
+def _build_request_meta(
+    provider: str, api_key: str, model: str
+) -> tuple[str, dict[str, str]]:
+    """Return (url, headers) for a provider request."""
+    config = PROVIDERS[provider]
+    if provider == "anthropic":
+        return config["endpoint"], {
+            "Content-Type": "application/json",
+            "x-api-key": api_key,
+            "anthropic-version": "2023-06-01",
+        }
+    if provider == "google":
+        url = f"{config['endpoint']}/{model}:generateContent?key={api_key}"
+        return url, {"Content-Type": "application/json"}
+    # openai, xai
+    return config["endpoint"], {
+        "Content-Type": "application/json",
+        "Authorization": f"Bearer {api_key}",
+    }
+
+
+def _extract_usage(provider: str, result: dict[str, Any]) -> TokenUsage:
+    """Extract TokenUsage from a provider response."""
+    usage = TokenUsage(api_calls=1)
+    if provider == "anthropic":
+        raw = result.get("usage", {})
+        usage.input_tokens = raw.get("input_tokens", 0)
+        usage.output_tokens = raw.get("output_tokens", 0)
+        usage.cache_creation_tokens = raw.get("cache_creation_input_tokens", 0)
+        usage.cache_read_tokens = raw.get("cache_read_input_tokens", 0)
+    elif provider == "google":
+        raw = result.get("usageMetadata", {})
+        usage.input_tokens = raw.get("promptTokenCount", 0)
+        usage.output_tokens = raw.get("candidatesTokenCount", 0)
+    else:  # openai, xai
+        raw = result.get("usage", {})
+        usage.input_tokens = raw.get("prompt_tokens", 0)
+        usage.output_tokens = raw.get("completion_tokens", 0)
+        cache_details = raw.get("prompt_tokens_details", {})
+        if cache_details:
+            usage.cache_read_tokens = cache_details.get("cached_tokens", 0)
+    return usage
+
+
+def _extract_text(provider: str, result: dict[str, Any]) -> str:
+    """Extract response text from a provider response. Calls error() on failure."""
+    if "error" in result:
+        error(f"API error: {result['error'].get('message', result)}")
+    if provider == "anthropic":
+        content = result.get("content", [])
+        return "".join(
+            block.get("text", "") for block in content if block.get("type") == "text"
+        )
+    if provider == "google":
+        candidates = result.get("candidates", [])
+        if not candidates:
+            error("No response from Gemini")
+        parts = candidates[0].get("content", {}).get("parts", [])
+        return "".join(part.get("text", "") for part in parts)
+    # openai, xai
+    choices = result.get("choices", [])
+    if not choices:
+        error("No response from API")
+    return choices[0].get("message", {}).get("content", "")
+
+
+def _print_verbose_usage(usage: TokenUsage) -> None:
+    """Print per-call token details to stderr."""
+    print("=== Token Usage ===", file=sys.stderr)
+    print(f"Input tokens: {usage.input_tokens:,}", file=sys.stderr)
+    print(f"Output tokens: {usage.output_tokens:,}", file=sys.stderr)
+    if usage.cache_creation_tokens:
+        print(f"Cache creation: {usage.cache_creation_tokens:,}", file=sys.stderr)
+    if usage.cache_read_tokens:
+        print(f"Cache read: {usage.cache_read_tokens:,}", file=sys.stderr)
+    print("===================", file=sys.stderr)
+
+
+def send_request(
+    provider: str,
+    api_key: str,
+    model: str,
+    request_data: dict[str, Any],
+    *,
+    timeout: int = 120,
+    verbose: bool = False,
+) -> tuple[str, TokenUsage]:
+    """Send a prebuilt request to a provider and return (response_text, usage).
+
+    The caller assembles the provider-specific request body via its own
+    build_*_request helpers (the prompts differ per script). This function
+    handles transport, error reporting, and token-usage extraction.
+    """
+    url, headers = _build_request_meta(provider, api_key, model)
+    body = json.dumps(request_data).encode("utf-8")
+    req = Request(url, data=body, headers=headers)
+
+    try:
+        with urlopen(req, timeout=timeout) as response:
+            result = json.loads(response.read().decode("utf-8"))
+    except HTTPError as e:
+        error_body = e.read().decode("utf-8")
+        try:
+            error_data = json.loads(error_body)
+            error(f"API error: {error_data.get('error', error_body)}")
+        except json.JSONDecodeError:
+            error(f"API error ({e.code}): {error_body}")
+    except URLError as e:
+        if isinstance(e.reason, TimeoutError):
+            error(f"Request timed out after {timeout} seconds")
+        error(f"Connection error: {e.reason}")
+
+    usage = _extract_usage(provider, result)
+    if verbose:
+        _print_verbose_usage(usage)
+    return _extract_text(provider, result), usage
-- 
2.53.0


^ permalink raw reply related

* [PATCH v15 0/6] Add AI code review tools and AGENTS
From: Stephen Hemminger @ 2026-05-21 22:44 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger
In-Reply-To: <20260126184205.104629-1-stephen@networkplumber.org>

Add guidelines and tooling for AI-assisted code review of DPDK
patches.

AGENTS.md provides a two-tier review framework: correctness bugs
(resource leaks, use-after-free, race conditions) are reported at
>=50% confidence; style issues require >80% with false positive
suppression. Mechanical checks handled by checkpatches.sh are
excluded to avoid redundant findings.

The analyze-patch.py and review-doc.py scripts support multiple AI
providers (Anthropic, OpenAI, xAI, Google) with mbox splitting,
prompt caching, direct SMTP sending, and token usage tracking with
optional cost estimation.

v15 - reflow AGENTS per doc guidelines
    - reduce redundancy in AGENTS (i.e don't overlap other tools)
    - move scripts to devtools/ai
    - move common code from scripts to one file
    - incorporate AI and human reviews

Stephen Hemminger (7):
  doc: add AGENTS.md for AI code review tools
  devtools: add common infrastructure for AI review scripts
  devtools: add multi-provider AI patch review script
  devtools: add compare-patch-reviews.sh for multi-provider analysis
  devtools: add multi-provider AI documentation review script
  doc: add AI-assisted patch review to contributing guide
  MAINTAINERS: add section for AI review tools

 AGENTS.md                              | 1826 ++++++++++++++++++++++++
 MAINTAINERS                            |    6 +
 devtools/ai/_common.py                 |  246 ++++
 devtools/ai/analyze-patch.py           | 1330 +++++++++++++++++
 devtools/ai/compare-patch-reviews.sh   |  306 ++++
 devtools/ai/review-doc.py              | 1013 +++++++++++++
 doc/guides/contributing/new_driver.rst |    2 +
 doc/guides/contributing/patches.rst    |   59 +
 doc/guides/rel_notes/release_26_07.rst |    5 +
 9 files changed, 4793 insertions(+)
 create mode 100644 AGENTS.md
 create mode 100644 devtools/ai/_common.py
 create mode 100755 devtools/ai/analyze-patch.py
 create mode 100755 devtools/ai/compare-patch-reviews.sh
 create mode 100755 devtools/ai/review-doc.py

-- 
2.53.0


^ permalink raw reply

* Re: [PATCH v11] eal/x86: optimize memcpy of small sizes
From: Stephen Hemminger @ 2026-05-21 22:42 UTC (permalink / raw)
  To: Morten Brørup
  Cc: dev, Bruce Richardson, Konstantin Ananyev, Vipin Varghese,
	Liangxing Wang, Thiyagarajan P, Bala Murali Krishna,
	Anatoly Burakov, Vladimir Medvedkin, Konstantin Ananyev
In-Reply-To: <20260521185631.116046-1-mb@smartsharesystems.com>

On Thu, 21 May 2026 18:56:31 +0000
Morten Brørup <mb@smartsharesystems.com> wrote:

> The implementation for copying up to 64 bytes does not depend on address
> alignment with the size of the CPU's vector registers. Nonetheless, the
> exact same code for copying up to 64 bytes was present in both the aligned
> copy function and all the CPU vector register size specific variants of
> the unaligned copy functions.
> With this patch, the implementation for copying up to 64 bytes was
> consolidated into one instance, located in the common copy function,
> before checking alignment requirements.
> This provides three benefits:
> 1. No copy-paste in the source code.
> 2. A performance gain for copying up to 64 bytes, because the
> address alignment check is avoided in this case.
> 3. Reduced instruction memory footprint, because the compiler only
> generates one instance of the function for copying up to 64 bytes, instead
> of two instances (one in the unaligned copy function, and one in the
> aligned copy function).
> 
> Furthermore, __rte_restrict was added to source and destination addresses.
> 
> Also, the missing implementation of rte_mov48() was added.
> 
> Until recently, some drivers required disabling stringop-overflow warnings
> when using rte_memcpy().
> For some strange reason, these warnings were disabled in the rte_memcpy
> header file, instead of in the problematic drivers.
> With series-38174 ("remove use of rte_memcpy from net/intel"), the
> problematic drivers were updated to use memcpy() instead of rte_memcpy(),
> so disabling these warnings is no longer required, and was removed.
> 
> Regarding performance...
> The memcpy performance test (cache-to-cache copy) shows:
> Copying up to 15 bytes takes ca. 4.5 cycles, versus ca. 6.5 cycles before.
> Copying 8 bytes takes 4 cycles, versus 7 cycles before.
> Copying 16 bytes takes 2 cycles, versus 4 cycles before.
> Copying 64 bytes takes 4 cycles, versus 7 cycles before.
> 
> Depends-on: series-38174 ("remove use of rte_memcpy from net/intel")
> 
> Signed-off-by: Morten Brørup <mb@smartsharesystems.com>
> Acked-by: Bruce Richardson <bruce.richardson@intel.com>
> Acked-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>
> ---

Here is the full wordy all providers reviews.
Mostly this is test to make sure script works.

$ ./devtools/ai/compare-patch-reviews.sh ~/Downloads/v11-eal-x86-optimize-memcpy-of-small-sizes.patch 
Reviewing patch: v11-eal-x86-optimize-memcpy-of-small-sizes.patch
Providers: anthropic,openai,xai,google
Format: text
========================================

>>> Running review with: anthropic

=== Patch Review: v11-eal-x86-optimize-memcpy-of-small-sizes.patch (via Claude) ===
Review date: 2026-05-21

# Review of v11-eal-x86-optimize-memcpy-of-small-sizes.patch

## Correctness Issues

### Errors

**Use-after-free pattern in overlapping copy optimization (lines 672-673, 684, 704, 707)**

The pattern `(uint8_t *)dst - 16 + n` performs pointer arithmetic that, when `n < 16`, results in accessing memory before the `dst` pointer. This is used in an optimization technique (two overlapping stores) that is correct when `n >= 16`, but the code paths in question guarantee `n` is in specific ranges where this is safe. However, the readability is poor and the intent is not obvious from the code.

**Confidence: 80%** - This pattern is widely used in memcpy implementations and appears intentional, but deserves a comment explaining the technique to avoid confusion in future reviews.

---

## Warnings

**Missing documentation for new function rte_mov48() (line 130)**

The patch adds `rte_mov48()` but does not mention it in the commit message's "Also, the missing implementation of rte_mov48() was added." The implementation itself is correct, but there's no context about why it was missing or whether it's used elsewhere in DPDK.

**Suggestion:** Verify that `rte_mov48()` is actually used somewhere, or document why it's being added speculatively.

---

**Inconsistent handling of compile-time constant size (lines 682-683)**

The code checks `__rte_constant(n) && n == 16` to avoid a "harmless duplicate copy" but does not apply this optimization consistently. For example:
- Line 682: avoids duplicate for `n == 16`
- Line 690: avoids duplicate for `n == 32`
- Line 693: avoids duplicate for `n == 64`

But in the 33-64 byte range (lines 695-707), there's no similar optimization. This is not a bug, but the inconsistency in optimization strategy could be noted.

**Suggestion:** Add a comment explaining the rationale for when this optimization matters vs when the "harmless duplicate" is acceptable.

---

**Potential alignment assumption in AVX path (line 699)**

The AVX path at line 699 uses two overlapping `rte_mov32()` calls for sizes 33-64 bytes:
```c
rte_mov32((uint8_t *)dst, (const uint8_t *)src);
rte_mov32((uint8_t *)dst - 32 + n, (const uint8_t *)src - 32 + n);
```

For `n = 33`, the second call accesses `dst + 1` and `src + 1`, which may be misaligned for the 32-byte `__m256i` operations. However, `_mm256_loadu_si256()` and `_mm256_storeu_si256()` are documented as supporting unaligned access, so this is safe.

**Confidence: 90%** - Not a bug, but worth noting that the AVX path relies on unaligned load/store support.

---

## Style and Process Issues

### Warnings

**Unconventional comment style in rte_mov15_or_less() (lines 67-72)**

The multi-line comment explaining the rationale for the `rte_uint*_alias` structures does not follow the standard DPDK comment block format with the `*` on each line. However, this may be intentional for a technical explanation.

---

**Return value inconsistency (lines 682, 690-691, 693, 707-708)**

The function returns `dst` in some branches but the original `ret` pointer in others:
- Lines 682, 690, 707: return `dst`
- Line 714: return from `rte_memcpy_aligned_more_than_64(dst, src, n)` which returns `ret = dst`
- Line 716: return from `rte_memcpy_generic_more_than_64(dst, src, n)` which returns `ret = dst`

Both `ret` and `dst` point to the same location, so this is harmless, but using `dst` consistently would be clearer. The patch description states "Pointer to the destination data" is returned, and both satisfy this.

**Confidence: 95%** - Not an error, but inconsistent style within the same function.

---

## Documentation Issues

### Warnings

**Missing documentation update for removed warning suppression (lines removed at top)**

The patch removes:
```c
#if defined(RTE_TOOLCHAIN_GCC) && (GCC_VERSION >= 100000)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-overflow"
#endif
```

The commit message explains this is because the problematic drivers were updated, with a `Depends-on: series-38174`. However, there's no comment in the code explaining why the suppression was removed or a note in `doc/guides/rel_notes/` documenting this change.

**Suggestion:** Add a release note entry documenting the removal of the warning suppression and the dependency on the driver updates.

---

## Summary

**Priority findings:**

1. **(Info)** The overlapping copy optimization `dst - 16 + n` is correct but deserves a comment explaining the technique.
2. **(Warning)** Missing documentation for why `rte_mov48()` was added.
3. **(Warning)** Missing release notes entry for removal of stringop-overflow warning suppression.
4. **(Info)** Return value uses both `dst` and `ret` inconsistently (harmless but could be unified).

**Overall assessment:** The patch appears functionally correct. The main concerns are documentation completeness (missing context for `rte_mov48()`, missing release notes for warning removal) and code clarity (overlapping copy pattern could use explanatory comments). No blocking correctness issues were identified.
(anthropic: review reported errors)

========================================

>>> Running review with: openai

=== Patch Review: v11-eal-x86-optimize-memcpy-of-small-sizes.patch (via ChatGPT) ===
Review date: 2026-05-21

**DPDK Patch Review: v11-eal-x86-optimize-memcpy-of-small-sizes.patch**

---

### Errors

None found.

- The patch does not introduce resource leaks, use-after-free, race conditions, or missing synchronization.
- There are no buffer overflows or out-of-bounds accesses in the memcpy routines; all copy size calculations and code paths for small copies (<64 bytes) are explicit and conservative.
- No uninitialized or stale variable use detected.
- All error paths are clean (these are computational functions; return values are always valid).

---

### Warnings

1. **Potential inconsistent comment phrasing regarding overlap requirements.**

   - Some Doxygen comments use "locations should not overlap" while others now correctly say "locations must not overlap".
   - While this isn't a correctness bug, consistent strong wording clarifies requirement for users.

   **Suggested fix:** Standardize all relevant Doxygen comments to use "locations must not overlap."

   ```c
   /**
    * Copy bytes from one location to another,
    * locations must not overlap.
    */
   ```

2. **Redundant Doxygen param alignment in function headers.**

   - Some parameter descriptions ("Pointer to the destination of the data"/"Pointer to the destination data") remain a bit unclear or inconsistent. If updating comments, consider making param descriptions exact and explicit.

   **Suggested fix:** For each `@param` clarify if it's the destination or source pointer. For `rte_memcpy`, `"Pointer to the destination."` and `"Pointer to the source."` suffice.

---

### Info

- Use of `__rte_restrict` is correct and improves optimization; matches DPDK style for pointer restrict qualifiers.
- Removal of `#pragma GCC diagnostic ignored "-Wstringop-overflow"` is correct because the problematic drivers now use standard `memcpy()`.
- All new `rte_mov*` functions properly use `static __rte_always_inline` and restrict-qualified pointers.
- Macro duplication is avoided by consolidation, in line with maintainability guidelines.
- No forbidden tokens, banned constructs, or use of deprecated APIs/macros were added.
- No symbol namespace pollution or missing `rte_`/`RTE_` prefix issues present.
- Coding style is maintained in indentation, tabs use, and function/variable names.
- No API, public header, or C11 atomic/volatile/race issues are present.
- No documentation or API tag requirements are triggered (no public API change).
- Patch is self-contained and does not require changes to release notes or test infrastructure.
- No RST or external documentation is involved in this patch.

---

### Summary

**No correctness/blocking issues found.**
Minor documentation consistency updates suggested for comment clarity, but these do not affect correctness.

**This patch is in good shape and complies with DPDK coding and architectural guidelines.**
(openai: review reported errors)

========================================

>>> Running review with: xai

=== Patch Review: v11-eal-x86-optimize-memcpy-of-small-sizes.patch (via Grok) ===
Review date: 2026-05-21

**Review of patch: v11-eal-x86-optimize-memcpy-of-small-sizes.patch**

### Errors

**1. Missing `__rte_restrict` in rte_mov48() implementation (consistency / correctness risk)**  
The new `rte_mov48()` function is declared with `__rte_restrict`, but the implementation casts away the restrict qualifier when calling `rte_mov32()` and `rte_mov16()`:

```c
rte_mov32((uint8_t *)dst, (const uint8_t *)src);
rte_mov16((uint8_t *)dst + 32, (const uint8_t *)src + 32);
```

**Suggested fix**: Keep the restrict-qualified parameters in the implementation or cast only after documenting the aliasing contract. This is a minor style inconsistency but worth fixing for uniformity.

**2. Potential sign-extension issue on 64-bit offset calculations (left-shift of narrow type)**  
In `rte_mov15_or_less()`, the expression `(n & 8)` is `size_t` (ok), but the subsequent pointer arithmetic:

```c
*(uint64_t *)((uint8_t *)dst + n - 8) = *(const uint64_t *)((const uint8_t *)src + n - 8);
```

relies on implicit promotion. While currently safe, the pattern of adding a `uint8_t`/`size_t` value to a pointer after a narrow mask (`n & 7`) could trigger Coverity SIGN_EXTENSION warnings on some toolchains. Consider an explicit cast:

```c
uint8_t *d = (uint8_t *)dst + (size_t)(n - 8);
```

**3. Use of `__rte_constant` inside hot path without documented behavior**  
The code repeatedly uses `__rte_constant(n)` to optimize compile-time known sizes. This is a DPDK internal macro. While acceptable for performance-critical code, it should be documented (or at least noted) that the optimization only triggers for constant `n`. No correctness bug, but worth a comment.

### Warnings

**1. Duplicate function definitions for AVX/SSE variants**  
The patch introduces three nearly identical `rte_memcpy_generic_more_than_64()` and `rte_memcpy_aligned_more_than_64()` functions (one per vector size). This increases maintenance burden and instruction cache pressure. Consider factoring the common >64-byte logic into a single helper.

**2. Hard-coded cast chains in rte_mov48()**  
The SSE implementation of `rte_mov48()` uses explicit `(uint8_t *)` casts on restrict-qualified pointers. While functionally correct, it reduces readability. A cleaner pattern would be:

```c
rte_mov16(dst + 0, src + 0);
rte_mov16(dst + 16, src + 16);
rte_mov16(dst + 32, src + 32);
```

**3. Inconsistent use of `return dst` vs `return ret`**  
In the new `rte_memcpy()` implementation the fast path returns `dst`, while the old `rte_memcpy_generic()` returned the saved `ret`. Although `dst` and `ret` are identical, using `ret` consistently would avoid any future divergence.

**4. Missing Doxygen for new rte_mov48()**  
The new function has a Doxygen comment, but the description is minimal. Consider adding a brief note that it is an internal helper (or mark it `__rte_internal` if it should not be public).

### Info

- The removal of the `#pragma GCC diagnostic` block for `-Wstringop-overflow` is correct and aligns with the series that removed `rte_memcpy` usage from drivers.
- Adding `__rte_restrict` to `rte_memcpy()` and helper functions is a good improvement and matches modern DPDK style.
- Performance claims in the commit message are outside the scope of this review (they are not code).

### Summary

No critical correctness bugs (use-after-free, leaks, races, or missing error checks) were identified. The main issues are minor style/consistency items around restrict qualifiers, pointer arithmetic, and code duplication. The patch is otherwise clean and follows DPDK coding guidelines for the x86 EAL memcpy implementation.
(xai: review reported errors)

========================================

>>> Running review with: google

=== Patch Review: v11-eal-x86-optimize-memcpy-of-small-sizes.patch (via Gemini) ===
Review date: 2026-05-21

The following is a review of the patch against DPDK coding standards and guidelines.

### Warnings

**Implicit integer comparison in rte_memcpy.**
DPDK coding style requires explicit comparison against 0 for integer types, rather than using truthiness/logical negation.
```c
/* Current implementation */
if (!(((uintptr_t)dst | (uintptr_t)src) & ALIGNMENT_MASK))

/* Suggested fix */
if ((((uintptr_t)dst | (uintptr_t)src) & ALIGNMENT_MASK) == 0)
```

### Info

**Use of bitwise logic in rte_mov15_or_less.**
While this patch only adds `__rte_restrict` to the signature, the existing
(google: review reported warnings)

========================================

Review comparison complete.
Summary across 4 provider(s): clean=0 warnings=1 errors=3 failed=0


^ permalink raw reply

* Re: [PATCH v11] eal/x86: optimize memcpy of small sizes
From: Stephen Hemminger @ 2026-05-21 19:48 UTC (permalink / raw)
  To: Morten Brørup
  Cc: dev, Bruce Richardson, Konstantin Ananyev, Vipin Varghese,
	Liangxing Wang, Thiyagarajan P, Bala Murali Krishna,
	Anatoly Burakov, Vladimir Medvedkin, Konstantin Ananyev
In-Reply-To: <20260521185631.116046-1-mb@smartsharesystems.com>

On Thu, 21 May 2026 18:56:31 +0000
Morten Brørup <mb@smartsharesystems.com> wrote:

> The implementation for copying up to 64 bytes does not depend on address
> alignment with the size of the CPU's vector registers. Nonetheless, the
> exact same code for copying up to 64 bytes was present in both the aligned
> copy function and all the CPU vector register size specific variants of
> the unaligned copy functions.
> With this patch, the implementation for copying up to 64 bytes was
> consolidated into one instance, located in the common copy function,
> before checking alignment requirements.
> This provides three benefits:
> 1. No copy-paste in the source code.
> 2. A performance gain for copying up to 64 bytes, because the
> address alignment check is avoided in this case.
> 3. Reduced instruction memory footprint, because the compiler only
> generates one instance of the function for copying up to 64 bytes, instead
> of two instances (one in the unaligned copy function, and one in the
> aligned copy function).
> 
> Furthermore, __rte_restrict was added to source and destination addresses.
> 
> Also, the missing implementation of rte_mov48() was added.
> 
> Until recently, some drivers required disabling stringop-overflow warnings
> when using rte_memcpy().
> For some strange reason, these warnings were disabled in the rte_memcpy
> header file, instead of in the problematic drivers.
> With series-38174 ("remove use of rte_memcpy from net/intel"), the
> problematic drivers were updated to use memcpy() instead of rte_memcpy(),
> so disabling these warnings is no longer required, and was removed.
> 
> Regarding performance...
> The memcpy performance test (cache-to-cache copy) shows:
> Copying up to 15 bytes takes ca. 4.5 cycles, versus ca. 6.5 cycles before.
> Copying 8 bytes takes 4 cycles, versus 7 cycles before.
> Copying 16 bytes takes 2 cycles, versus 4 cycles before.
> Copying 64 bytes takes 4 cycles, versus 7 cycles before.
> 
> Depends-on: series-38174 ("remove use of rte_memcpy from net/intel")
> 
> Signed-off-by: Morten Brørup <mb@smartsharesystems.com>
> Acked-by: Bruce Richardson <bruce.richardson@intel.com>
> Acked-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>
> ---

Used this as test case for comparing AI reviews.

Anthropic saw:

## Warnings

### 1. Missing rte_mov48() test coverage

**Issue:** The patch adds a new `rte_mov48()` function but does not mention adding tests for it.

**Why it matters:** New utility functions should have test coverage to verify correctness, especially for low-level copy operations that could silently corrupt data if implemented incorrectly.

**Suggested fix:** Add a test case in `app/test/test_memcpy.c` (or equivalent) that verifies `rte_mov48()` copies 48 bytes correctly and preserves data beyond the copy range.

### 2. Documentation comment inconsistency

**Issue:** The main `rte_memcpy()` function description comment was removed (lines 37-39 of original), but the opening documentation text about overlapping locations remains inconsistent across helper functions.

The original stated:
```c
/**
 * Copy bytes from one location to another. The locations must not overlap.
 *
 * @note This is implemented as a macro, so it's address should not be taken
 * and care is needed as parameter expressions may be evaluated multiple times.
```

The new version removes the `@note` about macro implementation (which is correct since it's now a function), but does not update the main function comment to include the same `@param` / `@return` documentation that other memory functions have.

**Suggested fix:** Add complete Doxygen documentation to `rte_memcpy()`:
```c
/**
 * Copy bytes from one location to another. The locations must not overlap.
 *
 * @param dst
 *   Pointer to the destination of the data.
 * @param src
 *   Pointer to the source data.
 * @param n
 *   Number of bytes to copy.
 * @return
 *   Pointer to the destination data.
 */
```

xAI (Grok) saw:
**Error**

- `rte_mov48()` implementation casts away `__rte_restrict` qualifiers (lines 137-140 and 143-147).  
  Casting a restrict-qualified pointer to a non-restrict pointer is undefined behavior under the C standard and can break compiler alias analysis.  
  Fix: either make the callees (`rte_mov32`/`rte_mov16`) accept the restrict qualifiers, or use a local non-restrict pointer only inside `rte_mov48` after proper validation.

**Error**

- Multiple functions (`rte_mov16`, `rte_mov32`, `rte_mov48`, `rte_mov64`, `rte_mov128`, `rte_mov256`, `rte_mov128blocks`, `rte_mov512blocks`, `rte_memcpy_generic_more_than_64`, etc.) now take `uint8_t *__rte_restrict` parameters, but their callers inside the same file (e.g., the new `rte_memcpy` implementation and the various block-copy macros) pass `uint8_t *` without the restrict qualifier.  
  This creates implicit non-restrict paths that defeat the purpose of adding `__rte_restrict` and may trigger compiler warnings or incorrect optimizations.  
  Recommended fix: either propagate `__rte_restrict` consistently through all internal call sites or keep internal helpers non-restrict and only expose restrict at the public `rte_memcpy` boundary.

**Warning**

- `rte_memcpy_generic_more_than_64` and `rte_memcpy_aligned_more_than_64` are now the only implementations for >64-byte copies, but the old `rte_memcpy_generic` and `rte_memcpy_aligned` names have been removed.  
  Any external code (or test) that took the address of the old functions will break.  
  The patch should either keep the old names as aliases or document the removal in release notes.

**Warning**

- The new `rte_memcpy` implementation for 17–64 bytes contains a compile-time constant check (`if (__rte_constant(n) && n == 64)`) followed by a fallback path that still executes `rte_mov16` three times for SSE when `n > 48`.  
  This path is correct but duplicates logic that already exists in `rte_mov48`.  
  Consider calling `rte_mov48` for the AVX case and a single `rte_mov48` + tail for SSE to reduce code duplication.

**Info**

- The removal of the `#pragma GCC diagnostic` block for `-Wstringop-overflow` is correct per the patch description (drivers were updated). No further action needed.

**Info**

- All other style, API-tag, and documentation requirements appear satisfied. No additional correctness bugs (leaks, use-after-free, races, or forbidden tokens) were identified.




^ permalink raw reply

* [PATCH v11] eal/x86: optimize memcpy of small sizes
From: Morten Brørup @ 2026-05-21 18:56 UTC (permalink / raw)
  To: dev, Bruce Richardson, Konstantin Ananyev, Vipin Varghese,
	Stephen Hemminger, Liangxing Wang
  Cc: Thiyagarajan P, Bala Murali Krishna, Anatoly Burakov,
	Vladimir Medvedkin, Morten Brørup, Konstantin Ananyev
In-Reply-To: <20251120114554.950287-1-mb@smartsharesystems.com>

The implementation for copying up to 64 bytes does not depend on address
alignment with the size of the CPU's vector registers. Nonetheless, the
exact same code for copying up to 64 bytes was present in both the aligned
copy function and all the CPU vector register size specific variants of
the unaligned copy functions.
With this patch, the implementation for copying up to 64 bytes was
consolidated into one instance, located in the common copy function,
before checking alignment requirements.
This provides three benefits:
1. No copy-paste in the source code.
2. A performance gain for copying up to 64 bytes, because the
address alignment check is avoided in this case.
3. Reduced instruction memory footprint, because the compiler only
generates one instance of the function for copying up to 64 bytes, instead
of two instances (one in the unaligned copy function, and one in the
aligned copy function).

Furthermore, __rte_restrict was added to source and destination addresses.

Also, the missing implementation of rte_mov48() was added.

Until recently, some drivers required disabling stringop-overflow warnings
when using rte_memcpy().
For some strange reason, these warnings were disabled in the rte_memcpy
header file, instead of in the problematic drivers.
With series-38174 ("remove use of rte_memcpy from net/intel"), the
problematic drivers were updated to use memcpy() instead of rte_memcpy(),
so disabling these warnings is no longer required, and was removed.

Regarding performance...
The memcpy performance test (cache-to-cache copy) shows:
Copying up to 15 bytes takes ca. 4.5 cycles, versus ca. 6.5 cycles before.
Copying 8 bytes takes 4 cycles, versus 7 cycles before.
Copying 16 bytes takes 2 cycles, versus 4 cycles before.
Copying 64 bytes takes 4 cycles, versus 7 cycles before.

Depends-on: series-38174 ("remove use of rte_memcpy from net/intel")

Signed-off-by: Morten Brørup <mb@smartsharesystems.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>
Acked-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>
---
v11:
* Removed ignoring stringop-overflow warnings.
  The drivers requiring this have been updated to use memcpy() instead.
  Added note about it in the cover letter, and a depends-on tag.
v10:
* Reverted removal of ignoring stringop-overflow warnings.
  Instead, added a comment about the reason for ignoring them.
  Some drivers still use elems[1] instead of elems[] for structures with
  flexible arrays.
  IMO, the drivers should be fixed, or the warnings should be igmored
  there; but I'm picking the easy solution, and not changing this.
  If they were using standard memcpy(), warnings would also be emitted.
v9:
* Removed new functions rte_mov16_to_32() and rte_mov32_to_64(), and moved
  their implementations into rte_memcpy() instead.
  There is no need for such public functions, and having them separate did
  not improve source code readability.
* Kept acks from Bruce and Konstantin (both given to v7).
v8:
* Reverted the first branch from size <= 16 back to size < 16, restored
  the original rte_mov15_or_less() function, and removed the new
  rte_mov16_or_less() function.
  When rte_memcpy() is used for copying an array of pointers, and the
  number of pointers to copy is low (size <= 64 bytes), it is more likely
  that the number of pointers to copy is 1 than 2.
  The rte_mov15_or_less() implementation handles copying 8 bytes more
  efficiently than the rte_mov16_or_less() implementation, which copied
  the 8-byte pointer twice.
  Also note that with rte_mov15_or_less(), the compiler can optimize away
  the branches handling n & 1, n & 2 and n & 4 when it is known at compile
  time that (8-byte) pointers are being copied. (For 32-bit architecture,
  the n & 4 will not be optimized away when copying pointers.)
  This reversion also makes the patch less revolutionary and more
  incremental.
* Removed a lot of code for handling compile time known sizes. (Bruce)
  The rte_memcpy() function should not be used for small copies with
  compile time known sizes, so handling it is considered superfluous.
  Removing it improves source code readability. And reduces the size of
  the patch.
* Kept acks from Bruce and Konstantin (both given to v7).
v7:
* Updated patch description. Mainly to clarify that the changes related to
  copying up to 64 bytes simply replaces multiple instances of copy-pasted
  code with one common instance.
* Fixed copy of compile time known 16 bytes in rte_mov17_to_32(). (Vipin)
* Rebased.
v6:
* Went back to using rte_uintN_alias structures for copying instead of
  using memcpy(). They were there for a reason.
  (Inspired by the discussion about optimizing the checksum function.)
* Removed note about copying uninitialized data.
* Added __rte_restrict to source and destination addresses.
  Updated function descriptions from "should" to "must" not overlap.
* Changed rte_mov48() AVX implementation to copy 32+16 bytes instead of
  copying 32 + 32 overlapping bytes. (Konstantin)
* Ignoring "-Wstringop-overflow" is not needed, so it was removed.
v5:
* Reverted v4: Replace SSE2 _mm_loadu_si128() with SSE3 _mm_lddqu_si128().
  It was slower.
* Improved some comments. (Konstantin Ananyev)
* Moved the size range 17..32 inside the size <= 64 branch, so when
  building for SSE, the generated code can start copying the first
  16 bytes before comparing if the size is greater than 32 or not.
* Just require RTE_MEMCPY_AVX for using rte_mov32() in rte_mov33_to_64().
v4:
* Replace SSE2 _mm_loadu_si128() with SSE3 _mm_lddqu_si128().
v3:
* Fixed typo in comment.
v2:
* Updated patch title to reflect that the performance is improved.
* Use the design pattern of two overlapping stores for small copies too.
* Expanded first branch from size < 16 to size <= 16.
* Handle more compile time constant copy sizes.
---
 lib/eal/x86/include/rte_memcpy.h | 250 +++++++++++++------------------
 1 file changed, 102 insertions(+), 148 deletions(-)

diff --git a/lib/eal/x86/include/rte_memcpy.h b/lib/eal/x86/include/rte_memcpy.h
index 46d34b8081..8ed8c55010 100644
--- a/lib/eal/x86/include/rte_memcpy.h
+++ b/lib/eal/x86/include/rte_memcpy.h
@@ -22,11 +22,6 @@
 extern "C" {
 #endif
 
-#if defined(RTE_TOOLCHAIN_GCC) && (GCC_VERSION >= 100000)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wstringop-overflow"
-#endif
-
 /*
  * GCC older than version 11 doesn't compile AVX properly, so use SSE instead.
  * There are no problems with AVX2.
@@ -40,9 +35,6 @@ extern "C" {
 /**
  * Copy bytes from one location to another. The locations must not overlap.
  *
- * @note This is implemented as a macro, so it's address should not be taken
- * and care is needed as parameter expressions may be evaluated multiple times.
- *
  * @param dst
  *   Pointer to the destination of the data.
  * @param src
@@ -53,15 +45,15 @@ extern "C" {
  *   Pointer to the destination data.
  */
 static __rte_always_inline void *
-rte_memcpy(void *dst, const void *src, size_t n);
+rte_memcpy(void *__rte_restrict dst, const void *__rte_restrict src, size_t n);
 
 /**
  * Copy bytes from one location to another,
- * locations should not overlap.
+ * locations must not overlap.
  * Use with n <= 15.
  */
 static __rte_always_inline void *
-rte_mov15_or_less(void *dst, const void *src, size_t n)
+rte_mov15_or_less(void *__rte_restrict dst, const void *__rte_restrict src, size_t n)
 {
 	/**
 	 * Use the following structs to avoid violating C standard
@@ -103,10 +95,10 @@ rte_mov15_or_less(void *dst, const void *src, size_t n)
 
 /**
  * Copy 16 bytes from one location to another,
- * locations should not overlap.
+ * locations must not overlap.
  */
 static __rte_always_inline void
-rte_mov16(uint8_t *dst, const uint8_t *src)
+rte_mov16(uint8_t *__rte_restrict dst, const uint8_t *__rte_restrict src)
 {
 	__m128i xmm0;
 
@@ -116,10 +108,10 @@ rte_mov16(uint8_t *dst, const uint8_t *src)
 
 /**
  * Copy 32 bytes from one location to another,
- * locations should not overlap.
+ * locations must not overlap.
  */
 static __rte_always_inline void
-rte_mov32(uint8_t *dst, const uint8_t *src)
+rte_mov32(uint8_t *__rte_restrict dst, const uint8_t *__rte_restrict src)
 {
 #if defined RTE_MEMCPY_AVX
 	__m256i ymm0;
@@ -132,12 +124,29 @@ rte_mov32(uint8_t *dst, const uint8_t *src)
 #endif
 }
 
+/**
+ * Copy 48 bytes from one location to another,
+ * locations must not overlap.
+ */
+static __rte_always_inline void
+rte_mov48(uint8_t *__rte_restrict dst, const uint8_t *__rte_restrict src)
+{
+#if defined RTE_MEMCPY_AVX
+	rte_mov32((uint8_t *)dst, (const uint8_t *)src);
+	rte_mov16((uint8_t *)dst + 32, (const uint8_t *)src + 32);
+#else /* SSE implementation */
+	rte_mov16((uint8_t *)dst + 0 * 16, (const uint8_t *)src + 0 * 16);
+	rte_mov16((uint8_t *)dst + 1 * 16, (const uint8_t *)src + 1 * 16);
+	rte_mov16((uint8_t *)dst + 2 * 16, (const uint8_t *)src + 2 * 16);
+#endif
+}
+
 /**
  * Copy 64 bytes from one location to another,
- * locations should not overlap.
+ * locations must not overlap.
  */
 static __rte_always_inline void
-rte_mov64(uint8_t *dst, const uint8_t *src)
+rte_mov64(uint8_t *__rte_restrict dst, const uint8_t *__rte_restrict src)
 {
 #if defined __AVX512F__ && defined RTE_MEMCPY_AVX512
 	__m512i zmm0;
@@ -152,10 +161,10 @@ rte_mov64(uint8_t *dst, const uint8_t *src)
 
 /**
  * Copy 128 bytes from one location to another,
- * locations should not overlap.
+ * locations must not overlap.
  */
 static __rte_always_inline void
-rte_mov128(uint8_t *dst, const uint8_t *src)
+rte_mov128(uint8_t *__rte_restrict dst, const uint8_t *__rte_restrict src)
 {
 	rte_mov64(dst + 0 * 64, src + 0 * 64);
 	rte_mov64(dst + 1 * 64, src + 1 * 64);
@@ -163,10 +172,10 @@ rte_mov128(uint8_t *dst, const uint8_t *src)
 
 /**
  * Copy 256 bytes from one location to another,
- * locations should not overlap.
+ * locations must not overlap.
  */
 static __rte_always_inline void
-rte_mov256(uint8_t *dst, const uint8_t *src)
+rte_mov256(uint8_t *__rte_restrict dst, const uint8_t *__rte_restrict src)
 {
 	rte_mov128(dst + 0 * 128, src + 0 * 128);
 	rte_mov128(dst + 1 * 128, src + 1 * 128);
@@ -182,10 +191,10 @@ rte_mov256(uint8_t *dst, const uint8_t *src)
 
 /**
  * Copy 128-byte blocks from one location to another,
- * locations should not overlap.
+ * locations must not overlap.
  */
 static __rte_always_inline void
-rte_mov128blocks(uint8_t *dst, const uint8_t *src, size_t n)
+rte_mov128blocks(uint8_t *__rte_restrict dst, const uint8_t *__rte_restrict src, size_t n)
 {
 	__m512i zmm0, zmm1;
 
@@ -202,10 +211,10 @@ rte_mov128blocks(uint8_t *dst, const uint8_t *src, size_t n)
 
 /**
  * Copy 512-byte blocks from one location to another,
- * locations should not overlap.
+ * locations must not overlap.
  */
 static inline void
-rte_mov512blocks(uint8_t *dst, const uint8_t *src, size_t n)
+rte_mov512blocks(uint8_t *__rte_restrict dst, const uint8_t *__rte_restrict src, size_t n)
 {
 	__m512i zmm0, zmm1, zmm2, zmm3, zmm4, zmm5, zmm6, zmm7;
 
@@ -232,45 +241,22 @@ rte_mov512blocks(uint8_t *dst, const uint8_t *src, size_t n)
 	}
 }
 
+/**
+ * Copy bytes from one location to another,
+ * locations must not overlap.
+ * Use with n > 64.
+ */
 static __rte_always_inline void *
-rte_memcpy_generic(void *dst, const void *src, size_t n)
+rte_memcpy_generic_more_than_64(void *__rte_restrict dst, const void *__rte_restrict src,
+		size_t n)
 {
 	void *ret = dst;
 	size_t dstofss;
 	size_t bits;
 
-	/**
-	 * Copy less than 16 bytes
-	 */
-	if (n < 16) {
-		return rte_mov15_or_less(dst, src, n);
-	}
-
 	/**
 	 * Fast way when copy size doesn't exceed 512 bytes
 	 */
-	if (__rte_constant(n) && n == 32) {
-		rte_mov32((uint8_t *)dst, (const uint8_t *)src);
-		return ret;
-	}
-	if (n <= 32) {
-		rte_mov16((uint8_t *)dst, (const uint8_t *)src);
-		if (__rte_constant(n) && n == 16)
-			return ret; /* avoid (harmless) duplicate copy */
-		rte_mov16((uint8_t *)dst - 16 + n,
-				  (const uint8_t *)src - 16 + n);
-		return ret;
-	}
-	if (__rte_constant(n) && n == 64) {
-		rte_mov64((uint8_t *)dst, (const uint8_t *)src);
-		return ret;
-	}
-	if (n <= 64) {
-		rte_mov32((uint8_t *)dst, (const uint8_t *)src);
-		rte_mov32((uint8_t *)dst - 32 + n,
-				  (const uint8_t *)src - 32 + n);
-		return ret;
-	}
 	if (n <= 512) {
 		if (n >= 256) {
 			n -= 256;
@@ -351,10 +337,10 @@ rte_memcpy_generic(void *dst, const void *src, size_t n)
 
 /**
  * Copy 128-byte blocks from one location to another,
- * locations should not overlap.
+ * locations must not overlap.
  */
 static __rte_always_inline void
-rte_mov128blocks(uint8_t *dst, const uint8_t *src, size_t n)
+rte_mov128blocks(uint8_t *__rte_restrict dst, const uint8_t *__rte_restrict src, size_t n)
 {
 	__m256i ymm0, ymm1, ymm2, ymm3;
 
@@ -381,41 +367,22 @@ rte_mov128blocks(uint8_t *dst, const uint8_t *src, size_t n)
 	}
 }
 
+/**
+ * Copy bytes from one location to another,
+ * locations must not overlap.
+ * Use with n > 64.
+ */
 static __rte_always_inline void *
-rte_memcpy_generic(void *dst, const void *src, size_t n)
+rte_memcpy_generic_more_than_64(void *__rte_restrict dst, const void *__rte_restrict src,
+		size_t n)
 {
 	void *ret = dst;
 	size_t dstofss;
 	size_t bits;
 
-	/**
-	 * Copy less than 16 bytes
-	 */
-	if (n < 16) {
-		return rte_mov15_or_less(dst, src, n);
-	}
-
 	/**
 	 * Fast way when copy size doesn't exceed 256 bytes
 	 */
-	if (__rte_constant(n) && n == 32) {
-		rte_mov32((uint8_t *)dst, (const uint8_t *)src);
-		return ret;
-	}
-	if (n <= 32) {
-		rte_mov16((uint8_t *)dst, (const uint8_t *)src);
-		if (__rte_constant(n) && n == 16)
-			return ret; /* avoid (harmless) duplicate copy */
-		rte_mov16((uint8_t *)dst - 16 + n,
-				(const uint8_t *)src - 16 + n);
-		return ret;
-	}
-	if (n <= 64) {
-		rte_mov32((uint8_t *)dst, (const uint8_t *)src);
-		rte_mov32((uint8_t *)dst - 32 + n,
-				(const uint8_t *)src - 32 + n);
-		return ret;
-	}
 	if (n <= 256) {
 		if (n >= 128) {
 			n -= 128;
@@ -482,7 +449,7 @@ rte_memcpy_generic(void *dst, const void *src, size_t n)
 /**
  * Macro for copying unaligned block from one location to another with constant load offset,
  * 47 bytes leftover maximum,
- * locations should not overlap.
+ * locations must not overlap.
  * Requirements:
  * - Store is aligned
  * - Load offset is <offset>, which must be immediate value within [1, 15]
@@ -542,7 +509,7 @@ rte_memcpy_generic(void *dst, const void *src, size_t n)
 /**
  * Macro for copying unaligned block from one location to another,
  * 47 bytes leftover maximum,
- * locations should not overlap.
+ * locations must not overlap.
  * Use switch here because the aligning instruction requires immediate value for shift count.
  * Requirements:
  * - Store is aligned
@@ -573,38 +540,23 @@ rte_memcpy_generic(void *dst, const void *src, size_t n)
     }                                                                 \
 }
 
+/**
+ * Copy bytes from one location to another,
+ * locations must not overlap.
+ * Use with n > 64.
+ */
 static __rte_always_inline void *
-rte_memcpy_generic(void *dst, const void *src, size_t n)
+rte_memcpy_generic_more_than_64(void *__rte_restrict dst, const void *__rte_restrict src,
+		size_t n)
 {
 	__m128i xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8;
 	void *ret = dst;
 	size_t dstofss;
 	size_t srcofs;
 
-	/**
-	 * Copy less than 16 bytes
-	 */
-	if (n < 16) {
-		return rte_mov15_or_less(dst, src, n);
-	}
-
 	/**
 	 * Fast way when copy size doesn't exceed 512 bytes
 	 */
-	if (n <= 32) {
-		rte_mov16((uint8_t *)dst, (const uint8_t *)src);
-		if (__rte_constant(n) && n == 16)
-			return ret; /* avoid (harmless) duplicate copy */
-		rte_mov16((uint8_t *)dst - 16 + n, (const uint8_t *)src - 16 + n);
-		return ret;
-	}
-	if (n <= 64) {
-		rte_mov32((uint8_t *)dst, (const uint8_t *)src);
-		if (n > 48)
-			rte_mov16((uint8_t *)dst + 32, (const uint8_t *)src + 32);
-		rte_mov16((uint8_t *)dst - 16 + n, (const uint8_t *)src - 16 + n);
-		return ret;
-	}
 	if (n <= 128) {
 		goto COPY_BLOCK_128_BACK15;
 	}
@@ -696,44 +648,17 @@ rte_memcpy_generic(void *dst, const void *src, size_t n)
 
 #endif /* __AVX512F__ */
 
+/**
+ * Copy bytes from one vector register size aligned location to another,
+ * locations must not overlap.
+ * Use with n > 64.
+ */
 static __rte_always_inline void *
-rte_memcpy_aligned(void *dst, const void *src, size_t n)
+rte_memcpy_aligned_more_than_64(void *__rte_restrict dst, const void *__rte_restrict src,
+		size_t n)
 {
 	void *ret = dst;
 
-	/* Copy size < 16 bytes */
-	if (n < 16) {
-		return rte_mov15_or_less(dst, src, n);
-	}
-
-	/* Copy 16 <= size <= 32 bytes */
-	if (__rte_constant(n) && n == 32) {
-		rte_mov32((uint8_t *)dst, (const uint8_t *)src);
-		return ret;
-	}
-	if (n <= 32) {
-		rte_mov16((uint8_t *)dst, (const uint8_t *)src);
-		if (__rte_constant(n) && n == 16)
-			return ret; /* avoid (harmless) duplicate copy */
-		rte_mov16((uint8_t *)dst - 16 + n,
-				(const uint8_t *)src - 16 + n);
-
-		return ret;
-	}
-
-	/* Copy 32 < size <= 64 bytes */
-	if (__rte_constant(n) && n == 64) {
-		rte_mov64((uint8_t *)dst, (const uint8_t *)src);
-		return ret;
-	}
-	if (n <= 64) {
-		rte_mov32((uint8_t *)dst, (const uint8_t *)src);
-		rte_mov32((uint8_t *)dst - 32 + n,
-				(const uint8_t *)src - 32 + n);
-
-		return ret;
-	}
-
 	/* Copy 64 bytes blocks */
 	for (; n > 64; n -= 64) {
 		rte_mov64((uint8_t *)dst, (const uint8_t *)src);
@@ -749,20 +674,49 @@ rte_memcpy_aligned(void *dst, const void *src, size_t n)
 }
 
 static __rte_always_inline void *
-rte_memcpy(void *dst, const void *src, size_t n)
+rte_memcpy(void *__rte_restrict dst, const void *__rte_restrict src, size_t n)
 {
+	/* Fast way when copy size doesn't exceed 64 bytes. */
+	if (n < 16)
+		return rte_mov15_or_less(dst, src, n);
+	if (n <= 32) {
+		if (__rte_constant(n) && n == 32) {
+			rte_mov32((uint8_t *)dst, (const uint8_t *)src);
+			return dst;
+		}
+		rte_mov16((uint8_t *)dst, (const uint8_t *)src);
+		if (__rte_constant(n) && n == 16)
+			return dst; /* avoid (harmless) duplicate copy */
+		rte_mov16((uint8_t *)dst - 16 + n, (const uint8_t *)src - 16 + n);
+		return dst;
+	}
+	if (n <= 64) {
+		if (__rte_constant(n) && n == 64) {
+			rte_mov64((uint8_t *)dst, (const uint8_t *)src);
+			return dst;
+		}
+#if defined RTE_MEMCPY_AVX
+		rte_mov32((uint8_t *)dst, (const uint8_t *)src);
+		rte_mov32((uint8_t *)dst - 32 + n, (const uint8_t *)src - 32 + n);
+#else /* SSE implementation */
+		rte_mov16((uint8_t *)dst + 0 * 16, (const uint8_t *)src + 0 * 16);
+		rte_mov16((uint8_t *)dst + 1 * 16, (const uint8_t *)src + 1 * 16);
+		if (n > 48)
+			rte_mov16((uint8_t *)dst + 2 * 16, (const uint8_t *)src + 2 * 16);
+		rte_mov16((uint8_t *)dst - 16 + n, (const uint8_t *)src - 16 + n);
+#endif
+		return dst;
+	}
+
+	/* Implementation for size > 64 bytes depends on alignment with vector register size. */
 	if (!(((uintptr_t)dst | (uintptr_t)src) & ALIGNMENT_MASK))
-		return rte_memcpy_aligned(dst, src, n);
+		return rte_memcpy_aligned_more_than_64(dst, src, n);
 	else
-		return rte_memcpy_generic(dst, src, n);
+		return rte_memcpy_generic_more_than_64(dst, src, n);
 }
 
 #undef ALIGNMENT_MASK
 
-#if defined(RTE_TOOLCHAIN_GCC) && (GCC_VERSION >= 100000)
-#pragma GCC diagnostic pop
-#endif
-
 #ifdef __cplusplus
 }
 #endif
-- 
2.43.0


^ permalink raw reply related

* [RFC v2 11/11] crypto/ccp: replace use of rte_atomic64 with stdatomic
From: Stephen Hemminger @ 2026-05-21 18:04 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Sunil Uttarwar
In-Reply-To: <20260521180706.678377-1-stephen@networkplumber.org>

The rte_atomicNN functions are deprecated. Replace the free
count with stdatomic.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/crypto/ccp/ccp_crypto.c | 11 +++++++----
 drivers/crypto/ccp/ccp_crypto.h |  2 +-
 drivers/crypto/ccp/ccp_dev.c    | 10 ++++++----
 drivers/crypto/ccp/ccp_dev.h    |  4 ++--
 4 files changed, 16 insertions(+), 11 deletions(-)

diff --git a/drivers/crypto/ccp/ccp_crypto.c b/drivers/crypto/ccp/ccp_crypto.c
index 5899d83bae..1800ad41c9 100644
--- a/drivers/crypto/ccp/ccp_crypto.c
+++ b/drivers/crypto/ccp/ccp_crypto.c
@@ -2683,7 +2683,8 @@ process_ops_to_enqueue(struct ccp_qp *qp,
 	b_info->cmd_q = cmd_q;
 	b_info->lsb_buf_phys = (phys_addr_t)rte_mem_virt2iova((void *)b_info->lsb_buf);
 
-	rte_atomic64_sub(&b_info->cmd_q->free_slots, slots_req);
+	rte_atomic_fetch_sub_explicit(&b_info->cmd_q->free_slots, slots_req,
+				      rte_memory_order_seq_cst);
 
 	b_info->head_offset = (uint32_t)(cmd_q->qbase_phys_addr + cmd_q->qidx *
 					 Q_DESC_SIZE);
@@ -2729,8 +2730,9 @@ process_ops_to_enqueue(struct ccp_qp *qp,
 			result = -1;
 		}
 		if (unlikely(result < 0)) {
-			rte_atomic64_add(&b_info->cmd_q->free_slots,
-					 (slots_req - b_info->desccnt));
+			rte_atomic_fetch_add_explicit(&b_info->cmd_q->free_slots,
+						      slots_req - b_info->desccnt,
+						      rte_memory_order_seq_cst);
 			break;
 		}
 		b_info->op[i] = op[i];
@@ -2914,7 +2916,8 @@ process_ops_to_dequeue(struct ccp_qp *qp,
 success:
 	*total_nb_ops = b_info->total_nb_ops;
 	nb_ops = ccp_prepare_ops(qp, op, b_info, nb_ops);
-	rte_atomic64_add(&b_info->cmd_q->free_slots, b_info->desccnt);
+	rte_atomic_fetch_add_explicit(&b_info->cmd_q->free_slots, b_info->desccnt,
+				      rte_memory_order_seq_cst);
 	b_info->desccnt = 0;
 	if (b_info->opcnt > 0) {
 		qp->b_info = b_info;
diff --git a/drivers/crypto/ccp/ccp_crypto.h b/drivers/crypto/ccp/ccp_crypto.h
index d0b417ca29..5c61b1582d 100644
--- a/drivers/crypto/ccp/ccp_crypto.h
+++ b/drivers/crypto/ccp/ccp_crypto.h
@@ -10,7 +10,7 @@
 #include <stdint.h>
 #include <string.h>
 
-#include <rte_atomic.h>
+#include <rte_stdatomic.h>
 #include <rte_byteorder.h>
 #include <rte_io.h>
 #include <rte_pci.h>
diff --git a/drivers/crypto/ccp/ccp_dev.c b/drivers/crypto/ccp/ccp_dev.c
index 5088d8ded6..a75816cdfc 100644
--- a/drivers/crypto/ccp/ccp_dev.c
+++ b/drivers/crypto/ccp/ccp_dev.c
@@ -47,14 +47,15 @@ ccp_allot_queue(struct rte_cryptodev *cdev, int slot_req)
 	priv->last_dev = dev;
 	if (dev->qidx >= dev->cmd_q_count)
 		dev->qidx = 0;
-	ret = rte_atomic64_read(&dev->cmd_q[dev->qidx].free_slots);
+	ret = rte_atomic_load_explicit(&dev->cmd_q[dev->qidx].free_slots, rte_memory_order_relaxed);
 	if (ret >= slot_req)
 		return &dev->cmd_q[dev->qidx];
 	for (i = 0; i < dev->cmd_q_count; i++) {
 		dev->qidx++;
 		if (dev->qidx >= dev->cmd_q_count)
 			dev->qidx = 0;
-		ret = rte_atomic64_read(&dev->cmd_q[dev->qidx].free_slots);
+		ret = rte_atomic_load_explicit(&dev->cmd_q[dev->qidx].free_slots,
+					       rte_memory_order_relaxed);
 		if (ret >= slot_req)
 			return &dev->cmd_q[dev->qidx];
 	}
@@ -583,8 +584,9 @@ ccp_add_device(struct ccp_device *dev)
 			CCP_LOG_ERR("queue doesn't have lsb regions");
 		cmd_q->lsb = -1;
 
-		rte_atomic64_init(&cmd_q->free_slots);
-		rte_atomic64_set(&cmd_q->free_slots, (COMMANDS_PER_QUEUE - 1));
+		rte_atomic_store_explicit(&cmd_q->free_slots,
+					  COMMANDS_PER_QUEUE - 1,
+					  rte_memory_order_seq_cst);
 		/* unused slot barrier b/w H&T */
 	}
 
diff --git a/drivers/crypto/ccp/ccp_dev.h b/drivers/crypto/ccp/ccp_dev.h
index cd63830759..0d343c2426 100644
--- a/drivers/crypto/ccp/ccp_dev.h
+++ b/drivers/crypto/ccp/ccp_dev.h
@@ -11,7 +11,7 @@
 #include <string.h>
 
 #include <bus_pci_driver.h>
-#include <rte_atomic.h>
+#include <rte_stdatomic.h>
 #include <rte_byteorder.h>
 #include <rte_io.h>
 #include <rte_pci.h>
@@ -182,7 +182,7 @@ struct __rte_cache_aligned ccp_queue {
 	struct ccp_device *dev;
 	char memz_name[RTE_MEMZONE_NAMESIZE];
 
-	rte_atomic64_t free_slots;
+	RTE_ATOMIC(uint64_t) free_slots;
 	/**< available free slots updated from enq/deq calls */
 
 	/* Queue identifier */
-- 
2.53.0


^ permalink raw reply related

* [RFC v2 10/11] net/sfc: replace rte_atomic with stdatomic
From: Stephen Hemminger @ 2026-05-21 18:04 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Andrew Rybchenko
In-Reply-To: <20260521180706.678377-1-stephen@networkplumber.org>

The rte_atomicNN functions are deprecated and need to be replaced.
Use stdatomic for the restart required flag.
Use existing ethdev helper to set link status.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/sfc/sfc.c       | 9 +++++----
 drivers/net/sfc/sfc.h       | 4 ++--
 drivers/net/sfc/sfc_port.c  | 7 +------
 drivers/net/sfc/sfc_stats.h | 2 +-
 4 files changed, 9 insertions(+), 13 deletions(-)

diff --git a/drivers/net/sfc/sfc.c b/drivers/net/sfc/sfc.c
index 69747e49ae..3470f7eed6 100644
--- a/drivers/net/sfc/sfc.c
+++ b/drivers/net/sfc/sfc.c
@@ -670,8 +670,8 @@ sfc_restart_if_required(void *arg)
 	struct sfc_adapter *sa = arg;
 
 	/* If restart is scheduled, clear the flag and do it */
-	if (rte_atomic32_cmpset((volatile uint32_t *)&sa->restart_required,
-				1, 0)) {
+	if (rte_atomic_exchange_explicit(&sa->restart_required, false,
+					 rte_memory_order_seq_cst)) {
 		sfc_adapter_lock(sa);
 		if (sa->state == SFC_ETHDEV_STARTED)
 			(void)sfc_restart(sa);
@@ -685,7 +685,8 @@ sfc_schedule_restart(struct sfc_adapter *sa)
 	int rc;
 
 	/* Schedule restart alarm if it is not scheduled yet */
-	if (!rte_atomic32_test_and_set(&sa->restart_required))
+	if (rte_atomic_exchange_explicit(&sa->restart_required, true,
+					 rte_memory_order_seq_cst))
 		return;
 
 	rc = rte_eal_alarm_set(1, sfc_restart_if_required, sa);
@@ -1292,7 +1293,7 @@ sfc_probe(struct sfc_adapter *sa)
 	SFC_ASSERT(sfc_adapter_is_locked(sa));
 
 	sa->socket_id = rte_socket_id();
-	rte_atomic32_init(&sa->restart_required);
+	sa->restart_required = false;
 
 	sfc_log_init(sa, "get family");
 	rc = sfc_efx_family(pci_dev, &mem_ebrp, &sa->family);
diff --git a/drivers/net/sfc/sfc.h b/drivers/net/sfc/sfc.h
index 629578549f..515e1e708d 100644
--- a/drivers/net/sfc/sfc.h
+++ b/drivers/net/sfc/sfc.h
@@ -17,7 +17,7 @@
 #include <ethdev_driver.h>
 #include <rte_kvargs.h>
 #include <rte_spinlock.h>
-#include <rte_atomic.h>
+#include <rte_stdatomic.h>
 
 #include "efx.h"
 
@@ -239,7 +239,7 @@ struct sfc_adapter {
 	efx_family_t			family;
 	efx_nic_t			*nic;
 	rte_spinlock_t			nic_lock;
-	rte_atomic32_t			restart_required;
+	RTE_ATOMIC(bool)		restart_required;
 
 	struct sfc_efx_mcdi		mcdi;
 	struct sfc_sriov		sriov;
diff --git a/drivers/net/sfc/sfc_port.c b/drivers/net/sfc/sfc_port.c
index 33b53f7ac8..d84648d454 100644
--- a/drivers/net/sfc/sfc_port.c
+++ b/drivers/net/sfc/sfc_port.c
@@ -121,7 +121,6 @@ sfc_port_reset_mac_stats(struct sfc_adapter *sa)
 static int
 sfc_port_init_dev_link(struct sfc_adapter *sa)
 {
-	struct rte_eth_link *dev_link = &sa->eth_dev->data->dev_link;
 	int rc;
 	efx_link_mode_t link_mode;
 	struct rte_eth_link current_link;
@@ -132,11 +131,7 @@ sfc_port_init_dev_link(struct sfc_adapter *sa)
 
 	sfc_port_link_mode_to_info(link_mode, sa->port.phy_adv_cap,
 				   &current_link);
-
-	EFX_STATIC_ASSERT(sizeof(*dev_link) == sizeof(rte_atomic64_t));
-	rte_atomic64_set((rte_atomic64_t *)dev_link,
-			 *(uint64_t *)&current_link);
-
+	rte_eth_linkstatus_set(sa->eth_dev, &current_link);
 	return 0;
 }
 
diff --git a/drivers/net/sfc/sfc_stats.h b/drivers/net/sfc/sfc_stats.h
index 597e14dab3..eaa2afd3fe 100644
--- a/drivers/net/sfc/sfc_stats.h
+++ b/drivers/net/sfc/sfc_stats.h
@@ -12,7 +12,7 @@
 
 #include <stdint.h>
 
-#include <rte_atomic.h>
+#include <rte_stdatomic.h>
 
 #include "sfc_tweak.h"
 
-- 
2.53.0


^ permalink raw reply related

* [RFC v2 09/11] net/pfe: use ethdev linkstatus helpers
From: Stephen Hemminger @ 2026-05-21 18:04 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Gagandeep Singh
In-Reply-To: <20260521180706.678377-1-stephen@networkplumber.org>

Rather than open coding with deprecated rte_atomic64,
use the existing ethdev helpers to get and set link status.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pfe/pfe_ethdev.c | 32 ++------------------------------
 1 file changed, 2 insertions(+), 30 deletions(-)

diff --git a/drivers/net/pfe/pfe_ethdev.c b/drivers/net/pfe/pfe_ethdev.c
index 1efa17539e..1b183ab1f3 100644
--- a/drivers/net/pfe/pfe_ethdev.c
+++ b/drivers/net/pfe/pfe_ethdev.c
@@ -531,34 +531,6 @@ pfe_supported_ptypes_get(struct rte_eth_dev *dev, size_t *no_of_elements)
 	return NULL;
 }
 
-static inline int
-pfe_eth_atomic_read_link_status(struct rte_eth_dev *dev,
-				struct rte_eth_link *link)
-{
-	struct rte_eth_link *dst = link;
-	struct rte_eth_link *src = &dev->data->dev_link;
-
-	if (rte_atomic64_cmpset((uint64_t *)dst, *(uint64_t *)dst,
-				*(uint64_t *)src) == 0)
-		return -1;
-
-	return 0;
-}
-
-static inline int
-pfe_eth_atomic_write_link_status(struct rte_eth_dev *dev,
-				 struct rte_eth_link *link)
-{
-	struct rte_eth_link *dst = &dev->data->dev_link;
-	struct rte_eth_link *src = link;
-
-	if (rte_atomic64_cmpset((uint64_t *)dst, *(uint64_t *)dst,
-				*(uint64_t *)src) == 0)
-		return -1;
-
-	return 0;
-}
-
 static int
 pfe_eth_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused)
 {
@@ -570,7 +542,7 @@ pfe_eth_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused)
 	memset(&old, 0, sizeof(old));
 	memset(&link, 0, sizeof(struct rte_eth_link));
 
-	pfe_eth_atomic_read_link_status(dev, &old);
+	rte_eth_linkstatus_get(dev, &old);
 
 	/* Read from PFE CDEV, status of link, if file was successfully
 	 * opened.
@@ -601,7 +573,7 @@ pfe_eth_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused)
 	link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
 	link.link_autoneg = RTE_ETH_LINK_AUTONEG;
 
-	pfe_eth_atomic_write_link_status(dev, &link);
+	rte_eth_linkstatus_set(dev, &link);
 
 	PFE_PMD_INFO("Port (%d) link is %s", dev->data->port_id,
 		     link.link_status ? "up" : "down");
-- 
2.53.0


^ permalink raw reply related

* [RFC v2 08/11] net/enic: do not use deprecated rte_atomic64
From: Stephen Hemminger @ 2026-05-21 18:04 UTC (permalink / raw)
  To: dev
  Cc: Stephen Hemminger, John Daley, Hyong Youb Kim, Bruce Richardson,
	Konstantin Ananyev
In-Reply-To: <20260521180706.678377-1-stephen@networkplumber.org>

The rte_atomic64 datatype and functions are deprecated.
This driver was only using it for error statistics where atomic
is not necessary. The DPDK PMD model is that statistics do
not have to be exact in face of contention.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/enic/enic.h               |  6 +++---
 drivers/net/enic/enic_compat.h        |  1 -
 drivers/net/enic/enic_main.c          | 17 +++++++----------
 drivers/net/enic/enic_rxtx.c          | 14 ++++++--------
 drivers/net/enic/enic_rxtx_vec_avx2.c |  4 ++--
 5 files changed, 18 insertions(+), 24 deletions(-)

diff --git a/drivers/net/enic/enic.h b/drivers/net/enic/enic.h
index 87f6b35fcd..0a8d4a29ca 100644
--- a/drivers/net/enic/enic.h
+++ b/drivers/net/enic/enic.h
@@ -59,9 +59,9 @@
 #define ENICPMD_RXQ_INTR_OFFSET 1
 
 struct enic_soft_stats {
-	rte_atomic64_t rx_nombuf;
-	rte_atomic64_t rx_packet_errors;
-	rte_atomic64_t tx_oversized;
+	uint64_t rx_nombuf;
+	uint64_t rx_packet_errors;
+	uint64_t tx_oversized;
 };
 
 struct enic_memzone_entry {
diff --git a/drivers/net/enic/enic_compat.h b/drivers/net/enic/enic_compat.h
index 7cff6831b9..3ce4299e81 100644
--- a/drivers/net/enic/enic_compat.h
+++ b/drivers/net/enic/enic_compat.h
@@ -9,7 +9,6 @@
 #include <stdio.h>
 #include <unistd.h>
 
-#include <rte_atomic.h>
 #include <rte_malloc.h>
 #include <rte_log.h>
 #include <rte_io.h>
diff --git a/drivers/net/enic/enic_main.c b/drivers/net/enic/enic_main.c
index 2696fa77d4..fb9a5754c9 100644
--- a/drivers/net/enic/enic_main.c
+++ b/drivers/net/enic/enic_main.c
@@ -83,17 +83,15 @@ static void enic_log_q_error(struct enic *enic)
 static void enic_clear_soft_stats(struct enic *enic)
 {
 	struct enic_soft_stats *soft_stats = &enic->soft_stats;
-	rte_atomic64_clear(&soft_stats->rx_nombuf);
-	rte_atomic64_clear(&soft_stats->rx_packet_errors);
-	rte_atomic64_clear(&soft_stats->tx_oversized);
+
+	memset(soft_stats, 0, sizeof(*soft_stats));
 }
 
 static void enic_init_soft_stats(struct enic *enic)
 {
 	struct enic_soft_stats *soft_stats = &enic->soft_stats;
-	rte_atomic64_init(&soft_stats->rx_nombuf);
-	rte_atomic64_init(&soft_stats->rx_packet_errors);
-	rte_atomic64_init(&soft_stats->tx_oversized);
+
+	memset(soft_stats, 0, sizeof(*soft_stats));
 	enic_clear_soft_stats(enic);
 }
 
@@ -132,7 +130,7 @@ int enic_dev_stats_get(struct enic *enic, struct rte_eth_stats *r_stats,
 	 * counted in ibytes even though truncated packets are dropped
 	 * which can make ibytes be slightly higher than it should be.
 	 */
-	rx_packet_errors = rte_atomic64_read(&soft_stats->rx_packet_errors);
+	rx_packet_errors = soft_stats->rx_packet_errors;
 	rx_truncated = rx_packet_errors - stats->rx.rx_errors;
 
 	r_stats->ipackets = stats->rx.rx_frames_ok - rx_truncated;
@@ -142,12 +140,11 @@ int enic_dev_stats_get(struct enic *enic, struct rte_eth_stats *r_stats,
 	r_stats->obytes = stats->tx.tx_bytes_ok;
 
 	r_stats->ierrors = stats->rx.rx_errors + stats->rx.rx_drop;
-	r_stats->oerrors = stats->tx.tx_errors
-			   + rte_atomic64_read(&soft_stats->tx_oversized);
+	r_stats->oerrors = stats->tx.tx_errors + soft_stats->tx_oversized;
 
 	r_stats->imissed = stats->rx.rx_no_bufs + rx_truncated;
 
-	r_stats->rx_nombuf = rte_atomic64_read(&soft_stats->rx_nombuf);
+	r_stats->rx_nombuf = soft_stats->rx_nombuf;
 	return 0;
 }
 
diff --git a/drivers/net/enic/enic_rxtx.c b/drivers/net/enic/enic_rxtx.c
index 549a153332..c87d947b93 100644
--- a/drivers/net/enic/enic_rxtx.c
+++ b/drivers/net/enic/enic_rxtx.c
@@ -112,7 +112,7 @@ enic_recv_pkts_common(void *rx_queue, struct rte_mbuf **rx_pkts,
 		/* allocate a new mbuf */
 		nmb = rte_mbuf_raw_alloc(rq->mp);
 		if (nmb == NULL) {
-			rte_atomic64_inc(&enic->soft_stats.rx_nombuf);
+			++enic->soft_stats.rx_nombuf;
 			break;
 		}
 
@@ -185,7 +185,7 @@ enic_recv_pkts_common(void *rx_queue, struct rte_mbuf **rx_pkts,
 		}
 		if (unlikely(packet_error)) {
 			rte_pktmbuf_free(first_seg);
-			rte_atomic64_inc(&enic->soft_stats.rx_packet_errors);
+			++enic->soft_stats.rx_packet_errors;
 			continue;
 		}
 
@@ -303,7 +303,7 @@ enic_noscatter_recv_pkts(void *rx_queue, struct rte_mbuf **rx_pkts,
 		if (unlikely(cqd->bytes_written_flags &
 			     CQ_ENET_RQ_DESC_FLAGS_TRUNCATED)) {
 			rte_pktmbuf_free(*rxmb++);
-			rte_atomic64_inc(&enic->soft_stats.rx_packet_errors);
+			++enic->soft_stats.rx_packet_errors;
 			cqd++;
 			continue;
 		}
@@ -505,14 +505,12 @@ uint16_t enic_xmit_pkts(void *tx_queue, struct rte_mbuf **tx_pkts,
 	uint8_t offload_mode;
 	uint16_t header_len;
 	uint64_t tso;
-	rte_atomic64_t *tx_oversized;
 
 	enic_cleanup_wq(enic, wq);
 	wq_desc_avail = vnic_wq_desc_avail(wq);
 	head_idx = wq->head_idx;
 	desc_count = wq->ring.desc_count;
 	ol_flags_mask = RTE_MBUF_F_TX_VLAN | RTE_MBUF_F_TX_IP_CKSUM | RTE_MBUF_F_TX_L4_MASK;
-	tx_oversized = &enic->soft_stats.tx_oversized;
 
 	nb_pkts = RTE_MIN(nb_pkts, ENIC_TX_XMIT_MAX);
 
@@ -527,7 +525,7 @@ uint16_t enic_xmit_pkts(void *tx_queue, struct rte_mbuf **tx_pkts,
 		/* drop packet if it's too big to send */
 		if (unlikely(!tso && pkt_len > ENIC_TX_MAX_PKT_SIZE)) {
 			rte_pktmbuf_free(tx_pkt);
-			rte_atomic64_inc(tx_oversized);
+			++enic->soft_stats.tx_oversized;
 			continue;
 		}
 
@@ -558,7 +556,7 @@ uint16_t enic_xmit_pkts(void *tx_queue, struct rte_mbuf **tx_pkts,
 			if (unlikely(header_len == 0 || ((tx_pkt->tso_segsz +
 			    header_len) > ENIC_TX_MAX_PKT_SIZE))) {
 				rte_pktmbuf_free(tx_pkt);
-				rte_atomic64_inc(tx_oversized);
+				++enic->soft_stats.tx_oversized;
 				continue;
 			}
 
@@ -681,7 +679,7 @@ static void enqueue_simple_pkts(struct rte_mbuf **pkts,
 		 */
 		if (unlikely(p->pkt_len > ENIC_TX_MAX_PKT_SIZE)) {
 			desc->length = ENIC_TX_MAX_PKT_SIZE;
-			rte_atomic64_inc(&enic->soft_stats.tx_oversized);
+			++enic->soft_stats.tx_oversized;
 		}
 		desc++;
 	}
diff --git a/drivers/net/enic/enic_rxtx_vec_avx2.c b/drivers/net/enic/enic_rxtx_vec_avx2.c
index 600efff270..53589ab788 100644
--- a/drivers/net/enic/enic_rxtx_vec_avx2.c
+++ b/drivers/net/enic/enic_rxtx_vec_avx2.c
@@ -81,7 +81,7 @@ enic_noscatter_vec_recv_pkts(void *rx_queue, struct rte_mbuf **rx_pkts,
 		if (unlikely(cqd->bytes_written_flags &
 			     CQ_ENET_RQ_DESC_FLAGS_TRUNCATED)) {
 			rte_pktmbuf_free(*rxmb++);
-			rte_atomic64_inc(&enic->soft_stats.rx_packet_errors);
+			++enic->soft_stats.rx_packet_errors;
 		} else {
 			*rx++ = rx_one(cqd, *rxmb++, enic);
 		}
@@ -761,7 +761,7 @@ enic_noscatter_vec_recv_pkts(void *rx_queue, struct rte_mbuf **rx_pkts,
 		if (unlikely(cqd->bytes_written_flags &
 			     CQ_ENET_RQ_DESC_FLAGS_TRUNCATED)) {
 			rte_pktmbuf_free(*rxmb++);
-			rte_atomic64_inc(&enic->soft_stats.rx_packet_errors);
+			++enic->soft_stats.rx_packet_errors;
 		} else {
 			*rx++ = rx_one(cqd, *rxmb++, enic);
 		}
-- 
2.53.0


^ permalink raw reply related

* [RFC v2 07/11] net/failsafe: convert to stdatomic
From: Stephen Hemminger @ 2026-05-21 18:04 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Gaetan Rivet
In-Reply-To: <20260521180706.678377-1-stephen@networkplumber.org>

The functions rte_atomic64 are deprecated, convert this
code to use stdatomic for reference count. Use the memory
order implied by naming P/V.

No need for initialization since refcnt is in space
allocated with rte_zmalloc().

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/failsafe/failsafe_ops.c     | 12 +++++-----
 drivers/net/failsafe/failsafe_private.h | 29 ++++++++++++++-----------
 drivers/net/failsafe/failsafe_rxtx.c    |  2 +-
 3 files changed, 22 insertions(+), 21 deletions(-)

diff --git a/drivers/net/failsafe/failsafe_ops.c b/drivers/net/failsafe/failsafe_ops.c
index ddc8808ebe..fcb0051777 100644
--- a/drivers/net/failsafe/failsafe_ops.c
+++ b/drivers/net/failsafe/failsafe_ops.c
@@ -11,7 +11,7 @@
 #endif
 
 #include <rte_debug.h>
-#include <rte_atomic.h>
+#include <rte_stdatomic.h>
 #include <ethdev_driver.h>
 #include <rte_malloc.h>
 #include <rte_flow.h>
@@ -440,14 +440,13 @@ fs_rx_queue_setup(struct rte_eth_dev *dev,
 	}
 	rxq = rte_zmalloc(NULL,
 			  sizeof(*rxq) +
-			  sizeof(rte_atomic64_t) * PRIV(dev)->subs_tail,
+			  sizeof(uint64_t) * PRIV(dev)->subs_tail,
 			  RTE_CACHE_LINE_SIZE);
 	if (rxq == NULL) {
 		fs_unlock(dev, 0);
 		return -ENOMEM;
 	}
-	FOREACH_SUBDEV(sdev, i, dev)
-		rte_atomic64_init(&rxq->refcnt[i]);
+
 	rxq->qid = rx_queue_id;
 	rxq->socket_id = socket_id;
 	rxq->info.mp = mb_pool;
@@ -617,14 +616,13 @@ fs_tx_queue_setup(struct rte_eth_dev *dev,
 	}
 	txq = rte_zmalloc("ethdev TX queue",
 			  sizeof(*txq) +
-			  sizeof(rte_atomic64_t) * PRIV(dev)->subs_tail,
+			  sizeof(uint64_t) * PRIV(dev)->subs_tail,
 			  RTE_CACHE_LINE_SIZE);
 	if (txq == NULL) {
 		fs_unlock(dev, 0);
 		return -ENOMEM;
 	}
-	FOREACH_SUBDEV(sdev, i, dev)
-		rte_atomic64_init(&txq->refcnt[i]);
+
 	txq->qid = tx_queue_id;
 	txq->socket_id = socket_id;
 	txq->info.conf = *tx_conf;
diff --git a/drivers/net/failsafe/failsafe_private.h b/drivers/net/failsafe/failsafe_private.h
index babea6016e..89b06f9756 100644
--- a/drivers/net/failsafe/failsafe_private.h
+++ b/drivers/net/failsafe/failsafe_private.h
@@ -10,7 +10,7 @@
 #include <sys/queue.h>
 #include <pthread.h>
 
-#include <rte_atomic.h>
+#include <rte_stdatomic.h>
 #include <dev_driver.h>
 #include <ethdev_driver.h>
 #include <rte_devargs.h>
@@ -75,7 +75,7 @@ struct rxq {
 	int event_fd;
 	unsigned int enable_events:1;
 	struct rte_eth_rxq_info info;
-	rte_atomic64_t refcnt[];
+	RTE_ATOMIC(uint64_t) refcnt[];
 };
 
 struct txq {
@@ -83,7 +83,7 @@ struct txq {
 	uint16_t qid;
 	unsigned int socket_id;
 	struct rte_eth_txq_info info;
-	rte_atomic64_t refcnt[];
+	RTE_ATOMIC(uint64_t) refcnt[];
 };
 
 struct rte_flow {
@@ -320,33 +320,36 @@ extern int failsafe_mac_from_arg;
  */
 
 /**
- * a: (rte_atomic64_t)
+ * a: _Atomic uint64_t
  */
 #define FS_ATOMIC_P(a) \
-	rte_atomic64_set(&(a), 1)
+	rte_atomic_exchange_explicit(&(a), 1, rte_memory_order_acquire)
 
 /**
- * a: (rte_atomic64_t)
+ * a: _Atomic uint64_t
  */
 #define FS_ATOMIC_V(a) \
-	rte_atomic64_set(&(a), 0)
+	rte_atomic_store_explicit(&(a), 0, rte_memory_order_release)
 
 /**
  * s: (struct sub_device *)
  * i: uint16_t qid
  */
 #define FS_ATOMIC_RX(s, i) \
-	rte_atomic64_read( \
-	 &((struct rxq *) \
-	 (fs_dev(s)->data->rx_queues[i]))->refcnt[(s)->sid])
+	rte_atomic_load_explicit( \
+		&((struct rxq *) \
+		  (fs_dev(s)->data->rx_queues[i]))->refcnt[(s)->sid], \
+		rte_memory_order_seq_cst)
+
 /**
  * s: (struct sub_device *)
  * i: uint16_t qid
  */
 #define FS_ATOMIC_TX(s, i) \
-	rte_atomic64_read( \
-	 &((struct txq *) \
-	 (fs_dev(s)->data->tx_queues[i]))->refcnt[(s)->sid])
+	rte_atomic_load_explicit( \
+		&((struct txq *) \
+		  (fs_dev(s)->data->tx_queues[i]))->refcnt[(s)->sid], \
+		rte_memory_order_seq_cst)
 
 #ifdef RTE_EXEC_ENV_FREEBSD
 #define FS_THREADID_TYPE void*
diff --git a/drivers/net/failsafe/failsafe_rxtx.c b/drivers/net/failsafe/failsafe_rxtx.c
index fe67293299..500483bda3 100644
--- a/drivers/net/failsafe/failsafe_rxtx.c
+++ b/drivers/net/failsafe/failsafe_rxtx.c
@@ -3,7 +3,7 @@
  * Copyright 2017 Mellanox Technologies, Ltd
  */
 
-#include <rte_atomic.h>
+#include <rte_stdatomic.h>
 #include <rte_debug.h>
 #include <rte_mbuf.h>
 #include <ethdev_driver.h>
-- 
2.53.0


^ permalink raw reply related

* [RFC v2 06/11] net/ena: replace use of rte_atomicNN
From: Stephen Hemminger @ 2026-05-21 18:04 UTC (permalink / raw)
  To: dev
  Cc: Stephen Hemminger, Shai Brandes, Evgeny Schemeilin, Ron Beider,
	Amit Bernstein, Wajeeh Atrash
In-Reply-To: <20260521180706.678377-1-stephen@networkplumber.org>

Convert the legacy rte_atomicNN operations to stdatomic.
* Remove variable ena_alloc_cnt is defined by not used.
  It is a leftover from previous memzone naming scheme.

* Convert the legacy rte_atomic32_t and rte_atomic32_{inc,dec,set,read}
  macros to C11 stdatomic equivalents.
  Memory ordering is kept at seq_cst,
  matching the implicit ordering of the legacy API.

* Do not use rte_atomic for statistics
  Unnecessary overhead to use atomic operations for error counts.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/ena/base/ena_plat_dpdk.h | 14 +++++++++-----
 drivers/net/ena/ena_ethdev.c         | 21 ++++++---------------
 drivers/net/ena/ena_ethdev.h         |  7 +++----
 3 files changed, 18 insertions(+), 24 deletions(-)

diff --git a/drivers/net/ena/base/ena_plat_dpdk.h b/drivers/net/ena/base/ena_plat_dpdk.h
index c84420de22..83b354d9da 100644
--- a/drivers/net/ena/base/ena_plat_dpdk.h
+++ b/drivers/net/ena/base/ena_plat_dpdk.h
@@ -40,7 +40,7 @@ typedef uint64_t dma_addr_t;
 #endif
 
 #define ENA_PRIu64 PRIu64
-#define ena_atomic32_t rte_atomic32_t
+typedef RTE_ATOMIC(int32_t) ena_atomic32_t;
 #define ena_mem_handle_t const struct rte_memzone *
 
 #define SZ_256 (256U)
@@ -267,10 +267,14 @@ ena_mem_alloc_coherent(struct rte_eth_dev_data *data, size_t size,
 #define ENA_REG_READ32(bus, reg)					       \
 	__extension__ ({ (void)(bus); rte_read32_relaxed((reg)); })
 
-#define ATOMIC32_INC(i32_ptr) rte_atomic32_inc(i32_ptr)
-#define ATOMIC32_DEC(i32_ptr) rte_atomic32_dec(i32_ptr)
-#define ATOMIC32_SET(i32_ptr, val) rte_atomic32_set(i32_ptr, val)
-#define ATOMIC32_READ(i32_ptr) rte_atomic32_read(i32_ptr)
+#define ATOMIC32_INC(i32_ptr)							\
+	rte_atomic_fetch_add_explicit((i32_ptr), 1, rte_memory_order_seq_cst)
+#define ATOMIC32_DEC(i32_ptr)							\
+	rte_atomic_fetch_sub_explicit((i32_ptr), 1, rte_memory_order_seq_cst)
+#define ATOMIC32_SET(i32_ptr, val)						\
+	rte_atomic_store_explicit((i32_ptr), (val), rte_memory_order_seq_cst)
+#define ATOMIC32_READ(i32_ptr)							\
+	rte_atomic_load_explicit((i32_ptr), rte_memory_order_seq_cst)
 
 #define msleep(x) rte_delay_us(x * 1000)
 #define udelay(x) rte_delay_us(x)
diff --git a/drivers/net/ena/ena_ethdev.c b/drivers/net/ena/ena_ethdev.c
index ea4afbc75d..e9c484456c 100644
--- a/drivers/net/ena/ena_ethdev.c
+++ b/drivers/net/ena/ena_ethdev.c
@@ -121,12 +121,6 @@ struct ena_stats {
  */
 #define ENA_DEVARG_ENABLE_FRAG_BYPASS "enable_frag_bypass"
 
-/*
- * Each rte_memzone should have unique name.
- * To satisfy it, count number of allocation and add it to name.
- */
-rte_atomic64_t ena_alloc_cnt;
-
 static const struct ena_stats ena_stats_global_strings[] = {
 	ENA_STAT_GLOBAL_ENTRY(wd_expired),
 	ENA_STAT_GLOBAL_ENTRY(dev_start),
@@ -1249,10 +1243,7 @@ static void ena_stats_restart(struct rte_eth_dev *dev)
 {
 	struct ena_adapter *adapter = dev->data->dev_private;
 
-	rte_atomic64_init(&adapter->drv_stats->ierrors);
-	rte_atomic64_init(&adapter->drv_stats->oerrors);
-	rte_atomic64_init(&adapter->drv_stats->rx_nombuf);
-	adapter->drv_stats->rx_drops = 0;
+	memset(adapter->drv_stats, 0, sizeof(struct ena_driver_stats));
 }
 
 static int ena_stats_get(struct rte_eth_dev *dev,
@@ -1289,9 +1280,9 @@ static int ena_stats_get(struct rte_eth_dev *dev,
 
 	/* Driver related stats */
 	stats->imissed = adapter->drv_stats->rx_drops;
-	stats->ierrors = rte_atomic64_read(&adapter->drv_stats->ierrors);
-	stats->oerrors = rte_atomic64_read(&adapter->drv_stats->oerrors);
-	stats->rx_nombuf = rte_atomic64_read(&adapter->drv_stats->rx_nombuf);
+	stats->ierrors = adapter->drv_stats->ierrors;
+	stats->oerrors = adapter->drv_stats->oerrors;
+	stats->rx_nombuf = adapter->drv_stats->rx_nombuf;
 
 	/* Queue statistics */
 	if (qstats) {
@@ -1887,7 +1878,7 @@ static int ena_populate_rx_queue(struct ena_ring *rxq, unsigned int count)
 	/* get resources for incoming packets */
 	rc = rte_pktmbuf_alloc_bulk(rxq->mb_pool, mbufs, count);
 	if (unlikely(rc < 0)) {
-		rte_atomic64_inc(&rxq->adapter->drv_stats->rx_nombuf);
+		++rxq->adapter->drv_stats->rx_nombuf;
 		++rxq->rx_stats.mbuf_alloc_fail;
 		PMD_RX_LOG_LINE(DEBUG, "There are not enough free buffers");
 		return 0;
@@ -3014,7 +3005,7 @@ static uint16_t eth_ena_recv_pkts(void *rx_queue, struct rte_mbuf **rx_pkts,
 
 		if (unlikely(mbuf->ol_flags &
 				(RTE_MBUF_F_RX_IP_CKSUM_BAD | RTE_MBUF_F_RX_L4_CKSUM_BAD)))
-			rte_atomic64_inc(&rx_ring->adapter->drv_stats->ierrors);
+			++rx_ring->adapter->drv_stats->ierrors;
 
 		rx_pkts[completed] = mbuf;
 		rx_ring->rx_stats.bytes += mbuf->pkt_len;
diff --git a/drivers/net/ena/ena_ethdev.h b/drivers/net/ena/ena_ethdev.h
index 3a66d79384..b204b07767 100644
--- a/drivers/net/ena/ena_ethdev.h
+++ b/drivers/net/ena/ena_ethdev.h
@@ -6,7 +6,6 @@
 #ifndef _ENA_ETHDEV_H_
 #define _ENA_ETHDEV_H_
 
-#include <rte_atomic.h>
 #include <rte_ether.h>
 #include <ethdev_driver.h>
 #include <ethdev_pci.h>
@@ -225,9 +224,9 @@ enum ena_adapter_state {
 };
 
 struct ena_driver_stats {
-	rte_atomic64_t ierrors;
-	rte_atomic64_t oerrors;
-	rte_atomic64_t rx_nombuf;
+	u64 ierrors;
+	u64 oerrors;
+	u64 rx_nombuf;
 	u64 rx_drops;
 };
 
-- 
2.53.0


^ permalink raw reply related

* [RFC v2 05/11] net/nbl: remove unused rte_atomic16 field
From: Stephen Hemminger @ 2026-05-21 18:04 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Dimon Zhao, Leon Yu, Sam Chen
In-Reply-To: <20260521180706.678377-1-stephen@networkplumber.org>

The tx_current_queue was defined as rte_atomic16_t which
is deprecated. Remove it since it was never used.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/nbl/nbl_hw/nbl_resource.h | 1 -
 1 file changed, 1 deletion(-)

diff --git a/drivers/net/nbl/nbl_hw/nbl_resource.h b/drivers/net/nbl/nbl_hw/nbl_resource.h
index bf5a9461f5..f2182ba6bc 100644
--- a/drivers/net/nbl/nbl_hw/nbl_resource.h
+++ b/drivers/net/nbl/nbl_hw/nbl_resource.h
@@ -225,7 +225,6 @@ struct nbl_res_info {
 	u16 base_qid;
 	u16 lcore_max;
 	u16 *pf_qid_to_lcore_id;
-	rte_atomic16_t tx_current_queue;
 };
 
 struct nbl_resource_mgt {
-- 
2.53.0


^ permalink raw reply related

* [RFC v2 04/11] net/bonding: use stdatomic
From: Stephen Hemminger @ 2026-05-21 18:04 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Chas Williams, Min Hu (Connor)
In-Reply-To: <20260521180706.678377-1-stephen@networkplumber.org>

The old rte_atomic16 and rte_atomic64 functions are deprecated.
Replace with rte_stdatomic for managing warning and timer flags.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/bonding/eth_bond_8023ad_private.h |  6 ++--
 drivers/net/bonding/rte_eth_bond_8023ad.c     | 35 ++++++++-----------
 2 files changed, 17 insertions(+), 24 deletions(-)

diff --git a/drivers/net/bonding/eth_bond_8023ad_private.h b/drivers/net/bonding/eth_bond_8023ad_private.h
index ab7d15f81a..dd3cf3ed26 100644
--- a/drivers/net/bonding/eth_bond_8023ad_private.h
+++ b/drivers/net/bonding/eth_bond_8023ad_private.h
@@ -9,7 +9,7 @@
 
 #include <rte_ether.h>
 #include <rte_byteorder.h>
-#include <rte_atomic.h>
+#include <rte_stdatomic.h>
 #include <rte_flow.h>
 
 #include "rte_eth_bond_8023ad.h"
@@ -140,10 +140,10 @@ struct port {
 	/** Timer which is also used as mutex. If is 0 (not running) RX marker
 	 * packet might be responded. Otherwise shall be dropped. It is zeroed in
 	 * mode 4 callback function after expire. */
-	volatile uint64_t rx_marker_timer;
+	RTE_ATOMIC(uint64_t) rx_marker_timer;
 
 	uint64_t warning_timer;
-	volatile uint16_t warnings_to_show;
+	RTE_ATOMIC(uint16_t) warnings_to_show;
 
 	/** Memory pool used to allocate slow queues */
 	struct rte_mempool *slow_pool;
diff --git a/drivers/net/bonding/rte_eth_bond_8023ad.c b/drivers/net/bonding/rte_eth_bond_8023ad.c
index ba88f6d261..cc7e4af2b9 100644
--- a/drivers/net/bonding/rte_eth_bond_8023ad.c
+++ b/drivers/net/bonding/rte_eth_bond_8023ad.c
@@ -171,27 +171,17 @@ timer_is_running(uint64_t *timer)
 static void
 set_warning_flags(struct port *port, uint16_t flags)
 {
-	int retval;
-	uint16_t old;
-	uint16_t new_flag = 0;
-
-	do {
-		old = port->warnings_to_show;
-		new_flag = old | flags;
-		retval = rte_atomic16_cmpset(&port->warnings_to_show, old, new_flag);
-	} while (unlikely(retval == 0));
+	rte_atomic_fetch_or_explicit(&port->warnings_to_show, flags, rte_memory_order_relaxed);
 }
 
 static void
 show_warnings(uint16_t member_id)
 {
 	struct port *port = &bond_mode_8023ad_ports[member_id];
-	uint8_t warnings;
-
-	do {
-		warnings = port->warnings_to_show;
-	} while (rte_atomic16_cmpset(&port->warnings_to_show, warnings, 0) == 0);
+	uint16_t warnings;
 
+	warnings = rte_atomic_exchange_explicit(&port->warnings_to_show, 0,
+						rte_memory_order_relaxed);
 	if (!warnings)
 		return;
 
@@ -1337,7 +1327,6 @@ bond_mode_8023ad_handle_slow_pkt(struct bond_dev_private *internals,
 	struct port *port = &bond_mode_8023ad_ports[member_id];
 	struct marker_header *m_hdr;
 	uint64_t marker_timer, old_marker_timer;
-	int retval;
 	uint8_t wrn, subtype;
 	/* If packet is a marker, we send response now by reusing given packet
 	 * and update only source MAC, destination MAC is multicast so don't
@@ -1354,17 +1343,19 @@ bond_mode_8023ad_handle_slow_pkt(struct bond_dev_private *internals,
 		}
 
 		/* Setup marker timer. Do it in loop in case concurrent access. */
+		old_marker_timer = rte_atomic_load_explicit(&port->rx_marker_timer,
+							    rte_memory_order_relaxed);
 		do {
-			old_marker_timer = port->rx_marker_timer;
 			if (!timer_is_expired(&old_marker_timer)) {
 				wrn = WRN_RX_MARKER_TO_FAST;
 				goto free_out;
 			}
 
 			timer_set(&marker_timer, mode4->rx_marker_timeout);
-			retval = rte_atomic64_cmpset(&port->rx_marker_timer,
-				old_marker_timer, marker_timer);
-		} while (unlikely(retval == 0));
+
+		} while (!rte_atomic_compare_exchange_weak_explicit(&port->rx_marker_timer,
+					&old_marker_timer, marker_timer,
+					rte_memory_order_seq_cst, rte_memory_order_relaxed));
 
 		m_hdr->marker.tlv_type_marker = MARKER_TLV_TYPE_RESP;
 		rte_eth_macaddr_get(member_id, &m_hdr->eth_hdr.src_addr);
@@ -1372,7 +1363,8 @@ bond_mode_8023ad_handle_slow_pkt(struct bond_dev_private *internals,
 		if (internals->mode4.dedicated_queues.enabled == 0) {
 			if (rte_ring_enqueue(port->tx_ring, pkt) != 0) {
 				/* reset timer */
-				port->rx_marker_timer = 0;
+				rte_atomic_store_explicit(&port->rx_marker_timer, 0,
+							  rte_memory_order_release);
 				wrn = WRN_TX_QUEUE_FULL;
 				goto free_out;
 			}
@@ -1386,7 +1378,8 @@ bond_mode_8023ad_handle_slow_pkt(struct bond_dev_private *internals,
 					&pkt, tx_count);
 			if (tx_count != 1) {
 				/* reset timer */
-				port->rx_marker_timer = 0;
+				rte_atomic_store_explicit(&port->rx_marker_timer, 0,
+							  rte_memory_order_release);
 				wrn = WRN_TX_QUEUE_FULL;
 				goto free_out;
 			}
-- 
2.53.0


^ permalink raw reply related

* [RFC v2 03/11] ring: use C11 atomic operations for MP/SP head/tail
From: Stephen Hemminger @ 2026-05-21 18:04 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Konstantin Ananyev, Wathsala Vithanage
In-Reply-To: <20260521180706.678377-1-stephen@networkplumber.org>

Last caller of rte_atomic32_cmpset() in lib/, blocking deprecation
of the rte_atomicNN_*() family.

Replace cmpset with rte_atomic_compare_exchange_weak_explicit(),
and convert head/tail loads/stores from implicit seq_cst to explicit
acquire/release. Matches the HTS/RTS pattern.

Acquire-load of d->head orders the subsequent load of s->tail (was
rte_smp_rmb()). Acquire-load of s->tail pairs with the release-store
of the counterpart tail in __rte_ring_update_tail(), which subsumes
the previous wmb/rmb barriers.

Weak CAS avoids arm64's hidden inner retry; the outer do-while already
loops. CAS orderings relaxed: no data published by the reservation.

The now-unused 'enqueue' parameter of __rte_ring_update_tail() is
removed; both call sites updated.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 lib/ring/rte_ring_generic_pvt.h | 65 +++++++++++++++++++++++----------
 1 file changed, 45 insertions(+), 20 deletions(-)

diff --git a/lib/ring/rte_ring_generic_pvt.h b/lib/ring/rte_ring_generic_pvt.h
index affd2d5ba7..84570fd5fc 100644
--- a/lib/ring/rte_ring_generic_pvt.h
+++ b/lib/ring/rte_ring_generic_pvt.h
@@ -23,21 +23,24 @@
  */
 static __rte_always_inline void
 __rte_ring_update_tail(struct rte_ring_headtail *ht, uint32_t old_val,
-		uint32_t new_val, uint32_t single, uint32_t enqueue)
+		uint32_t new_val, uint32_t single,
+		uint32_t enqueue __rte_unused)
 {
-	if (enqueue)
-		rte_smp_wmb();
-	else
-		rte_smp_rmb();
 	/*
 	 * If there are other enqueues/dequeues in progress that preceded us,
 	 * we need to wait for them to complete
 	 */
 	if (!single)
-		rte_wait_until_equal_32((volatile uint32_t *)(uintptr_t)&ht->tail, old_val,
-			rte_memory_order_relaxed);
-
-	ht->tail = new_val;
+		rte_wait_until_equal_32((volatile uint32_t *)(uintptr_t)&ht->tail,
+			old_val, rte_memory_order_relaxed);
+	/*
+	 * R0: Release store on the tail. Pairs with the acquire load of the
+	 * counterpart's tail at A0 in __rte_ring_headtail_move_head() on the
+	 * other side. Ensures slot operations performed by this thread (writes
+	 * for enqueue, reads for dequeue) become visible before the new tail
+	 * value is observed by the other side.
+	 */
+	rte_atomic_store_explicit(&ht->tail, new_val, rte_memory_order_release);
 }
 
 /**
@@ -76,25 +79,35 @@ __rte_ring_headtail_move_head(struct rte_ring_headtail *d,
 {
 	unsigned int max = n;
 	int success;
+	uint32_t tail;
 
 	do {
 		/* Reset n to the initial burst count */
 		n = max;
 
-		*old_head = d->head;
+		/*
+		 * Acquire on d->head and acquire on s->tail below together prevent
+		 * the two loads from being reordered (was rte_smp_rmb()) and
+		 * re-establish ordering after a failed CAS on retry.
+		 */
+		*old_head = rte_atomic_load_explicit(&d->head,
+				rte_memory_order_acquire);
 
-		/* add rmb barrier to avoid load/load reorder in weak
-		 * memory model. It is noop on x86
+		/*
+		 * A0: Acquire load on the counterpart's tail. Pairs with the
+		 * release store at R0 in __rte_ring_update_tail(), ensuring slot
+		 * operations on the other side are visible before this thread
+		 * accesses the reserved slots.
 		 */
-		rte_smp_rmb();
+		tail = rte_atomic_load_explicit(&s->tail, rte_memory_order_acquire);
 
 		/*
 		 *  The subtraction is done between two unsigned 32bits value
 		 * (the result is always modulo 32 bits even if we have
-		 * *old_head > s->tail). So 'entries' is always between 0
+		 * *old_head > tail). So 'entries' is always between 0
 		 * and capacity (which is < size).
 		 */
-		*entries = (capacity + s->tail - *old_head);
+		*entries = (capacity + tail - *old_head);
 
 		/* check that we have enough room in ring */
 		if (unlikely(n > *entries))
@@ -106,12 +119,24 @@ __rte_ring_headtail_move_head(struct rte_ring_headtail *d,
 
 		*new_head = *old_head + n;
 		if (is_st) {
-			d->head = *new_head;
+			rte_atomic_store_explicit(&d->head, *new_head, rte_memory_order_relaxed);
 			success = 1;
-		} else
-			success = rte_atomic32_cmpset(
-					(uint32_t *)(uintptr_t)&d->head,
-					*old_head, *new_head);
+		} else {
+			/*
+			 * Weak CAS: the outer do-while handles spurious
+			 * failures, so we avoid the strong variant's
+			 * internal retry (which on arm64 wraps the LL/SC
+			 * pair in a hidden inner loop).
+			 *
+			 * Relaxed on both success and failure: this CAS
+			 * does not publish data. Slot data visibility is
+			 * provided by the acquire loads above and the
+			 * release store of tail in __rte_ring_update_tail().
+			 */
+			success = rte_atomic_compare_exchange_weak_explicit(
+				&d->head, old_head, *new_head,
+				rte_memory_order_relaxed, rte_memory_order_relaxed);
+		}
 	} while (unlikely(success == 0));
 	return n;
 }
-- 
2.53.0


^ permalink raw reply related

* [RFC v2 02/11] eal: reimplement rte_smp_*mb with rte_atomic_thread_fence
From: Stephen Hemminger @ 2026-05-21 18:04 UTC (permalink / raw)
  To: dev
  Cc: Stephen Hemminger, Wathsala Vithanage, Bibo Mao,
	David Christensen, Sun Yuechi, Bruce Richardson,
	Konstantin Ananyev
In-Reply-To: <20260521180706.678377-1-stephen@networkplumber.org>

The rte_smp_mb(), rte_smp_wmb() and rte_smp_rmb() functions were
flagged as deprecated by commit 3ec965b6de12 ("doc: update atomic
operation deprecation") in 2021 but nothing came of it.
Reimplement them as inline wrappers over rte_atomic_thread_fence()
and drop the deprecation notice.
The API is preserved; only the implementation changes.

Generated code is unchanged on x86 (seq_cst keeps the lock-addl
trick, release/acquire collapse to a compiler barrier under TSO).
On arm64, release/acquire emit dmb ish instead of dmb ishst/ishld;
the difference is below measurement noise.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 doc/guides/rel_notes/deprecation.rst   |   8 --
 lib/eal/arm/include/rte_atomic_32.h    |   6 --
 lib/eal/arm/include/rte_atomic_64.h    |   6 --
 lib/eal/include/generic/rte_atomic.h   | 130 +++++--------------------
 lib/eal/loongarch/include/rte_atomic.h |   6 --
 lib/eal/ppc/include/rte_atomic.h       |   6 --
 lib/eal/riscv/include/rte_atomic.h     |   6 --
 lib/eal/x86/include/rte_atomic.h       |  33 +++----
 8 files changed, 37 insertions(+), 164 deletions(-)

diff --git a/doc/guides/rel_notes/deprecation.rst b/doc/guides/rel_notes/deprecation.rst
index 35c9b4e06c..2190419f79 100644
--- a/doc/guides/rel_notes/deprecation.rst
+++ b/doc/guides/rel_notes/deprecation.rst
@@ -47,14 +47,6 @@ Deprecation Notices
   operations must be used for patches that need to be merged in 20.08 onwards.
   This change will not introduce any performance degradation.
 
-* rte_smp_*mb: These APIs provide full barrier functionality. However, many
-  use cases do not require full barriers. To support such use cases, DPDK has
-  adopted atomic operations from
-  https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html. These
-  operations and a new wrapper ``rte_atomic_thread_fence`` instead of
-  ``__atomic_thread_fence`` must be used for patches that need to be merged in
-  20.08 onwards. This change will not introduce any performance degradation.
-
 * lib: will fix extending some enum/define breaking the ABI. There are multiple
   samples in DPDK that enum/define terminated with a ``.*MAX.*`` value which is
   used by iterators, and arrays holding these values are sized with this
diff --git a/lib/eal/arm/include/rte_atomic_32.h b/lib/eal/arm/include/rte_atomic_32.h
index 696a539fef..4115271091 100644
--- a/lib/eal/arm/include/rte_atomic_32.h
+++ b/lib/eal/arm/include/rte_atomic_32.h
@@ -17,12 +17,6 @@ extern "C" {
 
 #define	rte_rmb() __sync_synchronize()
 
-#define rte_smp_mb() rte_mb()
-
-#define rte_smp_wmb() rte_wmb()
-
-#define rte_smp_rmb() rte_rmb()
-
 #define rte_io_mb() rte_mb()
 
 #define rte_io_wmb() rte_wmb()
diff --git a/lib/eal/arm/include/rte_atomic_64.h b/lib/eal/arm/include/rte_atomic_64.h
index 9f790238df..604e777bcd 100644
--- a/lib/eal/arm/include/rte_atomic_64.h
+++ b/lib/eal/arm/include/rte_atomic_64.h
@@ -20,12 +20,6 @@ extern "C" {
 
 #define rte_rmb() asm volatile("dmb oshld" : : : "memory")
 
-#define rte_smp_mb() asm volatile("dmb ish" : : : "memory")
-
-#define rte_smp_wmb() asm volatile("dmb ishst" : : : "memory")
-
-#define rte_smp_rmb() asm volatile("dmb ishld" : : : "memory")
-
 #define rte_io_mb() rte_mb()
 
 #define rte_io_wmb() rte_wmb()
diff --git a/lib/eal/include/generic/rte_atomic.h b/lib/eal/include/generic/rte_atomic.h
index 292e52fade..1b04b43cbb 100644
--- a/lib/eal/include/generic/rte_atomic.h
+++ b/lib/eal/include/generic/rte_atomic.h
@@ -59,55 +59,25 @@ static inline void rte_rmb(void);
  *
  * Guarantees that the LOAD and STORE operations that precede the
  * rte_smp_mb() call are globally visible across the lcores
- * before the LOAD and STORE operations that follows it.
- *
- * @note
- *  This function is deprecated.
- *  It provides similar synchronization primitive as atomic fence,
- *  but has different syntax and memory ordering semantic. Hence
- *  deprecated for the simplicity of memory ordering semantics in use.
- *
- *  rte_atomic_thread_fence(rte_memory_order_acq_rel) should be used instead.
+ * before the LOAD and STORE operations that follow it.
  */
 static inline void rte_smp_mb(void);
 
 /**
  * Write memory barrier between lcores
  *
- * Guarantees that the STORE operations that precede the
- * rte_smp_wmb() call are globally visible across the lcores
- * before the STORE operations that follows it.
- *
- * @note
- *  This function is deprecated.
- *  It provides similar synchronization primitive as atomic fence,
- *  but has different syntax and memory ordering semantic. Hence
- *  deprecated for the simplicity of memory ordering semantics in use.
- *
- *  rte_atomic_thread_fence(rte_memory_order_release) should be used instead.
- *  The fence also guarantees LOAD operations that precede the call
- *  are globally visible across the lcores before the STORE operations
- *  that follows it.
+ * Guarantees that the LOAD and STORE operations that precede the
+ * rte_smp_wmb() call are globally visible across the lcores before
+ * any STORE operations that follow it.
  */
 static inline void rte_smp_wmb(void);
 
 /**
  * Read memory barrier between lcores
  *
- * Guarantees that the LOAD operations that precede the
- * rte_smp_rmb() call are globally visible across the lcores
- * before the LOAD operations that follows it.
- *
- * @note
- *  This function is deprecated.
- *  It provides similar synchronization primitive as atomic fence,
- *  but has different syntax and memory ordering semantic. Hence
- *  deprecated for the simplicity of memory ordering semantics in use.
- *
- *  rte_atomic_thread_fence(rte_memory_order_acquire) should be used instead.
- *  The fence also guarantees LOAD operations that precede the call
- *  are globally visible across the lcores before the STORE operations
- *  that follows it.
+ * Guarantees that any LOAD operations that precede the rte_smp_rmb()
+ * call complete before LOAD and STORE operations that follow it
+ * become globally visible.
  */
 static inline void rte_smp_rmb(void);
 ///@}
@@ -164,6 +134,24 @@ static inline void rte_io_rmb(void);
  */
 static inline void rte_atomic_thread_fence(rte_memory_order memorder);
 
+static __rte_always_inline void
+rte_smp_mb(void)
+{
+	rte_atomic_thread_fence(rte_memory_order_seq_cst);
+}
+
+static __rte_always_inline void
+rte_smp_wmb(void)
+{
+	rte_atomic_thread_fence(rte_memory_order_release);
+}
+
+static __rte_always_inline void
+rte_smp_rmb(void)
+{
+	rte_atomic_thread_fence(rte_memory_order_acquire);
+}
+
 /*------------------------- 16 bit atomic operations -------------------------*/
 
 #ifndef RTE_TOOLCHAIN_MSVC
@@ -184,9 +172,6 @@ static inline void rte_atomic_thread_fence(rte_memory_order memorder);
  * @return
  *   Non-zero on success; 0 on failure.
  */
-static inline int
-rte_atomic16_cmpset(volatile uint16_t *dst, uint16_t exp, uint16_t src);
-
 static inline int
 rte_atomic16_cmpset(volatile uint16_t *dst, uint16_t exp, uint16_t src)
 {
@@ -303,9 +288,6 @@ rte_atomic16_sub(rte_atomic16_t *v, int16_t dec)
  * @param v
  *   A pointer to the atomic counter.
  */
-static inline void
-rte_atomic16_inc(rte_atomic16_t *v);
-
 static inline void
 rte_atomic16_inc(rte_atomic16_t *v)
 {
@@ -318,9 +300,6 @@ rte_atomic16_inc(rte_atomic16_t *v)
  * @param v
  *   A pointer to the atomic counter.
  */
-static inline void
-rte_atomic16_dec(rte_atomic16_t *v);
-
 static inline void
 rte_atomic16_dec(rte_atomic16_t *v)
 {
@@ -379,8 +358,6 @@ rte_atomic16_sub_return(rte_atomic16_t *v, int16_t dec)
  * @return
  *   True if the result after the increment operation is 0; false otherwise.
  */
-static inline int rte_atomic16_inc_and_test(rte_atomic16_t *v);
-
 static inline int rte_atomic16_inc_and_test(rte_atomic16_t *v)
 {
 	return rte_atomic_fetch_add_explicit((volatile __rte_atomic int16_t *)&v->cnt, 1,
@@ -398,8 +375,6 @@ static inline int rte_atomic16_inc_and_test(rte_atomic16_t *v)
  * @return
  *   True if the result after the decrement operation is 0; false otherwise.
  */
-static inline int rte_atomic16_dec_and_test(rte_atomic16_t *v);
-
 static inline int rte_atomic16_dec_and_test(rte_atomic16_t *v)
 {
 	return rte_atomic_fetch_sub_explicit((volatile __rte_atomic int16_t *)&v->cnt, 1,
@@ -417,8 +392,6 @@ static inline int rte_atomic16_dec_and_test(rte_atomic16_t *v)
  * @return
  *   0 if failed; else 1, success.
  */
-static inline int rte_atomic16_test_and_set(rte_atomic16_t *v);
-
 static inline int rte_atomic16_test_and_set(rte_atomic16_t *v)
 {
 	return rte_atomic16_cmpset((volatile uint16_t *)&v->cnt, 0, 1);
@@ -453,9 +426,6 @@ static inline void rte_atomic16_clear(rte_atomic16_t *v)
  * @return
  *   Non-zero on success; 0 on failure.
  */
-static inline int
-rte_atomic32_cmpset(volatile uint32_t *dst, uint32_t exp, uint32_t src);
-
 static inline int
 rte_atomic32_cmpset(volatile uint32_t *dst, uint32_t exp, uint32_t src)
 {
@@ -572,9 +542,6 @@ rte_atomic32_sub(rte_atomic32_t *v, int32_t dec)
  * @param v
  *   A pointer to the atomic counter.
  */
-static inline void
-rte_atomic32_inc(rte_atomic32_t *v);
-
 static inline void
 rte_atomic32_inc(rte_atomic32_t *v)
 {
@@ -587,9 +554,6 @@ rte_atomic32_inc(rte_atomic32_t *v)
  * @param v
  *   A pointer to the atomic counter.
  */
-static inline void
-rte_atomic32_dec(rte_atomic32_t *v);
-
 static inline void
 rte_atomic32_dec(rte_atomic32_t *v)
 {
@@ -648,8 +612,6 @@ rte_atomic32_sub_return(rte_atomic32_t *v, int32_t dec)
  * @return
  *   True if the result after the increment operation is 0; false otherwise.
  */
-static inline int rte_atomic32_inc_and_test(rte_atomic32_t *v);
-
 static inline int rte_atomic32_inc_and_test(rte_atomic32_t *v)
 {
 	return rte_atomic_fetch_add_explicit((volatile __rte_atomic int32_t *)&v->cnt, 1,
@@ -667,8 +629,6 @@ static inline int rte_atomic32_inc_and_test(rte_atomic32_t *v)
  * @return
  *   True if the result after the decrement operation is 0; false otherwise.
  */
-static inline int rte_atomic32_dec_and_test(rte_atomic32_t *v);
-
 static inline int rte_atomic32_dec_and_test(rte_atomic32_t *v)
 {
 	return rte_atomic_fetch_sub_explicit((volatile __rte_atomic int32_t *)&v->cnt, 1,
@@ -686,8 +646,6 @@ static inline int rte_atomic32_dec_and_test(rte_atomic32_t *v)
  * @return
  *   0 if failed; else 1, success.
  */
-static inline int rte_atomic32_test_and_set(rte_atomic32_t *v);
-
 static inline int rte_atomic32_test_and_set(rte_atomic32_t *v)
 {
 	return rte_atomic32_cmpset((volatile uint32_t *)&v->cnt, 0, 1);
@@ -721,9 +679,6 @@ static inline void rte_atomic32_clear(rte_atomic32_t *v)
  * @return
  *   Non-zero on success; 0 on failure.
  */
-static inline int
-rte_atomic64_cmpset(volatile uint64_t *dst, uint64_t exp, uint64_t src);
-
 static inline int
 rte_atomic64_cmpset(volatile uint64_t *dst, uint64_t exp, uint64_t src)
 {
@@ -770,9 +725,6 @@ typedef struct {
  * @param v
  *   A pointer to the atomic counter.
  */
-static inline void
-rte_atomic64_init(rte_atomic64_t *v);
-
 static inline void
 rte_atomic64_init(rte_atomic64_t *v)
 {
@@ -798,9 +750,6 @@ rte_atomic64_init(rte_atomic64_t *v)
  * @return
  *   The value of the counter.
  */
-static inline int64_t
-rte_atomic64_read(rte_atomic64_t *v);
-
 static inline int64_t
 rte_atomic64_read(rte_atomic64_t *v)
 {
@@ -828,9 +777,6 @@ rte_atomic64_read(rte_atomic64_t *v)
  * @param new_value
  *   The new value of the counter.
  */
-static inline void
-rte_atomic64_set(rte_atomic64_t *v, int64_t new_value);
-
 static inline void
 rte_atomic64_set(rte_atomic64_t *v, int64_t new_value)
 {
@@ -856,9 +802,6 @@ rte_atomic64_set(rte_atomic64_t *v, int64_t new_value)
  * @param inc
  *   The value to be added to the counter.
  */
-static inline void
-rte_atomic64_add(rte_atomic64_t *v, int64_t inc);
-
 static inline void
 rte_atomic64_add(rte_atomic64_t *v, int64_t inc)
 {
@@ -874,9 +817,6 @@ rte_atomic64_add(rte_atomic64_t *v, int64_t inc)
  * @param dec
  *   The value to be subtracted from the counter.
  */
-static inline void
-rte_atomic64_sub(rte_atomic64_t *v, int64_t dec);
-
 static inline void
 rte_atomic64_sub(rte_atomic64_t *v, int64_t dec)
 {
@@ -890,9 +830,6 @@ rte_atomic64_sub(rte_atomic64_t *v, int64_t dec)
  * @param v
  *   A pointer to the atomic counter.
  */
-static inline void
-rte_atomic64_inc(rte_atomic64_t *v);
-
 static inline void
 rte_atomic64_inc(rte_atomic64_t *v)
 {
@@ -905,9 +842,6 @@ rte_atomic64_inc(rte_atomic64_t *v)
  * @param v
  *   A pointer to the atomic counter.
  */
-static inline void
-rte_atomic64_dec(rte_atomic64_t *v);
-
 static inline void
 rte_atomic64_dec(rte_atomic64_t *v)
 {
@@ -927,9 +861,6 @@ rte_atomic64_dec(rte_atomic64_t *v)
  * @return
  *   The value of v after the addition.
  */
-static inline int64_t
-rte_atomic64_add_return(rte_atomic64_t *v, int64_t inc);
-
 static inline int64_t
 rte_atomic64_add_return(rte_atomic64_t *v, int64_t inc)
 {
@@ -950,9 +881,6 @@ rte_atomic64_add_return(rte_atomic64_t *v, int64_t inc)
  * @return
  *   The value of v after the subtraction.
  */
-static inline int64_t
-rte_atomic64_sub_return(rte_atomic64_t *v, int64_t dec);
-
 static inline int64_t
 rte_atomic64_sub_return(rte_atomic64_t *v, int64_t dec)
 {
@@ -971,8 +899,6 @@ rte_atomic64_sub_return(rte_atomic64_t *v, int64_t dec)
  * @return
  *   True if the result after the addition is 0; false otherwise.
  */
-static inline int rte_atomic64_inc_and_test(rte_atomic64_t *v);
-
 static inline int rte_atomic64_inc_and_test(rte_atomic64_t *v)
 {
 	return rte_atomic64_add_return(v, 1) == 0;
@@ -989,8 +915,6 @@ static inline int rte_atomic64_inc_and_test(rte_atomic64_t *v)
  * @return
  *   True if the result after subtraction is 0; false otherwise.
  */
-static inline int rte_atomic64_dec_and_test(rte_atomic64_t *v);
-
 static inline int rte_atomic64_dec_and_test(rte_atomic64_t *v)
 {
 	return rte_atomic64_sub_return(v, 1) == 0;
@@ -1007,8 +931,6 @@ static inline int rte_atomic64_dec_and_test(rte_atomic64_t *v)
  * @return
  *   0 if failed; else 1, success.
  */
-static inline int rte_atomic64_test_and_set(rte_atomic64_t *v);
-
 static inline int rte_atomic64_test_and_set(rte_atomic64_t *v)
 {
 	return rte_atomic64_cmpset((volatile uint64_t *)&v->cnt, 0, 1);
@@ -1020,8 +942,6 @@ static inline int rte_atomic64_test_and_set(rte_atomic64_t *v)
  * @param v
  *   A pointer to the atomic counter.
  */
-static inline void rte_atomic64_clear(rte_atomic64_t *v);
-
 static inline void rte_atomic64_clear(rte_atomic64_t *v)
 {
 	rte_atomic64_set(v, 0);
diff --git a/lib/eal/loongarch/include/rte_atomic.h b/lib/eal/loongarch/include/rte_atomic.h
index 785a452c9e..a789e3ab4d 100644
--- a/lib/eal/loongarch/include/rte_atomic.h
+++ b/lib/eal/loongarch/include/rte_atomic.h
@@ -18,12 +18,6 @@ extern "C" {
 
 #define rte_rmb()	rte_mb()
 
-#define rte_smp_mb()	rte_mb()
-
-#define rte_smp_wmb()	rte_mb()
-
-#define rte_smp_rmb()	rte_mb()
-
 #define rte_io_mb()	rte_mb()
 
 #define rte_io_wmb()	rte_mb()
diff --git a/lib/eal/ppc/include/rte_atomic.h b/lib/eal/ppc/include/rte_atomic.h
index 64f4c3d670..0e64db2a35 100644
--- a/lib/eal/ppc/include/rte_atomic.h
+++ b/lib/eal/ppc/include/rte_atomic.h
@@ -24,12 +24,6 @@ extern "C" {
 
 #define	rte_rmb() asm volatile("sync" : : : "memory")
 
-#define rte_smp_mb() rte_mb()
-
-#define rte_smp_wmb() rte_wmb()
-
-#define rte_smp_rmb() rte_rmb()
-
 #define rte_io_mb() rte_mb()
 
 #define rte_io_wmb() rte_wmb()
diff --git a/lib/eal/riscv/include/rte_atomic.h b/lib/eal/riscv/include/rte_atomic.h
index 061b175f33..04c40e4e9b 100644
--- a/lib/eal/riscv/include/rte_atomic.h
+++ b/lib/eal/riscv/include/rte_atomic.h
@@ -23,12 +23,6 @@ extern "C" {
 
 #define rte_rmb()	asm volatile("fence r, r" : : : "memory")
 
-#define rte_smp_mb()	rte_mb()
-
-#define rte_smp_wmb()	rte_wmb()
-
-#define rte_smp_rmb()	rte_rmb()
-
 #define rte_io_mb()	asm volatile("fence iorw, iorw" : : : "memory")
 
 #define rte_io_wmb()	asm volatile("fence orw, ow" : : : "memory")
diff --git a/lib/eal/x86/include/rte_atomic.h b/lib/eal/x86/include/rte_atomic.h
index 4f05302c9f..f4d39ce4fe 100644
--- a/lib/eal/x86/include/rte_atomic.h
+++ b/lib/eal/x86/include/rte_atomic.h
@@ -23,10 +23,6 @@
 
 #define	rte_rmb() _mm_lfence()
 
-#define rte_smp_wmb() rte_compiler_barrier()
-
-#define rte_smp_rmb() rte_compiler_barrier()
-
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -63,20 +59,6 @@ extern "C" {
  * So below we use that technique for rte_smp_mb() implementation.
  */
 
-static __rte_always_inline void
-rte_smp_mb(void)
-{
-#ifdef RTE_TOOLCHAIN_MSVC
-	_mm_mfence();
-#else
-#ifdef RTE_ARCH_I686
-	asm volatile("lock addl $0, -128(%%esp); " ::: "memory");
-#else
-	asm volatile("lock addl $0, -128(%%rsp); " ::: "memory");
-#endif
-#endif
-}
-
 #define rte_io_mb() rte_mb()
 
 #define rte_io_wmb() rte_compiler_barrier()
@@ -93,10 +75,19 @@ rte_smp_mb(void)
 static __rte_always_inline void
 rte_atomic_thread_fence(rte_memory_order memorder)
 {
-	if (memorder == rte_memory_order_seq_cst)
-		rte_smp_mb();
-	else
+	if (memorder == rte_memory_order_seq_cst) {
+#ifdef RTE_TOOLCHAIN_MSVC
+		_mm_mfence();
+#else
+#ifdef RTE_ARCH_I686
+		asm volatile("lock addl $0, -128(%%esp); " ::: "memory");
+#else
+		asm volatile("lock addl $0, -128(%%rsp); " ::: "memory");
+#endif
+#endif
+	} else {
 		__rte_atomic_thread_fence(memorder);
+	}
 }
 
 #ifdef __cplusplus
-- 
2.53.0


^ permalink raw reply related

* [RFC v2 01/11] eal: use intrinsics for rte_atomic on all platforms
From: Stephen Hemminger @ 2026-05-21 18:04 UTC (permalink / raw)
  To: dev
  Cc: Stephen Hemminger, Wathsala Vithanage, Bibo Mao,
	David Christensen, Sun Yuechi, Bruce Richardson,
	Konstantin Ananyev
In-Reply-To: <20260521180706.678377-1-stephen@networkplumber.org>

Next step is to deprecate the rte_atomicNN_*() family. Rather than
maintaining both the inline asm and intrinsic fallbacks, drop the
asm paths and use intrinsics everywhere.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 lib/eal/arm/include/rte_atomic_32.h    |   4 -
 lib/eal/arm/include/rte_atomic_64.h    |   4 -
 lib/eal/include/generic/rte_atomic.h   |  76 +---------
 lib/eal/loongarch/include/rte_atomic.h |   4 -
 lib/eal/ppc/include/rte_atomic.h       | 173 -----------------------
 lib/eal/riscv/include/rte_atomic.h     |   4 -
 lib/eal/x86/include/rte_atomic.h       | 172 ----------------------
 lib/eal/x86/include/rte_atomic_32.h    | 188 -------------------------
 lib/eal/x86/include/rte_atomic_64.h    | 157 ---------------------
 9 files changed, 6 insertions(+), 776 deletions(-)

diff --git a/lib/eal/arm/include/rte_atomic_32.h b/lib/eal/arm/include/rte_atomic_32.h
index 0b9a0dfa30..696a539fef 100644
--- a/lib/eal/arm/include/rte_atomic_32.h
+++ b/lib/eal/arm/include/rte_atomic_32.h
@@ -5,10 +5,6 @@
 #ifndef _RTE_ATOMIC_ARM32_H_
 #define _RTE_ATOMIC_ARM32_H_
 
-#ifndef RTE_FORCE_INTRINSICS
-#  error Platform must be built with RTE_FORCE_INTRINSICS
-#endif
-
 #include "generic/rte_atomic.h"
 
 #ifdef __cplusplus
diff --git a/lib/eal/arm/include/rte_atomic_64.h b/lib/eal/arm/include/rte_atomic_64.h
index 181bb60929..9f790238df 100644
--- a/lib/eal/arm/include/rte_atomic_64.h
+++ b/lib/eal/arm/include/rte_atomic_64.h
@@ -6,10 +6,6 @@
 #ifndef _RTE_ATOMIC_ARM64_H_
 #define _RTE_ATOMIC_ARM64_H_
 
-#ifndef RTE_FORCE_INTRINSICS
-#  error Platform must be built with RTE_FORCE_INTRINSICS
-#endif
-
 #include "generic/rte_atomic.h"
 #include <rte_branch_prediction.h>
 #include <rte_debug.h>
diff --git a/lib/eal/include/generic/rte_atomic.h b/lib/eal/include/generic/rte_atomic.h
index 0a4f3f8528..292e52fade 100644
--- a/lib/eal/include/generic/rte_atomic.h
+++ b/lib/eal/include/generic/rte_atomic.h
@@ -187,13 +187,11 @@ static inline void rte_atomic_thread_fence(rte_memory_order memorder);
 static inline int
 rte_atomic16_cmpset(volatile uint16_t *dst, uint16_t exp, uint16_t src);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline int
 rte_atomic16_cmpset(volatile uint16_t *dst, uint16_t exp, uint16_t src)
 {
 	return __sync_bool_compare_and_swap(dst, exp, src);
 }
-#endif
 
 /**
  * Atomic exchange.
@@ -211,15 +209,11 @@ rte_atomic16_cmpset(volatile uint16_t *dst, uint16_t exp, uint16_t src)
  *   The original value at that location
  */
 static inline uint16_t
-rte_atomic16_exchange(volatile uint16_t *dst, uint16_t val);
-
-#ifdef RTE_FORCE_INTRINSICS
-static inline uint16_t
 rte_atomic16_exchange(volatile uint16_t *dst, uint16_t val)
 {
-	return rte_atomic_exchange_explicit(dst, val, rte_memory_order_seq_cst);
+	return rte_atomic_exchange_explicit((volatile __rte_atomic uint16_t *)dst,
+		val, rte_memory_order_seq_cst);
 }
-#endif
 
 /**
  * The atomic counter structure.
@@ -312,13 +306,11 @@ rte_atomic16_sub(rte_atomic16_t *v, int16_t dec)
 static inline void
 rte_atomic16_inc(rte_atomic16_t *v);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline void
 rte_atomic16_inc(rte_atomic16_t *v)
 {
 	rte_atomic16_add(v, 1);
 }
-#endif
 
 /**
  * Atomically decrement a counter by one.
@@ -329,13 +321,11 @@ rte_atomic16_inc(rte_atomic16_t *v)
 static inline void
 rte_atomic16_dec(rte_atomic16_t *v);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline void
 rte_atomic16_dec(rte_atomic16_t *v)
 {
 	rte_atomic16_sub(v, 1);
 }
-#endif
 
 /**
  * Atomically add a 16-bit value to a counter and return the result.
@@ -391,13 +381,11 @@ rte_atomic16_sub_return(rte_atomic16_t *v, int16_t dec)
  */
 static inline int rte_atomic16_inc_and_test(rte_atomic16_t *v);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline int rte_atomic16_inc_and_test(rte_atomic16_t *v)
 {
 	return rte_atomic_fetch_add_explicit((volatile __rte_atomic int16_t *)&v->cnt, 1,
 		rte_memory_order_seq_cst) + 1 == 0;
 }
-#endif
 
 /**
  * Atomically decrement a 16-bit counter by one and test.
@@ -412,13 +400,11 @@ static inline int rte_atomic16_inc_and_test(rte_atomic16_t *v)
  */
 static inline int rte_atomic16_dec_and_test(rte_atomic16_t *v);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline int rte_atomic16_dec_and_test(rte_atomic16_t *v)
 {
 	return rte_atomic_fetch_sub_explicit((volatile __rte_atomic int16_t *)&v->cnt, 1,
 		rte_memory_order_seq_cst) - 1 == 0;
 }
-#endif
 
 /**
  * Atomically test and set a 16-bit atomic counter.
@@ -433,12 +419,10 @@ static inline int rte_atomic16_dec_and_test(rte_atomic16_t *v)
  */
 static inline int rte_atomic16_test_and_set(rte_atomic16_t *v);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline int rte_atomic16_test_and_set(rte_atomic16_t *v)
 {
 	return rte_atomic16_cmpset((volatile uint16_t *)&v->cnt, 0, 1);
 }
-#endif
 
 /**
  * Atomically set a 16-bit counter to 0.
@@ -472,13 +456,11 @@ static inline void rte_atomic16_clear(rte_atomic16_t *v)
 static inline int
 rte_atomic32_cmpset(volatile uint32_t *dst, uint32_t exp, uint32_t src);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline int
 rte_atomic32_cmpset(volatile uint32_t *dst, uint32_t exp, uint32_t src)
 {
 	return __sync_bool_compare_and_swap(dst, exp, src);
 }
-#endif
 
 /**
  * Atomic exchange.
@@ -496,15 +478,11 @@ rte_atomic32_cmpset(volatile uint32_t *dst, uint32_t exp, uint32_t src)
  *   The original value at that location
  */
 static inline uint32_t
-rte_atomic32_exchange(volatile uint32_t *dst, uint32_t val);
-
-#ifdef RTE_FORCE_INTRINSICS
-static inline uint32_t
 rte_atomic32_exchange(volatile uint32_t *dst, uint32_t val)
 {
-	return rte_atomic_exchange_explicit(dst, val, rte_memory_order_seq_cst);
+	return rte_atomic_exchange_explicit((volatile __rte_atomic uint32_t *)dst,
+					    val, rte_memory_order_seq_cst);
 }
-#endif
 
 /**
  * The atomic counter structure.
@@ -597,13 +575,11 @@ rte_atomic32_sub(rte_atomic32_t *v, int32_t dec)
 static inline void
 rte_atomic32_inc(rte_atomic32_t *v);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline void
 rte_atomic32_inc(rte_atomic32_t *v)
 {
 	rte_atomic32_add(v, 1);
 }
-#endif
 
 /**
  * Atomically decrement a counter by one.
@@ -614,13 +590,11 @@ rte_atomic32_inc(rte_atomic32_t *v)
 static inline void
 rte_atomic32_dec(rte_atomic32_t *v);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline void
 rte_atomic32_dec(rte_atomic32_t *v)
 {
 	rte_atomic32_sub(v,1);
 }
-#endif
 
 /**
  * Atomically add a 32-bit value to a counter and return the result.
@@ -676,13 +650,11 @@ rte_atomic32_sub_return(rte_atomic32_t *v, int32_t dec)
  */
 static inline int rte_atomic32_inc_and_test(rte_atomic32_t *v);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline int rte_atomic32_inc_and_test(rte_atomic32_t *v)
 {
 	return rte_atomic_fetch_add_explicit((volatile __rte_atomic int32_t *)&v->cnt, 1,
 		rte_memory_order_seq_cst) + 1 == 0;
 }
-#endif
 
 /**
  * Atomically decrement a 32-bit counter by one and test.
@@ -697,13 +669,11 @@ static inline int rte_atomic32_inc_and_test(rte_atomic32_t *v)
  */
 static inline int rte_atomic32_dec_and_test(rte_atomic32_t *v);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline int rte_atomic32_dec_and_test(rte_atomic32_t *v)
 {
 	return rte_atomic_fetch_sub_explicit((volatile __rte_atomic int32_t *)&v->cnt, 1,
 		rte_memory_order_seq_cst) - 1 == 0;
 }
-#endif
 
 /**
  * Atomically test and set a 32-bit atomic counter.
@@ -718,12 +688,10 @@ static inline int rte_atomic32_dec_and_test(rte_atomic32_t *v)
  */
 static inline int rte_atomic32_test_and_set(rte_atomic32_t *v);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline int rte_atomic32_test_and_set(rte_atomic32_t *v)
 {
 	return rte_atomic32_cmpset((volatile uint32_t *)&v->cnt, 0, 1);
 }
-#endif
 
 /**
  * Atomically set a 32-bit counter to 0.
@@ -756,13 +724,11 @@ static inline void rte_atomic32_clear(rte_atomic32_t *v)
 static inline int
 rte_atomic64_cmpset(volatile uint64_t *dst, uint64_t exp, uint64_t src);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline int
 rte_atomic64_cmpset(volatile uint64_t *dst, uint64_t exp, uint64_t src)
 {
 	return __sync_bool_compare_and_swap(dst, exp, src);
 }
-#endif
 
 /**
  * Atomic exchange.
@@ -780,15 +746,11 @@ rte_atomic64_cmpset(volatile uint64_t *dst, uint64_t exp, uint64_t src)
  *   The original value at that location
  */
 static inline uint64_t
-rte_atomic64_exchange(volatile uint64_t *dst, uint64_t val);
-
-#ifdef RTE_FORCE_INTRINSICS
-static inline uint64_t
 rte_atomic64_exchange(volatile uint64_t *dst, uint64_t val)
 {
-	return rte_atomic_exchange_explicit(dst, val, rte_memory_order_seq_cst);
+	return rte_atomic_exchange_explicit((volatile __rte_atomic uint64_t *)dst,
+					    val, rte_memory_order_seq_cst);
 }
-#endif
 
 /**
  * The atomic counter structure.
@@ -811,7 +773,6 @@ typedef struct {
 static inline void
 rte_atomic64_init(rte_atomic64_t *v);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline void
 rte_atomic64_init(rte_atomic64_t *v)
 {
@@ -828,7 +789,6 @@ rte_atomic64_init(rte_atomic64_t *v)
 	}
 #endif
 }
-#endif
 
 /**
  * Atomically read a 64-bit counter.
@@ -841,7 +801,6 @@ rte_atomic64_init(rte_atomic64_t *v)
 static inline int64_t
 rte_atomic64_read(rte_atomic64_t *v);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline int64_t
 rte_atomic64_read(rte_atomic64_t *v)
 {
@@ -860,7 +819,6 @@ rte_atomic64_read(rte_atomic64_t *v)
 	return tmp;
 #endif
 }
-#endif
 
 /**
  * Atomically set a 64-bit counter.
@@ -873,7 +831,6 @@ rte_atomic64_read(rte_atomic64_t *v)
 static inline void
 rte_atomic64_set(rte_atomic64_t *v, int64_t new_value);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline void
 rte_atomic64_set(rte_atomic64_t *v, int64_t new_value)
 {
@@ -890,7 +847,6 @@ rte_atomic64_set(rte_atomic64_t *v, int64_t new_value)
 	}
 #endif
 }
-#endif
 
 /**
  * Atomically add a 64-bit value to a counter.
@@ -903,14 +859,12 @@ rte_atomic64_set(rte_atomic64_t *v, int64_t new_value)
 static inline void
 rte_atomic64_add(rte_atomic64_t *v, int64_t inc);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline void
 rte_atomic64_add(rte_atomic64_t *v, int64_t inc)
 {
 	rte_atomic_fetch_add_explicit((volatile __rte_atomic int64_t *)&v->cnt, inc,
 		rte_memory_order_seq_cst);
 }
-#endif
 
 /**
  * Atomically subtract a 64-bit value from a counter.
@@ -923,14 +877,12 @@ rte_atomic64_add(rte_atomic64_t *v, int64_t inc)
 static inline void
 rte_atomic64_sub(rte_atomic64_t *v, int64_t dec);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline void
 rte_atomic64_sub(rte_atomic64_t *v, int64_t dec)
 {
 	rte_atomic_fetch_sub_explicit((volatile __rte_atomic int64_t *)&v->cnt, dec,
 		rte_memory_order_seq_cst);
 }
-#endif
 
 /**
  * Atomically increment a 64-bit counter by one and test.
@@ -941,13 +893,11 @@ rte_atomic64_sub(rte_atomic64_t *v, int64_t dec)
 static inline void
 rte_atomic64_inc(rte_atomic64_t *v);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline void
 rte_atomic64_inc(rte_atomic64_t *v)
 {
 	rte_atomic64_add(v, 1);
 }
-#endif
 
 /**
  * Atomically decrement a 64-bit counter by one and test.
@@ -958,13 +908,11 @@ rte_atomic64_inc(rte_atomic64_t *v)
 static inline void
 rte_atomic64_dec(rte_atomic64_t *v);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline void
 rte_atomic64_dec(rte_atomic64_t *v)
 {
 	rte_atomic64_sub(v, 1);
 }
-#endif
 
 /**
  * Add a 64-bit value to an atomic counter and return the result.
@@ -982,14 +930,12 @@ rte_atomic64_dec(rte_atomic64_t *v)
 static inline int64_t
 rte_atomic64_add_return(rte_atomic64_t *v, int64_t inc);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline int64_t
 rte_atomic64_add_return(rte_atomic64_t *v, int64_t inc)
 {
 	return rte_atomic_fetch_add_explicit((volatile __rte_atomic int64_t *)&v->cnt, inc,
 		rte_memory_order_seq_cst) + inc;
 }
-#endif
 
 /**
  * Subtract a 64-bit value from an atomic counter and return the result.
@@ -1007,14 +953,12 @@ rte_atomic64_add_return(rte_atomic64_t *v, int64_t inc)
 static inline int64_t
 rte_atomic64_sub_return(rte_atomic64_t *v, int64_t dec);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline int64_t
 rte_atomic64_sub_return(rte_atomic64_t *v, int64_t dec)
 {
 	return rte_atomic_fetch_sub_explicit((volatile __rte_atomic int64_t *)&v->cnt, dec,
 		rte_memory_order_seq_cst) - dec;
 }
-#endif
 
 /**
  * Atomically increment a 64-bit counter by one and test.
@@ -1029,12 +973,10 @@ rte_atomic64_sub_return(rte_atomic64_t *v, int64_t dec)
  */
 static inline int rte_atomic64_inc_and_test(rte_atomic64_t *v);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline int rte_atomic64_inc_and_test(rte_atomic64_t *v)
 {
 	return rte_atomic64_add_return(v, 1) == 0;
 }
-#endif
 
 /**
  * Atomically decrement a 64-bit counter by one and test.
@@ -1049,12 +991,10 @@ static inline int rte_atomic64_inc_and_test(rte_atomic64_t *v)
  */
 static inline int rte_atomic64_dec_and_test(rte_atomic64_t *v);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline int rte_atomic64_dec_and_test(rte_atomic64_t *v)
 {
 	return rte_atomic64_sub_return(v, 1) == 0;
 }
-#endif
 
 /**
  * Atomically test and set a 64-bit atomic counter.
@@ -1069,12 +1009,10 @@ static inline int rte_atomic64_dec_and_test(rte_atomic64_t *v)
  */
 static inline int rte_atomic64_test_and_set(rte_atomic64_t *v);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline int rte_atomic64_test_and_set(rte_atomic64_t *v)
 {
 	return rte_atomic64_cmpset((volatile uint64_t *)&v->cnt, 0, 1);
 }
-#endif
 
 /**
  * Atomically set a 64-bit counter to 0.
@@ -1084,12 +1022,10 @@ static inline int rte_atomic64_test_and_set(rte_atomic64_t *v)
  */
 static inline void rte_atomic64_clear(rte_atomic64_t *v);
 
-#ifdef RTE_FORCE_INTRINSICS
 static inline void rte_atomic64_clear(rte_atomic64_t *v)
 {
 	rte_atomic64_set(v, 0);
 }
-#endif
 
 #endif
 
diff --git a/lib/eal/loongarch/include/rte_atomic.h b/lib/eal/loongarch/include/rte_atomic.h
index c8066a4612..785a452c9e 100644
--- a/lib/eal/loongarch/include/rte_atomic.h
+++ b/lib/eal/loongarch/include/rte_atomic.h
@@ -5,10 +5,6 @@
 #ifndef RTE_ATOMIC_LOONGARCH_H
 #define RTE_ATOMIC_LOONGARCH_H
 
-#ifndef RTE_FORCE_INTRINSICS
-#  error Platform must be built with RTE_FORCE_INTRINSICS
-#endif
-
 #include <rte_common.h>
 #include "generic/rte_atomic.h"
 
diff --git a/lib/eal/ppc/include/rte_atomic.h b/lib/eal/ppc/include/rte_atomic.h
index 10acc238f9..64f4c3d670 100644
--- a/lib/eal/ppc/include/rte_atomic.h
+++ b/lib/eal/ppc/include/rte_atomic.h
@@ -43,179 +43,6 @@ rte_atomic_thread_fence(rte_memory_order memorder)
 }
 
 /*------------------------- 16 bit atomic operations -------------------------*/
-#ifndef RTE_FORCE_INTRINSICS
-static inline int
-rte_atomic16_cmpset(volatile uint16_t *dst, uint16_t exp, uint16_t src)
-{
-	return rte_atomic_compare_exchange_strong_explicit(dst, &exp, src, rte_memory_order_acquire,
-		rte_memory_order_acquire) ? 1 : 0;
-}
-
-static inline int rte_atomic16_test_and_set(rte_atomic16_t *v)
-{
-	return rte_atomic16_cmpset((volatile uint16_t *)&v->cnt, 0, 1);
-}
-
-static inline void
-rte_atomic16_inc(rte_atomic16_t *v)
-{
-	rte_atomic_fetch_add_explicit(&v->cnt, 1, rte_memory_order_acquire);
-}
-
-static inline void
-rte_atomic16_dec(rte_atomic16_t *v)
-{
-	rte_atomic_fetch_sub_explicit(&v->cnt, 1, rte_memory_order_acquire);
-}
-
-static inline int rte_atomic16_inc_and_test(rte_atomic16_t *v)
-{
-	return rte_atomic_fetch_add_explicit(&v->cnt, 1, rte_memory_order_acquire) + 1 == 0;
-}
-
-static inline int rte_atomic16_dec_and_test(rte_atomic16_t *v)
-{
-	return rte_atomic_fetch_sub_explicit(&v->cnt, 1, rte_memory_order_acquire) - 1 == 0;
-}
-
-static inline uint16_t
-rte_atomic16_exchange(volatile uint16_t *dst, uint16_t val)
-{
-	return __atomic_exchange_2(dst, val, rte_memory_order_seq_cst);
-}
-
-/*------------------------- 32 bit atomic operations -------------------------*/
-
-static inline int
-rte_atomic32_cmpset(volatile uint32_t *dst, uint32_t exp, uint32_t src)
-{
-	return rte_atomic_compare_exchange_strong_explicit(dst, &exp, src, rte_memory_order_acquire,
-		rte_memory_order_acquire) ? 1 : 0;
-}
-
-static inline int rte_atomic32_test_and_set(rte_atomic32_t *v)
-{
-	return rte_atomic32_cmpset((volatile uint32_t *)&v->cnt, 0, 1);
-}
-
-static inline void
-rte_atomic32_inc(rte_atomic32_t *v)
-{
-	rte_atomic_fetch_add_explicit(&v->cnt, 1, rte_memory_order_acquire);
-}
-
-static inline void
-rte_atomic32_dec(rte_atomic32_t *v)
-{
-	rte_atomic_fetch_sub_explicit(&v->cnt, 1, rte_memory_order_acquire);
-}
-
-static inline int rte_atomic32_inc_and_test(rte_atomic32_t *v)
-{
-	return rte_atomic_fetch_add_explicit(&v->cnt, 1, rte_memory_order_acquire) + 1 == 0;
-}
-
-static inline int rte_atomic32_dec_and_test(rte_atomic32_t *v)
-{
-	return rte_atomic_fetch_sub_explicit(&v->cnt, 1, rte_memory_order_acquire) - 1 == 0;
-}
-
-static inline uint32_t
-rte_atomic32_exchange(volatile uint32_t *dst, uint32_t val)
-{
-	return __atomic_exchange_4(dst, val, rte_memory_order_seq_cst);
-}
-
-/*------------------------- 64 bit atomic operations -------------------------*/
-
-static inline int
-rte_atomic64_cmpset(volatile uint64_t *dst, uint64_t exp, uint64_t src)
-{
-	return rte_atomic_compare_exchange_strong_explicit(dst, &exp, src, rte_memory_order_acquire,
-		rte_memory_order_acquire) ? 1 : 0;
-}
-
-static inline void
-rte_atomic64_init(rte_atomic64_t *v)
-{
-	v->cnt = 0;
-}
-
-static inline int64_t
-rte_atomic64_read(rte_atomic64_t *v)
-{
-	return v->cnt;
-}
-
-static inline void
-rte_atomic64_set(rte_atomic64_t *v, int64_t new_value)
-{
-	v->cnt = new_value;
-}
-
-static inline void
-rte_atomic64_add(rte_atomic64_t *v, int64_t inc)
-{
-	rte_atomic_fetch_add_explicit(&v->cnt, inc, rte_memory_order_acquire);
-}
-
-static inline void
-rte_atomic64_sub(rte_atomic64_t *v, int64_t dec)
-{
-	rte_atomic_fetch_sub_explicit(&v->cnt, dec, rte_memory_order_acquire);
-}
-
-static inline void
-rte_atomic64_inc(rte_atomic64_t *v)
-{
-	rte_atomic_fetch_add_explicit(&v->cnt, 1, rte_memory_order_acquire);
-}
-
-static inline void
-rte_atomic64_dec(rte_atomic64_t *v)
-{
-	rte_atomic_fetch_sub_explicit(&v->cnt, 1, rte_memory_order_acquire);
-}
-
-static inline int64_t
-rte_atomic64_add_return(rte_atomic64_t *v, int64_t inc)
-{
-	return rte_atomic_fetch_add_explicit(&v->cnt, inc, rte_memory_order_acquire) + inc;
-}
-
-static inline int64_t
-rte_atomic64_sub_return(rte_atomic64_t *v, int64_t dec)
-{
-	return rte_atomic_fetch_sub_explicit(&v->cnt, dec, rte_memory_order_acquire) - dec;
-}
-
-static inline int rte_atomic64_inc_and_test(rte_atomic64_t *v)
-{
-	return rte_atomic_fetch_add_explicit(&v->cnt, 1, rte_memory_order_acquire) + 1 == 0;
-}
-
-static inline int rte_atomic64_dec_and_test(rte_atomic64_t *v)
-{
-	return rte_atomic_fetch_sub_explicit(&v->cnt, 1, rte_memory_order_acquire) - 1 == 0;
-}
-
-static inline int rte_atomic64_test_and_set(rte_atomic64_t *v)
-{
-	return rte_atomic64_cmpset((volatile uint64_t *)&v->cnt, 0, 1);
-}
-
-static inline void rte_atomic64_clear(rte_atomic64_t *v)
-{
-	v->cnt = 0;
-}
-
-static inline uint64_t
-rte_atomic64_exchange(volatile uint64_t *dst, uint64_t val)
-{
-	return __atomic_exchange_8(dst, val, rte_memory_order_seq_cst);
-}
-
-#endif
 
 #ifdef __cplusplus
 }
diff --git a/lib/eal/riscv/include/rte_atomic.h b/lib/eal/riscv/include/rte_atomic.h
index 66346ad474..061b175f33 100644
--- a/lib/eal/riscv/include/rte_atomic.h
+++ b/lib/eal/riscv/include/rte_atomic.h
@@ -8,10 +8,6 @@
 #ifndef RTE_ATOMIC_RISCV_H
 #define RTE_ATOMIC_RISCV_H
 
-#ifndef RTE_FORCE_INTRINSICS
-#  error Platform must be built with RTE_FORCE_INTRINSICS
-#endif
-
 #include <stdint.h>
 #include <rte_common.h>
 #include <rte_config.h>
diff --git a/lib/eal/x86/include/rte_atomic.h b/lib/eal/x86/include/rte_atomic.h
index e071e4234e..4f05302c9f 100644
--- a/lib/eal/x86/include/rte_atomic.h
+++ b/lib/eal/x86/include/rte_atomic.h
@@ -111,178 +111,6 @@ rte_atomic_thread_fence(rte_memory_order memorder)
 extern "C" {
 #endif
 
-#ifndef RTE_FORCE_INTRINSICS
-static inline int
-rte_atomic16_cmpset(volatile uint16_t *dst, uint16_t exp, uint16_t src)
-{
-	uint8_t res;
-
-	asm volatile(
-			MPLOCKED
-			"cmpxchgw %[src], %[dst];"
-			"sete %[res];"
-			: [res] "=a" (res),     /* output */
-			  [dst] "=m" (*dst)
-			: [src] "r" (src),      /* input */
-			  "a" (exp),
-			  "m" (*dst)
-			: "memory");            /* no-clobber list */
-	return res;
-}
-
-static inline uint16_t
-rte_atomic16_exchange(volatile uint16_t *dst, uint16_t val)
-{
-	asm volatile(
-			MPLOCKED
-			"xchgw %0, %1;"
-			: "=r" (val), "=m" (*dst)
-			: "0" (val),  "m" (*dst)
-			: "memory");         /* no-clobber list */
-	return val;
-}
-
-static inline int rte_atomic16_test_and_set(rte_atomic16_t *v)
-{
-	return rte_atomic16_cmpset((volatile uint16_t *)&v->cnt, 0, 1);
-}
-
-static inline void
-rte_atomic16_inc(rte_atomic16_t *v)
-{
-	asm volatile(
-			MPLOCKED
-			"incw %[cnt]"
-			: [cnt] "=m" (v->cnt)   /* output */
-			: "m" (v->cnt)          /* input */
-			);
-}
-
-static inline void
-rte_atomic16_dec(rte_atomic16_t *v)
-{
-	asm volatile(
-			MPLOCKED
-			"decw %[cnt]"
-			: [cnt] "=m" (v->cnt)   /* output */
-			: "m" (v->cnt)          /* input */
-			);
-}
-
-static inline int rte_atomic16_inc_and_test(rte_atomic16_t *v)
-{
-	uint8_t ret;
-
-	asm volatile(
-			MPLOCKED
-			"incw %[cnt] ; "
-			"sete %[ret]"
-			: [cnt] "+m" (v->cnt),  /* output */
-			  [ret] "=qm" (ret)
-			);
-	return ret != 0;
-}
-
-static inline int rte_atomic16_dec_and_test(rte_atomic16_t *v)
-{
-	uint8_t ret;
-
-	asm volatile(MPLOCKED
-			"decw %[cnt] ; "
-			"sete %[ret]"
-			: [cnt] "+m" (v->cnt),  /* output */
-			  [ret] "=qm" (ret)
-			);
-	return ret != 0;
-}
-
-/*------------------------- 32 bit atomic operations -------------------------*/
-
-static inline int
-rte_atomic32_cmpset(volatile uint32_t *dst, uint32_t exp, uint32_t src)
-{
-	uint8_t res;
-
-	asm volatile(
-			MPLOCKED
-			"cmpxchgl %[src], %[dst];"
-			"sete %[res];"
-			: [res] "=a" (res),     /* output */
-			  [dst] "=m" (*dst)
-			: [src] "r" (src),      /* input */
-			  "a" (exp),
-			  "m" (*dst)
-			: "memory");            /* no-clobber list */
-	return res;
-}
-
-static inline uint32_t
-rte_atomic32_exchange(volatile uint32_t *dst, uint32_t val)
-{
-	asm volatile(
-			MPLOCKED
-			"xchgl %0, %1;"
-			: "=r" (val), "=m" (*dst)
-			: "0" (val),  "m" (*dst)
-			: "memory");         /* no-clobber list */
-	return val;
-}
-
-static inline int rte_atomic32_test_and_set(rte_atomic32_t *v)
-{
-	return rte_atomic32_cmpset((volatile uint32_t *)&v->cnt, 0, 1);
-}
-
-static inline void
-rte_atomic32_inc(rte_atomic32_t *v)
-{
-	asm volatile(
-			MPLOCKED
-			"incl %[cnt]"
-			: [cnt] "=m" (v->cnt)   /* output */
-			: "m" (v->cnt)          /* input */
-			);
-}
-
-static inline void
-rte_atomic32_dec(rte_atomic32_t *v)
-{
-	asm volatile(
-			MPLOCKED
-			"decl %[cnt]"
-			: [cnt] "=m" (v->cnt)   /* output */
-			: "m" (v->cnt)          /* input */
-			);
-}
-
-static inline int rte_atomic32_inc_and_test(rte_atomic32_t *v)
-{
-	uint8_t ret;
-
-	asm volatile(
-			MPLOCKED
-			"incl %[cnt] ; "
-			"sete %[ret]"
-			: [cnt] "+m" (v->cnt),  /* output */
-			  [ret] "=qm" (ret)
-			);
-	return ret != 0;
-}
-
-static inline int rte_atomic32_dec_and_test(rte_atomic32_t *v)
-{
-	uint8_t ret;
-
-	asm volatile(MPLOCKED
-			"decl %[cnt] ; "
-			"sete %[ret]"
-			: [cnt] "+m" (v->cnt),  /* output */
-			  [ret] "=qm" (ret)
-			);
-	return ret != 0;
-}
-
-#endif /* !RTE_FORCE_INTRINSICS */
 
 #ifdef __cplusplus
 }
diff --git a/lib/eal/x86/include/rte_atomic_32.h b/lib/eal/x86/include/rte_atomic_32.h
index 0f25863aa5..37d139f30d 100644
--- a/lib/eal/x86/include/rte_atomic_32.h
+++ b/lib/eal/x86/include/rte_atomic_32.h
@@ -20,193 +20,5 @@
 
 /*------------------------- 64 bit atomic operations -------------------------*/
 
-#ifndef RTE_FORCE_INTRINSICS
-static inline int
-rte_atomic64_cmpset(volatile uint64_t *dst, uint64_t exp, uint64_t src)
-{
-	uint8_t res;
-	union {
-		struct {
-			uint32_t l32;
-			uint32_t h32;
-		};
-		uint64_t u64;
-	} _exp, _src;
-
-	_exp.u64 = exp;
-	_src.u64 = src;
-
-#ifndef __PIC__
-    asm volatile (
-            MPLOCKED
-            "cmpxchg8b (%[dst]);"
-            "setz %[res];"
-            : [res] "=a" (res)      /* result in eax */
-            : [dst] "S" (dst),      /* esi */
-             "b" (_src.l32),       /* ebx */
-             "c" (_src.h32),       /* ecx */
-             "a" (_exp.l32),       /* eax */
-             "d" (_exp.h32)        /* edx */
-			: "memory" );           /* no-clobber list */
-#else
-	asm volatile (
-            "xchgl %%ebx, %%edi;\n"
-			MPLOCKED
-			"cmpxchg8b (%[dst]);"
-			"setz %[res];"
-            "xchgl %%ebx, %%edi;\n"
-			: [res] "=a" (res)      /* result in eax */
-			: [dst] "S" (dst),      /* esi */
-			  "D" (_src.l32),       /* ebx */
-			  "c" (_src.h32),       /* ecx */
-			  "a" (_exp.l32),       /* eax */
-			  "d" (_exp.h32)        /* edx */
-			: "memory" );           /* no-clobber list */
-#endif
-
-	return res;
-}
-
-static inline uint64_t
-rte_atomic64_exchange(volatile uint64_t *dest, uint64_t val)
-{
-	uint64_t old;
-
-	do {
-		old = *dest;
-	} while (rte_atomic64_cmpset(dest, old, val) == 0);
-
-	return old;
-}
-
-static inline void
-rte_atomic64_init(rte_atomic64_t *v)
-{
-	int success = 0;
-	uint64_t tmp;
-
-	while (success == 0) {
-		tmp = v->cnt;
-		success = rte_atomic64_cmpset((volatile uint64_t *)&v->cnt,
-		                              tmp, 0);
-	}
-}
-
-static inline int64_t
-rte_atomic64_read(rte_atomic64_t *v)
-{
-	int success = 0;
-	uint64_t tmp;
-
-	while (success == 0) {
-		tmp = v->cnt;
-		/* replace the value by itself */
-		success = rte_atomic64_cmpset((volatile uint64_t *)&v->cnt,
-		                              tmp, tmp);
-	}
-	return tmp;
-}
-
-static inline void
-rte_atomic64_set(rte_atomic64_t *v, int64_t new_value)
-{
-	int success = 0;
-	uint64_t tmp;
-
-	while (success == 0) {
-		tmp = v->cnt;
-		success = rte_atomic64_cmpset((volatile uint64_t *)&v->cnt,
-		                              tmp, new_value);
-	}
-}
-
-static inline void
-rte_atomic64_add(rte_atomic64_t *v, int64_t inc)
-{
-	int success = 0;
-	uint64_t tmp;
-
-	while (success == 0) {
-		tmp = v->cnt;
-		success = rte_atomic64_cmpset((volatile uint64_t *)&v->cnt,
-		                              tmp, tmp + inc);
-	}
-}
-
-static inline void
-rte_atomic64_sub(rte_atomic64_t *v, int64_t dec)
-{
-	int success = 0;
-	uint64_t tmp;
-
-	while (success == 0) {
-		tmp = v->cnt;
-		success = rte_atomic64_cmpset((volatile uint64_t *)&v->cnt,
-		                              tmp, tmp - dec);
-	}
-}
-
-static inline void
-rte_atomic64_inc(rte_atomic64_t *v)
-{
-	rte_atomic64_add(v, 1);
-}
-
-static inline void
-rte_atomic64_dec(rte_atomic64_t *v)
-{
-	rte_atomic64_sub(v, 1);
-}
-
-static inline int64_t
-rte_atomic64_add_return(rte_atomic64_t *v, int64_t inc)
-{
-	int success = 0;
-	uint64_t tmp;
-
-	while (success == 0) {
-		tmp = v->cnt;
-		success = rte_atomic64_cmpset((volatile uint64_t *)&v->cnt,
-		                              tmp, tmp + inc);
-	}
-
-	return tmp + inc;
-}
-
-static inline int64_t
-rte_atomic64_sub_return(rte_atomic64_t *v, int64_t dec)
-{
-	int success = 0;
-	uint64_t tmp;
-
-	while (success == 0) {
-		tmp = v->cnt;
-		success = rte_atomic64_cmpset((volatile uint64_t *)&v->cnt,
-		                              tmp, tmp - dec);
-	}
-
-	return tmp - dec;
-}
-
-static inline int rte_atomic64_inc_and_test(rte_atomic64_t *v)
-{
-	return rte_atomic64_add_return(v, 1) == 0;
-}
-
-static inline int rte_atomic64_dec_and_test(rte_atomic64_t *v)
-{
-	return rte_atomic64_sub_return(v, 1) == 0;
-}
-
-static inline int rte_atomic64_test_and_set(rte_atomic64_t *v)
-{
-	return rte_atomic64_cmpset((volatile uint64_t *)&v->cnt, 0, 1);
-}
-
-static inline void rte_atomic64_clear(rte_atomic64_t *v)
-{
-	rte_atomic64_set(v, 0);
-}
-#endif
 
 #endif /* _RTE_ATOMIC_I686_H_ */
diff --git a/lib/eal/x86/include/rte_atomic_64.h b/lib/eal/x86/include/rte_atomic_64.h
index 0a7a2131e0..1cd12695a2 100644
--- a/lib/eal/x86/include/rte_atomic_64.h
+++ b/lib/eal/x86/include/rte_atomic_64.h
@@ -22,163 +22,6 @@
 
 /*------------------------- 64 bit atomic operations -------------------------*/
 
-#ifndef RTE_FORCE_INTRINSICS
-static inline int
-rte_atomic64_cmpset(volatile uint64_t *dst, uint64_t exp, uint64_t src)
-{
-	uint8_t res;
-
-
-	asm volatile(
-			MPLOCKED
-			"cmpxchgq %[src], %[dst];"
-			"sete %[res];"
-			: [res] "=a" (res),     /* output */
-			  [dst] "=m" (*dst)
-			: [src] "r" (src),      /* input */
-			  "a" (exp),
-			  "m" (*dst)
-			: "memory");            /* no-clobber list */
-
-	return res;
-}
-
-static inline uint64_t
-rte_atomic64_exchange(volatile uint64_t *dst, uint64_t val)
-{
-	asm volatile(
-			MPLOCKED
-			"xchgq %0, %1;"
-			: "=r" (val), "=m" (*dst)
-			: "0" (val),  "m" (*dst)
-			: "memory");         /* no-clobber list */
-	return val;
-}
-
-static inline void
-rte_atomic64_init(rte_atomic64_t *v)
-{
-	v->cnt = 0;
-}
-
-static inline int64_t
-rte_atomic64_read(rte_atomic64_t *v)
-{
-	return v->cnt;
-}
-
-static inline void
-rte_atomic64_set(rte_atomic64_t *v, int64_t new_value)
-{
-	v->cnt = new_value;
-}
-
-static inline void
-rte_atomic64_add(rte_atomic64_t *v, int64_t inc)
-{
-	asm volatile(
-			MPLOCKED
-			"addq %[inc], %[cnt]"
-			: [cnt] "=m" (v->cnt)   /* output */
-			: [inc] "ir" (inc),     /* input */
-			  "m" (v->cnt)
-			);
-}
-
-static inline void
-rte_atomic64_sub(rte_atomic64_t *v, int64_t dec)
-{
-	asm volatile(
-			MPLOCKED
-			"subq %[dec], %[cnt]"
-			: [cnt] "=m" (v->cnt)   /* output */
-			: [dec] "ir" (dec),     /* input */
-			  "m" (v->cnt)
-			);
-}
-
-static inline void
-rte_atomic64_inc(rte_atomic64_t *v)
-{
-	asm volatile(
-			MPLOCKED
-			"incq %[cnt]"
-			: [cnt] "=m" (v->cnt)   /* output */
-			: "m" (v->cnt)          /* input */
-			);
-}
-
-static inline void
-rte_atomic64_dec(rte_atomic64_t *v)
-{
-	asm volatile(
-			MPLOCKED
-			"decq %[cnt]"
-			: [cnt] "=m" (v->cnt)   /* output */
-			: "m" (v->cnt)          /* input */
-			);
-}
-
-static inline int64_t
-rte_atomic64_add_return(rte_atomic64_t *v, int64_t inc)
-{
-	int64_t prev = inc;
-
-	asm volatile(
-			MPLOCKED
-			"xaddq %[prev], %[cnt]"
-			: [prev] "+r" (prev),   /* output */
-			  [cnt] "=m" (v->cnt)
-			: "m" (v->cnt)          /* input */
-			);
-	return prev + inc;
-}
-
-static inline int64_t
-rte_atomic64_sub_return(rte_atomic64_t *v, int64_t dec)
-{
-	return rte_atomic64_add_return(v, -dec);
-}
-
-static inline int rte_atomic64_inc_and_test(rte_atomic64_t *v)
-{
-	uint8_t ret;
-
-	asm volatile(
-			MPLOCKED
-			"incq %[cnt] ; "
-			"sete %[ret]"
-			: [cnt] "+m" (v->cnt), /* output */
-			  [ret] "=qm" (ret)
-			);
-
-	return ret != 0;
-}
-
-static inline int rte_atomic64_dec_and_test(rte_atomic64_t *v)
-{
-	uint8_t ret;
-
-	asm volatile(
-			MPLOCKED
-			"decq %[cnt] ; "
-			"sete %[ret]"
-			: [cnt] "+m" (v->cnt),  /* output */
-			  [ret] "=qm" (ret)
-			);
-	return ret != 0;
-}
-
-static inline int rte_atomic64_test_and_set(rte_atomic64_t *v)
-{
-	return rte_atomic64_cmpset((volatile uint64_t *)&v->cnt, 0, 1);
-}
-
-static inline void rte_atomic64_clear(rte_atomic64_t *v)
-{
-	v->cnt = 0;
-}
-#endif
 
 /*------------------------ 128 bit atomic operations -------------------------*/
 
-- 
2.53.0


^ permalink raw reply related

* [RFC v2 00/11] prepare deprecation of rte_atomicNN_*() family
From: Stephen Hemminger @ 2026-05-21 18:04 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger
In-Reply-To: <20260521042043.1590536-1-stephen@networkplumber.org>

The goal is to land every deprecation currently listed in the release
notes by the 26.11 ABI bump. Functions to be removed in 26.11 need
to be marked __rte_deprecated by 26.07, with all in-tree users
converted off them first so CI stays clean.

This is the first step. After this series there are no remaining
in-tree users of rte_atomic64. Expected follow-ups:

  - convert remaining rte_atomic32 users (dpaa/fslmc, netvsc, vmbus,
    sw_evdev, txgbe, ifc, hinic, bnx2x, vhost)
  - convert remaining rte_atomic16 users (dpaa/fslmc, qman)
  - mark the rte_atomicNN_*() family __rte_deprecated
  - remove the legacy test_atomic.c
  - remove the API itself at 26.11

Patch 1 deletes the inline-asm atomic fallbacks across arm, ppc,
loongarch, riscv, and x86 now that RTE_FORCE_INTRINSICS has been
the default everywhere for years. Largest patch by line count and
the one most worth review attention.

Patch 2 retires the rte_smp_*mb deprecation notice (open since 2021)
by reimplementing those APIs as inline wrappers over
rte_atomic_thread_fence; the API is preserved for readability.

Patch 3 is the load-bearing change for lib/: the last caller of
rte_atomic32_cmpset() is converted, with explicit acquire/release
orderings matching the existing HTS/RTS ring pattern.

Driver conversions (patches 4-11) match each rte_atomic64 use to its
best fit rather than blanket seq_cst: software stats become plain
assignment (DPDK convention, torn reads accepted); CAS loops setting
a flag collapse to fetch_or or exchange; open-coded link-status CAS
in net/pfe and net/sfc moves to the existing rte_eth_linkstatus
helpers; genuine synchronization stays atomic with explicit ordering.

v2 - fix clang build
   - replace rte_atomic64 in more drivers
   - incorporate feedback on rte_smp and ring
   - drop zxdh change (only caused by intrinsics in spinlock)

Stephen Hemminger (11):
  eal: use intrinsics for rte_atomic on all platforms
  eal: reimplement rte_smp_*mb with rte_atomic_thread_fence
  ring: use C11 atomic operations for MP/SP head/tail
  net/bonding: use stdatomic
  net/nbl: remove unused rte_atomic16 field
  net/ena: replace use of rte_atomicNN
  net/failsafe: convert to stdatomic
  net/enic: do not use deprecated rte_atomic64
  net/pfe: use ethdev linkstatus helpers
  net/sfc: replace rte_atomic with stdatomic
  crypto/ccp: replace use of rte_atomic64 with stdatomic

 doc/guides/rel_notes/deprecation.rst          |   8 -
 drivers/crypto/ccp/ccp_crypto.c               |  11 +-
 drivers/crypto/ccp/ccp_crypto.h               |   2 +-
 drivers/crypto/ccp/ccp_dev.c                  |  10 +-
 drivers/crypto/ccp/ccp_dev.h                  |   4 +-
 drivers/net/bonding/eth_bond_8023ad_private.h |   6 +-
 drivers/net/bonding/rte_eth_bond_8023ad.c     |  35 ++-
 drivers/net/ena/base/ena_plat_dpdk.h          |  14 +-
 drivers/net/ena/ena_ethdev.c                  |  21 +-
 drivers/net/ena/ena_ethdev.h                  |   7 +-
 drivers/net/enic/enic.h                       |   6 +-
 drivers/net/enic/enic_compat.h                |   1 -
 drivers/net/enic/enic_main.c                  |  17 +-
 drivers/net/enic/enic_rxtx.c                  |  14 +-
 drivers/net/enic/enic_rxtx_vec_avx2.c         |   4 +-
 drivers/net/failsafe/failsafe_ops.c           |  12 +-
 drivers/net/failsafe/failsafe_private.h       |  29 +--
 drivers/net/failsafe/failsafe_rxtx.c          |   2 +-
 drivers/net/nbl/nbl_hw/nbl_resource.h         |   1 -
 drivers/net/pfe/pfe_ethdev.c                  |  32 +--
 drivers/net/sfc/sfc.c                         |   9 +-
 drivers/net/sfc/sfc.h                         |   4 +-
 drivers/net/sfc/sfc_port.c                    |   7 +-
 drivers/net/sfc/sfc_stats.h                   |   2 +-
 lib/eal/arm/include/rte_atomic_32.h           |  10 -
 lib/eal/arm/include/rte_atomic_64.h           |  10 -
 lib/eal/include/generic/rte_atomic.h          | 206 +++---------------
 lib/eal/loongarch/include/rte_atomic.h        |  10 -
 lib/eal/ppc/include/rte_atomic.h              | 179 ---------------
 lib/eal/riscv/include/rte_atomic.h            |  10 -
 lib/eal/x86/include/rte_atomic.h              | 205 +----------------
 lib/eal/x86/include/rte_atomic_32.h           | 188 ----------------
 lib/eal/x86/include/rte_atomic_64.h           | 157 -------------
 lib/ring/rte_ring_generic_pvt.h               |  65 ++++--
 34 files changed, 190 insertions(+), 1108 deletions(-)

-- 
2.53.0


^ permalink raw reply

* Re: [PATCH 00/10] e1000 base code update
From: Bruce Richardson @ 2026-05-21 16:47 UTC (permalink / raw)
  To: Ciara Loftus; +Cc: dev
In-Reply-To: <20260520125256.354336-1-ciara.loftus@intel.com>

On Wed, May 20, 2026 at 12:52:37PM +0000, Ciara Loftus wrote:
> Update the e1000 base code. The changes are mostly fixes, refactoring,
> and code cleanup.
> 
> Ciara Loftus (1):
>   net/e1000/base: update version info
> 
> Dima Ruinskiy (2):
>   net/e1000/base: fix coding style issues
>   net/e1000/base: propagate PHY control register write error
> 
> Lukasz Czapnik (1):
>   net/e1000/base: fix possible variable overflow
> 
> Mateusz Fryze (1):
>   net/e1000/base: auto-negotiation status for 1000BASE-T
> 
> Menachem Fogel (1):
>   net/e1000/base: fix NVM loop bounds and pointer access
> 
> Vitaly Lifshits (4):
>   net/e1000/base: refactor K1 exit timeout configuration
>   net/e1000/base: fix typo in e1000 base code
>   net/e1000/base: reclassify MAC type
>   net/e1000/base: clear DPG enable bit post MAC reset
> 
Series-acked-by: Bruce Richardson <bruce.richardson@intel.com>

Applied to next-net-intel.
Thanks,
/Bruce

^ permalink raw reply

* Re: [PATCH 05/10] net/e1000/base: clear DPG enable bit post MAC reset
From: Bruce Richardson @ 2026-05-21 16:42 UTC (permalink / raw)
  To: Ciara Loftus; +Cc: dev, Vitaly Lifshits
In-Reply-To: <20260520125256.354336-6-ciara.loftus@intel.com>

On Wed, May 20, 2026 at 12:52:42PM +0000, Ciara Loftus wrote:
> From: Vitaly Lifshits <vitaly.lifshits@intel.com>
> 
> The GbE autonomous power gating feature was added to support G3 to S5
> flow. However, this changed the reset value of DPG_EN bit to 1, causing
> a possible autonomous transition to power gating state during D0. This
> might result in undefined errors such as: packet loss, packet corruption
> and Tx/Rx hangs. Therefore, clear DPG_EN bit after hardware reset flow.
> 
> Signed-off-by: Vitaly Lifshits <vitaly.lifshits@intel.com>
> Signed-off-by: Ciara Loftus <ciara.loftus@intel.com>
> ---

Seems like a bug fix needing backport. Will add fixes tag and CC stable on
apply.

Fixes: 5a32a257f957 ("e1000: more NICs in base driver")

>  drivers/net/intel/e1000/base/e1000_defines.h | 1 +
>  drivers/net/intel/e1000/base/e1000_ich8lan.c | 7 +++++++
>  2 files changed, 8 insertions(+)
> 
> diff --git a/drivers/net/intel/e1000/base/e1000_defines.h b/drivers/net/intel/e1000/base/e1000_defines.h
> index eb93675823..6c710300a6 100644
> --- a/drivers/net/intel/e1000/base/e1000_defines.h
> +++ b/drivers/net/intel/e1000/base/e1000_defines.h
> @@ -36,6 +36,7 @@
>  
>  /* Extended Device Control */
>  #define E1000_CTRL_EXT_LPCD		0x00000004 /* LCD Power Cycle Done */
> +#define E1000_CTRL_EXT_DPG_EN		0x00000008 /* Dynamic Power Gating Enable */
>  #define E1000_CTRL_EXT_SDP4_DATA	0x00000010 /* SW Definable Pin 4 data */
>  #define E1000_CTRL_EXT_SDP6_DATA	0x00000040 /* SW Definable Pin 6 data */
>  #define E1000_CTRL_EXT_SDP3_DATA	0x00000080 /* SW Definable Pin 3 data */
> diff --git a/drivers/net/intel/e1000/base/e1000_ich8lan.c b/drivers/net/intel/e1000/base/e1000_ich8lan.c
> index 6190052368..96b9ad6a70 100644
> --- a/drivers/net/intel/e1000/base/e1000_ich8lan.c
> +++ b/drivers/net/intel/e1000/base/e1000_ich8lan.c
> @@ -5107,6 +5107,13 @@ STATIC s32 e1000_reset_hw_ich8lan(struct e1000_hw *hw)
>  	reg |= E1000_KABGTXD_BGSQLBIAS;
>  	E1000_WRITE_REG(hw, E1000_KABGTXD, reg);
>  
> +	if (hw->mac.type >= e1000_pch_ptp) {
> +		DEBUGOUT("Clearing DPG EN bit post reset\n");
> +		reg = E1000_READ_REG(hw, E1000_CTRL_EXT);
> +		reg &= ~E1000_CTRL_EXT_DPG_EN;
> +		E1000_WRITE_REG(hw, E1000_CTRL_EXT, reg);
> +	}
> +
>  	return E1000_SUCCESS;
>  }
>  
> -- 
> 2.43.0
> 

^ permalink raw reply

* Re: [PATCH] net/mlx5: remove nonsensical flow action class_id checks
From: Dariusz Sosnowski @ 2026-05-21 16:23 UTC (permalink / raw)
  To: Adrian Schollmeyer
  Cc: Viacheslav Ovsiienko, Bing Zhao, Ori Kam, Suanming Mou,
	Matan Azrad, Michael Baum, dev, Michael Pfeiffer, stable
In-Reply-To: <20260520132533.159996-1-a.schollmeyer@syseleven.de>

On Wed, May 20, 2026 at 03:25:32PM +0200, Adrian Schollmeyer wrote:
> From: Michael Pfeiffer <m.pfeiffer@syseleven.de>
> 
> For a MODIFY_FIELD action, flow_hw_validate_action_modify_field() is
> invoked and enforces class_id == 0 in the action's source and
> destination, if the modified field is none of
> RTE_FLOW_FIELD_GENEVE_OPT_*, as the value is used solely for GENEVE
> fields.
> 
> However, this check is flawed due to the way rte_flow_field_data is
> initialized. As it consists of unions and anonymous structs as members,
> empty initialization of this struct or initializing just the tag_index
> only guarantees initialization of the first union member, while the
> remaining member's default initialization behavior is unspecified.
> Therefore, depending on the compiler type, version and configuration,
> the remaining members may either be default-initialized as well or
> contain bytes from uninitialized memory. This causes the check to fail
> depending on how the struct is initialized wherever it is used.
> 
> For example, rte_flow_configure() sometimes fails on mlx5 under these
> circumstances with an error "destination class id is not supported"
> during creation of representor tagging rules, as these internally use
> MODIFY_FIELD actions in the following call stack:
> 
>   1. rte_flow_configure
>   2. mlx5_flow_port_configure
>   3. flow_hw_configure
>   4. __flow_hw_configure
>   5. flow_hw_setup_tx_repr_tagging
>   6. flow_hw_create_tx_repr_tag_jump_acts_tmpl
>      --> various rte_flow_action_modify_field are initialized here, but
>          class_id remains uninitialized
>   7. __flow_hw_actions_template_create
>   8. mlx5_flow_hw_actions_validate
>   9. flow_hw_validate_action_modify_field
>      --> invoked with class_id containing uninitialized bytes and
>          non-GENEVE field type
> 
> Remove the two checks for class_id in the non-GENEVE case, as this field
> is unused for these actions and avoids additional implicit dependencies
> on the correct ordering of union members.
> 
> Fixes: 1caa89ec1891 ("net/mlx5: support GENEVE options modification")
> Cc: stable@dpdk.org
> 
> Signed-off-by: Michael Pfeiffer <m.pfeiffer@syseleven.de>
> Signed-off-by: Adrian Schollmeyer <a.schollmeyer@syseleven.de>

Acked-by: Dariusz Sosnowski <dsosnowski@nvidia.com>

Thank you for the contribution.

Best regards,
Dariusz Sosnowski

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox