lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20250711114248.2288591-3-sashal@kernel.org>
Date: Fri, 11 Jul 2025 07:42:46 -0400
From: Sasha Levin <sashal@...nel.org>
To: linux-kernel@...r.kernel.org
Cc: linux-doc@...r.kernel.org,
	linux-api@...r.kernel.org,
	tools@...nel.org,
	Sasha Levin <sashal@...nel.org>
Subject: [RFC v3 2/4] kernel/api: enable kerneldoc-based API specifications

Allow kernel developers to write API specifications directly in
kerneldoc comments, which are automatically converted to machine-readable
C macros during build.

Signed-off-by: Sasha Levin <sashal@...nel.org>
---
 kernel/api/Makefile              |  21 +-
 scripts/Makefile.build           |  28 ++
 scripts/generate_api_specs.sh    |  59 +++
 scripts/kernel-doc.py            |   5 +
 scripts/lib/kdoc/kdoc_apispec.py | 623 +++++++++++++++++++++++++++++++
 scripts/lib/kdoc/kdoc_output.py  |   5 +-
 scripts/lib/kdoc/kdoc_parser.py  |  54 ++-
 7 files changed, 791 insertions(+), 4 deletions(-)
 create mode 100755 scripts/generate_api_specs.sh
 create mode 100644 scripts/lib/kdoc/kdoc_apispec.py

diff --git a/kernel/api/Makefile b/kernel/api/Makefile
index 4120ded7e5cf1..312d35179c78a 100644
--- a/kernel/api/Makefile
+++ b/kernel/api/Makefile
@@ -4,4 +4,23 @@
 #
 
 # Core API specification framework
-obj-$(CONFIG_KAPI_SPEC)		+= kernel_api_spec.o
\ No newline at end of file
+obj-$(CONFIG_KAPI_SPEC)		+= kernel_api_spec.o
+
+# Auto-generated API specifications collector
+ifeq ($(CONFIG_KAPI_SPEC),y)
+obj-$(CONFIG_KAPI_SPEC)		+= generated_api_specs.o
+
+# Find all potential apispec files (this is evaluated at make time)
+apispec-files := $(shell find $(objtree) -name "*.apispec.h" -type f 2>/dev/null)
+
+# Generate the collector file
+# Note: FORCE ensures this is always regenerated to pick up new apispec files
+$(obj)/generated_api_specs.c: $(srctree)/scripts/generate_api_specs.sh FORCE
+	$(Q)$(CONFIG_SHELL) $< $(srctree) $(objtree) > $@
+
+targets += generated_api_specs.c
+clean-files += generated_api_specs.c
+
+# Add explicit dependency on the generator script
+$(obj)/generated_api_specs.o: $(obj)/generated_api_specs.c
+endif
\ No newline at end of file
diff --git a/scripts/Makefile.build b/scripts/Makefile.build
index a6461ea411f7a..5c0e44d1b6dbc 100644
--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -172,6 +172,34 @@ ifneq ($(KBUILD_EXTRA_WARN),)
         $<
 endif
 
+# Generate API spec headers from kernel-doc comments
+ifeq ($(CONFIG_KAPI_SPEC),y)
+# Function to check if a file has API specifications
+has-apispec = $(shell grep -qE '^\s*\*\s*(api-type|long-desc|context-flags|param-type|error-code|capability|signal|lock|side-effect|state-trans):' $(src)/$(1) 2>/dev/null && echo $(1))
+
+# Get base names without directory prefix
+c-objs-base := $(notdir $(real-obj-y) $(real-obj-m))
+# Filter to only .o files with corresponding .c source files
+c-files := $(foreach o,$(c-objs-base),$(if $(wildcard $(src)/$(o:.o=.c)),$(o:.o=.c)))
+# Also check for any additional .c files that contain API specs but are included
+extra-c-files := $(shell find $(src) -maxdepth 1 -name "*.c" -exec grep -l '^\s*\*\s*\(api-type\|long-desc\|context-flags\|param-type\|error-code\|capability\|signal\|lock\|side-effect\|state-trans\):' {} \; 2>/dev/null | xargs -r basename -a)
+# Combine both lists and remove duplicates
+all-c-files := $(sort $(c-files) $(extra-c-files))
+# Only include files that actually have API specifications
+apispec-files := $(foreach f,$(all-c-files),$(call has-apispec,$(f)))
+# Generate apispec targets with proper directory prefix
+apispec-y := $(addprefix $(obj)/,$(apispec-files:.c=.apispec.h))
+always-y += $(apispec-y)
+targets += $(apispec-y)
+
+quiet_cmd_apispec = APISPEC $@
+      cmd_apispec = PYTHONDONTWRITEBYTECODE=1 $(KERNELDOC) -apispec \
+                    $(KDOCFLAGS) $< > $@ 2>/dev/null || rm -f $@
+
+$(obj)/%.apispec.h: $(src)/%.c FORCE
+	$(call if_changed,apispec)
+endif
+
 # Compile C sources (.c)
 # ---------------------------------------------------------------------------
 
diff --git a/scripts/generate_api_specs.sh b/scripts/generate_api_specs.sh
new file mode 100755
index 0000000000000..1ef817c0d1955
--- /dev/null
+++ b/scripts/generate_api_specs.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# generate_api_specs.sh - Generate C file that includes all API specification headers
+#
+# Usage: generate_api_specs.sh <srctree> <objtree>
+
+SRCTREE="$1"
+OBJTREE="$2"
+
+if [ -z "$SRCTREE" ] || [ -z "$OBJTREE" ]; then
+    echo "Usage: $0 <srctree> <objtree>" >&2
+    exit 1
+fi
+
+# Generate header
+cat <<EOF
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Auto-generated file - DO NOT EDIT
+ * Generated by: scripts/generate_api_specs.sh
+ *
+ * This file includes all kernel API specification headers
+ */
+
+#include <linux/kernel.h>
+#include <linux/kernel_api_spec.h>
+#include <linux/errno.h>
+#include <linux/capability.h>
+#include <uapi/linux/sched/types.h>
+
+#ifdef CONFIG_KAPI_SPEC
+
+EOF
+
+# Find all .apispec.h files and generate includes
+# We look in the object tree since that's where they're generated
+find "$OBJTREE" -name "*.apispec.h" -type f 2>/dev/null | \
+    grep -v "/generated_api_specs.c" | \
+    sort | \
+    while read -r apispec_file; do
+        # Get relative path from objtree
+        rel_path="${apispec_file#$OBJTREE/}"
+
+        # Skip if file is empty
+        if [ ! -s "$apispec_file" ]; then
+            continue
+        fi
+
+        # Generate include statement
+        # For includes from kernel/api/, we need to go up two levels
+        echo "#include \"../../${rel_path}\""
+    done
+
+# Close the ifdef
+cat <<EOF
+
+#endif /* CONFIG_KAPI_SPEC */
+EOF
\ No newline at end of file
diff --git a/scripts/kernel-doc.py b/scripts/kernel-doc.py
index 12ae66f40bd7d..8e0be6b3eacef 100755
--- a/scripts/kernel-doc.py
+++ b/scripts/kernel-doc.py
@@ -109,6 +109,7 @@ sys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR))
 
 from kdoc_files import KernelFiles                      # pylint: disable=C0413
 from kdoc_output import RestFormat, ManFormat           # pylint: disable=C0413
+from kdoc_apispec import ApiSpecFormat                  # pylint: disable=C0413
 
 DESC = """
 Read C language source or header FILEs, extract embedded documentation comments,
@@ -225,6 +226,8 @@ def main():
                          help="Output reStructuredText format (default).")
     out_fmt.add_argument("-N", "-none", "--none", action="store_true",
                          help="Do not output documentation, only warnings.")
+    out_fmt.add_argument("-apispec", "--apispec", action="store_true",
+                         help="Output C macro invocations for kernel API specifications.")
 
     # Output selection mutually-exclusive group
 
@@ -275,6 +278,8 @@ def main():
         out_style = ManFormat(modulename=args.modulename)
     elif args.none:
         out_style = None
+    elif args.apispec:
+        out_style = ApiSpecFormat()
     else:
         out_style = RestFormat()
 
diff --git a/scripts/lib/kdoc/kdoc_apispec.py b/scripts/lib/kdoc/kdoc_apispec.py
new file mode 100644
index 0000000000000..129fe5bf236a4
--- /dev/null
+++ b/scripts/lib/kdoc/kdoc_apispec.py
@@ -0,0 +1,623 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""
+Generate C macro invocations for kernel API specifications from kernel-doc comments.
+
+This module creates C header files with API specification macros that match
+the kernel API specification framework introduced in commit 9688de5c25bed.
+"""
+
+from kdoc_output import OutputFormat
+
+
+class ApiSpecFormat(OutputFormat):
+    """Generate C macro invocations for kernel API specifications"""
+
+    def __init__(self):
+        """Initialize ApiSpecFormat"""
+        super().__init__()
+        self.api_specs = {}
+
+    def _format_macro_param(self, value):
+        """Format a value for use in C macro parameter"""
+        if value is None:
+            return '""'
+        # Escape quotes and backslashes for C string literals
+        value = str(value).replace('\\', '\\\\').replace('"', '\\"')
+        # Handle multi-line strings by replacing newlines with escaped newlines
+        value = value.replace('\n', '\\n"\n\t\t       "')
+        return f'"{value}"'
+
+    def _get_api_section_value(self, sections, key):
+        """Get value from API sections"""
+        # sections is a dictionary where keys are section names
+        # Check both with and without @ prefix
+        if key in sections:
+            content = sections[key]
+            # Return full content, stripping trailing newlines
+            return content.rstrip('\n')
+        elif '@' + key in sections:
+            content = sections['@' + key]
+            return content.rstrip('\n')
+        return None
+
+    def _get_all_section_lines(self, sections, key):
+        """Get all lines from a section"""
+        if key in sections:
+            return [line.strip() for line in sections[key].strip().split('\n') if line.strip()]
+        elif '@' + key in sections:
+            return [line.strip() for line in sections['@' + key].strip().split('\n') if line.strip()]
+        return []
+
+    def _process_error_code(self, error_lines, error_idx):
+        """Process a multi-line error code specification"""
+        if not error_lines:
+            return
+
+        # First line has: -ECODE, NAME, Short desc, Long desc
+        first_line = error_lines[0]
+        parts = first_line.split(',', 3)
+        if len(parts) >= 3:
+            code = parts[0].strip()
+            name = parts[1].strip()
+            short_desc = parts[2].strip()
+
+            # Collect long description from remaining parts and lines
+            long_desc_parts = []
+            if len(parts) > 3:
+                long_desc_parts.append(parts[3].strip())
+            # Add continuation lines
+            for line in error_lines[1:]:
+                long_desc_parts.append(line.strip())
+            long_desc = ' '.join(long_desc_parts)
+
+            self.data += f"\n\tKAPI_ERROR({error_idx}, {code}, {self._format_macro_param(name)}, "
+            self.data += f"{self._format_macro_param(short_desc)},\n\t\t   {self._format_macro_param(long_desc)})\n"
+
+    def _parse_param_spec(self, param_name, sections):
+        """Parse all specifications for a parameter"""
+        specs = {}
+
+        # Look for parameter-specific sections
+        for key, content in sections.items():
+            # Check both param- and @param- prefixes
+            if key.startswith('param-') or key.startswith('@...am-'):
+                # Remove @ prefix if present for specs key
+                specs_key = key[1:] if key.startswith('@') else key
+
+                # Each section may contain multiple parameter specifications
+                # separated by newlines
+                for line in content.strip().split('\n'):
+                    line = line.strip()
+                    if ',' in line and line.split(',', 1)[0].strip() == param_name:
+                        value = line.split(',', 1)[1].strip()
+                        specs[specs_key] = value
+                        break  # Found this parameter's value for this key
+
+        return specs
+
+    def out_function(self, fname, name, args):
+        """Generate API spec for a function"""
+        function_name = args.get('function', '')
+        sections = args.get('sections', {})
+        parameterlist = args.get('parameterlist', [])
+        parameterdescs = args.get('parameterdescs', {})
+        parametertypes = args.get('parametertypes', {})
+        purpose = args.get('purpose', '')
+
+        # Check if this function has an API specification
+        # Look for key API spec sections that indicate this is a full API specification
+        api_indicators = [
+            'api-type', 'context-flags', 'param-type', 'error-code',
+            'capability', 'signal', 'lock', 'side-effect', 'state-trans'
+        ]
+
+        has_api_spec = False
+        for indicator in api_indicators:
+            # Check both with and without @ prefix
+            if any(key.startswith(indicator) or key.startswith('@' + indicator) for key in sections.keys()):
+                has_api_spec = True
+                break
+
+        if not has_api_spec:
+            return
+
+        # Clear warnings for API spec output since parameter-specific sections
+        # trigger false warnings
+        args['warnings'] = []
+
+
+        # Start building the macro invocation
+        self.data += f"DEFINE_KERNEL_API_SPEC({function_name})\n"
+
+        # Add description
+        if purpose:
+            self.data += f"\tKAPI_DESCRIPTION({self._format_macro_param(purpose)})\n"
+
+        # Add long description if present
+        long_desc = self._get_api_section_value(sections, 'long-desc')
+        if long_desc:
+            self.data += f"\tKAPI_LONG_DESC({self._format_macro_param(long_desc)})\n"
+
+        # Add context flags
+        context_flags = self._get_api_section_value(sections, 'context-flags')
+        if context_flags:
+            self.data += f"\tKAPI_CONTEXT({context_flags})\n"
+        elif self._get_api_section_value(sections, 'context'):
+            # Fallback to simple context
+            self.data += f"\tKAPI_CONTEXT({self._get_api_section_value(sections, 'context')})\n"
+
+        # Add parameter count first
+        param_count = len(parameterlist)
+        param_count_val = self._get_api_section_value(sections, 'param-count')
+        # Note: KAPI_PARAM_COUNT doesn't exist in the current infrastructure
+        # Parameters are handled individually with KAPI_PARAM/KAPI_PARAM_END
+
+        # Process parameters
+        for param_idx, param in enumerate(parameterlist):
+            param_name = param.strip()
+            param_desc = parameterdescs.get(param_name, '')
+            param_ctype = parametertypes.get(param_name, '')
+
+            # Get all parameter specifications
+            param_specs = self._parse_param_spec(param_name, sections)
+
+            self.data += f"\n\tKAPI_PARAM({param_idx}, {self._format_macro_param(param_name)}, "
+            self.data += f"{self._format_macro_param(param_ctype)}, {self._format_macro_param(param_desc)})\n"
+
+            # Add parameter type
+            if 'param-type' in param_specs:
+                self.data += f"\t\tKAPI_PARAM_TYPE({param_specs['param-type']})\n"
+
+            # Add parameter flags
+            if 'param-flags' in param_specs:
+                self.data += f"\t\tKAPI_PARAM_FLAGS({param_specs['param-flags']})\n"
+
+            # Add constraint type
+            if 'param-constraint-type' in param_specs:
+                self.data += f"\t\tKAPI_PARAM_CONSTRAINT_TYPE({param_specs['param-constraint-type']})\n"
+
+            # Add range
+            if 'param-range' in param_specs:
+                if ',' in param_specs['param-range']:
+                    min_val, max_val = param_specs['param-range'].split(',', 1)
+                    self.data += f"\t\tKAPI_PARAM_RANGE({min_val.strip()}, {max_val.strip()})\n"
+
+            # Add mask
+            if 'param-mask' in param_specs:
+                self.data += f"\t\tKAPI_PARAM_MASK({param_specs['param-mask']})\n"
+
+            # Add constraint description
+            if 'param-constraint' in param_specs:
+                self.data += f"\t\tKAPI_PARAM_CONSTRAINT({self._format_macro_param(param_specs['param-constraint'])})\n"
+
+            # struct-type information is stored as comments for documentation purposes
+            # The actual struct validation happens in the kernel based on param type
+
+            self.data += "\tKAPI_PARAM_END\n"
+
+        # Add return specification if we have meaningful return information
+        return_type = self._get_api_section_value(sections, 'return-type')
+        return_check = self._get_api_section_value(sections, 'return-check-type') or \
+                      self._get_api_section_value(sections, 'return-check')
+        return_success = self._get_api_section_value(sections, 'return-success')
+
+        if return_type or return_check or return_success:
+            # Get return description but don't use generic ones
+            return_desc = sections.get('return', sections.get('returns', sections.get('Return', '')))
+            if return_desc and any(phrase in return_desc.lower() for phrase in
+                                 ['error code', 'negative error', 'success or error']):
+                return_desc = ""  # Skip generic descriptions
+
+            self.data += f"\n\tKAPI_RETURN({self._format_macro_param(parametertypes.get('', 'long'))}, "
+            self.data += f"{self._format_macro_param(return_desc)})\n"
+
+            if return_type:
+                self.data += f"\t\tKAPI_RETURN_TYPE({return_type})\n"
+
+            if return_check:
+                self.data += f"\t\tKAPI_RETURN_CHECK_TYPE({return_check})\n"
+
+            if return_success:
+                self.data += f"\t\tKAPI_RETURN_SUCCESS({return_success})\n"
+
+            self.data += "\tKAPI_RETURN_END\n"
+
+        # Add error count before processing errors
+        error_lines = self._get_all_section_lines(sections, 'error-code')
+        error_count = self._get_api_section_value(sections, 'error-count')
+        if error_count:
+            self.data += f"\n\tKAPI_RETURN_ERROR_COUNT({error_count})\n"
+        else:
+            # Count the error lines
+            error_line_count = 0
+            for line in error_lines:
+                if line.startswith('-'):
+                    error_line_count += 1
+            if error_line_count > 0:
+                self.data += f"\n\tKAPI_RETURN_ERROR_COUNT({error_line_count})\n"
+
+        # Process error codes with extended format
+        error_idx = 0
+
+        # Process each error-code entry (which may span multiple lines)
+        current_error = []
+        for line in error_lines:
+            # Check if this starts a new error code (starts with -)
+            if line.startswith('-') and current_error:
+                # Process the previous error
+                self._process_error_code(current_error, error_idx)
+                error_idx += 1
+                current_error = [line]
+            else:
+                # Continuation of current error
+                current_error.append(line)
+
+        # Process the last error
+        if current_error:
+            self._process_error_code(current_error, error_idx)
+            error_idx += 1
+
+        # Add lock count before processing locks
+        lock_lines = self._get_all_section_lines(sections, 'lock')
+        lock_count = self._get_api_section_value(sections, 'lock-count')
+        if lock_count:
+            self.data += f"\n\tKAPI_LOCK_COUNT({lock_count})\n"
+        else:
+            # Count lock lines
+            lock_line_count = len(lock_lines)
+            lock_req = self._get_api_section_value(sections, 'lock-req')
+            if lock_req and lock_line_count == 0:
+                lock_line_count = 1
+            if lock_line_count > 0:
+                self.data += f"\n\tKAPI_LOCK_COUNT({lock_line_count})\n"
+
+        # Process locks
+        lock_idx = 0
+        for line in lock_lines:
+            parts = line.split(',')
+            if len(parts) >= 2:
+                lock_name = parts[0].strip()
+                lock_type = parts[1].strip()
+                self.data += f"\n\tKAPI_LOCK({lock_idx}, {self._format_macro_param(lock_name)}, {lock_type})\n"
+
+                # Check for lock attributes
+                if self._get_api_section_value(sections, 'lock-acquired'):
+                    self.data += "\t\tKAPI_LOCK_ACQUIRED\n"
+                if self._get_api_section_value(sections, 'lock-released'):
+                    self.data += "\t\tKAPI_LOCK_RELEASED\n"
+
+                lock_desc = self._get_api_section_value(sections, 'lock-desc')
+                if lock_desc:
+                    self.data += f"\t\tKAPI_LOCK_DESC({self._format_macro_param(lock_desc)})\n"
+
+                self.data += "\tKAPI_LOCK_END\n"
+                lock_idx += 1
+
+        # Legacy lock-req support
+        lock_req = self._get_api_section_value(sections, 'lock-req')
+        if lock_req and lock_idx == 0:
+            self.data += f"\n\tKAPI_LOCK(0, {self._format_macro_param(lock_req)}, KAPI_LOCK_CUSTOM)\n"
+            self.data += f"\t\tKAPI_LOCK_DESC({self._format_macro_param(lock_req)})\n"
+            self.data += "\tKAPI_LOCK_END\n"
+            lock_idx = 1
+
+        # Add constraint count before processing constraints
+        constraint_lines = self._get_all_section_lines(sections, 'constraint')
+        const_count = self._get_api_section_value(sections, 'constraint-count')
+        if const_count:
+            self.data += f"\n\tKAPI_CONSTRAINT_COUNT({const_count})\n"
+        elif len(constraint_lines) > 0:
+            self.data += f"\n\tKAPI_CONSTRAINT_COUNT({len(constraint_lines)})\n"
+
+        # Process constraints first (before signals/capabilities/etc)
+        constraint_idx = 0
+        for line in constraint_lines:
+            parts = line.split(',', 1)
+            if parts:
+                name = parts[0].strip()
+                desc = parts[1].strip() if len(parts) > 1 else ""
+
+                self.data += f"\n\tKAPI_CONSTRAINT({constraint_idx}, {self._format_macro_param(name)},\n"
+                self.data += f"\t\t\t{self._format_macro_param(desc)})\n"
+
+                # Check for constraint expression
+                expr_lines = self._get_all_section_lines(sections, 'constraint-expr')
+                for expr_line in expr_lines:
+                    if expr_line.startswith(name):
+                        expr = expr_line.split(',', 1)[1].strip() if ',' in expr_line else ""
+                        self.data += f"\t\tKAPI_CONSTRAINT_EXPR({self._format_macro_param(expr)})\n"
+                        break
+
+                self.data += "\tKAPI_CONSTRAINT_END\n"
+                constraint_idx += 1
+
+        # Process signals
+        signal_idx = 0
+        signal_lines = self._get_all_section_lines(sections, 'signal')
+        signal_count = self._get_api_section_value(sections, 'signal-count')
+
+        if signal_count:
+            self.data += f"\n\tKAPI_SIGNAL_COUNT({signal_count})\n"
+        elif len(signal_lines) > 0:
+            self.data += f"\n\tKAPI_SIGNAL_COUNT({len(signal_lines)})\n"
+
+        for line in signal_lines:
+            # Remove this redundant signal count check
+
+            self.data += f"\n\tKAPI_SIGNAL({signal_idx}, 0, {self._format_macro_param(line)}, "
+
+            # Add signal direction
+            signal_dir = self._get_api_section_value(sections, 'signal-direction')
+            if signal_dir:
+                self.data += f"{signal_dir}, "
+            else:
+                self.data += "KAPI_SIGNAL_RECEIVE, "
+
+            # Add signal action
+            signal_action = self._get_api_section_value(sections, 'signal-action')
+            if signal_action:
+                self.data += f"{signal_action})\n"
+            else:
+                self.data += "KAPI_SIGNAL_ACTION_RETURN)\n"
+
+            # Add signal attributes
+            signal_cond = self._get_api_section_value(sections, 'signal-condition')
+            if signal_cond:
+                self.data += f"\t\tKAPI_SIGNAL_CONDITION({self._format_macro_param(signal_cond)})\n"
+
+            signal_desc = self._get_api_section_value(sections, 'signal-desc')
+            if signal_desc:
+                self.data += f"\t\tKAPI_SIGNAL_DESC({self._format_macro_param(signal_desc)})\n"
+
+            signal_error = self._get_api_section_value(sections, 'signal-error')
+            if signal_error:
+                self.data += f"\t\tKAPI_SIGNAL_ERROR({signal_error})\n"
+
+            signal_timing = self._get_api_section_value(sections, 'signal-timing')
+            if signal_timing:
+                self.data += f"\t\tKAPI_SIGNAL_TIMING({signal_timing})\n"
+
+            signal_priority = self._get_api_section_value(sections, 'signal-priority')
+            if signal_priority:
+                self.data += f"\t\tKAPI_SIGNAL_PRIORITY({signal_priority})\n"
+
+            if self._get_api_section_value(sections, 'signal-interruptible'):
+                self.data += "\t\tKAPI_SIGNAL_INTERRUPTIBLE\n"
+
+            signal_state = self._get_api_section_value(sections, 'signal-state-req')
+            if signal_state:
+                self.data += f"\t\tKAPI_SIGNAL_STATE_REQ({signal_state})\n"
+
+            self.data += "\tKAPI_SIGNAL_END\n"
+            signal_idx += 1
+
+        # Process side effects
+        side_effect_lines = self._get_all_section_lines(sections, 'side-effect')
+        effect_count = self._get_api_section_value(sections, 'side-effect-count')
+        if effect_count:
+            self.data += f"\n\tKAPI_SIDE_EFFECT_COUNT({effect_count})\n"
+        elif len(side_effect_lines) > 0:
+            self.data += f"\n\tKAPI_SIDE_EFFECT_COUNT({len(side_effect_lines)})\n"
+
+        # Actually process side effects
+        side_effect_idx = 0
+        for line in side_effect_lines:
+            # Parse: type, target, description[, key=value pairs]
+            # First extract any key=value pairs at the end
+            import re
+
+            # Extract condition=... and reversible=yes
+            condition = None
+            reversible = False
+
+            # Look for condition=... pattern
+            cond_match = re.search(r',\s*condition=([^,]+?)(?:\s*,\s*reversible=yes\s*)?$', line)
+            if cond_match:
+                condition = cond_match.group(1).strip()
+                line = line[:cond_match.start()]  # Remove condition from line
+
+            # Check for reversible=yes
+            if line.endswith(', reversible=yes'):
+                reversible = True
+                line = line[:-len(', reversible=yes')]
+            elif ', reversible=yes,' in line:
+                reversible = True
+                line = line.replace(', reversible=yes,', ',')
+
+            # Now parse the main parts
+            parts = line.split(',', 2)
+            if len(parts) >= 2:
+                effect_type = parts[0].strip()
+                target = parts[1].strip()
+                desc = parts[2].strip() if len(parts) > 2 else ""
+
+                self.data += f"\n\tKAPI_SIDE_EFFECT({side_effect_idx}, {effect_type},\n"
+                self.data += f"\t\t\t {self._format_macro_param(target)},\n"
+                self.data += f"\t\t\t {self._format_macro_param(desc)})\n"
+
+                if condition:
+                    self.data += f"\t\tKAPI_EFFECT_CONDITION({self._format_macro_param(condition)})\n"
+
+                if reversible:
+                    self.data += "\t\tKAPI_EFFECT_REVERSIBLE\n"
+
+                self.data += "\tKAPI_SIDE_EFFECT_END\n"
+                side_effect_idx += 1
+
+        # Process state transitions
+        state_trans_lines = self._get_all_section_lines(sections, 'state-trans')
+        trans_count = self._get_api_section_value(sections, 'state-trans-count')
+        if trans_count:
+            self.data += f"\n\tKAPI_STATE_TRANS_COUNT({trans_count})\n"
+        elif len(state_trans_lines) > 0:
+            self.data += f"\n\tKAPI_STATE_TRANS_COUNT({len(state_trans_lines)})\n"
+
+        state_trans_idx = 0
+        for line in state_trans_lines:
+            parts = line.split(',', 3)
+            if len(parts) >= 3:
+                target = parts[0].strip()
+                from_state = parts[1].strip()
+                to_state = parts[2].strip()
+                desc = parts[3].strip() if len(parts) > 3 else ""
+
+                self.data += f"\n\tKAPI_STATE_TRANS({state_trans_idx}, {self._format_macro_param(target)}, "
+                self.data += f"{self._format_macro_param(from_state)}, {self._format_macro_param(to_state)},\n"
+                self.data += f"\t\t\t {self._format_macro_param(desc)})\n"
+                self.data += "\tKAPI_STATE_TRANS_END\n"
+                state_trans_idx += 1
+
+        # Process capabilities
+        cap_lines = self._get_all_section_lines(sections, 'capability')
+        cap_count = self._get_api_section_value(sections, 'capability-count')
+        if cap_count:
+            self.data += f"\n\tKAPI_CAPABILITY_COUNT({cap_count})\n"
+        elif len(cap_lines) > 0:
+            self.data += f"\n\tKAPI_CAPABILITY_COUNT({len(cap_lines)})\n"
+
+        cap_idx = 0
+        for line in cap_lines:
+            parts = line.split(',', 2)
+            if len(parts) >= 2:
+                cap_name = parts[0].strip()
+                cap_type = parts[1].strip()
+                cap_desc = parts[2].strip() if len(parts) > 2 else cap_name
+
+                self.data += f"\n\tKAPI_CAPABILITY({cap_idx}, {cap_name}, {self._format_macro_param(cap_desc)}, {cap_type})\n"
+
+                # Check for capability attributes
+                cap_allows = self._get_api_section_value(sections, 'capability-allows')
+                if cap_allows:
+                    self.data += f"\t\tKAPI_CAP_ALLOWS({self._format_macro_param(cap_allows)})\n"
+
+                cap_without = self._get_api_section_value(sections, 'capability-without')
+                if cap_without:
+                    self.data += f"\t\tKAPI_CAP_WITHOUT({self._format_macro_param(cap_without)})\n"
+
+                cap_cond = self._get_api_section_value(sections, 'capability-condition')
+                if cap_cond:
+                    self.data += f"\t\tKAPI_CAP_CONDITION({self._format_macro_param(cap_cond)})\n"
+
+                cap_priority = self._get_api_section_value(sections, 'capability-priority')
+                if cap_priority:
+                    self.data += f"\t\tKAPI_CAP_PRIORITY({cap_priority})\n"
+
+                self.data += "\tKAPI_CAPABILITY_END\n"
+                cap_idx += 1
+
+        # Add examples
+        examples = self._get_api_section_value(sections, 'examples')
+        if examples:
+            self.data += f"\n\tKAPI_EXAMPLES({self._format_macro_param(examples)})\n"
+
+        # Add notes
+        notes = self._get_api_section_value(sections, 'notes')
+        if notes:
+            self.data += f"\tKAPI_NOTES({self._format_macro_param(notes)})\n"
+
+        # Process struct specifications
+        struct_types = {}
+        # Find all unique struct types from struct-type and struct-field sections
+        struct_type_lines = self._get_all_section_lines(sections, 'struct-type')
+        for line in struct_type_lines:
+            parts = line.split(',', 1)
+            if len(parts) >= 2:
+                struct_name = parts[1].strip()
+                if struct_name not in struct_types:
+                    struct_types[struct_name] = {'fields': []}
+
+        # Collect struct fields
+        struct_field_lines = self._get_all_section_lines(sections, 'struct-field')
+        current_struct = None
+        if struct_types:
+            # Get the first struct type as the current one
+            current_struct = list(struct_types.keys())[0]
+
+        for line in struct_field_lines:
+            parts = line.split(',', 2)
+            if len(parts) >= 3:
+                field_name = parts[0].strip()
+                field_type = parts[1].strip()
+                field_desc = parts[2].strip()
+                if current_struct and current_struct in struct_types:
+                    struct_types[current_struct]['fields'].append({
+                        'name': field_name,
+                        'type': field_type,
+                        'desc': field_desc
+                    })
+
+        # Generate struct specifications
+        if struct_types:
+            struct_count = len(struct_types)
+            self.data += f"\n\tKAPI_STRUCT_SPEC_COUNT({struct_count})\n"
+
+            struct_idx = 0
+            for struct_name, struct_info in struct_types.items():
+                self.data += f"\n\tKAPI_STRUCT_SPEC({struct_idx}, {self._format_macro_param(struct_name)}, "
+                self.data += f"{self._format_macro_param(f'Structure specification for {struct_name}')})\n"
+
+                # Add field count
+                field_count = len(struct_info['fields'])
+                if field_count > 0:
+                    self.data += f"\t\tKAPI_STRUCT_FIELD_COUNT({field_count})\n"
+
+                # Add fields
+                field_idx = 0
+                for field in struct_info['fields']:
+                    # Map common C types to KAPI types
+                    kapi_type = "KAPI_TYPE_CUSTOM"
+                    if field['type'] in ['__u32', '__u64', '__s32', '__s64', 'u32', 'u64', 's32', 's64']:
+                        if field['type'].startswith('__s') or field['type'].startswith('s'):
+                            kapi_type = "KAPI_TYPE_INT"
+                        else:
+                            kapi_type = "KAPI_TYPE_UINT"
+
+                    self.data += f"\n\t\tKAPI_STRUCT_FIELD({field_idx}, {self._format_macro_param(field['name'])}, "
+                    self.data += f"{kapi_type}, {self._format_macro_param(field['type'])}, "
+                    self.data += f"{self._format_macro_param(field['desc'])})\n"
+
+                    # Add field constraints from other sections
+                    field_range_lines = self._get_all_section_lines(sections, 'struct-field-range')
+                    for range_line in field_range_lines:
+                        if range_line.startswith(field['name'] + ','):
+                            range_parts = range_line.split(',')
+                            if len(range_parts) >= 3:
+                                self.data += f"\t\t\tKAPI_FIELD_CONSTRAINT_RANGE({range_parts[1].strip()}, {range_parts[2].strip()})\n"
+
+                    # Add enum constraints if defined
+                    field_enum_lines = self._get_all_section_lines(sections, 'struct-field-enum')
+                    for enum_line in field_enum_lines:
+                        if enum_line.startswith(field['name'] + ','):
+                            enum_parts = enum_line.split(',', 1)
+                            if len(enum_parts) >= 2:
+                                self.data += f"\t\t\tKAPI_FIELD_CONSTRAINT_ENUM({self._format_macro_param(enum_parts[1].strip())})\n"
+
+                    self.data += "\t\tKAPI_STRUCT_FIELD_END\n"
+                    field_idx += 1
+
+                self.data += "\tKAPI_STRUCT_SPEC_END\n"
+                struct_idx += 1
+
+
+        # Version information is not supported in the current KAPI infrastructure
+        # The 'since' and 'since-version' sections are ignored for now
+
+        self.data += "\nKAPI_END_SPEC;\n\n"
+
+    def out_enum(self, fname, name, args):
+        """Skip enum output for API specs"""
+        pass
+
+    def out_typedef(self, fname, name, args):
+        """Skip typedef output for API specs"""
+        pass
+
+    def out_struct(self, fname, name, args):
+        """Skip struct output for API specs"""
+        pass
+
+    def out_doc(self, fname, name, args):
+        """Skip DOC block output for API specs"""
+        pass
\ No newline at end of file
diff --git a/scripts/lib/kdoc/kdoc_output.py b/scripts/lib/kdoc/kdoc_output.py
index 86102e628d917..0c47cffc25e9f 100644
--- a/scripts/lib/kdoc/kdoc_output.py
+++ b/scripts/lib/kdoc/kdoc_output.py
@@ -127,7 +127,10 @@ class OutputFormat:
         warnings = args.get('warnings', [])
 
         for log_msg in warnings:
-            self.config.warning(log_msg)
+            # Skip numeric warnings (line numbers) which are false positives
+            # from parameter-specific sections like "param-constraint: name, value"
+            if not isinstance(log_msg, int):
+                self.config.warning(log_msg)
 
     def check_doc(self, name, args):
         """Check if DOC should be output"""
diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py
index 062453eefc7a3..4b43023f2a297 100644
--- a/scripts/lib/kdoc/kdoc_parser.py
+++ b/scripts/lib/kdoc/kdoc_parser.py
@@ -43,7 +43,25 @@ doc_decl = doc_com + KernRe(r'(\w+)', cache=False)
 # while trying to not match literal block starts like "example::"
 #
 doc_sect = doc_com + \
-            KernRe(r'\s*(\@[.\w]+|\@\.\.\.|description|context|returns?|notes?|examples?)\s*:([^:].*)?$',
+            KernRe(r'\s*(\@[.\w\-]+|\@\.\.\.|description|context|returns?|notes?|examples?|' +
+                   r'@...i-type|@...i-version|@...ram-type|@...ram-flags|@...ram-constraint|@...ram-range|@...ram-mask|' +
+                   r'@...ram-constraint-type|@...ram-size|@...ram-alignment|@...ram-enum|@...ram-validate|' +
+                   r'@...ram-size-param|@...ram-size-multiplier|' +
+                   r'@...ruct-type|@...ruct-field|@...ruct-field-[a-z\-]+|' +
+                   r'@...lidation-group|@...lidation-policy|@...lidation-flag|@...lidation-rule|' +
+                   r'@...ror-code|@...ror-condition|@...ror-count|' +
+                   r'@...pability|@...pability-allows|@...pability-without|@...pability-condition|@...pability-priority|' +
+                   r'@...pability-count|' +
+                   r'@...gnal|@...gnal-direction|@...gnal-action|@...gnal-condition|@...gnal-desc|@...gnal-error|' +
+                   r'@...gnal-timing|@...gnal-priority|@...gnal-interruptible|@...gnal-state-req|@...gnal-count|' +
+                   r'@...ck|@...ck-type|@...ck-acquired|@...ck-released|@...ck-desc|@...ck-count|' +
+                   r'@...nce|@...nce-version|' +
+                   r'@...ntext-flags|@...turn-type|@...turn-check|@...turn-check-type|@...turn-success|' +
+                   r'@...ng-desc|@...nstraint|@...nstraint-expr|@...nstraint-count|' +
+                   r'@...de-effect|@...de-effect-type|@...de-effect-desc|@...de-effect-condition|' +
+                   r'@...de-effect-reversible|@...de-effect-count|' +
+                   r'@...ate-trans|@...ate-trans-desc|@...ate-trans-count|' +
+                   r'@...ram-count|@...pi-.*)\s*:([^:].*)?$',
                 flags=re.I, cache=False)
 
 doc_content = doc_com_body + KernRe(r'(.*)', cache=False)
@@ -159,7 +177,39 @@ class KernelEntry:
         name = self.section
         contents = self.contents
 
-        if type_param.match(name):
+        # Check if this is an API specification section
+        # These should always be treated as sections, not parameters
+        api_sections = {
+            'api-type', 'api-version', 'param-type', 'param-flags', 'param-constraint',
+            'param-range', 'param-mask', 'param-constraint-type', 'param-size',
+            'param-alignment', 'param-enum', 'param-validate', 'param-size-param',
+            'param-size-multiplier', 'struct-type', 'struct-field', 'struct-field-range',
+            'struct-field-enum', 'struct-field-mask', 'struct-field-policy',
+            'struct-field-version', 'struct-field-flag', 'struct-field-validate',
+            'validation-group', 'validation-policy', 'validation-flag', 'validation-rule',
+            'error-code', 'error-condition', 'error-count',
+            'capability', 'capability-allows', 'capability-without', 'capability-condition',
+            'capability-priority', 'capability-count', 'signal', 'signal-direction',
+            'signal-action', 'signal-condition', 'signal-desc', 'signal-error',
+            'signal-timing', 'signal-priority', 'signal-interruptible', 'signal-state-req',
+            'signal-count', 'lock', 'lock-type', 'lock-acquired', 'lock-released',
+            'lock-desc', 'lock-count', 'since', 'since-version', 'context-flags',
+            'return-type', 'return-check', 'return-check-type', 'return-success',
+            'long-desc', 'constraint', 'constraint-expr', 'constraint-count',
+            'side-effect', 'side-effect-type', 'side-effect-desc', 'side-effect-condition',
+            'side-effect-reversible', 'side-effect-count', 'state-trans',
+            'state-trans-desc', 'state-trans-count', 'param-count',
+            # Also include notes and examples which can appear with or without @
+            'notes', 'note', 'examples', 'example'
+        }
+
+        # Check if name starts with @ and matches kapi-.* pattern
+        is_api_section = (name.lower() in api_sections or
+                         (name.startswith('@') and name[1:].lower() in api_sections) or
+                         (name.lower().startswith('kapi-')) or
+                         (name.lower().startswith('@...i-')))
+
+        if not is_api_section and type_param.match(name):
             name = type_param.group(1)
 
             self.parameterdescs[name] = contents
-- 
2.39.5


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ