[<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