* [PATCH 0/9] Kernel API Specification Framework
@ 2026-03-13 15:09 Sasha Levin
2026-03-13 15:09 ` [PATCH 1/9] kernel/api: introduce kernel API specification framework Sasha Levin
` (9 more replies)
0 siblings, 10 replies; 37+ messages in thread
From: Sasha Levin @ 2026-03-13 15:09 UTC (permalink / raw)
To: linux-api, linux-kernel
Cc: linux-doc, linux-fsdevel, linux-kbuild, linux-kselftest,
workflows, tools, x86, Thomas Gleixner, Paul E. McKenney,
Greg Kroah-Hartman, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann, Sasha Levin
This proposal introduces machinery for documenting kernel APIs, addressing the
long-standing challenge of maintaining stable interfaces between the kernel and
user-space programs. Despite the kernel's commitment to never breaking user
space, the lack of machine-readable API specifications has led to breakages and
across system calls and IOCTLs.
Specifications can document parameter types, valid ranges, constraints, and
alignment requirements. They capture return value semantics including success
conditions and error codes with their meaning. Execution context requirements,
capabilities, locking constraints, signal handling behavior, and side effects
can all be formally specified.
These specifications live alongside the code they document and are both
human-readable and machine-parseable. They can be validated at runtime when
CONFIG_KAPI_RUNTIME_CHECKS is enabled, exported via debugfs for userspace
tools, and extracted from either vmlinux or source code.
This enables static analysis tools to verify userspace API usage at compile
time, test generation based on formal specifications, consistent error handling
validation, automated documentation generation, and formal verification of
kernel interfaces.
The implementation includes a core framework with ELF section storage,
kerneldoc integration for inline specification, a debugfs interface for runtime
querying, and a Rust-based extraction tool (tools/kapi) supporting JSON, RST,
and plain text output formats. Example specifications are provided for the four
fundamental file syscalls (sys_open, sys_close, sys_read, sys_write). The
series also includes a KUnit test suite with 38 tests and a runtime
verification selftest with 29+ TAP tests.
The series with runtime testing enabled (CONFIG_KAPI_RUNTIME_CHECKS=y)
currently survives LTP tests in a KVM VM.
Changes since RFC v5:
- Streamlined example specs: focus on open/close/read/write to start with.
- Added KUnit test suite.
- Added runtime verification selftest.
- Fixed kernel test robot warnings from v5: fixed "document isn't included in
any toctree" (kernel-api-spec.rst now properly added to
Documentation/dev-tools/index.rst), fixed sparse "non size-preserving
integer to pointer cast" warnings in kernel_api_spec.c.
- Rebased on v7.0-rc1.
References:
RFC v5: https://lore.kernel.org/lkml/20251218204239.4159453-1-sashal@kernel.org/
RFC v4: https://lore.kernel.org/lkml/20250825181434.3340805-1-sashal@kernel.org/
RFC v3: https://lore.kernel.org/lkml/20250711114248.2288591-1-sashal@kernel.org/
RFC v2: https://lore.kernel.org/lkml/20250624180742.5795-1-sashal@kernel.org/
RFC v1: https://lore.kernel.org/lkml/20250614134858.790460-1-sashal@kernel.org/
Sasha Levin (9):
kernel/api: introduce kernel API specification framework
kernel/api: enable kerneldoc-based API specifications
kernel/api: add debugfs interface for kernel API specifications
tools/kapi: Add kernel API specification extraction tool
kernel/api: add API specification for sys_open
kernel/api: add API specification for sys_close
kernel/api: add API specification for sys_read
kernel/api: add API specification for sys_write
kernel/api: add runtime verification selftest
.gitignore | 1 +
Documentation/dev-tools/index.rst | 1 +
Documentation/dev-tools/kernel-api-spec.rst | 629 +++++++
MAINTAINERS | 12 +
arch/x86/include/asm/syscall_wrapper.h | 40 +
fs/open.c | 576 +++++-
fs/read_write.c | 687 +++++++
include/asm-generic/vmlinux.lds.h | 28 +
include/linux/kernel_api_spec.h | 1580 +++++++++++++++++
include/linux/syscall_api_spec.h | 192 ++
include/linux/syscalls.h | 39 +
init/Kconfig | 2 +
kernel/Makefile | 3 +
kernel/api/.gitignore | 2 +
kernel/api/Kconfig | 70 +
kernel/api/Makefile | 14 +
kernel/api/kapi_debugfs.c | 503 ++++++
kernel/api/kapi_kunit.c | 536 ++++++
kernel/api/kernel_api_spec.c | 1277 +++++++++++++
scripts/Makefile.build | 31 +
scripts/Makefile.clean | 3 +
tools/docs/kernel-doc | 5 +
tools/kapi/.gitignore | 4 +
tools/kapi/Cargo.toml | 19 +
tools/kapi/src/extractor/debugfs.rs | 581 ++++++
tools/kapi/src/extractor/kerneldoc_parser.rs | 1554 ++++++++++++++++
tools/kapi/src/extractor/mod.rs | 463 +++++
tools/kapi/src/extractor/source_parser.rs | 405 +++++
.../src/extractor/vmlinux/binary_utils.rs | 505 ++++++
.../src/extractor/vmlinux/magic_finder.rs | 112 ++
tools/kapi/src/extractor/vmlinux/mod.rs | 842 +++++++++
tools/kapi/src/formatter/json.rs | 727 ++++++++
tools/kapi/src/formatter/mod.rs | 140 ++
tools/kapi/src/formatter/plain.rs | 708 ++++++++
tools/kapi/src/formatter/rst.rs | 852 +++++++++
tools/kapi/src/main.rs | 119 ++
tools/lib/python/kdoc/kdoc_apispec.py | 887 +++++++++
tools/lib/python/kdoc/kdoc_output.py | 9 +-
tools/lib/python/kdoc/kdoc_parser.py | 86 +-
tools/testing/selftests/kapi/Makefile | 7 +
tools/testing/selftests/kapi/kapi_test_util.h | 31 +
tools/testing/selftests/kapi/test_kapi.c | 1021 +++++++++++
42 files changed, 15294 insertions(+), 9 deletions(-)
create mode 100644 Documentation/dev-tools/kernel-api-spec.rst
create mode 100644 include/linux/kernel_api_spec.h
create mode 100644 include/linux/syscall_api_spec.h
create mode 100644 kernel/api/.gitignore
create mode 100644 kernel/api/Kconfig
create mode 100644 kernel/api/Makefile
create mode 100644 kernel/api/kapi_debugfs.c
create mode 100644 kernel/api/kapi_kunit.c
create mode 100644 kernel/api/kernel_api_spec.c
create mode 100644 tools/kapi/.gitignore
create mode 100644 tools/kapi/Cargo.toml
create mode 100644 tools/kapi/src/extractor/debugfs.rs
create mode 100644 tools/kapi/src/extractor/kerneldoc_parser.rs
create mode 100644 tools/kapi/src/extractor/mod.rs
create mode 100644 tools/kapi/src/extractor/source_parser.rs
create mode 100644 tools/kapi/src/extractor/vmlinux/binary_utils.rs
create mode 100644 tools/kapi/src/extractor/vmlinux/magic_finder.rs
create mode 100644 tools/kapi/src/extractor/vmlinux/mod.rs
create mode 100644 tools/kapi/src/formatter/json.rs
create mode 100644 tools/kapi/src/formatter/mod.rs
create mode 100644 tools/kapi/src/formatter/plain.rs
create mode 100644 tools/kapi/src/formatter/rst.rs
create mode 100644 tools/kapi/src/main.rs
create mode 100644 tools/lib/python/kdoc/kdoc_apispec.py
create mode 100644 tools/testing/selftests/kapi/Makefile
create mode 100644 tools/testing/selftests/kapi/kapi_test_util.h
create mode 100644 tools/testing/selftests/kapi/test_kapi.c
--
2.51.0
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH 1/9] kernel/api: introduce kernel API specification framework
2026-03-13 15:09 [PATCH 0/9] Kernel API Specification Framework Sasha Levin
@ 2026-03-13 15:09 ` Sasha Levin
2026-03-17 17:49 ` Jonathan Corbet
2026-03-13 15:09 ` [PATCH 2/9] kernel/api: enable kerneldoc-based API specifications Sasha Levin
` (8 subsequent siblings)
9 siblings, 1 reply; 37+ messages in thread
From: Sasha Levin @ 2026-03-13 15:09 UTC (permalink / raw)
To: linux-api, linux-kernel
Cc: linux-doc, linux-fsdevel, linux-kbuild, linux-kselftest,
workflows, tools, x86, Thomas Gleixner, Paul E. McKenney,
Greg Kroah-Hartman, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann, Sasha Levin
Add a framework for formally documenting kernel APIs with inline
specifications. This framework provides:
- Structured API documentation with parameter specifications, return
values, error conditions, and execution context requirements
- Runtime validation capabilities for debugging (CONFIG_KAPI_RUNTIME_CHECKS)
- Export of specifications via debugfs for tooling integration
- Support for both internal kernel APIs and system calls
The framework stores specifications in a dedicated ELF section and
provides infrastructure for:
- Compile-time validation of specifications
- Runtime querying of API documentation
- Machine-readable export formats
- Integration with existing SYSCALL_DEFINE macros
This commit introduces the core infrastructure without modifying any
existing APIs. Subsequent patches will add specifications to individual
subsystems.
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
.gitignore | 1 +
Documentation/dev-tools/index.rst | 1 +
Documentation/dev-tools/kernel-api-spec.rst | 482 ++++++
MAINTAINERS | 11 +
arch/x86/include/asm/syscall_wrapper.h | 40 +
include/asm-generic/vmlinux.lds.h | 28 +
include/linux/kernel_api_spec.h | 1580 +++++++++++++++++++
include/linux/syscall_api_spec.h | 192 +++
include/linux/syscalls.h | 39 +
init/Kconfig | 2 +
kernel/Makefile | 3 +
kernel/api/.gitignore | 2 +
kernel/api/Kconfig | 70 +
kernel/api/Makefile | 14 +
kernel/api/kapi_kunit.c | 536 +++++++
kernel/api/kernel_api_spec.c | 1277 +++++++++++++++
scripts/generate_api_specs.sh | 18 +
17 files changed, 4296 insertions(+)
create mode 100644 Documentation/dev-tools/kernel-api-spec.rst
create mode 100644 include/linux/kernel_api_spec.h
create mode 100644 include/linux/syscall_api_spec.h
create mode 100644 kernel/api/.gitignore
create mode 100644 kernel/api/Kconfig
create mode 100644 kernel/api/Makefile
create mode 100644 kernel/api/kapi_kunit.c
create mode 100644 kernel/api/kernel_api_spec.c
create mode 100755 scripts/generate_api_specs.sh
diff --git a/.gitignore b/.gitignore
index 3a7241c941f5e..7130001e444f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@
#
.*
*.a
+*.apispec.h
*.asn1.[ch]
*.bin
*.bz2
diff --git a/Documentation/dev-tools/index.rst b/Documentation/dev-tools/index.rst
index 59cbb77b33ff4..8d3768645d96c 100644
--- a/Documentation/dev-tools/index.rst
+++ b/Documentation/dev-tools/index.rst
@@ -36,6 +36,7 @@ Documentation/process/debugging/index.rst
kunit/index
ktap
checkuapi
+ kernel-api-spec
gpio-sloppy-logic-analyzer
autofdo
propeller
diff --git a/Documentation/dev-tools/kernel-api-spec.rst b/Documentation/dev-tools/kernel-api-spec.rst
new file mode 100644
index 0000000000000..7c0c1694f1f4a
--- /dev/null
+++ b/Documentation/dev-tools/kernel-api-spec.rst
@@ -0,0 +1,482 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+======================================
+Kernel API Specification Framework
+======================================
+
+:Author: Sasha Levin <sashal@kernel.org>
+:Date: June 2025
+
+.. contents:: Table of Contents
+ :depth: 3
+ :local:
+
+Introduction
+============
+
+The Kernel API Specification Framework (KAPI) provides a comprehensive system for
+formally documenting, validating, and introspecting kernel APIs. This framework
+addresses the long-standing challenge of maintaining accurate, machine-readable
+documentation for the thousands of internal kernel APIs and system calls.
+
+Purpose and Goals
+-----------------
+
+The framework aims to:
+
+1. **Improve API Documentation**: Provide structured, inline documentation that
+ lives alongside the code and is maintained as part of the development process.
+
+2. **Enable Runtime Validation**: Optionally validate API usage at runtime to catch
+ common programming errors during development and testing.
+
+3. **Support Tooling**: Export API specifications in machine-readable formats for
+ use by static analyzers, documentation generators, and development tools.
+
+4. **Enhance Debugging**: Provide detailed API information at runtime through debugfs
+ for debugging and introspection.
+
+5. **Formalize Contracts**: Explicitly document API contracts including parameter
+ constraints, execution contexts, locking requirements, and side effects.
+
+Architecture Overview
+=====================
+
+Components
+----------
+
+The framework consists of several key components:
+
+1. **Core Framework** (``kernel/api/kernel_api_spec.c``)
+
+ - API specification registration and storage
+ - Runtime validation engine
+ - Specification lookup and querying
+
+2. **DebugFS Interface** (``kernel/api/kapi_debugfs.c``)
+
+ - Runtime introspection via ``/sys/kernel/debug/kapi/``
+ - JSON and XML export formats
+ - Per-API detailed information
+
+3. **IOCTL Support** (``kernel/api/ioctl_validation.c``)
+
+ - Extended framework for IOCTL specifications
+ - Automatic validation wrappers
+ - Structure field validation
+
+4. **Specification Macros** (``include/linux/kernel_api_spec.h``)
+
+ - Declarative macros for API documentation
+ - Type-safe parameter specifications
+ - Context and constraint definitions
+
+Data Model
+----------
+
+The framework uses a hierarchical data model::
+
+ kernel_api_spec
+ ├── Basic Information
+ │ ├── name (API function name)
+ │ ├── version (specification version)
+ │ ├── description (human-readable description)
+ │ └── kernel_version (when API was introduced)
+ │
+ ├── Parameters (up to 16)
+ │ └── kapi_param_spec
+ │ ├── name
+ │ ├── type (int, pointer, string, etc.)
+ │ ├── direction (in, out, inout)
+ │ ├── constraints (range, mask, enum values)
+ │ └── validation rules
+ │
+ ├── Return Value
+ │ └── kapi_return_spec
+ │ ├── type
+ │ ├── success conditions
+ │ └── validation rules
+ │
+ ├── Error Conditions (up to 32)
+ │ └── kapi_error_spec
+ │ ├── error code
+ │ ├── condition description
+ │ └── recovery advice
+ │
+ ├── Execution Context
+ │ ├── allowed contexts (process, interrupt, etc.)
+ │ ├── locking requirements
+ │ └── preemption/interrupt state
+ │
+ └── Side Effects
+ ├── memory allocation
+ ├── state changes
+ └── signal handling
+
+Usage Guide
+===========
+
+Basic API Specification
+-----------------------
+
+API specifications are written as KAPI-annotated kerneldoc comments directly in
+the source file, immediately preceding the function implementation. The ``kapi``
+tool extracts these annotations to produce structured specifications.
+
+.. code-block:: c
+
+ /**
+ * kmalloc - allocate kernel memory
+ * @size: Number of bytes to allocate
+ * @flags: Allocation flags (GFP_*)
+ *
+ * context-flags: KAPI_CTX_PROCESS | KAPI_CTX_SOFTIRQ | KAPI_CTX_HARDIRQ
+ * param-count: 2
+ *
+ * param: size
+ * type: KAPI_TYPE_UINT
+ * flags: KAPI_PARAM_IN
+ * constraint-type: KAPI_CONSTRAINT_RANGE
+ * range: 0, KMALLOC_MAX_SIZE
+ *
+ * error: ENOMEM, Out of memory
+ * desc: Insufficient memory available for the requested allocation.
+ */
+ void *kmalloc(size_t size, gfp_t flags)
+ {
+ /* Implementation */
+ }
+
+Alternatively, specifications can be defined using the ``DEFINE_KERNEL_API_SPEC``
+macro for compiled-in specs that are stored in the ``.kapi_specs`` ELF section:
+
+.. code-block:: c
+
+ #include <linux/kernel_api_spec.h>
+
+ DEFINE_KERNEL_API_SPEC(sys_open)
+ KAPI_DESCRIPTION("Open or create a file")
+ KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+ /* ... parameter, error, constraint definitions ... */
+ KAPI_END_SPEC
+
+System Call Specification
+-------------------------
+
+System calls are documented inline in the implementation file (e.g., ``fs/open.c``)
+using KAPI-annotated kerneldoc comments. When ``CONFIG_KAPI_RUNTIME_CHECKS`` is
+enabled, the ``SYSCALL_DEFINEx`` macros automatically look up the specification
+and validate parameters before and after the syscall executes.
+
+IOCTL Specification
+-------------------
+
+IOCTLs use the same annotation approach with additional structure field
+specifications
+
+Runtime Validation
+==================
+
+Enabling Validation
+-------------------
+
+Runtime validation is controlled by kernel configuration:
+
+1. Enable ``CONFIG_KAPI_SPEC`` to build the framework
+2. Enable ``CONFIG_KAPI_RUNTIME_CHECKS`` for runtime validation
+3. Optionally enable ``CONFIG_KAPI_SPEC_DEBUGFS`` for debugfs interface
+
+Validation Modes
+----------------
+
+When ``CONFIG_KAPI_RUNTIME_CHECKS`` is enabled, all registered API specifications
+are validated automatically at call time. The framework checks parameter constraints,
+execution context, and return values. Parameter violations are reported via
+``pr_warn_ratelimited`` and return value violations via ``WARN_ONCE`` to avoid
+flooding the kernel log.
+
+Custom Validators
+-----------------
+
+Parameters can use the ``KAPI_CONSTRAINT_CUSTOM`` constraint type to register
+custom validation functions via the ``validate`` field in the constraint spec:
+
+.. code-block:: c
+
+ static bool validate_buffer_size(s64 value)
+ {
+ size_t size = (size_t)value;
+
+ return size > 0 && size <= MAX_BUFFER_SIZE;
+ }
+
+ /* In the constraint definition: */
+ .type = KAPI_CONSTRAINT_CUSTOM,
+ .validate = validate_buffer_size,
+
+DebugFS Interface
+=================
+
+The debugfs interface provides runtime access to API specifications:
+
+Directory Structure
+-------------------
+
+::
+
+ /sys/kernel/debug/kapi/
+ ├── apis/ # All registered APIs
+ │ ├── kmalloc/
+ │ │ ├── specification # Human-readable spec
+ │ │ ├── json # JSON format
+ │ │ └── xml # XML format
+ │ └── open/
+ │ └── ...
+ ├── summary # Overview of all APIs
+ ├── validation/ # Validation controls
+ │ ├── enabled # Global enable/disable
+ │ ├── level # Validation level
+ │ └── stats # Validation statistics
+ └── export/ # Bulk export options
+ ├── all.json # All specs in JSON
+ └── all.xml # All specs in XML
+
+Usage Examples
+--------------
+
+Query specific API::
+
+ $ cat /sys/kernel/debug/kapi/apis/kmalloc/specification
+ API: kmalloc
+ Version: 3.0
+ Description: Allocate kernel memory
+
+ Parameters:
+ [0] size (size_t, in): Number of bytes to allocate
+ Range: 0 - 4194304
+ [1] flags (flags, in): Allocation flags (GFP_*)
+ Mask: 0x1ffffff
+
+ Returns: pointer - Pointer to allocated memory or NULL
+
+ Errors:
+ ENOMEM: Out of memory
+
+ Context: process, softirq, hardirq
+
+ Side Effects:
+ - Allocates memory from kernel heap
+
+Export all specifications::
+
+ $ cat /sys/kernel/debug/kapi/export/all.json > kernel-apis.json
+
+Enable validation for specific API::
+
+ $ echo 1 > /sys/kernel/debug/kapi/apis/kmalloc/validate
+
+Performance Considerations
+==========================
+
+Memory Overhead
+---------------
+
+Each API specification consumes approximately 400-450KB of memory due to the
+fixed-size arrays in ``struct kernel_api_spec``. With the current 4 syscall
+specifications, total memory usage is approximately 1.7MB. Consider:
+
+1. Building with ``CONFIG_KAPI_SPEC=n`` for production kernels
+2. Using ``__init`` annotations for APIs only used during boot
+3. Implementing lazy loading for rarely used specifications
+
+Runtime Overhead
+----------------
+
+When ``CONFIG_KAPI_RUNTIME_CHECKS`` is enabled:
+
+- Each validated API call adds 50-200ns overhead
+- Complex validations (custom validators) may add more
+- Use validation only in development/testing kernels
+
+Optimization Strategies
+-----------------------
+
+1. **Compile-time optimization**: When validation is disabled, all
+ validation code is optimized away by the compiler.
+
+2. **Selective validation**: Enable validation only for specific APIs
+ or subsystems under test.
+
+3. **Caching**: The framework caches validation results for repeated
+ calls with identical parameters.
+
+Documentation Generation
+------------------------
+
+The framework exports specifications via debugfs that can be used
+to generate documentation. Tools for automatic documentation generation
+from specifications are planned for future development.
+
+IDE Integration
+---------------
+
+Modern IDEs can use the JSON export for:
+
+- Parameter hints
+- Type checking
+- Context validation
+- Error code documentation
+
+Testing Framework
+-----------------
+
+The framework includes test helpers::
+
+ #ifdef CONFIG_KAPI_TESTING
+ /* Verify API behaves according to specification */
+ kapi_test_api("kmalloc", test_cases);
+ #endif
+
+Best Practices
+==============
+
+Writing Specifications
+----------------------
+
+1. **Be Comprehensive**: Document all parameters, errors, and side effects
+2. **Keep Updated**: Update specs when API behavior changes
+3. **Use Examples**: Include usage examples in descriptions
+4. **Validate Constraints**: Define realistic constraints for parameters
+5. **Document Context**: Clearly specify allowed execution contexts
+
+Maintenance
+-----------
+
+1. **Version Specifications**: Increment version when API changes
+2. **Deprecation**: Mark deprecated APIs and suggest replacements
+3. **Cross-reference**: Link related APIs in descriptions
+4. **Test Specifications**: Verify specs match implementation
+
+Common Patterns
+---------------
+
+**Optional Parameters**::
+
+ KAPI_PARAM(2, "optional_arg", "void *", "Optional argument (may be NULL)")
+ KAPI_PARAM_TYPE(KAPI_TYPE_PTR)
+ KAPI_PARAM_FLAGS(KAPI_PARAM_IN | KAPI_PARAM_OPTIONAL)
+ KAPI_PARAM_END
+
+**Buffer with Size Parameter**::
+
+ KAPI_PARAM(1, "buf", "char __user *", "User-space buffer")
+ KAPI_PARAM_TYPE(KAPI_TYPE_USER_PTR)
+ KAPI_PARAM_FLAGS(KAPI_PARAM_OUT | KAPI_PARAM_USER)
+ KAPI_PARAM_CONSTRAINT_TYPE(KAPI_CONSTRAINT_BUFFER)
+ KAPI_PARAM_SIZE_PARAM(2)
+ KAPI_PARAM_END
+
+**Callback Functions**::
+
+ KAPI_PARAM(1, "callback", "int (*)(void *)", "Callback function")
+ KAPI_PARAM_TYPE(KAPI_TYPE_FUNC_PTR)
+ KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+ KAPI_PARAM_END
+
+Troubleshooting
+===============
+
+Common Issues
+-------------
+
+**Specification Not Found**::
+
+ kernel: KAPI: Specification for 'my_api' not found
+
+ Solution: Ensure KAPI_DEFINE_SPEC is in the same translation unit
+ as the function implementation.
+
+**Validation Failures**::
+
+ kernel: KAPI: Validation failed for kmalloc parameter 'size':
+ value 5242880 exceeds maximum 4194304
+
+ Solution: Check parameter constraints or adjust specification if
+ the constraint is incorrect.
+
+**Build Errors**::
+
+ error: 'KAPI_TYPE_UNKNOWN' undeclared
+
+ Solution: Include <linux/kernel_api_spec.h> and ensure
+ CONFIG_KAPI_SPEC is enabled.
+
+Debug Options
+-------------
+
+Enable verbose debugging::
+
+ echo 8 > /proc/sys/kernel/printk
+ echo 1 > /sys/kernel/debug/kapi/debug/verbose
+
+Future Directions
+=================
+
+Planned Features
+----------------
+
+1. **Automatic Extraction**: Tool to extract specifications from existing
+ kernel-doc comments
+
+2. **Contract Verification**: Static analysis to verify implementation
+ matches specification
+
+3. **Performance Profiling**: Measure actual API performance against
+ documented expectations
+
+4. **Fuzzing Integration**: Use specifications to guide intelligent
+ fuzzing of kernel APIs
+
+5. **Version Compatibility**: Track API changes across kernel versions
+
+Research Areas
+--------------
+
+1. **Formal Verification**: Use specifications for mathematical proofs
+ of correctness
+
+2. **Runtime Monitoring**: Detect specification violations in production
+ with minimal overhead
+
+3. **API Evolution**: Analyze how kernel APIs change over time
+
+4. **Security Applications**: Use specifications for security policy
+ enforcement
+
+Contributing
+============
+
+Submitting Specifications
+-------------------------
+
+1. Add specifications to the same file as the API implementation
+2. Follow existing patterns and naming conventions
+3. Test with CONFIG_KAPI_RUNTIME_CHECKS enabled
+4. Verify debugfs output is correct
+5. Run scripts/checkpatch.pl on your changes
+
+Review Criteria
+---------------
+
+Specifications will be reviewed for:
+
+1. **Completeness**: All parameters and errors documented
+2. **Accuracy**: Specification matches implementation
+3. **Clarity**: Descriptions are clear and helpful
+4. **Consistency**: Follows framework conventions
+5. **Performance**: No unnecessary runtime overhead
+
+Contact
+-------
+
+- Maintainer: Sasha Levin <sashal@kernel.org>
diff --git a/MAINTAINERS b/MAINTAINERS
index 55af015174a54..6fa403d620aab 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13810,6 +13810,17 @@ W: https://linuxtv.org
T: git git://linuxtv.org/media.git
F: drivers/media/radio/radio-keene*
+KERNEL API SPECIFICATION FRAMEWORK (KAPI)
+M: Sasha Levin <sashal@kernel.org>
+L: linux-api@vger.kernel.org
+S: Maintained
+F: Documentation/dev-tools/kernel-api-spec.rst
+F: include/linux/kernel_api_spec.h
+F: include/linux/syscall_api_spec.h
+F: kernel/api/
+F: tools/kapi/
+F: tools/lib/python/kdoc/kdoc_apispec.py
+
KERNEL AUTOMOUNTER
M: Ian Kent <raven@themaw.net>
L: autofs@vger.kernel.org
diff --git a/arch/x86/include/asm/syscall_wrapper.h b/arch/x86/include/asm/syscall_wrapper.h
index 7e88705e907f4..2262b9d7ab097 100644
--- a/arch/x86/include/asm/syscall_wrapper.h
+++ b/arch/x86/include/asm/syscall_wrapper.h
@@ -7,6 +7,14 @@
#define _ASM_X86_SYSCALL_WRAPPER_H
#include <asm/ptrace.h>
+#ifdef CONFIG_KAPI_RUNTIME_CHECKS
+struct kernel_api_spec;
+extern const struct kernel_api_spec *kapi_get_spec(const char *name);
+extern int kapi_validate_syscall_params(const struct kernel_api_spec *spec,
+ const s64 *params, int param_count);
+extern int kapi_validate_syscall_return(const struct kernel_api_spec *spec,
+ s64 retval);
+#endif
extern long __x64_sys_ni_syscall(const struct pt_regs *regs);
extern long __ia32_sys_ni_syscall(const struct pt_regs *regs);
@@ -220,6 +228,37 @@ extern long __ia32_sys_ni_syscall(const struct pt_regs *regs);
#endif /* CONFIG_COMPAT */
+#ifdef CONFIG_KAPI_RUNTIME_CHECKS
+#define __SYSCALL_DEFINEx(x, name, ...) \
+ static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
+ static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\
+ static inline long __do_kapi_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \
+ __X64_SYS_STUBx(x, name, __VA_ARGS__) \
+ __IA32_SYS_STUBx(x, name, __VA_ARGS__) \
+ static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \
+ { \
+ long ret = __do_kapi_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));\
+ __MAP(x,__SC_TEST,__VA_ARGS__); \
+ __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \
+ return ret; \
+ } \
+ static inline long __do_kapi_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))\
+ { \
+ const struct kernel_api_spec *__spec = kapi_get_spec("sys" #name); \
+ if (__spec) { \
+ s64 __params[x] = { __MAP(x,__SC_CAST_TO_S64,__VA_ARGS__) }; \
+ int __ret = kapi_validate_syscall_params(__spec, __params, x); \
+ if (__ret) \
+ return __ret; \
+ } \
+ long ret = __do_sys##name(__MAP(x,__SC_ARGS,__VA_ARGS__)); \
+ if (__spec) { \
+ kapi_validate_syscall_return(__spec, (s64)ret); \
+ } \
+ return ret; \
+ } \
+ static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
+#else /* !CONFIG_KAPI_RUNTIME_CHECKS */
#define __SYSCALL_DEFINEx(x, name, ...) \
static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\
@@ -233,6 +272,7 @@ extern long __ia32_sys_ni_syscall(const struct pt_regs *regs);
return ret; \
} \
static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
+#endif /* CONFIG_KAPI_RUNTIME_CHECKS */
/*
* As the generic SYSCALL_DEFINE0() macro does not decode any parameters for
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index eeb070f330bdb..77f6dd10b7cd5 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -296,6 +296,33 @@
#define TRACE_SYSCALLS()
#endif
+#ifdef CONFIG_KAPI_SPEC
+/*
+ * KAPI_SPECS - Include kernel API specifications in current section
+ *
+ * The .kapi_specs input section has 32-byte alignment requirement from
+ * the compiler, so we must align to 32 bytes before setting the start
+ * symbol to avoid padding between the symbol and actual data.
+ */
+#define KAPI_SPECS() \
+ . = ALIGN(32); \
+ __start_kapi_specs = .; \
+ KEEP(*(.kapi_specs)) \
+ __stop_kapi_specs = .;
+
+/* For placing KAPI specs in a dedicated section */
+#define KAPI_SPECS_SECTION() \
+ .kapi_specs : AT(ADDR(.kapi_specs) - LOAD_OFFSET) { \
+ . = ALIGN(32); \
+ __start_kapi_specs = .; \
+ KEEP(*(.kapi_specs)) \
+ __stop_kapi_specs = .; \
+ }
+#else
+#define KAPI_SPECS()
+#define KAPI_SPECS_SECTION()
+#endif
+
#ifdef CONFIG_BPF_EVENTS
#define BPF_RAW_TP() STRUCT_ALIGN(); \
BOUNDED_SECTION_BY(__bpf_raw_tp_map, __bpf_raw_tp)
@@ -485,6 +512,7 @@
. = ALIGN(8); \
BOUNDED_SECTION_BY(__tracepoints_ptrs, ___tracepoints_ptrs) \
*(__tracepoints_strings)/* Tracepoints: strings */ \
+ KAPI_SPECS() \
} \
\
.rodata1 : AT(ADDR(.rodata1) - LOAD_OFFSET) { \
diff --git a/include/linux/kernel_api_spec.h b/include/linux/kernel_api_spec.h
new file mode 100644
index 0000000000000..54b0008d1a814
--- /dev/null
+++ b/include/linux/kernel_api_spec.h
@@ -0,0 +1,1580 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * kernel_api_spec.h - Kernel API Formal Specification Framework
+ *
+ * This framework provides structures and macros to formally specify kernel APIs
+ * in both human and machine-readable formats. It supports comprehensive documentation
+ * of function signatures, parameters, return values, error conditions, and constraints.
+ */
+
+#ifndef _LINUX_KERNEL_API_SPEC_H
+#define _LINUX_KERNEL_API_SPEC_H
+
+#include <linux/types.h>
+#include <linux/stringify.h>
+#include <linux/compiler.h>
+#include <linux/errno.h>
+
+struct sigaction;
+
+#define KAPI_MAX_PARAMS 16
+#define KAPI_MAX_ERRORS 32
+#define KAPI_MAX_CONSTRAINTS 32
+#define KAPI_MAX_SIGNALS 32
+#define KAPI_MAX_NAME_LEN 128
+#define KAPI_MAX_DESC_LEN 512
+#define KAPI_MAX_CAPABILITIES 8
+#define KAPI_MAX_SOCKET_STATES 16
+#define KAPI_MAX_PROTOCOL_BEHAVIORS 8
+#define KAPI_MAX_NET_ERRORS 16
+#define KAPI_MAX_SOCKOPTS 16
+#define KAPI_MAX_ADDR_FAMILIES 8
+
+/* Magic numbers for section validation (ASCII mnemonics) */
+#define KAPI_MAGIC_PARAMS 0x4B415031 /* 'KAP1' */
+#define KAPI_MAGIC_RETURN 0x4B415232 /* 'KAR2' */
+#define KAPI_MAGIC_ERRORS 0x4B414533 /* 'KAE3' */
+#define KAPI_MAGIC_LOCKS 0x4B414C34 /* 'KAL4' */
+#define KAPI_MAGIC_CONSTRAINTS 0x4B414335 /* 'KAC5' */
+#define KAPI_MAGIC_INFO 0x4B414936 /* 'KAI6' */
+#define KAPI_MAGIC_SIGNALS 0x4B415337 /* 'KAS7' */
+#define KAPI_MAGIC_SIGMASK 0x4B414D38 /* 'KAM8' */
+#define KAPI_MAGIC_STRUCTS 0x4B415439 /* 'KAT9' */
+#define KAPI_MAGIC_EFFECTS 0x4B414641 /* 'KAFA' */
+#define KAPI_MAGIC_TRANS 0x4B415442 /* 'KATB' */
+#define KAPI_MAGIC_CAPS 0x4B414343 /* 'KACC' */
+
+/**
+ * enum kapi_param_type - Parameter type classification
+ * @KAPI_TYPE_VOID: void type
+ * @KAPI_TYPE_INT: Integer types (int, long, etc.)
+ * @KAPI_TYPE_UINT: Unsigned integer types
+ * @KAPI_TYPE_PTR: Pointer types
+ * @KAPI_TYPE_STRUCT: Structure types
+ * @KAPI_TYPE_UNION: Union types
+ * @KAPI_TYPE_ENUM: Enumeration types
+ * @KAPI_TYPE_FUNC_PTR: Function pointer types
+ * @KAPI_TYPE_ARRAY: Array types
+ * @KAPI_TYPE_FD: File descriptor - validated in process context
+ * @KAPI_TYPE_USER_PTR: User space pointer - validated for access and size
+ * @KAPI_TYPE_PATH: Pathname - validated for access and path limits
+ * @KAPI_TYPE_CUSTOM: Custom/complex types
+ */
+enum kapi_param_type {
+ KAPI_TYPE_VOID = 0,
+ KAPI_TYPE_INT,
+ KAPI_TYPE_UINT,
+ KAPI_TYPE_PTR,
+ KAPI_TYPE_STRUCT,
+ KAPI_TYPE_UNION,
+ KAPI_TYPE_ENUM,
+ KAPI_TYPE_FUNC_PTR,
+ KAPI_TYPE_ARRAY,
+ KAPI_TYPE_FD, /* File descriptor - validated in process context */
+ KAPI_TYPE_USER_PTR, /* User space pointer - validated for access and size */
+ KAPI_TYPE_PATH, /* Pathname - validated for access and path limits */
+ KAPI_TYPE_CUSTOM,
+};
+
+/**
+ * enum kapi_param_flags - Parameter attribute flags
+ * @KAPI_PARAM_IN: Input parameter
+ * @KAPI_PARAM_OUT: Output parameter
+ * @KAPI_PARAM_INOUT: Input/output parameter
+ * @KAPI_PARAM_OPTIONAL: Optional parameter (can be NULL)
+ * @KAPI_PARAM_CONST: Const qualified parameter
+ * @KAPI_PARAM_VOLATILE: Volatile qualified parameter
+ * @KAPI_PARAM_USER: User space pointer
+ * @KAPI_PARAM_DMA: DMA-capable memory required
+ * @KAPI_PARAM_ALIGNED: Alignment requirements
+ */
+enum kapi_param_flags {
+ KAPI_PARAM_IN = (1 << 0),
+ KAPI_PARAM_OUT = (1 << 1),
+ KAPI_PARAM_INOUT = (KAPI_PARAM_IN | KAPI_PARAM_OUT),
+ KAPI_PARAM_OPTIONAL = (1 << 3),
+ KAPI_PARAM_CONST = (1 << 4),
+ KAPI_PARAM_VOLATILE = (1 << 5),
+ KAPI_PARAM_USER = (1 << 6),
+ KAPI_PARAM_DMA = (1 << 7),
+ KAPI_PARAM_ALIGNED = (1 << 8),
+};
+
+/**
+ * enum kapi_context_flags - Function execution context flags
+ * @KAPI_CTX_PROCESS: Can be called from process context
+ * @KAPI_CTX_SOFTIRQ: Can be called from softirq context
+ * @KAPI_CTX_HARDIRQ: Can be called from hardirq context
+ * @KAPI_CTX_NMI: Can be called from NMI context
+ * @KAPI_CTX_ATOMIC: Must be called in atomic context
+ * @KAPI_CTX_SLEEPABLE: May sleep
+ * @KAPI_CTX_PREEMPT_DISABLED: Requires preemption disabled
+ * @KAPI_CTX_IRQ_DISABLED: Requires interrupts disabled
+ */
+enum kapi_context_flags {
+ KAPI_CTX_PROCESS = (1 << 0),
+ KAPI_CTX_SOFTIRQ = (1 << 1),
+ KAPI_CTX_HARDIRQ = (1 << 2),
+ KAPI_CTX_NMI = (1 << 3),
+ KAPI_CTX_ATOMIC = (1 << 4),
+ KAPI_CTX_SLEEPABLE = (1 << 5),
+ KAPI_CTX_PREEMPT_DISABLED = (1 << 6),
+ KAPI_CTX_IRQ_DISABLED = (1 << 7),
+};
+
+/**
+ * enum kapi_lock_type - Lock types used/required by the function
+ * @KAPI_LOCK_NONE: No locking requirements
+ * @KAPI_LOCK_MUTEX: Mutex lock
+ * @KAPI_LOCK_SPINLOCK: Spinlock
+ * @KAPI_LOCK_RWLOCK: Read-write lock
+ * @KAPI_LOCK_SEQLOCK: Sequence lock
+ * @KAPI_LOCK_RCU: RCU lock
+ * @KAPI_LOCK_SEMAPHORE: Semaphore
+ * @KAPI_LOCK_CUSTOM: Custom locking mechanism
+ */
+enum kapi_lock_type {
+ KAPI_LOCK_NONE = 0,
+ KAPI_LOCK_MUTEX,
+ KAPI_LOCK_SPINLOCK,
+ KAPI_LOCK_RWLOCK,
+ KAPI_LOCK_SEQLOCK,
+ KAPI_LOCK_RCU,
+ KAPI_LOCK_SEMAPHORE,
+ KAPI_LOCK_CUSTOM,
+};
+
+/**
+ * enum kapi_constraint_type - Types of parameter constraints
+ * @KAPI_CONSTRAINT_NONE: No constraint
+ * @KAPI_CONSTRAINT_RANGE: Numeric range constraint
+ * @KAPI_CONSTRAINT_MASK: Bitmask constraint
+ * @KAPI_CONSTRAINT_ENUM: Enumerated values constraint
+ * @KAPI_CONSTRAINT_ALIGNMENT: Alignment constraint (must be aligned to specified boundary)
+ * @KAPI_CONSTRAINT_POWER_OF_TWO: Value must be a power of two
+ * @KAPI_CONSTRAINT_PAGE_ALIGNED: Value must be page-aligned
+ * @KAPI_CONSTRAINT_NONZERO: Value must be non-zero
+ * @KAPI_CONSTRAINT_USER_STRING: Userspace null-terminated string with length range
+ * @KAPI_CONSTRAINT_USER_PATH: Userspace pathname string (validated for accessibility and PATH_MAX)
+ * @KAPI_CONSTRAINT_USER_PTR: Userspace pointer (validated for accessibility and size)
+ * @KAPI_CONSTRAINT_BUFFER: Userspace buffer pointer (validated by copy_to/from_user)
+ * @KAPI_CONSTRAINT_CUSTOM: Custom validation function
+ */
+enum kapi_constraint_type {
+ KAPI_CONSTRAINT_NONE = 0,
+ KAPI_CONSTRAINT_RANGE,
+ KAPI_CONSTRAINT_MASK,
+ KAPI_CONSTRAINT_ENUM,
+ KAPI_CONSTRAINT_ALIGNMENT,
+ KAPI_CONSTRAINT_POWER_OF_TWO,
+ KAPI_CONSTRAINT_PAGE_ALIGNED,
+ KAPI_CONSTRAINT_NONZERO,
+ KAPI_CONSTRAINT_USER_STRING,
+ KAPI_CONSTRAINT_USER_PATH,
+ KAPI_CONSTRAINT_USER_PTR,
+ KAPI_CONSTRAINT_BUFFER,
+ KAPI_CONSTRAINT_CUSTOM,
+};
+
+/**
+ * struct kapi_param_spec - Parameter specification
+ * @name: Parameter name
+ * @type_name: Type name as string
+ * @type: Parameter type classification
+ * @flags: Parameter attribute flags
+ * @size: Size in bytes (for arrays/buffers)
+ * @alignment: Required alignment
+ * @min_value: Minimum valid value (for numeric types)
+ * @max_value: Maximum valid value (for numeric types)
+ * @valid_mask: Valid bits mask (for flag parameters)
+ * @enum_values: Array of valid enumerated values
+ * @enum_count: Number of valid enumerated values
+ * @constraint_type: Type of constraint applied
+ * @validate: Custom validation function
+ * @description: Human-readable description
+ * @constraints: Additional constraints description
+ * @size_param_idx: Index of parameter that determines size (-1 if fixed size)
+ * @size_multiplier: Multiplier for size calculation (e.g., sizeof(struct))
+ */
+struct kapi_param_spec {
+ char name[KAPI_MAX_NAME_LEN];
+ char type_name[KAPI_MAX_NAME_LEN];
+ enum kapi_param_type type;
+ u32 flags;
+ size_t size;
+ size_t alignment;
+ s64 min_value;
+ s64 max_value;
+ u64 valid_mask;
+ const s64 *enum_values;
+ u32 enum_count;
+ enum kapi_constraint_type constraint_type;
+ bool (*validate)(s64 value);
+ char description[KAPI_MAX_DESC_LEN];
+ char constraints[KAPI_MAX_DESC_LEN];
+ int size_param_idx; /* Index of param that determines size, -1 if N/A */
+ size_t size_multiplier; /* Size per unit (e.g., sizeof(struct epoll_event)) */
+} __packed;
+
+/**
+ * struct kapi_error_spec - Error condition specification
+ * @error_code: Error code value
+ * @name: Error code name (e.g., "EINVAL")
+ * @condition: Condition that triggers this error
+ * @description: Detailed error description
+ */
+struct kapi_error_spec {
+ int error_code;
+ char name[KAPI_MAX_NAME_LEN];
+ char condition[KAPI_MAX_DESC_LEN];
+ char description[KAPI_MAX_DESC_LEN];
+} __packed;
+
+/**
+ * enum kapi_return_check_type - Return value check types
+ * @KAPI_RETURN_EXACT: Success is an exact value
+ * @KAPI_RETURN_RANGE: Success is within a range
+ * @KAPI_RETURN_ERROR_CHECK: Success is when NOT in error list
+ * @KAPI_RETURN_FD: Return value is a file descriptor (>= 0 is success)
+ * @KAPI_RETURN_CUSTOM: Custom validation function
+ * @KAPI_RETURN_NO_RETURN: Function does not return (e.g., exec on success)
+ */
+enum kapi_return_check_type {
+ KAPI_RETURN_EXACT,
+ KAPI_RETURN_RANGE,
+ KAPI_RETURN_ERROR_CHECK,
+ KAPI_RETURN_FD,
+ KAPI_RETURN_CUSTOM,
+ KAPI_RETURN_NO_RETURN,
+};
+
+/**
+ * struct kapi_return_spec - Return value specification
+ * @type_name: Return type name
+ * @type: Return type classification
+ * @check_type: Type of success check to perform
+ * @success_value: Exact value indicating success (for EXACT)
+ * @success_min: Minimum success value (for RANGE)
+ * @success_max: Maximum success value (for RANGE)
+ * @error_values: Array of error values (for ERROR_CHECK)
+ * @error_count: Number of error values
+ * @is_success: Custom function to check success
+ * @description: Return value description
+ */
+struct kapi_return_spec {
+ char type_name[KAPI_MAX_NAME_LEN];
+ enum kapi_param_type type;
+ enum kapi_return_check_type check_type;
+ s64 success_value;
+ s64 success_min;
+ s64 success_max;
+ const s64 *error_values;
+ u32 error_count;
+ bool (*is_success)(s64 retval);
+ char description[KAPI_MAX_DESC_LEN];
+} __packed;
+
+/**
+ * enum kapi_lock_scope - Lock acquisition/release scope
+ * @KAPI_LOCK_INTERNAL: Lock is acquired and released within the function (common case)
+ * @KAPI_LOCK_ACQUIRES: Function acquires lock but does not release it
+ * @KAPI_LOCK_RELEASES: Function releases lock (must be held on entry)
+ * @KAPI_LOCK_CALLER_HELD: Lock must be held by caller throughout the call
+ */
+enum kapi_lock_scope {
+ KAPI_LOCK_INTERNAL = 0,
+ KAPI_LOCK_ACQUIRES,
+ KAPI_LOCK_RELEASES,
+ KAPI_LOCK_CALLER_HELD,
+};
+
+/**
+ * struct kapi_lock_spec - Lock requirement specification
+ * @lock_name: Name of the lock
+ * @lock_type: Type of lock
+ * @scope: Lock scope (internal, acquires, releases, or caller-held)
+ * @description: Additional lock requirements
+ */
+struct kapi_lock_spec {
+ char lock_name[KAPI_MAX_NAME_LEN];
+ enum kapi_lock_type lock_type;
+ enum kapi_lock_scope scope;
+ char description[KAPI_MAX_DESC_LEN];
+} __packed;
+
+/**
+ * struct kapi_constraint_spec - Additional constraint specification
+ * @name: Constraint name
+ * @description: Constraint description
+ * @expression: Formal expression (if applicable)
+ */
+struct kapi_constraint_spec {
+ char name[KAPI_MAX_NAME_LEN];
+ char description[KAPI_MAX_DESC_LEN];
+ char expression[KAPI_MAX_DESC_LEN];
+} __packed;
+
+/**
+ * enum kapi_signal_direction - Signal flow direction
+ * @KAPI_SIGNAL_RECEIVE: Function may receive this signal
+ * @KAPI_SIGNAL_SEND: Function may send this signal
+ * @KAPI_SIGNAL_HANDLE: Function handles this signal specially
+ * @KAPI_SIGNAL_BLOCK: Function blocks this signal
+ * @KAPI_SIGNAL_IGNORE: Function ignores this signal
+ */
+enum kapi_signal_direction {
+ KAPI_SIGNAL_RECEIVE = (1 << 0),
+ KAPI_SIGNAL_SEND = (1 << 1),
+ KAPI_SIGNAL_HANDLE = (1 << 2),
+ KAPI_SIGNAL_BLOCK = (1 << 3),
+ KAPI_SIGNAL_IGNORE = (1 << 4),
+};
+
+/**
+ * enum kapi_signal_action - What the function does with the signal
+ * @KAPI_SIGNAL_ACTION_DEFAULT: Default signal action applies
+ * @KAPI_SIGNAL_ACTION_TERMINATE: Causes termination
+ * @KAPI_SIGNAL_ACTION_COREDUMP: Causes termination with core dump
+ * @KAPI_SIGNAL_ACTION_STOP: Stops the process
+ * @KAPI_SIGNAL_ACTION_CONTINUE: Continues a stopped process
+ * @KAPI_SIGNAL_ACTION_CUSTOM: Custom handling described in notes
+ * @KAPI_SIGNAL_ACTION_RETURN: Returns from syscall with EINTR
+ * @KAPI_SIGNAL_ACTION_RESTART: Restarts the syscall
+ * @KAPI_SIGNAL_ACTION_QUEUE: Queues the signal for later delivery
+ * @KAPI_SIGNAL_ACTION_DISCARD: Discards the signal
+ * @KAPI_SIGNAL_ACTION_TRANSFORM: Transforms to another signal
+ */
+enum kapi_signal_action {
+ KAPI_SIGNAL_ACTION_DEFAULT = 0,
+ KAPI_SIGNAL_ACTION_TERMINATE,
+ KAPI_SIGNAL_ACTION_COREDUMP,
+ KAPI_SIGNAL_ACTION_STOP,
+ KAPI_SIGNAL_ACTION_CONTINUE,
+ KAPI_SIGNAL_ACTION_CUSTOM,
+ KAPI_SIGNAL_ACTION_RETURN,
+ KAPI_SIGNAL_ACTION_RESTART,
+ KAPI_SIGNAL_ACTION_QUEUE,
+ KAPI_SIGNAL_ACTION_DISCARD,
+ KAPI_SIGNAL_ACTION_TRANSFORM,
+};
+
+/**
+ * struct kapi_signal_spec - Signal specification
+ * @signal_num: Signal number (e.g., SIGKILL, SIGTERM)
+ * @signal_name: Signal name as string
+ * @direction: Direction flags (OR of kapi_signal_direction)
+ * @action: What happens when signal is received
+ * @target: Description of target process/thread for sent signals
+ * @condition: Condition under which signal is sent/received/handled
+ * @description: Detailed description of signal handling
+ * @restartable: Whether syscall is restartable after this signal
+ * @sa_flags_required: Required signal action flags (SA_*)
+ * @sa_flags_forbidden: Forbidden signal action flags
+ * @error_on_signal: Error code returned when signal occurs (-EINTR, etc)
+ * @transform_to: Signal number to transform to (if action is TRANSFORM)
+ * @timing: When signal can occur ("entry", "during", "exit", "anytime")
+ * @priority: Signal handling priority (lower processed first)
+ * @interruptible: Whether this operation is interruptible by this signal
+ * @queue_behavior: How signal is queued ("realtime", "standard", "coalesce")
+ * @state_required: Required process state for signal to be delivered
+ * @state_forbidden: Forbidden process state for signal delivery
+ */
+struct kapi_signal_spec {
+ int signal_num;
+ char signal_name[32];
+ u32 direction;
+ enum kapi_signal_action action;
+ char target[KAPI_MAX_DESC_LEN];
+ char condition[KAPI_MAX_DESC_LEN];
+ char description[KAPI_MAX_DESC_LEN];
+ bool restartable;
+ u32 sa_flags_required;
+ u32 sa_flags_forbidden;
+ int error_on_signal;
+ int transform_to;
+ char timing[32];
+ u8 priority;
+ bool interruptible;
+ char queue_behavior[128];
+ u32 state_required;
+ u32 state_forbidden;
+} __packed;
+
+/**
+ * struct kapi_signal_mask_spec - Signal mask specification
+ * @mask_name: Name of the signal mask
+ * @signals: Array of signal numbers in the mask
+ * @signal_count: Number of signals in the mask
+ * @description: Description of what this mask represents
+ */
+struct kapi_signal_mask_spec {
+ char mask_name[KAPI_MAX_NAME_LEN];
+ int signals[KAPI_MAX_SIGNALS];
+ u32 signal_count;
+ char description[KAPI_MAX_DESC_LEN];
+} __packed;
+
+/**
+ * struct kapi_struct_field - Structure field specification
+ * @name: Field name
+ * @type: Field type classification
+ * @type_name: Type name as string
+ * @offset: Offset within structure
+ * @size: Size of field in bytes
+ * @flags: Field attribute flags
+ * @constraint_type: Type of constraint applied
+ * @min_value: Minimum valid value (for numeric types)
+ * @max_value: Maximum valid value (for numeric types)
+ * @valid_mask: Valid bits mask (for flag fields)
+ * @enum_values: Comma-separated list of valid enum values (for enum types)
+ * @description: Field description
+ */
+struct kapi_struct_field {
+ char name[KAPI_MAX_NAME_LEN];
+ enum kapi_param_type type;
+ char type_name[KAPI_MAX_NAME_LEN];
+ size_t offset;
+ size_t size;
+ u32 flags;
+ enum kapi_constraint_type constraint_type;
+ s64 min_value;
+ s64 max_value;
+ u64 valid_mask;
+ char enum_values[KAPI_MAX_DESC_LEN]; /* Comma-separated list of valid enum values */
+ char description[KAPI_MAX_DESC_LEN];
+} __packed;
+
+/**
+ * struct kapi_struct_spec - Structure type specification
+ * @name: Structure name
+ * @size: Total size of structure
+ * @alignment: Required alignment
+ * @field_count: Number of fields
+ * @fields: Field specifications
+ * @description: Structure description
+ */
+struct kapi_struct_spec {
+ char name[KAPI_MAX_NAME_LEN];
+ size_t size;
+ size_t alignment;
+ u32 field_count;
+ struct kapi_struct_field fields[KAPI_MAX_PARAMS];
+ char description[KAPI_MAX_DESC_LEN];
+} __packed;
+
+/**
+ * enum kapi_capability_action - What the capability allows
+ * @KAPI_CAP_BYPASS_CHECK: Bypasses a check entirely
+ * @KAPI_CAP_INCREASE_LIMIT: Increases or removes a limit
+ * @KAPI_CAP_OVERRIDE_RESTRICTION: Overrides a restriction
+ * @KAPI_CAP_GRANT_PERMISSION: Grants permission that would otherwise be denied
+ * @KAPI_CAP_MODIFY_BEHAVIOR: Changes the behavior of the operation
+ * @KAPI_CAP_ACCESS_RESOURCE: Allows access to restricted resources
+ * @KAPI_CAP_PERFORM_OPERATION: Allows performing a privileged operation
+ */
+enum kapi_capability_action {
+ KAPI_CAP_BYPASS_CHECK = 0,
+ KAPI_CAP_INCREASE_LIMIT,
+ KAPI_CAP_OVERRIDE_RESTRICTION,
+ KAPI_CAP_GRANT_PERMISSION,
+ KAPI_CAP_MODIFY_BEHAVIOR,
+ KAPI_CAP_ACCESS_RESOURCE,
+ KAPI_CAP_PERFORM_OPERATION,
+};
+
+/**
+ * struct kapi_capability_spec - Capability requirement specification
+ * @capability: The capability constant (e.g., CAP_IPC_LOCK)
+ * @cap_name: Capability name as string
+ * @action: What the capability allows (kapi_capability_action)
+ * @allows: Description of what the capability allows
+ * @without_cap: What happens without the capability
+ * @check_condition: Condition when capability is checked
+ * @priority: Check priority (lower checked first)
+ * @alternative: Alternative capabilities that can be used
+ * @alternative_count: Number of alternative capabilities
+ */
+struct kapi_capability_spec {
+ int capability;
+ char cap_name[KAPI_MAX_NAME_LEN];
+ enum kapi_capability_action action;
+ char allows[KAPI_MAX_DESC_LEN];
+ char without_cap[KAPI_MAX_DESC_LEN];
+ char check_condition[KAPI_MAX_DESC_LEN];
+ u8 priority;
+ int alternative[KAPI_MAX_CAPABILITIES];
+ u32 alternative_count;
+} __packed;
+
+/**
+ * enum kapi_side_effect_type - Types of side effects
+ * @KAPI_EFFECT_NONE: No side effects
+ * @KAPI_EFFECT_ALLOC_MEMORY: Allocates memory
+ * @KAPI_EFFECT_FREE_MEMORY: Frees memory
+ * @KAPI_EFFECT_MODIFY_STATE: Modifies global/shared state
+ * @KAPI_EFFECT_SIGNAL_SEND: Sends signals
+ * @KAPI_EFFECT_FILE_POSITION: Modifies file position
+ * @KAPI_EFFECT_LOCK_ACQUIRE: Acquires locks
+ * @KAPI_EFFECT_LOCK_RELEASE: Releases locks
+ * @KAPI_EFFECT_RESOURCE_CREATE: Creates system resources (FDs, PIDs, etc)
+ * @KAPI_EFFECT_RESOURCE_DESTROY: Destroys system resources
+ * @KAPI_EFFECT_SCHEDULE: May cause scheduling/context switch
+ * @KAPI_EFFECT_HARDWARE: Interacts with hardware
+ * @KAPI_EFFECT_NETWORK: Network I/O operation
+ * @KAPI_EFFECT_FILESYSTEM: Filesystem modification
+ * @KAPI_EFFECT_PROCESS_STATE: Modifies process state
+ * @KAPI_EFFECT_IRREVERSIBLE: Effect cannot be undone
+ */
+enum kapi_side_effect_type {
+ KAPI_EFFECT_NONE = 0,
+ KAPI_EFFECT_ALLOC_MEMORY = (1 << 0),
+ KAPI_EFFECT_FREE_MEMORY = (1 << 1),
+ KAPI_EFFECT_MODIFY_STATE = (1 << 2),
+ KAPI_EFFECT_SIGNAL_SEND = (1 << 3),
+ KAPI_EFFECT_FILE_POSITION = (1 << 4),
+ KAPI_EFFECT_LOCK_ACQUIRE = (1 << 5),
+ KAPI_EFFECT_LOCK_RELEASE = (1 << 6),
+ KAPI_EFFECT_RESOURCE_CREATE = (1 << 7),
+ KAPI_EFFECT_RESOURCE_DESTROY = (1 << 8),
+ KAPI_EFFECT_SCHEDULE = (1 << 9),
+ KAPI_EFFECT_HARDWARE = (1 << 10),
+ KAPI_EFFECT_NETWORK = (1 << 11),
+ KAPI_EFFECT_FILESYSTEM = (1 << 12),
+ KAPI_EFFECT_PROCESS_STATE = (1 << 13),
+ KAPI_EFFECT_IRREVERSIBLE = (1 << 14),
+};
+
+/**
+ * struct kapi_side_effect - Side effect specification
+ * @type: Bitmask of effect types
+ * @target: What is affected (e.g., "process memory", "file descriptor table")
+ * @condition: Condition under which effect occurs
+ * @description: Detailed description of the effect
+ * @reversible: Whether the effect can be undone
+ */
+struct kapi_side_effect {
+ u32 type;
+ char target[KAPI_MAX_NAME_LEN];
+ char condition[KAPI_MAX_DESC_LEN];
+ char description[KAPI_MAX_DESC_LEN];
+ bool reversible;
+} __packed;
+
+/**
+ * struct kapi_state_transition - State transition specification
+ * @from_state: Starting state description
+ * @to_state: Ending state description
+ * @condition: Condition for transition
+ * @object: Object whose state changes
+ * @description: Detailed description
+ */
+struct kapi_state_transition {
+ char from_state[KAPI_MAX_NAME_LEN];
+ char to_state[KAPI_MAX_NAME_LEN];
+ char condition[KAPI_MAX_DESC_LEN];
+ char object[KAPI_MAX_NAME_LEN];
+ char description[KAPI_MAX_DESC_LEN];
+} __packed;
+
+#define KAPI_MAX_STRUCT_SPECS 8
+#define KAPI_MAX_SIDE_EFFECTS 32
+#define KAPI_MAX_STATE_TRANS 8
+
+/**
+ * enum kapi_socket_state - Socket states for state machine
+ */
+enum kapi_socket_state {
+ KAPI_SOCK_STATE_UNSPEC = 0,
+ KAPI_SOCK_STATE_CLOSED,
+ KAPI_SOCK_STATE_OPEN,
+ KAPI_SOCK_STATE_BOUND,
+ KAPI_SOCK_STATE_LISTEN,
+ KAPI_SOCK_STATE_SYN_SENT,
+ KAPI_SOCK_STATE_SYN_RECV,
+ KAPI_SOCK_STATE_ESTABLISHED,
+ KAPI_SOCK_STATE_FIN_WAIT1,
+ KAPI_SOCK_STATE_FIN_WAIT2,
+ KAPI_SOCK_STATE_CLOSE_WAIT,
+ KAPI_SOCK_STATE_CLOSING,
+ KAPI_SOCK_STATE_LAST_ACK,
+ KAPI_SOCK_STATE_TIME_WAIT,
+ KAPI_SOCK_STATE_CONNECTED,
+ KAPI_SOCK_STATE_DISCONNECTED,
+};
+
+/**
+ * enum kapi_socket_protocol - Socket protocol types
+ */
+enum kapi_socket_protocol {
+ KAPI_PROTO_TCP = (1 << 0),
+ KAPI_PROTO_UDP = (1 << 1),
+ KAPI_PROTO_UNIX = (1 << 2),
+ KAPI_PROTO_RAW = (1 << 3),
+ KAPI_PROTO_PACKET = (1 << 4),
+ KAPI_PROTO_NETLINK = (1 << 5),
+ KAPI_PROTO_SCTP = (1 << 6),
+ KAPI_PROTO_DCCP = (1 << 7),
+ KAPI_PROTO_ALL = 0xFFFFFFFF,
+};
+
+/**
+ * enum kapi_buffer_behavior - Network buffer handling behaviors
+ */
+enum kapi_buffer_behavior {
+ KAPI_BUF_PEEK = (1 << 0),
+ KAPI_BUF_TRUNCATE = (1 << 1),
+ KAPI_BUF_SCATTER = (1 << 2),
+ KAPI_BUF_ZERO_COPY = (1 << 3),
+ KAPI_BUF_KERNEL_ALLOC = (1 << 4),
+ KAPI_BUF_DMA_CAPABLE = (1 << 5),
+ KAPI_BUF_FRAGMENT = (1 << 6),
+};
+
+/**
+ * enum kapi_async_behavior - Asynchronous operation behaviors
+ */
+enum kapi_async_behavior {
+ KAPI_ASYNC_BLOCK = 0,
+ KAPI_ASYNC_NONBLOCK = (1 << 0),
+ KAPI_ASYNC_POLL_READY = (1 << 1),
+ KAPI_ASYNC_SIGNAL_DRIVEN = (1 << 2),
+ KAPI_ASYNC_AIO = (1 << 3),
+ KAPI_ASYNC_IO_URING = (1 << 4),
+ KAPI_ASYNC_EPOLL = (1 << 5),
+};
+
+/**
+ * struct kapi_socket_state_spec - Socket state requirement/transition
+ */
+struct kapi_socket_state_spec {
+ enum kapi_socket_state required_states[KAPI_MAX_SOCKET_STATES];
+ u32 required_state_count;
+ enum kapi_socket_state forbidden_states[KAPI_MAX_SOCKET_STATES];
+ u32 forbidden_state_count;
+ enum kapi_socket_state resulting_state;
+ char state_condition[KAPI_MAX_DESC_LEN];
+ u32 applicable_protocols;
+} __packed;
+
+/**
+ * struct kapi_protocol_behavior - Protocol-specific behavior
+ */
+struct kapi_protocol_behavior {
+ u32 applicable_protocols;
+ char behavior[KAPI_MAX_DESC_LEN];
+ s64 protocol_flags;
+ char flag_description[KAPI_MAX_DESC_LEN];
+} __packed;
+
+/**
+ * struct kapi_buffer_spec - Network buffer specification
+ */
+struct kapi_buffer_spec {
+ u32 buffer_behaviors;
+ size_t min_buffer_size;
+ size_t max_buffer_size;
+ size_t optimal_buffer_size;
+ char fragmentation_rules[KAPI_MAX_DESC_LEN];
+ bool can_partial_transfer;
+ char partial_transfer_rules[KAPI_MAX_DESC_LEN];
+} __packed;
+
+/**
+ * struct kapi_async_spec - Asynchronous behavior specification
+ */
+struct kapi_async_spec {
+ enum kapi_async_behavior supported_modes;
+ int nonblock_errno;
+ u32 poll_events_in;
+ u32 poll_events_out;
+ char completion_condition[KAPI_MAX_DESC_LEN];
+ bool supports_timeout;
+ char timeout_behavior[KAPI_MAX_DESC_LEN];
+} __packed;
+
+/**
+ * struct kapi_addr_family_spec - Address family specification
+ */
+struct kapi_addr_family_spec {
+ int family;
+ char family_name[32];
+ size_t addr_struct_size;
+ size_t min_addr_len;
+ size_t max_addr_len;
+ char addr_format[KAPI_MAX_DESC_LEN];
+ bool supports_wildcard;
+ bool supports_multicast;
+ bool supports_broadcast;
+ char special_addresses[KAPI_MAX_DESC_LEN];
+ u32 port_range_min;
+ u32 port_range_max;
+} __packed;
+
+/**
+ * struct kernel_api_spec - Complete kernel API specification
+ * @name: Function name
+ * @version: API version
+ * @description: Brief description
+ * @long_description: Detailed description
+ * @context_flags: Execution context flags
+ * @param_count: Number of parameters
+ * @params: Parameter specifications
+ * @return_spec: Return value specification
+ * @error_count: Number of possible errors
+ * @errors: Error specifications
+ * @lock_count: Number of lock specifications
+ * @locks: Lock requirement specifications
+ * @constraint_count: Number of additional constraints
+ * @constraints: Additional constraint specifications
+ * @examples: Usage examples
+ * @notes: Additional notes
+ * @since_version: Kernel version when introduced
+ * @signal_count: Number of signal specifications
+ * @signals: Signal handling specifications
+ * @signal_mask_count: Number of signal mask specifications
+ * @signal_masks: Signal mask specifications
+ * @struct_spec_count: Number of structure specifications
+ * @struct_specs: Structure type specifications
+ * @side_effect_count: Number of side effect specifications
+ * @side_effects: Side effect specifications
+ * @state_trans_count: Number of state transition specifications
+ * @state_transitions: State transition specifications
+ */
+struct kernel_api_spec {
+ char name[KAPI_MAX_NAME_LEN];
+ u32 version;
+ char description[KAPI_MAX_DESC_LEN];
+ char long_description[KAPI_MAX_DESC_LEN * 4];
+ u32 context_flags;
+
+ /* Parameters */
+ u32 param_magic; /* 0x4B415031 = 'KAP1' */
+ u32 param_count;
+ struct kapi_param_spec params[KAPI_MAX_PARAMS];
+
+ /* Return value */
+ u32 return_magic; /* 0x4B415232 = 'KAR2' */
+ struct kapi_return_spec return_spec;
+
+ /* Errors */
+ u32 error_magic; /* 0x4B414533 = 'KAE3' */
+ u32 error_count;
+ struct kapi_error_spec errors[KAPI_MAX_ERRORS];
+
+ /* Locking */
+ u32 lock_magic; /* 0x4B414C34 = 'KAL4' */
+ u32 lock_count;
+ struct kapi_lock_spec locks[KAPI_MAX_CONSTRAINTS];
+
+ /* Constraints */
+ u32 constraint_magic; /* 0x4B414335 = 'KAC5' */
+ u32 constraint_count;
+ struct kapi_constraint_spec constraints[KAPI_MAX_CONSTRAINTS];
+
+ /* Additional information */
+ u32 info_magic; /* 0x4B414936 = 'KAI6' */
+ char examples[KAPI_MAX_DESC_LEN * 2];
+ char notes[KAPI_MAX_DESC_LEN * 2];
+ char since_version[32];
+
+ /* Signal specifications */
+ u32 signal_magic; /* 0x4B415337 = 'KAS7' */
+ u32 signal_count;
+ struct kapi_signal_spec signals[KAPI_MAX_SIGNALS];
+
+ /* Signal mask specifications */
+ u32 sigmask_magic; /* 0x4B414D38 = 'KAM8' */
+ u32 signal_mask_count;
+ struct kapi_signal_mask_spec signal_masks[KAPI_MAX_SIGNALS];
+
+ /* Structure specifications */
+ u32 struct_magic; /* 0x4B415439 = 'KAT9' */
+ u32 struct_spec_count;
+ struct kapi_struct_spec struct_specs[KAPI_MAX_STRUCT_SPECS];
+
+ /* Side effects */
+ u32 effect_magic; /* 0x4B414641 = 'KAFA' */
+ u32 side_effect_count;
+ struct kapi_side_effect side_effects[KAPI_MAX_SIDE_EFFECTS];
+
+ /* State transitions */
+ u32 trans_magic; /* 0x4B415442 = 'KATB' */
+ u32 state_trans_count;
+ struct kapi_state_transition state_transitions[KAPI_MAX_STATE_TRANS];
+
+ /* Capability specifications */
+ u32 cap_magic; /* 0x4B414343 = 'KACC' */
+ u32 capability_count;
+ struct kapi_capability_spec capabilities[KAPI_MAX_CAPABILITIES];
+
+ /* Extended fields for socket and network operations */
+ struct kapi_socket_state_spec socket_state;
+ struct kapi_protocol_behavior protocol_behaviors[KAPI_MAX_PROTOCOL_BEHAVIORS];
+ u32 protocol_behavior_count;
+ struct kapi_buffer_spec buffer_spec;
+ struct kapi_async_spec async_spec;
+ struct kapi_addr_family_spec addr_families[KAPI_MAX_ADDR_FAMILIES];
+ u32 addr_family_count;
+
+ /* Operation characteristics */
+ bool is_connection_oriented;
+ bool is_message_oriented;
+ bool supports_oob_data;
+ bool supports_peek;
+ bool supports_select_poll;
+ bool is_reentrant;
+
+ /* Semantic descriptions */
+ char connection_establishment[KAPI_MAX_DESC_LEN];
+ char connection_termination[KAPI_MAX_DESC_LEN];
+ char data_transfer_semantics[KAPI_MAX_DESC_LEN];
+} __packed;
+
+/* Macros for defining API specifications */
+
+/**
+ * DEFINE_KERNEL_API_SPEC - Define a kernel API specification
+ * @func_name: Function name to specify
+ */
+#define DEFINE_KERNEL_API_SPEC(func_name) \
+ static struct kernel_api_spec __kapi_spec_##func_name \
+ __used __section(".kapi_specs") = { \
+ .name = __stringify(func_name), \
+ .version = 1,
+
+#define KAPI_END_SPEC };
+
+/**
+ * KAPI_DESCRIPTION - Set API description
+ * @desc: Description string
+ */
+#define KAPI_DESCRIPTION(desc) \
+ .description = desc,
+
+/**
+ * KAPI_LONG_DESC - Set detailed API description
+ * @desc: Detailed description string
+ */
+#define KAPI_LONG_DESC(desc) \
+ .long_description = desc,
+
+/**
+ * KAPI_CONTEXT - Set execution context flags
+ * @flags: Context flags (OR'ed KAPI_CTX_* values)
+ */
+#define KAPI_CONTEXT(flags) \
+ .context_flags = flags,
+
+/**
+ * KAPI_PARAM - Define a parameter specification
+ * @idx: Parameter index (0-based)
+ * @pname: Parameter name
+ * @ptype: Type name string
+ * @pdesc: Parameter description
+ */
+#define KAPI_PARAM(idx, pname, ptype, pdesc) \
+ .params[idx] = { \
+ .name = pname, \
+ .type_name = ptype, \
+ .description = pdesc, \
+ .size_param_idx = -1, /* Default: no dynamic sizing */
+
+#define KAPI_PARAM_TYPE(ptype) \
+ .type = ptype,
+
+#define KAPI_PARAM_FLAGS(pflags) \
+ .flags = pflags,
+
+#define KAPI_PARAM_SIZE(psize) \
+ .size = psize,
+
+#define KAPI_PARAM_RANGE(pmin, pmax) \
+ .min_value = pmin, \
+ .max_value = pmax,
+
+#define KAPI_PARAM_CONSTRAINT_TYPE(ctype) \
+ .constraint_type = ctype,
+
+#define KAPI_PARAM_CONSTRAINT(desc) \
+ .constraints = desc,
+
+#define KAPI_PARAM_VALID_MASK(mask) \
+ .valid_mask = mask,
+
+#define KAPI_PARAM_ENUM_VALUES(values) \
+ .enum_values = values, \
+ .enum_count = ARRAY_SIZE(values),
+
+#define KAPI_PARAM_ALIGNMENT(align) \
+ .alignment = align,
+
+#define KAPI_PARAM_SIZE_PARAM(idx) \
+ .size_param_idx = idx,
+
+#define KAPI_PARAM_END },
+
+/**
+ * KAPI_PARAM_COUNT - Set the number of parameters
+ * @n: Number of parameters
+ */
+#define KAPI_PARAM_COUNT(n) \
+ .param_magic = KAPI_MAGIC_PARAMS, \
+ .param_count = n,
+
+/**
+ * KAPI_RETURN - Define return value specification
+ * @rtype: Return type name
+ * @rdesc: Return value description
+ */
+#define KAPI_RETURN(rtype, rdesc) \
+ .return_magic = KAPI_MAGIC_RETURN, \
+ .return_spec = { \
+ .type_name = rtype, \
+ .description = rdesc,
+
+#define KAPI_RETURN_SUCCESS(val, ...) \
+ .success_value = val,
+
+#define KAPI_RETURN_TYPE(rtype) \
+ .type = rtype,
+
+#define KAPI_RETURN_CHECK_TYPE(ctype) \
+ .check_type = ctype,
+
+#define KAPI_RETURN_ERROR_VALUES(values) \
+ .error_values = values,
+
+#define KAPI_RETURN_ERROR_COUNT(count) \
+ .error_count = count,
+
+#define KAPI_RETURN_SUCCESS_RANGE(min, max) \
+ .success_min = min, \
+ .success_max = max,
+
+#define KAPI_RETURN_END },
+
+/**
+ * KAPI_ERROR - Define an error condition
+ * @idx: Error index
+ * @ecode: Error code value
+ * @ename: Error name
+ * @econd: Error condition
+ * @edesc: Error description
+ */
+#define KAPI_ERROR(idx, ecode, ename, econd, edesc) \
+ .errors[idx] = { \
+ .error_code = ecode, \
+ .name = ename, \
+ .condition = econd, \
+ .description = edesc, \
+ },
+
+/**
+ * KAPI_ERROR_COUNT - Set the number of errors
+ * @n: Number of errors
+ */
+#define KAPI_ERROR_COUNT(n) \
+ .error_magic = KAPI_MAGIC_ERRORS, \
+ .error_count = n,
+
+/**
+ * KAPI_LOCK - Define a lock requirement
+ * @idx: Lock index
+ * @lname: Lock name
+ * @ltype: Lock type
+ */
+#define KAPI_LOCK(idx, lname, ltype) \
+ .locks[idx] = { \
+ .lock_name = lname, \
+ .lock_type = ltype,
+
+#define KAPI_LOCK_ACQUIRED \
+ .scope = KAPI_LOCK_ACQUIRES,
+
+#define KAPI_LOCK_RELEASED \
+ .scope = KAPI_LOCK_RELEASES,
+
+#define KAPI_LOCK_HELD_ENTRY \
+ .scope = KAPI_LOCK_CALLER_HELD,
+
+#define KAPI_LOCK_HELD_EXIT \
+ .scope = KAPI_LOCK_CALLER_HELD,
+
+#define KAPI_LOCK_DESC(ldesc) \
+ .description = ldesc,
+
+#define KAPI_LOCK_END },
+
+/**
+ * KAPI_CONSTRAINT - Define an additional constraint
+ * @idx: Constraint index
+ * @cname: Constraint name
+ * @cdesc: Constraint description
+ */
+#define KAPI_CONSTRAINT(idx, cname, cdesc) \
+ .constraints[idx] = { \
+ .name = cname, \
+ .description = cdesc,
+
+#define KAPI_CONSTRAINT_EXPR(expr) \
+ .expression = expr,
+
+#define KAPI_CONSTRAINT_END },
+
+/**
+ * KAPI_EXAMPLES - Set API usage examples
+ * @examples: Examples string
+ */
+#define KAPI_EXAMPLES(ex) \
+ .info_magic = KAPI_MAGIC_INFO, \
+ .examples = ex,
+
+/**
+ * KAPI_NOTES - Set API notes
+ * @notes: Notes string
+ */
+#define KAPI_NOTES(n) \
+ .notes = n,
+
+
+/**
+ * KAPI_SIGNAL - Define a signal specification
+ * @idx: Signal index
+ * @signum: Signal number (e.g., SIGKILL)
+ * @signame: Signal name string
+ * @dir: Direction flags
+ * @act: Action taken
+ */
+#define KAPI_SIGNAL(idx, signum, signame, dir, act) \
+ .signals[idx] = { \
+ .signal_num = signum, \
+ .signal_name = signame, \
+ .direction = dir, \
+ .action = act,
+
+#define KAPI_SIGNAL_TARGET(tgt) \
+ .target = tgt,
+
+#define KAPI_SIGNAL_CONDITION(cond) \
+ .condition = cond,
+
+#define KAPI_SIGNAL_DESC(desc) \
+ .description = desc,
+
+#define KAPI_SIGNAL_RESTARTABLE \
+ .restartable = true,
+
+#define KAPI_SIGNAL_SA_FLAGS_REQ(flags) \
+ .sa_flags_required = flags,
+
+#define KAPI_SIGNAL_SA_FLAGS_FORBID(flags) \
+ .sa_flags_forbidden = flags,
+
+#define KAPI_SIGNAL_ERROR(err) \
+ .error_on_signal = err,
+
+#define KAPI_SIGNAL_TRANSFORM(sig) \
+ .transform_to = sig,
+
+#define KAPI_SIGNAL_TIMING(when) \
+ .timing = when,
+
+#define KAPI_SIGNAL_PRIORITY(prio) \
+ .priority = prio,
+
+#define KAPI_SIGNAL_INTERRUPTIBLE \
+ .interruptible = true,
+
+#define KAPI_SIGNAL_QUEUE(behavior) \
+ .queue_behavior = behavior,
+
+#define KAPI_SIGNAL_STATE_REQ(state) \
+ .state_required = state,
+
+#define KAPI_SIGNAL_STATE_FORBID(state) \
+ .state_forbidden = state,
+
+#define KAPI_SIGNAL_END },
+
+#define KAPI_SIGNAL_COUNT(n) \
+ .signal_magic = KAPI_MAGIC_SIGNALS, \
+ .signal_count = n,
+
+/**
+ * KAPI_SIGNAL_MASK - Define a signal mask specification
+ * @idx: Mask index
+ * @name: Mask name
+ * @desc: Mask description
+ */
+#define KAPI_SIGNAL_MASK(idx, name, desc) \
+ .signal_masks[idx] = { \
+ .mask_name = name, \
+ .description = desc,
+
+/*
+ * KAPI_SIGNAL_MASK_SIGNALS - Specify signals in a signal mask
+ * @...: Variadic list of signal numbers
+ *
+ * Usage:
+ * KAPI_SIGNAL_MASK(0, "blocked", "Signals blocked during operation")
+ * KAPI_SIGNAL_MASK_SIGNALS(SIGINT, SIGTERM, SIGQUIT)
+ * KAPI_SIGNAL_MASK_END
+ */
+#define KAPI_SIGNAL_MASK_SIGNALS(...) \
+ .signals = { __VA_ARGS__ }, \
+ .signal_count = sizeof((int[]){ __VA_ARGS__ }) / sizeof(int),
+
+#define KAPI_SIGNAL_MASK_END },
+
+/**
+ * KAPI_STRUCT_SPEC - Define a structure specification
+ * @idx: Structure spec index
+ * @sname: Structure name
+ * @sdesc: Structure description
+ */
+#define KAPI_STRUCT_SPEC(idx, sname, sdesc) \
+ .struct_specs[idx] = { \
+ .name = #sname, \
+ .description = sdesc,
+
+#define KAPI_STRUCT_SIZE(ssize, salign) \
+ .size = ssize, \
+ .alignment = salign,
+
+#define KAPI_STRUCT_FIELD_COUNT(n) \
+ .field_count = n,
+
+/**
+ * KAPI_STRUCT_FIELD - Define a structure field
+ * @fidx: Field index
+ * @fname: Field name
+ * @ftype: Field type (KAPI_TYPE_*)
+ * @ftype_name: Type name as string
+ * @fdesc: Field description
+ */
+#define KAPI_STRUCT_FIELD(fidx, fname, ftype, ftype_name, fdesc) \
+ .fields[fidx] = { \
+ .name = fname, \
+ .type = ftype, \
+ .type_name = ftype_name, \
+ .description = fdesc,
+
+#define KAPI_FIELD_OFFSET(foffset) \
+ .offset = foffset,
+
+#define KAPI_FIELD_SIZE(fsize) \
+ .size = fsize,
+
+#define KAPI_FIELD_FLAGS(fflags) \
+ .flags = fflags,
+
+#define KAPI_FIELD_CONSTRAINT_RANGE(min, max) \
+ .constraint_type = KAPI_CONSTRAINT_RANGE, \
+ .min_value = min, \
+ .max_value = max,
+
+#define KAPI_FIELD_CONSTRAINT_MASK(mask) \
+ .constraint_type = KAPI_CONSTRAINT_MASK, \
+ .valid_mask = mask,
+
+#define KAPI_FIELD_CONSTRAINT_ENUM(values) \
+ .constraint_type = KAPI_CONSTRAINT_ENUM, \
+ .enum_values = values,
+
+#define KAPI_STRUCT_FIELD_END },
+
+#define KAPI_STRUCT_SPEC_END },
+
+/* Counter for structure specifications */
+#define KAPI_STRUCT_SPEC_COUNT(n) \
+ .struct_magic = KAPI_MAGIC_STRUCTS, \
+ .struct_spec_count = n,
+
+/* Additional lock-related macros */
+#define KAPI_LOCK_COUNT(n) \
+ .lock_magic = KAPI_MAGIC_LOCKS, \
+ .lock_count = n,
+
+/**
+ * KAPI_SIDE_EFFECT - Define a side effect
+ * @idx: Side effect index
+ * @etype: Effect type bitmask (OR'ed KAPI_EFFECT_* values)
+ * @etarget: What is affected
+ * @edesc: Effect description
+ */
+#define KAPI_SIDE_EFFECT(idx, etype, etarget, edesc) \
+ .side_effects[idx] = { \
+ .type = etype, \
+ .target = etarget, \
+ .description = edesc,
+
+#define KAPI_EFFECT_CONDITION(cond) \
+ .condition = cond,
+
+#define KAPI_EFFECT_REVERSIBLE \
+ .reversible = true,
+
+#define KAPI_SIDE_EFFECT_END },
+
+/**
+ * KAPI_STATE_TRANS - Define a state transition
+ * @idx: State transition index
+ * @obj: Object whose state changes
+ * @from: From state
+ * @to: To state
+ * @desc: Transition description
+ */
+#define KAPI_STATE_TRANS(idx, obj, from, to, desc) \
+ .state_transitions[idx] = { \
+ .object = obj, \
+ .from_state = from, \
+ .to_state = to, \
+ .description = desc,
+
+#define KAPI_STATE_TRANS_COND(cond) \
+ .condition = cond,
+
+#define KAPI_STATE_TRANS_END },
+
+/* Counters for side effects and state transitions */
+#define KAPI_SIDE_EFFECT_COUNT(n) \
+ .effect_magic = KAPI_MAGIC_EFFECTS, \
+ .side_effect_count = n,
+
+#define KAPI_STATE_TRANS_COUNT(n) \
+ .trans_magic = KAPI_MAGIC_TRANS, \
+ .state_trans_count = n,
+
+/* Helper macros for common side effect patterns */
+#define KAPI_EFFECTS_MEMORY (KAPI_EFFECT_ALLOC_MEMORY | KAPI_EFFECT_FREE_MEMORY)
+#define KAPI_EFFECTS_LOCKING (KAPI_EFFECT_LOCK_ACQUIRE | KAPI_EFFECT_LOCK_RELEASE)
+#define KAPI_EFFECTS_RESOURCES (KAPI_EFFECT_RESOURCE_CREATE | KAPI_EFFECT_RESOURCE_DESTROY)
+#define KAPI_EFFECTS_IO (KAPI_EFFECT_NETWORK | KAPI_EFFECT_FILESYSTEM)
+
+/*
+ * Helper macros for combining common parameter flag patterns.
+ * Note: KAPI_PARAM_IN, KAPI_PARAM_OUT, KAPI_PARAM_INOUT, and KAPI_PARAM_OPTIONAL
+ * are already defined in enum kapi_param_flags - use those directly.
+ */
+#define KAPI_PARAM_FLAGS_INOUT (KAPI_PARAM_IN | KAPI_PARAM_OUT)
+#define KAPI_PARAM_FLAGS_USER (KAPI_PARAM_USER | KAPI_PARAM_IN)
+
+/* Common signal timing constants */
+#define KAPI_SIGNAL_TIME_ENTRY "entry"
+#define KAPI_SIGNAL_TIME_DURING "during"
+#define KAPI_SIGNAL_TIME_EXIT "exit"
+#define KAPI_SIGNAL_TIME_ANYTIME "anytime"
+#define KAPI_SIGNAL_TIME_BLOCKING "while_blocked"
+#define KAPI_SIGNAL_TIME_SLEEPING "while_sleeping"
+#define KAPI_SIGNAL_TIME_BEFORE "before"
+#define KAPI_SIGNAL_TIME_AFTER "after"
+
+/* Common signal queue behaviors */
+#define KAPI_SIGNAL_QUEUE_STANDARD "standard"
+#define KAPI_SIGNAL_QUEUE_REALTIME "realtime"
+#define KAPI_SIGNAL_QUEUE_COALESCE "coalesce"
+#define KAPI_SIGNAL_QUEUE_REPLACE "replace"
+#define KAPI_SIGNAL_QUEUE_DISCARD "discard"
+
+/* Process state flags for signal delivery */
+#define KAPI_SIGNAL_STATE_RUNNING (1 << 0)
+#define KAPI_SIGNAL_STATE_SLEEPING (1 << 1)
+#define KAPI_SIGNAL_STATE_STOPPED (1 << 2)
+#define KAPI_SIGNAL_STATE_TRACED (1 << 3)
+#define KAPI_SIGNAL_STATE_ZOMBIE (1 << 4)
+#define KAPI_SIGNAL_STATE_DEAD (1 << 5)
+
+/* Capability specification macros */
+
+/**
+ * KAPI_CAPABILITY - Define a capability requirement
+ * @idx: Capability index
+ * @cap: Capability constant (e.g., CAP_IPC_LOCK)
+ * @name: Capability name string
+ * @act: Action type (kapi_capability_action)
+ */
+#define KAPI_CAPABILITY(idx, cap, name, act) \
+ .capabilities[idx] = { \
+ .capability = cap, \
+ .cap_name = name, \
+ .action = act,
+
+#define KAPI_CAP_ALLOWS(desc) \
+ .allows = desc,
+
+#define KAPI_CAP_WITHOUT(desc) \
+ .without_cap = desc,
+
+#define KAPI_CAP_CONDITION(cond) \
+ .check_condition = cond,
+
+#define KAPI_CAP_PRIORITY(prio) \
+ .priority = prio,
+
+#define KAPI_CAP_ALTERNATIVE(caps, count) \
+ .alternative = caps, \
+ .alternative_count = count,
+
+#define KAPI_CAPABILITY_END },
+
+/* Counter for capability specifications */
+#define KAPI_CAPABILITY_COUNT(n) \
+ .cap_magic = KAPI_MAGIC_CAPS, \
+ .capability_count = n,
+
+/* Common signal patterns for syscalls */
+#define KAPI_SIGNAL_INTERRUPTIBLE_SLEEP \
+ KAPI_SIGNAL(0, SIGINT, "SIGINT", KAPI_SIGNAL_RECEIVE, KAPI_SIGNAL_ACTION_RETURN) \
+ KAPI_SIGNAL_TIMING(KAPI_SIGNAL_TIME_SLEEPING) \
+ KAPI_SIGNAL_ERROR(-EINTR) \
+ KAPI_SIGNAL_RESTARTABLE \
+ KAPI_SIGNAL_DESC("Interrupts sleep, returns -EINTR") \
+ KAPI_SIGNAL_END, \
+ KAPI_SIGNAL(1, SIGTERM, "SIGTERM", KAPI_SIGNAL_RECEIVE, KAPI_SIGNAL_ACTION_RETURN) \
+ KAPI_SIGNAL_TIMING(KAPI_SIGNAL_TIME_SLEEPING) \
+ KAPI_SIGNAL_ERROR(-EINTR) \
+ KAPI_SIGNAL_RESTARTABLE \
+ KAPI_SIGNAL_DESC("Interrupts sleep, returns -EINTR") \
+ KAPI_SIGNAL_END
+
+#define KAPI_SIGNAL_FATAL_DEFAULT \
+ KAPI_SIGNAL(2, SIGKILL, "SIGKILL", KAPI_SIGNAL_RECEIVE, KAPI_SIGNAL_ACTION_TERMINATE) \
+ KAPI_SIGNAL_TIMING(KAPI_SIGNAL_TIME_ANYTIME) \
+ KAPI_SIGNAL_PRIORITY(0) \
+ KAPI_SIGNAL_DESC("Process terminated immediately") \
+ KAPI_SIGNAL_END
+
+#define KAPI_SIGNAL_STOP_CONT \
+ KAPI_SIGNAL(3, SIGSTOP, "SIGSTOP", KAPI_SIGNAL_RECEIVE, KAPI_SIGNAL_ACTION_STOP) \
+ KAPI_SIGNAL_TIMING(KAPI_SIGNAL_TIME_ANYTIME) \
+ KAPI_SIGNAL_DESC("Process stopped") \
+ KAPI_SIGNAL_END, \
+ KAPI_SIGNAL(4, SIGCONT, "SIGCONT", KAPI_SIGNAL_RECEIVE, KAPI_SIGNAL_ACTION_CONTINUE) \
+ KAPI_SIGNAL_TIMING(KAPI_SIGNAL_TIME_ANYTIME) \
+ KAPI_SIGNAL_DESC("Process continued") \
+ KAPI_SIGNAL_END
+
+/* Validation and runtime checking */
+
+#ifdef CONFIG_KAPI_RUNTIME_CHECKS
+bool kapi_validate_param(const struct kapi_param_spec *param_spec, s64 value);
+bool kapi_validate_param_with_context(const struct kapi_param_spec *param_spec,
+ s64 value, const s64 *all_params, int param_count);
+int kapi_validate_syscall_param(const struct kernel_api_spec *spec,
+ int param_idx, s64 value);
+int kapi_validate_syscall_params(const struct kernel_api_spec *spec,
+ const s64 *params, int param_count);
+bool kapi_check_return_success(const struct kapi_return_spec *return_spec, s64 retval);
+bool kapi_validate_return_value(const struct kernel_api_spec *spec, s64 retval);
+int kapi_validate_syscall_return(const struct kernel_api_spec *spec, s64 retval);
+void kapi_check_context(const struct kernel_api_spec *spec);
+#else
+static inline bool kapi_validate_param(const struct kapi_param_spec *param_spec, s64 value)
+{
+ return true;
+}
+static inline bool kapi_validate_param_with_context(const struct kapi_param_spec *param_spec,
+ s64 value, const s64 *all_params, int param_count)
+{
+ return true;
+}
+static inline int kapi_validate_syscall_param(const struct kernel_api_spec *spec,
+ int param_idx, s64 value)
+{
+ return 0;
+}
+static inline int kapi_validate_syscall_params(const struct kernel_api_spec *spec,
+ const s64 *params, int param_count)
+{
+ return 0;
+}
+static inline bool kapi_check_return_success(const struct kapi_return_spec *return_spec, s64 retval)
+{
+ return true;
+}
+static inline bool kapi_validate_return_value(const struct kernel_api_spec *spec, s64 retval)
+{
+ return true;
+}
+static inline int kapi_validate_syscall_return(const struct kernel_api_spec *spec, s64 retval)
+{
+ return 0;
+}
+static inline void kapi_check_context(const struct kernel_api_spec *spec) {}
+#endif
+
+/*
+ * Export/query functions
+ *
+ * kapi_get_spec() returns a pointer that is valid only while the caller can
+ * guarantee the spec is not concurrently unregistered (e.g., module unload).
+ * For static specs this is always safe; for dynamic specs callers must hold
+ * a reference or ensure the owning module is pinned.
+ */
+const struct kernel_api_spec *kapi_get_spec(const char *name);
+int kapi_export_json(const struct kernel_api_spec *spec, char *buf, size_t size);
+void kapi_print_spec(const struct kernel_api_spec *spec);
+
+/* Registration for dynamic APIs */
+int kapi_register_spec(struct kernel_api_spec *spec);
+void kapi_unregister_spec(const char *name);
+
+/* Helper to get parameter constraint info */
+static inline bool kapi_get_param_constraint(const char *api_name, int param_idx,
+ enum kapi_constraint_type *type,
+ u64 *valid_mask, s64 *min_val, s64 *max_val)
+{
+ const struct kernel_api_spec *spec;
+
+ might_sleep();
+ spec = kapi_get_spec(api_name);
+
+ if (!spec || param_idx >= spec->param_count)
+ return false;
+
+ if (type)
+ *type = spec->params[param_idx].constraint_type;
+ if (valid_mask)
+ *valid_mask = spec->params[param_idx].valid_mask;
+ if (min_val)
+ *min_val = spec->params[param_idx].min_value;
+ if (max_val)
+ *max_val = spec->params[param_idx].max_value;
+
+ return true;
+}
+
+/* Socket state requirement macros */
+#define KAPI_SOCKET_STATE_REQ(...) \
+ .socket_state = { \
+ .required_states = { __VA_ARGS__ }, \
+ .required_state_count = sizeof((enum kapi_socket_state[]){__VA_ARGS__})/sizeof(enum kapi_socket_state),
+
+#define KAPI_SOCKET_STATE_FORBID(...) \
+ .forbidden_states = { __VA_ARGS__ }, \
+ .forbidden_state_count = sizeof((enum kapi_socket_state[]){__VA_ARGS__})/sizeof(enum kapi_socket_state),
+
+#define KAPI_SOCKET_STATE_RESULT(state) \
+ .resulting_state = state,
+
+#define KAPI_SOCKET_STATE_COND(cond) \
+ .state_condition = cond,
+
+#define KAPI_SOCKET_STATE_PROTOS(protos) \
+ .applicable_protocols = protos,
+
+#define KAPI_SOCKET_STATE_END },
+
+/* Protocol behavior macros */
+#define KAPI_PROTOCOL_BEHAVIOR(idx, protos, desc) \
+ .protocol_behaviors[idx] = { \
+ .applicable_protocols = protos, \
+ .behavior = desc,
+
+#define KAPI_PROTOCOL_FLAGS(flags, desc) \
+ .protocol_flags = flags, \
+ .flag_description = desc,
+
+#define KAPI_PROTOCOL_BEHAVIOR_END },
+
+/* Async behavior macros */
+#define KAPI_ASYNC_SPEC(modes, errno) \
+ .async_spec = { \
+ .supported_modes = modes, \
+ .nonblock_errno = errno,
+
+#define KAPI_ASYNC_POLL(in, out) \
+ .poll_events_in = in, \
+ .poll_events_out = out,
+
+#define KAPI_ASYNC_COMPLETION(cond) \
+ .completion_condition = cond,
+
+#define KAPI_ASYNC_TIMEOUT(supported, desc) \
+ .supports_timeout = supported, \
+ .timeout_behavior = desc,
+
+#define KAPI_ASYNC_END },
+
+/* Buffer behavior macros */
+#define KAPI_BUFFER_SPEC(behaviors) \
+ .buffer_spec = { \
+ .buffer_behaviors = behaviors,
+
+#define KAPI_BUFFER_SIZE(min, max, optimal) \
+ .min_buffer_size = min, \
+ .max_buffer_size = max, \
+ .optimal_buffer_size = optimal,
+
+#define KAPI_BUFFER_PARTIAL(allowed, rules) \
+ .can_partial_transfer = allowed, \
+ .partial_transfer_rules = rules,
+
+#define KAPI_BUFFER_FRAGMENT(rules) \
+ .fragmentation_rules = rules,
+
+#define KAPI_BUFFER_END },
+
+/* Address family macros */
+#define KAPI_ADDR_FAMILY(idx, fam, name, struct_sz, min_len, max_len) \
+ .addr_families[idx] = { \
+ .family = fam, \
+ .family_name = name, \
+ .addr_struct_size = struct_sz, \
+ .min_addr_len = min_len, \
+ .max_addr_len = max_len,
+
+#define KAPI_ADDR_FORMAT(fmt) \
+ .addr_format = fmt,
+
+#define KAPI_ADDR_FEATURES(wildcard, multicast, broadcast) \
+ .supports_wildcard = wildcard, \
+ .supports_multicast = multicast, \
+ .supports_broadcast = broadcast,
+
+#define KAPI_ADDR_SPECIAL(addrs) \
+ .special_addresses = addrs,
+
+#define KAPI_ADDR_PORTS(min, max) \
+ .port_range_min = min, \
+ .port_range_max = max,
+
+#define KAPI_ADDR_FAMILY_END },
+
+#define KAPI_ADDR_FAMILY_COUNT(n) \
+ .addr_family_count = n,
+
+#define KAPI_PROTOCOL_BEHAVIOR_COUNT(n) \
+ .protocol_behavior_count = n,
+
+#define KAPI_CONSTRAINT_COUNT(n) \
+ .constraint_magic = KAPI_MAGIC_CONSTRAINTS, \
+ .constraint_count = n,
+
+/* Network operation characteristics macros */
+#define KAPI_NET_CONNECTION_ORIENTED \
+ .is_connection_oriented = true,
+
+#define KAPI_NET_MESSAGE_ORIENTED \
+ .is_message_oriented = true,
+
+#define KAPI_NET_SUPPORTS_OOB \
+ .supports_oob_data = true,
+
+#define KAPI_NET_SUPPORTS_PEEK \
+ .supports_peek = true,
+
+#define KAPI_NET_REENTRANT \
+ .is_reentrant = true,
+
+/* Semantic description macros */
+#define KAPI_NET_CONN_ESTABLISH(desc) \
+ .connection_establishment = desc,
+
+#define KAPI_NET_CONN_TERMINATE(desc) \
+ .connection_termination = desc,
+
+#define KAPI_NET_DATA_TRANSFER(desc) \
+ .data_transfer_semantics = desc,
+
+#endif /* _LINUX_KERNEL_API_SPEC_H */
diff --git a/include/linux/syscall_api_spec.h b/include/linux/syscall_api_spec.h
new file mode 100644
index 0000000000000..63f7ddc6290b3
--- /dev/null
+++ b/include/linux/syscall_api_spec.h
@@ -0,0 +1,192 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * syscall_api_spec.h - System Call API Specification Integration
+ *
+ * This header extends the SYSCALL_DEFINEX macros to support inline API specifications,
+ * allowing syscall documentation to be written alongside the implementation in a
+ * human-readable and machine-parseable format.
+ */
+
+#ifndef _LINUX_SYSCALL_API_SPEC_H
+#define _LINUX_SYSCALL_API_SPEC_H
+
+#include <linux/kernel_api_spec.h>
+
+/* Automatic syscall validation infrastructure */
+/*
+ * The validation is now integrated directly into the SYSCALL_DEFINEx macros
+ * in syscalls.h when CONFIG_KAPI_RUNTIME_CHECKS is enabled.
+ *
+ * The validation happens in the __do_kapi_sys##name wrapper function which:
+ * 1. Validates all parameters before calling the actual syscall
+ * 2. Calls the real syscall implementation
+ * 3. Validates the return value
+ * 4. Returns the result
+ */
+
+
+/*
+ * Helper macros for common syscall patterns
+ */
+
+/* For syscalls that can sleep */
+#define KAPI_SYSCALL_SLEEPABLE \
+ KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+/* For syscalls that must be atomic */
+#define KAPI_SYSCALL_ATOMIC \
+ KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_ATOMIC)
+
+/* Common parameter specifications */
+#define KAPI_PARAM_FD(idx, desc) \
+ KAPI_PARAM(idx, "fd", "int", desc) \
+ KAPI_PARAM_FLAGS(KAPI_PARAM_IN) \
+ .type = KAPI_TYPE_FD, \
+ .constraint_type = KAPI_CONSTRAINT_NONE, \
+ KAPI_PARAM_END
+
+#define KAPI_PARAM_USER_BUF(idx, name, desc) \
+ KAPI_PARAM(idx, name, "void __user *", desc) \
+ KAPI_PARAM_FLAGS(KAPI_PARAM_USER | KAPI_PARAM_IN) \
+ KAPI_PARAM_END
+
+/**
+ * KAPI_PARAM_USER_STRUCT - Define a userspace struct pointer parameter
+ * @idx: Parameter index (0-based)
+ * @name: Parameter name
+ * @struct_type: The struct type (e.g., struct iocb)
+ * @desc: Parameter description
+ *
+ * This macro defines a parameter that is a userspace pointer to a struct.
+ * The pointer will be validated to ensure:
+ * - The pointer is accessible in userspace
+ * - The memory region of sizeof(struct_type) bytes is accessible
+ */
+#define KAPI_PARAM_USER_STRUCT(idx, name, struct_type, desc) \
+ KAPI_PARAM(idx, name, #struct_type " __user *", desc) \
+ KAPI_PARAM_FLAGS(KAPI_PARAM_USER | KAPI_PARAM_IN) \
+ .type = KAPI_TYPE_USER_PTR, \
+ .size = sizeof(struct_type), \
+ .constraint_type = KAPI_CONSTRAINT_USER_PTR, \
+ KAPI_PARAM_END
+
+/**
+ * KAPI_PARAM_USER_PTR_SIZED - Define a userspace pointer with explicit size
+ * @idx: Parameter index (0-based)
+ * @name: Parameter name
+ * @ptr_size: Size in bytes of the memory region
+ * @desc: Parameter description
+ *
+ * This macro defines a parameter that is a userspace pointer to a memory
+ * region of a specific size. The pointer will be validated to ensure:
+ * - The pointer is accessible in userspace
+ * - The memory region of ptr_size bytes is accessible
+ */
+#define KAPI_PARAM_USER_PTR_SIZED(idx, name, ptr_size, desc) \
+ KAPI_PARAM(idx, name, "void __user *", desc) \
+ KAPI_PARAM_FLAGS(KAPI_PARAM_USER | KAPI_PARAM_IN) \
+ .type = KAPI_TYPE_USER_PTR, \
+ .size = ptr_size, \
+ .constraint_type = KAPI_CONSTRAINT_USER_PTR, \
+ KAPI_PARAM_END
+
+/**
+ * KAPI_PARAM_USER_STRING - Define a userspace null-terminated string parameter
+ * @idx: Parameter index (0-based)
+ * @name: Parameter name
+ * @min_len: Minimum string length (excluding null terminator)
+ * @max_len: Maximum string length (excluding null terminator)
+ * @desc: Parameter description
+ *
+ * This macro defines a parameter that is a userspace pointer to a
+ * null-terminated string. The string will be validated to ensure:
+ * - The pointer is accessible in userspace
+ * - The string length (excluding null terminator) is within [min_len, max_len]
+ */
+#define KAPI_PARAM_USER_STRING(idx, name, min_len, max_len, desc) \
+ KAPI_PARAM(idx, name, "const char __user *", desc) \
+ KAPI_PARAM_FLAGS(KAPI_PARAM_USER | KAPI_PARAM_IN) \
+ .type = KAPI_TYPE_USER_PTR, \
+ .constraint_type = KAPI_CONSTRAINT_USER_STRING, \
+ .min_value = min_len, \
+ .max_value = max_len, \
+ KAPI_PARAM_END
+
+/**
+ * KAPI_PARAM_USER_PATH - Define a userspace pathname parameter
+ * @idx: Parameter index (0-based)
+ * @name: Parameter name
+ * @desc: Parameter description
+ *
+ * This macro defines a parameter that is a userspace pointer to a
+ * null-terminated pathname string. The path will be validated to ensure:
+ * - The pointer is accessible in userspace
+ * - The path is a valid null-terminated string
+ * - The path length does not exceed PATH_MAX (4096 bytes)
+ */
+#define KAPI_PARAM_USER_PATH(idx, name, desc) \
+ KAPI_PARAM(idx, name, "const char __user *", desc) \
+ KAPI_PARAM_FLAGS(KAPI_PARAM_USER | KAPI_PARAM_IN) \
+ .type = KAPI_TYPE_PATH, \
+ .constraint_type = KAPI_CONSTRAINT_USER_PATH, \
+ KAPI_PARAM_END
+
+#define KAPI_PARAM_SIZE_T(idx, name, desc) \
+ KAPI_PARAM(idx, name, "size_t", desc) \
+ KAPI_PARAM_FLAGS(KAPI_PARAM_IN) \
+ KAPI_PARAM_RANGE(0, S64_MAX) \
+ KAPI_PARAM_END
+
+/* Common error specifications */
+#define KAPI_ERROR_EBADF(idx) \
+ KAPI_ERROR(idx, -EBADF, "EBADF", "Invalid file descriptor", \
+ "The file descriptor is not valid or has been closed")
+
+#define KAPI_ERROR_EINVAL(idx, condition) \
+ KAPI_ERROR(idx, -EINVAL, "EINVAL", condition, \
+ "Invalid argument provided")
+
+#define KAPI_ERROR_ENOMEM(idx) \
+ KAPI_ERROR(idx, -ENOMEM, "ENOMEM", "Insufficient memory", \
+ "Cannot allocate memory for the operation")
+
+#define KAPI_ERROR_EPERM(idx) \
+ KAPI_ERROR(idx, -EPERM, "EPERM", "Operation not permitted", \
+ "The calling process does not have the required permissions")
+
+#define KAPI_ERROR_EFAULT(idx) \
+ KAPI_ERROR(idx, -EFAULT, "EFAULT", "Bad address", \
+ "Invalid user space address provided")
+
+/* Standard return value specifications */
+#define KAPI_RETURN_SUCCESS_ZERO \
+ KAPI_RETURN("long", "0 on success, negative error code on failure") \
+ KAPI_RETURN_SUCCESS(0, "== 0") \
+ KAPI_RETURN_END
+
+#define KAPI_RETURN_FD_SPEC \
+ KAPI_RETURN("long", "File descriptor on success, negative error code on failure") \
+ .check_type = KAPI_RETURN_FD, \
+ KAPI_RETURN_END
+
+#define KAPI_RETURN_COUNT \
+ KAPI_RETURN("long", "Number of bytes processed on success, negative error code on failure") \
+ KAPI_RETURN_SUCCESS(0, ">= 0") \
+ KAPI_RETURN_END
+
+/**
+ * KAPI_SINCE_VERSION - Set the since version
+ * @version: Version string when the API was introduced
+ */
+#define KAPI_SINCE_VERSION(version) \
+ .since_version = version,
+
+
+/**
+ * KAPI_SIGNAL_MASK_COUNT - Set the signal mask count
+ * @count: Number of signal masks defined
+ */
+#define KAPI_SIGNAL_MASK_COUNT(count) \
+ .signal_mask_count = count,
+
+#endif /* _LINUX_SYSCALL_API_SPEC_H */
\ No newline at end of file
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 02bd6ddb62782..76758cc0f50d8 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -88,6 +88,7 @@ struct file_attr;
#include <linux/bug.h>
#include <linux/sem.h>
#include <asm/siginfo.h>
+#include <linux/syscall_api_spec.h>
#include <linux/unistd.h>
#include <linux/quota.h>
#include <linux/key.h>
@@ -133,6 +134,7 @@ struct file_attr;
#define __SC_TYPE(t, a) t
#define __SC_ARGS(t, a) a
#define __SC_TEST(t, a) (void)BUILD_BUG_ON_ZERO(!__TYPE_IS_LL(t) && sizeof(t) > sizeof(long))
+#define __SC_CAST_TO_S64(t, a) (s64)(a)
#ifdef CONFIG_FTRACE_SYSCALLS
#define __SC_STR_ADECL(t, a) #a
@@ -243,6 +245,42 @@ static inline int is_syscall_trace_event(struct trace_event_call *tp_event)
* done within __do_sys_*().
*/
#ifndef __SYSCALL_DEFINEx
+#ifdef CONFIG_KAPI_RUNTIME_CHECKS
+#define __SYSCALL_DEFINEx(x, name, ...) \
+ __diag_push(); \
+ __diag_ignore(GCC, 8, "-Wattribute-alias", \
+ "Type aliasing is used to sanitize syscall arguments");\
+ asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) \
+ __attribute__((alias(__stringify(__se_sys##name)))); \
+ ALLOW_ERROR_INJECTION(sys##name, ERRNO); \
+ static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\
+ static inline long __do_kapi_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \
+ asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
+ asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \
+ { \
+ long ret = __do_kapi_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));\
+ __MAP(x,__SC_TEST,__VA_ARGS__); \
+ __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \
+ return ret; \
+ } \
+ __diag_pop(); \
+ static inline long __do_kapi_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))\
+ { \
+ const struct kernel_api_spec *__spec = kapi_get_spec("sys" #name); \
+ if (__spec) { \
+ s64 __params[x] = { __MAP(x,__SC_CAST_TO_S64,__VA_ARGS__) }; \
+ int __ret = kapi_validate_syscall_params(__spec, __params, x); \
+ if (__ret) \
+ return __ret; \
+ } \
+ long ret = __do_sys##name(__MAP(x,__SC_ARGS,__VA_ARGS__)); \
+ if (__spec) { \
+ kapi_validate_syscall_return(__spec, (s64)ret); \
+ } \
+ return ret; \
+ } \
+ static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
+#else /* !CONFIG_KAPI_RUNTIME_CHECKS */
#define __SYSCALL_DEFINEx(x, name, ...) \
__diag_push(); \
__diag_ignore(GCC, 8, "-Wattribute-alias", \
@@ -261,6 +299,7 @@ static inline int is_syscall_trace_event(struct trace_event_call *tp_event)
} \
__diag_pop(); \
static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
+#endif /* CONFIG_KAPI_RUNTIME_CHECKS */
#endif /* __SYSCALL_DEFINEx */
/* For split 64-bit arguments on 32-bit architectures */
diff --git a/init/Kconfig b/init/Kconfig
index c25869cf59c17..32e7bd8fc9991 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -2223,6 +2223,8 @@ source "kernel/Kconfig.kexec"
source "kernel/liveupdate/Kconfig"
+source "kernel/api/Kconfig"
+
endmenu # General setup
source "arch/Kconfig"
diff --git a/kernel/Makefile b/kernel/Makefile
index 6785982013dce..5643151536439 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -59,6 +59,9 @@ obj-y += dma/
obj-y += entry/
obj-y += unwind/
obj-$(CONFIG_MODULES) += module/
+obj-$(CONFIG_KAPI_SPEC) += api/
+# Ensure api/ is always cleaned even when CONFIG_KAPI_SPEC is not set
+obj- += api/
obj-$(CONFIG_KCMP) += kcmp.o
obj-$(CONFIG_FREEZER) += freezer.o
diff --git a/kernel/api/.gitignore b/kernel/api/.gitignore
new file mode 100644
index 0000000000000..ca2f632621cfc
--- /dev/null
+++ b/kernel/api/.gitignore
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+/generated_api_specs.c
diff --git a/kernel/api/Kconfig b/kernel/api/Kconfig
new file mode 100644
index 0000000000000..bdd655458dc2f
--- /dev/null
+++ b/kernel/api/Kconfig
@@ -0,0 +1,70 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Kernel API Specification Framework Configuration
+#
+
+config KAPI_SPEC
+ bool "Kernel API Specification Framework"
+ default n
+ help
+ This option enables the kernel API specification framework,
+ which provides formal documentation of kernel APIs in both
+ human and machine-readable formats.
+
+ The framework allows developers to document APIs inline with
+ their implementation, including parameter specifications,
+ return values, error conditions, locking requirements, and
+ execution context constraints.
+
+ When enabled, API specifications can be queried at runtime
+ and exported in JSON format through debugfs.
+
+ If unsure, say N.
+
+config KAPI_RUNTIME_CHECKS
+ bool "Runtime API specification checks"
+ depends on KAPI_SPEC
+ depends on DEBUG_KERNEL
+ default n
+ help
+ Enable runtime validation of API usage against specifications.
+ This includes checking execution context requirements, parameter
+ validation, and lock state verification.
+
+ This adds overhead and should only be used for debugging and
+ development. The checks use WARN_ONCE to report violations.
+
+ If unsure, say N.
+
+config KAPI_SPEC_DEBUGFS
+ bool "Export kernel API specifications via debugfs"
+ depends on KAPI_SPEC
+ depends on DEBUG_FS
+ default n
+ help
+ This option enables exporting kernel API specifications through
+ the debugfs filesystem. When enabled, specifications can be
+ accessed at /sys/kernel/debug/kapi/.
+
+ The debugfs interface provides:
+ - A list of all available API specifications
+ - Detailed information for each API including parameters,
+ return values, errors, locking requirements, and constraints
+ - Complete machine-readable representation of the specs
+
+ This is useful for documentation tools, static analyzers, and
+ runtime introspection of kernel APIs.
+
+ If unsure, say N.
+
+config KAPI_KUNIT_TEST
+ tristate "KUnit tests for KAPI framework" if !KUNIT_ALL_TESTS
+ depends on KAPI_SPEC
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ KUnit tests for the Kernel API Specification Framework.
+ Tests registration, lookup, validation constraints, and
+ JSON export functionality.
+
+ If unsure, say N.
diff --git a/kernel/api/Makefile b/kernel/api/Makefile
new file mode 100644
index 0000000000000..c0a13fc590e4a
--- /dev/null
+++ b/kernel/api/Makefile
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the Kernel API Specification Framework
+#
+
+# Core API specification framework
+obj-$(CONFIG_KAPI_SPEC) += kernel_api_spec.o
+
+# Debugfs interface for kernel API specs
+obj-$(CONFIG_KAPI_SPEC_DEBUGFS) += kapi_debugfs.o
+
+# KUnit tests
+obj-$(CONFIG_KAPI_KUNIT_TEST) += kapi_kunit.o
+
diff --git a/kernel/api/kapi_kunit.c b/kernel/api/kapi_kunit.c
new file mode 100644
index 0000000000000..da4b69d2ea2a8
--- /dev/null
+++ b/kernel/api/kapi_kunit.c
@@ -0,0 +1,536 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for the Kernel API Specification Framework
+ *
+ * Tests registration, lookup, validation, and JSON export functionality.
+ */
+
+#include <kunit/test.h>
+#include <linux/kernel_api_spec.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+
+static void init_test_spec(struct kernel_api_spec *spec, const char *name)
+{
+ memset(spec, 0, sizeof(*spec));
+ strscpy(spec->name, name, KAPI_MAX_NAME_LEN);
+ spec->version = 1;
+ strscpy(spec->description, "Test API", KAPI_MAX_DESC_LEN);
+}
+
+/* Test 1: kapi_register_spec with valid spec returns 0 */
+static void test_register_valid(struct kunit *test)
+{
+ struct kernel_api_spec *spec;
+ int ret;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec);
+
+ init_test_spec(spec, "test_register_valid");
+
+ ret = kapi_register_spec(spec);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+
+ kapi_unregister_spec("test_register_valid");
+ kfree(spec);
+}
+
+/* Test 2: kapi_get_spec returns registered spec */
+static void test_lookup_registered(struct kunit *test)
+{
+ struct kernel_api_spec *spec;
+ const struct kernel_api_spec *found;
+ int ret;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec);
+
+ init_test_spec(spec, "test_lookup_func");
+
+ ret = kapi_register_spec(spec);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ found = kapi_get_spec("test_lookup_func");
+ KUNIT_EXPECT_PTR_EQ(test, found, (const struct kernel_api_spec *)spec);
+
+ kapi_unregister_spec("test_lookup_func");
+ kfree(spec);
+}
+
+/* Test 3: Double registration returns -EEXIST */
+static void test_double_register(struct kunit *test)
+{
+ struct kernel_api_spec *spec;
+ int ret;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec);
+
+ init_test_spec(spec, "test_double_reg");
+
+ ret = kapi_register_spec(spec);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ ret = kapi_register_spec(spec);
+ KUNIT_EXPECT_EQ(test, ret, -EEXIST);
+
+ kapi_unregister_spec("test_double_reg");
+ kfree(spec);
+}
+
+/* Test 4: Unregister makes spec unfindable */
+static void test_unregister(struct kunit *test)
+{
+ struct kernel_api_spec *spec;
+ const struct kernel_api_spec *found;
+ int ret;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec);
+
+ init_test_spec(spec, "test_unreg_func");
+
+ ret = kapi_register_spec(spec);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ kapi_unregister_spec("test_unreg_func");
+
+ found = kapi_get_spec("test_unreg_func");
+ KUNIT_EXPECT_NULL(test, found);
+
+ kfree(spec);
+}
+
+/* Test 5: kapi_get_spec(NULL) returns NULL */
+static void test_get_spec_null(struct kunit *test)
+{
+ const struct kernel_api_spec *found;
+
+ found = kapi_get_spec(NULL);
+ KUNIT_EXPECT_NULL(test, found);
+}
+
+/* Test 6: kapi_register_spec(NULL) returns -EINVAL */
+static void test_register_null(struct kunit *test)
+{
+ int ret;
+
+ ret = kapi_register_spec(NULL);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+}
+
+/* Test 7: Spec with empty name is rejected */
+static void test_register_empty_name(struct kunit *test)
+{
+ struct kernel_api_spec *spec;
+ int ret;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec);
+
+ /* name[0] == '\0' from kzalloc */
+ ret = kapi_register_spec(spec);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ kfree(spec);
+}
+
+#ifdef CONFIG_KAPI_RUNTIME_CHECKS
+
+/* Test 8: RANGE constraint - value in range is valid */
+static void test_constraint_range_valid(struct kunit *test)
+{
+ struct kapi_param_spec param = {};
+
+ strscpy(param.name, "test_param", sizeof(param.name));
+ param.constraint_type = KAPI_CONSTRAINT_RANGE;
+ param.min_value = 0;
+ param.max_value = 100;
+
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 0));
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 50));
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 100));
+}
+
+/* Test 9: RANGE constraint - value out of range is invalid */
+static void test_constraint_range_invalid(struct kunit *test)
+{
+ struct kapi_param_spec param = {};
+
+ strscpy(param.name, "test_param", sizeof(param.name));
+ param.constraint_type = KAPI_CONSTRAINT_RANGE;
+ param.min_value = 0;
+ param.max_value = 100;
+
+ KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, -1));
+ KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 101));
+}
+
+/* Test 10: MASK constraint - valid bits pass */
+static void test_constraint_mask_valid(struct kunit *test)
+{
+ struct kapi_param_spec param = {};
+
+ strscpy(param.name, "test_flags", sizeof(param.name));
+ param.constraint_type = KAPI_CONSTRAINT_MASK;
+ param.valid_mask = 0xFF;
+
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 0x00));
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 0x0F));
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 0xFF));
+}
+
+/* Test 11: MASK constraint - extra bits fail */
+static void test_constraint_mask_invalid(struct kunit *test)
+{
+ struct kapi_param_spec param = {};
+
+ strscpy(param.name, "test_flags", sizeof(param.name));
+ param.constraint_type = KAPI_CONSTRAINT_MASK;
+ param.valid_mask = 0xFF;
+
+ KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 0x100));
+ KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 0x1FF));
+}
+
+/* Test 12: POWER_OF_TWO constraint */
+static void test_constraint_power_of_two(struct kunit *test)
+{
+ struct kapi_param_spec param = {};
+
+ strscpy(param.name, "test_pot", sizeof(param.name));
+ param.constraint_type = KAPI_CONSTRAINT_POWER_OF_TWO;
+
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 1));
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 2));
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 4));
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 8));
+ KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 0));
+ KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 3));
+ KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 5));
+}
+
+/* Test 13: PAGE_ALIGNED constraint */
+static void test_constraint_page_aligned(struct kunit *test)
+{
+ struct kapi_param_spec param = {};
+
+ strscpy(param.name, "test_page", sizeof(param.name));
+ param.constraint_type = KAPI_CONSTRAINT_PAGE_ALIGNED;
+
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 0));
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, PAGE_SIZE));
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 2 * PAGE_SIZE));
+ KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 1));
+ KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, PAGE_SIZE - 1));
+}
+
+/* Test 14: NONZERO constraint */
+static void test_constraint_nonzero(struct kunit *test)
+{
+ struct kapi_param_spec param = {};
+
+ strscpy(param.name, "test_nz", sizeof(param.name));
+ param.constraint_type = KAPI_CONSTRAINT_NONZERO;
+
+ KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 0));
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 1));
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, -1));
+}
+
+/* Test 15: Return value validation - success */
+static void test_return_validation(struct kunit *test)
+{
+ struct kernel_api_spec *spec;
+
+ spec = kunit_kzalloc(test, sizeof(*spec), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec);
+
+ strscpy(spec->name, "test_ret", sizeof(spec->name));
+ spec->return_magic = KAPI_MAGIC_RETURN;
+ spec->return_spec.check_type = KAPI_RETURN_EXACT;
+ spec->return_spec.success_value = 0;
+
+ KUNIT_EXPECT_TRUE(test, kapi_validate_return_value(spec, 0));
+}
+
+/* Test 16: Return value validation - known error */
+static void test_return_known_error(struct kunit *test)
+{
+ struct kernel_api_spec *spec;
+
+ spec = kunit_kzalloc(test, sizeof(*spec), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec);
+
+ strscpy(spec->name, "test_ret_err", sizeof(spec->name));
+ spec->return_magic = KAPI_MAGIC_RETURN;
+ spec->return_spec.check_type = KAPI_RETURN_FD;
+ spec->error_count = 1;
+ spec->errors[0].error_code = -ENOENT;
+ strscpy(spec->errors[0].name, "ENOENT", sizeof(spec->errors[0].name));
+
+ /* -ENOENT is in the error list, so it's valid */
+ KUNIT_EXPECT_TRUE(test, kapi_validate_return_value(spec, -ENOENT));
+}
+
+/* Test 17: Return value validation - unknown error */
+static void test_return_unknown_error(struct kunit *test)
+{
+ struct kernel_api_spec *spec;
+
+ spec = kunit_kzalloc(test, sizeof(*spec), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec);
+
+ strscpy(spec->name, "test_ret_unk", sizeof(spec->name));
+ spec->return_magic = KAPI_MAGIC_RETURN;
+ spec->return_spec.check_type = KAPI_RETURN_FD;
+ spec->error_count = 1;
+ spec->errors[0].error_code = -ENOENT;
+ strscpy(spec->errors[0].name, "ENOENT", sizeof(spec->errors[0].name));
+
+ /* -EPERM is not in the error list, but unlisted errors are accepted
+ * since filesystem/device-specific errors may not be exhaustively listed
+ */
+ KUNIT_EXPECT_TRUE(test, kapi_validate_return_value(spec, -EPERM));
+}
+
+/* Test 18: ALIGNMENT constraint */
+static void test_constraint_alignment(struct kunit *test)
+{
+ struct kapi_param_spec param = {};
+
+ strscpy(param.name, "test_align", sizeof(param.name));
+ param.constraint_type = KAPI_CONSTRAINT_ALIGNMENT;
+ param.alignment = 8;
+
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 0));
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 8));
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 16));
+ KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 1));
+ KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 7));
+}
+
+/* Test 19: FD validation rejects values > INT_MAX */
+static void test_fd_int_overflow(struct kunit *test)
+{
+ struct kapi_param_spec param = {};
+
+ strscpy(param.name, "test_fd", sizeof(param.name));
+ param.type = KAPI_TYPE_FD;
+ param.constraint_type = KAPI_CONSTRAINT_NONE;
+
+ /* Value that overflows int: 0x100000003 -> truncates to 3 */
+ KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 0x100000003LL));
+}
+
+/* Test 23: ENUM constraint */
+static const s64 test_enum_vals[] = { 1, 5, 10 };
+
+static void test_constraint_enum(struct kunit *test)
+{
+ struct kapi_param_spec param = {};
+
+ strscpy(param.name, "test_enum", sizeof(param.name));
+ param.constraint_type = KAPI_CONSTRAINT_ENUM;
+ param.enum_values = test_enum_vals;
+ param.enum_count = ARRAY_SIZE(test_enum_vals);
+
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 1));
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 5));
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 10));
+ KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 0));
+ KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 3));
+ KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 11));
+}
+
+/* Test 24: BUFFER constraint always accepts (size checked at runtime) */
+static void test_constraint_buffer(struct kunit *test)
+{
+ struct kapi_param_spec param = {};
+
+ strscpy(param.name, "test_buf", sizeof(param.name));
+ param.constraint_type = KAPI_CONSTRAINT_BUFFER;
+
+ /* Buffer constraint doesn't validate the value itself */
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 0));
+ KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 4096));
+}
+
+/* Test 25: RETURN_RANGE check type */
+static void test_return_range(struct kunit *test)
+{
+ struct kernel_api_spec *spec;
+
+ spec = kunit_kzalloc(test, sizeof(*spec), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec);
+
+ strscpy(spec->name, "test_ret_range", sizeof(spec->name));
+ spec->return_magic = KAPI_MAGIC_RETURN;
+ spec->return_spec.check_type = KAPI_RETURN_RANGE;
+ spec->return_spec.success_min = 0;
+ spec->return_spec.success_max = 100;
+
+ KUNIT_EXPECT_TRUE(test, kapi_validate_return_value(spec, 0));
+ KUNIT_EXPECT_TRUE(test, kapi_validate_return_value(spec, 50));
+ KUNIT_EXPECT_TRUE(test, kapi_validate_return_value(spec, 100));
+}
+
+#endif /* CONFIG_KAPI_RUNTIME_CHECKS */
+
+/* Test 26: Unregister non-existent spec is a no-op */
+static void test_unregister_nonexistent(struct kunit *test)
+{
+ /* Should not crash or error */
+ kapi_unregister_spec("nonexistent_spec_xyz");
+}
+
+/* Test 27: Multiple specs can be registered and looked up */
+static void test_multiple_specs(struct kunit *test)
+{
+ struct kernel_api_spec *spec1, *spec2;
+ const struct kernel_api_spec *found;
+
+ spec1 = kzalloc_obj(*spec1, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec1);
+ spec2 = kzalloc_obj(*spec2, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec2);
+
+ init_test_spec(spec1, "multi_spec_1");
+ init_test_spec(spec2, "multi_spec_2");
+
+ KUNIT_ASSERT_EQ(test, kapi_register_spec(spec1), 0);
+ KUNIT_ASSERT_EQ(test, kapi_register_spec(spec2), 0);
+
+ found = kapi_get_spec("multi_spec_1");
+ KUNIT_EXPECT_PTR_EQ(test, found, (const struct kernel_api_spec *)spec1);
+
+ found = kapi_get_spec("multi_spec_2");
+ KUNIT_EXPECT_PTR_EQ(test, found, (const struct kernel_api_spec *)spec2);
+
+ kapi_unregister_spec("multi_spec_1");
+ kapi_unregister_spec("multi_spec_2");
+ kfree(spec1);
+ kfree(spec2);
+}
+
+/* Test 20: JSON export produces valid output */
+static void test_json_export(struct kunit *test)
+{
+ struct kernel_api_spec *spec;
+ char *buf;
+ int ret;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec);
+
+ buf = kzalloc(4096, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
+
+ init_test_spec(spec, "test_json");
+ spec->param_count = 1;
+ strscpy(spec->params[0].name, "arg0", sizeof(spec->params[0].name));
+ strscpy(spec->params[0].type_name, "int", sizeof(spec->params[0].type_name));
+
+ ret = kapi_export_json(spec, buf, 4096);
+ KUNIT_EXPECT_GT(test, ret, 0);
+
+ /* Verify it starts with '{' and ends with '}' */
+ KUNIT_EXPECT_EQ(test, buf[0], '{');
+ KUNIT_ASSERT_GT(test, ret, 1);
+ /* Find last non-whitespace char */
+ while (ret > 0 && (buf[ret - 1] == '\n' || buf[ret - 1] == ' '))
+ ret--;
+ KUNIT_EXPECT_EQ(test, buf[ret - 1], '}');
+
+ /* Verify key fields are present */
+ KUNIT_EXPECT_NOT_NULL(test, strstr(buf, "\"name\""));
+ KUNIT_EXPECT_NOT_NULL(test, strstr(buf, "\"test_json\""));
+ KUNIT_EXPECT_NOT_NULL(test, strstr(buf, "\"parameters\""));
+
+ kfree(buf);
+ kfree(spec);
+}
+
+/* Test 21: JSON export with NULL args returns -EINVAL */
+static void test_json_export_null(struct kunit *test)
+{
+ struct kernel_api_spec *spec;
+ char buf[64];
+ int ret;
+
+ ret = kapi_export_json(NULL, buf, sizeof(buf));
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ spec = kunit_kzalloc(test, sizeof(*spec), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec);
+ init_test_spec(spec, "test");
+
+ ret = kapi_export_json(spec, NULL, 64);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ ret = kapi_export_json(spec, buf, 0);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+}
+
+/* Test 22: JSON export with small buffer truncates gracefully */
+static void test_json_export_small_buffer(struct kunit *test)
+{
+ struct kernel_api_spec *spec;
+ char buf[64];
+ int ret;
+
+ spec = kunit_kzalloc(test, sizeof(*spec), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec);
+ init_test_spec(spec, "test_small");
+
+ ret = kapi_export_json(spec, buf, sizeof(buf));
+
+ /* Should return number of bytes written, buffer too small for full JSON */
+ KUNIT_EXPECT_GT(test, ret, 0);
+ KUNIT_EXPECT_LT(test, ret, (int)sizeof(buf));
+}
+
+static struct kunit_case kapi_test_cases[] = {
+ KUNIT_CASE(test_register_valid),
+ KUNIT_CASE(test_lookup_registered),
+ KUNIT_CASE(test_double_register),
+ KUNIT_CASE(test_unregister),
+ KUNIT_CASE(test_get_spec_null),
+ KUNIT_CASE(test_register_null),
+ KUNIT_CASE(test_register_empty_name),
+#ifdef CONFIG_KAPI_RUNTIME_CHECKS
+ KUNIT_CASE(test_constraint_range_valid),
+ KUNIT_CASE(test_constraint_range_invalid),
+ KUNIT_CASE(test_constraint_mask_valid),
+ KUNIT_CASE(test_constraint_mask_invalid),
+ KUNIT_CASE(test_constraint_power_of_two),
+ KUNIT_CASE(test_constraint_page_aligned),
+ KUNIT_CASE(test_constraint_nonzero),
+ KUNIT_CASE(test_return_validation),
+ KUNIT_CASE(test_return_known_error),
+ KUNIT_CASE(test_return_unknown_error),
+ KUNIT_CASE(test_constraint_alignment),
+ KUNIT_CASE(test_fd_int_overflow),
+ KUNIT_CASE(test_constraint_enum),
+ KUNIT_CASE(test_constraint_buffer),
+ KUNIT_CASE(test_return_range),
+#endif
+ KUNIT_CASE(test_unregister_nonexistent),
+ KUNIT_CASE(test_multiple_specs),
+ KUNIT_CASE(test_json_export),
+ KUNIT_CASE(test_json_export_null),
+ KUNIT_CASE(test_json_export_small_buffer),
+ {}
+};
+
+static struct kunit_suite kapi_test_suite = {
+ .name = "kapi",
+ .test_cases = kapi_test_cases,
+};
+
+kunit_test_suite(kapi_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for Kernel API Specification Framework");
+MODULE_LICENSE("GPL");
diff --git a/kernel/api/kernel_api_spec.c b/kernel/api/kernel_api_spec.c
new file mode 100644
index 0000000000000..836af6d501a13
--- /dev/null
+++ b/kernel/api/kernel_api_spec.c
@@ -0,0 +1,1277 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * kernel_api_spec.c - Kernel API Specification Framework Implementation
+ *
+ * Provides runtime support for kernel API specifications including validation,
+ * export to various formats, and querying capabilities.
+ */
+
+#include <linux/kernel.h>
+#include <linux/kernel_api_spec.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/export.h>
+#include <linux/preempt.h>
+#include <linux/hardirq.h>
+#include <linux/file.h>
+#include <linux/fdtable.h>
+#include <linux/uaccess.h>
+#include <linux/limits.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/ratelimit.h>
+
+/* Section where API specifications are stored */
+extern struct kernel_api_spec __start_kapi_specs[];
+extern struct kernel_api_spec __stop_kapi_specs[];
+
+/* Dynamic API registration */
+static LIST_HEAD(dynamic_api_specs);
+static DEFINE_MUTEX(api_spec_mutex);
+
+struct dynamic_api_spec {
+ struct list_head list;
+ struct kernel_api_spec *spec;
+};
+
+/*
+ * __kapi_find_spec_locked - Internal lookup, caller must hold api_spec_mutex
+ */
+static const struct kernel_api_spec *__kapi_find_spec_locked(const char *name)
+{
+ struct kernel_api_spec *spec;
+ struct dynamic_api_spec *dyn_spec;
+
+ /* Search static specifications */
+ for (spec = __start_kapi_specs; spec < __stop_kapi_specs; spec++) {
+ if (strcmp(spec->name, name) == 0)
+ return spec;
+ }
+
+ /* Search dynamic specifications (mutex already held) */
+ list_for_each_entry(dyn_spec, &dynamic_api_specs, list) {
+ if (strcmp(dyn_spec->spec->name, name) == 0)
+ return dyn_spec->spec;
+ }
+
+ return NULL;
+}
+
+/**
+ * kapi_get_spec - Get API specification by name
+ * @name: Function name to look up
+ *
+ * Return: Pointer to API specification or NULL if not found
+ */
+const struct kernel_api_spec *kapi_get_spec(const char *name)
+{
+ const struct kernel_api_spec *spec;
+
+ if (!name)
+ return NULL;
+
+ mutex_lock(&api_spec_mutex);
+ spec = __kapi_find_spec_locked(name);
+ mutex_unlock(&api_spec_mutex);
+
+ return spec;
+}
+EXPORT_SYMBOL_GPL(kapi_get_spec);
+
+/**
+ * kapi_register_spec - Register a dynamic API specification
+ * @spec: API specification to register
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int kapi_register_spec(struct kernel_api_spec *spec)
+{
+ struct dynamic_api_spec *dyn_spec;
+ int ret = 0;
+
+ if (!spec || !spec->name[0])
+ return -EINVAL;
+
+ dyn_spec = kzalloc(sizeof(*dyn_spec), GFP_KERNEL);
+ if (!dyn_spec)
+ return -ENOMEM;
+
+ dyn_spec->spec = spec;
+
+ mutex_lock(&api_spec_mutex);
+
+ /* Check if already exists while holding lock to prevent races */
+ if (__kapi_find_spec_locked(spec->name)) {
+ ret = -EEXIST;
+ kfree(dyn_spec);
+ } else {
+ list_add_tail(&dyn_spec->list, &dynamic_api_specs);
+ }
+
+ mutex_unlock(&api_spec_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(kapi_register_spec);
+
+/**
+ * kapi_unregister_spec - Unregister a dynamic API specification
+ * @name: Name of API to unregister
+ */
+void kapi_unregister_spec(const char *name)
+{
+ struct dynamic_api_spec *dyn_spec, *tmp;
+
+ if (!name)
+ return;
+
+ mutex_lock(&api_spec_mutex);
+ list_for_each_entry_safe(dyn_spec, tmp, &dynamic_api_specs, list) {
+ if (strcmp(dyn_spec->spec->name, name) == 0) {
+ list_del(&dyn_spec->list);
+ kfree(dyn_spec);
+ break;
+ }
+ }
+ mutex_unlock(&api_spec_mutex);
+}
+EXPORT_SYMBOL_GPL(kapi_unregister_spec);
+
+/**
+ * param_type_to_string - Convert parameter type to string
+ * @type: Parameter type
+ *
+ * Return: String representation of type
+ */
+static const char *param_type_to_string(enum kapi_param_type type)
+{
+ static const char * const type_names[] = {
+ [KAPI_TYPE_VOID] = "void",
+ [KAPI_TYPE_INT] = "int",
+ [KAPI_TYPE_UINT] = "uint",
+ [KAPI_TYPE_PTR] = "pointer",
+ [KAPI_TYPE_STRUCT] = "struct",
+ [KAPI_TYPE_UNION] = "union",
+ [KAPI_TYPE_ENUM] = "enum",
+ [KAPI_TYPE_FUNC_PTR] = "function_pointer",
+ [KAPI_TYPE_ARRAY] = "array",
+ [KAPI_TYPE_FD] = "file_descriptor",
+ [KAPI_TYPE_USER_PTR] = "user_pointer",
+ [KAPI_TYPE_PATH] = "pathname",
+ [KAPI_TYPE_CUSTOM] = "custom",
+ };
+
+ if (type >= ARRAY_SIZE(type_names))
+ return "unknown";
+
+ return type_names[type];
+}
+
+/**
+ * lock_type_to_string - Convert lock type to string
+ * @type: Lock type
+ *
+ * Return: String representation of lock type
+ */
+static const char *lock_type_to_string(enum kapi_lock_type type)
+{
+ static const char * const lock_names[] = {
+ [KAPI_LOCK_NONE] = "none",
+ [KAPI_LOCK_MUTEX] = "mutex",
+ [KAPI_LOCK_SPINLOCK] = "spinlock",
+ [KAPI_LOCK_RWLOCK] = "rwlock",
+ [KAPI_LOCK_SEQLOCK] = "seqlock",
+ [KAPI_LOCK_RCU] = "rcu",
+ [KAPI_LOCK_SEMAPHORE] = "semaphore",
+ [KAPI_LOCK_CUSTOM] = "custom",
+ };
+
+ if (type >= ARRAY_SIZE(lock_names))
+ return "unknown";
+
+ return lock_names[type];
+}
+
+/**
+ * lock_scope_to_string - Convert lock scope to string
+ * @scope: Lock scope
+ *
+ * Return: String representation of lock scope
+ */
+static const char *lock_scope_to_string(enum kapi_lock_scope scope)
+{
+ static const char * const scope_names[] = {
+ [KAPI_LOCK_INTERNAL] = "internal",
+ [KAPI_LOCK_ACQUIRES] = "acquires",
+ [KAPI_LOCK_RELEASES] = "releases",
+ [KAPI_LOCK_CALLER_HELD] = "caller_held",
+ };
+
+ if (scope >= ARRAY_SIZE(scope_names))
+ return "unknown";
+
+ return scope_names[scope];
+}
+
+/**
+ * return_check_type_to_string - Convert return check type to string
+ * @type: Return check type
+ *
+ * Return: String representation of return check type
+ */
+static const char *return_check_type_to_string(enum kapi_return_check_type type)
+{
+ static const char * const check_names[] = {
+ [KAPI_RETURN_EXACT] = "exact",
+ [KAPI_RETURN_RANGE] = "range",
+ [KAPI_RETURN_ERROR_CHECK] = "error_check",
+ [KAPI_RETURN_FD] = "file_descriptor",
+ [KAPI_RETURN_CUSTOM] = "custom",
+ [KAPI_RETURN_NO_RETURN] = "no_return",
+ };
+
+ if (type >= ARRAY_SIZE(check_names))
+ return "unknown";
+
+ return check_names[type];
+}
+
+/**
+ * capability_action_to_string - Convert capability action to string
+ * @action: Capability action
+ *
+ * Return: String representation of capability action
+ */
+static const char *capability_action_to_string(enum kapi_capability_action action)
+{
+ static const char * const action_names[] = {
+ [KAPI_CAP_BYPASS_CHECK] = "bypass_check",
+ [KAPI_CAP_INCREASE_LIMIT] = "increase_limit",
+ [KAPI_CAP_OVERRIDE_RESTRICTION] = "override_restriction",
+ [KAPI_CAP_GRANT_PERMISSION] = "grant_permission",
+ [KAPI_CAP_MODIFY_BEHAVIOR] = "modify_behavior",
+ [KAPI_CAP_ACCESS_RESOURCE] = "access_resource",
+ [KAPI_CAP_PERFORM_OPERATION] = "perform_operation",
+ };
+
+ if (action >= ARRAY_SIZE(action_names))
+ return "unknown";
+
+ return action_names[action];
+}
+
+/*
+ * kapi_json_escape - Write a JSON-escaped string into a buffer
+ * @buf: Output buffer
+ * @size: Remaining space in buffer
+ * @str: Input string to escape
+ *
+ * Escapes backslash, double-quote, and control characters for JSON output.
+ * Return: Number of bytes written (via scnprintf semantics)
+ */
+static int kapi_json_escape(char *buf, size_t size, const char *str)
+{
+ int ret = 0;
+ const char *p;
+
+ if (!str || size == 0)
+ return 0;
+
+ for (p = str; *p && ret < size - 1; p++) {
+ switch (*p) {
+ case '\\':
+ ret += scnprintf(buf + ret, size - ret, "\\\\");
+ break;
+ case '"':
+ ret += scnprintf(buf + ret, size - ret, "\\\"");
+ break;
+ case '\n':
+ ret += scnprintf(buf + ret, size - ret, "\\n");
+ break;
+ case '\r':
+ ret += scnprintf(buf + ret, size - ret, "\\r");
+ break;
+ case '\t':
+ ret += scnprintf(buf + ret, size - ret, "\\t");
+ break;
+ default:
+ if ((unsigned char)*p < 0x20) {
+ ret += scnprintf(buf + ret, size - ret,
+ "\\u%04x", (unsigned char)*p);
+ } else {
+ ret += scnprintf(buf + ret, size - ret,
+ "%c", *p);
+ }
+ break;
+ }
+ }
+
+ if (ret < size)
+ buf[ret] = '\0';
+
+ return ret;
+}
+
+/* Helper to write a JSON-escaped string field */
+static int kapi_json_str(char *buf, size_t size, const char *str)
+{
+ int ret = 0;
+
+ ret += scnprintf(buf, size, "\"");
+ ret += kapi_json_escape(buf + ret, size - ret, str);
+ ret += scnprintf(buf + ret, size - ret, "\"");
+ return ret;
+}
+
+/**
+ * kapi_export_json - Export API specification to JSON format
+ * @spec: API specification to export
+ * @buf: Buffer to write JSON to
+ * @size: Size of buffer
+ *
+ * Return: Number of bytes written or negative error
+ */
+int kapi_export_json(const struct kernel_api_spec *spec, char *buf, size_t size)
+{
+ int ret = 0;
+ int i;
+
+ if (!spec || !buf || size == 0)
+ return -EINVAL;
+
+ ret = scnprintf(buf, size, "{\n \"name\": ");
+ ret += kapi_json_str(buf + ret, size - ret, spec->name);
+ ret += scnprintf(buf + ret, size - ret, ",\n \"version\": %u,\n \"description\": ", spec->version);
+ ret += kapi_json_str(buf + ret, size - ret, spec->description);
+ ret += scnprintf(buf + ret, size - ret, ",\n \"long_description\": ");
+ ret += kapi_json_str(buf + ret, size - ret, spec->long_description);
+ ret += scnprintf(buf + ret, size - ret, ",\n \"context_flags\": \"0x%x\",\n", spec->context_flags);
+
+ /* Parameters */
+ ret += scnprintf(buf + ret, size - ret, " \"parameters\": [\n");
+
+ for (i = 0; i < spec->param_count && i < KAPI_MAX_PARAMS; i++) {
+ const struct kapi_param_spec *param = &spec->params[i];
+
+ ret += scnprintf(buf + ret, size - ret, " {\n \"name\": ");
+ ret += kapi_json_str(buf + ret, size - ret, param->name);
+ ret += scnprintf(buf + ret, size - ret, ",\n \"type\": ");
+ ret += kapi_json_str(buf + ret, size - ret, param->type_name);
+ ret += scnprintf(buf + ret, size - ret,
+ ",\n \"type_class\": \"%s\","
+ "\n \"flags\": \"0x%x\","
+ "\n \"description\": ",
+ param_type_to_string(param->type),
+ param->flags);
+ ret += kapi_json_str(buf + ret, size - ret, param->description);
+ ret += scnprintf(buf + ret, size - ret,
+ "\n }%s\n",
+ (i < spec->param_count - 1) ? "," : "");
+ }
+
+ ret += scnprintf(buf + ret, size - ret, " ],\n");
+
+ /* Return value */
+ ret += scnprintf(buf + ret, size - ret, " \"return\": {\n \"type\": ");
+ ret += kapi_json_str(buf + ret, size - ret, spec->return_spec.type_name);
+ ret += scnprintf(buf + ret, size - ret,
+ ",\n \"type_class\": \"%s\","
+ "\n \"check_type\": \"%s\",\n",
+ param_type_to_string(spec->return_spec.type),
+ return_check_type_to_string(spec->return_spec.check_type));
+
+ switch (spec->return_spec.check_type) {
+ case KAPI_RETURN_EXACT:
+ ret += scnprintf(buf + ret, size - ret,
+ " \"success_value\": %lld,\n",
+ spec->return_spec.success_value);
+ break;
+ case KAPI_RETURN_RANGE:
+ ret += scnprintf(buf + ret, size - ret,
+ " \"success_min\": %lld,\n"
+ " \"success_max\": %lld,\n",
+ spec->return_spec.success_min,
+ spec->return_spec.success_max);
+ break;
+ case KAPI_RETURN_ERROR_CHECK:
+ ret += scnprintf(buf + ret, size - ret,
+ " \"error_count\": %u,\n",
+ spec->return_spec.error_count);
+ break;
+ default:
+ break;
+ }
+
+ ret += scnprintf(buf + ret, size - ret, " \"description\": ");
+ ret += kapi_json_str(buf + ret, size - ret, spec->return_spec.description);
+ ret += scnprintf(buf + ret, size - ret, "\n },\n");
+
+ /* Errors */
+ ret += scnprintf(buf + ret, size - ret, " \"errors\": [\n");
+
+ for (i = 0; i < spec->error_count && i < KAPI_MAX_ERRORS; i++) {
+ const struct kapi_error_spec *error = &spec->errors[i];
+
+ ret += scnprintf(buf + ret, size - ret,
+ " {\n \"code\": %d,\n \"name\": ",
+ error->error_code);
+ ret += kapi_json_str(buf + ret, size - ret, error->name);
+ ret += scnprintf(buf + ret, size - ret, ",\n \"condition\": ");
+ ret += kapi_json_str(buf + ret, size - ret, error->condition);
+ ret += scnprintf(buf + ret, size - ret, ",\n \"description\": ");
+ ret += kapi_json_str(buf + ret, size - ret, error->description);
+ ret += scnprintf(buf + ret, size - ret,
+ "\n }%s\n",
+ (i < spec->error_count - 1) ? "," : "");
+ }
+
+ ret += scnprintf(buf + ret, size - ret, " ],\n");
+
+ /* Locks */
+ ret += scnprintf(buf + ret, size - ret, " \"locks\": [\n");
+
+ for (i = 0; i < spec->lock_count && i < KAPI_MAX_CONSTRAINTS; i++) {
+ const struct kapi_lock_spec *lock = &spec->locks[i];
+
+ ret += scnprintf(buf + ret, size - ret, " {\n \"name\": ");
+ ret += kapi_json_str(buf + ret, size - ret, lock->lock_name);
+ ret += scnprintf(buf + ret, size - ret,
+ ",\n \"type\": \"%s\","
+ "\n \"scope\": \"%s\","
+ "\n \"description\": ",
+ lock_type_to_string(lock->lock_type),
+ lock_scope_to_string(lock->scope));
+ ret += kapi_json_str(buf + ret, size - ret, lock->description);
+ ret += scnprintf(buf + ret, size - ret,
+ "\n }%s\n",
+ (i < spec->lock_count - 1) ? "," : "");
+ }
+
+ ret += scnprintf(buf + ret, size - ret, " ],\n");
+
+ /* Capabilities */
+ ret += scnprintf(buf + ret, size - ret, " \"capabilities\": [\n");
+
+ for (i = 0; i < spec->capability_count && i < KAPI_MAX_CAPABILITIES; i++) {
+ const struct kapi_capability_spec *cap = &spec->capabilities[i];
+
+ ret += scnprintf(buf + ret, size - ret,
+ " {\n \"capability\": %d,\n \"name\": ",
+ cap->capability);
+ ret += kapi_json_str(buf + ret, size - ret, cap->cap_name);
+ ret += scnprintf(buf + ret, size - ret,
+ ",\n \"action\": \"%s\",\n \"allows\": ",
+ capability_action_to_string(cap->action));
+ ret += kapi_json_str(buf + ret, size - ret, cap->allows);
+ ret += scnprintf(buf + ret, size - ret, ",\n \"without_cap\": ");
+ ret += kapi_json_str(buf + ret, size - ret, cap->without_cap);
+ ret += scnprintf(buf + ret, size - ret, ",\n \"check_condition\": ");
+ ret += kapi_json_str(buf + ret, size - ret, cap->check_condition);
+ ret += scnprintf(buf + ret, size - ret, ",\n \"priority\": %u", cap->priority);
+
+ if (cap->alternative_count > 0) {
+ int j;
+
+ ret += scnprintf(buf + ret, size - ret,
+ ",\n \"alternatives\": [");
+ for (j = 0; j < cap->alternative_count; j++) {
+ ret += scnprintf(buf + ret, size - ret,
+ "%d%s", cap->alternative[j],
+ (j < cap->alternative_count - 1) ? ", " : "");
+ }
+ ret += scnprintf(buf + ret, size - ret, "]");
+ }
+
+ ret += scnprintf(buf + ret, size - ret,
+ "\n }%s\n",
+ (i < spec->capability_count - 1) ? "," : "");
+ }
+
+ ret += scnprintf(buf + ret, size - ret, " ],\n");
+
+ /* Additional info */
+ ret += scnprintf(buf + ret, size - ret, " \"since_version\": ");
+ ret += kapi_json_str(buf + ret, size - ret, spec->since_version);
+ ret += scnprintf(buf + ret, size - ret, ",\n \"examples\": ");
+ ret += kapi_json_str(buf + ret, size - ret, spec->examples);
+ ret += scnprintf(buf + ret, size - ret, ",\n \"notes\": ");
+ ret += kapi_json_str(buf + ret, size - ret, spec->notes);
+ ret += scnprintf(buf + ret, size - ret, "\n}\n");
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(kapi_export_json);
+
+
+/**
+ * kapi_print_spec - Print API specification to kernel log
+ * @spec: API specification to print
+ */
+void kapi_print_spec(const struct kernel_api_spec *spec)
+{
+ int i;
+
+ if (!spec)
+ return;
+
+ pr_info("=== Kernel API Specification ===\n");
+ pr_info("Name: %s\n", spec->name);
+ pr_info("Version: %u\n", spec->version);
+ pr_info("Description: %s\n", spec->description);
+
+ if (spec->long_description[0])
+ pr_info("Long Description: %s\n", spec->long_description);
+
+ pr_info("Context Flags: 0x%x\n", spec->context_flags);
+
+ /* Parameters */
+ if (spec->param_count > 0) {
+ pr_info("Parameters:\n");
+ for (i = 0; i < spec->param_count && i < KAPI_MAX_PARAMS; i++) {
+ const struct kapi_param_spec *param = &spec->params[i];
+ pr_info(" [%d] %s: %s (flags: 0x%x)\n",
+ i, param->name, param->type_name, param->flags);
+ if (param->description[0])
+ pr_info(" Description: %s\n", param->description);
+ }
+ }
+
+ /* Return value */
+ pr_info("Return: %s\n", spec->return_spec.type_name);
+ if (spec->return_spec.description[0])
+ pr_info(" Description: %s\n", spec->return_spec.description);
+
+ /* Errors */
+ if (spec->error_count > 0) {
+ pr_info("Possible Errors:\n");
+ for (i = 0; i < spec->error_count && i < KAPI_MAX_ERRORS; i++) {
+ const struct kapi_error_spec *error = &spec->errors[i];
+ pr_info(" %s (%d): %s\n",
+ error->name, error->error_code, error->condition);
+ }
+ }
+
+ /* Capabilities */
+ if (spec->capability_count > 0) {
+ pr_info("Capabilities:\n");
+ for (i = 0; i < spec->capability_count && i < KAPI_MAX_CAPABILITIES; i++) {
+ const struct kapi_capability_spec *cap = &spec->capabilities[i];
+ pr_info(" %s (%d):\n", cap->cap_name, cap->capability);
+ pr_info(" Action: %s\n", capability_action_to_string(cap->action));
+ pr_info(" Allows: %s\n", cap->allows);
+ pr_info(" Without: %s\n", cap->without_cap);
+ if (cap->check_condition[0])
+ pr_info(" Condition: %s\n", cap->check_condition);
+ }
+ }
+
+ pr_info("================================\n");
+}
+EXPORT_SYMBOL_GPL(kapi_print_spec);
+
+#ifdef CONFIG_KAPI_RUNTIME_CHECKS
+
+/**
+ * kapi_validate_fd - Validate that a file descriptor value is in valid range
+ * @fd: File descriptor to validate
+ *
+ * Only checks the numeric range, not whether the fd is currently open.
+ * Checking openness would be TOCTOU (the fd can be closed between check
+ * and use) and incorrect for close() (which should accept any fd, returning
+ * EBADF for invalid ones). The kernel's own syscall handling validates
+ * actual fd state.
+ *
+ * Return: true if fd is in valid range, false otherwise
+ */
+static bool kapi_validate_fd(int fd)
+{
+ /* Special case: AT_FDCWD is always valid */
+ if (fd == AT_FDCWD)
+ return true;
+
+ /* Check basic range - negative fds are invalid */
+ if (fd < 0)
+ return false;
+
+ return true;
+}
+
+/**
+ * kapi_validate_user_ptr - Validate that a user pointer is accessible
+ * @ptr: User pointer to validate
+ * @size: Size in bytes to validate
+ *
+ * Return: true if user memory is accessible, false otherwise
+ */
+static bool kapi_validate_user_ptr(const void __user *ptr, size_t size)
+{
+ /* NULL pointers are not valid; caller handles optional case */
+ if (!ptr)
+ return false;
+
+ return access_ok(ptr, size);
+}
+
+/**
+ * kapi_validate_user_ptr_with_params - Validate user pointer with dynamic size
+ * @param_spec: Parameter specification
+ * @ptr: User pointer to validate
+ * @all_params: Array of all parameter values
+ * @param_count: Number of parameters
+ *
+ * Return: true if user memory is accessible, false otherwise
+ */
+static bool kapi_validate_user_ptr_with_params(const struct kapi_param_spec *param_spec,
+ const void __user *ptr,
+ const s64 *all_params,
+ int param_count)
+{
+ size_t actual_size;
+
+ /* NULL is allowed for optional parameters */
+ if (!ptr && (param_spec->flags & KAPI_PARAM_OPTIONAL))
+ return true;
+
+ /* Calculate actual size based on related parameter */
+ if (param_spec->size_param_idx >= 0 &&
+ param_spec->size_param_idx < param_count) {
+ s64 count = all_params[param_spec->size_param_idx];
+
+ /* Validate count is non-negative */
+ if (count < 0) {
+ pr_warn_ratelimited("Parameter %s: size determinant is negative (%lld)\n",
+ param_spec->name, count);
+ return false;
+ }
+
+ /* Check for multiplication overflow */
+ if (param_spec->size_multiplier > 0 &&
+ count > SIZE_MAX / param_spec->size_multiplier) {
+ pr_warn_ratelimited("Parameter %s: size calculation overflow\n",
+ param_spec->name);
+ return false;
+ }
+
+ actual_size = (size_t)count * param_spec->size_multiplier;
+ } else {
+ /* Use fixed size */
+ actual_size = param_spec->size;
+ }
+
+ return kapi_validate_user_ptr(ptr, actual_size);
+}
+
+/**
+ * kapi_validate_path - Validate that a pathname is accessible and within limits
+ * @path: User pointer to pathname
+ * @param_spec: Parameter specification
+ *
+ * Return: true if path is valid, false otherwise
+ */
+static bool kapi_validate_path(const char __user *path,
+ const struct kapi_param_spec *param_spec)
+{
+ size_t len;
+
+ /* NULL is allowed for optional parameters */
+ if (!path && (param_spec->flags & KAPI_PARAM_OPTIONAL))
+ return true;
+
+ if (!path) {
+ pr_warn_ratelimited("Parameter %s: NULL path not allowed\n", param_spec->name);
+ return false;
+ }
+
+ /* Check if the path is accessible */
+ if (!access_ok(path, 1)) {
+ pr_warn_ratelimited("Parameter %s: path pointer %p not accessible\n",
+ param_spec->name, path);
+ return false;
+ }
+
+ /*
+ * Use strnlen_user to check the path length and accessibility.
+ * Note: strnlen_user() is subject to TOCTOU -- the measured length
+ * may change if another thread modifies the user memory. This is
+ * acceptable since the kernel re-copies and re-validates the path
+ * later in the syscall path. This check is best-effort.
+ */
+ len = strnlen_user(path, PATH_MAX + 1);
+ if (len == 0) {
+ pr_warn_ratelimited("Parameter %s: invalid path pointer %p\n",
+ param_spec->name, path);
+ return false;
+ }
+
+ /* Check path length limit */
+ if (len > PATH_MAX) {
+ pr_warn_ratelimited("Parameter %s: path too long (exceeds PATH_MAX)\n",
+ param_spec->name);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * kapi_validate_user_string - Validate a userspace null-terminated string
+ * @str: User pointer to string
+ * @param_spec: Parameter specification containing length constraints
+ *
+ * Validates that the userspace string pointer is accessible and that the
+ * string length (excluding null terminator) is within the range specified
+ * by min_value and max_value in the parameter specification.
+ *
+ * Return: true if string is valid, false otherwise
+ */
+static bool kapi_validate_user_string(const char __user *str,
+ const struct kapi_param_spec *param_spec)
+{
+ size_t len;
+ size_t max_check_len;
+
+ /* NULL is allowed for optional parameters */
+ if (!str && (param_spec->flags & KAPI_PARAM_OPTIONAL))
+ return true;
+
+ if (!str) {
+ pr_warn_ratelimited("Parameter %s: NULL string not allowed\n", param_spec->name);
+ return false;
+ }
+
+ /* Check if the string pointer is accessible */
+ if (!access_ok(str, 1)) {
+ pr_warn_ratelimited("Parameter %s: string pointer %p not accessible\n",
+ param_spec->name, str);
+ return false;
+ }
+
+ /*
+ * Use strnlen_user to check the string length and validate accessibility.
+ * Check up to max_value + 1 to detect strings that are too long.
+ * If max_value is 0 or unset, use PATH_MAX as a reasonable default.
+ *
+ * Note: strnlen_user() is subject to TOCTOU -- see comment in
+ * kapi_validate_path() above. This check is best-effort.
+ */
+ max_check_len = param_spec->max_value > 0 ?
+ (size_t)param_spec->max_value + 1 : PATH_MAX + 1;
+ len = strnlen_user(str, max_check_len);
+
+ if (len == 0) {
+ pr_warn_ratelimited("Parameter %s: invalid string pointer %p\n",
+ param_spec->name, str);
+ return false;
+ }
+
+ /*
+ * strnlen_user returns the length including the null terminator.
+ * Convert to string length (excluding terminator) for range check.
+ */
+ len--;
+
+ /* Check minimum length constraint */
+ if (param_spec->min_value > 0 && len < (size_t)param_spec->min_value) {
+ pr_warn_ratelimited("Parameter %s: string too short (%zu < %lld)\n",
+ param_spec->name, len, param_spec->min_value);
+ return false;
+ }
+
+ /* Check maximum length constraint */
+ if (param_spec->max_value > 0 && len > (size_t)param_spec->max_value) {
+ pr_warn_ratelimited("Parameter %s: string too long (%zu > %lld)\n",
+ param_spec->name, len, param_spec->max_value);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * kapi_validate_user_ptr_constraint - Validate a userspace pointer with size
+ * @ptr: User pointer to validate
+ * @param_spec: Parameter specification containing size
+ *
+ * Validates that the userspace pointer is accessible and that the memory
+ * region of the specified size can be accessed. The size is taken from
+ * the param_spec->size field.
+ *
+ * Return: true if pointer is valid, false otherwise
+ */
+static bool kapi_validate_user_ptr_constraint(const void __user *ptr,
+ const struct kapi_param_spec *param_spec)
+{
+ /* NULL is allowed for optional parameters */
+ if (!ptr && (param_spec->flags & KAPI_PARAM_OPTIONAL))
+ return true;
+
+ if (!ptr) {
+ pr_warn_ratelimited("Parameter %s: NULL pointer not allowed\n", param_spec->name);
+ return false;
+ }
+
+ /* Validate size is specified */
+ if (param_spec->size == 0) {
+ pr_warn_ratelimited("Parameter %s: size not specified for user pointer validation\n",
+ param_spec->name);
+ return false;
+ }
+
+ /* Check if the memory region is accessible */
+ if (!access_ok(ptr, param_spec->size)) {
+ pr_warn_ratelimited("Parameter %s: user pointer %p not accessible for %zu bytes\n",
+ param_spec->name, ptr, param_spec->size);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * kapi_validate_param - Validate a parameter against its specification
+ * @param_spec: Parameter specification
+ * @value: Parameter value to validate
+ *
+ * Return: true if valid, false otherwise
+ */
+bool kapi_validate_param(const struct kapi_param_spec *param_spec, s64 value)
+{
+ int i;
+
+ /* Special handling for file descriptor type */
+ if (param_spec->type == KAPI_TYPE_FD) {
+ if (value < INT_MIN || value > INT_MAX) {
+ pr_warn_ratelimited("Parameter %s: file descriptor %lld out of int range\n",
+ param_spec->name, value);
+ return false;
+ }
+ if (!kapi_validate_fd((int)value)) {
+ pr_warn_ratelimited("Parameter %s: invalid file descriptor %lld\n",
+ param_spec->name, value);
+ return false;
+ }
+ /* Continue with additional constraint checks if needed */
+ }
+
+ /* Special handling for user pointer type */
+ if (param_spec->type == KAPI_TYPE_USER_PTR) {
+ const void __user *ptr = (const void __user *)value;
+
+ /* NULL is allowed for optional parameters */
+ if (!ptr && (param_spec->flags & KAPI_PARAM_OPTIONAL))
+ return true;
+
+ if (!kapi_validate_user_ptr(ptr, param_spec->size)) {
+ pr_warn_ratelimited("Parameter %s: invalid user pointer %p (size: %zu)\n",
+ param_spec->name, ptr, param_spec->size);
+ return false;
+ }
+ /* Continue with additional constraint checks if needed */
+ }
+
+ /* Special handling for path type */
+ if (param_spec->type == KAPI_TYPE_PATH) {
+ const char __user *path = (const char __user *)value;
+
+ if (!kapi_validate_path(path, param_spec)) {
+ return false;
+ }
+ /* Continue with additional constraint checks if needed */
+ }
+
+ switch (param_spec->constraint_type) {
+ case KAPI_CONSTRAINT_NONE:
+ case KAPI_CONSTRAINT_BUFFER:
+ return true;
+
+ case KAPI_CONSTRAINT_RANGE:
+ /*
+ * If max_value is negative, it was likely set from an unsigned
+ * constant (e.g. SIZE_MAX) that overflowed s64. Treat as no
+ * upper bound — only check the minimum.
+ */
+ if (param_spec->max_value >= 0) {
+ if (value < param_spec->min_value ||
+ value > param_spec->max_value) {
+ pr_warn_ratelimited("Parameter %s value %lld out of range [%lld, %lld]\n",
+ param_spec->name, value,
+ param_spec->min_value,
+ param_spec->max_value);
+ return false;
+ }
+ } else {
+ if (value < param_spec->min_value) {
+ pr_warn_ratelimited("Parameter %s value %lld below minimum %lld\n",
+ param_spec->name, value,
+ param_spec->min_value);
+ return false;
+ }
+ }
+ return true;
+
+ case KAPI_CONSTRAINT_MASK:
+ if (value & ~param_spec->valid_mask) {
+ pr_warn_ratelimited("Parameter %s value 0x%llx contains invalid bits (valid mask: 0x%llx)\n",
+ param_spec->name, value, param_spec->valid_mask);
+ return false;
+ }
+ return true;
+
+ case KAPI_CONSTRAINT_ENUM:
+ if (!param_spec->enum_values || param_spec->enum_count == 0)
+ return true;
+
+ for (i = 0; i < param_spec->enum_count; i++) {
+ if (value == param_spec->enum_values[i])
+ return true;
+ }
+ pr_warn_ratelimited("Parameter %s value %lld not in valid enumeration\n",
+ param_spec->name, value);
+ return false;
+
+ case KAPI_CONSTRAINT_ALIGNMENT:
+ if (param_spec->alignment == 0) {
+ pr_warn_ratelimited("Parameter %s: alignment constraint specified but alignment is 0\n",
+ param_spec->name);
+ return false;
+ }
+ if (param_spec->alignment & (param_spec->alignment - 1)) {
+ pr_warn_ratelimited("Parameter %s: alignment %zu is not a power of two\n",
+ param_spec->name, param_spec->alignment);
+ return false;
+ }
+ if (value & (param_spec->alignment - 1)) {
+ pr_warn_ratelimited("Parameter %s value 0x%llx not aligned to %zu boundary\n",
+ param_spec->name, value, param_spec->alignment);
+ return false;
+ }
+ return true;
+
+ case KAPI_CONSTRAINT_POWER_OF_TWO:
+ if (value == 0 || (value & (value - 1))) {
+ pr_warn_ratelimited("Parameter %s value %lld is not a power of two\n",
+ param_spec->name, value);
+ return false;
+ }
+ return true;
+
+ case KAPI_CONSTRAINT_PAGE_ALIGNED:
+ if (value & (PAGE_SIZE - 1)) {
+ pr_warn_ratelimited("Parameter %s value 0x%llx not page-aligned (PAGE_SIZE=%ld)\n",
+ param_spec->name, value, PAGE_SIZE);
+ return false;
+ }
+ return true;
+
+ case KAPI_CONSTRAINT_NONZERO:
+ if (value == 0) {
+ pr_warn_ratelimited("Parameter %s must be non-zero\n", param_spec->name);
+ return false;
+ }
+ return true;
+
+ case KAPI_CONSTRAINT_USER_STRING:
+ return kapi_validate_user_string((const char __user *)value, param_spec);
+
+ case KAPI_CONSTRAINT_USER_PATH:
+ return kapi_validate_path((const char __user *)value, param_spec);
+
+ case KAPI_CONSTRAINT_USER_PTR:
+ return kapi_validate_user_ptr_constraint((const void __user *)value, param_spec);
+
+ case KAPI_CONSTRAINT_CUSTOM:
+ if (param_spec->validate)
+ return param_spec->validate(value);
+ return true;
+
+ default:
+ return true;
+ }
+}
+EXPORT_SYMBOL_GPL(kapi_validate_param);
+
+/**
+ * kapi_validate_param_with_context - Validate parameter with access to all params
+ * @param_spec: Parameter specification
+ * @value: Parameter value to validate
+ * @all_params: Array of all parameter values
+ * @param_count: Number of parameters
+ *
+ * Return: true if valid, false otherwise
+ */
+bool kapi_validate_param_with_context(const struct kapi_param_spec *param_spec,
+ s64 value, const s64 *all_params, int param_count)
+{
+ /* Special handling for user pointer type with dynamic sizing */
+ if (param_spec->type == KAPI_TYPE_USER_PTR) {
+ const void __user *ptr = (const void __user *)value;
+
+ /* NULL is allowed for optional parameters */
+ if (!ptr && (param_spec->flags & KAPI_PARAM_OPTIONAL))
+ return true;
+
+ if (!kapi_validate_user_ptr_with_params(param_spec, ptr, all_params, param_count)) {
+ pr_warn_ratelimited("Parameter %s: invalid user pointer %p\n",
+ param_spec->name, ptr);
+ return false;
+ }
+ /* Continue with additional constraint checks if needed */
+ }
+
+ /* For other types, fall back to regular validation */
+ return kapi_validate_param(param_spec, value);
+}
+EXPORT_SYMBOL_GPL(kapi_validate_param_with_context);
+
+/**
+ * kapi_validate_syscall_param - Validate syscall parameter with enforcement
+ * @spec: API specification
+ * @param_idx: Parameter index
+ * @value: Parameter value
+ *
+ * Return: -EINVAL if invalid, 0 if valid
+ */
+int kapi_validate_syscall_param(const struct kernel_api_spec *spec,
+ int param_idx, s64 value)
+{
+ const struct kapi_param_spec *param_spec;
+
+ if (!spec || param_idx < 0 || param_idx >= spec->param_count)
+ return 0;
+
+ param_spec = &spec->params[param_idx];
+
+ if (!kapi_validate_param(param_spec, value)) {
+ if (strncmp(spec->name, "sys_", 4) == 0) {
+ /* For syscalls, we can return EINVAL to userspace */
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(kapi_validate_syscall_param);
+
+/**
+ * kapi_validate_syscall_params - Validate all syscall parameters together
+ * @spec: API specification
+ * @params: Array of parameter values
+ * @param_count: Number of parameters
+ *
+ * Return: -EINVAL if any parameter is invalid, 0 if all valid
+ */
+int kapi_validate_syscall_params(const struct kernel_api_spec *spec,
+ const s64 *params, int param_count)
+{
+ int i;
+
+ if (!spec || !params)
+ return 0;
+
+ /* Validate that we have the expected number of parameters */
+ if (param_count != spec->param_count) {
+ pr_warn_ratelimited("API %s: parameter count mismatch (expected %u, got %d)\n",
+ spec->name, spec->param_count, param_count);
+ return -EINVAL;
+ }
+
+ /* Validate each parameter with context */
+ for (i = 0; i < spec->param_count && i < KAPI_MAX_PARAMS; i++) {
+ const struct kapi_param_spec *param_spec = &spec->params[i];
+
+ if (!kapi_validate_param_with_context(param_spec, params[i], params, param_count)) {
+ if (strncmp(spec->name, "sys_", 4) == 0) {
+ /* For syscalls, we can return EINVAL to userspace */
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(kapi_validate_syscall_params);
+
+/**
+ * kapi_check_return_success - Check if return value indicates success
+ * @return_spec: Return specification
+ * @retval: Return value to check
+ *
+ * Returns true if the return value indicates success according to the spec.
+ */
+bool kapi_check_return_success(const struct kapi_return_spec *return_spec, s64 retval)
+{
+ u32 i;
+
+ if (!return_spec)
+ return true; /* No spec means we can't validate */
+
+ switch (return_spec->check_type) {
+ case KAPI_RETURN_EXACT:
+ return retval == return_spec->success_value;
+
+ case KAPI_RETURN_RANGE:
+ return retval >= return_spec->success_min &&
+ retval <= return_spec->success_max;
+
+ case KAPI_RETURN_ERROR_CHECK:
+ /* Success if NOT in error list */
+ if (return_spec->error_values) {
+ for (i = 0; i < return_spec->error_count; i++) {
+ if (retval == return_spec->error_values[i])
+ return false; /* Found in error list */
+ }
+ }
+ return true; /* Not in error list = success */
+
+ case KAPI_RETURN_FD:
+ /* File descriptors: >= 0 is success, < 0 is error */
+ return retval >= 0;
+
+ case KAPI_RETURN_CUSTOM:
+ if (return_spec->is_success)
+ return return_spec->is_success(retval);
+ fallthrough;
+
+ default:
+ return true; /* Unknown check type, assume success */
+ }
+}
+EXPORT_SYMBOL_GPL(kapi_check_return_success);
+
+/**
+ * kapi_validate_return_value - Validate that return value matches spec
+ * @spec: API specification
+ * @retval: Return value to validate
+ *
+ * Return: true if return value is valid according to spec, false otherwise.
+ *
+ * This function checks:
+ * 1. If the value indicates success, it must match the success criteria
+ * 2. If the value indicates error, it must be one of the specified error codes
+ */
+bool kapi_validate_return_value(const struct kernel_api_spec *spec, s64 retval)
+{
+ int i;
+ bool is_success;
+
+ if (!spec)
+ return true; /* No spec means we can't validate */
+
+ /* First check if this is a success return */
+ is_success = kapi_check_return_success(&spec->return_spec, retval);
+
+ if (is_success) {
+ /* Special validation for file descriptor returns */
+ if (spec->return_spec.check_type == KAPI_RETURN_FD) {
+ /* For successful FD returns, validate it's a valid FD */
+ if (retval > INT_MAX || !kapi_validate_fd((int)retval)) {
+ pr_warn_ratelimited("API %s returned invalid file descriptor %lld\n",
+ spec->name, retval);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /* Error case - check if it's one of the specified errors */
+ if (spec->error_count == 0) {
+ /* No errors specified, so any error is potentially valid */
+ pr_debug("API %s returned unspecified error %lld\n",
+ spec->name, retval);
+ return true;
+ }
+
+ /* Check if the error is in our list of specified errors */
+ for (i = 0; i < spec->error_count && i < KAPI_MAX_ERRORS; i++) {
+ if (retval == spec->errors[i].error_code)
+ return true;
+ }
+
+ /*
+ * Error not in spec - log at debug level since filesystem-specific and
+ * device-specific error codes may not be exhaustively listed.
+ */
+ pr_debug("API %s returned error code %lld not listed in spec\n",
+ spec->name, retval);
+
+ return true;
+}
+EXPORT_SYMBOL_GPL(kapi_validate_return_value);
+
+/**
+ * kapi_validate_syscall_return - Validate syscall return value with enforcement
+ * @spec: API specification
+ * @retval: Return value
+ *
+ * Return: 0 if valid, -EINVAL if the return value doesn't match spec
+ *
+ * For syscalls, this can help detect kernel bugs where unspecified error
+ * codes are returned to userspace.
+ */
+int kapi_validate_syscall_return(const struct kernel_api_spec *spec, s64 retval)
+{
+ if (!spec)
+ return 0;
+
+ /* Skip return validation if return spec was not defined */
+ if (spec->return_magic != KAPI_MAGIC_RETURN)
+ return 0;
+
+ if (!kapi_validate_return_value(spec, retval)) {
+ /* Log the violation but don't change the return value */
+ pr_warn_ratelimited("KAPI: Syscall %s returned unspecified value %lld\n",
+ spec->name, retval);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(kapi_validate_syscall_return);
+
+/**
+ * kapi_check_context - Check if current context matches API requirements
+ * @spec: API specification to check against
+ */
+void kapi_check_context(const struct kernel_api_spec *spec)
+{
+ bool valid = false;
+ u32 ctx;
+
+ if (!spec)
+ return;
+
+ ctx = spec->context_flags;
+
+ if (!ctx)
+ return;
+
+ /* Check if we're in an allowed context */
+ if ((ctx & KAPI_CTX_PROCESS) && !in_interrupt())
+ valid = true;
+
+ if ((ctx & KAPI_CTX_SOFTIRQ) && in_softirq())
+ valid = true;
+
+ if ((ctx & KAPI_CTX_HARDIRQ) && in_hardirq())
+ valid = true;
+
+ if ((ctx & KAPI_CTX_NMI) && in_nmi())
+ valid = true;
+
+ if (!valid) {
+ WARN_ONCE(1, "API %s called from invalid context\n", spec->name);
+ }
+
+ /* Check specific requirements */
+ if ((ctx & KAPI_CTX_ATOMIC) && preemptible()) {
+ WARN_ONCE(1, "API %s requires atomic context\n", spec->name);
+ }
+
+ if ((ctx & KAPI_CTX_SLEEPABLE) && !preemptible()) {
+ WARN_ONCE(1, "API %s requires sleepable context\n", spec->name);
+ }
+}
+EXPORT_SYMBOL_GPL(kapi_check_context);
+
+#endif /* CONFIG_KAPI_RUNTIME_CHECKS */
diff --git a/scripts/generate_api_specs.sh b/scripts/generate_api_specs.sh
new file mode 100755
index 0000000000000..2c3078a508fef
--- /dev/null
+++ b/scripts/generate_api_specs.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Stub script for generating API specifications collector
+# This is a placeholder until the full implementation is available
+#
+
+cat << 'EOF'
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Auto-generated API specifications collector (stub)
+ * Generated by scripts/generate_api_specs.sh
+ */
+
+#include <linux/kernel_api_spec.h>
+
+/* No API specifications collected yet */
+EOF
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH 2/9] kernel/api: enable kerneldoc-based API specifications
2026-03-13 15:09 [PATCH 0/9] Kernel API Specification Framework Sasha Levin
2026-03-13 15:09 ` [PATCH 1/9] kernel/api: introduce kernel API specification framework Sasha Levin
@ 2026-03-13 15:09 ` Sasha Levin
2026-03-13 15:09 ` [PATCH 3/9] kernel/api: add debugfs interface for kernel " Sasha Levin
` (7 subsequent siblings)
9 siblings, 0 replies; 37+ messages in thread
From: Sasha Levin @ 2026-03-13 15:09 UTC (permalink / raw)
To: linux-api, linux-kernel
Cc: linux-doc, linux-fsdevel, linux-kbuild, linux-kselftest,
workflows, tools, x86, Thomas Gleixner, Paul E. McKenney,
Greg Kroah-Hartman, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann, Sasha Levin
This patch adds support for extracting API specifications from
kernel-doc comments and generating C macro invocations for the
kernel API specification framework.
Changes include:
- New kdoc_apispec.py module for generating API spec macros
- Updates to kernel-doc.py to support -apispec output format
- Build system integration in Makefile.build
- Generator script for collecting all API specifications
- Support for API-specific sections in kernel-doc comments
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
scripts/Makefile.build | 31 +
scripts/Makefile.clean | 3 +
scripts/generate_api_specs.sh | 18 -
tools/docs/kernel-doc | 5 +
tools/lib/python/kdoc/kdoc_apispec.py | 887 ++++++++++++++++++++++++++
tools/lib/python/kdoc/kdoc_output.py | 9 +-
tools/lib/python/kdoc/kdoc_parser.py | 86 ++-
7 files changed, 1016 insertions(+), 23 deletions(-)
delete mode 100755 scripts/generate_api_specs.sh
create mode 100644 tools/lib/python/kdoc/kdoc_apispec.py
diff --git a/scripts/Makefile.build b/scripts/Makefile.build
index 32e209bc7985c..7081884e3c528 100644
--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -174,6 +174,37 @@ ifneq ($(KBUILD_EXTRA_WARN),)
endif
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*context-flags:' $(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*\(long-desc\|context-flags\|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) $< > $@ || rm -f $@
+
+$(obj)/%.apispec.h: $(src)/%.c $(KERNELDOC) FORCE
+ $(call if_changed,apispec)
+
+# Source files that include their own apispec.h need to depend on it
+$(foreach f,$(apispec-files),$(eval $(obj)/$(f:.c=.o): $(obj)/$(f:.c=.apispec.h)))
+endif
+
# Compile C sources (.c)
# ---------------------------------------------------------------------------
diff --git a/scripts/Makefile.clean b/scripts/Makefile.clean
index 6ead00ec7313b..f78dbbe637f27 100644
--- a/scripts/Makefile.clean
+++ b/scripts/Makefile.clean
@@ -35,6 +35,9 @@ __clean-files := $(filter-out $(no-clean-files), $(__clean-files))
__clean-files := $(wildcard $(addprefix $(obj)/, $(__clean-files)))
+# Also clean generated apispec headers (computed dynamically in Makefile.build)
+__clean-files += $(wildcard $(obj)/*.apispec.h)
+
# ==========================================================================
# To make this rule robust against "Argument list too long" error,
diff --git a/scripts/generate_api_specs.sh b/scripts/generate_api_specs.sh
deleted file mode 100755
index 2c3078a508fef..0000000000000
--- a/scripts/generate_api_specs.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-# SPDX-License-Identifier: GPL-2.0
-#
-# Stub script for generating API specifications collector
-# This is a placeholder until the full implementation is available
-#
-
-cat << 'EOF'
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Auto-generated API specifications collector (stub)
- * Generated by scripts/generate_api_specs.sh
- */
-
-#include <linux/kernel_api_spec.h>
-
-/* No API specifications collected yet */
-EOF
diff --git a/tools/docs/kernel-doc b/tools/docs/kernel-doc
index aed09f9a54dd1..e71e663d9b7c0 100755
--- a/tools/docs/kernel-doc
+++ b/tools/docs/kernel-doc
@@ -253,6 +253,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
@@ -323,11 +325,14 @@ def main():
#
from kdoc.kdoc_files import KernelFiles # pylint: disable=C0415
from kdoc.kdoc_output import RestFormat, ManFormat # pylint: disable=C0415
+ from kdoc.kdoc_apispec import ApiSpecFormat # pylint: disable=C0415
if args.man:
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/tools/lib/python/kdoc/kdoc_apispec.py b/tools/lib/python/kdoc/kdoc_apispec.py
new file mode 100644
index 0000000000000..1d83539548a42
--- /dev/null
+++ b/tools/lib/python/kdoc/kdoc_apispec.py
@@ -0,0 +1,887 @@
+#!/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.kdoc_output import OutputFormat
+import re
+
+
+# Maximum string lengths (from kernel_api_spec.h)
+KAPI_MAX_DESC_LEN = 512
+KAPI_MAX_NAME_LEN = 128
+KAPI_MAX_SIGNAL_NAME_LEN = 32
+KAPI_MAX_LONG_DESC_LEN = KAPI_MAX_DESC_LEN * 4 # 2048 bytes
+KAPI_MAX_NOTES_LEN = KAPI_MAX_DESC_LEN * 2 # 1024 bytes
+KAPI_MAX_EXAMPLES_LEN = KAPI_MAX_DESC_LEN * 2 # 1024 bytes
+
+# Valid KAPI effect types
+VALID_EFFECT_TYPES = {
+ 'KAPI_EFFECT_NONE', 'KAPI_EFFECT_MODIFY_STATE', 'KAPI_EFFECT_PROCESS_STATE',
+ 'KAPI_EFFECT_IRREVERSIBLE', 'KAPI_EFFECT_SCHEDULE', 'KAPI_EFFECT_FILESYSTEM',
+ 'KAPI_EFFECT_HARDWARE', 'KAPI_EFFECT_ALLOC_MEMORY', 'KAPI_EFFECT_FREE_MEMORY',
+ 'KAPI_EFFECT_SIGNAL_SEND', 'KAPI_EFFECT_FILE_POSITION', 'KAPI_EFFECT_LOCK_ACQUIRE',
+ 'KAPI_EFFECT_LOCK_RELEASE', 'KAPI_EFFECT_RESOURCE_CREATE', 'KAPI_EFFECT_RESOURCE_DESTROY',
+ 'KAPI_EFFECT_NETWORK'
+}
+
+
+class ApiSpecFormat(OutputFormat):
+ """Generate C macro invocations for kernel API specifications"""
+
+ def __init__(self):
+ super().__init__()
+ self.header_written = False
+
+ def msg(self, fname, name, args):
+ """Handles a single entry from kernel-doc parser"""
+ if not self.header_written:
+ header = self._generate_header()
+ self.header_written = True
+ else:
+ header = ""
+
+ self.data = ""
+ result = super().msg(fname, name, args)
+ return header + (result if result else self.data)
+
+ def _generate_header(self):
+ """Generate the file header"""
+ return (
+ "/* SPDX-License-Identifier: GPL-2.0 */\n"
+ "/* Auto-generated from kerneldoc annotations - DO NOT EDIT */\n\n"
+ "#include <linux/kernel_api_spec.h>\n"
+ "#include <linux/errno.h>\n\n"
+ )
+
+ def _format_macro_param(self, value, max_len=KAPI_MAX_DESC_LEN):
+ """Format a value for use in C macro parameter, truncating if needed"""
+ if value is None:
+ return '""'
+ value = str(value).replace('\\', '\\\\').replace('"', '\\"')
+ value = value.replace('\n', ' ').replace('\t', ' ').replace('\r', '')
+ value = value.replace('\0', '')
+ # Truncate to fit within max_len, accounting for null terminator
+ if len(value) > max_len - 1:
+ value = value[:max_len - 4] + '...'
+ return f'"{value}"'
+
+ def _get_section(self, sections, key):
+ """Get first line from sections, checking with and without @ prefix and case variants"""
+ for variant in [key, key.capitalize(), key.title()]:
+ for prefix in ['', '@']:
+ full_key = prefix + variant
+ if full_key in sections:
+ content = sections[full_key].strip()
+ # Return only first line to avoid mixing sections
+ return content.split('\n')[0].strip() if content else ''
+ return None
+
+ def _get_raw_section(self, sections, key):
+ """Get full section content, checking with and without @ prefix and case variants"""
+ for variant in [key, key.capitalize(), key.title()]:
+ for prefix in ['', '@']:
+ full_key = prefix + variant
+ if full_key in sections:
+ return sections[full_key]
+ return ''
+
+ def _get_multiline_section(self, sections, key):
+ """Get full multi-line section content, joined into a single string.
+
+ This is used for fields like notes, long-desc, and examples that
+ can span multiple lines in the kerneldoc comment.
+ """
+ content = self._get_raw_section(sections, key)
+ if not content:
+ return None
+
+ # Split into lines, strip each, and join with space
+ lines = content.strip().split('\n')
+ # Join lines, preserving paragraph breaks (double newlines become single space)
+ result = ' '.join(line.strip() for line in lines if line.strip())
+ return result if result else None
+
+ def _parse_indented_items(self, section_content, item_parser):
+ """Generic parser for indented items.
+
+ Args:
+ section_content: Raw section content
+ item_parser: Function that takes (lines, start_index) and returns (item, next_index)
+
+ Returns:
+ List of parsed items
+ """
+ if not section_content:
+ return []
+
+ items = []
+ lines = section_content.strip().split('\n')
+ i = 0
+
+ while i < len(lines):
+ if not lines[i].strip():
+ i += 1
+ continue
+
+ # Check if this is a main item (not indented)
+ if not lines[i].startswith((' ', '\t')):
+ item, i = item_parser(lines, i)
+ if item:
+ items.append(item)
+ else:
+ i += 1
+
+ return items
+
+ def _parse_subfields(self, lines, start_idx):
+ """Parse indented subfields starting from start_idx+1.
+
+ Returns: (dict of subfields, next index)
+ """
+ subfields = {}
+ i = start_idx + 1
+
+ while i < len(lines) and (lines[i].startswith((' ', '\t'))):
+ line = lines[i].strip()
+ if ':' in line:
+ key, value = line.split(':', 1)
+ subfields[key.strip()] = value.strip()
+ i += 1
+
+ return subfields, i
+
+ def _parse_signal_item(self, lines, i):
+ """Parse a single signal specification"""
+ signal = {'name': lines[i].strip()}
+ subfields, next_i = self._parse_subfields(lines, i)
+
+ # Map subfields to signal attributes
+ signal.update({
+ 'direction': subfields.get('direction', 'KAPI_SIGNAL_RECEIVE'),
+ 'action': subfields.get('action', 'KAPI_SIGNAL_ACTION_RETURN'),
+ 'condition': subfields.get('condition'),
+ 'desc': subfields.get('desc'),
+ 'error': subfields.get('error'),
+ 'timing': subfields.get('timing'),
+ 'priority': subfields.get('priority'),
+ 'interruptible': subfields.get('interruptible', '').lower() == 'yes',
+ 'number': subfields.get('number', '0'),
+ })
+
+ return signal, next_i
+
+ def _parse_error_item(self, lines, i):
+ """Parse a single error specification"""
+ line = lines[i].strip()
+
+ # Skip desc: lines
+ if line.startswith('desc:'):
+ return None, i + 1
+
+ # Check for error pattern
+ if not re.match(r'^[A-Z][A-Z0-9_]+,', line):
+ return None, i + 1
+
+ error = {'line': line, 'desc': ''}
+
+ # Look for desc: continuation
+ i += 1
+ desc_lines = []
+ while i < len(lines):
+ next_line = lines[i].strip()
+ if next_line.startswith('desc:'):
+ desc_lines.append(next_line[5:].strip())
+ i += 1
+ elif not next_line:
+ break
+ elif not desc_lines and re.match(r'^[A-Z][A-Z0-9_]+,', next_line):
+ # New error entry, but only if we haven't started a desc block
+ break
+ else:
+ desc_lines.append(next_line)
+ i += 1
+
+ if desc_lines:
+ error['desc'] = ' '.join(desc_lines)
+
+ return error, i
+
+ def _parse_lock_item(self, lines, i):
+ """Parse a single lock specification"""
+ line = lines[i].strip()
+ if ':' not in line:
+ return None, i + 1
+
+ parts = line.split(':', 1)[1].strip().split(',', 1)
+ if len(parts) < 2:
+ return None, i + 1
+
+ lock = {
+ 'name': parts[0].strip(),
+ 'type': parts[1].strip()
+ }
+
+ subfields, next_i = self._parse_subfields(lines, i)
+
+ # Map boolean fields
+ for field in ['acquired', 'released', 'held-on-entry', 'held-on-exit']:
+ if subfields.get(field, '').lower() == 'true':
+ lock[field] = True
+
+ lock['desc'] = subfields.get('desc', '')
+
+ return lock, next_i
+
+ def _parse_constraint_item(self, lines, i):
+ """Parse a single constraint specification"""
+ line = lines[i].strip()
+
+ # Check for old format with comma
+ if ',' in line:
+ parts = line.split(',', 1)
+ constraint = {
+ 'name': parts[0].strip(),
+ 'desc': parts[1].strip() if len(parts) > 1 else '',
+ 'expr': None
+ }
+ else:
+ constraint = {'name': line, 'desc': '', 'expr': None}
+
+ subfields, next_i = self._parse_subfields(lines, i)
+
+ if 'desc' in subfields:
+ constraint['desc'] = (constraint['desc'] + ' ' + subfields['desc']).strip()
+ constraint['expr'] = subfields.get('expr')
+
+ return constraint, next_i
+
+ def _parse_side_effect_item(self, lines, i):
+ """Parse a single side effect specification"""
+ line = lines[i].strip()
+
+ # Default to new format
+ effect = {
+ 'type': line,
+ 'target': '',
+ 'desc': '',
+ 'condition': None,
+ 'reversible': False
+ }
+
+ # Check for old format with commas
+ if ',' in line:
+ # Handle condition and reversible flags
+ cond_match = re.search(r',\s*condition=([^,]+?)(?:\s*,\s*reversible=(yes|no)\s*)?$', line)
+ if cond_match:
+ effect['condition'] = cond_match.group(1).strip()
+ effect['reversible'] = cond_match.group(2) == 'yes'
+ line = line[:cond_match.start()]
+ elif ', reversible=yes' in line:
+ effect['reversible'] = True
+ line = line.replace(', reversible=yes', '')
+ elif ', reversible=no' in line:
+ line = line.replace(', reversible=no', '')
+
+ parts = line.split(',', 2)
+ if len(parts) >= 1:
+ effect['type'] = parts[0].strip()
+ if len(parts) >= 2:
+ effect['target'] = parts[1].strip()
+ if len(parts) >= 3:
+ effect['desc'] = parts[2].strip()
+ else:
+ # Multi-line format with subfields
+ subfields, next_i = self._parse_subfields(lines, i)
+ effect.update({
+ 'target': subfields.get('target', ''),
+ 'desc': subfields.get('desc', ''),
+ 'condition': subfields.get('condition'),
+ 'reversible': subfields.get('reversible', '').lower() == 'yes'
+ })
+ return effect, next_i
+
+ return effect, i + 1
+
+ def _parse_state_trans_item(self, lines, i):
+ """Parse a single state transition specification"""
+ line = lines[i].strip()
+
+ trans = {
+ 'target': line,
+ 'from': '',
+ 'to': '',
+ 'condition': '',
+ 'desc': ''
+ }
+
+ # Check for old format with commas
+ if ',' in line:
+ parts = line.split(',', 3)
+ if len(parts) >= 1:
+ trans['target'] = parts[0].strip()
+ if len(parts) >= 2:
+ trans['from'] = parts[1].strip()
+ if len(parts) >= 3:
+ trans['to'] = parts[2].strip()
+ if len(parts) >= 4:
+ desc_part = parts[3].strip()
+ desc_parts = desc_part.split(',', 1)
+ if len(desc_parts) > 1:
+ trans['condition'] = desc_parts[0].strip()
+ trans['desc'] = desc_parts[1].strip()
+ else:
+ trans['desc'] = desc_part
+ return trans, i + 1
+ else:
+ # Multi-line format with subfields
+ subfields, next_i = self._parse_subfields(lines, i)
+ trans.update({
+ 'from': subfields.get('from', ''),
+ 'to': subfields.get('to', ''),
+ 'condition': subfields.get('condition', ''),
+ 'desc': subfields.get('desc', '')
+ })
+ return trans, next_i
+
+ def _process_parameters(self, sections, parameterlist, parameterdescs, parametertypes):
+ """Process and output parameter specifications"""
+ param_count = len(parameterlist)
+ if param_count > 0:
+ self.data += f"\n\tKAPI_PARAM_COUNT({param_count})\n"
+
+ for param_idx, param in enumerate(parameterlist):
+ param_name = param.strip()
+ param_desc = parameterdescs.get(param_name, '')
+ param_ctype = parametertypes.get(param_name, '')
+
+ # Parse parameter specifications
+ param_section = self._get_raw_section(sections, 'param')
+ param_specs = {}
+ if param_section:
+ param_specs = self._parse_param_spec(param_section, param_name)
+
+ 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 attributes
+ for key, macro in [
+ ('param-type', 'KAPI_PARAM_TYPE'),
+ ('param-flags', 'KAPI_PARAM_FLAGS'),
+ ('param-size', 'KAPI_PARAM_SIZE'),
+ ('param-alignment', 'KAPI_PARAM_ALIGNMENT'),
+ ]:
+ if key in param_specs:
+ self.data += f"\t\t{macro}({param_specs[key]})\n"
+
+ # Handle constraint type
+ if 'param-constraint-type' in param_specs:
+ ctype = param_specs['param-constraint-type']
+ if ctype == 'KAPI_CONSTRAINT_BITMASK':
+ ctype = 'KAPI_CONSTRAINT_MASK'
+ self.data += f"\t\tKAPI_PARAM_CONSTRAINT_TYPE({ctype})\n"
+
+ # Handle range
+ if 'param-range' in param_specs and ',' 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"
+
+ # Handle mask
+ if 'param-mask' in param_specs:
+ self.data += f"\t\tKAPI_PARAM_VALID_MASK({param_specs['param-mask']})\n"
+
+ # Handle enum values
+ if 'param-enum-values' in param_specs:
+ self.data += f"\t\tKAPI_PARAM_ENUM_VALUES({param_specs['param-enum-values']})\n"
+
+ # Handle size parameter index
+ if 'param-size-param' in param_specs:
+ self.data += f"\t\tKAPI_PARAM_SIZE_PARAM({param_specs['param-size-param']})\n"
+
+ # Handle constraint description
+ if 'param-constraint' in param_specs:
+ self.data += f"\t\tKAPI_PARAM_CONSTRAINT({self._format_macro_param(param_specs['param-constraint'])})\n"
+
+ self.data += "\tKAPI_PARAM_END\n"
+
+ def _parse_param_spec(self, section_content, param_name):
+ """Parse parameter specifications from indented format"""
+ specs = {}
+ lines = section_content.strip().split('\n')
+ current_item = None
+
+ # Map to expected keys
+ field_map = {
+ 'type': 'param-type',
+ 'flags': 'param-flags',
+ 'size': 'param-size',
+ 'constraint-type': 'param-constraint-type',
+ 'constraint': 'param-constraint',
+ 'cdesc': 'param-constraint',
+ 'range': 'param-range',
+ 'mask': 'param-mask',
+ 'valid-mask': 'param-mask',
+ 'valid-values': 'param-enum-values',
+ 'alignment': 'param-alignment',
+ 'size-param': 'param-size-param',
+ 'struct-type': 'param-struct-type',
+ }
+
+ i = 0
+ while i < len(lines):
+ line = lines[i]
+ if not line.strip():
+ i += 1
+ continue
+
+ # Check if this is our parameter (non-indented line)
+ if not line.startswith((' ', '\t')):
+ parts = line.strip().split(',', 1)
+ current_item = param_name if parts[0].strip() == param_name else None
+ if current_item and len(parts) > 1:
+ specs['param-type'] = parts[1].strip()
+ i += 1
+ elif current_item == param_name:
+ # Parse subfield
+ stripped = line.strip()
+ if ':' in stripped:
+ key, value = stripped.split(':', 1)
+ key = key.strip()
+ value = value.strip()
+
+ # Collect continuation lines (indented lines without a colon that
+ # defines a new key, i.e., lines that are pure continuations)
+ i += 1
+ while i < len(lines):
+ next_line = lines[i]
+ # Stop if we hit a non-indented line (new param)
+ if next_line.strip() and not next_line.startswith((' ', '\t')):
+ break
+ next_stripped = next_line.strip()
+ # Stop if we hit a new key (contains colon with known key prefix)
+ if next_stripped and ':' in next_stripped:
+ potential_key = next_stripped.split(':', 1)[0].strip()
+ if potential_key in field_map or potential_key in ['type', 'desc']:
+ break
+ # This is a continuation line
+ if next_stripped:
+ value = value + ' ' + next_stripped
+ i += 1
+
+ if key in field_map:
+ # Clean up the value - remove excessive whitespace
+ value = ' '.join(value.split())
+ specs[field_map[key]] = value
+ else:
+ i += 1
+
+ return specs
+
+ def _validate_effect_type(self, effect_type):
+ """Validate and normalize effect type"""
+ if 'KAPI_EFFECT_SCHEDULER' in effect_type:
+ return effect_type.replace('KAPI_EFFECT_SCHEDULER', 'KAPI_EFFECT_SCHEDULE')
+
+ if 'KAPI_EFFECT_' in effect_type and effect_type not in VALID_EFFECT_TYPES:
+ if '|' in effect_type:
+ parts = [p.strip() for p in effect_type.split('|')]
+ valid_parts = []
+ for p in parts:
+ if p in VALID_EFFECT_TYPES:
+ valid_parts.append(p)
+ else:
+ import sys
+ print(f"warning: unrecognized effect type '{p}', "
+ f"defaulting to KAPI_EFFECT_MODIFY_STATE", file=sys.stderr)
+ valid_parts.append('KAPI_EFFECT_MODIFY_STATE')
+ return ' | '.join(valid_parts)
+ import sys
+ print(f"warning: unrecognized effect type '{effect_type}', "
+ f"defaulting to KAPI_EFFECT_MODIFY_STATE", file=sys.stderr)
+ return 'KAPI_EFFECT_MODIFY_STATE'
+
+ return effect_type
+
+ def _has_api_spec(self, sections):
+ """Check if this function has an API specification.
+
+ Returns True if at least 2 KAPI-specific section indicators are present.
+ We require 2+ indicators (not just 1) to avoid false positives from
+ regular kernel-doc comments that happen to use a common section name
+ like 'return' or 'error'. Having multiple KAPI sections strongly
+ suggests intentional API specification rather than coincidence.
+ """
+ indicators = [
+ 'api-type', 'context-flags', 'param-type', 'error-code',
+ 'capability', 'signal', 'lock', 'state-trans', 'constraint',
+ 'side-effect', 'long-desc'
+ ]
+
+ count = sum(1 for ind in indicators
+ if any(key.lower().startswith(ind.lower()) or
+ key.lower().startswith('@' + ind.lower())
+ for key in sections.keys()))
+
+ # Require 2+ indicators to distinguish from regular kernel-doc
+ return count >= 2
+
+ def out_function(self, fname, name, args):
+ """Generate API spec for a function"""
+ function_name = args.get('function', name)
+ sections = args.sections if hasattr(args, 'sections') else args.get('sections', {})
+
+ if not self._has_api_spec(sections):
+ return
+
+ parameterlist = args.parameterlist if hasattr(args, 'parameterlist') else args.get('parameterlist', [])
+ parameterdescs = args.parameterdescs if hasattr(args, 'parameterdescs') else args.get('parameterdescs', {})
+ parametertypes = args.parametertypes if hasattr(args, 'parametertypes') else args.get('parametertypes', {})
+ purpose = args.get('purpose', '')
+
+ # Start macro invocation
+ self.data += f"DEFINE_KERNEL_API_SPEC({function_name})\n"
+
+ # Basic info
+ if purpose:
+ self.data += f"\tKAPI_DESCRIPTION({self._format_macro_param(purpose)})\n"
+
+ long_desc = self._get_multiline_section(sections, 'long-desc')
+ if long_desc:
+ self.data += f"\tKAPI_LONG_DESC({self._format_macro_param(long_desc, KAPI_MAX_LONG_DESC_LEN)})\n"
+
+ # Context flags
+ context = self._get_section(sections, 'context-flags') or self._get_section(sections, 'context')
+ if context:
+ self.data += f"\tKAPI_CONTEXT({context})\n"
+
+ # Process parameters
+ self._process_parameters(sections, parameterlist, parameterdescs, parametertypes)
+
+ # Process return value
+ self._process_return(sections)
+
+ # Process errors
+ errors = self._parse_indented_items(
+ self._get_raw_section(sections, 'error'),
+ self._parse_error_item
+ )
+
+ if errors:
+ self.data += f"\n\tKAPI_ERROR_COUNT({len(errors)})\n"
+
+ for idx, error in enumerate(errors):
+ self._output_error(idx, error)
+
+ # Process signals
+ signals = self._parse_indented_items(
+ self._get_raw_section(sections, 'signal'),
+ self._parse_signal_item
+ )
+
+ if signals:
+ self.data += f"\n\tKAPI_SIGNAL_COUNT({len(signals)})\n"
+
+ for idx, signal in enumerate(signals):
+ self._output_signal(idx, signal)
+
+ # Process other specifications
+ self._process_locks(sections)
+ self._process_constraints(sections)
+ self._process_side_effects(sections)
+ self._process_state_transitions(sections)
+ self._process_capabilities(sections)
+
+ # Add examples and notes (use multiline extraction for full content)
+ for key, macro, max_len in [
+ ('examples', 'KAPI_EXAMPLES', KAPI_MAX_EXAMPLES_LEN),
+ ('notes', 'KAPI_NOTES', KAPI_MAX_NOTES_LEN),
+ ]:
+ value = self._get_multiline_section(sections, key)
+ if value:
+ self.data += f"\n\t{macro}({self._format_macro_param(value, max_len)})\n"
+
+ self.data += "\nKAPI_END_SPEC;\n\n"
+
+ def _process_return(self, sections):
+ """Process the return value specification from kerneldoc annotations"""
+ raw = self._get_raw_section(sections, 'return')
+ if not raw:
+ return
+
+ # Parse subfields from the return section, handling continuation lines
+ lines = raw.strip().split('\n')
+ subfields = {}
+ current_key = None
+ for line in lines:
+ stripped = line.strip()
+ if ':' in stripped and not stripped.startswith(' '):
+ key, value = stripped.split(':', 1)
+ current_key = key.strip()
+ subfields[current_key] = value.strip()
+ elif current_key and stripped:
+ # Continuation line
+ subfields[current_key] += ' ' + stripped
+
+ ret_type = subfields.get('type', '')
+ check_type = subfields.get('check-type', '')
+ desc = subfields.get('desc', '')
+ success = subfields.get('success', '')
+
+ if not ret_type and not desc:
+ return
+
+ self.data += f"\n\tKAPI_RETURN({self._format_macro_param(ret_type)}, "
+ self.data += f"{self._format_macro_param(desc)})\n"
+
+ if ret_type:
+ self.data += f"\t\tKAPI_RETURN_TYPE({ret_type})\n"
+
+ if check_type:
+ self.data += f"\t\tKAPI_RETURN_CHECK_TYPE({check_type})\n"
+
+ if success and check_type == 'KAPI_RETURN_RANGE':
+ self.data += f"\t\tKAPI_RETURN_SUCCESS_RANGE(0, S64_MAX)\n"
+
+ self.data += "\tKAPI_RETURN_END\n"
+
+ def _output_error(self, idx, error):
+ """Output a single error specification"""
+ line = error['line']
+ if line.startswith('-'):
+ line = line[1:].strip()
+
+ parts = line.split(',', 2)
+ if len(parts) == 2:
+ # Format: NAME, description
+ name = parts[0].strip()
+ short_desc = parts[1].strip()
+ code = f"-{name}"
+ elif len(parts) >= 3:
+ # Format: code, name, description
+ code = parts[0].strip()
+ name = parts[1].strip()
+ short_desc = parts[2].strip()
+ if not code.startswith('-'):
+ code = f"-{code}"
+ else:
+ return
+
+ long_desc = error.get('desc', '') or short_desc
+
+ self.data += f"\n\tKAPI_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 _output_signal(self, idx, signal):
+ """Output a single signal specification"""
+ self.data += f"\n\tKAPI_SIGNAL({idx}, {signal['number']}, "
+ self.data += f"{self._format_macro_param(signal['name'], KAPI_MAX_SIGNAL_NAME_LEN)}, "
+ self.data += f"{signal['direction']}, {signal['action']})\n"
+
+ for key, macro in [
+ ('condition', 'KAPI_SIGNAL_CONDITION'),
+ ('desc', 'KAPI_SIGNAL_DESC'),
+ ('error', 'KAPI_SIGNAL_ERROR'),
+ ('timing', 'KAPI_SIGNAL_TIMING'),
+ ('priority', 'KAPI_SIGNAL_PRIORITY'),
+ ]:
+ if signal.get(key):
+ # Priority field is numeric
+ if key == 'priority':
+ self.data += f"\t\t{macro}({signal[key]})\n"
+ else:
+ self.data += f"\t\t{macro}({self._format_macro_param(signal[key])})\n"
+
+ if signal.get('interruptible'):
+ self.data += "\t\tKAPI_SIGNAL_INTERRUPTIBLE\n"
+
+ self.data += "\tKAPI_SIGNAL_END\n"
+
+ def _process_locks(self, sections):
+ """Process lock specifications"""
+ locks = self._parse_indented_items(
+ self._get_raw_section(sections, 'lock'),
+ self._parse_lock_item
+ )
+
+ if locks:
+ self.data += f"\n\tKAPI_LOCK_COUNT({len(locks)})\n"
+
+ for idx, lock in enumerate(locks):
+ self.data += f"\n\tKAPI_LOCK({idx}, {self._format_macro_param(lock['name'])}, {lock['type']})\n"
+
+ for flag in ['acquired', 'released']:
+ if lock.get(flag):
+ self.data += f"\t\tKAPI_LOCK_{flag.upper()}\n"
+
+ if lock.get('desc'):
+ self.data += f"\t\tKAPI_LOCK_DESC({self._format_macro_param(lock['desc'])})\n"
+
+ self.data += "\tKAPI_LOCK_END\n"
+
+ def _process_constraints(self, sections):
+ """Process constraint specifications"""
+ constraints = self._parse_indented_items(
+ self._get_raw_section(sections, 'constraint'),
+ self._parse_constraint_item
+ )
+
+ if constraints:
+ self.data += f"\n\tKAPI_CONSTRAINT_COUNT({len(constraints)})\n"
+
+ for idx, constraint in enumerate(constraints):
+ self.data += f"\n\tKAPI_CONSTRAINT({idx}, {self._format_macro_param(constraint['name'])},\n"
+ self.data += f"\t\t\t{self._format_macro_param(constraint['desc'])})\n"
+
+ if constraint.get('expr'):
+ self.data += f"\t\tKAPI_CONSTRAINT_EXPR({self._format_macro_param(constraint['expr'])})\n"
+
+ self.data += "\tKAPI_CONSTRAINT_END\n"
+
+ def _process_side_effects(self, sections):
+ """Process side effect specifications"""
+ effects = self._parse_indented_items(
+ self._get_raw_section(sections, 'side-effect'),
+ self._parse_side_effect_item
+ )
+
+ if effects:
+ self.data += f"\n\tKAPI_SIDE_EFFECT_COUNT({len(effects)})\n"
+
+ for idx, effect in enumerate(effects):
+ effect_type = self._validate_effect_type(effect['type'])
+
+ self.data += f"\n\tKAPI_SIDE_EFFECT({idx}, {effect_type},\n"
+ self.data += f"\t\t\t {self._format_macro_param(effect['target'])},\n"
+ self.data += f"\t\t\t {self._format_macro_param(effect['desc'])})\n"
+
+ if effect.get('condition'):
+ self.data += f"\t\tKAPI_EFFECT_CONDITION({self._format_macro_param(effect['condition'])})\n"
+
+ if effect.get('reversible'):
+ self.data += "\t\tKAPI_EFFECT_REVERSIBLE\n"
+
+ self.data += "\tKAPI_SIDE_EFFECT_END\n"
+
+ def _process_state_transitions(self, sections):
+ """Process state transition specifications"""
+ transitions = self._parse_indented_items(
+ self._get_raw_section(sections, 'state-trans'),
+ self._parse_state_trans_item
+ )
+
+ if transitions:
+ self.data += f"\n\tKAPI_STATE_TRANS_COUNT({len(transitions)})\n"
+
+ for idx, trans in enumerate(transitions):
+ desc = trans['desc']
+ if trans.get('condition'):
+ desc = trans['condition'] + (', ' + desc if desc else '')
+
+ self.data += f"\n\tKAPI_STATE_TRANS({idx}, {self._format_macro_param(trans['target'])}, "
+ self.data += f"{self._format_macro_param(trans['from'])}, {self._format_macro_param(trans['to'])},\n"
+ self.data += f"\t\t\t {self._format_macro_param(desc)})\n"
+ self.data += "\tKAPI_STATE_TRANS_END\n"
+
+ def _process_capabilities(self, sections):
+ """Process capability specifications"""
+ cap_section = self._get_raw_section(sections, 'capability')
+ if not cap_section:
+ return
+
+ lines = cap_section.strip().split('\n')
+ capabilities = []
+ i = 0
+
+ while i < len(lines):
+ line = lines[i].strip()
+ # Skip empty lines and subfield lines (they'll be parsed with their parent)
+ if not line or line.startswith(('allows:', 'without:', 'condition:', 'priority:', 'type:', 'desc:')):
+ i += 1
+ continue
+
+ cap_info = {'line': line}
+
+ # Parse subfields
+ subfields, next_i = self._parse_subfields(lines, i)
+ cap_info.update(subfields)
+ capabilities.append(cap_info)
+ i = next_i
+
+ if capabilities:
+ # Filter out "none" capabilities (no capability required)
+ valid_caps = [cap for cap in capabilities if cap['line'].strip().lower() != 'none']
+
+ if not valid_caps:
+ return
+
+ self.data += f"\n\tKAPI_CAPABILITY_COUNT({len(valid_caps)})\n"
+
+ for idx, cap in enumerate(valid_caps):
+ line = cap['line']
+ parts = line.split(',', 2)
+
+ # Handle both formats:
+ # 1. New format: "CAP_NAME" with type/desc as subfields
+ # 2. Old format: "CAP_NAME, TYPE, description"
+ if len(parts) >= 2:
+ # Old comma-separated format
+ cap_name = parts[0].strip()
+ cap_type = parts[1].strip()
+ cap_desc = parts[2].strip() if len(parts) > 2 else cap.get('desc', cap_name)
+ else:
+ # New subfield format - capability name on main line
+ cap_name = line.strip()
+ cap_type = cap.get('type', 'KAPI_CAP_PERFORM_OPERATION')
+ cap_desc = cap.get('desc', cap_name)
+
+ # Map capability type to valid kernel enum values
+ cap_type_map = {
+ 'KAPI_CAP_REQUIRED': 'KAPI_CAP_PERFORM_OPERATION',
+ 'required': 'KAPI_CAP_PERFORM_OPERATION',
+ 'bypass': 'KAPI_CAP_BYPASS_CHECK',
+ 'grant': 'KAPI_CAP_GRANT_PERMISSION',
+ 'override': 'KAPI_CAP_OVERRIDE_RESTRICTION',
+ 'access': 'KAPI_CAP_ACCESS_RESOURCE',
+ 'modify': 'KAPI_CAP_MODIFY_BEHAVIOR',
+ 'limit': 'KAPI_CAP_INCREASE_LIMIT',
+ }
+ cap_type = cap_type_map.get(cap_type, cap_type)
+
+ # Fix common type issues
+ if 'BYPASS' in cap_type and cap_type != 'KAPI_CAP_BYPASS_CHECK':
+ cap_type = 'KAPI_CAP_BYPASS_CHECK'
+
+ # Ensure cap_type is a valid enum
+ valid_types = [
+ 'KAPI_CAP_BYPASS_CHECK', 'KAPI_CAP_INCREASE_LIMIT',
+ 'KAPI_CAP_OVERRIDE_RESTRICTION', 'KAPI_CAP_GRANT_PERMISSION',
+ 'KAPI_CAP_MODIFY_BEHAVIOR', 'KAPI_CAP_ACCESS_RESOURCE',
+ 'KAPI_CAP_PERFORM_OPERATION'
+ ]
+ if cap_type not in valid_types:
+ cap_type = 'KAPI_CAP_PERFORM_OPERATION'
+
+ self.data += f"\n\tKAPI_CAPABILITY({idx}, {cap_name}, {self._format_macro_param(cap_desc)}, {cap_type})\n"
+
+ for key, macro in [
+ ('allows', 'KAPI_CAP_ALLOWS'),
+ ('without', 'KAPI_CAP_WITHOUT'),
+ ('condition', 'KAPI_CAP_CONDITION'),
+ ('priority', 'KAPI_CAP_PRIORITY'),
+ ]:
+ if cap.get(key):
+ value = self._format_macro_param(cap[key]) if key != 'priority' else cap[key]
+ self.data += f"\t\t{macro}({value})\n"
+
+ self.data += "\tKAPI_CAPABILITY_END\n"
+
+ # Skip output methods for non-function types
+ def out_enum(self, fname, name, args): pass
+ def out_typedef(self, fname, name, args): pass
+ def out_struct(self, fname, name, args): pass
+ def out_doc(self, fname, name, args): pass
diff --git a/tools/lib/python/kdoc/kdoc_output.py b/tools/lib/python/kdoc/kdoc_output.py
index 4210b91dde5f1..cd91a4f59f275 100644
--- a/tools/lib/python/kdoc/kdoc_output.py
+++ b/tools/lib/python/kdoc/kdoc_output.py
@@ -129,8 +129,13 @@ class OutputFormat:
Output warnings for identifiers that will be displayed.
"""
- for log_msg in args.warnings:
- self.config.warning(log_msg)
+ warnings = getattr(args, 'warnings', [])
+
+ for log_msg in warnings:
+ # 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/tools/lib/python/kdoc/kdoc_parser.py b/tools/lib/python/kdoc/kdoc_parser.py
index ca00695b47b31..9468f91c07597 100644
--- a/tools/lib/python/kdoc/kdoc_parser.py
+++ b/tools/lib/python/kdoc/kdoc_parser.py
@@ -28,6 +28,23 @@ from kdoc.kdoc_item import KdocItem
# Allow whitespace at end of comment start.
doc_start = KernRe(r'^/\*\*\s*$', cache=False)
+# Sections that are allowed to be duplicated for API specifications
+# These represent lists of items (multiple errors, signals, etc.)
+ALLOWED_DUPLICATE_SECTIONS = {
+ 'param', '@param',
+ 'error', '@error',
+ 'signal', '@signal',
+ 'lock', '@lock',
+ 'side-effect', '@side-effect',
+ 'state-trans', '@state-trans',
+ 'capability', '@capability',
+ 'constraint', '@constraint',
+ 'validation-group', '@validation-group',
+ 'validation-rule', '@validation-rule',
+ 'validation-flag', '@validation-flag',
+ 'struct-field', '@struct-field',
+}
+
doc_end = KernRe(r'\*/', cache=False)
doc_com = KernRe(r'\s*\*\s*', cache=False)
doc_com_body = KernRe(r'\s*\* ?', cache=False)
@@ -40,10 +57,71 @@ doc_decl = doc_com + KernRe(r'(\w+)', cache=False)
# @{section-name}:
# while trying to not match literal block starts like "example::"
#
+# Base kernel-doc section names
known_section_names = 'description|context|returns?|notes?|examples?'
-known_sections = KernRe(known_section_names, flags = re.I)
+
+# API specification section names (for KAPI spec framework)
+# Format: (base_name, has_count_variant, has_other_variants)
+# Sections with has_count_variant=True need negative lookahead in doc_sect
+# to avoid matching 'error' when 'error-count' is intended
+_kapi_base_sections = [
+ # (name, needs_lookahead, additional_variants)
+ ('api-type', False, []),
+ ('api-version', False, []),
+ ('param', True, []), # has param-count
+ ('struct', True, ['struct-type', 'struct-field', 'struct-field-[a-z\\-]+']),
+ ('validation-group', False, []),
+ ('validation-policy', False, []),
+ ('validation-flag', False, []),
+ ('validation-rule', False, []),
+ ('error', True, ['error-code', 'error-condition']),
+ ('capability', True, []),
+ ('signal', True, []),
+ ('lock', True, []),
+ ('since', False, ['since-version']),
+ ('context-flags', False, []),
+ ('return', True, ['return-type', 'return-check', 'return-check-type',
+ 'return-success', 'return-desc']),
+ ('long-desc', False, []),
+ ('constraint', True, []),
+ ('side-effect', True, []),
+ ('state-trans', True, []),
+]
+
+def _build_kapi_patterns():
+ """Build KAPI section patterns from the base definitions."""
+ validation_parts = [] # For known_sections (simple validation)
+ parsing_parts = [] # For doc_sect (with negative lookaheads)
+
+ for name, has_count, variants in _kapi_base_sections:
+ # Add base name (with optional @ prefix)
+ validation_parts.append(f'@?{name}')
+ if has_count:
+ # Need negative lookahead to not match 'name-count' or 'name-*'
+ parsing_parts.append(f'@?{name}(?!-)')
+ validation_parts.append(f'@?{name}-count')
+ parsing_parts.append(f'@?{name}-count')
+ else:
+ parsing_parts.append(f'@?{name}')
+
+ # Add variants
+ for variant in variants:
+ validation_parts.append(f'@?{variant}')
+ parsing_parts.append(f'@?{variant}')
+
+ # Add catch-all for kapi-* extensions
+ validation_parts.append(r'@?kapi-.*')
+ parsing_parts.append(r'@?kapi-.*')
+
+ return '|'.join(validation_parts), '|'.join(parsing_parts)
+
+_kapi_validation_pattern, _kapi_parsing_pattern = _build_kapi_patterns()
+
+known_sections = KernRe(known_section_names + '|' + _kapi_validation_pattern,
+ flags=re.I)
doc_sect = doc_com + \
- KernRe(r'\s*(@[.\w]+|@\.\.\.|' + known_section_names + r')\s*:([^:].*)?$',
+ KernRe(r'\s*(@[.\w\-]+|@\.\.\.|' + known_section_names + '|' +
+ _kapi_parsing_pattern + r')\s*:([^:].*)?$',
flags=re.I, cache=False)
doc_content = doc_com_body + KernRe(r'(.*)', cache=False)
@@ -349,7 +427,9 @@ class KernelEntry:
else:
if name in self.sections and self.sections[name] != "":
# Only warn on user-specified duplicate section names
- if name != SECTION_DEFAULT:
+ # Skip warning for sections that are expected to have duplicates
+ # (like error, param, signal, etc. for API specifications)
+ if name != SECTION_DEFAULT and name not in ALLOWED_DUPLICATE_SECTIONS:
self.emit_msg(self.new_start_line,
f"duplicate section name '{name}'")
# Treat as a new paragraph - add a blank line
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH 3/9] kernel/api: add debugfs interface for kernel API specifications
2026-03-13 15:09 [PATCH 0/9] Kernel API Specification Framework Sasha Levin
2026-03-13 15:09 ` [PATCH 1/9] kernel/api: introduce kernel API specification framework Sasha Levin
2026-03-13 15:09 ` [PATCH 2/9] kernel/api: enable kerneldoc-based API specifications Sasha Levin
@ 2026-03-13 15:09 ` Sasha Levin
2026-03-13 15:32 ` Greg Kroah-Hartman
2026-03-13 15:09 ` [PATCH 4/9] tools/kapi: Add kernel API specification extraction tool Sasha Levin
` (6 subsequent siblings)
9 siblings, 1 reply; 37+ messages in thread
From: Sasha Levin @ 2026-03-13 15:09 UTC (permalink / raw)
To: linux-api, linux-kernel
Cc: linux-doc, linux-fsdevel, linux-kbuild, linux-kselftest,
workflows, tools, x86, Thomas Gleixner, Paul E. McKenney,
Greg Kroah-Hartman, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann, Sasha Levin
Add a debugfs interface to expose kernel API specifications at runtime.
This allows tools and users to query the complete API specifications
through the debugfs filesystem.
The interface provides:
- /sys/kernel/debug/kapi/list - lists all available API specifications
- /sys/kernel/debug/kapi/specs/<name> - detailed info for each API
Each specification file includes:
- Function name, version, and descriptions
- Execution context requirements and flags
- Parameter details with types, flags, and constraints
- Return value specifications and success conditions
- Error codes with descriptions and conditions
- Locking requirements and constraints
- Signal handling specifications
- Examples, notes, and deprecation status
This enables runtime introspection of kernel APIs for documentation
tools, static analyzers, and debugging purposes.
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
Documentation/dev-tools/kernel-api-spec.rst | 88 ++--
kernel/api/kapi_debugfs.c | 503 ++++++++++++++++++++
2 files changed, 534 insertions(+), 57 deletions(-)
create mode 100644 kernel/api/kapi_debugfs.c
diff --git a/Documentation/dev-tools/kernel-api-spec.rst b/Documentation/dev-tools/kernel-api-spec.rst
index 7c0c1694f1f4a..6ec6e65a1902a 100644
--- a/Documentation/dev-tools/kernel-api-spec.rst
+++ b/Documentation/dev-tools/kernel-api-spec.rst
@@ -56,16 +56,10 @@ The framework consists of several key components:
2. **DebugFS Interface** (``kernel/api/kapi_debugfs.c``)
- Runtime introspection via ``/sys/kernel/debug/kapi/``
- - JSON and XML export formats
- - Per-API detailed information
+ - Per-API detailed specification output
+ - List of all registered API specifications
-3. **IOCTL Support** (``kernel/api/ioctl_validation.c``)
-
- - Extended framework for IOCTL specifications
- - Automatic validation wrappers
- - Structure field validation
-
-4. **Specification Macros** (``include/linux/kernel_api_spec.h``)
+3. **Specification Macros** (``include/linux/kernel_api_spec.h``)
- Declarative macros for API documentation
- Type-safe parameter specifications
@@ -186,8 +180,8 @@ Runtime validation is controlled by kernel configuration:
2. Enable ``CONFIG_KAPI_RUNTIME_CHECKS`` for runtime validation
3. Optionally enable ``CONFIG_KAPI_SPEC_DEBUGFS`` for debugfs interface
-Validation Modes
-----------------
+Validation Behavior
+-------------------
When ``CONFIG_KAPI_RUNTIME_CHECKS`` is enabled, all registered API specifications
are validated automatically at call time. The framework checks parameter constraints,
@@ -225,55 +219,39 @@ Directory Structure
::
/sys/kernel/debug/kapi/
- ├── apis/ # All registered APIs
- │ ├── kmalloc/
- │ │ ├── specification # Human-readable spec
- │ │ ├── json # JSON format
- │ │ └── xml # XML format
- │ └── open/
- │ └── ...
- ├── summary # Overview of all APIs
- ├── validation/ # Validation controls
- │ ├── enabled # Global enable/disable
- │ ├── level # Validation level
- │ └── stats # Validation statistics
- └── export/ # Bulk export options
- ├── all.json # All specs in JSON
- └── all.xml # All specs in XML
+ ├── list # Overview of all registered API specs
+ └── specs/ # Per-API specification files
+ ├── sys_open # Human-readable spec for sys_open
+ ├── sys_close # Human-readable spec for sys_close
+ ├── sys_read # Human-readable spec for sys_read
+ └── sys_write # Human-readable spec for sys_write
Usage Examples
--------------
-Query specific API::
-
- $ cat /sys/kernel/debug/kapi/apis/kmalloc/specification
- API: kmalloc
- Version: 3.0
- Description: Allocate kernel memory
+List all available API specifications::
- Parameters:
- [0] size (size_t, in): Number of bytes to allocate
- Range: 0 - 4194304
- [1] flags (flags, in): Allocation flags (GFP_*)
- Mask: 0x1ffffff
+ $ cat /sys/kernel/debug/kapi/list
+ Available Kernel API Specifications
+ ===================================
- Returns: pointer - Pointer to allocated memory or NULL
+ sys_open - Open or create a file
+ sys_close - Close a file descriptor
+ sys_read - Read data from a file descriptor
+ sys_write - Write data to a file descriptor
- Errors:
- ENOMEM: Out of memory
+ Total: 4 specifications
- Context: process, softirq, hardirq
-
- Side Effects:
- - Allocates memory from kernel heap
-
-Export all specifications::
-
- $ cat /sys/kernel/debug/kapi/export/all.json > kernel-apis.json
+Query specific API::
-Enable validation for specific API::
+ $ cat /sys/kernel/debug/kapi/specs/sys_open
+ Kernel API Specification
+ ========================
- $ echo 1 > /sys/kernel/debug/kapi/apis/kmalloc/validate
+ Name: sys_open
+ Version: 1
+ Description: Open or create a file
+ ...
Performance Considerations
==========================
@@ -304,11 +282,8 @@ Optimization Strategies
1. **Compile-time optimization**: When validation is disabled, all
validation code is optimized away by the compiler.
-2. **Selective validation**: Enable validation only for specific APIs
- or subsystems under test.
-
-3. **Caching**: The framework caches validation results for repeated
- calls with identical parameters.
+2. **Selective enablement**: Enable ``CONFIG_KAPI_RUNTIME_CHECKS``
+ only in development/testing kernels, not in production.
Documentation Generation
------------------------
@@ -414,10 +389,9 @@ Common Issues
Debug Options
-------------
-Enable verbose debugging::
+Enable verbose kernel logging to see KAPI validation messages::
echo 8 > /proc/sys/kernel/printk
- echo 1 > /sys/kernel/debug/kapi/debug/verbose
Future Directions
=================
diff --git a/kernel/api/kapi_debugfs.c b/kernel/api/kapi_debugfs.c
new file mode 100644
index 0000000000000..be187646ee768
--- /dev/null
+++ b/kernel/api/kapi_debugfs.c
@@ -0,0 +1,503 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Kernel API specification debugfs interface
+ *
+ * This provides a debugfs interface to expose kernel API specifications
+ * at runtime, allowing tools and users to query the complete API specs.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/seq_file.h>
+#include <linux/kernel_api_spec.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+/* External symbols for kernel API spec section */
+extern struct kernel_api_spec __start_kapi_specs[];
+extern struct kernel_api_spec __stop_kapi_specs[];
+
+static struct dentry *kapi_debugfs_root;
+
+/* Helper function to print parameter type as string */
+static const char * const param_type_names[] = {
+ [KAPI_TYPE_VOID] = "void",
+ [KAPI_TYPE_INT] = "int",
+ [KAPI_TYPE_UINT] = "uint",
+ [KAPI_TYPE_PTR] = "ptr",
+ [KAPI_TYPE_STRUCT] = "struct",
+ [KAPI_TYPE_UNION] = "union",
+ [KAPI_TYPE_ARRAY] = "array",
+ [KAPI_TYPE_FD] = "fd",
+ [KAPI_TYPE_ENUM] = "enum",
+ [KAPI_TYPE_USER_PTR] = "user_ptr",
+ [KAPI_TYPE_PATH] = "path",
+ [KAPI_TYPE_FUNC_PTR] = "func_ptr",
+ [KAPI_TYPE_CUSTOM] = "custom",
+};
+
+static const char *param_type_str(enum kapi_param_type type)
+{
+ if (type < ARRAY_SIZE(param_type_names) && param_type_names[type])
+ return param_type_names[type];
+ return "unknown";
+}
+
+/* Helper to print parameter flags */
+static void print_param_flags(struct seq_file *m, u32 flags)
+{
+ seq_puts(m, " flags: ");
+ if ((flags & KAPI_PARAM_INOUT) == KAPI_PARAM_INOUT)
+ seq_puts(m, "INOUT ");
+ else if (flags & KAPI_PARAM_IN)
+ seq_puts(m, "IN ");
+ else if (flags & KAPI_PARAM_OUT)
+ seq_puts(m, "OUT ");
+ if (flags & KAPI_PARAM_OPTIONAL)
+ seq_puts(m, "OPTIONAL ");
+ if (flags & KAPI_PARAM_CONST)
+ seq_puts(m, "CONST ");
+ if (flags & KAPI_PARAM_USER)
+ seq_puts(m, "USER ");
+ if (flags & KAPI_PARAM_VOLATILE)
+ seq_puts(m, "VOLATILE ");
+ if (flags & KAPI_PARAM_DMA)
+ seq_puts(m, "DMA ");
+ if (flags & KAPI_PARAM_ALIGNED)
+ seq_puts(m, "ALIGNED ");
+ seq_puts(m, "\n");
+}
+
+/* Helper to print context flags */
+static void print_context_flags(struct seq_file *m, u32 flags)
+{
+ seq_puts(m, "Context flags: ");
+ if (flags & KAPI_CTX_PROCESS)
+ seq_puts(m, "PROCESS ");
+ if (flags & KAPI_CTX_HARDIRQ)
+ seq_puts(m, "HARDIRQ ");
+ if (flags & KAPI_CTX_SOFTIRQ)
+ seq_puts(m, "SOFTIRQ ");
+ if (flags & KAPI_CTX_NMI)
+ seq_puts(m, "NMI ");
+ if (flags & KAPI_CTX_SLEEPABLE)
+ seq_puts(m, "SLEEPABLE ");
+ if (flags & KAPI_CTX_ATOMIC)
+ seq_puts(m, "ATOMIC ");
+ if (flags & KAPI_CTX_PREEMPT_DISABLED)
+ seq_puts(m, "PREEMPT_DISABLED ");
+ if (flags & KAPI_CTX_IRQ_DISABLED)
+ seq_puts(m, "IRQ_DISABLED ");
+ seq_puts(m, "\n");
+}
+
+/* Show function for individual API spec */
+static int kapi_spec_show(struct seq_file *m, void *v)
+{
+ struct kernel_api_spec *spec = m->private;
+ int i;
+
+ seq_puts(m, "Kernel API Specification\n");
+ seq_puts(m, "========================\n\n");
+
+ /* Basic info */
+ seq_printf(m, "Name: %s\n", spec->name);
+ seq_printf(m, "Version: %u\n", spec->version);
+ seq_printf(m, "Description: %s\n", spec->description);
+ if (strnlen(spec->long_description, sizeof(spec->long_description)) > 0)
+ seq_printf(m, "Long description: %.*s\n",
+ (int)sizeof(spec->long_description), spec->long_description);
+
+ /* Context */
+ print_context_flags(m, spec->context_flags);
+ seq_puts(m, "\n");
+
+ /* Parameters */
+ if (spec->param_count > 0) {
+ seq_printf(m, "Parameters (%u):\n", spec->param_count);
+ for (i = 0; i < spec->param_count && i < KAPI_MAX_PARAMS; i++) {
+ struct kapi_param_spec *param = &spec->params[i];
+ seq_printf(m, " [%d] %s:\n", i, param->name);
+ seq_printf(m, " type: %s (%s)\n",
+ param_type_str(param->type), param->type_name);
+ print_param_flags(m, param->flags);
+ if (strnlen(param->description, sizeof(param->description)) > 0)
+ seq_printf(m, " description: %.*s\n",
+ (int)sizeof(param->description), param->description);
+ if (param->size > 0)
+ seq_printf(m, " size: %zu\n", param->size);
+ if (param->alignment > 0)
+ seq_printf(m, " alignment: %zu\n", param->alignment);
+
+ /* Print constraints if any */
+ if (param->constraint_type != KAPI_CONSTRAINT_NONE) {
+ seq_puts(m, " constraints:\n");
+ switch (param->constraint_type) {
+ case KAPI_CONSTRAINT_RANGE:
+ seq_puts(m, " type: range\n");
+ seq_printf(m, " min: %lld\n", param->min_value);
+ seq_printf(m, " max: %lld\n", param->max_value);
+ break;
+ case KAPI_CONSTRAINT_MASK:
+ seq_puts(m, " type: mask\n");
+ seq_printf(m, " valid_bits: 0x%llx\n", param->valid_mask);
+ break;
+ case KAPI_CONSTRAINT_ENUM:
+ seq_puts(m, " type: enum\n");
+ seq_printf(m, " count: %u\n", param->enum_count);
+ break;
+ case KAPI_CONSTRAINT_USER_STRING:
+ seq_puts(m, " type: user_string\n");
+ seq_printf(m, " min_len: %lld\n", param->min_value);
+ seq_printf(m, " max_len: %lld\n", param->max_value);
+ break;
+ case KAPI_CONSTRAINT_USER_PATH:
+ seq_puts(m, " type: user_path\n");
+ seq_puts(m, " max_len: PATH_MAX (4096)\n");
+ break;
+ case KAPI_CONSTRAINT_USER_PTR:
+ seq_puts(m, " type: user_ptr\n");
+ seq_printf(m, " size: %zu bytes\n", param->size);
+ break;
+ case KAPI_CONSTRAINT_ALIGNMENT:
+ seq_puts(m, " type: alignment\n");
+ seq_printf(m, " alignment: %zu\n", param->alignment);
+ break;
+ case KAPI_CONSTRAINT_POWER_OF_TWO:
+ seq_puts(m, " type: power_of_two\n");
+ break;
+ case KAPI_CONSTRAINT_PAGE_ALIGNED:
+ seq_puts(m, " type: page_aligned\n");
+ break;
+ case KAPI_CONSTRAINT_NONZERO:
+ seq_puts(m, " type: nonzero\n");
+ break;
+ case KAPI_CONSTRAINT_CUSTOM:
+ seq_puts(m, " type: custom\n");
+ if (strnlen(param->constraints, sizeof(param->constraints)) > 0)
+ seq_printf(m, " description: %.*s\n",
+ (int)sizeof(param->constraints),
+ param->constraints);
+ break;
+ default:
+ break;
+ }
+ }
+ seq_puts(m, "\n");
+ }
+ }
+
+ /* Return value */
+ seq_puts(m, "Return value:\n");
+ seq_printf(m, " type: %s\n", spec->return_spec.type_name);
+ if (strnlen(spec->return_spec.description, sizeof(spec->return_spec.description)) > 0)
+ seq_printf(m, " description: %.*s\n",
+ (int)sizeof(spec->return_spec.description),
+ spec->return_spec.description);
+
+ switch (spec->return_spec.check_type) {
+ case KAPI_RETURN_EXACT:
+ seq_printf(m, " success: == %lld\n", spec->return_spec.success_value);
+ break;
+ case KAPI_RETURN_RANGE:
+ seq_printf(m, " success: [%lld, %lld]\n",
+ spec->return_spec.success_min,
+ spec->return_spec.success_max);
+ break;
+ case KAPI_RETURN_FD:
+ seq_puts(m, " success: valid file descriptor (>= 0)\n");
+ break;
+ case KAPI_RETURN_ERROR_CHECK:
+ seq_puts(m, " success: error check\n");
+ break;
+ case KAPI_RETURN_CUSTOM:
+ seq_puts(m, " success: custom check\n");
+ break;
+ default:
+ break;
+ }
+ seq_puts(m, "\n");
+
+ /* Errors */
+ if (spec->error_count > 0) {
+ seq_printf(m, "Errors (%u):\n", spec->error_count);
+ for (i = 0; i < spec->error_count && i < KAPI_MAX_ERRORS; i++) {
+ struct kapi_error_spec *err = &spec->errors[i];
+ seq_printf(m, " %s (%d): %s\n",
+ err->name, err->error_code, err->description);
+ if (strnlen(err->condition, sizeof(err->condition)) > 0)
+ seq_printf(m, " condition: %s\n", err->condition);
+ }
+ seq_puts(m, "\n");
+ }
+
+ /* Locks */
+ if (spec->lock_count > 0) {
+ seq_printf(m, "Locks (%u):\n", spec->lock_count);
+ for (i = 0; i < spec->lock_count && i < KAPI_MAX_CONSTRAINTS; i++) {
+ struct kapi_lock_spec *lock = &spec->locks[i];
+ const char *type_str, *scope_str;
+ switch (lock->lock_type) {
+ case KAPI_LOCK_MUTEX:
+ type_str = "mutex";
+ break;
+ case KAPI_LOCK_SPINLOCK:
+ type_str = "spinlock";
+ break;
+ case KAPI_LOCK_RWLOCK:
+ type_str = "rwlock";
+ break;
+ case KAPI_LOCK_SEMAPHORE:
+ type_str = "semaphore";
+ break;
+ case KAPI_LOCK_RCU:
+ type_str = "rcu";
+ break;
+ case KAPI_LOCK_SEQLOCK:
+ type_str = "seqlock";
+ break;
+ default:
+ type_str = "unknown";
+ break;
+ }
+ switch (lock->scope) {
+ case KAPI_LOCK_INTERNAL:
+ scope_str = "acquired and released";
+ break;
+ case KAPI_LOCK_ACQUIRES:
+ scope_str = "acquired (not released)";
+ break;
+ case KAPI_LOCK_RELEASES:
+ scope_str = "released (held on entry)";
+ break;
+ case KAPI_LOCK_CALLER_HELD:
+ scope_str = "held by caller";
+ break;
+ default:
+ scope_str = "unknown";
+ break;
+ }
+ seq_printf(m, " %s (%s): %s\n",
+ lock->lock_name, type_str, lock->description);
+ seq_printf(m, " scope: %s\n", scope_str);
+ }
+ seq_puts(m, "\n");
+ }
+
+ /* Constraints */
+ if (spec->constraint_count > 0) {
+ seq_printf(m, "Additional constraints (%u):\n", spec->constraint_count);
+ for (i = 0; i < spec->constraint_count && i < KAPI_MAX_CONSTRAINTS; i++) {
+ struct kapi_constraint_spec *cons = &spec->constraints[i];
+
+ seq_printf(m, " - %s", cons->name);
+ if (cons->description[0])
+ seq_printf(m, ": %s", cons->description);
+ seq_puts(m, "\n");
+ if (cons->expression[0])
+ seq_printf(m, " expression: %s\n", cons->expression);
+ }
+ seq_puts(m, "\n");
+ }
+
+ /* Signals */
+ if (spec->signal_count > 0) {
+ seq_printf(m, "Signal handling (%u):\n", spec->signal_count);
+ for (i = 0; i < spec->signal_count && i < KAPI_MAX_SIGNALS; i++) {
+ struct kapi_signal_spec *sig = &spec->signals[i];
+ seq_printf(m, " %s (%d):\n", sig->signal_name, sig->signal_num);
+ seq_puts(m, " direction: ");
+ if (sig->direction & KAPI_SIGNAL_SEND)
+ seq_puts(m, "send ");
+ if (sig->direction & KAPI_SIGNAL_RECEIVE)
+ seq_puts(m, "receive ");
+ if (sig->direction & KAPI_SIGNAL_HANDLE)
+ seq_puts(m, "handle ");
+ if (sig->direction & KAPI_SIGNAL_BLOCK)
+ seq_puts(m, "block ");
+ if (sig->direction & KAPI_SIGNAL_IGNORE)
+ seq_puts(m, "ignore ");
+ seq_puts(m, "\n");
+ seq_puts(m, " action: ");
+ switch (sig->action) {
+ case KAPI_SIGNAL_ACTION_DEFAULT:
+ seq_puts(m, "default");
+ break;
+ case KAPI_SIGNAL_ACTION_TERMINATE:
+ seq_puts(m, "terminate");
+ break;
+ case KAPI_SIGNAL_ACTION_COREDUMP:
+ seq_puts(m, "coredump");
+ break;
+ case KAPI_SIGNAL_ACTION_STOP:
+ seq_puts(m, "stop");
+ break;
+ case KAPI_SIGNAL_ACTION_CONTINUE:
+ seq_puts(m, "continue");
+ break;
+ case KAPI_SIGNAL_ACTION_CUSTOM:
+ seq_puts(m, "custom");
+ break;
+ case KAPI_SIGNAL_ACTION_RETURN:
+ seq_puts(m, "return");
+ break;
+ case KAPI_SIGNAL_ACTION_RESTART:
+ seq_puts(m, "restart");
+ break;
+ default:
+ seq_puts(m, "unknown");
+ break;
+ }
+ seq_puts(m, "\n");
+ if (strnlen(sig->description, sizeof(sig->description)) > 0)
+ seq_printf(m, " description: %.*s\n",
+ (int)sizeof(sig->description), sig->description);
+ }
+ seq_puts(m, "\n");
+ }
+
+ /* Side effects */
+ if (spec->side_effect_count > 0) {
+ seq_printf(m, "Side effects (%u):\n", spec->side_effect_count);
+ for (i = 0; i < spec->side_effect_count && i < KAPI_MAX_SIDE_EFFECTS; i++) {
+ const struct kapi_side_effect *eff = &spec->side_effects[i];
+
+ seq_printf(m, " - %.*s",
+ (int)sizeof(eff->target), eff->target);
+ if (strnlen(eff->description, sizeof(eff->description)) > 0)
+ seq_printf(m, ": %.*s",
+ (int)sizeof(eff->description), eff->description);
+ if (eff->reversible)
+ seq_puts(m, " (reversible)");
+ seq_puts(m, "\n");
+ }
+ seq_puts(m, "\n");
+ }
+
+ /* State transitions */
+ if (spec->state_trans_count > 0) {
+ seq_printf(m, "State transitions (%u):\n", spec->state_trans_count);
+ for (i = 0; i < spec->state_trans_count && i < KAPI_MAX_STATE_TRANS; i++) {
+ const struct kapi_state_transition *trans = &spec->state_transitions[i];
+
+ seq_printf(m, " %.*s: %.*s -> %.*s\n",
+ (int)sizeof(trans->object), trans->object,
+ (int)sizeof(trans->from_state), trans->from_state,
+ (int)sizeof(trans->to_state), trans->to_state);
+ if (strnlen(trans->description, sizeof(trans->description)) > 0)
+ seq_printf(m, " %.*s\n",
+ (int)sizeof(trans->description), trans->description);
+ }
+ seq_puts(m, "\n");
+ }
+
+ /* Capabilities */
+ if (spec->capability_count > 0) {
+ seq_printf(m, "Capabilities (%u):\n", spec->capability_count);
+ for (i = 0; i < spec->capability_count && i < KAPI_MAX_CAPABILITIES; i++) {
+ const struct kapi_capability_spec *cap = &spec->capabilities[i];
+
+ seq_printf(m, " %.*s (%d):\n",
+ (int)sizeof(cap->cap_name), cap->cap_name,
+ cap->capability);
+ if (strnlen(cap->allows, sizeof(cap->allows)) > 0)
+ seq_printf(m, " allows: %.*s\n",
+ (int)sizeof(cap->allows), cap->allows);
+ if (strnlen(cap->without_cap, sizeof(cap->without_cap)) > 0)
+ seq_printf(m, " without: %.*s\n",
+ (int)sizeof(cap->without_cap), cap->without_cap);
+ }
+ seq_puts(m, "\n");
+ }
+
+ /* Additional info */
+ if (strnlen(spec->examples, sizeof(spec->examples)) > 0) {
+ seq_printf(m, "Examples:\n%.*s\n\n",
+ (int)sizeof(spec->examples), spec->examples);
+ }
+ if (strnlen(spec->notes, sizeof(spec->notes)) > 0) {
+ seq_printf(m, "Notes:\n%.*s\n\n",
+ (int)sizeof(spec->notes), spec->notes);
+ }
+ if (strnlen(spec->since_version, sizeof(spec->since_version)) > 0) {
+ seq_printf(m, "Since: %.*s\n",
+ (int)sizeof(spec->since_version), spec->since_version);
+ }
+
+ return 0;
+}
+
+static int kapi_spec_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, kapi_spec_show, inode->i_private);
+}
+
+static const struct file_operations kapi_spec_fops = {
+ .open = kapi_spec_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/*
+ * Show all available API specs.
+ *
+ * Note: This only iterates the static .kapi_specs section. Specs registered
+ * dynamically via kapi_register_spec() are not included in this listing
+ * or in the per-spec debugfs files.
+ */
+static int kapi_list_show(struct seq_file *m, void *v)
+{
+ struct kernel_api_spec *spec;
+ int count = 0;
+
+ seq_puts(m, "Available Kernel API Specifications\n");
+ seq_puts(m, "===================================\n\n");
+
+ for (spec = __start_kapi_specs; spec < __stop_kapi_specs; spec++) {
+ seq_printf(m, "%s - %s\n", spec->name, spec->description);
+ count++;
+ }
+
+ seq_printf(m, "\nTotal: %d specifications\n", count);
+ return 0;
+}
+
+static int kapi_list_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, kapi_list_show, NULL);
+}
+
+static const struct file_operations kapi_list_fops = {
+ .open = kapi_list_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int __init kapi_debugfs_init(void)
+{
+ struct kernel_api_spec *spec;
+ struct dentry *spec_dir;
+
+ /* Create main directory */
+ kapi_debugfs_root = debugfs_create_dir("kapi", NULL);
+
+ /* Create list file */
+ debugfs_create_file("list", 0444, kapi_debugfs_root, NULL, &kapi_list_fops);
+
+ /* Create specs subdirectory */
+ spec_dir = debugfs_create_dir("specs", kapi_debugfs_root);
+
+ /* Create a file for each API spec */
+ for (spec = __start_kapi_specs; spec < __stop_kapi_specs; spec++) {
+ debugfs_create_file(spec->name, 0444, spec_dir, spec, &kapi_spec_fops);
+ }
+
+ pr_info("Kernel API debugfs interface initialized\n");
+ return 0;
+}
+
+/* Initialize as part of kernel, not as a module */
+fs_initcall(kapi_debugfs_init);
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH 4/9] tools/kapi: Add kernel API specification extraction tool
2026-03-13 15:09 [PATCH 0/9] Kernel API Specification Framework Sasha Levin
` (2 preceding siblings ...)
2026-03-13 15:09 ` [PATCH 3/9] kernel/api: add debugfs interface for kernel " Sasha Levin
@ 2026-03-13 15:09 ` Sasha Levin
2026-03-13 15:09 ` [PATCH 5/9] kernel/api: add API specification for sys_open Sasha Levin
` (5 subsequent siblings)
9 siblings, 0 replies; 37+ messages in thread
From: Sasha Levin @ 2026-03-13 15:09 UTC (permalink / raw)
To: linux-api, linux-kernel
Cc: linux-doc, linux-fsdevel, linux-kbuild, linux-kselftest,
workflows, tools, x86, Thomas Gleixner, Paul E. McKenney,
Greg Kroah-Hartman, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann, Sasha Levin
The kapi tool extracts and displays kernel API specifications.
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
Documentation/dev-tools/kernel-api-spec.rst | 195 ++-
tools/kapi/.gitignore | 4 +
tools/kapi/Cargo.toml | 19 +
| 581 ++++++
| 1554 +++++++++++++++++
| 463 +++++
| 405 +++++
| 505 ++++++
| 112 ++
| 842 +++++++++
tools/kapi/src/formatter/json.rs | 727 ++++++++
tools/kapi/src/formatter/mod.rs | 140 ++
tools/kapi/src/formatter/plain.rs | 708 ++++++++
tools/kapi/src/formatter/rst.rs | 852 +++++++++
tools/kapi/src/main.rs | 119 ++
15 files changed, 7215 insertions(+), 11 deletions(-)
create mode 100644 tools/kapi/.gitignore
create mode 100644 tools/kapi/Cargo.toml
create mode 100644 tools/kapi/src/extractor/debugfs.rs
create mode 100644 tools/kapi/src/extractor/kerneldoc_parser.rs
create mode 100644 tools/kapi/src/extractor/mod.rs
create mode 100644 tools/kapi/src/extractor/source_parser.rs
create mode 100644 tools/kapi/src/extractor/vmlinux/binary_utils.rs
create mode 100644 tools/kapi/src/extractor/vmlinux/magic_finder.rs
create mode 100644 tools/kapi/src/extractor/vmlinux/mod.rs
create mode 100644 tools/kapi/src/formatter/json.rs
create mode 100644 tools/kapi/src/formatter/mod.rs
create mode 100644 tools/kapi/src/formatter/plain.rs
create mode 100644 tools/kapi/src/formatter/rst.rs
create mode 100644 tools/kapi/src/main.rs
diff --git a/Documentation/dev-tools/kernel-api-spec.rst b/Documentation/dev-tools/kernel-api-spec.rst
index 6ec6e65a1902a..9103ba1d580cf 100644
--- a/Documentation/dev-tools/kernel-api-spec.rst
+++ b/Documentation/dev-tools/kernel-api-spec.rst
@@ -31,7 +31,9 @@ The framework aims to:
common programming errors during development and testing.
3. **Support Tooling**: Export API specifications in machine-readable formats for
- use by static analyzers, documentation generators, and development tools.
+ use by static analyzers, documentation generators, and development tools. The
+ ``kapi`` tool (see `The kapi Tool`_) provides comprehensive extraction and
+ formatting capabilities.
4. **Enhance Debugging**: Provide detailed API information at runtime through debugfs
for debugging and introspection.
@@ -65,6 +67,13 @@ The framework consists of several key components:
- Type-safe parameter specifications
- Context and constraint definitions
+5. **kapi Tool** (``tools/kapi/``)
+
+ - Userspace utility for extracting specifications
+ - Multiple input sources (source, binary, debugfs)
+ - Multiple output formats (plain, JSON, RST)
+ - Testing and validation utilities
+
Data Model
----------
@@ -289,8 +298,177 @@ Documentation Generation
------------------------
The framework exports specifications via debugfs that can be used
-to generate documentation. Tools for automatic documentation generation
-from specifications are planned for future development.
+to generate documentation. The ``kapi`` tool provides comprehensive
+extraction and formatting capabilities for kernel API specifications.
+
+The kapi Tool
+=============
+
+Overview
+--------
+
+The ``kapi`` tool is a userspace utility that extracts and displays kernel API
+specifications from multiple sources. It provides a unified interface to access
+API documentation whether from compiled kernels, source code, or runtime systems.
+
+Installation
+------------
+
+Build the tool from the kernel source tree::
+
+ $ cd tools/kapi
+ $ cargo build --release
+
+ # Optional: Install system-wide
+ $ cargo install --path .
+
+The tool requires Rust and Cargo to build. The binary will be available at
+``tools/kapi/target/release/kapi``.
+
+Command-Line Usage
+------------------
+
+Basic syntax::
+
+ kapi [OPTIONS] [API_NAME]
+
+Options:
+
+- ``--vmlinux <PATH>``: Extract from compiled kernel binary
+- ``--source <PATH>``: Extract from kernel source code
+- ``--debugfs <PATH>``: Extract from debugfs (default: /sys/kernel/debug)
+- ``-f, --format <FORMAT>``: Output format (plain, json, rst)
+- ``-h, --help``: Display help information
+- ``-V, --version``: Display version information
+
+Input Modes
+-----------
+
+**1. Source Code Mode**
+
+Extract specifications directly from kernel source::
+
+ # Scan entire kernel source tree
+ $ kapi --source /path/to/linux
+
+ # Extract from specific file
+ $ kapi --source kernel/sched/core.c
+
+ # Get details for specific API
+ $ kapi --source /path/to/linux sys_sched_yield
+
+**2. Vmlinux Mode**
+
+Extract from compiled kernel with debug symbols::
+
+ # List all APIs in vmlinux
+ $ kapi --vmlinux /boot/vmlinux-5.15.0
+
+ # Get specific syscall details
+ $ kapi --vmlinux ./vmlinux sys_read
+
+**3. Debugfs Mode**
+
+Extract from running kernel via debugfs::
+
+ # Use default debugfs path
+ $ kapi
+
+ # Use custom debugfs mount
+ $ kapi --debugfs /mnt/debugfs
+
+ # Get specific API from running kernel
+ $ kapi sys_write
+
+Output Formats
+--------------
+
+**Plain Text Format** (default)::
+
+ $ kapi sys_read
+
+ Detailed information for sys_read:
+ ==================================
+ Description: Read from a file descriptor
+
+ Detailed Description:
+ Reads up to count bytes from file descriptor fd into the buffer starting at buf.
+
+ Execution Context:
+ - KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE
+
+ Parameters (3):
+
+ Available since: 1.0
+
+**JSON Format**::
+
+ $ kapi --format json sys_read
+ {
+ "api_details": {
+ "name": "sys_read",
+ "description": "Read from a file descriptor",
+ "long_description": "Reads up to count bytes...",
+ "context_flags": ["KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE"],
+ "since_version": "1.0"
+ }
+ }
+
+**ReStructuredText Format**::
+
+ $ kapi --format rst sys_read
+
+ sys_read
+ ========
+
+ **Read from a file descriptor**
+
+ Reads up to count bytes from file descriptor fd into the buffer...
+
+Usage Examples
+--------------
+
+**Generate complete API documentation**::
+
+ # Export all kernel APIs to JSON
+ $ kapi --source /path/to/linux --format json > kernel-apis.json
+
+ # Generate RST documentation for all syscalls
+ $ kapi --vmlinux ./vmlinux --format rst > syscalls.rst
+
+ # List APIs from specific subsystem
+ $ kapi --source drivers/gpu/drm/
+
+**Integration with other tools**::
+
+ # Find all APIs that can sleep
+ $ kapi --format json | jq '.apis[] | select(.context_flags[] | contains("SLEEPABLE"))'
+
+ # Generate markdown documentation
+ $ kapi --format rst sys_mmap | pandoc -f rst -t markdown
+
+**Debugging and analysis**::
+
+ # Compare API between kernel versions
+ $ diff <(kapi --vmlinux vmlinux-5.10) <(kapi --vmlinux vmlinux-5.15)
+
+ # Check if specific API exists
+ $ kapi --source . my_custom_api || echo "API not found"
+
+Implementation Details
+----------------------
+
+The tool extracts API specifications from three sources:
+
+1. **Source Code**: Parses KAPI specification macros using regular expressions
+2. **Vmlinux**: Reads the ``.kapi_specs`` ELF section from compiled kernels
+3. **Debugfs**: Reads from ``/sys/kernel/debug/kapi/`` filesystem interface
+
+The tool supports all KAPI specification types:
+
+- System calls (``DEFINE_KERNEL_API_SPEC`` or kerneldoc annotations)
+- IOCTLs (kerneldoc annotations with ioctl-specific tags)
+- Kernel functions (kerneldoc annotations with KAPI tags)
IDE Integration
---------------
@@ -302,15 +480,10 @@ Modern IDEs can use the JSON export for:
- Context validation
- Error code documentation
-Testing Framework
------------------
-
-The framework includes test helpers::
+Example IDE integration::
- #ifdef CONFIG_KAPI_TESTING
- /* Verify API behaves according to specification */
- kapi_test_api("kmalloc", test_cases);
- #endif
+ # Generate IDE completion data
+ $ kapi --format json > .vscode/kernel-apis.json
Best Practices
==============
diff --git a/tools/kapi/.gitignore b/tools/kapi/.gitignore
new file mode 100644
index 0000000000000..1390bfc12686c
--- /dev/null
+++ b/tools/kapi/.gitignore
@@ -0,0 +1,4 @@
+# Rust build artifacts
+/target/
+**/*.rs.bk
+
diff --git a/tools/kapi/Cargo.toml b/tools/kapi/Cargo.toml
new file mode 100644
index 0000000000000..7df8a159689f4
--- /dev/null
+++ b/tools/kapi/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "kapi"
+version = "0.1.0"
+edition = "2021"
+authors = ["Sasha Levin <sashal@kernel.org>"]
+description = "Tool for extracting and displaying kernel API specifications"
+license = "GPL-2.0"
+
+[dependencies]
+goblin = "0.10"
+clap = { version = "4.4", features = ["derive"] }
+anyhow = "1.0"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+regex = "1.10"
+walkdir = "2.4"
+
+[dev-dependencies]
+tempfile = "3.8"
--git a/tools/kapi/src/extractor/debugfs.rs b/tools/kapi/src/extractor/debugfs.rs
new file mode 100644
index 0000000000000..9a4f4c1599b78
--- /dev/null
+++ b/tools/kapi/src/extractor/debugfs.rs
@@ -0,0 +1,581 @@
+use crate::formatter::OutputFormatter;
+use anyhow::{Context, Result, bail};
+use serde::Deserialize;
+use std::fs;
+use std::io::Write;
+use std::path::PathBuf;
+
+use super::{ApiExtractor, ApiSpec, CapabilitySpec, ErrorSpec, LockSpec, ParamSpec, ReturnSpec, display_api_spec};
+
+#[derive(Deserialize)]
+struct KernelApiJson {
+ name: String,
+ api_type: Option<String>,
+ version: Option<u32>,
+ description: Option<String>,
+ long_description: Option<String>,
+ context_flags: Option<u32>,
+ since_version: Option<String>,
+ examples: Option<String>,
+ notes: Option<String>,
+ capabilities: Option<Vec<KernelCapabilityJson>>,
+ #[serde(default)]
+ parameters: Option<Vec<KernelParamJson>>,
+ #[serde(default)]
+ errors: Option<Vec<KernelErrorJson>>,
+ #[serde(default)]
+ return_spec: Option<KernelReturnJson>,
+ #[serde(default)]
+ locks: Option<Vec<KernelLockJson>>,
+}
+
+#[derive(Deserialize)]
+struct KernelParamJson {
+ name: String,
+ #[serde(rename = "type")]
+ type_name: Option<String>,
+ description: Option<String>,
+ #[serde(default)]
+ flags: u32,
+ #[serde(default)]
+ param_type: u32,
+}
+
+#[derive(Deserialize)]
+struct KernelErrorJson {
+ error_code: i32,
+ name: Option<String>,
+ condition: Option<String>,
+ description: Option<String>,
+}
+
+#[derive(Deserialize)]
+struct KernelReturnJson {
+ #[serde(rename = "type")]
+ type_name: Option<String>,
+ description: Option<String>,
+ #[serde(default)]
+ return_type: u32,
+ #[serde(default)]
+ check_type: u32,
+ success_value: Option<i64>,
+}
+
+#[derive(Deserialize)]
+struct KernelLockJson {
+ name: String,
+ #[serde(default)]
+ lock_type: u32,
+ #[serde(default)]
+ scope: u32,
+ description: Option<String>,
+}
+
+#[derive(Deserialize)]
+struct KernelCapabilityJson {
+ capability: i32,
+ name: String,
+ action: String,
+ allows: String,
+ without_cap: String,
+ check_condition: Option<String>,
+ priority: Option<u8>,
+ alternatives: Option<Vec<i32>>,
+}
+
+/// Extractor for kernel API specifications from debugfs
+pub struct DebugfsExtractor {
+ debugfs_path: PathBuf,
+}
+
+impl DebugfsExtractor {
+ /// Create a new debugfs extractor with the specified debugfs path
+ pub fn new(debugfs_path: Option<String>) -> Result<Self> {
+ let path = match debugfs_path {
+ Some(p) => PathBuf::from(p),
+ None => PathBuf::from("/sys/kernel/debug"),
+ };
+
+ // Check if the debugfs path exists
+ if !path.exists() {
+ bail!("Debugfs path does not exist: {}", path.display());
+ }
+
+ // Check if kapi directory exists
+ let kapi_path = path.join("kapi");
+ if !kapi_path.exists() {
+ bail!(
+ "Kernel API debugfs interface not found at: {}",
+ kapi_path.display()
+ );
+ }
+
+ Ok(Self { debugfs_path: path })
+ }
+
+ /// Parse the list file to get all available API names
+ fn parse_list_file(&self) -> Result<Vec<String>> {
+ let list_path = self.debugfs_path.join("kapi/list");
+ let content = fs::read_to_string(&list_path)
+ .with_context(|| format!("Failed to read {}", list_path.display()))?;
+
+ let mut apis = Vec::new();
+ let mut in_list = false;
+
+ for line in content.lines() {
+ if line.contains("===") {
+ in_list = true;
+ continue;
+ }
+
+ if in_list && line.starts_with("Total:") {
+ break;
+ }
+
+ if in_list && !line.trim().is_empty() {
+ // Extract API name from lines like "sys_read - Read from a file descriptor"
+ if let Some(name) = line.split(" - ").next() {
+ apis.push(name.trim().to_string());
+ }
+ }
+ }
+
+ Ok(apis)
+ }
+
+ /// Try to parse JSON content, convert context flags from u32 to string representations
+ fn parse_context_flags(flags: u32) -> Vec<String> {
+ let mut result = Vec::new();
+
+ // These values should match KAPI_CTX_* flags from kernel
+ if flags & (1 << 0) != 0 {
+ result.push("PROCESS".to_string());
+ }
+ if flags & (1 << 1) != 0 {
+ result.push("SOFTIRQ".to_string());
+ }
+ if flags & (1 << 2) != 0 {
+ result.push("HARDIRQ".to_string());
+ }
+ if flags & (1 << 3) != 0 {
+ result.push("NMI".to_string());
+ }
+ if flags & (1 << 4) != 0 {
+ result.push("ATOMIC".to_string());
+ }
+ if flags & (1 << 5) != 0 {
+ result.push("SLEEPABLE".to_string());
+ }
+ if flags & (1 << 6) != 0 {
+ result.push("PREEMPT_DISABLED".to_string());
+ }
+ if flags & (1 << 7) != 0 {
+ result.push("IRQ_DISABLED".to_string());
+ }
+
+ result
+ }
+
+ /// Convert capability action from kernel representation
+ fn parse_capability_action(action: &str) -> String {
+ match action {
+ "bypass_check" => "Bypasses check".to_string(),
+ "increase_limit" => "Increases limit".to_string(),
+ "override_restriction" => "Overrides restriction".to_string(),
+ "grant_permission" => "Grants permission".to_string(),
+ "modify_behavior" => "Modifies behavior".to_string(),
+ "access_resource" => "Allows resource access".to_string(),
+ "perform_operation" => "Allows operation".to_string(),
+ _ => action.to_string(),
+ }
+ }
+
+ /// Try to parse as JSON first
+ fn try_parse_json(&self, content: &str) -> Option<ApiSpec> {
+ let json_data: KernelApiJson = serde_json::from_str(content).ok()?;
+
+ let mut spec = ApiSpec {
+ name: json_data.name,
+ api_type: json_data.api_type.unwrap_or_else(|| "unknown".to_string()),
+ description: json_data.description,
+ long_description: json_data.long_description,
+ version: json_data.version.map(|v| v.to_string()),
+ context_flags: json_data
+ .context_flags
+ .map_or_else(Vec::new, Self::parse_context_flags),
+ param_count: None,
+ error_count: None,
+ examples: json_data.examples,
+ notes: json_data.notes,
+ since_version: json_data.since_version,
+ subsystem: None, // Not in current JSON format
+ sysfs_path: None, // Not in current JSON format
+ permissions: None, // Not in current JSON format
+ socket_state: None,
+ protocol_behaviors: vec![],
+ addr_families: vec![],
+ buffer_spec: None,
+ async_spec: None,
+ net_data_transfer: None,
+ capabilities: vec![],
+ parameters: vec![],
+ return_spec: None,
+ errors: vec![],
+ signals: vec![],
+ signal_masks: vec![],
+ side_effects: vec![],
+ state_transitions: vec![],
+ constraints: vec![],
+ locks: vec![],
+ struct_specs: vec![],
+ };
+
+ // Convert capabilities
+ if let Some(caps) = json_data.capabilities {
+ for cap in caps {
+ spec.capabilities.push(CapabilitySpec {
+ capability: cap.capability,
+ name: cap.name,
+ action: Self::parse_capability_action(&cap.action),
+ allows: cap.allows,
+ without_cap: cap.without_cap,
+ check_condition: cap.check_condition,
+ priority: cap.priority,
+ alternatives: cap.alternatives.unwrap_or_default(),
+ });
+ }
+ }
+
+ // Convert parameters
+ if let Some(params) = json_data.parameters {
+ for (i, p) in params.into_iter().enumerate() {
+ spec.parameters.push(ParamSpec {
+ index: i as u32,
+ name: p.name,
+ type_name: p.type_name.unwrap_or_default(),
+ description: p.description.unwrap_or_default(),
+ flags: p.flags,
+ param_type: p.param_type,
+ constraint_type: 0,
+ constraint: None,
+ min_value: None,
+ max_value: None,
+ valid_mask: None,
+ enum_values: vec![],
+ size: None,
+ alignment: None,
+ });
+ }
+ spec.param_count = Some(spec.parameters.len() as u32);
+ }
+
+ // Convert errors
+ if let Some(errors) = json_data.errors {
+ for e in errors {
+ spec.errors.push(ErrorSpec {
+ error_code: e.error_code,
+ name: e.name.unwrap_or_default(),
+ condition: e.condition.unwrap_or_default(),
+ description: e.description.unwrap_or_default(),
+ });
+ }
+ spec.error_count = Some(spec.errors.len() as u32);
+ }
+
+ // Convert return spec
+ if let Some(ret) = json_data.return_spec {
+ spec.return_spec = Some(ReturnSpec {
+ type_name: ret.type_name.unwrap_or_default(),
+ description: ret.description.unwrap_or_default(),
+ return_type: ret.return_type,
+ check_type: ret.check_type,
+ success_value: ret.success_value,
+ success_min: None,
+ success_max: None,
+ error_values: vec![],
+ });
+ }
+
+ // Convert locks
+ if let Some(locks) = json_data.locks {
+ for l in locks {
+ spec.locks.push(LockSpec {
+ lock_name: l.name,
+ lock_type: l.lock_type,
+ scope: l.scope,
+ description: l.description.unwrap_or_default(),
+ });
+ }
+ }
+
+ Some(spec)
+ }
+
+ /// Parse a single API specification file
+ fn parse_spec_file(&self, api_name: &str) -> Result<ApiSpec> {
+ let spec_path = self.debugfs_path.join(format!("kapi/specs/{}", api_name));
+ let content = fs::read_to_string(&spec_path)
+ .with_context(|| format!("Failed to read {}", spec_path.display()))?;
+
+ // Try JSON parsing first
+ if let Some(spec) = self.try_parse_json(&content) {
+ return Ok(spec);
+ }
+
+ // Fall back to plain text parsing
+ let mut spec = ApiSpec {
+ name: api_name.to_string(),
+ api_type: "unknown".to_string(),
+ description: None,
+ long_description: None,
+ version: None,
+ context_flags: Vec::new(),
+ param_count: None,
+ error_count: None,
+ examples: None,
+ notes: None,
+ since_version: None,
+ subsystem: None,
+ sysfs_path: None,
+ permissions: None,
+ socket_state: None,
+ protocol_behaviors: vec![],
+ addr_families: vec![],
+ buffer_spec: None,
+ async_spec: None,
+ net_data_transfer: None,
+ capabilities: vec![],
+ parameters: vec![],
+ return_spec: None,
+ errors: vec![],
+ signals: vec![],
+ signal_masks: vec![],
+ side_effects: vec![],
+ state_transitions: vec![],
+ constraints: vec![],
+ locks: vec![],
+ struct_specs: vec![],
+ };
+
+ // Parse the content
+ let mut collecting_multiline = false;
+ let mut multiline_buffer = String::new();
+ let mut multiline_field = "";
+ let mut parsing_capability = false;
+ let mut current_capability: Option<CapabilitySpec> = None;
+
+ for line in content.lines() {
+ // Handle capability sections
+ if line.starts_with("Capabilities (") {
+ continue; // Skip the header
+ }
+ if line.starts_with(" ") && line.contains(" (") && line.ends_with("):") {
+ // Start of a capability entry like " CAP_IPC_LOCK (14):"
+ if let Some(cap) = current_capability.take() {
+ spec.capabilities.push(cap);
+ }
+
+ let parts: Vec<&str> = line.trim().split(" (").collect();
+ if parts.len() == 2 {
+ let cap_name = parts[0].to_string();
+ let cap_id = parts[1].trim_end_matches("):").parse().unwrap_or(0);
+ current_capability = Some(CapabilitySpec {
+ capability: cap_id,
+ name: cap_name,
+ action: String::new(),
+ allows: String::new(),
+ without_cap: String::new(),
+ check_condition: None,
+ priority: None,
+ alternatives: Vec::new(),
+ });
+ parsing_capability = true;
+ }
+ continue;
+ }
+ if parsing_capability && line.starts_with(" ") {
+ // Parse capability fields
+ if let Some(ref mut cap) = current_capability {
+ if let Some(action) = line.strip_prefix(" Action: ") {
+ cap.action = action.to_string();
+ } else if let Some(allows) = line.strip_prefix(" Allows: ") {
+ cap.allows = allows.to_string();
+ } else if let Some(without) = line.strip_prefix(" Without: ") {
+ cap.without_cap = without.to_string();
+ } else if let Some(cond) = line.strip_prefix(" Condition: ") {
+ cap.check_condition = Some(cond.to_string());
+ } else if let Some(prio) = line.strip_prefix(" Priority: ") {
+ cap.priority = prio.parse().ok();
+ } else if let Some(alts) = line.strip_prefix(" Alternatives: ") {
+ cap.alternatives =
+ alts.split(", ").filter_map(|s| s.parse().ok()).collect();
+ }
+ }
+ continue;
+ }
+ if parsing_capability && !line.starts_with(" ") {
+ // End of capabilities section
+ if let Some(cap) = current_capability.take() {
+ spec.capabilities.push(cap);
+ }
+ parsing_capability = false;
+ }
+
+ // Handle section headers
+ if line.starts_with("Parameters (") {
+ if let Some(count_str) = line
+ .strip_prefix("Parameters (")
+ .and_then(|s| s.strip_suffix("):"))
+ {
+ spec.param_count = count_str.parse().ok();
+ }
+ continue;
+ } else if line.starts_with("Errors (") {
+ if let Some(count_str) = line
+ .strip_prefix("Errors (")
+ .and_then(|s| s.strip_suffix("):"))
+ {
+ spec.error_count = count_str.parse().ok();
+ }
+ continue;
+ } else if line.starts_with("Examples:") {
+ collecting_multiline = true;
+ multiline_field = "examples";
+ multiline_buffer.clear();
+ continue;
+ } else if line.starts_with("Notes:") {
+ collecting_multiline = true;
+ multiline_field = "notes";
+ multiline_buffer.clear();
+ continue;
+ }
+
+ // Handle multiline sections
+ if collecting_multiline {
+ // Terminate multiline on known field patterns or double blank line
+ let is_field = line.starts_with("Description: ")
+ || line.starts_with("Long description: ")
+ || line.starts_with("Version: ")
+ || line.starts_with("Since: ")
+ || line.starts_with("Context flags: ")
+ || line.starts_with("Subsystem: ")
+ || line.starts_with("Sysfs Path: ")
+ || line.starts_with("Permissions: ")
+ || line.starts_with("Parameters (")
+ || line.starts_with("Errors (")
+ || line.starts_with("Capabilities (");
+ if is_field || (line.trim().is_empty() && multiline_buffer.ends_with("\n\n")) {
+ collecting_multiline = false;
+ match multiline_field {
+ "examples" => spec.examples = Some(multiline_buffer.trim().to_string()),
+ "notes" => spec.notes = Some(multiline_buffer.trim().to_string()),
+ _ => {}
+ }
+ multiline_buffer.clear();
+ if !is_field {
+ continue;
+ }
+ // Fall through to parse this line as a field
+ } else {
+ if !multiline_buffer.is_empty() {
+ multiline_buffer.push('\n');
+ }
+ multiline_buffer.push_str(line);
+ continue;
+ }
+ }
+
+ // Parse regular fields
+ if let Some(desc) = line.strip_prefix("Description: ") {
+ spec.description = Some(desc.to_string());
+ } else if let Some(long_desc) = line.strip_prefix("Long description: ") {
+ spec.long_description = Some(long_desc.to_string());
+ } else if let Some(version) = line.strip_prefix("Version: ") {
+ spec.version = Some(version.to_string());
+ } else if let Some(since) = line.strip_prefix("Since: ") {
+ spec.since_version = Some(since.to_string());
+ } else if let Some(flags) = line.strip_prefix("Context flags: ") {
+ spec.context_flags = flags.split_whitespace().map(str::to_string).collect();
+ } else if let Some(subsys) = line.strip_prefix("Subsystem: ") {
+ spec.subsystem = Some(subsys.to_string());
+ } else if let Some(path) = line.strip_prefix("Sysfs Path: ") {
+ spec.sysfs_path = Some(path.to_string());
+ } else if let Some(perms) = line.strip_prefix("Permissions: ") {
+ spec.permissions = Some(perms.to_string());
+ }
+ }
+
+ // Flush any remaining multiline buffer
+ if collecting_multiline {
+ match multiline_field {
+ "examples" => spec.examples = Some(multiline_buffer.trim().to_string()),
+ "notes" => spec.notes = Some(multiline_buffer.trim().to_string()),
+ _ => {}
+ }
+ }
+
+ // Handle any remaining capability
+ if let Some(cap) = current_capability.take() {
+ spec.capabilities.push(cap);
+ }
+
+ // Determine API type based on name
+ if api_name.starts_with("sys_") {
+ spec.api_type = "syscall".to_string();
+ } else if api_name.contains("_ioctl") || api_name.starts_with("ioctl_") {
+ spec.api_type = "ioctl".to_string();
+ } else if api_name.contains("sysfs")
+ || api_name.ends_with("_show")
+ || api_name.ends_with("_store")
+ {
+ spec.api_type = "sysfs".to_string();
+ } else {
+ spec.api_type = "function".to_string();
+ }
+
+ Ok(spec)
+ }
+}
+
+impl ApiExtractor for DebugfsExtractor {
+ fn extract_all(&self) -> Result<Vec<ApiSpec>> {
+ let api_names = self.parse_list_file()?;
+ let mut specs = Vec::new();
+
+ for name in api_names {
+ match self.parse_spec_file(&name) {
+ Ok(spec) => specs.push(spec),
+ Err(e) => {
+ eprintln!("Warning: failed to parse API spec '{}': {}", name, e);
+ }
+ }
+ }
+
+ Ok(specs)
+ }
+
+ fn extract_by_name(&self, name: &str) -> Result<Option<ApiSpec>> {
+ let api_names = self.parse_list_file()?;
+
+ if api_names.contains(&name.to_string()) {
+ Ok(Some(self.parse_spec_file(name)?))
+ } else {
+ Ok(None)
+ }
+ }
+
+ fn display_api_details(
+ &self,
+ api_name: &str,
+ formatter: &mut dyn OutputFormatter,
+ writer: &mut dyn Write,
+ ) -> Result<()> {
+ if let Some(spec) = self.extract_by_name(api_name)? {
+ display_api_spec(&spec, formatter, writer)?;
+ } else {
+ writeln!(writer, "API '{api_name}' not found in debugfs")?;
+ }
+
+ Ok(())
+ }
+}
--git a/tools/kapi/src/extractor/kerneldoc_parser.rs b/tools/kapi/src/extractor/kerneldoc_parser.rs
new file mode 100644
index 0000000000000..767e4b2bf26b9
--- /dev/null
+++ b/tools/kapi/src/extractor/kerneldoc_parser.rs
@@ -0,0 +1,1554 @@
+use super::{
+ ApiSpec, CapabilitySpec, ConstraintSpec, ErrorSpec, LockSpec, ParamSpec,
+ ReturnSpec, SideEffectSpec, SignalSpec, StateTransitionSpec, StructSpec,
+ StructFieldSpec,
+};
+use anyhow::Result;
+use std::collections::HashMap;
+
+/// Real kerneldoc parser that extracts KAPI annotations
+pub struct KerneldocParserImpl;
+
+/// What block are we currently inside?
+#[derive(Debug, Clone, PartialEq)]
+enum BlockContext {
+ None,
+ Param(String), // param: <name>
+ Error(String), // error: <name>
+ Signal, // signal: <name>
+ Capability, // capability: <name>
+ SideEffect, // side-effect: <type>
+ StateTransition, // state-trans: ...
+ Constraint, // constraint: <name>
+ Lock, // lock: <name>
+ Return, // return:
+}
+
+impl KerneldocParserImpl {
+ pub fn new() -> Self {
+ KerneldocParserImpl
+ }
+
+ pub fn parse_kerneldoc(
+ &self,
+ doc: &str,
+ name: &str,
+ api_type: &str,
+ signature: Option<&str>,
+ ) -> Result<ApiSpec> {
+ let mut spec = ApiSpec {
+ name: name.to_string(),
+ api_type: api_type.to_string(),
+ ..Default::default()
+ };
+
+ let lines: Vec<&str> = doc.lines().collect();
+
+ // Extract main description from function name line
+ if let Some(first_line) = lines.first() {
+ if let Some((_, desc)) = first_line.split_once(" - ") {
+ spec.description = Some(desc.trim().to_string());
+ }
+ }
+
+ // Extract type names from SYSCALL_DEFINE signature
+ let type_map = if let Some(sig) = signature {
+ self.extract_types_from_signature(sig)
+ } else {
+ HashMap::new()
+ };
+
+ // Keep track of parameters we've seen (from @param lines)
+ let mut param_map: HashMap<String, ParamSpec> = HashMap::new();
+ let mut struct_fields: Vec<StructFieldSpec> = Vec::new();
+
+ // Current block being parsed
+ let mut block = BlockContext::None;
+
+ // Temporary storage for current block items
+ let mut current_lock: Option<LockSpec> = None;
+ let mut current_signal: Option<SignalSpec> = None;
+ let mut current_capability: Option<CapabilitySpec> = None;
+ let mut current_side_effect: Option<SideEffectSpec> = None;
+ let mut current_constraint: Option<ConstraintSpec> = None;
+ let mut current_error: Option<ErrorSpec> = None;
+ let mut current_return: Option<ReturnSpec> = None;
+
+ let mut i = 0;
+
+ while i < lines.len() {
+ let line = lines[i];
+ let trimmed = line.trim();
+
+ // Skip empty lines
+ if trimmed.is_empty() {
+ i += 1;
+ continue;
+ }
+
+ // Check if this is an indented continuation line (part of current block)
+ let is_indented = line.starts_with(" ") || line.starts_with('\t');
+
+ // If indented and we're in a block, parse as block attribute
+ if is_indented && block != BlockContext::None {
+ self.parse_block_attribute(trimmed, &block, &mut param_map,
+ &mut current_error, &mut current_signal,
+ &mut current_capability, &mut current_side_effect,
+ &mut current_constraint, &mut current_lock,
+ &mut current_return);
+ i += 1;
+ continue;
+ }
+
+ // Not indented or not in block — flush current block if any
+ self.flush_block(&mut block, &mut spec, &mut current_error,
+ &mut current_signal, &mut current_capability,
+ &mut current_side_effect, &mut current_constraint,
+ &mut current_lock, &mut current_return);
+
+ // Parse top-level annotations
+ if let Some(rest) = trimmed.strip_prefix("@") {
+ // @param: description — standard kerneldoc parameter
+ if let Some((param_name, desc)) = rest.split_once(':') {
+ let param_name = param_name.trim();
+ let desc = desc.trim();
+ if !param_name.contains('-') {
+ let idx = param_map.len() as u32;
+ let type_name = type_map.get(param_name)
+ .cloned()
+ .unwrap_or_default();
+ param_map.insert(param_name.to_string(), ParamSpec {
+ index: idx,
+ name: param_name.to_string(),
+ type_name,
+ description: desc.to_string(),
+ flags: 0,
+ param_type: 0,
+ constraint_type: 0,
+ constraint: None,
+ min_value: None,
+ max_value: None,
+ valid_mask: None,
+ enum_values: vec![],
+ size: None,
+ alignment: None,
+ });
+ }
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("long-desc:") {
+ let (val, next_i) = self.collect_multiline_value(&lines, i, rest);
+ spec.long_description = Some(val);
+ i = next_i;
+ continue;
+ } else if let Some(rest) = trimmed.strip_prefix("context-flags:") {
+ spec.context_flags = self.parse_context_flags(rest.trim());
+ } else if let Some(rest) = trimmed.strip_prefix("param-count:") {
+ spec.param_count = rest.trim().parse().ok();
+ }
+ // Flat param-* annotations (alternative format)
+ else if let Some(rest) = trimmed.strip_prefix("param-type:") {
+ let parts: Vec<&str> = rest.split(',').map(|s| s.trim()).collect();
+ if parts.len() >= 2 {
+ if let Some(param) = param_map.get_mut(parts[0]) {
+ param.param_type = self.parse_param_type(parts[1]);
+ }
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("param-flags:") {
+ let parts: Vec<&str> = rest.split(',').map(|s| s.trim()).collect();
+ if parts.len() >= 2 {
+ if let Some(param) = param_map.get_mut(parts[0]) {
+ param.flags = self.parse_param_flags(parts[1]);
+ }
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("param-range:") {
+ let parts: Vec<&str> = rest.split(',').map(|s| s.trim()).collect();
+ if parts.len() >= 3 {
+ if let Some(param) = param_map.get_mut(parts[0]) {
+ param.min_value = parts[1].parse().ok();
+ param.max_value = parts[2].parse().ok();
+ param.constraint_type = 1; // KAPI_CONSTRAINT_RANGE
+ }
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("param-constraint:") {
+ let parts: Vec<&str> = rest.splitn(2, ',').map(|s| s.trim()).collect();
+ if parts.len() >= 2 {
+ if let Some(param) = param_map.get_mut(parts[0]) {
+ param.constraint = Some(parts[1].to_string());
+ }
+ }
+ }
+ // Block-start annotations
+ else if let Some(rest) = trimmed.strip_prefix("param:") {
+ let param_name = rest.trim().to_string();
+ block = BlockContext::Param(param_name.clone());
+ // Ensure param exists in map
+ if !param_map.contains_key(¶m_name) {
+ let idx = param_map.len() as u32;
+ let type_name = type_map.get(param_name.as_str())
+ .cloned()
+ .unwrap_or_default();
+ param_map.insert(param_name.clone(), ParamSpec {
+ index: idx,
+ name: param_name,
+ type_name,
+ description: String::new(),
+ flags: 0,
+ param_type: 0,
+ constraint_type: 0,
+ constraint: None,
+ min_value: None,
+ max_value: None,
+ valid_mask: None,
+ enum_values: vec![],
+ size: None,
+ alignment: None,
+ });
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("error:") {
+ // error: NAME, condition
+ let parts: Vec<&str> = rest.splitn(2, ',').map(|s| s.trim()).collect();
+ if !parts.is_empty() {
+ let error_name = parts[0].to_string();
+ let condition = if parts.len() >= 2 {
+ parts[1].to_string()
+ } else {
+ String::new()
+ };
+ let error_code = self.error_name_to_code(&error_name);
+ current_error = Some(ErrorSpec {
+ error_code,
+ name: error_name.clone(),
+ condition,
+ description: String::new(),
+ });
+ block = BlockContext::Error(error_name);
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("signal:") {
+ let signal_name = rest.trim().to_string();
+ current_signal = Some(SignalSpec {
+ signal_num: 0,
+ signal_name,
+ direction: 1,
+ action: 0,
+ target: None,
+ condition: None,
+ description: None,
+ restartable: false,
+ timing: 0,
+ priority: 0,
+ interruptible: false,
+ queue: None,
+ sa_flags: 0,
+ sa_flags_required: 0,
+ sa_flags_forbidden: 0,
+ state_required: 0,
+ state_forbidden: 0,
+ error_on_signal: None,
+ });
+ block = BlockContext::Signal;
+ } else if let Some(rest) = trimmed.strip_prefix("capability:") {
+ let parts: Vec<&str> = rest.split(',').map(|s| s.trim()).collect();
+ if !parts.is_empty() {
+ let cap_name = parts[0].to_string();
+ let cap_value = self.parse_capability_value(&cap_name);
+ // If we have 3 parts, it's flat format: capability: CAP, action, name
+ let (action, name) = if parts.len() >= 3 {
+ (parts[1].to_string(), parts[2].to_string())
+ } else {
+ (String::new(), cap_name.clone())
+ };
+ current_capability = Some(CapabilitySpec {
+ capability: cap_value,
+ name,
+ action,
+ allows: String::new(),
+ without_cap: String::new(),
+ check_condition: None,
+ priority: Some(0),
+ alternatives: vec![],
+ });
+ block = BlockContext::Capability;
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("side-effect:") {
+ // Could be flat format (comma-separated) or block start
+ let rest = rest.trim();
+ // Check if it's the flat format with commas
+ let comma_parts: Vec<&str> = rest.splitn(3, ',').map(|s| s.trim()).collect();
+ if comma_parts.len() >= 3 {
+ // Flat format: side-effect: TYPE, target, desc
+ let mut effect = SideEffectSpec {
+ effect_type: self.parse_effect_type(comma_parts[0]),
+ target: comma_parts[1].to_string(),
+ condition: None,
+ description: comma_parts[2].to_string(),
+ reversible: false,
+ };
+ if comma_parts[2].contains("reversible=yes") {
+ effect.reversible = true;
+ }
+ spec.side_effects.push(effect);
+ } else {
+ // Block format: side-effect: TYPE
+ current_side_effect = Some(SideEffectSpec {
+ effect_type: self.parse_effect_type(rest),
+ target: String::new(),
+ condition: None,
+ description: String::new(),
+ reversible: false,
+ });
+ block = BlockContext::SideEffect;
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("state-trans:") {
+ let parts: Vec<&str> = rest.split(',').map(|s| s.trim()).collect();
+ if parts.len() >= 4 {
+ spec.state_transitions.push(StateTransitionSpec {
+ object: parts[0].to_string(),
+ from_state: parts[1].to_string(),
+ to_state: parts[2].to_string(),
+ condition: None,
+ description: parts[3].to_string(),
+ });
+ }
+ block = BlockContext::StateTransition;
+ } else if let Some(rest) = trimmed.strip_prefix("constraint:") {
+ let rest = rest.trim();
+ // Could be flat format: constraint: name, desc
+ // Or block format: constraint: name
+ let parts: Vec<&str> = rest.splitn(2, ',').map(|s| s.trim()).collect();
+ if parts.len() >= 2 {
+ // Flat format
+ current_constraint = Some(ConstraintSpec {
+ name: parts[0].to_string(),
+ description: parts[1].to_string(),
+ expression: None,
+ });
+ } else {
+ // Block format
+ current_constraint = Some(ConstraintSpec {
+ name: rest.to_string(),
+ description: String::new(),
+ expression: None,
+ });
+ }
+ block = BlockContext::Constraint;
+ } else if let Some(rest) = trimmed.strip_prefix("constraint-expr:") {
+ // Flat format: constraint-expr: name, expr
+ let parts: Vec<&str> = rest.splitn(2, ',').map(|s| s.trim()).collect();
+ if parts.len() >= 2 {
+ if let Some(constraint) = spec.constraints.iter_mut().find(|c| c.name == parts[0]) {
+ constraint.expression = Some(parts[1].to_string());
+ }
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("lock:") {
+ let rest = rest.trim();
+ // Could be flat: lock: name, type
+ // Or block: lock: name
+ let parts: Vec<&str> = rest.split(',').map(|s| s.trim()).collect();
+ if parts.len() >= 2 {
+ current_lock = Some(LockSpec {
+ lock_name: parts[0].to_string(),
+ lock_type: self.parse_lock_type(parts[1]),
+ scope: super::KAPI_LOCK_INTERNAL,
+ description: String::new(),
+ });
+ } else {
+ current_lock = Some(LockSpec {
+ lock_name: rest.to_string(),
+ lock_type: 0,
+ scope: super::KAPI_LOCK_INTERNAL,
+ description: String::new(),
+ });
+ }
+ block = BlockContext::Lock;
+ }
+ // Flat signal-* attributes (alternative format)
+ else if let Some(rest) = trimmed.strip_prefix("signal-direction:") {
+ if let Some(signal) = current_signal.as_mut() {
+ signal.direction = self.parse_signal_direction(rest.trim());
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("signal-action:") {
+ if let Some(signal) = current_signal.as_mut() {
+ signal.action = self.parse_signal_action(rest.trim());
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("signal-condition:") {
+ if let Some(signal) = current_signal.as_mut() {
+ let (val, next_i) = self.collect_multiline_value(&lines, i, rest);
+ signal.condition = Some(val);
+ i = next_i;
+ continue;
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("signal-desc:") {
+ if let Some(signal) = current_signal.as_mut() {
+ let (val, next_i) = self.collect_multiline_value(&lines, i, rest);
+ signal.description = Some(val);
+ i = next_i;
+ continue;
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("signal-timing:") {
+ if let Some(signal) = current_signal.as_mut() {
+ signal.timing = self.parse_signal_timing(rest.trim());
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("signal-priority:") {
+ if let Some(signal) = current_signal.as_mut() {
+ signal.priority = rest.trim().parse().unwrap_or(0);
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("signal-interruptible:") {
+ if let Some(signal) = current_signal.as_mut() {
+ let val = rest.trim().to_lowercase();
+ signal.interruptible = !matches!(val.as_str(), "no" | "false" | "0");
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("signal-state-req:") {
+ if let Some(signal) = current_signal.as_mut() {
+ signal.state_required = self.parse_signal_state(rest.trim());
+ }
+ }
+ // Flat capability-* attributes
+ else if let Some(rest) = trimmed.strip_prefix("capability-allows:") {
+ if let Some(cap) = current_capability.as_mut() {
+ let (val, next_i) = self.collect_multiline_value(&lines, i, rest);
+ cap.allows = val;
+ i = next_i;
+ continue;
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("capability-without:") {
+ if let Some(cap) = current_capability.as_mut() {
+ let (val, next_i) = self.collect_multiline_value(&lines, i, rest);
+ cap.without_cap = val;
+ i = next_i;
+ continue;
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("capability-condition:") {
+ if let Some(cap) = current_capability.as_mut() {
+ let (val, next_i) = self.collect_multiline_value(&lines, i, rest);
+ cap.check_condition = Some(val);
+ i = next_i;
+ continue;
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("capability-priority:") {
+ if let Some(cap) = current_capability.as_mut() {
+ cap.priority = rest.trim().parse().ok();
+ }
+ }
+ // Lock flat attributes
+ else if let Some(rest) = trimmed.strip_prefix("lock-scope:") {
+ if let Some(lock) = current_lock.as_mut() {
+ lock.scope = match rest.trim() {
+ "internal" => super::KAPI_LOCK_INTERNAL,
+ "acquires" => super::KAPI_LOCK_ACQUIRES,
+ "releases" => super::KAPI_LOCK_RELEASES,
+ "caller_held" => super::KAPI_LOCK_CALLER_HELD,
+ _ => super::KAPI_LOCK_INTERNAL,
+ };
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("lock-desc:") {
+ if let Some(lock) = current_lock.as_mut() {
+ let (val, next_i) = self.collect_multiline_value(&lines, i, rest);
+ lock.description = val;
+ i = next_i;
+ continue;
+ }
+ }
+ // Struct field annotations
+ else if let Some(rest) = trimmed.strip_prefix("struct-field:") {
+ let parts: Vec<&str> = rest.split(',').map(|s| s.trim()).collect();
+ if parts.len() >= 3 {
+ struct_fields.push(StructFieldSpec {
+ name: parts[0].to_string(),
+ field_type: self.parse_field_type(parts[1]),
+ type_name: parts[1].to_string(),
+ offset: 0,
+ size: 0,
+ flags: 0,
+ constraint_type: 0,
+ min_value: 0,
+ max_value: 0,
+ valid_mask: 0,
+ description: parts[2].to_string(),
+ });
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("struct-field-range:") {
+ let parts: Vec<&str> = rest.split(',').map(|s| s.trim()).collect();
+ if parts.len() >= 3 {
+ if let Some(field) = struct_fields.iter_mut().find(|f| f.name == parts[0]) {
+ field.min_value = parts[1].parse().unwrap_or(0);
+ field.max_value = parts[2].parse().unwrap_or(0);
+ field.constraint_type = 1;
+ }
+ }
+ }
+ // Other top-level annotations
+ else if let Some(rest) = trimmed.strip_prefix("return:") {
+ let rest = rest.trim();
+ if rest.is_empty() {
+ // Block format
+ current_return = Some(ReturnSpec {
+ type_name: String::new(),
+ description: String::new(),
+ return_type: 0,
+ check_type: 0,
+ success_value: None,
+ success_min: None,
+ success_max: None,
+ error_values: vec![],
+ });
+ block = BlockContext::Return;
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("return-type:") {
+ if spec.return_spec.is_none() {
+ spec.return_spec = Some(ReturnSpec {
+ type_name: rest.trim().to_string(),
+ description: String::new(),
+ return_type: self.parse_param_type(rest.trim()),
+ check_type: 0,
+ success_value: None,
+ success_min: None,
+ success_max: None,
+ error_values: vec![],
+ });
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("return-check-type:") {
+ if let Some(ret) = spec.return_spec.as_mut() {
+ ret.check_type = self.parse_return_check_type(rest.trim());
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("return-success:") {
+ if let Some(ret) = spec.return_spec.as_mut() {
+ ret.success_value = rest.trim().parse().ok();
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("examples:") {
+ let (val, next_i) = self.collect_multiline_value(&lines, i, rest);
+ spec.examples = Some(val);
+ i = next_i;
+ continue;
+ } else if let Some(rest) = trimmed.strip_prefix("notes:") {
+ let (val, next_i) = self.collect_multiline_value(&lines, i, rest);
+ spec.notes = Some(val);
+ i = next_i;
+ continue;
+ } else if let Some(rest) = trimmed.strip_prefix("since-version:") {
+ spec.since_version = Some(rest.trim().to_string());
+ }
+
+ i += 1;
+ }
+
+ // Flush any remaining block
+ self.flush_block(&mut block, &mut spec, &mut current_error,
+ &mut current_signal, &mut current_capability,
+ &mut current_side_effect, &mut current_constraint,
+ &mut current_lock, &mut current_return);
+
+ // Convert param_map to vec preserving order
+ let mut params: Vec<ParamSpec> = param_map.into_values().collect();
+ params.sort_by_key(|p| p.index);
+ spec.parameters = params;
+
+ // Create struct spec if we have fields
+ if !struct_fields.is_empty() {
+ spec.struct_specs.push(StructSpec {
+ name: format!("struct {name}"),
+ size: 0,
+ alignment: 0,
+ field_count: struct_fields.len() as u32,
+ fields: struct_fields,
+ description: "Structure specification".to_string(),
+ });
+ }
+
+ Ok(spec)
+ }
+
+ /// Parse an indented attribute line within a block
+ fn parse_block_attribute(
+ &self,
+ trimmed: &str,
+ block: &BlockContext,
+ param_map: &mut HashMap<String, ParamSpec>,
+ current_error: &mut Option<ErrorSpec>,
+ current_signal: &mut Option<SignalSpec>,
+ current_capability: &mut Option<CapabilitySpec>,
+ current_side_effect: &mut Option<SideEffectSpec>,
+ current_constraint: &mut Option<ConstraintSpec>,
+ current_lock: &mut Option<LockSpec>,
+ current_return: &mut Option<ReturnSpec>,
+ ) {
+ match block {
+ BlockContext::Param(param_name) => {
+ if let Some(param) = param_map.get_mut(param_name) {
+ if let Some(rest) = trimmed.strip_prefix("type:") {
+ param.param_type = self.parse_param_type(rest.trim());
+ } else if let Some(rest) = trimmed.strip_prefix("flags:") {
+ param.flags = self.parse_param_flags(rest.trim());
+ } else if let Some(rest) = trimmed.strip_prefix("constraint-type:") {
+ param.constraint_type = self.parse_constraint_type(rest.trim());
+ } else if let Some(rest) = trimmed.strip_prefix("valid-mask:") {
+ // Don't try to parse symbolic mask values — leave for binary
+ let _ = rest; // valid-mask parsing needs constant resolution
+ } else if let Some(rest) = trimmed.strip_prefix("constraint:") {
+ // May be multiline — append
+ let text = rest.trim();
+ if param.constraint.is_none() {
+ param.constraint = Some(text.to_string());
+ } else if let Some(c) = param.constraint.as_mut() {
+ c.push(' ');
+ c.push_str(text);
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("range:") {
+ let parts: Vec<&str> = rest.split(',').map(|s| s.trim()).collect();
+ if parts.len() >= 2 {
+ param.min_value = parts[0].parse().ok();
+ param.max_value = parts[1].parse().ok();
+ param.constraint_type = 1; // KAPI_CONSTRAINT_RANGE
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("desc:") {
+ param.description = rest.trim().to_string();
+ } else if !trimmed.contains(':') || trimmed.starts_with(" ") {
+ // Continuation of previous attribute (e.g., multiline constraint)
+ if let Some(c) = param.constraint.as_mut() {
+ c.push(' ');
+ c.push_str(trimmed);
+ }
+ }
+ }
+ }
+ BlockContext::Error(_) => {
+ if let Some(error) = current_error.as_mut() {
+ if let Some(rest) = trimmed.strip_prefix("desc:") {
+ let text = rest.trim().to_string();
+ if error.description.is_empty() {
+ error.description = text;
+ } else {
+ error.description.push(' ');
+ error.description.push_str(&text);
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("condition:") {
+ error.condition = rest.trim().to_string();
+ } else {
+ // Continuation of description
+ if !error.description.is_empty() {
+ error.description.push(' ');
+ error.description.push_str(trimmed);
+ }
+ }
+ }
+ }
+ BlockContext::Signal => {
+ if let Some(signal) = current_signal.as_mut() {
+ if let Some(rest) = trimmed.strip_prefix("direction:") {
+ signal.direction = self.parse_signal_direction(rest.trim());
+ } else if let Some(rest) = trimmed.strip_prefix("action:") {
+ signal.action = self.parse_signal_action(rest.trim());
+ } else if let Some(rest) = trimmed.strip_prefix("condition:") {
+ signal.condition = Some(rest.trim().to_string());
+ } else if let Some(rest) = trimmed.strip_prefix("desc:") {
+ let text = rest.trim().to_string();
+ if signal.description.is_none() {
+ signal.description = Some(text);
+ } else if let Some(d) = signal.description.as_mut() {
+ d.push(' ');
+ d.push_str(&text);
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("error:") {
+ let code_str = rest.trim().trim_start_matches('-');
+ if let Ok(code) = code_str.parse::<i32>() {
+ signal.error_on_signal = Some(code);
+ } else {
+ signal.error_on_signal = Some(self.error_name_to_code(rest.trim().trim_start_matches('-')));
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("timing:") {
+ signal.timing = self.parse_signal_timing(rest.trim());
+ } else if let Some(rest) = trimmed.strip_prefix("restartable:") {
+ let val = rest.trim().to_lowercase();
+ signal.restartable = matches!(val.as_str(), "yes" | "true" | "1");
+ } else if let Some(rest) = trimmed.strip_prefix("interruptible:") {
+ let val = rest.trim().to_lowercase();
+ signal.interruptible = matches!(val.as_str(), "yes" | "true" | "1");
+ } else if let Some(rest) = trimmed.strip_prefix("priority:") {
+ signal.priority = rest.trim().parse().unwrap_or(0);
+ } else {
+ // Continuation of description
+ if let Some(d) = signal.description.as_mut() {
+ d.push(' ');
+ d.push_str(trimmed);
+ }
+ }
+ }
+ }
+ BlockContext::Capability => {
+ if let Some(cap) = current_capability.as_mut() {
+ if let Some(rest) = trimmed.strip_prefix("type:") {
+ cap.action = rest.trim().to_string();
+ } else if let Some(rest) = trimmed.strip_prefix("allows:") {
+ cap.allows = rest.trim().to_string();
+ } else if let Some(rest) = trimmed.strip_prefix("without:") {
+ cap.without_cap = rest.trim().to_string();
+ } else if let Some(rest) = trimmed.strip_prefix("condition:") {
+ cap.check_condition = Some(rest.trim().to_string());
+ } else if let Some(rest) = trimmed.strip_prefix("priority:") {
+ cap.priority = rest.trim().parse().ok();
+ }
+ }
+ }
+ BlockContext::SideEffect => {
+ if let Some(effect) = current_side_effect.as_mut() {
+ if let Some(rest) = trimmed.strip_prefix("target:") {
+ effect.target = rest.trim().to_string();
+ } else if let Some(rest) = trimmed.strip_prefix("condition:") {
+ effect.condition = Some(rest.trim().to_string());
+ } else if let Some(rest) = trimmed.strip_prefix("desc:") {
+ let text = rest.trim().to_string();
+ if effect.description.is_empty() {
+ effect.description = text;
+ } else {
+ effect.description.push(' ');
+ effect.description.push_str(&text);
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("reversible:") {
+ let val = rest.trim().to_lowercase();
+ effect.reversible = matches!(val.as_str(), "yes" | "true" | "1");
+ } else {
+ // Continuation of description
+ if !effect.description.is_empty() {
+ effect.description.push(' ');
+ effect.description.push_str(trimmed);
+ }
+ }
+ }
+ }
+ BlockContext::Constraint => {
+ if let Some(constraint) = current_constraint.as_mut() {
+ if let Some(rest) = trimmed.strip_prefix("desc:") {
+ let text = rest.trim().to_string();
+ if constraint.description.is_empty() {
+ constraint.description = text;
+ } else {
+ constraint.description.push(' ');
+ constraint.description.push_str(&text);
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("expr:") {
+ constraint.expression = Some(rest.trim().to_string());
+ } else {
+ // Continuation of description
+ if !constraint.description.is_empty() {
+ constraint.description.push(' ');
+ constraint.description.push_str(trimmed);
+ }
+ }
+ }
+ }
+ BlockContext::Lock => {
+ if let Some(lock) = current_lock.as_mut() {
+ if let Some(rest) = trimmed.strip_prefix("type:") {
+ lock.lock_type = self.parse_lock_type(rest.trim());
+ } else if let Some(rest) = trimmed.strip_prefix("scope:") {
+ lock.scope = match rest.trim() {
+ "internal" => super::KAPI_LOCK_INTERNAL,
+ "acquires" => super::KAPI_LOCK_ACQUIRES,
+ "releases" => super::KAPI_LOCK_RELEASES,
+ "caller_held" => super::KAPI_LOCK_CALLER_HELD,
+ _ => super::KAPI_LOCK_INTERNAL,
+ };
+ } else if let Some(rest) = trimmed.strip_prefix("desc:") {
+ let text = rest.trim().to_string();
+ if lock.description.is_empty() {
+ lock.description = text;
+ } else {
+ lock.description.push(' ');
+ lock.description.push_str(&text);
+ }
+ } else if trimmed.starts_with("acquired:") || trimmed.starts_with("released:") {
+ // Ignored — handled via scope
+ } else {
+ // Continuation of description
+ if !lock.description.is_empty() {
+ lock.description.push(' ');
+ lock.description.push_str(trimmed);
+ }
+ }
+ }
+ }
+ BlockContext::Return => {
+ if let Some(ret) = current_return.as_mut() {
+ if let Some(rest) = trimmed.strip_prefix("type:") {
+ ret.type_name = rest.trim().to_string();
+ ret.return_type = self.parse_param_type(rest.trim());
+ } else if let Some(rest) = trimmed.strip_prefix("check-type:") {
+ ret.check_type = self.parse_return_check_type(rest.trim());
+ } else if let Some(rest) = trimmed.strip_prefix("success:") {
+ // Parse success value — could be "= 0", ">= 0", etc.
+ let val = rest.trim().trim_start_matches(|c: char| !c.is_ascii_digit() && c != '-');
+ ret.success_value = val.parse().ok();
+ } else if let Some(rest) = trimmed.strip_prefix("desc:") {
+ let text = rest.trim().to_string();
+ if ret.description.is_empty() {
+ ret.description = text;
+ } else {
+ ret.description.push(' ');
+ ret.description.push_str(&text);
+ }
+ } else {
+ // Continuation of description
+ if !ret.description.is_empty() {
+ ret.description.push(' ');
+ ret.description.push_str(trimmed);
+ }
+ }
+ }
+ }
+ BlockContext::StateTransition | BlockContext::None => {}
+ }
+ }
+
+ /// Flush the current block, pushing items into the spec
+ fn flush_block(
+ &self,
+ block: &mut BlockContext,
+ spec: &mut ApiSpec,
+ current_error: &mut Option<ErrorSpec>,
+ current_signal: &mut Option<SignalSpec>,
+ current_capability: &mut Option<CapabilitySpec>,
+ current_side_effect: &mut Option<SideEffectSpec>,
+ current_constraint: &mut Option<ConstraintSpec>,
+ current_lock: &mut Option<LockSpec>,
+ current_return: &mut Option<ReturnSpec>,
+ ) {
+ match block {
+ BlockContext::Error(_) => {
+ if let Some(error) = current_error.take() {
+ spec.errors.push(error);
+ }
+ }
+ BlockContext::Signal => {
+ if let Some(signal) = current_signal.take() {
+ spec.signals.push(signal);
+ }
+ }
+ BlockContext::Capability => {
+ if let Some(cap) = current_capability.take() {
+ spec.capabilities.push(cap);
+ }
+ }
+ BlockContext::SideEffect => {
+ if let Some(effect) = current_side_effect.take() {
+ spec.side_effects.push(effect);
+ }
+ }
+ BlockContext::Constraint => {
+ if let Some(constraint) = current_constraint.take() {
+ spec.constraints.push(constraint);
+ }
+ }
+ BlockContext::Lock => {
+ if let Some(lock) = current_lock.take() {
+ spec.locks.push(lock);
+ }
+ }
+ BlockContext::Return => {
+ if let Some(ret) = current_return.take() {
+ spec.return_spec = Some(ret);
+ }
+ }
+ _ => {}
+ }
+ *block = BlockContext::None;
+ }
+
+ /// Extract parameter type names from SYSCALL_DEFINE signature
+ fn extract_types_from_signature(&self, sig: &str) -> HashMap<String, String> {
+ let mut types = HashMap::new();
+
+ // Find content between outermost parens
+ let content = if let Some(start) = sig.find('(') {
+ let end = sig.rfind(')').unwrap_or(sig.len());
+ &sig[start + 1..end]
+ } else {
+ return types;
+ };
+
+ // Split by comma and process type/name pairs
+ // SYSCALL_DEFINE format: (syscall_name, type1, name1, type2, name2, ...)
+ let parts: Vec<&str> = content.split(',').map(|s| s.trim()).collect();
+
+ // Skip first part (syscall name), then process pairs
+ let mut i = 1;
+ while i + 1 < parts.len() {
+ let type_part = parts[i].trim();
+ let name_part = parts[i + 1].trim();
+
+ // Build the type_name string: "type name"
+ let type_name = format!("{} {}", type_part, name_part);
+ types.insert(name_part.to_string(), type_name);
+
+ i += 2;
+ }
+
+ types
+ }
+
+ fn collect_multiline_value(&self, lines: &[&str], start_idx: usize, first_part: &str) -> (String, usize) {
+ let mut result = String::from(first_part.trim());
+ let mut i = start_idx + 1;
+
+ while i < lines.len() {
+ let line = lines[i];
+
+ if self.is_annotation_line(line) {
+ break;
+ }
+
+ if !line.trim().is_empty() && line.starts_with(" ") {
+ if !result.is_empty() {
+ result.push(' ');
+ }
+ result.push_str(line.trim());
+ } else if line.trim().is_empty() {
+ i += 1;
+ continue;
+ } else {
+ break;
+ }
+
+ i += 1;
+ }
+
+ (result, i)
+ }
+
+ fn is_annotation_line(&self, line: &str) -> bool {
+ let trimmed = line.trim_start();
+ if !trimmed.contains(':') {
+ return false;
+ }
+ let annotations = [
+ "param:", "param-", "error:", "error-", "lock:", "lock-",
+ "signal:", "signal-", "side-effect:", "state-trans:",
+ "capability:", "capability-", "constraint:", "constraint-",
+ "struct-", "return:", "return-", "examples:", "notes:",
+ "since-", "context-", "long-desc:", "api-type:",
+ ];
+
+ for ann in &annotations {
+ if trimmed.starts_with(ann) {
+ return true;
+ }
+ }
+ false
+ }
+
+ fn parse_context_flags(&self, flags: &str) -> Vec<String> {
+ flags.split('|')
+ .map(|f| f.trim().to_string())
+ .filter(|f| !f.is_empty())
+ .collect()
+ }
+
+ fn error_name_to_code(&self, name: &str) -> i32 {
+ match name {
+ "EPERM" => -1,
+ "ENOENT" => -2,
+ "ESRCH" => -3,
+ "EINTR" => -4,
+ "EIO" => -5,
+ "ENXIO" => -6,
+ "E2BIG" => -7,
+ "ENOEXEC" => -8,
+ "EBADF" => -9,
+ "ECHILD" => -10,
+ "EAGAIN" | "EWOULDBLOCK" => -11,
+ "ENOMEM" => -12,
+ "EACCES" => -13,
+ "EFAULT" => -14,
+ "ENOTBLK" => -15,
+ "EBUSY" => -16,
+ "EEXIST" => -17,
+ "EXDEV" => -18,
+ "ENODEV" => -19,
+ "ENOTDIR" => -20,
+ "EISDIR" => -21,
+ "EINVAL" => -22,
+ "ENFILE" => -23,
+ "EMFILE" => -24,
+ "ENOTTY" => -25,
+ "ETXTBSY" => -26,
+ "EFBIG" => -27,
+ "ENOSPC" => -28,
+ "ESPIPE" => -29,
+ "EROFS" => -30,
+ "EMLINK" => -31,
+ "EPIPE" => -32,
+ "EDOM" => -33,
+ "ERANGE" => -34,
+ "EDEADLK" => -35,
+ "ENAMETOOLONG" => -36,
+ "ENOLCK" => -37,
+ "ENOSYS" => -38,
+ "ENOTEMPTY" => -39,
+ "ELOOP" => -40,
+ "ENOMSG" => -42,
+ "ENODATA" => -61,
+ "ENOLINK" => -67,
+ "EPROTO" => -71,
+ "EOVERFLOW" => -75,
+ "ELIBBAD" => -80,
+ "EILSEQ" => -84,
+ "ENOTSOCK" => -88,
+ "EDESTADDRREQ" => -89,
+ "EMSGSIZE" => -90,
+ "EPROTOTYPE" => -91,
+ "ENOPROTOOPT" => -92,
+ "EPROTONOSUPPORT" => -93,
+ "EOPNOTSUPP" | "ENOTSUP" => -95,
+ "EADDRINUSE" => -98,
+ "EADDRNOTAVAIL" => -99,
+ "ENETDOWN" => -100,
+ "ENETUNREACH" => -101,
+ "ENETRESET" => -102,
+ "ECONNABORTED" => -103,
+ "ECONNRESET" => -104,
+ "ENOBUFS" => -105,
+ "EISCONN" => -106,
+ "ENOTCONN" => -107,
+ "ETIMEDOUT" => -110,
+ "ECONNREFUSED" => -111,
+ "EALREADY" => -114,
+ "EINPROGRESS" => -115,
+ "ESTALE" => -116,
+ "EDQUOT" => -122,
+ "ENOMEDIUM" => -123,
+ "ENOKEY" => -126,
+ "ERESTARTSYS" => -512,
+ _ => 0,
+ }
+ }
+
+ fn parse_param_type(&self, type_str: &str) -> u32 {
+ match type_str {
+ "KAPI_TYPE_INT" => 1,
+ "KAPI_TYPE_UINT" => 2,
+ "KAPI_TYPE_LONG" => 3,
+ "KAPI_TYPE_ULONG" => 4,
+ "KAPI_TYPE_STRING" => 5,
+ "KAPI_TYPE_USER_PTR" => 6,
+ "KAPI_TYPE_PATH" => 5, // PATH is a string type
+ _ => 0,
+ }
+ }
+
+ fn parse_constraint_type(&self, type_str: &str) -> u32 {
+ match type_str {
+ "KAPI_CONSTRAINT_RANGE" => 1,
+ "KAPI_CONSTRAINT_MASK" => 2,
+ "KAPI_CONSTRAINT_ENUM" => 3,
+ "KAPI_CONSTRAINT_ALIGN" => 4,
+ "KAPI_CONSTRAINT_CUSTOM" => 5,
+ "KAPI_CONSTRAINT_STRLEN" => 6,
+ "KAPI_CONSTRAINT_NULLABLE" => 7,
+ "KAPI_CONSTRAINT_FD" => 8,
+ "KAPI_CONSTRAINT_USER_PATH" => 9,
+ "KAPI_CONSTRAINT_PID" => 10,
+ "KAPI_CONSTRAINT_BUFFER" => 11,
+ "KAPI_CONSTRAINT_IOCTL_CMD" => 12,
+ _ => 0,
+ }
+ }
+
+ fn parse_field_type(&self, type_str: &str) -> u32 {
+ match type_str {
+ "__s32" | "int" => 1,
+ "__u32" | "unsigned int" => 2,
+ "__s64" | "long" => 3,
+ "__u64" | "unsigned long" => 4,
+ _ => 0,
+ }
+ }
+
+ fn parse_param_flags(&self, flags: &str) -> u32 {
+ let mut result = 0;
+ for flag in flags.split('|') {
+ match flag.trim() {
+ "KAPI_PARAM_IN" | "IN" => result |= 1,
+ "KAPI_PARAM_OUT" | "OUT" => result |= 2,
+ "KAPI_PARAM_INOUT" | "INOUT" => result |= 3,
+ "KAPI_PARAM_USER" | "USER" => result |= 64,
+ _ => {}
+ }
+ }
+ result
+ }
+
+ fn parse_lock_type(&self, type_str: &str) -> u32 {
+ match type_str.trim() {
+ "KAPI_LOCK_SPINLOCK" => 0,
+ "KAPI_LOCK_MUTEX" => 1,
+ "KAPI_LOCK_RWLOCK" => 2,
+ "KAPI_LOCK_RCU" => 3,
+ _ => 3,
+ }
+ }
+
+ fn parse_signal_direction(&self, dir: &str) -> u32 {
+ match dir {
+ "KAPI_SIGNAL_RECEIVE" => 1,
+ "KAPI_SIGNAL_SEND" => 2,
+ "KAPI_SIGNAL_HANDLE" => 4,
+ "KAPI_SIGNAL_BLOCK" => 8,
+ "KAPI_SIGNAL_IGNORE" => 16,
+ _ => 0,
+ }
+ }
+
+ fn parse_signal_action(&self, action: &str) -> u32 {
+ match action {
+ "KAPI_SIGNAL_ACTION_DEFAULT" => 0,
+ "KAPI_SIGNAL_ACTION_TERMINATE" => 1,
+ "KAPI_SIGNAL_ACTION_COREDUMP" => 2,
+ "KAPI_SIGNAL_ACTION_STOP" => 3,
+ "KAPI_SIGNAL_ACTION_CONTINUE" => 4,
+ "KAPI_SIGNAL_ACTION_CUSTOM" => 5,
+ "KAPI_SIGNAL_ACTION_RETURN" => 6,
+ "KAPI_SIGNAL_ACTION_RESTART" => 7,
+ "KAPI_SIGNAL_ACTION_QUEUE" => 8,
+ "KAPI_SIGNAL_ACTION_DISCARD" => 9,
+ "KAPI_SIGNAL_ACTION_TRANSFORM" => 10,
+ _ => 0,
+ }
+ }
+
+ fn parse_signal_timing(&self, timing: &str) -> u32 {
+ match timing {
+ "KAPI_SIGNAL_TIME_BEFORE" => 0,
+ "KAPI_SIGNAL_TIME_DURING" => 1,
+ "KAPI_SIGNAL_TIME_AFTER" => 2,
+ _ => 0,
+ }
+ }
+
+ fn parse_signal_state(&self, state: &str) -> u32 {
+ match state {
+ "KAPI_SIGNAL_STATE_RUNNING" => 1,
+ "KAPI_SIGNAL_STATE_SLEEPING" => 2,
+ _ => 0,
+ }
+ }
+
+ fn parse_effect_type(&self, type_str: &str) -> u32 {
+ let mut result = 0;
+ for flag in type_str.split('|') {
+ match flag.trim() {
+ "KAPI_EFFECT_MODIFY_STATE" => result |= 1,
+ "KAPI_EFFECT_PROCESS_STATE" => result |= 2,
+ "KAPI_EFFECT_SCHEDULE" => result |= 4,
+ "KAPI_EFFECT_ALLOC_MEMORY" => result |= 128,
+ "KAPI_EFFECT_RESOURCE_CREATE" => result |= 1,
+ "KAPI_EFFECT_FILESYSTEM" => result |= 4096,
+ _ => {}
+ }
+ }
+ result
+ }
+
+ fn parse_capability_value(&self, cap: &str) -> i32 {
+ match cap {
+ "CAP_CHOWN" => 0,
+ "CAP_DAC_OVERRIDE" => 1,
+ "CAP_DAC_READ_SEARCH" => 2,
+ "CAP_FOWNER" => 3,
+ "CAP_FSETID" => 4,
+ "CAP_KILL" => 5,
+ "CAP_SETGID" => 6,
+ "CAP_SETUID" => 7,
+ "CAP_SETPCAP" => 8,
+ "CAP_LINUX_IMMUTABLE" => 9,
+ "CAP_NET_BIND_SERVICE" => 10,
+ "CAP_NET_BROADCAST" => 11,
+ "CAP_NET_ADMIN" => 12,
+ "CAP_NET_RAW" => 13,
+ "CAP_IPC_LOCK" => 14,
+ "CAP_IPC_OWNER" => 15,
+ "CAP_SYS_MODULE" => 16,
+ "CAP_SYS_RAWIO" => 17,
+ "CAP_SYS_CHROOT" => 18,
+ "CAP_SYS_PTRACE" => 19,
+ "CAP_SYS_PACCT" => 20,
+ "CAP_SYS_ADMIN" => 21,
+ "CAP_SYS_BOOT" => 22,
+ "CAP_SYS_NICE" => 23,
+ "CAP_SYS_RESOURCE" => 24,
+ "CAP_SYS_TIME" => 25,
+ "CAP_SYS_TTY_CONFIG" => 26,
+ "CAP_MKNOD" => 27,
+ "CAP_LEASE" => 28,
+ "CAP_AUDIT_WRITE" => 29,
+ "CAP_AUDIT_CONTROL" => 30,
+ "CAP_SETFCAP" => 31,
+ "CAP_MAC_OVERRIDE" => 32,
+ "CAP_MAC_ADMIN" => 33,
+ "CAP_SYSLOG" => 34,
+ "CAP_WAKE_ALARM" => 35,
+ "CAP_BLOCK_SUSPEND" => 36,
+ "CAP_AUDIT_READ" => 37,
+ "CAP_PERFMON" => 38,
+ "CAP_BPF" => 39,
+ "CAP_CHECKPOINT_RESTORE" => 40,
+ _ => 0,
+ }
+ }
+
+ fn parse_return_check_type(&self, check: &str) -> u32 {
+ match check {
+ "KAPI_RETURN_ERROR_CHECK" => 1,
+ "KAPI_RETURN_SUCCESS_CHECK" => 2,
+ "KAPI_RETURN_FD" => 3,
+ _ => 0,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn parser() -> KerneldocParserImpl {
+ KerneldocParserImpl::new()
+ }
+
+ #[test]
+ fn parse_minimal_kerneldoc() {
+ let doc = "\
+sys_foo - Do something useful
+context-flags: KAPI_CTX_PROCESS
+param-count: 1
+@fd: The file descriptor
+param-type: fd, KAPI_TYPE_INT
+error: EBADF, Bad file descriptor
+";
+ let spec = parser()
+ .parse_kerneldoc(doc, "sys_foo", "syscall", None)
+ .unwrap();
+
+ assert_eq!(spec.name, "sys_foo");
+ assert_eq!(spec.api_type, "syscall");
+ assert_eq!(spec.description.as_deref(), Some("Do something useful"));
+ assert_eq!(spec.param_count, Some(1));
+ assert_eq!(spec.parameters.len(), 1);
+ assert_eq!(spec.parameters[0].name, "fd");
+ assert_eq!(spec.parameters[0].description, "The file descriptor");
+ assert_eq!(spec.parameters[0].param_type, 1); // KAPI_TYPE_INT
+ assert_eq!(spec.errors.len(), 1);
+ assert_eq!(spec.errors[0].name, "EBADF");
+ assert_eq!(spec.errors[0].error_code, -9);
+ }
+
+ #[test]
+ fn parse_multiple_param_types() {
+ let doc = "\
+sys_bar - Multiple params
+@fd: file descriptor arg
+@buf: user buffer
+@count: byte count
+@flags: option flags
+param-type: fd, KAPI_TYPE_INT
+param-type: buf, KAPI_TYPE_USER_PTR
+param-type: count, KAPI_TYPE_UINT
+param-type: flags, KAPI_TYPE_ULONG
+";
+ let sig = "(bar, int, fd, char __user *, buf, size_t, count, unsigned long, flags)";
+ let spec = parser()
+ .parse_kerneldoc(doc, "sys_bar", "syscall", Some(sig))
+ .unwrap();
+
+ assert_eq!(spec.parameters.len(), 4);
+
+ let fd_param = spec.parameters.iter().find(|p| p.name == "fd").unwrap();
+ assert_eq!(fd_param.param_type, 1); // INT
+
+ let buf_param = spec.parameters.iter().find(|p| p.name == "buf").unwrap();
+ assert_eq!(buf_param.param_type, 6); // USER_PTR
+ assert_eq!(buf_param.type_name, "char __user * buf");
+
+ let count_param = spec.parameters.iter().find(|p| p.name == "count").unwrap();
+ assert_eq!(count_param.param_type, 2); // UINT
+
+ let flags_param = spec.parameters.iter().find(|p| p.name == "flags").unwrap();
+ assert_eq!(flags_param.param_type, 4); // ULONG
+ }
+
+ #[test]
+ fn parse_error_codes_with_descriptions() {
+ let doc = "\
+sys_err - Error test
+error: EBADF
+ desc: Bad file descriptor
+ condition: fd < 0
+error: EFAULT
+ desc: Bad user pointer
+ condition: buf is NULL
+error: EINVAL
+ desc: Invalid argument
+";
+ let spec = parser()
+ .parse_kerneldoc(doc, "sys_err", "syscall", None)
+ .unwrap();
+
+ assert_eq!(spec.errors.len(), 3);
+
+ assert_eq!(spec.errors[0].name, "EBADF");
+ assert_eq!(spec.errors[0].error_code, -9);
+ assert_eq!(spec.errors[0].description, "Bad file descriptor");
+ assert_eq!(spec.errors[0].condition, "fd < 0");
+
+ assert_eq!(spec.errors[1].name, "EFAULT");
+ assert_eq!(spec.errors[1].error_code, -14);
+ assert_eq!(spec.errors[1].description, "Bad user pointer");
+
+ assert_eq!(spec.errors[2].name, "EINVAL");
+ assert_eq!(spec.errors[2].error_code, -22);
+ assert_eq!(spec.errors[2].description, "Invalid argument");
+ }
+
+ #[test]
+ fn parse_context_flags() {
+ let doc = "\
+sys_ctx - Context test
+context-flags: KAPI_CTX_PROCESS|KAPI_CTX_SLEEPABLE
+";
+ let spec = parser()
+ .parse_kerneldoc(doc, "sys_ctx", "syscall", None)
+ .unwrap();
+
+ assert_eq!(spec.context_flags.len(), 2);
+ assert_eq!(spec.context_flags[0], "KAPI_CTX_PROCESS");
+ assert_eq!(spec.context_flags[1], "KAPI_CTX_SLEEPABLE");
+ }
+
+ #[test]
+ fn parse_capability_block() {
+ let doc = "\
+sys_cap - Capability test
+capability: CAP_SYS_ADMIN
+ type: required
+ allows: Full system administration
+ without: Operation not permitted
+ condition: always
+ priority: 5
+";
+ let spec = parser()
+ .parse_kerneldoc(doc, "sys_cap", "syscall", None)
+ .unwrap();
+
+ assert_eq!(spec.capabilities.len(), 1);
+ let cap = &spec.capabilities[0];
+ assert_eq!(cap.capability, 21); // CAP_SYS_ADMIN
+ assert_eq!(cap.action, "required");
+ assert_eq!(cap.allows, "Full system administration");
+ assert_eq!(cap.without_cap, "Operation not permitted");
+ assert_eq!(cap.check_condition.as_deref(), Some("always"));
+ assert_eq!(cap.priority, Some(5));
+ }
+
+ #[test]
+ fn parse_lock_block() {
+ let doc = "\
+sys_lock - Lock test
+lock: files_lock, KAPI_LOCK_MUTEX
+ scope: acquires
+ desc: Protects file table
+";
+ let spec = parser()
+ .parse_kerneldoc(doc, "sys_lock", "syscall", None)
+ .unwrap();
+
+ assert_eq!(spec.locks.len(), 1);
+ let lock = &spec.locks[0];
+ assert_eq!(lock.lock_name, "files_lock");
+ assert_eq!(lock.lock_type, 1); // MUTEX
+ assert_eq!(lock.scope, super::super::KAPI_LOCK_ACQUIRES);
+ assert_eq!(lock.description, "Protects file table");
+ }
+
+ #[test]
+ fn parse_signal_block() {
+ let doc = "\
+sys_sig - Signal test
+signal: SIGKILL
+ direction: KAPI_SIGNAL_RECEIVE
+ action: KAPI_SIGNAL_ACTION_TERMINATE
+ timing: KAPI_SIGNAL_TIME_DURING
+ priority: 3
+ restartable: yes
+ interruptible: yes
+ desc: Process termination signal
+";
+ let spec = parser()
+ .parse_kerneldoc(doc, "sys_sig", "syscall", None)
+ .unwrap();
+
+ assert_eq!(spec.signals.len(), 1);
+ let sig = &spec.signals[0];
+ assert_eq!(sig.signal_name, "SIGKILL");
+ assert_eq!(sig.direction, 1); // RECEIVE
+ assert_eq!(sig.action, 1); // TERMINATE
+ assert_eq!(sig.timing, 1); // DURING
+ assert_eq!(sig.priority, 3);
+ assert!(sig.restartable);
+ assert!(sig.interruptible);
+ assert_eq!(sig.description.as_deref(), Some("Process termination signal"));
+ }
+
+ #[test]
+ fn parse_side_effect_flat() {
+ let doc = "\
+sys_se - Side effect test
+side-effect: KAPI_EFFECT_MODIFY_STATE, file_table, Allocates a new file descriptor
+";
+ let spec = parser()
+ .parse_kerneldoc(doc, "sys_se", "syscall", None)
+ .unwrap();
+
+ assert_eq!(spec.side_effects.len(), 1);
+ let se = &spec.side_effects[0];
+ assert_eq!(se.effect_type, 1); // MODIFY_STATE
+ assert_eq!(se.target, "file_table");
+ assert_eq!(se.description, "Allocates a new file descriptor");
+ }
+
+ #[test]
+ fn parse_side_effect_block() {
+ let doc = "\
+sys_se2 - Side effect block test
+side-effect: KAPI_EFFECT_ALLOC_MEMORY
+ target: kernel_heap
+ desc: Allocates kernel memory
+ reversible: yes
+ condition: size > 0
+";
+ let spec = parser()
+ .parse_kerneldoc(doc, "sys_se2", "syscall", None)
+ .unwrap();
+
+ assert_eq!(spec.side_effects.len(), 1);
+ let se = &spec.side_effects[0];
+ assert_eq!(se.effect_type, 128); // ALLOC_MEMORY
+ assert_eq!(se.target, "kernel_heap");
+ assert_eq!(se.description, "Allocates kernel memory");
+ assert!(se.reversible);
+ assert_eq!(se.condition.as_deref(), Some("size > 0"));
+ }
+
+ #[test]
+ fn parse_empty_doc_no_error() {
+ let doc = "";
+ let spec = parser()
+ .parse_kerneldoc(doc, "sys_empty", "syscall", None)
+ .unwrap();
+
+ assert_eq!(spec.name, "sys_empty");
+ assert!(spec.description.is_none());
+ assert!(spec.parameters.is_empty());
+ assert!(spec.errors.is_empty());
+ assert!(spec.signals.is_empty());
+ assert!(spec.capabilities.is_empty());
+ assert!(spec.locks.is_empty());
+ assert!(spec.side_effects.is_empty());
+ assert!(spec.context_flags.is_empty());
+ }
+
+ #[test]
+ fn parse_missing_sections_no_error() {
+ // Only has a description, no KAPI annotations
+ let doc = "\
+sys_simple - Just a simple syscall
+";
+ let spec = parser()
+ .parse_kerneldoc(doc, "sys_simple", "syscall", None)
+ .unwrap();
+
+ assert_eq!(spec.description.as_deref(), Some("Just a simple syscall"));
+ assert!(spec.parameters.is_empty());
+ assert!(spec.errors.is_empty());
+ assert!(spec.context_flags.is_empty());
+ }
+
+ #[test]
+ fn parse_constraint_block() {
+ let doc = "\
+sys_cst - Constraint test
+constraint: valid_fd
+ desc: File descriptor must be valid and open
+ expr: fd >= 0 && fd < NR_OPEN
+";
+ let spec = parser()
+ .parse_kerneldoc(doc, "sys_cst", "syscall", None)
+ .unwrap();
+
+ assert_eq!(spec.constraints.len(), 1);
+ let cst = &spec.constraints[0];
+ assert_eq!(cst.name, "valid_fd");
+ assert_eq!(cst.description, "File descriptor must be valid and open");
+ assert_eq!(cst.expression.as_deref(), Some("fd >= 0 && fd < NR_OPEN"));
+ }
+
+ #[test]
+ fn parse_state_transition_flat() {
+ let doc = "\
+sys_st - State transition test
+state-trans: fd, open, closed, File descriptor is closed
+";
+ let spec = parser()
+ .parse_kerneldoc(doc, "sys_st", "syscall", None)
+ .unwrap();
+
+ assert_eq!(spec.state_transitions.len(), 1);
+ let st = &spec.state_transitions[0];
+ assert_eq!(st.object, "fd");
+ assert_eq!(st.from_state, "open");
+ assert_eq!(st.to_state, "closed");
+ assert_eq!(st.description, "File descriptor is closed");
+ }
+
+ #[test]
+ fn parse_param_block_with_range() {
+ let doc = "\
+sys_rng - Range test
+@count: byte count
+param: count
+ type: KAPI_TYPE_UINT
+ flags: IN
+ range: 0, 4096
+ constraint-type: KAPI_CONSTRAINT_RANGE
+";
+ let spec = parser()
+ .parse_kerneldoc(doc, "sys_rng", "syscall", None)
+ .unwrap();
+
+ assert_eq!(spec.parameters.len(), 1);
+ let p = &spec.parameters[0];
+ assert_eq!(p.name, "count");
+ assert_eq!(p.param_type, 2); // UINT
+ assert_eq!(p.flags, 1); // IN
+ assert_eq!(p.min_value, Some(0));
+ assert_eq!(p.max_value, Some(4096));
+ assert_eq!(p.constraint_type, 1); // RANGE
+ }
+
+ #[test]
+ fn parse_return_block() {
+ let doc = "\
+sys_ret - Return test
+return:
+ type: KAPI_TYPE_INT
+ check-type: KAPI_RETURN_FD
+ success: 0
+ desc: Returns file descriptor on success
+";
+ let spec = parser()
+ .parse_kerneldoc(doc, "sys_ret", "syscall", None)
+ .unwrap();
+
+ let ret = spec.return_spec.as_ref().unwrap();
+ assert_eq!(ret.type_name, "KAPI_TYPE_INT");
+ assert_eq!(ret.return_type, 1); // INT
+ assert_eq!(ret.check_type, 3); // FD
+ assert_eq!(ret.success_value, Some(0));
+ assert_eq!(ret.description, "Returns file descriptor on success");
+ }
+}
--git a/tools/kapi/src/extractor/mod.rs b/tools/kapi/src/extractor/mod.rs
new file mode 100644
index 0000000000000..336e8c0a29cb3
--- /dev/null
+++ b/tools/kapi/src/extractor/mod.rs
@@ -0,0 +1,463 @@
+use crate::formatter::OutputFormatter;
+use anyhow::Result;
+use std::io::Write;
+
+pub mod debugfs;
+pub mod kerneldoc_parser;
+pub mod source_parser;
+pub mod vmlinux;
+
+pub use debugfs::DebugfsExtractor;
+pub use source_parser::SourceExtractor;
+pub use vmlinux::VmlinuxExtractor;
+
+/// Socket state specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct SocketStateSpec {
+ pub required_states: Vec<String>,
+ pub forbidden_states: Vec<String>,
+ pub resulting_state: Option<String>,
+ pub condition: Option<String>,
+ pub applicable_protocols: Option<String>,
+}
+
+/// Protocol behavior specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct ProtocolBehaviorSpec {
+ pub applicable_protocols: String,
+ pub behavior: String,
+ pub protocol_flags: Option<String>,
+ pub flag_description: Option<String>,
+}
+
+/// Address family specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct AddrFamilySpec {
+ pub family: i32,
+ pub family_name: String,
+ pub addr_struct_size: usize,
+ pub min_addr_len: usize,
+ pub max_addr_len: usize,
+ pub addr_format: Option<String>,
+ pub supports_wildcard: bool,
+ pub supports_multicast: bool,
+ pub supports_broadcast: bool,
+ pub special_addresses: Option<String>,
+ pub port_range_min: u32,
+ pub port_range_max: u32,
+}
+
+/// Buffer specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct BufferSpec {
+ pub buffer_behaviors: Option<String>,
+ pub min_buffer_size: Option<usize>,
+ pub max_buffer_size: Option<usize>,
+ pub optimal_buffer_size: Option<usize>,
+}
+
+/// Async specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct AsyncSpec {
+ pub supported_modes: Option<String>,
+ pub nonblock_errno: Option<i32>,
+}
+
+/// Capability specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct CapabilitySpec {
+ pub capability: i32,
+ pub name: String,
+ pub action: String,
+ pub allows: String,
+ pub without_cap: String,
+ pub check_condition: Option<String>,
+ pub priority: Option<u8>,
+ pub alternatives: Vec<i32>,
+}
+
+/// Parameter specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct ParamSpec {
+ pub index: u32,
+ pub name: String,
+ pub type_name: String,
+ pub description: String,
+ pub flags: u32,
+ pub param_type: u32,
+ pub constraint_type: u32,
+ pub constraint: Option<String>,
+ pub min_value: Option<i64>,
+ pub max_value: Option<i64>,
+ pub valid_mask: Option<u64>,
+ pub enum_values: Vec<String>,
+ pub size: Option<u32>,
+ pub alignment: Option<u32>,
+}
+
+/// Return value specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct ReturnSpec {
+ pub type_name: String,
+ pub description: String,
+ pub return_type: u32,
+ pub check_type: u32,
+ pub success_value: Option<i64>,
+ pub success_min: Option<i64>,
+ pub success_max: Option<i64>,
+ pub error_values: Vec<i32>,
+}
+
+/// Error specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct ErrorSpec {
+ pub error_code: i32,
+ pub name: String,
+ pub condition: String,
+ pub description: String,
+}
+
+/// Signal specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct SignalSpec {
+ pub signal_num: i32,
+ pub signal_name: String,
+ pub direction: u32,
+ pub action: u32,
+ pub target: Option<String>,
+ pub condition: Option<String>,
+ pub description: Option<String>,
+ pub timing: u32,
+ pub priority: u32,
+ pub restartable: bool,
+ pub interruptible: bool,
+ pub queue: Option<String>,
+ pub sa_flags: u32,
+ pub sa_flags_required: u32,
+ pub sa_flags_forbidden: u32,
+ pub state_required: u32,
+ pub state_forbidden: u32,
+ pub error_on_signal: Option<i32>,
+}
+
+/// Signal mask specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct SignalMaskSpec {
+ pub name: String,
+ pub description: String,
+}
+
+/// Side effect specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct SideEffectSpec {
+ pub effect_type: u32,
+ pub target: String,
+ pub condition: Option<String>,
+ pub description: String,
+ pub reversible: bool,
+}
+
+/// State transition specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct StateTransitionSpec {
+ pub object: String,
+ pub from_state: String,
+ pub to_state: String,
+ pub condition: Option<String>,
+ pub description: String,
+}
+
+/// Constraint specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct ConstraintSpec {
+ pub name: String,
+ pub description: String,
+ pub expression: Option<String>,
+}
+
+/// Lock scope enum values matching kernel enum kapi_lock_scope
+pub const KAPI_LOCK_INTERNAL: u32 = 0;
+pub const KAPI_LOCK_ACQUIRES: u32 = 1;
+pub const KAPI_LOCK_RELEASES: u32 = 2;
+pub const KAPI_LOCK_CALLER_HELD: u32 = 3;
+
+/// Lock specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct LockSpec {
+ pub lock_name: String,
+ pub lock_type: u32,
+ pub scope: u32,
+ pub description: String,
+}
+
+/// Struct field specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct StructFieldSpec {
+ pub name: String,
+ pub field_type: u32,
+ pub type_name: String,
+ pub offset: usize,
+ pub size: usize,
+ pub flags: u32,
+ pub constraint_type: u32,
+ pub min_value: i64,
+ pub max_value: i64,
+ pub valid_mask: u64,
+ pub description: String,
+}
+
+/// Struct specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct StructSpec {
+ pub name: String,
+ pub size: usize,
+ pub alignment: usize,
+ pub field_count: u32,
+ pub fields: Vec<StructFieldSpec>,
+ pub description: String,
+}
+
+/// Common API specification information that all extractors should provide
+#[derive(Debug, Clone, Default)]
+pub struct ApiSpec {
+ pub name: String,
+ pub api_type: String,
+ pub description: Option<String>,
+ pub long_description: Option<String>,
+ pub version: Option<String>,
+ pub context_flags: Vec<String>,
+ pub param_count: Option<u32>,
+ pub error_count: Option<u32>,
+ pub examples: Option<String>,
+ pub notes: Option<String>,
+ pub since_version: Option<String>,
+ // Sysfs-specific fields
+ pub subsystem: Option<String>,
+ pub sysfs_path: Option<String>,
+ pub permissions: Option<String>,
+ // Networking-specific fields
+ pub socket_state: Option<SocketStateSpec>,
+ pub protocol_behaviors: Vec<ProtocolBehaviorSpec>,
+ pub addr_families: Vec<AddrFamilySpec>,
+ pub buffer_spec: Option<BufferSpec>,
+ pub async_spec: Option<AsyncSpec>,
+ pub net_data_transfer: Option<String>,
+ pub capabilities: Vec<CapabilitySpec>,
+ pub parameters: Vec<ParamSpec>,
+ pub return_spec: Option<ReturnSpec>,
+ pub errors: Vec<ErrorSpec>,
+ pub signals: Vec<SignalSpec>,
+ pub signal_masks: Vec<SignalMaskSpec>,
+ pub side_effects: Vec<SideEffectSpec>,
+ pub state_transitions: Vec<StateTransitionSpec>,
+ pub constraints: Vec<ConstraintSpec>,
+ pub locks: Vec<LockSpec>,
+ pub struct_specs: Vec<StructSpec>,
+}
+
+/// Trait for extracting API specifications from different sources
+pub trait ApiExtractor {
+ /// Extract all API specifications from the source
+ fn extract_all(&self) -> Result<Vec<ApiSpec>>;
+
+ /// Extract a specific API specification by name
+ fn extract_by_name(&self, name: &str) -> Result<Option<ApiSpec>>;
+
+ /// Display detailed information about a specific API
+ fn display_api_details(
+ &self,
+ api_name: &str,
+ formatter: &mut dyn OutputFormatter,
+ writer: &mut dyn Write,
+ ) -> Result<()>;
+}
+
+/// Helper function to display an ApiSpec using a formatter
+pub fn display_api_spec(
+ spec: &ApiSpec,
+ formatter: &mut dyn OutputFormatter,
+ writer: &mut dyn Write,
+) -> Result<()> {
+ formatter.begin_api_details(writer, &spec.name)?;
+
+ if let Some(desc) = &spec.description {
+ formatter.description(writer, desc)?;
+ }
+
+ if let Some(long_desc) = &spec.long_description {
+ formatter.long_description(writer, long_desc)?;
+ }
+
+ if let Some(version) = &spec.since_version {
+ formatter.since_version(writer, version)?;
+ }
+
+ if !spec.context_flags.is_empty() {
+ formatter.begin_context_flags(writer)?;
+ for flag in &spec.context_flags {
+ formatter.context_flag(writer, flag)?;
+ }
+ formatter.end_context_flags(writer)?;
+ }
+
+ if !spec.parameters.is_empty() {
+ formatter.begin_parameters(writer, spec.parameters.len().try_into().unwrap_or(u32::MAX))?;
+ for param in &spec.parameters {
+ formatter.parameter(writer, param)?;
+ }
+ formatter.end_parameters(writer)?;
+ }
+
+ if let Some(ret) = &spec.return_spec {
+ formatter.return_spec(writer, ret)?;
+ }
+
+ if !spec.errors.is_empty() {
+ formatter.begin_errors(writer, spec.errors.len().try_into().unwrap_or(u32::MAX))?;
+ for error in &spec.errors {
+ formatter.error(writer, error)?;
+ }
+ formatter.end_errors(writer)?;
+ }
+
+ if let Some(notes) = &spec.notes {
+ formatter.notes(writer, notes)?;
+ }
+
+ if let Some(examples) = &spec.examples {
+ formatter.examples(writer, examples)?;
+ }
+
+ // Display sysfs-specific fields
+ if spec.api_type == "sysfs" {
+ if let Some(subsystem) = &spec.subsystem {
+ formatter.sysfs_subsystem(writer, subsystem)?;
+ }
+ if let Some(path) = &spec.sysfs_path {
+ formatter.sysfs_path(writer, path)?;
+ }
+ if let Some(perms) = &spec.permissions {
+ formatter.sysfs_permissions(writer, perms)?;
+ }
+ }
+
+ // Display networking-specific fields
+ if let Some(socket_state) = &spec.socket_state {
+ formatter.socket_state(writer, socket_state)?;
+ }
+
+ if !spec.protocol_behaviors.is_empty() {
+ formatter.begin_protocol_behaviors(writer)?;
+ for behavior in &spec.protocol_behaviors {
+ formatter.protocol_behavior(writer, behavior)?;
+ }
+ formatter.end_protocol_behaviors(writer)?;
+ }
+
+ if !spec.addr_families.is_empty() {
+ formatter.begin_addr_families(writer)?;
+ for family in &spec.addr_families {
+ formatter.addr_family(writer, family)?;
+ }
+ formatter.end_addr_families(writer)?;
+ }
+
+ if let Some(buffer_spec) = &spec.buffer_spec {
+ formatter.buffer_spec(writer, buffer_spec)?;
+ }
+
+ if let Some(async_spec) = &spec.async_spec {
+ formatter.async_spec(writer, async_spec)?;
+ }
+
+ if let Some(net_data_transfer) = &spec.net_data_transfer {
+ formatter.net_data_transfer(writer, net_data_transfer)?;
+ }
+
+ if !spec.capabilities.is_empty() {
+ formatter.begin_capabilities(writer)?;
+ for cap in &spec.capabilities {
+ formatter.capability(writer, cap)?;
+ }
+ formatter.end_capabilities(writer)?;
+ }
+
+ // Display signals
+ if !spec.signals.is_empty() {
+ formatter.begin_signals(writer, spec.signals.len().try_into().unwrap_or(u32::MAX))?;
+ for signal in &spec.signals {
+ formatter.signal(writer, signal)?;
+ }
+ formatter.end_signals(writer)?;
+ }
+
+ // Display signal masks
+ if !spec.signal_masks.is_empty() {
+ formatter.begin_signal_masks(
+ writer,
+ spec.signal_masks.len().try_into().unwrap_or(u32::MAX),
+ )?;
+ for mask in &spec.signal_masks {
+ formatter.signal_mask(writer, mask)?;
+ }
+ formatter.end_signal_masks(writer)?;
+ }
+
+ // Display side effects
+ if !spec.side_effects.is_empty() {
+ formatter.begin_side_effects(
+ writer,
+ spec.side_effects.len().try_into().unwrap_or(u32::MAX),
+ )?;
+ for effect in &spec.side_effects {
+ formatter.side_effect(writer, effect)?;
+ }
+ formatter.end_side_effects(writer)?;
+ }
+
+ // Display state transitions
+ if !spec.state_transitions.is_empty() {
+ formatter.begin_state_transitions(
+ writer,
+ spec.state_transitions.len().try_into().unwrap_or(u32::MAX),
+ )?;
+ for trans in &spec.state_transitions {
+ formatter.state_transition(writer, trans)?;
+ }
+ formatter.end_state_transitions(writer)?;
+ }
+
+ // Display constraints
+ if !spec.constraints.is_empty() {
+ formatter.begin_constraints(
+ writer,
+ spec.constraints.len().try_into().unwrap_or(u32::MAX),
+ )?;
+ for constraint in &spec.constraints {
+ formatter.constraint(writer, constraint)?;
+ }
+ formatter.end_constraints(writer)?;
+ }
+
+ // Display locks
+ if !spec.locks.is_empty() {
+ formatter.begin_locks(writer, spec.locks.len().try_into().unwrap_or(u32::MAX))?;
+ for lock in &spec.locks {
+ formatter.lock(writer, lock)?;
+ }
+ formatter.end_locks(writer)?;
+ }
+
+ // Display struct specs
+ if !spec.struct_specs.is_empty() {
+ formatter.begin_struct_specs(writer, spec.struct_specs.len().try_into().unwrap_or(u32::MAX))?;
+ for struct_spec in &spec.struct_specs {
+ formatter.struct_spec(writer, struct_spec)?;
+ }
+ formatter.end_struct_specs(writer)?;
+ }
+
+ formatter.end_api_details(writer)?;
+
+ Ok(())
+}
--git a/tools/kapi/src/extractor/source_parser.rs b/tools/kapi/src/extractor/source_parser.rs
new file mode 100644
index 0000000000000..4d92d69e3ef28
--- /dev/null
+++ b/tools/kapi/src/extractor/source_parser.rs
@@ -0,0 +1,405 @@
+use super::{
+ ApiExtractor, ApiSpec, display_api_spec,
+};
+use super::kerneldoc_parser::KerneldocParserImpl;
+use crate::formatter::OutputFormatter;
+use anyhow::{Context, Result};
+use regex::Regex;
+use std::fs;
+use std::io::Write;
+use std::path::Path;
+use walkdir::WalkDir;
+
+/// Extractor for kernel source files with KAPI-annotated kerneldoc
+pub struct SourceExtractor {
+ path: String,
+ parser: KerneldocParserImpl,
+ syscall_regex: Regex,
+ ioctl_regex: Regex,
+ function_regex: Regex,
+}
+
+impl SourceExtractor {
+ pub fn new(path: &str) -> Result<Self> {
+ Ok(SourceExtractor {
+ path: path.to_string(),
+ parser: KerneldocParserImpl::new(),
+ syscall_regex: Regex::new(r"SYSCALL_DEFINE\d+\((\w+)")?,
+ ioctl_regex: Regex::new(r"(?:static\s+)?long\s+(\w+_ioctl)\s*\(")?,
+ function_regex: Regex::new(
+ r"(?m)^(?:static\s+)?(?:inline\s+)?(?:(?:unsigned\s+)?(?:long|int|void|char|short|struct\s+\w+\s*\*?|[\w_]+_t)\s*\*?\s+)?(\w+)\s*\([^)]*\)",
+ )?,
+ })
+ }
+
+ fn extract_from_file(&self, path: &Path) -> Result<Vec<ApiSpec>> {
+ let content = fs::read_to_string(path)
+ .with_context(|| format!("Failed to read file: {}", path.display()))?;
+
+ self.extract_from_content(&content)
+ }
+
+ fn extract_from_content(&self, content: &str) -> Result<Vec<ApiSpec>> {
+ let mut specs = Vec::new();
+ let mut in_kerneldoc = false;
+ let mut current_doc = String::new();
+ let lines: Vec<&str> = content.lines().collect();
+ let mut i = 0;
+
+ while i < lines.len() {
+ let line = lines[i];
+
+ // Start of kerneldoc comment
+ if line.trim_start().starts_with("/**") {
+ in_kerneldoc = true;
+ current_doc.clear();
+ i += 1;
+ continue;
+ }
+
+ // Inside kerneldoc comment
+ if in_kerneldoc {
+ if line.contains("*/") {
+ in_kerneldoc = false;
+
+ // Check if this kerneldoc has KAPI annotations
+ if current_doc.contains("context-flags:") ||
+ current_doc.contains("param-count:") ||
+ current_doc.contains("side-effect:") ||
+ current_doc.contains("state-trans:") ||
+ current_doc.contains("error-code:") {
+
+ // Look ahead for the function declaration
+ if let Some((name, api_type, signature)) = self.find_function_after(&lines, i + 1) {
+ if let Ok(spec) = self.parser.parse_kerneldoc(¤t_doc, &name, &api_type, Some(&signature)) {
+ specs.push(spec);
+ }
+ }
+ }
+ } else {
+ // Remove leading asterisk and preserve content
+ let cleaned = if let Some(stripped) = line.trim_start().strip_prefix("*") {
+ if let Some(no_space) = stripped.strip_prefix(' ') {
+ no_space
+ } else {
+ stripped
+ }
+ } else {
+ line.trim_start()
+ };
+ current_doc.push_str(cleaned);
+ current_doc.push('\n');
+ }
+ }
+
+ i += 1;
+ }
+
+ Ok(specs)
+ }
+
+ fn find_function_after(&self, lines: &[&str], start: usize) -> Option<(String, String, String)> {
+ for i in start..lines.len().min(start + 10) {
+ let line = lines[i];
+
+ // Skip empty lines
+ if line.trim().is_empty() {
+ continue;
+ }
+
+ // Check for SYSCALL_DEFINE
+ if let Some(caps) = self.syscall_regex.captures(line) {
+ let name = format!("sys_{}", caps.get(1).unwrap().as_str());
+ let signature = self.extract_syscall_signature(lines, i);
+ return Some((name, "syscall".to_string(), signature));
+ }
+
+ // Check for ioctl function
+ if let Some(caps) = self.ioctl_regex.captures(line) {
+ let name = caps.get(1).unwrap().as_str().to_string();
+ return Some((name, "ioctl".to_string(), line.to_string()));
+ }
+
+ // Check for regular function
+ if let Some(caps) = self.function_regex.captures(line) {
+ let name = caps.get(1).unwrap().as_str().to_string();
+ return Some((name, "function".to_string(), line.to_string()));
+ }
+
+ // Stop if we hit something that's clearly not part of the function declaration
+ if !line.starts_with(' ') && !line.starts_with('\t') && !line.trim().is_empty() {
+ break;
+ }
+ }
+
+ None
+ }
+
+ fn extract_syscall_signature(&self, lines: &[&str], start: usize) -> String {
+ // Extract the full SYSCALL_DEFINE signature
+ let mut sig = String::new();
+ let mut in_paren = false;
+ let mut paren_count = 0;
+
+ for line in lines.iter().skip(start).take(20) {
+ let line = *line;
+
+ // Start of SYSCALL_DEFINE
+ if line.contains("SYSCALL_DEFINE") {
+ if let Some(pos) = line.find('(') {
+ sig.push_str(&line[pos..]);
+ in_paren = true;
+ paren_count = line[pos..].chars().filter(|&c| c == '(').count() -
+ line[pos..].chars().filter(|&c| c == ')').count();
+ }
+ } else if in_paren {
+ sig.push(' ');
+ sig.push_str(line.trim());
+ paren_count += line.chars().filter(|&c| c == '(').count();
+ paren_count = paren_count.saturating_sub(line.chars().filter(|&c| c == ')').count());
+
+ if paren_count == 0 {
+ break;
+ }
+ }
+ }
+
+ sig
+ }
+}
+
+impl ApiExtractor for SourceExtractor {
+ fn extract_all(&self) -> Result<Vec<ApiSpec>> {
+ let path = Path::new(&self.path);
+ let mut all_specs = Vec::new();
+
+ if path.is_file() {
+ // Single file
+ all_specs.extend(self.extract_from_file(path)?);
+ } else if path.is_dir() {
+ // Directory - walk all .c files
+ for entry in WalkDir::new(path)
+ .into_iter()
+ .filter_map(|e| e.ok())
+ .filter(|e| e.path().extension().is_some_and(|ext| ext == "c" || ext == "h"))
+ {
+ match self.extract_from_file(entry.path()) {
+ Ok(specs) => all_specs.extend(specs),
+ Err(e) => {
+ eprintln!("Warning: failed to parse {}: {}", entry.path().display(), e);
+ }
+ }
+ }
+ }
+
+ Ok(all_specs)
+ }
+
+ fn extract_by_name(&self, name: &str) -> Result<Option<ApiSpec>> {
+ let all_specs = self.extract_all()?;
+ Ok(all_specs.into_iter().find(|s| s.name == name))
+ }
+
+ fn display_api_details(
+ &self,
+ api_name: &str,
+ formatter: &mut dyn OutputFormatter,
+ output: &mut dyn Write,
+ ) -> Result<()> {
+ if let Some(spec) = self.extract_by_name(api_name)? {
+ display_api_spec(&spec, formatter, output)?;
+ } else {
+ writeln!(output, "API '{}' not found", api_name)?;
+ }
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn make_extractor() -> SourceExtractor {
+ SourceExtractor::new("/dev/null").unwrap()
+ }
+
+ #[test]
+ fn detect_syscall_define3() {
+ let content = r#"
+/**
+ * sys_open - open a file
+ * context-flags: KAPI_CTX_PROCESS
+ * param-count: 3
+ * @filename: pathname to open
+ * param-type: filename, KAPI_TYPE_STRING
+ * error-code: ENOENT
+ */
+SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
+{
+ return 0;
+}
+"#;
+ let ext = make_extractor();
+ let specs = ext.extract_from_content(content).unwrap();
+ assert_eq!(specs.len(), 1);
+ assert_eq!(specs[0].name, "sys_open");
+ assert_eq!(specs[0].api_type, "syscall");
+ }
+
+ #[test]
+ fn detect_syscall_define1() {
+ let content = r#"
+/**
+ * sys_close - close a file descriptor
+ * context-flags: KAPI_CTX_PROCESS
+ * @fd: file descriptor to close
+ * error-code: EBADF
+ */
+SYSCALL_DEFINE1(close, unsigned int, fd)
+{
+ return 0;
+}
+"#;
+ let ext = make_extractor();
+ let specs = ext.extract_from_content(content).unwrap();
+ assert_eq!(specs.len(), 1);
+ assert_eq!(specs[0].name, "sys_close");
+ }
+
+ #[test]
+ fn detect_syscall_define6() {
+ let content = r#"
+/**
+ * sys_mmap - map memory
+ * context-flags: KAPI_CTX_PROCESS
+ * error-code: ENOMEM
+ */
+SYSCALL_DEFINE6(mmap, unsigned long, addr, unsigned long, len, unsigned long, prot,
+ unsigned long, flags, unsigned long, fd, unsigned long, offset)
+{
+ return 0;
+}
+"#;
+ let ext = make_extractor();
+ let specs = ext.extract_from_content(content).unwrap();
+ assert_eq!(specs.len(), 1);
+ assert_eq!(specs[0].name, "sys_mmap");
+ }
+
+ #[test]
+ fn detect_ioctl_pattern() {
+ let content = r#"
+/**
+ * my_ioctl - handle ioctl
+ * context-flags: KAPI_CTX_PROCESS
+ * error-code: EINVAL
+ */
+static long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ return 0;
+}
+"#;
+ let ext = make_extractor();
+ let specs = ext.extract_from_content(content).unwrap();
+ assert_eq!(specs.len(), 1);
+ assert_eq!(specs[0].name, "my_ioctl");
+ assert_eq!(specs[0].api_type, "ioctl");
+ }
+
+ #[test]
+ fn find_function_after_skips_blanks() {
+ // Test that find_function_after looks past blank lines
+ let lines = vec![
+ "",
+ "",
+ "SYSCALL_DEFINE2(foo, int, bar, int, baz)",
+ "{",
+ ];
+ let ext = make_extractor();
+ let result = ext.find_function_after(&lines, 0);
+ assert!(result.is_some());
+ let (name, api_type, _sig) = result.unwrap();
+ assert_eq!(name, "sys_foo");
+ assert_eq!(api_type, "syscall");
+ }
+
+ #[test]
+ fn find_function_after_returns_none_for_no_match() {
+ // No function declaration within lookahead range
+ let lines = vec![
+ "#include <linux/fs.h>",
+ "#define FOO 1",
+ "/* comment */",
+ ];
+ let ext = make_extractor();
+ let result = ext.find_function_after(&lines, 0);
+ // The function_regex may or may not match #define, but let's check
+ // that a pure preprocessor/comment block doesn't false-positive on syscall/ioctl
+ if let Some((_, api_type, _)) = &result {
+ assert_ne!(api_type, "syscall");
+ assert_ne!(api_type, "ioctl");
+ }
+ }
+
+ #[test]
+ fn find_function_after_detects_regular_function() {
+ let lines = vec![
+ "",
+ "int do_something(struct task_struct *task)",
+ "{",
+ ];
+ let ext = make_extractor();
+ let result = ext.find_function_after(&lines, 0);
+ assert!(result.is_some());
+ let (name, api_type, _) = result.unwrap();
+ assert_eq!(name, "do_something");
+ assert_eq!(api_type, "function");
+ }
+
+ #[test]
+ fn no_kapi_annotations_produces_empty() {
+ // kerneldoc without any KAPI annotations should not produce a spec
+ let content = r#"
+/**
+ * my_func - does stuff
+ * @arg: an argument
+ */
+void my_func(int arg)
+{
+}
+"#;
+ let ext = make_extractor();
+ let specs = ext.extract_from_content(content).unwrap();
+ assert!(specs.is_empty());
+ }
+
+ #[test]
+ fn multiple_syscalls_in_one_file() {
+ let content = r#"
+/**
+ * sys_read - read from fd
+ * context-flags: KAPI_CTX_PROCESS
+ * error-code: EBADF
+ */
+SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
+{
+ return 0;
+}
+
+/**
+ * sys_write - write to fd
+ * context-flags: KAPI_CTX_PROCESS
+ * error-code: EBADF
+ */
+SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, size_t, count)
+{
+ return 0;
+}
+"#;
+ let ext = make_extractor();
+ let specs = ext.extract_from_content(content).unwrap();
+ assert_eq!(specs.len(), 2);
+ assert_eq!(specs[0].name, "sys_read");
+ assert_eq!(specs[1].name, "sys_write");
+ }
+}
\ No newline at end of file
--git a/tools/kapi/src/extractor/vmlinux/binary_utils.rs b/tools/kapi/src/extractor/vmlinux/binary_utils.rs
new file mode 100644
index 0000000000000..95610eedce546
--- /dev/null
+++ b/tools/kapi/src/extractor/vmlinux/binary_utils.rs
@@ -0,0 +1,505 @@
+// Constants for all structure field sizes
+pub mod sizes {
+ pub const NAME: usize = 128;
+ pub const DESC: usize = 512;
+ pub const MAX_PARAMS: usize = 16;
+ pub const MAX_ERRORS: usize = 32;
+ pub const MAX_CONSTRAINTS: usize = 32;
+ pub const MAX_CAPABILITIES: usize = 8;
+ pub const MAX_SIGNALS: usize = 32;
+ pub const MAX_STRUCT_SPECS: usize = 8;
+ pub const MAX_SIDE_EFFECTS: usize = 32;
+ pub const MAX_STATE_TRANS: usize = 8;
+ pub const MAX_PROTOCOL_BEHAVIORS: usize = 8;
+ pub const MAX_ADDR_FAMILIES: usize = 8;
+}
+
+/// Endianness of the target ELF binary
+#[derive(Clone, Copy, PartialEq)]
+pub enum Endian {
+ Little,
+ Big,
+}
+
+// Helper for reading data at specific offsets
+pub struct DataReader<'a> {
+ pub data: &'a [u8],
+ pub pos: usize,
+ pub endian: Endian,
+ /// true for 64-bit ELF, false for 32-bit
+ pub is_64bit: bool,
+}
+
+impl<'a> DataReader<'a> {
+ pub fn new(data: &'a [u8], offset: usize, endian: Endian, is_64bit: bool) -> Self {
+ Self {
+ data,
+ pos: offset,
+ endian,
+ is_64bit,
+ }
+ }
+
+ pub fn read_bytes(&mut self, len: usize) -> Option<&'a [u8]> {
+ if self.pos + len <= self.data.len() {
+ let bytes = &self.data[self.pos..self.pos + len];
+ self.pos += len;
+ Some(bytes)
+ } else {
+ None
+ }
+ }
+
+ pub fn read_cstring(&mut self, max_len: usize) -> Option<String> {
+ let bytes = self.read_bytes(max_len)?;
+ if let Some(null_pos) = bytes.iter().position(|&b| b == 0) {
+ if null_pos > 0 {
+ if let Ok(s) = std::str::from_utf8(&bytes[..null_pos]) {
+ return Some(s.to_string());
+ }
+ }
+ }
+ None
+ }
+
+ pub fn read_u32(&mut self) -> Option<u32> {
+ let b: [u8; 4] = self.read_bytes(4)?.try_into().unwrap();
+ Some(match self.endian {
+ Endian::Little => u32::from_le_bytes(b),
+ Endian::Big => u32::from_be_bytes(b),
+ })
+ }
+
+ pub fn read_u8(&mut self) -> Option<u8> {
+ self.read_bytes(1).map(|b| b[0])
+ }
+
+ pub fn read_i32(&mut self) -> Option<i32> {
+ let b: [u8; 4] = self.read_bytes(4)?.try_into().unwrap();
+ Some(match self.endian {
+ Endian::Little => i32::from_le_bytes(b),
+ Endian::Big => i32::from_be_bytes(b),
+ })
+ }
+
+ pub fn read_u64(&mut self) -> Option<u64> {
+ let b: [u8; 8] = self.read_bytes(8)?.try_into().unwrap();
+ Some(match self.endian {
+ Endian::Little => u64::from_le_bytes(b),
+ Endian::Big => u64::from_be_bytes(b),
+ })
+ }
+
+ pub fn read_i64(&mut self) -> Option<i64> {
+ let b: [u8; 8] = self.read_bytes(8)?.try_into().unwrap();
+ Some(match self.endian {
+ Endian::Little => i64::from_le_bytes(b),
+ Endian::Big => i64::from_be_bytes(b),
+ })
+ }
+
+ /// Read a target-sized unsigned value (4 bytes for 32-bit, 8 bytes for 64-bit)
+ pub fn read_usize(&mut self) -> Option<usize> {
+ if self.is_64bit {
+ self.read_u64().map(|v| v as usize)
+ } else {
+ self.read_u32().map(|v| v as usize)
+ }
+ }
+
+ pub fn skip(&mut self, len: usize) {
+ self.pos = (self.pos + len).min(self.data.len());
+ }
+
+ // Helper methods for common patterns
+ pub fn read_bool(&mut self) -> Option<bool> {
+ self.read_u8().map(|v| v != 0)
+ }
+
+ pub fn read_optional_string(&mut self, max_len: usize) -> Option<String> {
+ self.read_cstring(max_len).filter(|s| !s.is_empty())
+ }
+
+ pub fn read_string_or_default(&mut self, max_len: usize) -> String {
+ self.read_cstring(max_len).unwrap_or_default()
+ }
+
+ // Skip and discard - advances position by reading and discarding
+ pub fn discard_cstring(&mut self, max_len: usize) {
+ let _ = self.read_cstring(max_len);
+ }
+
+}
+
+// Structure layout definitions for calculating sizes
+pub fn signal_mask_spec_layout_size() -> usize {
+ // Packed structure from struct kapi_signal_mask_spec
+ sizes::NAME + // mask_name
+ 4 * sizes::MAX_SIGNALS + // signals array
+ 4 + // signal_count
+ sizes::DESC // description
+}
+
+pub fn struct_field_layout_size() -> usize {
+ // Packed structure from struct kapi_struct_field
+ sizes::NAME + // name
+ 4 + // type (enum)
+ sizes::NAME + // type_name
+ 8 + // offset (size_t)
+ 8 + // size (size_t)
+ 4 + // flags
+ 4 + // constraint_type (enum)
+ 8 + // min_value (s64)
+ 8 + // max_value (s64)
+ 8 + // valid_mask (u64)
+ sizes::DESC + // enum_values
+ sizes::DESC // description
+}
+
+pub fn socket_state_spec_layout_size() -> usize {
+ // struct kapi_socket_state_spec
+ sizes::NAME * sizes::MAX_CONSTRAINTS + // required_states array
+ sizes::NAME * sizes::MAX_CONSTRAINTS + // forbidden_states array
+ sizes::NAME + // resulting_state
+ sizes::DESC + // condition
+ sizes::NAME + // applicable_protocols
+ 4 + // required_count
+ 4 // forbidden_count
+}
+
+pub fn protocol_behavior_spec_layout_size() -> usize {
+ // struct kapi_protocol_behavior
+ sizes::NAME + // applicable_protocols
+ sizes::DESC + // behavior
+ sizes::NAME + // protocol_flags
+ sizes::DESC // flag_description
+}
+
+pub fn buffer_spec_layout_size() -> usize {
+ // struct kapi_buffer_spec
+ sizes::DESC + // buffer_behaviors
+ 8 + // min_buffer_size (size_t)
+ 8 + // max_buffer_size (size_t)
+ 8 // optimal_buffer_size (size_t)
+}
+
+pub fn async_spec_layout_size() -> usize {
+ // struct kapi_async_spec
+ sizes::NAME + // supported_modes
+ 4 // nonblock_errno (int)
+}
+
+pub fn addr_family_spec_layout_size() -> usize {
+ // struct kapi_addr_family_spec
+ 4 + // family (int)
+ sizes::NAME + // family_name
+ 8 + // addr_struct_size (size_t)
+ 8 + // min_addr_len (size_t)
+ 8 + // max_addr_len (size_t)
+ sizes::DESC + // addr_format
+ 1 + // supports_wildcard (bool)
+ 1 + // supports_multicast (bool)
+ 1 + // supports_broadcast (bool)
+ sizes::DESC + // special_addresses
+ 4 + // port_range_min (u32)
+ 4 // port_range_max (u32)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ // ---- DataReader little-endian tests ----
+
+ #[test]
+ fn read_u32_little_endian() {
+ let data = [0x78, 0x56, 0x34, 0x12];
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ assert_eq!(reader.read_u32(), Some(0x12345678));
+ }
+
+ #[test]
+ fn read_u32_big_endian() {
+ let data = [0x12, 0x34, 0x56, 0x78];
+ let mut reader = DataReader::new(&data, 0, Endian::Big, true);
+ assert_eq!(reader.read_u32(), Some(0x12345678));
+ }
+
+ #[test]
+ fn read_u64_little_endian() {
+ let data = 0xDEADBEEFCAFEBABEu64.to_le_bytes();
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ assert_eq!(reader.read_u64(), Some(0xDEADBEEFCAFEBABE));
+ }
+
+ #[test]
+ fn read_u64_big_endian() {
+ let data = 0xDEADBEEFCAFEBABEu64.to_be_bytes();
+ let mut reader = DataReader::new(&data, 0, Endian::Big, true);
+ assert_eq!(reader.read_u64(), Some(0xDEADBEEFCAFEBABE));
+ }
+
+ #[test]
+ fn read_i32_little_endian_negative() {
+ let val: i32 = -42;
+ let data = val.to_le_bytes();
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ assert_eq!(reader.read_i32(), Some(-42));
+ }
+
+ #[test]
+ fn read_i32_big_endian_negative() {
+ let val: i32 = -1;
+ let data = val.to_be_bytes();
+ let mut reader = DataReader::new(&data, 0, Endian::Big, true);
+ assert_eq!(reader.read_i32(), Some(-1));
+ }
+
+ #[test]
+ fn read_i64_little_endian() {
+ let val: i64 = -9999999999;
+ let data = val.to_le_bytes();
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ assert_eq!(reader.read_i64(), Some(-9999999999));
+ }
+
+ #[test]
+ fn read_i64_big_endian() {
+ let val: i64 = i64::MIN;
+ let data = val.to_be_bytes();
+ let mut reader = DataReader::new(&data, 0, Endian::Big, true);
+ assert_eq!(reader.read_i64(), Some(i64::MIN));
+ }
+
+ // ---- read_cstring tests ----
+
+ #[test]
+ fn read_cstring_nul_at_start() {
+ let data = [0u8, b'h', b'e', b'l', b'l', b'o'];
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ // NUL at position 0 means null_pos == 0, so the function returns None
+ assert_eq!(reader.read_cstring(6), None);
+ }
+
+ #[test]
+ fn read_cstring_nul_in_middle() {
+ let data = [b'h', b'i', 0, b'x', b'y'];
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ assert_eq!(reader.read_cstring(5), Some("hi".to_string()));
+ }
+
+ #[test]
+ fn read_cstring_nul_at_end() {
+ let data = [b'a', b'b', b'c', b'd', 0];
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ assert_eq!(reader.read_cstring(5), Some("abcd".to_string()));
+ }
+
+ #[test]
+ fn read_cstring_no_nul_returns_none() {
+ let data = [b'x', b'y', b'z'];
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ // No NUL terminator in the 3 bytes -> None
+ assert_eq!(reader.read_cstring(3), None);
+ }
+
+ // ---- read_usize tests ----
+
+ #[test]
+ fn read_usize_64bit() {
+ let val: u64 = 0x00000000FFFFFFFF;
+ let data = val.to_le_bytes();
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ assert_eq!(reader.read_usize(), Some(0xFFFFFFFF));
+ }
+
+ #[test]
+ fn read_usize_32bit() {
+ let val: u32 = 0xABCD1234;
+ let data = val.to_le_bytes();
+ let mut reader = DataReader::new(&data, 0, Endian::Little, false);
+ assert_eq!(reader.read_usize(), Some(0xABCD1234));
+ }
+
+ #[test]
+ fn read_usize_32bit_does_not_consume_8_bytes() {
+ // In 32-bit mode, read_usize should only consume 4 bytes
+ let mut data = [0u8; 8];
+ data[..4].copy_from_slice(&42u32.to_le_bytes());
+ data[4..8].copy_from_slice(&99u32.to_le_bytes());
+ let mut reader = DataReader::new(&data, 0, Endian::Little, false);
+ assert_eq!(reader.read_usize(), Some(42));
+ // After reading 4 bytes, pos should be at 4
+ assert_eq!(reader.pos, 4);
+ assert_eq!(reader.read_usize(), Some(99));
+ }
+
+ // ---- Bounds checking ----
+
+ #[test]
+ fn read_u32_past_end_returns_none() {
+ let data = [0x01, 0x02, 0x03]; // only 3 bytes, need 4
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ assert_eq!(reader.read_u32(), None);
+ }
+
+ #[test]
+ fn read_u64_past_end_returns_none() {
+ let data = [0u8; 7]; // only 7 bytes, need 8
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ assert_eq!(reader.read_u64(), None);
+ }
+
+ #[test]
+ fn read_bytes_past_end_returns_none() {
+ let data = [0u8; 4];
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ assert_eq!(reader.read_bytes(5), None);
+ }
+
+ #[test]
+ fn read_at_offset() {
+ let data = [0x00, 0x00, 0x78, 0x56, 0x34, 0x12];
+ let mut reader = DataReader::new(&data, 2, Endian::Little, true);
+ assert_eq!(reader.read_u32(), Some(0x12345678));
+ }
+
+ #[test]
+ fn read_bool_values() {
+ let data = [0, 1, 255];
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ assert_eq!(reader.read_bool(), Some(false));
+ assert_eq!(reader.read_bool(), Some(true));
+ assert_eq!(reader.read_bool(), Some(true)); // any non-zero is true
+ }
+
+ #[test]
+ fn skip_advances_position() {
+ let data = [0u8; 20];
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ reader.skip(10);
+ assert_eq!(reader.pos, 10);
+ reader.skip(5);
+ assert_eq!(reader.pos, 15);
+ }
+
+ #[test]
+ fn skip_clamps_to_data_len() {
+ let data = [0u8; 10];
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ reader.skip(100);
+ assert_eq!(reader.pos, 10);
+ }
+
+ #[test]
+ fn sequential_reads_advance_position() {
+ let mut data = [0u8; 12];
+ data[..4].copy_from_slice(&1u32.to_le_bytes());
+ data[4..8].copy_from_slice(&2u32.to_le_bytes());
+ data[8..12].copy_from_slice(&3u32.to_le_bytes());
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ assert_eq!(reader.read_u32(), Some(1));
+ assert_eq!(reader.read_u32(), Some(2));
+ assert_eq!(reader.read_u32(), Some(3));
+ assert_eq!(reader.pos, 12);
+ }
+
+ // ---- is_valid_api_name tests (from vmlinux/mod.rs) ----
+ // We test it via the super module since it's defined in vmlinux/mod.rs
+
+ #[test]
+ fn read_optional_string_empty_returns_none() {
+ // A string buffer that is just NUL
+ let data = [0u8; 10];
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ // read_cstring returns None when null_pos == 0
+ // read_optional_string filters empty strings, but read_cstring won't return empty
+ assert_eq!(reader.read_optional_string(10), None);
+ }
+
+ #[test]
+ fn read_string_or_default_with_empty() {
+ let data = [0u8; 10];
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ assert_eq!(reader.read_string_or_default(10), "");
+ }
+
+ #[test]
+ fn read_u8_value() {
+ let data = [0x42];
+ let mut reader = DataReader::new(&data, 0, Endian::Little, true);
+ assert_eq!(reader.read_u8(), Some(0x42));
+ }
+}
+
+/// Tests for is_valid_api_name (defined in vmlinux/mod.rs, tested here
+/// since binary_utils is its sibling module)
+#[cfg(test)]
+mod api_name_tests {
+ use super::super::is_valid_api_name;
+
+ #[test]
+ fn valid_syscall_name() {
+ assert!(is_valid_api_name("sys_open"));
+ assert!(is_valid_api_name("sys_read"));
+ assert!(is_valid_api_name("sys_write"));
+ }
+
+ #[test]
+ fn valid_ioctl_name() {
+ assert!(is_valid_api_name("vfs_ioctl"));
+ assert!(is_valid_api_name("drm_ioctl"));
+ }
+
+ #[test]
+ fn valid_dunder_name() {
+ assert!(is_valid_api_name("__do_sys_open"));
+ assert!(is_valid_api_name("__x64_sys_read"));
+ }
+
+ #[test]
+ fn empty_name_is_invalid() {
+ assert!(!is_valid_api_name(""));
+ }
+
+ #[test]
+ fn too_short_name_is_invalid() {
+ assert!(!is_valid_api_name("ab")); // len < 3
+ }
+
+ #[test]
+ fn too_long_name_is_invalid() {
+ let long_name = "a".repeat(101);
+ assert!(!is_valid_api_name(&long_name));
+ }
+
+ #[test]
+ fn name_starting_with_digit_is_invalid() {
+ assert!(!is_valid_api_name("3func_test"));
+ }
+
+ #[test]
+ fn name_with_special_chars_is_invalid() {
+ assert!(!is_valid_api_name("sys-open")); // dash not allowed
+ assert!(!is_valid_api_name("sys.open")); // dot not allowed
+ assert!(!is_valid_api_name("sys open")); // space not allowed
+ }
+
+ #[test]
+ fn name_with_underscore_is_valid() {
+ assert!(is_valid_api_name("do_some_thing"));
+ assert!(is_valid_api_name("_internal_func"));
+ }
+
+ #[test]
+ fn long_name_without_underscore_is_valid_if_gt_6() {
+ // name.len() > 6 is the fallback condition
+ assert!(is_valid_api_name("longname")); // 8 chars, no underscore but len > 6
+ }
+
+ #[test]
+ fn short_name_without_pattern_may_be_invalid() {
+ // "abc" has len 3, no underscore, no prefix match, len <= 6
+ assert!(!is_valid_api_name("abc"));
+ }
+}
--git a/tools/kapi/src/extractor/vmlinux/magic_finder.rs b/tools/kapi/src/extractor/vmlinux/magic_finder.rs
new file mode 100644
index 0000000000000..8b660e99f6015
--- /dev/null
+++ b/tools/kapi/src/extractor/vmlinux/magic_finder.rs
@@ -0,0 +1,112 @@
+use super::binary_utils::Endian;
+
+// Magic markers for each section
+pub const MAGIC_PARAM: u32 = 0x4B415031; // 'KAP1'
+pub const MAGIC_RETURN: u32 = 0x4B415232; // 'KAR2'
+pub const MAGIC_ERROR: u32 = 0x4B414533; // 'KAE3'
+pub const MAGIC_LOCK: u32 = 0x4B414C34; // 'KAL4'
+pub const MAGIC_CONSTRAINT: u32 = 0x4B414335; // 'KAC5'
+pub const MAGIC_INFO: u32 = 0x4B414936; // 'KAI6'
+pub const MAGIC_SIGNAL: u32 = 0x4B415337; // 'KAS7'
+pub const MAGIC_SIGMASK: u32 = 0x4B414D38; // 'KAM8'
+pub const MAGIC_STRUCT: u32 = 0x4B415439; // 'KAT9'
+pub const MAGIC_EFFECT: u32 = 0x4B414641; // 'KAFA'
+pub const MAGIC_TRANS: u32 = 0x4B415442; // 'KATB'
+pub const MAGIC_CAP: u32 = 0x4B414343; // 'KACC'
+
+fn read_u32_endian(bytes: &[u8], endian: Endian) -> u32 {
+ let b = [bytes[0], bytes[1], bytes[2], bytes[3]];
+ match endian {
+ Endian::Little => u32::from_le_bytes(b),
+ Endian::Big => u32::from_be_bytes(b),
+ }
+}
+
+pub struct MagicOffsets {
+ pub param_offset: Option<usize>,
+ pub return_offset: Option<usize>,
+ pub error_offset: Option<usize>,
+ pub lock_offset: Option<usize>,
+ pub constraint_offset: Option<usize>,
+ pub info_offset: Option<usize>,
+ pub signal_offset: Option<usize>,
+ pub sigmask_offset: Option<usize>,
+ pub struct_offset: Option<usize>,
+ pub effect_offset: Option<usize>,
+ pub trans_offset: Option<usize>,
+ pub cap_offset: Option<usize>,
+}
+
+impl MagicOffsets {
+ /// Find magic markers in the provided data slice
+ /// data: slice of data to search (typically one spec's worth)
+ /// base_offset: absolute offset where this slice starts in the full buffer
+ pub fn find_in_data(data: &[u8], base_offset: usize, endian: Endian) -> Self {
+ let mut offsets = MagicOffsets {
+ param_offset: None,
+ return_offset: None,
+ error_offset: None,
+ lock_offset: None,
+ constraint_offset: None,
+ info_offset: None,
+ signal_offset: None,
+ sigmask_offset: None,
+ struct_offset: None,
+ effect_offset: None,
+ trans_offset: None,
+ cap_offset: None,
+ };
+
+ // Scan through data looking for magic markers
+ // Only find the first occurrence of each magic to avoid cross-spec contamination
+ let mut i = 0;
+ while i + 4 <= data.len() {
+ let bytes = &data[i..i + 4];
+ let value = read_u32_endian(bytes, endian);
+
+ match value {
+ MAGIC_PARAM if offsets.param_offset.is_none() => {
+ offsets.param_offset = Some(base_offset + i);
+ },
+ MAGIC_RETURN if offsets.return_offset.is_none() => {
+ offsets.return_offset = Some(base_offset + i);
+ },
+ MAGIC_ERROR if offsets.error_offset.is_none() => {
+ offsets.error_offset = Some(base_offset + i);
+ },
+ MAGIC_LOCK if offsets.lock_offset.is_none() => {
+ offsets.lock_offset = Some(base_offset + i);
+ },
+ MAGIC_CONSTRAINT if offsets.constraint_offset.is_none() => {
+ offsets.constraint_offset = Some(base_offset + i);
+ },
+ MAGIC_INFO if offsets.info_offset.is_none() => {
+ offsets.info_offset = Some(base_offset + i);
+ },
+ MAGIC_SIGNAL if offsets.signal_offset.is_none() => {
+ offsets.signal_offset = Some(base_offset + i);
+ },
+ MAGIC_SIGMASK if offsets.sigmask_offset.is_none() => {
+ offsets.sigmask_offset = Some(base_offset + i);
+ },
+ MAGIC_STRUCT if offsets.struct_offset.is_none() => {
+ offsets.struct_offset = Some(base_offset + i);
+ },
+ MAGIC_EFFECT if offsets.effect_offset.is_none() => {
+ offsets.effect_offset = Some(base_offset + i);
+ },
+ MAGIC_TRANS if offsets.trans_offset.is_none() => {
+ offsets.trans_offset = Some(base_offset + i);
+ },
+ MAGIC_CAP if offsets.cap_offset.is_none() => {
+ offsets.cap_offset = Some(base_offset + i);
+ },
+ _ => {}
+ }
+
+ i += 1;
+ }
+
+ offsets
+ }
+}
\ No newline at end of file
--git a/tools/kapi/src/extractor/vmlinux/mod.rs b/tools/kapi/src/extractor/vmlinux/mod.rs
new file mode 100644
index 0000000000000..168f4efe23ec7
--- /dev/null
+++ b/tools/kapi/src/extractor/vmlinux/mod.rs
@@ -0,0 +1,842 @@
+use super::{
+ ApiExtractor, ApiSpec, CapabilitySpec, ConstraintSpec, ErrorSpec, LockSpec, ParamSpec,
+ ReturnSpec, SideEffectSpec, SignalMaskSpec, SignalSpec, StateTransitionSpec, StructSpec,
+ StructFieldSpec,
+};
+use crate::formatter::OutputFormatter;
+use anyhow::{Context, Result};
+use goblin::elf::Elf;
+use std::fs;
+use std::io::Write;
+
+mod binary_utils;
+mod magic_finder;
+use binary_utils::{
+ DataReader, Endian, addr_family_spec_layout_size, async_spec_layout_size,
+ buffer_spec_layout_size, protocol_behavior_spec_layout_size, signal_mask_spec_layout_size,
+ sizes, socket_state_spec_layout_size, struct_field_layout_size,
+};
+
+// Helper to convert empty strings to None
+fn opt_string(s: String) -> Option<String> {
+ if s.is_empty() { None } else { Some(s) }
+}
+
+pub struct VmlinuxExtractor {
+ kapi_data: Vec<u8>,
+ specs: Vec<KapiSpec>,
+ endian: Endian,
+ is_64bit: bool,
+}
+
+#[derive(Debug)]
+struct KapiSpec {
+ name: String,
+ api_type: String,
+ offset: usize,
+}
+
+impl VmlinuxExtractor {
+ pub fn new(vmlinux_path: &str) -> Result<Self> {
+ let vmlinux_data = fs::read(vmlinux_path)
+ .with_context(|| format!("Failed to read vmlinux file: {vmlinux_path}"))?;
+
+ let elf = Elf::parse(&vmlinux_data).context("Failed to parse ELF file")?;
+
+ let endian = if elf.little_endian { Endian::Little } else { Endian::Big };
+ let is_64bit = elf.is_64;
+
+ // Find __start_kapi_specs and __stop_kapi_specs symbols first
+ let mut start_addr = None;
+ let mut stop_addr = None;
+
+ for sym in &elf.syms {
+ if let Some(name) = elf.strtab.get_at(sym.st_name) {
+ match name {
+ "__start_kapi_specs" => start_addr = Some(sym.st_value),
+ "__stop_kapi_specs" => stop_addr = Some(sym.st_value),
+ _ => {}
+ }
+ }
+ }
+
+ let start = start_addr.context("Could not find __start_kapi_specs symbol")?;
+ let stop = stop_addr.context("Could not find __stop_kapi_specs symbol")?;
+
+ if stop <= start {
+ anyhow::bail!("No kernel API specifications found in vmlinux");
+ }
+
+ // Find the section containing the kapi specs data
+ // The specs may be in .kapi_specs (standalone) or .rodata (embedded in RO_DATA)
+ let containing_section = elf
+ .section_headers
+ .iter()
+ .find(|sh| {
+ // Check if this section contains the start address
+ start >= sh.sh_addr && start < sh.sh_addr + sh.sh_size
+ })
+ .context("Could not find section containing kapi_specs data")?;
+
+ // Calculate the offset within the file
+ let section_vaddr = containing_section.sh_addr;
+ let file_offset = containing_section.sh_offset + (start - section_vaddr);
+ let data_size: usize = (stop - start)
+ .try_into()
+ .context("Data size too large for platform")?;
+
+ let file_offset_usize: usize = file_offset
+ .try_into()
+ .context("File offset too large for platform")?;
+
+ if file_offset_usize + data_size > vmlinux_data.len() {
+ anyhow::bail!("Invalid offset/size for kapi_specs data");
+ }
+
+ // Extract the raw data
+ let kapi_data = vmlinux_data[file_offset_usize..(file_offset_usize + data_size)].to_vec();
+
+ // Parse the specifications
+ let specs = parse_kapi_specs(&kapi_data, endian)?;
+
+ Ok(VmlinuxExtractor { kapi_data, specs, endian, is_64bit })
+ }
+}
+
+fn parse_kapi_specs(data: &[u8], endian: Endian) -> Result<Vec<KapiSpec>> {
+ let mut specs = Vec::new();
+ let mut offset = 0;
+ let mut last_found_offset = None;
+
+ // Expected offset from struct start to param_magic based on struct layout
+ let param_magic_offset = sizes::NAME + 4 + sizes::DESC + (sizes::DESC * 4) + 4;
+
+ // Find specs by validating API name and magic marker pairs
+ while offset + param_magic_offset + 4 <= data.len() {
+ // Read potential API name
+ let name_bytes = &data[offset..offset + sizes::NAME.min(data.len() - offset)];
+
+ // Find null terminator
+ let name_len = name_bytes.iter().position(|&b| b == 0).unwrap_or(0);
+
+ if name_len > 0 && name_len < 100 {
+ let name = String::from_utf8_lossy(&name_bytes[..name_len]).to_string();
+
+ // Validate API name format
+ if is_valid_api_name(&name) {
+ // Verify magic marker at expected position
+ let magic_offset = offset + param_magic_offset;
+ if magic_offset + 4 <= data.len() {
+ let magic_bytes = &data[magic_offset..magic_offset + 4];
+ let magic_value = match endian {
+ Endian::Little => u32::from_le_bytes([magic_bytes[0], magic_bytes[1], magic_bytes[2], magic_bytes[3]]),
+ Endian::Big => u32::from_be_bytes([magic_bytes[0], magic_bytes[1], magic_bytes[2], magic_bytes[3]]),
+ };
+
+ if magic_value == magic_finder::MAGIC_PARAM {
+ // Avoid duplicate detection of the same spec
+ if last_found_offset.is_none() || offset >= last_found_offset.unwrap() + param_magic_offset {
+ let api_type = if name.starts_with("sys_") {
+ "syscall"
+ } else if name.ends_with("_ioctl") {
+ "ioctl"
+ } else if name.contains("sysfs") {
+ "sysfs"
+ } else {
+ "function"
+ }
+ .to_string();
+
+ specs.push(KapiSpec {
+ name: name.clone(),
+ api_type,
+ offset,
+ });
+
+ last_found_offset = Some(offset);
+ }
+ }
+ }
+ }
+ }
+
+ // Scan byte by byte to find all specs
+ offset += 1;
+ }
+
+ Ok(specs)
+}
+
+
+
+
+fn is_valid_api_name(name: &str) -> bool {
+ // Validate API name format and length
+ if name.is_empty() || name.len() < 3 || name.len() > 100 {
+ return false;
+ }
+
+ // Alphanumeric and underscore characters only
+ if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
+ return false;
+ }
+
+ // Must start with letter or underscore
+ let first_char = name.chars().next().unwrap();
+ if !first_char.is_ascii_alphabetic() && first_char != '_' {
+ return false;
+ }
+
+ // Match common kernel API patterns
+ name.starts_with("sys_") ||
+ name.starts_with("__") ||
+ name.ends_with("_ioctl") ||
+ name.contains("_") ||
+ name.len() > 6
+}
+
+impl ApiExtractor for VmlinuxExtractor {
+ fn extract_all(&self) -> Result<Vec<ApiSpec>> {
+ Ok(self
+ .specs
+ .iter()
+ .map(|spec| {
+ // Parse the full spec for listing
+ parse_binary_to_api_spec(&self.kapi_data, spec.offset, self.endian, self.is_64bit)
+ .unwrap_or_else(|_| ApiSpec {
+ name: spec.name.clone(),
+ api_type: spec.api_type.clone(),
+ ..Default::default()
+ })
+ })
+ .collect())
+ }
+
+ fn extract_by_name(&self, api_name: &str) -> Result<Option<ApiSpec>> {
+ if let Some(spec) = self.specs.iter().find(|s| s.name == api_name) {
+ Ok(Some(parse_binary_to_api_spec(&self.kapi_data, spec.offset, self.endian, self.is_64bit)?))
+ } else {
+ Ok(None)
+ }
+ }
+
+ fn display_api_details(
+ &self,
+ api_name: &str,
+ formatter: &mut dyn OutputFormatter,
+ writer: &mut dyn Write,
+ ) -> Result<()> {
+ if let Some(spec) = self.specs.iter().find(|s| s.name == api_name) {
+ let api_spec = parse_binary_to_api_spec(&self.kapi_data, spec.offset, self.endian, self.is_64bit)?;
+ super::display_api_spec(&api_spec, formatter, writer)?;
+ }
+ Ok(())
+ }
+}
+
+/// Helper to read count and parse array items with optional magic offset
+fn parse_array_with_magic<T, F>(
+ reader: &mut DataReader,
+ magic_offset: Option<usize>,
+ max_items: u32,
+ parse_fn: F,
+) -> Vec<T>
+where
+ F: Fn(&mut DataReader, usize) -> Option<T>,
+{
+ // Read count - position at magic+4 if magic offset exists
+ let count = if let Some(offset) = magic_offset {
+ reader.pos = offset + 4;
+ reader.read_u32()
+ } else {
+ reader.read_u32()
+ };
+
+ let mut items = Vec::new();
+ if let Some(count) = count {
+ // Position at start of array data if magic offset exists
+ if let Some(offset) = magic_offset {
+ reader.pos = offset + 8; // +4 for magic, +4 for count
+ }
+ // Parse items up to max_items
+ for i in 0..count.min(max_items) as usize {
+ if let Some(item) = parse_fn(reader, i) {
+ items.push(item);
+ }
+ }
+ }
+ items
+}
+
+fn parse_binary_to_api_spec(data: &[u8], offset: usize, endian: Endian, is_64bit: bool) -> Result<ApiSpec> {
+ let mut reader = DataReader::new(data, offset, endian, is_64bit);
+
+ // Search for magic markers in the entire spec data
+ let search_end = (offset + 0x70000).min(data.len()); // Search full spec size
+ let spec_data = &data[offset..search_end];
+
+ // Find magic markers relative to the spec start
+ let magic_offsets = magic_finder::MagicOffsets::find_in_data(spec_data, offset, endian);
+
+ // Read fields in exact order of struct kernel_api_spec
+
+ // Read name (128 bytes)
+ let name = reader
+ .read_cstring(sizes::NAME)
+ .ok_or_else(|| anyhow::anyhow!("Failed to read API name"))?;
+
+ // Determine API type
+ let api_type = if name.starts_with("sys_") {
+ "syscall"
+ } else if name.ends_with("_ioctl") {
+ "ioctl"
+ } else if name.contains("sysfs") {
+ "sysfs"
+ } else {
+ "function"
+ }
+ .to_string();
+
+ // Read version (u32)
+ let version = reader.read_u32().map(|v| v.to_string());
+
+ // Read description (512 bytes)
+ let description = reader.read_cstring(sizes::DESC).filter(|s| !s.is_empty());
+
+ // Read long_description (2048 bytes)
+ let long_description = reader
+ .read_cstring(sizes::DESC * 4)
+ .filter(|s| !s.is_empty());
+
+ // Read context_flags (u32)
+ let context_flags = parse_context_flags(&mut reader);
+
+ // Parse params array
+ let parameters = parse_array_with_magic(
+ &mut reader,
+ magic_offsets.param_offset,
+ sizes::MAX_PARAMS as u32,
+ parse_param,
+ );
+
+ // Read return_spec - position using magic offset if available
+ if let Some(offset) = magic_offsets.return_offset {
+ reader.pos = offset + 4; // skip past the return_magic u32
+ }
+ let return_spec = parse_return_spec(&mut reader);
+
+ // Parse errors array
+ let errors = parse_array_with_magic(
+ &mut reader,
+ magic_offsets.error_offset,
+ sizes::MAX_ERRORS as u32,
+ |r, _| parse_error(r),
+ );
+
+ // Parse locks array
+ let locks = parse_array_with_magic(
+ &mut reader,
+ magic_offsets.lock_offset,
+ sizes::MAX_CONSTRAINTS as u32,
+ |r, _| parse_lock(r),
+ );
+
+ // Parse constraints array
+ let constraints = parse_array_with_magic(
+ &mut reader,
+ magic_offsets.constraint_offset,
+ sizes::MAX_CONSTRAINTS as u32,
+ |r, _| parse_constraint(r),
+ );
+
+ // Read examples and notes - position reader at info section if magic found
+ let (examples, notes) = if let Some(info_offset) = magic_offsets.info_offset {
+ reader.pos = info_offset + 4; // +4 to skip magic
+ let examples = reader.read_cstring(sizes::DESC * 2).filter(|s| !s.is_empty());
+ let notes = reader.read_cstring(sizes::DESC * 2).filter(|s| !s.is_empty());
+ (examples, notes)
+ } else {
+ let examples = reader.read_cstring(sizes::DESC * 2).filter(|s| !s.is_empty());
+ let notes = reader.read_cstring(sizes::DESC * 2).filter(|s| !s.is_empty());
+ (examples, notes)
+ };
+
+ // Read since_version (32 bytes)
+ let since_version = reader.read_cstring(32).filter(|s| !s.is_empty());
+
+ // Skip deprecated (bool = 1 byte + 3 bytes padding) and replacement (128 bytes)
+ // These fields were removed from kernel but we need to skip them for binary compatibility
+ reader.skip(4); // deprecated + padding
+ reader.discard_cstring(sizes::NAME); // replacement
+
+ // Parse signals array
+ let signals = parse_array_with_magic(
+ &mut reader,
+ magic_offsets.signal_offset,
+ sizes::MAX_SIGNALS as u32,
+ |r, _| parse_signal(r),
+ );
+
+ // Read signal_mask_count (u32)
+ let signal_mask_count = reader.read_u32();
+
+ // Parse signal_masks array
+ let mut signal_masks = Vec::new();
+ if let Some(count) = signal_mask_count {
+ for i in 0..sizes::MAX_SIGNALS {
+ if i < count as usize {
+ if let Some(mask) = parse_signal_mask(&mut reader) {
+ signal_masks.push(mask);
+ }
+ } else {
+ reader.skip(signal_mask_spec_layout_size());
+ }
+ }
+ } else {
+ reader.skip(signal_mask_spec_layout_size() * sizes::MAX_SIGNALS);
+ }
+
+ // Parse struct_specs array
+ let struct_specs = parse_array_with_magic(
+ &mut reader,
+ magic_offsets.struct_offset,
+ sizes::MAX_STRUCT_SPECS as u32,
+ |r, _| parse_struct_spec(r),
+ );
+
+ // According to the C struct, the order is:
+ // side_effect_count, side_effects array, state_trans_count, state_transitions array,
+ // capability_count, capabilities array
+
+ // Parse side_effects array
+ let side_effects = parse_array_with_magic(
+ &mut reader,
+ magic_offsets.effect_offset,
+ sizes::MAX_SIDE_EFFECTS as u32,
+ |r, _| parse_side_effect(r),
+ );
+
+ // Parse state_transitions array
+ let state_transitions = parse_array_with_magic(
+ &mut reader,
+ magic_offsets.trans_offset,
+ sizes::MAX_STATE_TRANS as u32,
+ |r, _| parse_state_transition(r),
+ );
+
+ // Parse capabilities array
+ let capabilities = parse_array_with_magic(
+ &mut reader,
+ magic_offsets.cap_offset,
+ sizes::MAX_CAPABILITIES as u32,
+ |r, _| parse_capability(r),
+ );
+
+ // Skip remaining network/socket fields
+ reader.skip(
+ socket_state_spec_layout_size() +
+ protocol_behavior_spec_layout_size() * sizes::MAX_PROTOCOL_BEHAVIORS +
+ 4 + // protocol_behavior_count
+ buffer_spec_layout_size() +
+ async_spec_layout_size() +
+ addr_family_spec_layout_size() * sizes::MAX_ADDR_FAMILIES +
+ 4 + // addr_family_count
+ 6 + 2 + // 6 bool flags + padding
+ sizes::DESC * 3 // 3 semantic descriptions
+ );
+
+ Ok(ApiSpec {
+ name,
+ api_type,
+ description,
+ long_description,
+ version,
+ context_flags,
+ param_count: if parameters.is_empty() { None } else { Some(parameters.len() as u32) },
+ error_count: if errors.is_empty() { None } else { Some(errors.len() as u32) },
+ examples,
+ notes,
+ since_version,
+ subsystem: None,
+ sysfs_path: None,
+ permissions: None,
+ socket_state: None,
+ protocol_behaviors: vec![],
+ addr_families: vec![],
+ buffer_spec: None,
+ async_spec: None,
+ net_data_transfer: None,
+ capabilities,
+ parameters,
+ return_spec,
+ errors,
+ signals,
+ signal_masks,
+ side_effects,
+ state_transitions,
+ constraints,
+ locks,
+ struct_specs,
+ })
+}
+
+// Helper parsing functions
+
+fn parse_context_flags(reader: &mut DataReader) -> Vec<String> {
+ const KAPI_CTX_PROCESS: u32 = 1 << 0;
+ const KAPI_CTX_SOFTIRQ: u32 = 1 << 1;
+ const KAPI_CTX_HARDIRQ: u32 = 1 << 2;
+ const KAPI_CTX_NMI: u32 = 1 << 3;
+ const KAPI_CTX_ATOMIC: u32 = 1 << 4;
+ const KAPI_CTX_SLEEPABLE: u32 = 1 << 5;
+ const KAPI_CTX_PREEMPT_DISABLED: u32 = 1 << 6;
+ const KAPI_CTX_IRQ_DISABLED: u32 = 1 << 7;
+
+ if let Some(flags) = reader.read_u32() {
+ let mut parts = Vec::new();
+
+ if flags & KAPI_CTX_PROCESS != 0 {
+ parts.push("KAPI_CTX_PROCESS");
+ }
+ if flags & KAPI_CTX_SOFTIRQ != 0 {
+ parts.push("KAPI_CTX_SOFTIRQ");
+ }
+ if flags & KAPI_CTX_HARDIRQ != 0 {
+ parts.push("KAPI_CTX_HARDIRQ");
+ }
+ if flags & KAPI_CTX_NMI != 0 {
+ parts.push("KAPI_CTX_NMI");
+ }
+ if flags & KAPI_CTX_ATOMIC != 0 {
+ parts.push("KAPI_CTX_ATOMIC");
+ }
+ if flags & KAPI_CTX_SLEEPABLE != 0 {
+ parts.push("KAPI_CTX_SLEEPABLE");
+ }
+ if flags & KAPI_CTX_PREEMPT_DISABLED != 0 {
+ parts.push("KAPI_CTX_PREEMPT_DISABLED");
+ }
+ if flags & KAPI_CTX_IRQ_DISABLED != 0 {
+ parts.push("KAPI_CTX_IRQ_DISABLED");
+ }
+
+ parts.into_iter().map(|s| s.to_string()).collect()
+ } else {
+ vec![]
+ }
+}
+
+fn parse_param(reader: &mut DataReader, index: usize) -> Option<ParamSpec> {
+ let name = reader.read_cstring(sizes::NAME)?;
+ let type_name = reader.read_cstring(sizes::NAME)?;
+ let param_type = reader.read_u32()?;
+ let flags = reader.read_u32()?;
+ let size = reader.read_usize()?;
+ let alignment = reader.read_usize()?;
+ let min_value = reader.read_i64()?;
+ let max_value = reader.read_i64()?;
+ let valid_mask = reader.read_u64()?;
+
+ // Skip enum_values pointer (8 bytes)
+ reader.skip(8);
+ let _enum_count = reader.read_u32()?; // Must use ? to propagate errors
+ let constraint_type = reader.read_u32()?;
+ // Skip validate function pointer (8 bytes)
+ reader.skip(8);
+
+ let description = reader.read_string_or_default(sizes::DESC);
+ let constraint = reader.read_optional_string(sizes::DESC);
+ let _size_param_idx = reader.read_i32()?; // Must use ? to propagate errors
+ let _size_multiplier = reader.read_usize()?; // Must use ? to propagate errors
+
+ Some(ParamSpec {
+ index: index as u32,
+ name,
+ type_name,
+ description,
+ flags,
+ param_type,
+ constraint_type,
+ constraint,
+ min_value: Some(min_value),
+ max_value: Some(max_value),
+ valid_mask: Some(valid_mask),
+ enum_values: vec![],
+ size: Some(size as u32),
+ alignment: Some(alignment as u32),
+ })
+}
+
+fn parse_return_spec(reader: &mut DataReader) -> Option<ReturnSpec> {
+ // Read type_name, but treat empty as valid (will be empty string)
+ let type_name = reader.read_string_or_default(sizes::NAME);
+
+ // Read return_type and check_type
+ let return_type = reader.read_u32().unwrap_or(0);
+ let check_type = reader.read_u32().unwrap_or(0);
+ let success_value = reader.read_i64().unwrap_or(0);
+ let success_min = reader.read_i64().unwrap_or(0);
+ let success_max = reader.read_i64().unwrap_or(0);
+
+ // Skip error_values pointer (8 bytes)
+ reader.skip(8);
+ let _error_count = reader.read_u32().unwrap_or(0); // Don't fail on return spec
+ // Skip is_success function pointer (8 bytes)
+ reader.skip(8);
+
+ let description = reader.read_string_or_default(sizes::DESC);
+
+ // Return a spec even if type_name is empty, as long as we have some data
+ // The type_name might be a string like "KAPI_TYPE_INT" that gets stored literally
+ if type_name.is_empty() && return_type == 0 && check_type == 0 && success_value == 0 {
+ // No return spec at all
+ return None;
+ }
+
+ Some(ReturnSpec {
+ type_name,
+ description,
+ return_type,
+ check_type,
+ success_value: Some(success_value),
+ success_min: Some(success_min),
+ success_max: Some(success_max),
+ error_values: vec![],
+ })
+}
+
+fn parse_error(reader: &mut DataReader) -> Option<ErrorSpec> {
+ let error_code = reader.read_i32()?;
+ let name = reader.read_cstring(sizes::NAME)?;
+ let condition = reader.read_string_or_default(sizes::DESC);
+ let description = reader.read_string_or_default(sizes::DESC);
+
+ Some(ErrorSpec {
+ error_code,
+ name,
+ condition,
+ description,
+ })
+}
+
+fn parse_lock(reader: &mut DataReader) -> Option<LockSpec> {
+ let lock_name = reader.read_cstring(sizes::NAME)?;
+ let lock_type = reader.read_u32()?;
+ let scope = reader.read_u32()?;
+ let description = reader.read_string_or_default(sizes::DESC);
+
+ Some(LockSpec {
+ lock_name,
+ lock_type,
+ scope,
+ description,
+ })
+}
+
+fn parse_constraint(reader: &mut DataReader) -> Option<ConstraintSpec> {
+ let name = reader.read_cstring(sizes::NAME)?;
+ let description = reader.read_string_or_default(sizes::DESC);
+ let expression = reader.read_string_or_default(sizes::DESC);
+
+ // No function pointer in packed struct
+
+ Some(ConstraintSpec {
+ name,
+ description,
+ expression: opt_string(expression),
+ })
+}
+
+fn parse_signal(reader: &mut DataReader) -> Option<SignalSpec> {
+ let signal_num = reader.read_i32()?;
+ let signal_name = reader.read_cstring(32)?; // signal_name[32]
+ let direction = reader.read_u32()?;
+ let action = reader.read_u32()?;
+ let target = reader.read_optional_string(sizes::DESC); // target[512]
+ let condition = reader.read_optional_string(sizes::DESC); // condition[512]
+ let description = reader.read_optional_string(sizes::DESC); // description[512]
+ let restartable = reader.read_bool()?;
+ let sa_flags_required = reader.read_u32()?;
+ let sa_flags_forbidden = reader.read_u32()?;
+ let error_on_signal = reader.read_i32()?;
+ let _transform_to = reader.read_i32()?; // transform_to
+ let timing_bytes = reader.read_bytes(32)?; // timing[32]
+ let timing = if let Some(end) = timing_bytes.iter().position(|&b| b == 0) {
+ String::from_utf8_lossy(&timing_bytes[..end]).parse().unwrap_or(0)
+ } else {
+ 0
+ };
+ let priority = reader.read_u8()?;
+ let interruptible = reader.read_bool()?;
+ let _queue_behavior = reader.read_bytes(128)?; // queue_behavior[128]
+ let state_required = reader.read_u32()?;
+ let state_forbidden = reader.read_u32()?;
+
+ Some(SignalSpec {
+ signal_num,
+ signal_name,
+ direction,
+ action,
+ target,
+ condition,
+ description,
+ timing,
+ priority: priority as u32,
+ restartable,
+ interruptible,
+ queue: None, // queue_behavior not exposed in SignalSpec
+ sa_flags: 0, // Not directly available
+ sa_flags_required,
+ sa_flags_forbidden,
+ state_required,
+ state_forbidden,
+ error_on_signal: Some(error_on_signal),
+ })
+}
+
+fn parse_signal_mask(reader: &mut DataReader) -> Option<SignalMaskSpec> {
+ let name = reader.read_cstring(sizes::NAME)?;
+ let description = reader.read_string_or_default(sizes::DESC);
+
+ // Skip signals array
+ for _ in 0..sizes::MAX_SIGNALS {
+ reader.read_i32();
+ }
+
+ let _signal_count = reader.read_u32()?;
+
+ Some(SignalMaskSpec {
+ name,
+ description,
+ })
+}
+
+fn parse_struct_field(reader: &mut DataReader) -> Option<StructFieldSpec> {
+ let name = reader.read_cstring(sizes::NAME)?;
+ let field_type = reader.read_u32()?;
+ let type_name = reader.read_cstring(sizes::NAME)?;
+ let offset = reader.read_usize()?;
+ let size = reader.read_usize()?;
+ let flags = reader.read_u32()?;
+ let constraint_type = reader.read_u32()?;
+ let min_value = reader.read_i64()?;
+ let max_value = reader.read_i64()?;
+ let valid_mask = reader.read_u64()?;
+ // Skip enum_values field (512 bytes)
+ let _enum_values = reader.read_cstring(sizes::DESC); // Don't fail on optional field
+ let description = reader.read_string_or_default(sizes::DESC);
+
+ Some(StructFieldSpec {
+ name,
+ field_type,
+ type_name,
+ offset,
+ size,
+ flags,
+ constraint_type,
+ min_value,
+ max_value,
+ valid_mask,
+ description,
+ })
+}
+
+fn parse_struct_spec(reader: &mut DataReader) -> Option<StructSpec> {
+ let name = reader.read_cstring(sizes::NAME)?;
+ let size = reader.read_usize()?;
+ let alignment = reader.read_usize()?;
+ let field_count = reader.read_u32()?;
+
+ // Parse fields array
+ let mut fields = Vec::new();
+ for _ in 0..field_count.min(sizes::MAX_PARAMS as u32) {
+ if let Some(field) = parse_struct_field(reader) {
+ fields.push(field);
+ } else {
+ // Skip this field if we can't parse it
+ reader.skip(struct_field_layout_size());
+ }
+ }
+
+ // Skip remaining fields if any
+ let remaining = sizes::MAX_PARAMS as u32 - field_count.min(sizes::MAX_PARAMS as u32);
+ for _ in 0..remaining {
+ reader.skip(struct_field_layout_size());
+ }
+
+ let description = reader.read_string_or_default(sizes::DESC);
+
+ Some(StructSpec {
+ name,
+ size,
+ alignment,
+ field_count,
+ fields,
+ description,
+ })
+}
+
+fn parse_side_effect(reader: &mut DataReader) -> Option<SideEffectSpec> {
+ let effect_type = reader.read_u32()?;
+ let target = reader.read_cstring(sizes::NAME)?;
+ let condition = reader.read_string_or_default(sizes::DESC);
+ let description = reader.read_string_or_default(sizes::DESC);
+ let reversible = reader.read_bool()?;
+ // No padding needed for packed struct
+
+ Some(SideEffectSpec {
+ effect_type,
+ target,
+ condition: opt_string(condition),
+ description,
+ reversible,
+ })
+}
+
+fn parse_state_transition(reader: &mut DataReader) -> Option<StateTransitionSpec> {
+ let from_state = reader.read_cstring(sizes::NAME)?;
+ let to_state = reader.read_cstring(sizes::NAME)?;
+ let condition = reader.read_string_or_default(sizes::DESC);
+ let object = reader.read_cstring(sizes::NAME)?;
+ let description = reader.read_string_or_default(sizes::DESC);
+
+ Some(StateTransitionSpec {
+ object,
+ from_state,
+ to_state,
+ condition: opt_string(condition),
+ description,
+ })
+}
+
+fn parse_capability(reader: &mut DataReader) -> Option<CapabilitySpec> {
+ let capability = reader.read_i32()?;
+ let cap_name = reader.read_cstring(sizes::NAME)?;
+ let action = reader.read_u32()?;
+ let allows = reader.read_string_or_default(sizes::DESC);
+ let without_cap = reader.read_string_or_default(sizes::DESC);
+ let check_condition = reader.read_optional_string(sizes::DESC);
+ let priority = reader.read_u32()?;
+
+ let mut alternatives = Vec::new();
+ for _ in 0..sizes::MAX_CAPABILITIES {
+ if let Some(alt) = reader.read_i32() {
+ if alt != 0 {
+ alternatives.push(alt);
+ }
+ }
+ }
+
+ let _alternative_count = reader.read_u32()?; // alternative_count
+
+ Some(CapabilitySpec {
+ capability,
+ name: cap_name,
+ action: action.to_string(),
+ allows,
+ without_cap,
+ check_condition,
+ priority: Some(priority as u8),
+ alternatives,
+ })
+}
\ No newline at end of file
diff --git a/tools/kapi/src/formatter/json.rs b/tools/kapi/src/formatter/json.rs
new file mode 100644
index 0000000000000..c281eed9ce593
--- /dev/null
+++ b/tools/kapi/src/formatter/json.rs
@@ -0,0 +1,727 @@
+use super::OutputFormatter;
+use crate::extractor::{
+ AddrFamilySpec, AsyncSpec, BufferSpec, CapabilitySpec, ConstraintSpec, ErrorSpec, LockSpec,
+ ParamSpec, ProtocolBehaviorSpec, ReturnSpec, SideEffectSpec, SignalMaskSpec, SignalSpec,
+ SocketStateSpec, StateTransitionSpec, StructSpec,
+};
+use serde::Serialize;
+use std::io::Write;
+
+pub struct JsonFormatter {
+ data: JsonData,
+}
+
+#[derive(Serialize)]
+struct JsonData {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ apis: Option<Vec<JsonApi>>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ api_details: Option<JsonApiDetails>,
+}
+
+#[derive(Serialize)]
+struct JsonApi {
+ name: String,
+ api_type: String,
+}
+
+#[derive(Serialize)]
+struct JsonApiDetails {
+ name: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ description: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ long_description: Option<String>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ context_flags: Vec<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ examples: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ notes: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ since_version: Option<String>,
+ // Sysfs-specific fields
+ #[serde(skip_serializing_if = "Option::is_none")]
+ subsystem: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ sysfs_path: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ permissions: Option<String>,
+ // Networking-specific fields
+ #[serde(skip_serializing_if = "Option::is_none")]
+ socket_state: Option<SocketStateSpec>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ protocol_behaviors: Vec<ProtocolBehaviorSpec>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ addr_families: Vec<AddrFamilySpec>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ buffer_spec: Option<BufferSpec>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ async_spec: Option<AsyncSpec>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ net_data_transfer: Option<String>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ capabilities: Vec<CapabilitySpec>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ state_transitions: Vec<StateTransitionSpec>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ side_effects: Vec<SideEffectSpec>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ parameters: Vec<ParamSpec>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ return_spec: Option<ReturnSpec>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ errors: Vec<ErrorSpec>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ locks: Vec<LockSpec>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ struct_specs: Vec<StructSpec>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ signals: Vec<SignalSpec>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ signal_masks: Vec<SignalMaskSpec>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ constraints: Vec<ConstraintSpec>,
+}
+
+impl JsonFormatter {
+ pub fn new() -> Self {
+ JsonFormatter {
+ data: JsonData {
+ apis: None,
+ api_details: None,
+ },
+ }
+ }
+}
+
+impl OutputFormatter for JsonFormatter {
+ fn begin_document(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn end_document(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ let json = serde_json::to_string_pretty(&self.data)?;
+ writeln!(w, "{json}")?;
+ Ok(())
+ }
+
+ fn begin_api_list(&mut self, _w: &mut dyn Write, _title: &str) -> std::io::Result<()> {
+ if self.data.apis.is_none() {
+ self.data.apis = Some(Vec::new());
+ }
+ Ok(())
+ }
+
+ fn api_item(&mut self, _w: &mut dyn Write, name: &str, api_type: &str) -> std::io::Result<()> {
+ if let Some(apis) = &mut self.data.apis {
+ apis.push(JsonApi {
+ name: name.to_string(),
+ api_type: api_type.to_string(),
+ });
+ }
+ Ok(())
+ }
+
+ fn end_api_list(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn total_specs(&mut self, _w: &mut dyn Write, _count: usize) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_api_details(&mut self, _w: &mut dyn Write, name: &str) -> std::io::Result<()> {
+ self.data.api_details = Some(JsonApiDetails {
+ name: name.to_string(),
+ description: None,
+ long_description: None,
+ context_flags: Vec::new(),
+ examples: None,
+ notes: None,
+ since_version: None,
+ subsystem: None,
+ sysfs_path: None,
+ permissions: None,
+ socket_state: None,
+ protocol_behaviors: Vec::new(),
+ addr_families: Vec::new(),
+ buffer_spec: None,
+ async_spec: None,
+ net_data_transfer: None,
+ capabilities: Vec::new(),
+ state_transitions: Vec::new(),
+ side_effects: Vec::new(),
+ parameters: Vec::new(),
+ return_spec: None,
+ errors: Vec::new(),
+ locks: Vec::new(),
+ struct_specs: Vec::new(),
+ signals: Vec::new(),
+ signal_masks: Vec::new(),
+ constraints: Vec::new(),
+ });
+ Ok(())
+ }
+
+ fn end_api_details(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn description(&mut self, _w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.description = Some(desc.to_string());
+ }
+ Ok(())
+ }
+
+ fn long_description(&mut self, _w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.long_description = Some(desc.to_string());
+ }
+ Ok(())
+ }
+
+ fn begin_context_flags(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn context_flag(&mut self, _w: &mut dyn Write, flag: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.context_flags.push(flag.to_string());
+ }
+ Ok(())
+ }
+
+ fn end_context_flags(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_parameters(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn end_parameters(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_errors(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn end_errors(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn examples(&mut self, _w: &mut dyn Write, examples: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.examples = Some(examples.to_string());
+ }
+ Ok(())
+ }
+
+ fn notes(&mut self, _w: &mut dyn Write, notes: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.notes = Some(notes.to_string());
+ }
+ Ok(())
+ }
+
+ fn since_version(&mut self, _w: &mut dyn Write, version: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.since_version = Some(version.to_string());
+ }
+ Ok(())
+ }
+
+ fn sysfs_subsystem(&mut self, _w: &mut dyn Write, subsystem: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.subsystem = Some(subsystem.to_string());
+ }
+ Ok(())
+ }
+
+ fn sysfs_path(&mut self, _w: &mut dyn Write, path: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.sysfs_path = Some(path.to_string());
+ }
+ Ok(())
+ }
+
+ fn sysfs_permissions(&mut self, _w: &mut dyn Write, perms: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.permissions = Some(perms.to_string());
+ }
+ Ok(())
+ }
+
+ // Networking-specific methods
+ fn socket_state(&mut self, _w: &mut dyn Write, state: &SocketStateSpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.socket_state = Some(state.clone());
+ }
+ Ok(())
+ }
+
+ fn begin_protocol_behaviors(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn protocol_behavior(
+ &mut self,
+ _w: &mut dyn Write,
+ behavior: &ProtocolBehaviorSpec,
+ ) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.protocol_behaviors.push(behavior.clone());
+ }
+ Ok(())
+ }
+
+ fn end_protocol_behaviors(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_addr_families(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn addr_family(&mut self, _w: &mut dyn Write, family: &AddrFamilySpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.addr_families.push(family.clone());
+ }
+ Ok(())
+ }
+
+ fn end_addr_families(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn buffer_spec(&mut self, _w: &mut dyn Write, spec: &BufferSpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.buffer_spec = Some(spec.clone());
+ }
+ Ok(())
+ }
+
+ fn async_spec(&mut self, _w: &mut dyn Write, spec: &AsyncSpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.async_spec = Some(spec.clone());
+ }
+ Ok(())
+ }
+
+ fn net_data_transfer(&mut self, _w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.net_data_transfer = Some(desc.to_string());
+ }
+ Ok(())
+ }
+
+ fn begin_capabilities(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn capability(&mut self, _w: &mut dyn Write, cap: &CapabilitySpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.capabilities.push(cap.clone());
+ }
+ Ok(())
+ }
+
+ fn end_capabilities(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn parameter(&mut self, _w: &mut dyn Write, param: &ParamSpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.parameters.push(param.clone());
+ }
+ Ok(())
+ }
+
+ fn return_spec(&mut self, _w: &mut dyn Write, ret: &ReturnSpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.return_spec = Some(ret.clone());
+ }
+ Ok(())
+ }
+
+ fn error(&mut self, _w: &mut dyn Write, error: &ErrorSpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.errors.push(error.clone());
+ }
+ Ok(())
+ }
+
+ fn begin_signals(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn signal(&mut self, _w: &mut dyn Write, signal: &SignalSpec) -> std::io::Result<()> {
+ if let Some(api_details) = &mut self.data.api_details {
+ api_details.signals.push(signal.clone());
+ }
+ Ok(())
+ }
+
+ fn end_signals(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_signal_masks(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn signal_mask(&mut self, _w: &mut dyn Write, mask: &SignalMaskSpec) -> std::io::Result<()> {
+ if let Some(api_details) = &mut self.data.api_details {
+ api_details.signal_masks.push(mask.clone());
+ }
+ Ok(())
+ }
+
+ fn end_signal_masks(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_side_effects(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn side_effect(&mut self, _w: &mut dyn Write, effect: &SideEffectSpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.side_effects.push(effect.clone());
+ }
+ Ok(())
+ }
+
+ fn end_side_effects(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_state_transitions(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn state_transition(
+ &mut self,
+ _w: &mut dyn Write,
+ trans: &StateTransitionSpec,
+ ) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.state_transitions.push(trans.clone());
+ }
+ Ok(())
+ }
+
+ fn end_state_transitions(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_constraints(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn constraint(
+ &mut self,
+ _w: &mut dyn Write,
+ constraint: &ConstraintSpec,
+ ) -> std::io::Result<()> {
+ if let Some(api_details) = &mut self.data.api_details {
+ api_details.constraints.push(constraint.clone());
+ }
+ Ok(())
+ }
+
+ fn end_constraints(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_locks(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn lock(&mut self, _w: &mut dyn Write, lock: &LockSpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.locks.push(lock.clone());
+ }
+ Ok(())
+ }
+
+ fn end_locks(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_struct_specs(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn struct_spec(&mut self, _w: &mut dyn Write, spec: &StructSpec) -> std::io::Result<()> {
+ if let Some(ref mut details) = self.data.api_details {
+ details.struct_specs.push(spec.clone());
+ }
+ Ok(())
+ }
+
+ fn end_struct_specs(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::extractor::{ErrorSpec, ParamSpec, ReturnSpec};
+
+ fn render_json(f: &mut JsonFormatter) -> String {
+ let mut buf = Vec::new();
+ f.end_document(&mut buf).unwrap();
+ String::from_utf8(buf).unwrap()
+ }
+
+ #[test]
+ fn json_output_is_valid() {
+ let mut f = JsonFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_details(&mut sink, "sys_test").unwrap();
+ f.description(&mut sink, "A test syscall").unwrap();
+ f.end_api_details(&mut sink).unwrap();
+
+ let json = render_json(&mut f);
+
+ // Verify it parses as valid JSON
+ let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
+ assert_eq!(
+ parsed["api_details"]["name"].as_str(),
+ Some("sys_test")
+ );
+ assert_eq!(
+ parsed["api_details"]["description"].as_str(),
+ Some("A test syscall")
+ );
+ }
+
+ #[test]
+ fn json_api_list() {
+ let mut f = JsonFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_list(&mut sink, "Syscalls").unwrap();
+ f.api_item(&mut sink, "sys_open", "syscall").unwrap();
+ f.api_item(&mut sink, "sys_read", "syscall").unwrap();
+ f.end_api_list(&mut sink).unwrap();
+
+ let json = render_json(&mut f);
+ let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
+
+ let apis = parsed["apis"].as_array().unwrap();
+ assert_eq!(apis.len(), 2);
+ assert_eq!(apis[0]["name"].as_str(), Some("sys_open"));
+ assert_eq!(apis[0]["api_type"].as_str(), Some("syscall"));
+ assert_eq!(apis[1]["name"].as_str(), Some("sys_read"));
+ }
+
+ #[test]
+ fn json_special_characters_in_description() {
+ let mut f = JsonFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_details(&mut sink, "sys_test").unwrap();
+ f.description(&mut sink, "Contains \"quotes\" and \\backslashes\\").unwrap();
+ f.end_api_details(&mut sink).unwrap();
+
+ let json = render_json(&mut f);
+
+ // Must be valid JSON despite special characters
+ let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
+ assert_eq!(
+ parsed["api_details"]["description"].as_str(),
+ Some("Contains \"quotes\" and \\backslashes\\")
+ );
+ }
+
+ #[test]
+ fn json_special_characters_in_name() {
+ let mut f = JsonFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_list(&mut sink, "APIs").unwrap();
+ // Names with underscores (common in kernel) and unusual strings
+ f.api_item(&mut sink, "sys_new\tline", "syscall").unwrap();
+ f.end_api_list(&mut sink).unwrap();
+
+ let json = render_json(&mut f);
+
+ // Must parse correctly; serde_json handles escaping for us
+ let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
+ assert_eq!(
+ parsed["apis"][0]["name"].as_str(),
+ Some("sys_new\tline")
+ );
+ }
+
+ #[test]
+ fn json_parameters_serialized() {
+ let mut f = JsonFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_details(&mut sink, "sys_write").unwrap();
+ f.begin_parameters(&mut sink, 2).unwrap();
+ f.parameter(
+ &mut sink,
+ &ParamSpec {
+ index: 0,
+ name: "fd".to_string(),
+ type_name: "unsigned int".to_string(),
+ description: "file descriptor".to_string(),
+ flags: 1,
+ param_type: 2,
+ constraint_type: 0,
+ constraint: None,
+ min_value: Some(0),
+ max_value: Some(1024),
+ valid_mask: None,
+ enum_values: vec![],
+ size: None,
+ alignment: None,
+ },
+ )
+ .unwrap();
+ f.end_parameters(&mut sink).unwrap();
+ f.end_api_details(&mut sink).unwrap();
+
+ let json = render_json(&mut f);
+ let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
+
+ let params = parsed["api_details"]["parameters"].as_array().unwrap();
+ assert_eq!(params.len(), 1);
+ assert_eq!(params[0]["name"].as_str(), Some("fd"));
+ assert_eq!(params[0]["param_type"].as_u64(), Some(2));
+ }
+
+ #[test]
+ fn json_errors_serialized() {
+ let mut f = JsonFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_details(&mut sink, "sys_read").unwrap();
+ f.begin_errors(&mut sink, 1).unwrap();
+ f.error(
+ &mut sink,
+ &ErrorSpec {
+ error_code: -9,
+ name: "EBADF".to_string(),
+ condition: "fd is not valid".to_string(),
+ description: "Bad file descriptor".to_string(),
+ },
+ )
+ .unwrap();
+ f.end_errors(&mut sink).unwrap();
+ f.end_api_details(&mut sink).unwrap();
+
+ let json = render_json(&mut f);
+ let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
+
+ let errors = parsed["api_details"]["errors"].as_array().unwrap();
+ assert_eq!(errors.len(), 1);
+ assert_eq!(errors[0]["name"].as_str(), Some("EBADF"));
+ assert_eq!(errors[0]["error_code"].as_i64(), Some(-9));
+ }
+
+ #[test]
+ fn json_empty_details_omits_empty_fields() {
+ let mut f = JsonFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_details(&mut sink, "sys_empty").unwrap();
+ f.end_api_details(&mut sink).unwrap();
+
+ let json = render_json(&mut f);
+ let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
+
+ // description should not be present (skip_serializing_if = Option::is_none)
+ assert!(parsed["api_details"]["description"].is_null());
+ // parameters empty array should not be present (skip_serializing_if = Vec::is_empty)
+ assert!(parsed["api_details"]["parameters"].is_null());
+ // errors empty array should not be present
+ assert!(parsed["api_details"]["errors"].is_null());
+ }
+
+ #[test]
+ fn json_braces_balance() {
+ let mut f = JsonFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_details(&mut sink, "sys_balanced").unwrap();
+ f.description(&mut sink, "Test braces balance").unwrap();
+ f.end_api_details(&mut sink).unwrap();
+
+ let json = render_json(&mut f);
+
+ let open_braces = json.chars().filter(|&c| c == '{').count();
+ let close_braces = json.chars().filter(|&c| c == '}').count();
+ assert_eq!(open_braces, close_braces, "Braces are unbalanced");
+
+ let open_brackets = json.chars().filter(|&c| c == '[').count();
+ let close_brackets = json.chars().filter(|&c| c == ']').count();
+ assert_eq!(open_brackets, close_brackets, "Brackets are unbalanced");
+ }
+
+ #[test]
+ fn json_return_spec_serialized() {
+ let mut f = JsonFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_details(&mut sink, "sys_open").unwrap();
+ f.return_spec(
+ &mut sink,
+ &ReturnSpec {
+ type_name: "int".to_string(),
+ description: "file descriptor on success".to_string(),
+ return_type: 1,
+ check_type: 3,
+ success_value: Some(0),
+ success_min: None,
+ success_max: None,
+ error_values: vec![-1],
+ },
+ )
+ .unwrap();
+ f.end_api_details(&mut sink).unwrap();
+
+ let json = render_json(&mut f);
+ let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
+
+ let ret = &parsed["api_details"]["return_spec"];
+ assert_eq!(ret["type_name"].as_str(), Some("int"));
+ assert_eq!(ret["check_type"].as_u64(), Some(3));
+ }
+
+ #[test]
+ fn json_unicode_in_description() {
+ let mut f = JsonFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_details(&mut sink, "sys_uni").unwrap();
+ f.description(&mut sink, "Supports unicode: \u{00e9}\u{00e8}\u{00ea}").unwrap();
+ f.end_api_details(&mut sink).unwrap();
+
+ let json = render_json(&mut f);
+ let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
+ assert!(parsed["api_details"]["description"]
+ .as_str()
+ .unwrap()
+ .contains('\u{00e9}'));
+ }
+}
diff --git a/tools/kapi/src/formatter/mod.rs b/tools/kapi/src/formatter/mod.rs
new file mode 100644
index 0000000000000..3de8bf23bc29a
--- /dev/null
+++ b/tools/kapi/src/formatter/mod.rs
@@ -0,0 +1,140 @@
+use crate::extractor::{
+ AddrFamilySpec, AsyncSpec, BufferSpec, CapabilitySpec, ConstraintSpec, ErrorSpec, LockSpec,
+ ParamSpec, ProtocolBehaviorSpec, ReturnSpec, SideEffectSpec, SignalMaskSpec, SignalSpec,
+ SocketStateSpec, StateTransitionSpec, StructSpec,
+};
+use std::io::Write;
+
+mod json;
+mod plain;
+mod rst;
+
+pub use json::JsonFormatter;
+pub use plain::PlainFormatter;
+pub use rst::RstFormatter;
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum OutputFormat {
+ Plain,
+ Json,
+ Rst,
+}
+
+impl std::str::FromStr for OutputFormat {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s.to_lowercase().as_str() {
+ "plain" => Ok(OutputFormat::Plain),
+ "json" => Ok(OutputFormat::Json),
+ "rst" => Ok(OutputFormat::Rst),
+ _ => Err(format!("Unknown output format: {}", s)),
+ }
+ }
+}
+
+pub trait OutputFormatter {
+ fn begin_document(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+ fn end_document(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn begin_api_list(&mut self, w: &mut dyn Write, title: &str) -> std::io::Result<()>;
+ fn api_item(&mut self, w: &mut dyn Write, name: &str, api_type: &str) -> std::io::Result<()>;
+ fn end_api_list(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn total_specs(&mut self, w: &mut dyn Write, count: usize) -> std::io::Result<()>;
+
+ fn begin_api_details(&mut self, w: &mut dyn Write, name: &str) -> std::io::Result<()>;
+ fn end_api_details(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+ fn description(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()>;
+ fn long_description(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()>;
+
+ fn begin_context_flags(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+ fn context_flag(&mut self, w: &mut dyn Write, flag: &str) -> std::io::Result<()>;
+ fn end_context_flags(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn begin_parameters(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()>;
+ fn parameter(&mut self, w: &mut dyn Write, param: &ParamSpec) -> std::io::Result<()>;
+ fn end_parameters(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn return_spec(&mut self, w: &mut dyn Write, ret: &ReturnSpec) -> std::io::Result<()>;
+
+ fn begin_errors(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()>;
+ fn error(&mut self, w: &mut dyn Write, error: &ErrorSpec) -> std::io::Result<()>;
+ fn end_errors(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn examples(&mut self, w: &mut dyn Write, examples: &str) -> std::io::Result<()>;
+ fn notes(&mut self, w: &mut dyn Write, notes: &str) -> std::io::Result<()>;
+ fn since_version(&mut self, w: &mut dyn Write, version: &str) -> std::io::Result<()>;
+
+ // Sysfs-specific methods
+ fn sysfs_subsystem(&mut self, w: &mut dyn Write, subsystem: &str) -> std::io::Result<()>;
+ fn sysfs_path(&mut self, w: &mut dyn Write, path: &str) -> std::io::Result<()>;
+ fn sysfs_permissions(&mut self, w: &mut dyn Write, perms: &str) -> std::io::Result<()>;
+
+ // Networking-specific methods
+ fn socket_state(&mut self, w: &mut dyn Write, state: &SocketStateSpec) -> std::io::Result<()>;
+
+ fn begin_protocol_behaviors(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+ fn protocol_behavior(
+ &mut self,
+ w: &mut dyn Write,
+ behavior: &ProtocolBehaviorSpec,
+ ) -> std::io::Result<()>;
+ fn end_protocol_behaviors(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn begin_addr_families(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+ fn addr_family(&mut self, w: &mut dyn Write, family: &AddrFamilySpec) -> std::io::Result<()>;
+ fn end_addr_families(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn buffer_spec(&mut self, w: &mut dyn Write, spec: &BufferSpec) -> std::io::Result<()>;
+ fn async_spec(&mut self, w: &mut dyn Write, spec: &AsyncSpec) -> std::io::Result<()>;
+ fn net_data_transfer(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()>;
+
+ fn begin_capabilities(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+ fn capability(&mut self, w: &mut dyn Write, cap: &CapabilitySpec) -> std::io::Result<()>;
+ fn end_capabilities(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ // Signal-related methods
+ fn begin_signals(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()>;
+ fn signal(&mut self, w: &mut dyn Write, signal: &SignalSpec) -> std::io::Result<()>;
+ fn end_signals(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn begin_signal_masks(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()>;
+ fn signal_mask(&mut self, w: &mut dyn Write, mask: &SignalMaskSpec) -> std::io::Result<()>;
+ fn end_signal_masks(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ // Side effects and state transitions
+ fn begin_side_effects(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()>;
+ fn side_effect(&mut self, w: &mut dyn Write, effect: &SideEffectSpec) -> std::io::Result<()>;
+ fn end_side_effects(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn begin_state_transitions(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()>;
+ fn state_transition(
+ &mut self,
+ w: &mut dyn Write,
+ trans: &StateTransitionSpec,
+ ) -> std::io::Result<()>;
+ fn end_state_transitions(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ // Constraints and locks
+ fn begin_constraints(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()>;
+ fn constraint(&mut self, w: &mut dyn Write, constraint: &ConstraintSpec)
+ -> std::io::Result<()>;
+ fn end_constraints(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn begin_locks(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()>;
+ fn lock(&mut self, w: &mut dyn Write, lock: &LockSpec) -> std::io::Result<()>;
+ fn end_locks(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn begin_struct_specs(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()>;
+ fn struct_spec(&mut self, w: &mut dyn Write, spec: &StructSpec) -> std::io::Result<()>;
+ fn end_struct_specs(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+}
+
+pub fn create_formatter(format: OutputFormat) -> Box<dyn OutputFormatter> {
+ match format {
+ OutputFormat::Plain => Box::new(PlainFormatter::new()),
+ OutputFormat::Json => Box::new(JsonFormatter::new()),
+ OutputFormat::Rst => Box::new(RstFormatter::new()),
+ }
+}
diff --git a/tools/kapi/src/formatter/plain.rs b/tools/kapi/src/formatter/plain.rs
new file mode 100644
index 0000000000000..09e054e323f5e
--- /dev/null
+++ b/tools/kapi/src/formatter/plain.rs
@@ -0,0 +1,708 @@
+use super::OutputFormatter;
+use crate::extractor::{
+ AddrFamilySpec, AsyncSpec, BufferSpec, CapabilitySpec, ConstraintSpec, ErrorSpec, LockSpec,
+ ParamSpec, ProtocolBehaviorSpec, ReturnSpec, SideEffectSpec, SignalMaskSpec, SignalSpec,
+ SocketStateSpec, StateTransitionSpec,
+};
+use std::io::Write;
+
+pub struct PlainFormatter;
+
+impl PlainFormatter {
+ pub fn new() -> Self {
+ PlainFormatter
+ }
+}
+
+impl OutputFormatter for PlainFormatter {
+ fn begin_document(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn end_document(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_api_list(&mut self, w: &mut dyn Write, title: &str) -> std::io::Result<()> {
+ writeln!(w, "\n{title}:")?;
+ writeln!(w, "{}", "-".repeat(title.len() + 1))
+ }
+
+ fn api_item(&mut self, w: &mut dyn Write, name: &str, _api_type: &str) -> std::io::Result<()> {
+ writeln!(w, " {name}")
+ }
+
+ fn end_api_list(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn total_specs(&mut self, w: &mut dyn Write, count: usize) -> std::io::Result<()> {
+ writeln!(w, "\nTotal specifications found: {count}")
+ }
+
+ fn begin_api_details(&mut self, w: &mut dyn Write, name: &str) -> std::io::Result<()> {
+ writeln!(w, "\nDetailed information for {name}:")?;
+ writeln!(w, "{}=", "=".repeat(25 + name.len()))
+ }
+
+ fn end_api_details(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn description(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ writeln!(w, "Description: {desc}")
+ }
+
+ fn long_description(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ writeln!(w, "\nDetailed Description:")?;
+ writeln!(w, "{desc}")
+ }
+
+ fn begin_context_flags(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ writeln!(w, "\nExecution Context:")
+ }
+
+ fn context_flag(&mut self, w: &mut dyn Write, flag: &str) -> std::io::Result<()> {
+ writeln!(w, " - {flag}")
+ }
+
+ fn end_context_flags(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_parameters(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nParameters ({count}):")
+ }
+
+ fn parameter(&mut self, w: &mut dyn Write, param: &ParamSpec) -> std::io::Result<()> {
+ writeln!(
+ w,
+ " [{}] {} ({})",
+ param.index, param.name, param.type_name
+ )?;
+ if !param.description.is_empty() {
+ writeln!(w, " {}", param.description)?;
+ }
+
+ // Display flags
+ let mut flags = Vec::new();
+ if param.flags & 0x01 != 0 {
+ flags.push("IN");
+ }
+ if param.flags & 0x02 != 0 {
+ flags.push("OUT");
+ }
+ if param.flags & 0x04 != 0 {
+ flags.push("INOUT");
+ }
+ if param.flags & 0x08 != 0 {
+ flags.push("USER");
+ }
+ if param.flags & 0x10 != 0 {
+ flags.push("OPTIONAL");
+ }
+ if !flags.is_empty() {
+ writeln!(w, " Flags: {}", flags.join(" | "))?;
+ }
+
+ // Display constraints
+ if let Some(constraint) = ¶m.constraint {
+ writeln!(w, " Constraint: {constraint}")?;
+ }
+ if let (Some(min), Some(max)) = (param.min_value, param.max_value) {
+ writeln!(w, " Range: {min} to {max}")?;
+ }
+ if let Some(mask) = param.valid_mask {
+ writeln!(w, " Valid mask: 0x{mask:x}")?;
+ }
+ Ok(())
+ }
+
+ fn end_parameters(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn return_spec(&mut self, w: &mut dyn Write, ret: &ReturnSpec) -> std::io::Result<()> {
+ writeln!(w, "\nReturn Value:")?;
+ writeln!(w, " Type: {}", ret.type_name)?;
+ writeln!(w, " {}", ret.description)?;
+ if let Some(val) = ret.success_value {
+ writeln!(w, " Success value: {val}")?;
+ }
+ if let (Some(min), Some(max)) = (ret.success_min, ret.success_max) {
+ writeln!(w, " Success range: {min} to {max}")?;
+ }
+ Ok(())
+ }
+
+ fn begin_errors(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nPossible Errors ({count}):")
+ }
+
+ fn error(&mut self, w: &mut dyn Write, error: &ErrorSpec) -> std::io::Result<()> {
+ writeln!(w, " {} ({})", error.name, error.error_code)?;
+ if !error.condition.is_empty() {
+ writeln!(w, " Condition: {}", error.condition)?;
+ }
+ if !error.description.is_empty() {
+ writeln!(w, " {}", error.description)?;
+ }
+ Ok(())
+ }
+
+ fn end_errors(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn examples(&mut self, w: &mut dyn Write, examples: &str) -> std::io::Result<()> {
+ writeln!(w, "\nExamples:")?;
+ writeln!(w, "{examples}")
+ }
+
+ fn notes(&mut self, w: &mut dyn Write, notes: &str) -> std::io::Result<()> {
+ writeln!(w, "\nNotes:")?;
+ writeln!(w, "{notes}")
+ }
+
+ fn since_version(&mut self, w: &mut dyn Write, version: &str) -> std::io::Result<()> {
+ writeln!(w, "\nAvailable since: {version}")
+ }
+
+ fn sysfs_subsystem(&mut self, w: &mut dyn Write, subsystem: &str) -> std::io::Result<()> {
+ writeln!(w, "Subsystem: {subsystem}")
+ }
+
+ fn sysfs_path(&mut self, w: &mut dyn Write, path: &str) -> std::io::Result<()> {
+ writeln!(w, "Sysfs Path: {path}")
+ }
+
+ fn sysfs_permissions(&mut self, w: &mut dyn Write, perms: &str) -> std::io::Result<()> {
+ writeln!(w, "Permissions: {perms}")
+ }
+
+ // Networking-specific methods
+ fn socket_state(&mut self, w: &mut dyn Write, state: &SocketStateSpec) -> std::io::Result<()> {
+ writeln!(w, "\nSocket State Requirements:")?;
+ if !state.required_states.is_empty() {
+ writeln!(w, " Required states: {:?}", state.required_states)?;
+ }
+ if !state.forbidden_states.is_empty() {
+ writeln!(w, " Forbidden states: {:?}", state.forbidden_states)?;
+ }
+ if let Some(result) = &state.resulting_state {
+ writeln!(w, " Resulting state: {result}")?;
+ }
+ if let Some(cond) = &state.condition {
+ writeln!(w, " Condition: {cond}")?;
+ }
+ if let Some(protos) = &state.applicable_protocols {
+ writeln!(w, " Applicable protocols: {protos}")?;
+ }
+ Ok(())
+ }
+
+ fn begin_protocol_behaviors(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ writeln!(w, "\nProtocol-Specific Behaviors:")
+ }
+
+ fn protocol_behavior(
+ &mut self,
+ w: &mut dyn Write,
+ behavior: &ProtocolBehaviorSpec,
+ ) -> std::io::Result<()> {
+ writeln!(
+ w,
+ " {} - {}",
+ behavior.applicable_protocols, behavior.behavior
+ )?;
+ if let Some(flags) = &behavior.protocol_flags {
+ writeln!(w, " Flags: {flags}")?;
+ }
+ Ok(())
+ }
+
+ fn end_protocol_behaviors(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_addr_families(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ writeln!(w, "\nSupported Address Families:")
+ }
+
+ fn addr_family(&mut self, w: &mut dyn Write, family: &AddrFamilySpec) -> std::io::Result<()> {
+ writeln!(w, " {} ({}):", family.family_name, family.family)?;
+ writeln!(w, " Struct size: {} bytes", family.addr_struct_size)?;
+ writeln!(
+ w,
+ " Address length: {}-{} bytes",
+ family.min_addr_len, family.max_addr_len
+ )?;
+ if let Some(format) = &family.addr_format {
+ writeln!(w, " Format: {format}")?;
+ }
+ writeln!(
+ w,
+ " Features: wildcard={}, multicast={}, broadcast={}",
+ family.supports_wildcard, family.supports_multicast, family.supports_broadcast
+ )?;
+ if let Some(special) = &family.special_addresses {
+ writeln!(w, " Special addresses: {special}")?;
+ }
+ if family.port_range_max > 0 {
+ writeln!(
+ w,
+ " Port range: {}-{}",
+ family.port_range_min, family.port_range_max
+ )?;
+ }
+ Ok(())
+ }
+
+ fn end_addr_families(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn buffer_spec(&mut self, w: &mut dyn Write, spec: &BufferSpec) -> std::io::Result<()> {
+ writeln!(w, "\nBuffer Specification:")?;
+ if let Some(behaviors) = &spec.buffer_behaviors {
+ writeln!(w, " Behaviors: {behaviors}")?;
+ }
+ if let Some(min) = spec.min_buffer_size {
+ writeln!(w, " Min size: {min} bytes")?;
+ }
+ if let Some(max) = spec.max_buffer_size {
+ writeln!(w, " Max size: {max} bytes")?;
+ }
+ if let Some(optimal) = spec.optimal_buffer_size {
+ writeln!(w, " Optimal size: {optimal} bytes")?;
+ }
+ Ok(())
+ }
+
+ fn async_spec(&mut self, w: &mut dyn Write, spec: &AsyncSpec) -> std::io::Result<()> {
+ writeln!(w, "\nAsynchronous Operation:")?;
+ if let Some(modes) = &spec.supported_modes {
+ writeln!(w, " Supported modes: {modes}")?;
+ }
+ if let Some(errno) = spec.nonblock_errno {
+ writeln!(w, " Non-blocking errno: {errno}")?;
+ }
+ Ok(())
+ }
+
+ fn net_data_transfer(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ writeln!(w, "\nNetwork Data Transfer: {desc}")
+ }
+
+ fn begin_capabilities(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ writeln!(w, "\nRequired Capabilities:")
+ }
+
+ fn capability(&mut self, w: &mut dyn Write, cap: &CapabilitySpec) -> std::io::Result<()> {
+ writeln!(w, " {} ({}) - {}", cap.name, cap.capability, cap.action)?;
+ if !cap.allows.is_empty() {
+ writeln!(w, " Allows: {}", cap.allows)?;
+ }
+ if !cap.without_cap.is_empty() {
+ writeln!(w, " Without capability: {}", cap.without_cap)?;
+ }
+ if let Some(cond) = &cap.check_condition {
+ writeln!(w, " Condition: {cond}")?;
+ }
+ Ok(())
+ }
+
+ fn end_capabilities(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ // Signal-related methods
+ fn begin_signals(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nSignal Specifications ({count}):")
+ }
+
+ fn signal(&mut self, w: &mut dyn Write, signal: &SignalSpec) -> std::io::Result<()> {
+ write!(w, " {} ({})", signal.signal_name, signal.signal_num)?;
+
+ // Display direction (bitmask matching C enum kapi_signal_direction)
+ let mut dirs = Vec::new();
+ if signal.direction & 1 != 0 { dirs.push("RECEIVE"); }
+ if signal.direction & 2 != 0 { dirs.push("SEND"); }
+ if signal.direction & 4 != 0 { dirs.push("HANDLE"); }
+ if signal.direction & 8 != 0 { dirs.push("BLOCK"); }
+ if signal.direction & 16 != 0 { dirs.push("IGNORE"); }
+ let direction = if dirs.is_empty() { "UNKNOWN".to_string() } else { dirs.join("|") };
+ write!(w, " - {direction}")?;
+
+ // Display action (matching C enum kapi_signal_action)
+ let action = match signal.action {
+ 0 => "DEFAULT",
+ 1 => "TERMINATE",
+ 2 => "COREDUMP",
+ 3 => "STOP",
+ 4 => "CONTINUE",
+ 5 => "CUSTOM",
+ 6 => "RETURN",
+ 7 => "RESTART",
+ 8 => "QUEUE",
+ 9 => "DISCARD",
+ 10 => "TRANSFORM",
+ _ => "UNKNOWN",
+ };
+ writeln!(w, " - {action}")?;
+
+ if let Some(target) = &signal.target {
+ writeln!(w, " Target: {target}")?;
+ }
+ if let Some(condition) = &signal.condition {
+ writeln!(w, " Condition: {condition}")?;
+ }
+ if let Some(desc) = &signal.description {
+ writeln!(w, " {desc}")?;
+ }
+
+ // Display timing
+ let timing = match signal.timing {
+ 0 => "BEFORE",
+ 1 => "DURING",
+ 2 => "AFTER",
+ 3 => "EXIT",
+ _ => "UNKNOWN",
+ };
+ writeln!(w, " Timing: {timing}")?;
+ writeln!(w, " Priority: {}", signal.priority)?;
+
+ if signal.restartable {
+ writeln!(w, " Restartable: yes")?;
+ }
+ if signal.interruptible {
+ writeln!(w, " Interruptible: yes")?;
+ }
+ if let Some(error) = signal.error_on_signal {
+ writeln!(w, " Error on signal: {error}")?;
+ }
+ Ok(())
+ }
+
+ fn end_signals(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_signal_masks(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nSignal Masks ({count}):")
+ }
+
+ fn signal_mask(&mut self, w: &mut dyn Write, mask: &SignalMaskSpec) -> std::io::Result<()> {
+ writeln!(w, " {}", mask.name)?;
+ if !mask.description.is_empty() {
+ writeln!(w, " {}", mask.description)?;
+ }
+ Ok(())
+ }
+
+ fn end_signal_masks(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ // Side effects and state transitions
+ fn begin_side_effects(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nSide Effects ({count}):")
+ }
+
+ fn side_effect(&mut self, w: &mut dyn Write, effect: &SideEffectSpec) -> std::io::Result<()> {
+ writeln!(w, " {} - {}", effect.target, effect.description)?;
+ if let Some(condition) = &effect.condition {
+ writeln!(w, " Condition: {condition}")?;
+ }
+ if effect.reversible {
+ writeln!(w, " Reversible: yes")?;
+ }
+ Ok(())
+ }
+
+ fn end_side_effects(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_state_transitions(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nState Transitions ({count}):")
+ }
+
+ fn state_transition(
+ &mut self,
+ w: &mut dyn Write,
+ trans: &StateTransitionSpec,
+ ) -> std::io::Result<()> {
+ writeln!(
+ w,
+ " {} : {} -> {}",
+ trans.object, trans.from_state, trans.to_state
+ )?;
+ if let Some(condition) = &trans.condition {
+ writeln!(w, " Condition: {condition}")?;
+ }
+ if !trans.description.is_empty() {
+ writeln!(w, " {}", trans.description)?;
+ }
+ Ok(())
+ }
+
+ fn end_state_transitions(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ // Constraints and locks
+ fn begin_constraints(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nAdditional Constraints ({count}):")
+ }
+
+ fn constraint(
+ &mut self,
+ w: &mut dyn Write,
+ constraint: &ConstraintSpec,
+ ) -> std::io::Result<()> {
+ writeln!(w, " {}", constraint.name)?;
+ if !constraint.description.is_empty() {
+ writeln!(w, " {}", constraint.description)?;
+ }
+ if let Some(expr) = &constraint.expression {
+ writeln!(w, " Expression: {expr}")?;
+ }
+ Ok(())
+ }
+
+ fn end_constraints(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_locks(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nLocking Requirements ({count}):")
+ }
+
+ fn lock(&mut self, w: &mut dyn Write, lock: &LockSpec) -> std::io::Result<()> {
+ write!(w, " {}", lock.lock_name)?;
+
+ // Display lock type
+ let lock_type = match lock.lock_type {
+ 0 => "NONE",
+ 1 => "MUTEX",
+ 2 => "SPINLOCK",
+ 3 => "RWLOCK",
+ 4 => "SEQLOCK",
+ 5 => "RCU",
+ 6 => "SEMAPHORE",
+ 7 => "CUSTOM",
+ _ => "UNKNOWN",
+ };
+ writeln!(w, " ({lock_type})")?;
+
+ let scope_str = match lock.scope {
+ 0 => "acquired and released",
+ 1 => "acquired (not released)",
+ 2 => "released (held on entry)",
+ 3 => "held by caller",
+ _ => "unknown",
+ };
+ writeln!(w, " Scope: {scope_str}")?;
+
+ if !lock.description.is_empty() {
+ writeln!(w, " {}", lock.description)?;
+ }
+ Ok(())
+ }
+
+ fn end_locks(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_struct_specs(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nStructure Specifications ({count}):")
+ }
+
+ fn struct_spec(&mut self, w: &mut dyn Write, spec: &crate::extractor::StructSpec) -> std::io::Result<()> {
+ writeln!(w, " {} (size={}, align={}):", spec.name, spec.size, spec.alignment)?;
+ if !spec.description.is_empty() {
+ writeln!(w, " {}", spec.description)?;
+ }
+
+ if !spec.fields.is_empty() {
+ writeln!(w, " Fields ({}):", spec.field_count)?;
+ for field in &spec.fields {
+ write!(w, " - {} ({}):", field.name, field.type_name)?;
+ if !field.description.is_empty() {
+ write!(w, " {}", field.description)?;
+ }
+ writeln!(w)?;
+
+ // Show constraints if present
+ if field.min_value != 0 || field.max_value != 0 {
+ writeln!(w, " Range: [{}, {}]", field.min_value, field.max_value)?;
+ }
+ if field.valid_mask != 0 {
+ writeln!(w, " Mask: {:#x}", field.valid_mask)?;
+ }
+ }
+ }
+ Ok(())
+ }
+
+ fn end_struct_specs(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::extractor::{ErrorSpec, ParamSpec, ReturnSpec};
+
+ fn render_plain(f: &mut PlainFormatter, sink: &mut Vec<u8>) -> String {
+ f.end_document(sink).unwrap();
+ String::from_utf8(sink.clone()).unwrap()
+ }
+
+ #[test]
+ fn plain_api_list() {
+ let mut f = PlainFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_list(&mut sink, "System Calls").unwrap();
+ f.api_item(&mut sink, "sys_open", "syscall").unwrap();
+ f.api_item(&mut sink, "sys_read", "syscall").unwrap();
+ f.end_api_list(&mut sink).unwrap();
+ f.total_specs(&mut sink, 2).unwrap();
+
+ let out = render_plain(&mut f, &mut sink);
+ assert!(out.contains("sys_open"));
+ assert!(out.contains("sys_read"));
+ assert!(out.contains("Total specifications found: 2"));
+ }
+
+ #[test]
+ fn plain_api_details() {
+ let mut f = PlainFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_details(&mut sink, "sys_test").unwrap();
+ f.description(&mut sink, "A test syscall").unwrap();
+ f.long_description(&mut sink, "Detailed description here").unwrap();
+ f.end_api_details(&mut sink).unwrap();
+
+ let out = render_plain(&mut f, &mut sink);
+ assert!(out.contains("sys_test"));
+ assert!(out.contains("A test syscall"));
+ assert!(out.contains("Detailed description here"));
+ }
+
+ #[test]
+ fn plain_parameters() {
+ let mut f = PlainFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_details(&mut sink, "sys_write").unwrap();
+ f.begin_parameters(&mut sink, 1).unwrap();
+ f.parameter(
+ &mut sink,
+ &ParamSpec {
+ index: 0,
+ name: "fd".to_string(),
+ type_name: "unsigned int".to_string(),
+ description: "file descriptor".to_string(),
+ flags: 1,
+ param_type: 2,
+ constraint_type: 0,
+ constraint: None,
+ min_value: None,
+ max_value: None,
+ valid_mask: None,
+ enum_values: vec![],
+ size: None,
+ alignment: None,
+ },
+ )
+ .unwrap();
+ f.end_parameters(&mut sink).unwrap();
+ f.end_api_details(&mut sink).unwrap();
+
+ let out = render_plain(&mut f, &mut sink);
+ assert!(out.contains("fd"));
+ assert!(out.contains("unsigned int"));
+ assert!(out.contains("file descriptor"));
+ }
+
+ #[test]
+ fn plain_errors() {
+ let mut f = PlainFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_details(&mut sink, "sys_test").unwrap();
+ f.begin_errors(&mut sink, 1).unwrap();
+ f.error(
+ &mut sink,
+ &ErrorSpec {
+ error_code: -2,
+ name: "ENOENT".to_string(),
+ condition: "File not found".to_string(),
+ description: "The file does not exist".to_string(),
+ },
+ )
+ .unwrap();
+ f.end_errors(&mut sink).unwrap();
+ f.end_api_details(&mut sink).unwrap();
+
+ let out = render_plain(&mut f, &mut sink);
+ assert!(out.contains("ENOENT"));
+ assert!(out.contains("-2"));
+ assert!(out.contains("File not found"));
+ }
+
+ #[test]
+ fn plain_return_spec() {
+ let mut f = PlainFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_details(&mut sink, "sys_test").unwrap();
+ f.return_spec(
+ &mut sink,
+ &ReturnSpec {
+ type_name: "KAPI_TYPE_INT".to_string(),
+ description: "Returns 0 on success".to_string(),
+ return_type: 1,
+ check_type: 0,
+ success_value: Some(0),
+ success_min: None,
+ success_max: None,
+ error_values: vec![],
+ },
+ )
+ .unwrap();
+ f.end_api_details(&mut sink).unwrap();
+
+ let out = render_plain(&mut f, &mut sink);
+ assert!(out.contains("KAPI_TYPE_INT"));
+ assert!(out.contains("Returns 0 on success"));
+ }
+
+ #[test]
+ fn plain_context_flags() {
+ let mut f = PlainFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_details(&mut sink, "sys_test").unwrap();
+ f.begin_context_flags(&mut sink).unwrap();
+ f.context_flag(&mut sink, "KAPI_CTX_PROCESS").unwrap();
+ f.context_flag(&mut sink, "KAPI_CTX_SLEEPABLE").unwrap();
+ f.end_context_flags(&mut sink).unwrap();
+ f.end_api_details(&mut sink).unwrap();
+
+ let out = render_plain(&mut f, &mut sink);
+ assert!(out.contains("KAPI_CTX_PROCESS"));
+ assert!(out.contains("KAPI_CTX_SLEEPABLE"));
+ }
+}
diff --git a/tools/kapi/src/formatter/rst.rs b/tools/kapi/src/formatter/rst.rs
new file mode 100644
index 0000000000000..21c5f95e60e93
--- /dev/null
+++ b/tools/kapi/src/formatter/rst.rs
@@ -0,0 +1,852 @@
+use super::OutputFormatter;
+use crate::extractor::{
+ AddrFamilySpec, AsyncSpec, BufferSpec, CapabilitySpec, ConstraintSpec, ErrorSpec, LockSpec,
+ ParamSpec, ProtocolBehaviorSpec, ReturnSpec, SideEffectSpec, SignalMaskSpec, SignalSpec,
+ SocketStateSpec, StateTransitionSpec,
+};
+use std::io::Write;
+
+pub struct RstFormatter;
+
+impl RstFormatter {
+ pub fn new() -> Self {
+ RstFormatter
+ }
+
+ fn section_char(level: usize) -> char {
+ match level {
+ 0 => '=',
+ 1 => '-',
+ 2 => '~',
+ 3 => '^',
+ _ => '"',
+ }
+ }
+}
+
+impl OutputFormatter for RstFormatter {
+ fn begin_document(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn end_document(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_api_list(&mut self, w: &mut dyn Write, title: &str) -> std::io::Result<()> {
+ writeln!(w, "\n{title}")?;
+ writeln!(
+ w,
+ "{}",
+ Self::section_char(0).to_string().repeat(title.len())
+ )?;
+ writeln!(w)
+ }
+
+ fn api_item(&mut self, w: &mut dyn Write, name: &str, api_type: &str) -> std::io::Result<()> {
+ writeln!(w, "* **{name}** (*{api_type}*)")
+ }
+
+ fn end_api_list(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn total_specs(&mut self, w: &mut dyn Write, count: usize) -> std::io::Result<()> {
+ writeln!(w, "\n**Total specifications found:** {count}")
+ }
+
+ fn begin_api_details(&mut self, w: &mut dyn Write, name: &str) -> std::io::Result<()> {
+
+ writeln!(w, "\n{name}")?;
+ writeln!(
+ w,
+ "{}",
+ Self::section_char(0).to_string().repeat(name.len())
+ )?;
+ writeln!(w)
+ }
+
+ fn end_api_details(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn description(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ writeln!(w, "**{desc}**")?;
+ writeln!(w)
+ }
+
+ fn long_description(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ writeln!(w, "{desc}")?;
+ writeln!(w)
+ }
+
+ fn begin_context_flags(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+
+ let title = "Execution Context";
+ writeln!(w, "{title}")?;
+ writeln!(
+ w,
+ "{}",
+ Self::section_char(1).to_string().repeat(title.len())
+ )?;
+ writeln!(w)
+ }
+
+ fn context_flag(&mut self, w: &mut dyn Write, flag: &str) -> std::io::Result<()> {
+ writeln!(w, "* {flag}")
+ }
+
+ fn end_context_flags(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ writeln!(w)
+ }
+
+ fn begin_parameters(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+
+ let title = format!("Parameters ({count})");
+ writeln!(w, "{title}")?;
+ writeln!(
+ w,
+ "{}",
+ Self::section_char(1).to_string().repeat(title.len())
+ )?;
+ writeln!(w)
+ }
+
+ fn end_parameters(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_errors(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+
+ let title = format!("Possible Errors ({count})");
+ writeln!(w, "{title}")?;
+ writeln!(
+ w,
+ "{}",
+ Self::section_char(1).to_string().repeat(title.len())
+ )?;
+ writeln!(w)
+ }
+
+ fn end_errors(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn examples(&mut self, w: &mut dyn Write, examples: &str) -> std::io::Result<()> {
+
+ let title = "Examples";
+ writeln!(w, "{title}")?;
+ writeln!(
+ w,
+ "{}",
+ Self::section_char(1).to_string().repeat(title.len())
+ )?;
+ writeln!(w)?;
+ writeln!(w, ".. code-block:: c")?;
+ writeln!(w)?;
+ for line in examples.lines() {
+ writeln!(w, " {line}")?;
+ }
+ writeln!(w)
+ }
+
+ fn notes(&mut self, w: &mut dyn Write, notes: &str) -> std::io::Result<()> {
+
+ let title = "Notes";
+ writeln!(w, "{title}")?;
+ writeln!(
+ w,
+ "{}",
+ Self::section_char(1).to_string().repeat(title.len())
+ )?;
+ writeln!(w)?;
+ writeln!(w, "{notes}")?;
+ writeln!(w)
+ }
+
+ fn since_version(&mut self, w: &mut dyn Write, version: &str) -> std::io::Result<()> {
+ writeln!(w, ":Available since: {version}")?;
+ writeln!(w)
+ }
+
+ fn sysfs_subsystem(&mut self, w: &mut dyn Write, subsystem: &str) -> std::io::Result<()> {
+ writeln!(w, ":Subsystem: {subsystem}")?;
+ writeln!(w)
+ }
+
+ fn sysfs_path(&mut self, w: &mut dyn Write, path: &str) -> std::io::Result<()> {
+ writeln!(w, ":Sysfs Path: {path}")?;
+ writeln!(w)
+ }
+
+ fn sysfs_permissions(&mut self, w: &mut dyn Write, perms: &str) -> std::io::Result<()> {
+ writeln!(w, ":Permissions: {perms}")?;
+ writeln!(w)
+ }
+
+ // Networking-specific methods
+ fn socket_state(&mut self, w: &mut dyn Write, state: &SocketStateSpec) -> std::io::Result<()> {
+
+ let title = "Socket State Requirements";
+ writeln!(w, "{title}")?;
+ writeln!(
+ w,
+ "{}",
+ Self::section_char(1).to_string().repeat(title.len())
+ )?;
+ writeln!(w)?;
+
+ if !state.required_states.is_empty() {
+ writeln!(
+ w,
+ "**Required states:** {}",
+ state.required_states.join(", ")
+ )?;
+ }
+ if !state.forbidden_states.is_empty() {
+ writeln!(
+ w,
+ "**Forbidden states:** {}",
+ state.forbidden_states.join(", ")
+ )?;
+ }
+ if let Some(result) = &state.resulting_state {
+ writeln!(w, "**Resulting state:** {result}")?;
+ }
+ if let Some(cond) = &state.condition {
+ writeln!(w, "**Condition:** {cond}")?;
+ }
+ if let Some(protos) = &state.applicable_protocols {
+ writeln!(w, "**Applicable protocols:** {protos}")?;
+ }
+ writeln!(w)
+ }
+
+ fn begin_protocol_behaviors(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+
+ let title = "Protocol-Specific Behaviors";
+ writeln!(w, "{title}")?;
+ writeln!(
+ w,
+ "{}",
+ Self::section_char(1).to_string().repeat(title.len())
+ )?;
+ writeln!(w)
+ }
+
+ fn protocol_behavior(
+ &mut self,
+ w: &mut dyn Write,
+ behavior: &ProtocolBehaviorSpec,
+ ) -> std::io::Result<()> {
+ writeln!(w, "**{}**", behavior.applicable_protocols)?;
+ writeln!(w)?;
+ writeln!(w, "{}", behavior.behavior)?;
+ if let Some(flags) = &behavior.protocol_flags {
+ writeln!(w)?;
+ writeln!(w, "*Flags:* {flags}")?;
+ }
+ writeln!(w)
+ }
+
+ fn end_protocol_behaviors(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_addr_families(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+
+ let title = "Supported Address Families";
+ writeln!(w, "{title}")?;
+ writeln!(
+ w,
+ "{}",
+ Self::section_char(1).to_string().repeat(title.len())
+ )?;
+ writeln!(w)
+ }
+
+ fn addr_family(&mut self, w: &mut dyn Write, family: &AddrFamilySpec) -> std::io::Result<()> {
+ writeln!(w, "**{} ({})**", family.family_name, family.family)?;
+ writeln!(w)?;
+ writeln!(w, "* **Struct size:** {} bytes", family.addr_struct_size)?;
+ writeln!(
+ w,
+ "* **Address length:** {}-{} bytes",
+ family.min_addr_len, family.max_addr_len
+ )?;
+ if let Some(format) = &family.addr_format {
+ writeln!(w, "* **Format:** ``{format}``")?;
+ }
+ writeln!(
+ w,
+ "* **Features:** wildcard={}, multicast={}, broadcast={}",
+ family.supports_wildcard, family.supports_multicast, family.supports_broadcast
+ )?;
+ if let Some(special) = &family.special_addresses {
+ writeln!(w, "* **Special addresses:** {special}")?;
+ }
+ if family.port_range_max > 0 {
+ writeln!(
+ w,
+ "* **Port range:** {}-{}",
+ family.port_range_min, family.port_range_max
+ )?;
+ }
+ writeln!(w)
+ }
+
+ fn end_addr_families(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn buffer_spec(&mut self, w: &mut dyn Write, spec: &BufferSpec) -> std::io::Result<()> {
+
+ let title = "Buffer Specification";
+ writeln!(w, "{title}")?;
+ writeln!(
+ w,
+ "{}",
+ Self::section_char(1).to_string().repeat(title.len())
+ )?;
+ writeln!(w)?;
+
+ if let Some(behaviors) = &spec.buffer_behaviors {
+ writeln!(w, "**Behaviors:** {behaviors}")?;
+ }
+ if let Some(min) = spec.min_buffer_size {
+ writeln!(w, "**Min size:** {min} bytes")?;
+ }
+ if let Some(max) = spec.max_buffer_size {
+ writeln!(w, "**Max size:** {max} bytes")?;
+ }
+ if let Some(optimal) = spec.optimal_buffer_size {
+ writeln!(w, "**Optimal size:** {optimal} bytes")?;
+ }
+ writeln!(w)
+ }
+
+ fn async_spec(&mut self, w: &mut dyn Write, spec: &AsyncSpec) -> std::io::Result<()> {
+
+ let title = "Asynchronous Operation";
+ writeln!(w, "{title}")?;
+ writeln!(
+ w,
+ "{}",
+ Self::section_char(1).to_string().repeat(title.len())
+ )?;
+ writeln!(w)?;
+
+ if let Some(modes) = &spec.supported_modes {
+ writeln!(w, "**Supported modes:** {modes}")?;
+ }
+ if let Some(errno) = spec.nonblock_errno {
+ writeln!(w, "**Non-blocking errno:** {errno}")?;
+ }
+ writeln!(w)
+ }
+
+ fn net_data_transfer(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ writeln!(w, "**Network Data Transfer:** {desc}")?;
+ writeln!(w)
+ }
+
+ fn begin_capabilities(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+
+ let title = "Required Capabilities";
+ writeln!(w, "{title}")?;
+ writeln!(
+ w,
+ "{}",
+ Self::section_char(1).to_string().repeat(title.len())
+ )?;
+ writeln!(w)
+ }
+
+ fn capability(&mut self, w: &mut dyn Write, cap: &CapabilitySpec) -> std::io::Result<()> {
+ writeln!(w, "**{} ({})** - {}", cap.name, cap.capability, cap.action)?;
+ writeln!(w)?;
+ if !cap.allows.is_empty() {
+ writeln!(w, "* **Allows:** {}", cap.allows)?;
+ }
+ if !cap.without_cap.is_empty() {
+ writeln!(w, "* **Without capability:** {}", cap.without_cap)?;
+ }
+ if let Some(cond) = &cap.check_condition {
+ writeln!(w, "* **Condition:** {}", cond)?;
+ }
+ writeln!(w)
+ }
+
+ fn end_capabilities(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn parameter(&mut self, w: &mut dyn Write, param: &ParamSpec) -> std::io::Result<()> {
+ writeln!(
+ w,
+ "**[{}] {}** (*{}*)",
+ param.index, param.name, param.type_name
+ )?;
+ writeln!(w)?;
+ writeln!(w, " {}", param.description)?;
+
+ // Display flags
+ let mut flags = Vec::new();
+ if param.flags & 0x01 != 0 {
+ flags.push("IN");
+ }
+ if param.flags & 0x02 != 0 {
+ flags.push("OUT");
+ }
+ if param.flags & 0x04 != 0 {
+ flags.push("USER");
+ }
+ if param.flags & 0x08 != 0 {
+ flags.push("OPTIONAL");
+ }
+ if !flags.is_empty() {
+ writeln!(w, " :Flags: {}", flags.join(", "))?;
+ }
+
+ if let Some(constraint) = ¶m.constraint {
+ writeln!(w, " :Constraint: {}", constraint)?;
+ }
+
+ if let (Some(min), Some(max)) = (param.min_value, param.max_value) {
+ writeln!(w, " :Range: {} to {}", min, max)?;
+ }
+
+ writeln!(w)
+ }
+
+ fn return_spec(&mut self, w: &mut dyn Write, ret: &ReturnSpec) -> std::io::Result<()> {
+ writeln!(w, "\nReturn Value")?;
+ writeln!(w, "{}\n", Self::section_char(1).to_string().repeat(12))?;
+ writeln!(w)?;
+ writeln!(w, ":Type: {}", ret.type_name)?;
+ writeln!(w, ":Description: {}", ret.description)?;
+ if let Some(success) = ret.success_value {
+ writeln!(w, ":Success value: {}", success)?;
+ }
+ writeln!(w)
+ }
+
+ fn error(&mut self, w: &mut dyn Write, error: &ErrorSpec) -> std::io::Result<()> {
+ writeln!(w, "**{}** ({})", error.name, error.error_code)?;
+ writeln!(w)?;
+ writeln!(w, " :Condition: {}", error.condition)?;
+ if !error.description.is_empty() {
+ writeln!(w, " :Description: {}", error.description)?;
+ }
+ writeln!(w)
+ }
+
+ fn begin_signals(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+
+ let title = format!("Signals ({count})");
+ writeln!(w, "{title}")?;
+ writeln!(
+ w,
+ "{}",
+ Self::section_char(1).to_string().repeat(title.len())
+ )?;
+ writeln!(w)
+ }
+
+ fn signal(&mut self, w: &mut dyn Write, signal: &SignalSpec) -> std::io::Result<()> {
+ write!(w, "* **{}**", signal.signal_name)?;
+ if signal.signal_num != 0 {
+ write!(w, " ({})", signal.signal_num)?;
+ }
+ writeln!(w)?;
+
+ // Direction (bitmask matching C enum kapi_signal_direction)
+ let mut dirs = Vec::new();
+ if signal.direction & 1 != 0 { dirs.push("receive"); }
+ if signal.direction & 2 != 0 { dirs.push("send"); }
+ if signal.direction & 4 != 0 { dirs.push("handle"); }
+ if signal.direction & 8 != 0 { dirs.push("block"); }
+ if signal.direction & 16 != 0 { dirs.push("ignore"); }
+ let direction = if dirs.is_empty() { "unknown".to_string() } else { dirs.join(", ") };
+ writeln!(w, " :Direction: {}", direction)?;
+
+ // Action (matching C enum kapi_signal_action)
+ let action = match signal.action {
+ 0 => "default",
+ 1 => "terminate",
+ 2 => "coredump",
+ 3 => "stop",
+ 4 => "continue",
+ 5 => "custom",
+ 6 => "return",
+ 7 => "restart",
+ 8 => "queue",
+ 9 => "discard",
+ 10 => "transform",
+ _ => "unknown",
+ };
+ writeln!(w, " :Action: {}", action)?;
+
+ if let Some(cond) = &signal.condition {
+ writeln!(w, " :Condition: {}", cond)?;
+ }
+ if let Some(desc) = &signal.description {
+ writeln!(w, " :Description: {}", desc)?;
+ }
+ if signal.interruptible {
+ writeln!(w, " :Interruptible: yes")?;
+ }
+ if signal.restartable {
+ writeln!(w, " :Restartable: yes")?;
+ }
+ writeln!(w)
+ }
+
+ fn end_signals(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_signal_masks(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+
+ let title = format!("Signal Masks ({count})");
+ writeln!(w, "{title}")?;
+ writeln!(
+ w,
+ "{}",
+ Self::section_char(1).to_string().repeat(title.len())
+ )?;
+ writeln!(w)
+ }
+
+ fn signal_mask(&mut self, w: &mut dyn Write, mask: &SignalMaskSpec) -> std::io::Result<()> {
+ writeln!(w, "* **{}**", mask.name)?;
+ if !mask.description.is_empty() {
+ writeln!(w, " {}", mask.description)?;
+ }
+ writeln!(w)
+ }
+
+ fn end_signal_masks(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_side_effects(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+
+ let title = format!("Side Effects ({count})");
+ writeln!(w, "{}\n", title)?;
+ writeln!(
+ w,
+ "{}\n",
+ Self::section_char(1).to_string().repeat(title.len())
+ )
+ }
+
+ fn side_effect(&mut self, w: &mut dyn Write, effect: &SideEffectSpec) -> std::io::Result<()> {
+ write!(w, "* **{}**", effect.target)?;
+ if effect.reversible {
+ write!(w, " *(reversible)*")?;
+ }
+ writeln!(w)?;
+ writeln!(w, " {}", effect.description)?;
+ if let Some(cond) = &effect.condition {
+ writeln!(w, " :Condition: {}", cond)?;
+ }
+ writeln!(w)
+ }
+
+ fn end_side_effects(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_state_transitions(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+
+ let title = format!("State Transitions ({count})");
+ writeln!(w, "{}\n", title)?;
+ writeln!(
+ w,
+ "{}\n",
+ Self::section_char(1).to_string().repeat(title.len())
+ )
+ }
+
+ fn state_transition(
+ &mut self,
+ w: &mut dyn Write,
+ trans: &StateTransitionSpec,
+ ) -> std::io::Result<()> {
+ writeln!(
+ w,
+ "* **{}**: {} → {}",
+ trans.object, trans.from_state, trans.to_state
+ )?;
+ writeln!(w, " {}", trans.description)?;
+ if let Some(cond) = &trans.condition {
+ writeln!(w, " :Condition: {}", cond)?;
+ }
+ writeln!(w)
+ }
+
+ fn end_state_transitions(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_constraints(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+
+ let title = format!("Constraints ({count})");
+ writeln!(w, "{title}")?;
+ writeln!(
+ w,
+ "{}",
+ Self::section_char(1).to_string().repeat(title.len())
+ )?;
+ writeln!(w)
+ }
+
+ fn constraint(
+ &mut self,
+ w: &mut dyn Write,
+ constraint: &ConstraintSpec,
+ ) -> std::io::Result<()> {
+ writeln!(w, "* **{}**", constraint.name)?;
+ if !constraint.description.is_empty() {
+ writeln!(w, " {}", constraint.description)?;
+ }
+ if let Some(expr) = &constraint.expression {
+ writeln!(w, " :Expression: ``{}``", expr)?;
+ }
+ writeln!(w)
+ }
+
+ fn end_constraints(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_locks(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+
+ let title = format!("Locks ({count})");
+ writeln!(w, "{}\n", title)?;
+ writeln!(
+ w,
+ "{}\n",
+ Self::section_char(1).to_string().repeat(title.len())
+ )
+ }
+
+ fn lock(&mut self, w: &mut dyn Write, lock: &LockSpec) -> std::io::Result<()> {
+ write!(w, "* **{}**", lock.lock_name)?;
+ let lock_type_str = match lock.lock_type {
+ 1 => " *(mutex)*",
+ 2 => " *(spinlock)*",
+ 3 => " *(rwlock)*",
+ 4 => " *(semaphore)*",
+ 5 => " *(RCU)*",
+ _ => "",
+ };
+ writeln!(w, "{}", lock_type_str)?;
+ if !lock.description.is_empty() {
+ writeln!(w, " {}", lock.description)?;
+ }
+ writeln!(w)
+ }
+
+ fn end_locks(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_struct_specs(&mut self, w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ writeln!(w)?;
+ writeln!(w, "Structure Specifications")?;
+ writeln!(w, "~~~~~~~~~~~~~~~~~~~~~~~")?;
+ writeln!(w)
+ }
+
+ fn struct_spec(&mut self, w: &mut dyn Write, spec: &crate::extractor::StructSpec) -> std::io::Result<()> {
+ writeln!(w, "**{}**", spec.name)?;
+ writeln!(w)?;
+
+ if !spec.description.is_empty() {
+ writeln!(w, " {}", spec.description)?;
+ writeln!(w)?;
+ }
+
+ writeln!(w, " :Size: {} bytes", spec.size)?;
+ writeln!(w, " :Alignment: {} bytes", spec.alignment)?;
+ writeln!(w, " :Fields: {}", spec.field_count)?;
+ writeln!(w)?;
+
+ if !spec.fields.is_empty() {
+ for field in &spec.fields {
+ writeln!(w, " * **{}** ({})", field.name, field.type_name)?;
+ if !field.description.is_empty() {
+ writeln!(w, " {}", field.description)?;
+ }
+ if field.min_value != 0 || field.max_value != 0 {
+ writeln!(w, " Range: [{}, {}]", field.min_value, field.max_value)?;
+ }
+ }
+ writeln!(w)?;
+ }
+
+ Ok(())
+ }
+
+ fn end_struct_specs(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::extractor::{ErrorSpec, ParamSpec, ReturnSpec};
+
+ fn render_rst(f: &mut RstFormatter, sink: &mut Vec<u8>) -> String {
+ f.end_document(sink).unwrap();
+ String::from_utf8(sink.clone()).unwrap()
+ }
+
+ #[test]
+ fn rst_api_details_has_heading() {
+ let mut f = RstFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_details(&mut sink, "sys_test").unwrap();
+ f.description(&mut sink, "A test syscall").unwrap();
+ f.end_api_details(&mut sink).unwrap();
+
+ let out = render_rst(&mut f, &mut sink);
+ assert!(out.contains("sys_test"));
+ assert!(out.contains("========"));
+ assert!(out.contains("**A test syscall**"));
+ }
+
+ #[test]
+ fn rst_api_list() {
+ let mut f = RstFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_list(&mut sink, "System Calls").unwrap();
+ f.api_item(&mut sink, "sys_open", "syscall").unwrap();
+ f.api_item(&mut sink, "sys_read", "syscall").unwrap();
+ f.end_api_list(&mut sink).unwrap();
+ f.total_specs(&mut sink, 2).unwrap();
+
+ let out = render_rst(&mut f, &mut sink);
+ assert!(out.contains("sys_open"));
+ assert!(out.contains("sys_read"));
+ }
+
+ #[test]
+ fn rst_parameters() {
+ let mut f = RstFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_details(&mut sink, "sys_write").unwrap();
+ f.begin_parameters(&mut sink, 1).unwrap();
+ f.parameter(
+ &mut sink,
+ &ParamSpec {
+ index: 0,
+ name: "fd".to_string(),
+ type_name: "unsigned int".to_string(),
+ description: "file descriptor".to_string(),
+ flags: 1,
+ param_type: 2,
+ constraint_type: 0,
+ constraint: None,
+ min_value: None,
+ max_value: None,
+ valid_mask: None,
+ enum_values: vec![],
+ size: None,
+ alignment: None,
+ },
+ )
+ .unwrap();
+ f.end_parameters(&mut sink).unwrap();
+ f.end_api_details(&mut sink).unwrap();
+
+ let out = render_rst(&mut f, &mut sink);
+ assert!(out.contains("**[0] fd**"));
+ assert!(out.contains("unsigned int"));
+ assert!(out.contains("file descriptor"));
+ }
+
+ #[test]
+ fn rst_errors() {
+ let mut f = RstFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_details(&mut sink, "sys_test").unwrap();
+ f.begin_errors(&mut sink, 1).unwrap();
+ f.error(
+ &mut sink,
+ &ErrorSpec {
+ error_code: -2,
+ name: "ENOENT".to_string(),
+ condition: "File not found".to_string(),
+ description: "The file does not exist".to_string(),
+ },
+ )
+ .unwrap();
+ f.end_errors(&mut sink).unwrap();
+ f.end_api_details(&mut sink).unwrap();
+
+ let out = render_rst(&mut f, &mut sink);
+ assert!(out.contains("**ENOENT**"));
+ assert!(out.contains("-2"));
+ assert!(out.contains("File not found"));
+ }
+
+ #[test]
+ fn rst_return_spec() {
+ let mut f = RstFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_details(&mut sink, "sys_test").unwrap();
+ f.return_spec(
+ &mut sink,
+ &ReturnSpec {
+ type_name: "KAPI_TYPE_INT".to_string(),
+ description: "Returns 0 on success".to_string(),
+ return_type: 1,
+ check_type: 0,
+ success_value: Some(0),
+ success_min: None,
+ success_max: None,
+ error_values: vec![],
+ },
+ )
+ .unwrap();
+ f.end_api_details(&mut sink).unwrap();
+
+ let out = render_rst(&mut f, &mut sink);
+ assert!(out.contains("KAPI_TYPE_INT"));
+ assert!(out.contains("Returns 0 on success"));
+ assert!(out.contains("Return Value"));
+ }
+
+ #[test]
+ fn rst_context_flags() {
+ let mut f = RstFormatter::new();
+ let mut sink = Vec::new();
+
+ f.begin_document(&mut sink).unwrap();
+ f.begin_api_details(&mut sink, "sys_test").unwrap();
+ f.begin_context_flags(&mut sink).unwrap();
+ f.context_flag(&mut sink, "KAPI_CTX_PROCESS").unwrap();
+ f.context_flag(&mut sink, "KAPI_CTX_SLEEPABLE").unwrap();
+ f.end_context_flags(&mut sink).unwrap();
+ f.end_api_details(&mut sink).unwrap();
+
+ let out = render_rst(&mut f, &mut sink);
+ assert!(out.contains("KAPI_CTX_PROCESS"));
+ assert!(out.contains("KAPI_CTX_SLEEPABLE"));
+ assert!(out.contains("Execution Context"));
+ }
+}
diff --git a/tools/kapi/src/main.rs b/tools/kapi/src/main.rs
new file mode 100644
index 0000000000000..ceaf174481e3c
--- /dev/null
+++ b/tools/kapi/src/main.rs
@@ -0,0 +1,119 @@
+//! kapi - Kernel API Specification Tool
+//!
+//! This tool extracts and displays kernel API specifications from multiple sources:
+//! - Kernel source code (KAPI macros)
+//! - Compiled vmlinux binaries (`.kapi_specs` ELF section)
+//! - Running kernel via debugfs
+
+use anyhow::Result;
+use clap::Parser;
+use std::io::{self, Write};
+
+mod extractor;
+mod formatter;
+
+use extractor::{ApiExtractor, DebugfsExtractor, SourceExtractor, VmlinuxExtractor};
+use formatter::{OutputFormat, create_formatter};
+
+#[derive(Parser, Debug)]
+#[command(author, version, about, long_about = None)]
+struct Args {
+ /// Path to the vmlinux file
+ #[arg(long, value_name = "PATH", group = "input")]
+ vmlinux: Option<String>,
+
+ /// Path to kernel source directory or file
+ #[arg(long, value_name = "PATH", group = "input")]
+ source: Option<String>,
+
+ /// Path to debugfs (defaults to /sys/kernel/debug if not specified)
+ #[arg(long, value_name = "PATH", group = "input")]
+ debugfs: Option<String>,
+
+ /// Optional: Name of specific API to show details for
+ api_name: Option<String>,
+
+ /// Output format
+ #[arg(long, short = 'f', default_value = "plain")]
+ format: String,
+}
+
+fn main() -> Result<()> {
+ let args = Args::parse();
+
+ let output_format: OutputFormat = args
+ .format
+ .parse()
+ .map_err(|e: String| anyhow::anyhow!(e))?;
+
+ let extractor: Box<dyn ApiExtractor> = match (&args.vmlinux, &args.source, &args.debugfs) {
+ (Some(vmlinux_path), None, None) => Box::new(VmlinuxExtractor::new(vmlinux_path)?),
+ (None, Some(source_path), None) => Box::new(SourceExtractor::new(source_path)?),
+ (None, None, Some(_) | None) => {
+ // If debugfs is specified or no input is provided, use debugfs
+ Box::new(DebugfsExtractor::new(args.debugfs.clone())?)
+ }
+ _ => {
+ anyhow::bail!("Please specify only one of --vmlinux, --source, or --debugfs")
+ }
+ };
+
+ display_apis(extractor.as_ref(), args.api_name, output_format)
+}
+
+fn display_apis(
+ extractor: &dyn ApiExtractor,
+ api_name: Option<String>,
+ output_format: OutputFormat,
+) -> Result<()> {
+ let mut formatter = create_formatter(output_format);
+ let mut stdout = io::stdout();
+
+ formatter.begin_document(&mut stdout)?;
+
+ if let Some(api_name_req) = api_name {
+ // Use the extractor to display API details
+ if let Some(_spec) = extractor.extract_by_name(&api_name_req)? {
+ extractor.display_api_details(&api_name_req, &mut *formatter, &mut stdout)?;
+ } else {
+ eprintln!("API '{}' not found.", api_name_req);
+ if output_format == OutputFormat::Plain {
+ writeln!(stdout, "\nAvailable APIs:")?;
+ for spec in extractor.extract_all()? {
+ writeln!(stdout, " {} ({})", spec.name, spec.api_type)?;
+ }
+ }
+ std::process::exit(1);
+ }
+ } else {
+ // Display list of APIs using the extractor
+ let all_specs = extractor.extract_all()?;
+
+ // Helper to display API list for a specific type
+ let mut display_api_type = |api_type: &str, title: &str| -> Result<()> {
+ let filtered: Vec<_> = all_specs.iter()
+ .filter(|s| s.api_type == api_type)
+ .collect();
+
+ if !filtered.is_empty() {
+ formatter.begin_api_list(&mut stdout, title)?;
+ for spec in filtered {
+ formatter.api_item(&mut stdout, &spec.name, &spec.api_type)?;
+ }
+ formatter.end_api_list(&mut stdout)?;
+ }
+ Ok(())
+ };
+
+ display_api_type("syscall", "System Calls")?;
+ display_api_type("ioctl", "IOCTLs")?;
+ display_api_type("function", "Functions")?;
+ display_api_type("sysfs", "Sysfs Attributes")?;
+
+ formatter.total_specs(&mut stdout, all_specs.len())?;
+ }
+
+ formatter.end_document(&mut stdout)?;
+
+ Ok(())
+}
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH 5/9] kernel/api: add API specification for sys_open
2026-03-13 15:09 [PATCH 0/9] Kernel API Specification Framework Sasha Levin
` (3 preceding siblings ...)
2026-03-13 15:09 ` [PATCH 4/9] tools/kapi: Add kernel API specification extraction tool Sasha Levin
@ 2026-03-13 15:09 ` Sasha Levin
2026-03-13 15:33 ` Greg Kroah-Hartman
2026-03-13 15:09 ` [PATCH 6/9] kernel/api: add API specification for sys_close Sasha Levin
` (4 subsequent siblings)
9 siblings, 1 reply; 37+ messages in thread
From: Sasha Levin @ 2026-03-13 15:09 UTC (permalink / raw)
To: linux-api, linux-kernel
Cc: linux-doc, linux-fsdevel, linux-kbuild, linux-kselftest,
workflows, tools, x86, Thomas Gleixner, Paul E. McKenney,
Greg Kroah-Hartman, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann, Sasha Levin
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/open.c | 329 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 329 insertions(+)
diff --git a/fs/open.c b/fs/open.c
index 91f1139591abe..46641176483a3 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -1373,6 +1373,330 @@ int do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
}
+/**
+ * sys_open - Open or create a file
+ * @filename: Pathname of the file to open or create
+ * @flags: File access mode and behavior flags (O_RDONLY, O_WRONLY, O_RDWR, etc.)
+ * @mode: File permission bits for newly created files (only with O_CREAT/O_TMPFILE)
+ *
+ * long-desc: Opens the file specified by pathname. If O_CREAT or O_TMPFILE is
+ * specified in flags, the file is created if it does not exist; its mode is
+ * set according to the mode parameter modified by the process's umask.
+ *
+ * The flags argument must include one of the following access modes: O_RDONLY
+ * (read-only), O_WRONLY (write-only), or O_RDWR (read/write). These are the
+ * low-order two bits of flags. In addition, zero or more file creation and
+ * file status flags can be bitwise-ORed in flags.
+ *
+ * File creation flags: O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC, O_DIRECTORY,
+ * O_NOFOLLOW, O_CLOEXEC, O_TMPFILE. These flags affect open behavior.
+ *
+ * File status flags: O_APPEND, FASYNC, O_DIRECT, O_DSYNC, O_LARGEFILE,
+ * O_NOATIME, O_NONBLOCK (O_NDELAY), O_PATH, O_SYNC. These become part of the
+ * file's open file description and can be retrieved/modified with fcntl().
+ *
+ * The return value is a file descriptor, a small nonnegative integer used in
+ * subsequent system calls (read, write, lseek, fcntl, etc.) to refer to the
+ * open file. The file descriptor returned by a successful open is the lowest-
+ * numbered file descriptor not currently open for the process.
+ *
+ * On 64-bit systems, O_LARGEFILE is automatically added to the flags. On 32-bit
+ * systems, files larger than 2GB require O_LARGEFILE to be explicitly set.
+ *
+ * This syscall is a legacy interface. Modern code should prefer openat() for
+ * relative path operations and openat2() for additional control via resolve
+ * flags. The open() call is equivalent to openat(AT_FDCWD, pathname, flags).
+ *
+ * context-flags: KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE
+ *
+ * param: filename
+ * type: KAPI_TYPE_PATH
+ * flags: KAPI_PARAM_IN | KAPI_PARAM_USER
+ * constraint-type: KAPI_CONSTRAINT_USER_PATH
+ * cdesc: Must be a valid null-terminated path string in user memory.
+ * Maximum path length is PATH_MAX (4096 bytes) including null terminator.
+ * For relative paths, resolution starts from current working directory.
+ * The path is followed (symlinks resolved) unless O_NOFOLLOW is specified.
+ *
+ * param: flags
+ * type: KAPI_TYPE_INT
+ * flags: KAPI_PARAM_IN
+ * constraint-type: KAPI_CONSTRAINT_MASK
+ * valid-mask: O_RDONLY | O_WRONLY | O_RDWR | O_CREAT | O_EXCL | O_NOCTTY |
+ * O_TRUNC | O_APPEND | O_NONBLOCK | O_DSYNC | O_SYNC | FASYNC |
+ * O_DIRECT | O_LARGEFILE | O_DIRECTORY | O_NOFOLLOW | O_NOATIME |
+ * O_CLOEXEC | O_PATH | O_TMPFILE
+ * cdesc: Must include exactly one of O_RDONLY (0), O_WRONLY (1), or
+ * O_RDWR (2) as the access mode. Additional flags may be ORed. Invalid flag
+ * combinations (e.g., O_PATH with incompatible flags, O_TMPFILE without
+ * O_DIRECTORY, O_TMPFILE with read-only mode) return EINVAL. Since Linux
+ * 6.7, O_CREAT is silently ignored when combined with O_DIRECTORY. Unknown
+ * flags are silently ignored for backward compatibility (unlike openat2
+ * which rejects them).
+ *
+ * param: mode
+ * type: KAPI_TYPE_UINT
+ * flags: KAPI_PARAM_IN
+ * constraint-type: KAPI_CONSTRAINT_MASK
+ * valid-mask: S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO
+ * cdesc: Only meaningful when O_CREAT or O_TMPFILE is specified in
+ * flags. Specifies the file mode bits (permissions and setuid/setgid/sticky
+ * bits) for a newly created file. The effective mode is (mode & ~umask).
+ * When O_CREAT/O_TMPFILE is not set, mode is ignored. Mode values exceeding
+ * S_IALLUGO (07777) are masked off.
+ *
+ * return:
+ * type: KAPI_TYPE_INT
+ * check-type: KAPI_RETURN_FD
+ * success: >= 0
+ * desc: On success, returns a new file descriptor (non-negative integer).
+ * The returned file descriptor is the lowest-numbered descriptor not
+ * currently open for the process. On error, returns a negative error code.
+ *
+ * error: EACCES, Permission denied
+ * desc: The requested access to the file is not allowed, or search permission
+ * is denied for one of the directories in the path prefix of pathname, or
+ * the file did not exist yet and write access to the parent directory is
+ * not allowed, or O_TRUNC is specified but write permission is denied, or
+ * the file is on a filesystem mounted with noexec and MAY_EXEC was implied.
+ *
+ * error: EAGAIN, Resource temporarily unavailable
+ * desc: The file is a FIFO or regular file, O_NONBLOCK is specified, and the
+ * operation would block. Also returned when RESOLVE_CACHED is used with
+ * openat2() and the lookup cannot be satisfied from the dentry cache.
+ *
+ * error: EBUSY, Device or resource busy
+ * desc: O_EXCL was specified in flags and pathname refers to a block device
+ * that is in use by the system (e.g., it is mounted).
+ *
+ * error: EDQUOT, Disk quota exceeded
+ * desc: O_CREAT is specified and the file does not exist, and the user's quota
+ * of disk blocks or inodes on the filesystem has been exhausted.
+ *
+ * error: EEXIST, File exists
+ * desc: O_CREAT and O_EXCL were specified in flags, but pathname already exists.
+ * This error is atomic with respect to file creation - it prevents race
+ * conditions (TOCTOU) when creating files.
+ *
+ * error: EFAULT, Bad address
+ * desc: pathname points outside the process's accessible address space.
+ *
+ * error: EINTR, Interrupted system call
+ * desc: The call was interrupted by a signal handler before completing file
+ * open. This can occur during lock acquisition or when breaking leases.
+ *
+ * error: EINVAL, Invalid argument
+ * desc: Returned for several conditions: (1) Invalid O_* flag combinations
+ * (O_TMPFILE without O_DIRECTORY, O_TMPFILE with read-only access, O_PATH
+ * with flags other than O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC).
+ * (2) mode contains bits outside S_IALLUGO when O_CREAT/O_TMPFILE
+ * is set (openat2 only). (3) O_DIRECT requested but filesystem doesn't
+ * support it. (4) The filesystem does not support O_SYNC or O_DSYNC.
+ *
+ * error: EISDIR, Is a directory
+ * desc: pathname refers to a directory and the access requested involved
+ * writing (O_WRONLY, O_RDWR, or O_TRUNC). Also returned when O_TMPFILE is
+ * used on a directory that doesn't support tmpfile operations.
+ *
+ * error: ELOOP, Too many symbolic links
+ * desc: Too many symbolic links were encountered in resolving pathname, or
+ * O_NOFOLLOW was specified but pathname refers to a symbolic link.
+ *
+ * error: EMFILE, Too many open files
+ * desc: The per-process limit on the number of open file descriptors has been
+ * reached. This limit is RLIMIT_NOFILE (default typically 1024, max set by
+ * /proc/sys/fs/nr_open).
+ *
+ * error: ENAMETOOLONG, File name too long
+ * desc: pathname was too long, exceeding PATH_MAX (4096) bytes, or a single
+ * path component exceeded NAME_MAX (usually 255) bytes.
+ *
+ * error: ENFILE, Too many open files in system
+ * desc: The system-wide limit on the total number of open files has been
+ * reached (/proc/sys/fs/file-max). Processes with CAP_SYS_ADMIN can exceed
+ * this limit.
+ *
+ * error: ENODEV, No such device
+ * desc: pathname refers to a special file that has no corresponding device, or
+ * the file's inode has no file operations assigned.
+ *
+ * error: ENOENT, No such file or directory
+ * desc: A directory component in pathname does not exist or is a dangling
+ * symbolic link, or O_CREAT is not set and the named file does not exist,
+ * or pathname is an empty string (unless AT_EMPTY_PATH is used with openat2).
+ *
+ * error: ENOMEM, Out of memory
+ * desc: The kernel could not allocate sufficient memory for the file structure,
+ * path lookup structures, or the filename buffer.
+ *
+ * error: ENOSPC, No space left on device
+ * desc: O_CREAT was specified and the file does not exist, and the directory
+ * or filesystem containing the file has no room for a new file entry.
+ *
+ * error: ENOTDIR, Not a directory
+ * desc: A component used as a directory in pathname is not actually a directory,
+ * or O_DIRECTORY was specified and pathname was not a directory.
+ *
+ * error: ENXIO, No such device or address
+ * desc: O_NONBLOCK | O_WRONLY is set and the named file is a FIFO and no
+ * process has the FIFO open for reading. Also returned when opening a device
+ * special file that does not exist.
+ *
+ * error: EOPNOTSUPP, Operation not supported
+ * desc: The filesystem containing pathname does not support O_TMPFILE.
+ *
+ * error: EOVERFLOW, Value too large for defined data type
+ * desc: pathname refers to a regular file that is too large to be opened.
+ * This occurs on 32-bit systems without O_LARGEFILE when the file size
+ * exceeds 2GB (2^31 - 1 bytes).
+ *
+ * error: EPERM, Operation not permitted
+ * desc: O_NOATIME flag was specified but the effective UID of the caller did
+ * not match the owner of the file and the caller is not privileged, or the
+ * file is append-only and O_TRUNC was specified or write mode without
+ * O_APPEND, or the file is immutable, or a seal prevents the operation.
+ *
+ * error: EROFS, Read-only file system
+ * desc: pathname refers to a file on a read-only filesystem and write access
+ * was requested.
+ *
+ * error: ETXTBSY, Text file busy
+ * desc: pathname refers to an executable image which is currently being
+ * executed, or to a swap file, and write access or truncation was requested.
+ *
+ * error: EWOULDBLOCK, Resource temporarily unavailable
+ * desc: O_NONBLOCK was specified and an incompatible lease is held on the file.
+ *
+ * lock: files->file_lock
+ * type: KAPI_LOCK_SPINLOCK
+ * acquired: true
+ * released: true
+ * desc: Acquired when allocating a file descriptor slot. Held briefly during
+ * fd allocation via alloc_fd() and released before the syscall returns.
+ *
+ * lock: inode->i_rwsem (parent directory)
+ * type: KAPI_LOCK_RWLOCK
+ * acquired: conditional
+ * released: true
+ * desc: Write lock acquired on parent directory inode when creating a new file
+ * (O_CREAT). Acquired via inode_lock_nested() in lookup path. May use
+ * killable variant which can return EINTR on fatal signal.
+ *
+ * lock: RCU read-side
+ * type: KAPI_LOCK_RCU
+ * acquired: true
+ * released: true
+ * desc: Path lookup uses RCU mode initially for performance. If RCU lookup
+ * fails (returns -ECHILD), falls back to reference-based lookup.
+ *
+ * signal: Any signal
+ * direction: KAPI_SIGNAL_RECEIVE
+ * action: KAPI_SIGNAL_ACTION_RETURN
+ * condition: When blocked on interruptible or killable operations
+ * desc: The syscall may be interrupted during path lookup, lock acquisition,
+ * or lease breaking. Fatal signals (SIGKILL, etc.) will interrupt killable
+ * operations. Non-fatal signals may interrupt interruptible operations.
+ * error: -EINTR
+ * timing: KAPI_SIGNAL_TIME_DURING
+ * restartable: yes
+ *
+ * side-effect: KAPI_EFFECT_RESOURCE_CREATE | KAPI_EFFECT_ALLOC_MEMORY
+ * target: file descriptor, file structure, dentry cache
+ * desc: Allocates a new file descriptor in the process's fd table. Allocates
+ * a struct file from the filp slab cache. May allocate dentries and inodes
+ * during path lookup. System-wide file count (nr_files) is incremented.
+ * reversible: yes
+ *
+ * side-effect: KAPI_EFFECT_FILESYSTEM
+ * target: filesystem, inode
+ * condition: When O_CREAT is specified and file doesn't exist
+ * desc: Creates a new file on the filesystem. Creates new inode, allocates
+ * data blocks as needed, and creates directory entry. Updates parent
+ * directory mtime and ctime.
+ * reversible: no
+ *
+ * side-effect: KAPI_EFFECT_FILESYSTEM
+ * target: file content
+ * condition: When O_TRUNC is specified for existing file
+ * desc: Truncates the file to zero length, releasing data blocks. Updates
+ * file mtime and ctime. May trigger notifications to lease holders.
+ * reversible: no
+ *
+ * side-effect: KAPI_EFFECT_MODIFY_STATE
+ * target: inode timestamps
+ * condition: Unless O_NOATIME is specified
+ * desc: Opens for reading may update inode access time (atime) unless mounted
+ * with noatime/relatime or O_NOATIME is specified. Opens for writing that
+ * truncate or create update mtime and ctime.
+ *
+ * capability: CAP_DAC_OVERRIDE
+ * type: KAPI_CAP_BYPASS_CHECK
+ * allows: Bypass file read, write, and execute permission checks
+ * without: Standard DAC (discretionary access control) checks are applied
+ * condition: Checked when file permission would otherwise deny access
+ *
+ * capability: CAP_DAC_READ_SEARCH
+ * type: KAPI_CAP_BYPASS_CHECK
+ * allows: Bypass read permission on files and search permission on directories
+ * without: Must have read permission on file or search permission on directory
+ * condition: Checked during path traversal and file open
+ *
+ * capability: CAP_FOWNER
+ * type: KAPI_CAP_BYPASS_CHECK
+ * allows: Use O_NOATIME on files not owned by caller
+ * without: O_NOATIME returns EPERM if caller is not file owner
+ * condition: Checked when O_NOATIME is specified and caller is not owner
+ *
+ * capability: CAP_SYS_ADMIN
+ * type: KAPI_CAP_INCREASE_LIMIT
+ * allows: Exceed the system-wide file limit (file-max)
+ * without: Returns ENFILE when system limit is reached
+ * condition: Checked in alloc_empty_file() when nr_files >= max_files
+ *
+ * constraint: RLIMIT_NOFILE (per-process fd limit)
+ * desc: The returned file descriptor must be less than the process's
+ * RLIMIT_NOFILE limit. Default is typically 1024, maximum is controlled
+ * by /proc/sys/fs/nr_open (default 1048576). Exceeding returns EMFILE.
+ * expr: fd < rlimit(RLIMIT_NOFILE)
+ *
+ * constraint: file-max (system-wide limit)
+ * desc: System-wide limit on open files in /proc/sys/fs/file-max. Processes
+ * without CAP_SYS_ADMIN receive ENFILE when this limit is reached. The
+ * limit is computed based on system memory at boot time.
+ * expr: nr_files < files_stat.max_files || capable(CAP_SYS_ADMIN)
+ *
+ * constraint: PATH_MAX
+ * desc: Maximum length of pathname including null terminator is PATH_MAX
+ * (4096 bytes). Individual path components must not exceed NAME_MAX (255).
+ *
+ * examples: fd = open("/etc/passwd", O_RDONLY); // Read existing file
+ * fd = open("/tmp/newfile", O_WRONLY | O_CREAT | O_TRUNC, 0644); // Create/truncate
+ * fd = open("/tmp/lockfile", O_WRONLY | O_CREAT | O_EXCL, 0600); // Exclusive create
+ * fd = open("/dev/null", O_RDWR); // Open device
+ * fd = open("/tmp", O_RDONLY | O_DIRECTORY); // Open directory
+ * fd = open("/tmp", O_TMPFILE | O_RDWR, 0600); // Anonymous temp file
+ *
+ * notes: The distinction between O_RDONLY, O_WRONLY, and O_RDWR is critical.
+ * O_RDONLY is defined as 0, so (flags & O_RDONLY) will be true for all flags.
+ * Test access mode using (flags & O_ACCMODE) == O_RDONLY.
+ *
+ * When O_CREAT is specified without O_EXCL, there is a race condition between
+ * testing for file existence and creating it. Use O_CREAT | O_EXCL for atomic
+ * exclusive file creation.
+ *
+ * O_CLOEXEC should be used in multithreaded programs to prevent file descriptor
+ * leaks to child processes between fork() and execve().
+ *
+ * O_DIRECT has alignment requirements that vary by filesystem. Use statx()
+ * with STATX_DIOALIGN (Linux 6.1+) to query requirements. Unaligned I/O may
+ * fail with EINVAL or fall back to buffered I/O.
+ *
+ * O_PATH opens a file descriptor that can be used only for certain operations
+ * (fstat, dup, fcntl, close, fchdir on directories, as dirfd for *at() calls).
+ * I/O operations will fail with EBADF.
+ *
+ * since-version: 1.0
+ */
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
if (force_o_largefile())
@@ -1581,3 +1905,8 @@ int stream_open(struct inode *inode, struct file *filp)
}
EXPORT_SYMBOL(stream_open);
+
+/* Include auto-generated API specifications from kerneldoc annotations */
+#if IS_ENABLED(CONFIG_KAPI_SPEC)
+#include "open.apispec.h"
+#endif
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH 6/9] kernel/api: add API specification for sys_close
2026-03-13 15:09 [PATCH 0/9] Kernel API Specification Framework Sasha Levin
` (4 preceding siblings ...)
2026-03-13 15:09 ` [PATCH 5/9] kernel/api: add API specification for sys_open Sasha Levin
@ 2026-03-13 15:09 ` Sasha Levin
2026-03-13 15:49 ` Greg Kroah-Hartman
2026-03-13 15:52 ` Greg Kroah-Hartman
2026-03-13 15:09 ` [PATCH 7/9] kernel/api: add API specification for sys_read Sasha Levin
` (3 subsequent siblings)
9 siblings, 2 replies; 37+ messages in thread
From: Sasha Levin @ 2026-03-13 15:09 UTC (permalink / raw)
To: linux-api, linux-kernel
Cc: linux-doc, linux-fsdevel, linux-kbuild, linux-kselftest,
workflows, tools, x86, Thomas Gleixner, Paul E. McKenney,
Greg Kroah-Hartman, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann, Sasha Levin
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/open.c | 247 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 243 insertions(+), 4 deletions(-)
diff --git a/fs/open.c b/fs/open.c
index 46641176483a3..71827108270cd 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -1810,10 +1810,249 @@ int filp_close(struct file *filp, fl_owner_t id)
}
EXPORT_SYMBOL(filp_close);
-/*
- * Careful here! We test whether the file pointer is NULL before
- * releasing the fd. This ensures that one clone task can't release
- * an fd while another clone is opening it.
+/**
+ * sys_close - Close a file descriptor
+ * @fd: The file descriptor to close
+ *
+ * long-desc: Terminates access to an open file descriptor, releasing the file
+ * descriptor for reuse by subsequent open(), dup(), or similar syscalls. Any
+ * advisory record locks (POSIX locks, OFD locks, and flock locks) held on the
+ * associated file are released. When this is the last file descriptor
+ * referring to the underlying open file description, associated resources are
+ * freed. If the file was previously unlinked, the file itself is deleted when
+ * the last reference is closed.
+ *
+ * CRITICAL: The file descriptor is ALWAYS closed, even when close() returns
+ * an error. This differs from POSIX semantics where the state of the file
+ * descriptor is unspecified after EINTR. On Linux, the fd is released early
+ * in close() processing before flush operations that may fail. Therefore,
+ * retrying close() after an error return is DANGEROUS and may close an
+ * unrelated file descriptor that was assigned to another thread.
+ *
+ * Errors returned from close() (EIO, ENOSPC, EDQUOT) indicate that the final
+ * flush of buffered data failed. These errors commonly occur on network
+ * filesystems like NFS when write errors are deferred to close time. A
+ * successful return from close() does NOT guarantee that data has been
+ * successfully written to disk; the kernel uses buffer cache to defer writes.
+ * To ensure data persistence, call fsync() before close().
+ *
+ * On close, the following cleanup operations are performed: POSIX advisory
+ * locks are removed, dnotify registrations are cleaned up, the file is
+ * flushed if the file operations define a flush callback, and the file
+ * reference is released. If this was the last reference, additional cleanup
+ * includes: fsnotify close notification, epoll cleanup, flock and lease
+ * removal, FASYNC cleanup, the file's release callback invocation, and
+ * the file structure deallocation.
+ *
+ * context-flags: KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE
+ *
+ * param: fd
+ * type: KAPI_TYPE_FD
+ * flags: KAPI_PARAM_IN
+ * constraint-type: KAPI_CONSTRAINT_RANGE
+ * range: 0, INT_MAX
+ * cdesc: Must be a valid, open file descriptor for the current process.
+ * The value 0, 1, or 2 (stdin, stdout, stderr) may be closed like any other
+ * fd, though this is unusual and may cause issues with libraries that assume
+ * these descriptors are valid. The parameter is unsigned int to match kernel
+ * file descriptor table indexing, but values exceeding INT_MAX are effectively
+ * invalid due to internal checks.
+ *
+ * return:
+ * type: KAPI_TYPE_INT
+ * check-type: KAPI_RETURN_EXACT
+ * success: 0
+ * desc: Returns 0 on success. On error, returns a negative error code.
+ * IMPORTANT: Even when an error is returned, the file descriptor is still
+ * closed and must not be used again. The error indicates a problem with
+ * the final flush operation, not that the fd remains open.
+ *
+ * error: EBADF, Bad file descriptor
+ * desc: The file descriptor fd is not a valid open file descriptor, or was
+ * already closed. This is the only error that indicates the fd was NOT
+ * closed (because it was never open to begin with). Occurs when fd is out
+ * of range, has no file assigned, or was already closed.
+ *
+ * error: EINTR, Interrupted system call
+ * desc: The flush operation was interrupted by a signal before completion.
+ * This occurs when a file's flush callback (e.g., NFS) performs an
+ * interruptible wait that receives a signal. IMPORTANT: Despite this error,
+ * the file descriptor IS closed and must not be used again. This error
+ * is generated by converting kernel-internal restart codes (ERESTARTSYS,
+ * ERESTARTNOINTR, ERESTARTNOHAND, ERESTART_RESTARTBLOCK) to EINTR because
+ * restarting the syscall would be incorrect once the fd is freed.
+ *
+ * error: EIO, I/O error
+ * desc: An I/O error occurred during the flush of buffered data to the
+ * underlying storage. This typically indicates a hardware error, network
+ * failure on NFS, or other storage system error. The file descriptor is
+ * still closed. Previously buffered write data may have been lost.
+ *
+ * error: ENOSPC, No space left on device
+ * desc: There was insufficient space on the storage device to flush buffered
+ * writes. This is common on NFS when the server runs out of space between
+ * write() and close(). The file descriptor is still closed.
+ *
+ * error: EDQUOT, Disk quota exceeded
+ * desc: The user's disk quota was exceeded while attempting to flush buffered
+ * writes. Common on NFS when quota is exceeded between write() and close().
+ * The file descriptor is still closed.
+ *
+ * lock: files->file_lock
+ * type: KAPI_LOCK_SPINLOCK
+ * acquired: true
+ * released: true
+ * desc: Acquired via file_close_fd() to atomically lookup and remove the fd
+ * from the file descriptor table. Held only during the table manipulation;
+ * released before flush and final cleanup operations. This ensures that
+ * another thread cannot allocate the same fd number while close is in
+ * progress.
+ *
+ * lock: file->f_lock
+ * type: KAPI_LOCK_SPINLOCK
+ * acquired: true
+ * released: true
+ * desc: Acquired during epoll cleanup (eventpoll_release_file) and dnotify
+ * cleanup to safely unlink the file from monitoring structures. May also
+ * be acquired during lock context operations.
+ *
+ * lock: ep->mtx
+ * type: KAPI_LOCK_MUTEX
+ * acquired: true
+ * released: true
+ * desc: Acquired during epoll cleanup if the file was monitored by epoll.
+ * Used to safely remove the file from epoll interest lists.
+ *
+ * lock: flc_lock
+ * type: KAPI_LOCK_SPINLOCK
+ * acquired: true
+ * released: true
+ * desc: File lock context spinlock, acquired during locks_remove_file() to
+ * safely remove POSIX, flock, and lease locks associated with the file.
+ *
+ * signal: pending_signals
+ * direction: KAPI_SIGNAL_RECEIVE
+ * action: KAPI_SIGNAL_ACTION_RETURN
+ * condition: When flush callback performs interruptible wait
+ * desc: If the file's flush callback (e.g., nfs_file_flush) performs an
+ * interruptible wait and a signal is pending, the wait is interrupted.
+ * Any kernel restart codes are converted to EINTR since close cannot be
+ * restarted after the fd is freed.
+ * error: -EINTR
+ * timing: KAPI_SIGNAL_TIME_DURING
+ * restartable: no
+ *
+ * side-effect: KAPI_EFFECT_RESOURCE_DESTROY | KAPI_EFFECT_IRREVERSIBLE
+ * target: File descriptor table entry
+ * desc: The file descriptor is removed from the process's file descriptor
+ * table, making the fd number available for reuse by subsequent open(),
+ * dup(), or similar calls. This occurs BEFORE any flush or cleanup that
+ * might fail, making the operation irreversible regardless of return value.
+ * condition: Always (when fd is valid)
+ * reversible: no
+ *
+ * side-effect: KAPI_EFFECT_LOCK_RELEASE
+ * target: POSIX advisory locks, OFD locks, flock locks
+ * desc: All advisory locks held on the file by this process are removed.
+ * POSIX locks are removed via locks_remove_posix() during filp_flush().
+ * All lock types (POSIX, OFD, flock) are removed via locks_remove_file()
+ * during __fput() when this is the last reference.
+ * condition: File has FMODE_OPENED and !(FMODE_PATH)
+ * reversible: no
+ *
+ * side-effect: KAPI_EFFECT_RESOURCE_DESTROY
+ * target: File leases
+ * desc: Any file leases held on the file are removed during locks_remove_file()
+ * when this is the last reference to the open file description.
+ * condition: File had leases and this is the last close
+ * reversible: no
+ *
+ * side-effect: KAPI_EFFECT_MODIFY_STATE
+ * target: dnotify registrations
+ * desc: Directory notification (dnotify) registrations associated with this
+ * file are cleaned up via dnotify_flush(). This only applies to directories.
+ * condition: File is a directory with dnotify registrations
+ * reversible: no
+ *
+ * side-effect: KAPI_EFFECT_MODIFY_STATE
+ * target: epoll interest lists
+ * desc: If the file was being monitored by epoll instances, it is removed
+ * from those interest lists via eventpoll_release().
+ * condition: File was added to epoll instances
+ * reversible: no
+ *
+ * side-effect: KAPI_EFFECT_FILESYSTEM
+ * target: Buffered data
+ * desc: The file's flush callback is invoked if defined (e.g., NFS calls
+ * nfs_file_flush). This attempts to write any buffered data to storage
+ * and may return errors (EIO, ENOSPC, EDQUOT) if the flush fails. The
+ * success of this flush is NOT guaranteed even with a 0 return; use
+ * fsync() before close() to ensure data persistence.
+ * condition: File has a flush callback and was opened for writing
+ * reversible: no
+ *
+ * side-effect: KAPI_EFFECT_FREE_MEMORY
+ * target: struct file and related structures
+ * desc: When this is the last reference to the file, __fput() is called
+ * synchronously (fput_close_sync), which frees the file structure, releases
+ * the dentry and mount references, and invokes the file's release callback.
+ * condition: This is the last reference to the file
+ * reversible: no
+ *
+ * side-effect: KAPI_EFFECT_FILESYSTEM
+ * target: Unlinked file deletion
+ * desc: If the file was previously unlinked (deleted) but kept open, closing
+ * the last reference causes the actual file data to be removed from the
+ * filesystem and the inode to be freed.
+ * condition: File was unlinked and this is the last reference
+ * reversible: no
+ *
+ * state-trans: file_descriptor
+ * from: open
+ * to: closed/free
+ * condition: Valid fd passed to close
+ * desc: The file descriptor transitions from open (usable) to closed (invalid).
+ * The fd number becomes available for reuse. This transition occurs early
+ * in close() processing, before any operations that might fail.
+ *
+ * state-trans: file_reference_count
+ * from: n
+ * to: n-1 (or freed if n was 1)
+ * condition: Always on successful fd lookup
+ * desc: The file's reference count is decremented. If this was the last
+ * reference, the file is fully cleaned up and freed.
+ *
+ * constraint: File Descriptor Reuse Race
+ * desc: Because the fd is freed early in close() processing, another thread
+ * may receive the same fd number from a concurrent open() before close()
+ * returns. Applications must not retry close() after an error return, as
+ * this could close an unrelated file opened by another thread.
+ * expr: After close(fd) returns (even with error), fd is invalid
+ *
+ * examples: close(fd); // Basic usage - ignore errors (common but not ideal)
+ * if (close(fd) == -1) perror("close"); // Log errors for debugging
+ * fsync(fd); close(fd); // Ensure data persistence before closing
+ *
+ * notes: This syscall has subtle non-POSIX semantics: the fd is ALWAYS closed
+ * regardless of the return value. POSIX specifies that on EINTR, the state
+ * of the fd is unspecified, but Linux always closes it. HP-UX requires
+ * retrying close() on EINTR, but doing so on Linux may close an unrelated
+ * fd that was reassigned by another thread. For portable code, the safest
+ * approach is to check for errors but never retry close().
+ *
+ * Error codes from the flush callback (EIO, ENOSPC, EDQUOT) indicate that
+ * previously written data may have been lost. These errors are particularly
+ * common on NFS where write errors are often deferred to close time.
+ *
+ * The driver's release() callback errors are explicitly ignored by the
+ * kernel, so device driver cleanup errors are not propagated to userspace.
+ *
+ * Calling close() on a file descriptor while another thread is using it
+ * (e.g., in a blocking read() or write()) has implementation-defined
+ * behavior. On Linux, the blocked operation continues on the underlying
+ * file and may complete even after close() returns.
+ *
+ * since-version: 1.0
*/
SYSCALL_DEFINE1(close, unsigned int, fd)
{
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH 7/9] kernel/api: add API specification for sys_read
2026-03-13 15:09 [PATCH 0/9] Kernel API Specification Framework Sasha Levin
` (5 preceding siblings ...)
2026-03-13 15:09 ` [PATCH 6/9] kernel/api: add API specification for sys_close Sasha Levin
@ 2026-03-13 15:09 ` Sasha Levin
2026-03-13 15:09 ` [PATCH 8/9] kernel/api: add API specification for sys_write Sasha Levin
` (2 subsequent siblings)
9 siblings, 0 replies; 37+ messages in thread
From: Sasha Levin @ 2026-03-13 15:09 UTC (permalink / raw)
To: linux-api, linux-kernel
Cc: linux-doc, linux-fsdevel, linux-kbuild, linux-kselftest,
workflows, tools, x86, Thomas Gleixner, Paul E. McKenney,
Greg Kroah-Hartman, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann, Sasha Levin
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/read_write.c | 305 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 305 insertions(+)
diff --git a/fs/read_write.c b/fs/read_write.c
index 50bff7edc91f3..6b1d2eccbbe28 100644
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -721,6 +721,306 @@ ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count)
return ret;
}
+/**
+ * sys_read - Read data from a file descriptor
+ * @fd: File descriptor to read from
+ * @buf: User-space buffer to read data into
+ * @count: Maximum number of bytes to read
+ *
+ * long-desc: Attempts to read up to count bytes from file descriptor fd into
+ * the buffer starting at buf. For seekable files (regular files, block
+ * devices), the read begins at the current file offset, and the file offset
+ * is advanced by the number of bytes read. For non-seekable files (pipes,
+ * FIFOs, sockets, character devices), the file offset is not used.
+ *
+ * If count is zero and fd refers to a regular file, read() may detect errors
+ * as described below. In the absence of errors, or if read() does not check
+ * for errors, a read() with a count of 0 returns zero and has no other effects.
+ *
+ * On success, the number of bytes read is returned (zero indicates end of
+ * file for regular files). It is not an error if this number is smaller than
+ * the number of bytes requested; this may happen because fewer bytes are
+ * actually available right now (maybe because we were close to end-of-file,
+ * or because we are reading from a pipe, socket, or terminal), or because
+ * read() was interrupted by a signal.
+ *
+ * On Linux, read() transfers at most MAX_RW_COUNT (0x7ffff000, approximately
+ * 2GB) bytes per call, regardless of whether the filesystem would allow more.
+ * This is to avoid issues with signed arithmetic overflow on 32-bit systems.
+ *
+ * POSIX allows reads that are interrupted after reading some data to either
+ * return -1 (with errno set to EINTR) or return the number of bytes already
+ * read. Linux follows the latter behavior: if data has been read before a
+ * signal arrives, the call returns the bytes read rather than failing.
+ *
+ * context-flags: KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE
+ *
+ * param: fd
+ * type: KAPI_TYPE_FD
+ * flags: KAPI_PARAM_IN
+ * constraint-type: KAPI_CONSTRAINT_RANGE
+ * range: 0, INT_MAX
+ * cdesc: Must be a valid, open file descriptor with read permission.
+ * The file must have been opened with O_RDONLY or O_RDWR. Special values
+ * like AT_FDCWD are not valid. File descriptors for directories return
+ * EISDIR. Standard file descriptors 0 (stdin), 1 (stdout), 2 (stderr) are
+ * valid if open and readable.
+ *
+ * param: buf
+ * type: KAPI_TYPE_USER_PTR
+ * flags: KAPI_PARAM_OUT | KAPI_PARAM_USER
+ * constraint-type: KAPI_CONSTRAINT_BUFFER
+ * size-param: 2
+ * cdesc: Must point to a valid, writable user-space memory region of at
+ * least count bytes. The buffer is validated via access_ok() before any
+ * read operation. NULL is invalid and will return EFAULT. The buffer may
+ * be partially written if an error occurs mid-read. For O_DIRECT reads,
+ * the buffer may need to be aligned to the filesystem's block size (varies
+ * by filesystem, check via statx() with STATX_DIOALIGN).
+ *
+ * param: count
+ * type: KAPI_TYPE_UINT
+ * flags: KAPI_PARAM_IN
+ * constraint-type: KAPI_CONSTRAINT_RANGE
+ * range: 0, SIZE_MAX
+ * cdesc: Maximum number of bytes to read. Clamped internally to
+ * MAX_RW_COUNT (INT_MAX & PAGE_MASK, approximately 0x7ffff000 bytes) to
+ * prevent signed overflow issues. A count of 0 returns immediately with 0
+ * without accessing the file (but may still detect errors). Large values
+ * are not errors but will be clamped. Cast to ssize_t must not be negative.
+ *
+ * return:
+ * type: KAPI_TYPE_INT
+ * check-type: KAPI_RETURN_RANGE
+ * success: >= 0
+ * desc: On success, returns the number of bytes read (non-negative). Zero
+ * indicates end-of-file (EOF) for regular files, or no data available
+ * from a device that does not block. The return value may be less than
+ * count if fewer bytes were available (short read). Partial reads are
+ * not errors. On error, returns a negative error code.
+ *
+ * error: EBADF, Bad file descriptor
+ * desc: fd is not a valid file descriptor, or fd was not opened for reading.
+ * This includes file descriptors opened with O_WRONLY, O_PATH, or file
+ * descriptors that have been closed. Also returned if the file structure
+ * does not have FMODE_READ set.
+ *
+ * error: EFAULT, Bad address
+ * desc: buf points outside the accessible address space. The buffer address
+ * failed access_ok() validation. Can also occur if a fault happens during
+ * copy_to_user() when transferring data to user space after the read
+ * completes in kernel space.
+ *
+ * error: EINVAL, Invalid argument
+ * desc: Returned in several cases: (1) The file descriptor refers to an
+ * object that is not suitable for reading (no read or read_iter method).
+ * (2) The file was opened with O_DIRECT and the buffer alignment, offset,
+ * or count does not meet the filesystem's alignment requirements. (3) For
+ * timerfd file descriptors, the buffer is smaller than 8 bytes. (4) The
+ * count argument, when cast to ssize_t, is negative.
+ *
+ * error: EISDIR, Is a directory
+ * desc: fd refers to a directory. Directories cannot be read using read();
+ * use getdents64() instead. This error is returned by the generic_read_dir()
+ * handler installed for directory file operations.
+ *
+ * error: EAGAIN, Resource temporarily unavailable
+ * desc: fd refers to a file (pipe, socket, device) that is marked non-blocking
+ * (O_NONBLOCK) and the read would block. Also returned with IOCB_NOWAIT
+ * when data is not immediately available. Equivalent to EWOULDBLOCK.
+ * The application should retry the read later or use select/poll/epoll.
+ *
+ * error: EINTR, Interrupted system call
+ * desc: The call was interrupted by a signal before any data was read. This
+ * only occurs if no data has been transferred; if some data was read before
+ * the signal, the call returns the number of bytes read. The caller should
+ * typically restart the read.
+ *
+ * error: EIO, Input/output error
+ * desc: A low-level I/O error occurred. For regular files, this typically
+ * indicates a hardware error on the storage device, a filesystem error,
+ * or a network filesystem timeout. For terminals, this may indicate the
+ * controlling terminal has been closed for a background process.
+ *
+ * error: EOVERFLOW, Value too large for defined data type
+ * desc: The file position plus count would exceed LLONG_MAX. Also returned
+ * when reading from certain files (e.g., some /proc files) where the file
+ * position would overflow. For files without FOP_UNSIGNED_OFFSET flag,
+ * negative file positions are not allowed.
+ *
+ * error: ENOBUFS, No buffer space available
+ * desc: Returned when reading from pipe-based watch queues (CONFIG_WATCH_QUEUE)
+ * when the buffer is too small to hold a complete notification, or when
+ * reading packets from pipes with PIPE_BUF_FLAG_WHOLE set.
+ *
+ * error: ERESTARTSYS, Restart system call (internal)
+ * desc: Internal error code indicating the syscall should be restarted. This
+ * is typically translated to EINTR if SA_RESTART is not set on the signal
+ * handler, or the syscall is transparently restarted if SA_RESTART is set.
+ * User space should not see this error code directly.
+ *
+ * error: EACCES, Permission denied
+ * desc: The security subsystem (LSM such as SELinux or AppArmor) denied
+ * the read operation via security_file_permission(). This can occur even
+ * if the file was successfully opened, as LSM policies may enforce per-
+ * operation checks.
+ *
+ * error: EPERM, Operation not permitted
+ * desc: Returned by fanotify permission events (CONFIG_FANOTIFY_ACCESS_PERMISSIONS)
+ * when a user-space fanotify listener denies the read operation via
+ * fsnotify_file_area_perm().
+ *
+ * error: ENODATA, No data available
+ * desc: Returned when reading from files backed by fscache/cachefiles
+ * and the requested data range is not available in the cache
+ * (e.g., beyond EOF or in an uncached region). Also returned by
+ * some filesystem-specific read handlers (e.g., xattr reads).
+ *
+ * error: EOPNOTSUPP, Operation not supported
+ * desc: Returned when the file descriptor does not support the read
+ * operation, such as reading from certain special files or when the
+ * filesystem does not implement read for this file type.
+ *
+ * lock: file->f_pos_lock
+ * type: KAPI_LOCK_MUTEX
+ * acquired: conditional
+ * released: true
+ * desc: For regular files that require atomic position updates (FMODE_ATOMIC_POS),
+ * the f_pos_lock mutex is acquired by fdget_pos() at syscall entry and released
+ * by fdput_pos() at syscall exit. This serializes concurrent reads that share
+ * the same file description. Not acquired for files opened with FMODE_STREAM
+ * (pipes, sockets) or when the file is not shared.
+ *
+ * lock: Filesystem-specific locks
+ * type: KAPI_LOCK_CUSTOM
+ * acquired: conditional
+ * released: true
+ * desc: The filesystem's read_iter or read method may acquire additional locks.
+ * For regular files, this typically includes the inode's i_rwsem for certain
+ * operations. For pipes, the pipe->mutex is acquired. For sockets, socket
+ * lock is acquired. These are internal to the file operation and released
+ * before return.
+ *
+ * lock: RCU read-side
+ * type: KAPI_LOCK_RCU
+ * acquired: conditional
+ * released: true
+ * desc: Used during file descriptor lookup via fdget(). RCU read lock protects
+ * access to the file descriptor table. Released by fdput() at syscall exit.
+ *
+ * signal: Any signal
+ * direction: KAPI_SIGNAL_RECEIVE
+ * action: KAPI_SIGNAL_ACTION_RETURN
+ * condition: When blocked waiting for data on interruptible operations
+ * desc: The syscall may be interrupted by signals while waiting for data to
+ * become available (pipes, sockets, terminals) or waiting for locks. If
+ * interrupted before any data is read, returns -EINTR or -ERESTARTSYS.
+ * If data has already been read, returns the number of bytes read.
+ * error: -EINTR
+ * timing: KAPI_SIGNAL_TIME_DURING
+ * restartable: yes
+ *
+ * side-effect: KAPI_EFFECT_FILE_POSITION
+ * target: file->f_pos
+ * condition: For seekable files when read succeeds (returns > 0)
+ * desc: The file offset (f_pos) is advanced by the number of bytes read.
+ * For stream files (FMODE_STREAM such as pipes and sockets), the offset
+ * is not used or modified. The offset update is protected by f_pos_lock
+ * when the file is shared between threads/processes.
+ * reversible: no
+ *
+ * side-effect: KAPI_EFFECT_MODIFY_STATE
+ * target: inode access time (atime)
+ * condition: When read succeeds and O_NOATIME is not set
+ * desc: Updates the file's access time (atime) via touch_atime(). The update
+ * may be suppressed by mount options (noatime, relatime), the O_NOATIME
+ * flag, or if the filesystem does not support atime. Relatime only updates
+ * atime if it is older than mtime or ctime, or more than a day old.
+ * reversible: no
+ *
+ * side-effect: KAPI_EFFECT_MODIFY_STATE
+ * target: task I/O accounting
+ * condition: Always
+ * desc: Updates the current task's I/O accounting statistics. The rchar field
+ * (read characters) is incremented by bytes read via add_rchar() only on
+ * successful reads (ret > 0). The syscr field (syscall read count) is
+ * incremented unconditionally via inc_syscr(). These statistics are visible
+ * in /proc/[pid]/io.
+ * reversible: no
+ *
+ * side-effect: KAPI_EFFECT_MODIFY_STATE
+ * target: fsnotify events
+ * condition: When read returns > 0
+ * desc: Generates an FS_ACCESS fsnotify event via fsnotify_access() allowing
+ * inotify, fanotify, and dnotify watchers to be notified of the read. This
+ * occurs after data transfer completes successfully.
+ * reversible: no
+ *
+ * capability: CAP_DAC_OVERRIDE
+ * type: KAPI_CAP_BYPASS_CHECK
+ * allows: Bypass discretionary access control on read permission
+ * without: Standard DAC checks are enforced
+ * condition: Checked via security_file_permission() during rw_verify_area()
+ *
+ * capability: CAP_DAC_READ_SEARCH
+ * type: KAPI_CAP_BYPASS_CHECK
+ * allows: Bypass read permission checks on regular files
+ * without: Must have read permission on file
+ * condition: Checked by LSM hooks during the read operation
+ *
+ * constraint: MAX_RW_COUNT
+ * desc: The count parameter is silently clamped to MAX_RW_COUNT (INT_MAX &
+ * PAGE_MASK, approximately 2GB minus one page) to prevent integer overflow
+ * in internal calculations. This is transparent to the caller; the syscall
+ * succeeds but reads at most MAX_RW_COUNT bytes.
+ * expr: actual_count = min(count, MAX_RW_COUNT)
+ *
+ * constraint: File must be open for reading
+ * desc: The file descriptor must have been opened with O_RDONLY or O_RDWR.
+ * Files opened with O_WRONLY or O_PATH cannot be read and return EBADF.
+ * The file must have both FMODE_READ and FMODE_CAN_READ flags set.
+ * expr: (file->f_mode & FMODE_READ) && (file->f_mode & FMODE_CAN_READ)
+ *
+ * examples: n = read(fd, buf, sizeof(buf)); // Basic read
+ * n = read(STDIN_FILENO, buf, 1024); // Read from stdin
+ * while ((n = read(fd, buf, 4096)) > 0) { process(buf, n); } // Read loop
+ * if (read(fd, buf, count) == 0) { handle_eof(); } // Check for EOF
+ *
+ * notes: The behavior of read() varies significantly depending on the type of
+ * file descriptor:
+ *
+ * - Regular files: Reads from current position, advances position, returns 0
+ * at EOF. Short reads are rare but possible near EOF or on signal.
+ *
+ * - Pipes and FIFOs: Blocking by default. Returns available data (up to count)
+ * or blocks until data is available. Returns 0 when all writers have closed.
+ * O_NONBLOCK returns EAGAIN when empty instead of blocking.
+ *
+ * - Sockets: Similar to pipes. Specific behavior depends on socket type and
+ * protocol. MSG_* flags can be specified via recv() for more control.
+ *
+ * - Terminals: Line-buffered in canonical mode; read returns when newline is
+ * entered or buffer is full. Raw mode returns immediately when data available.
+ * Special handling for signals (SIGINT on Ctrl+C, etc.).
+ *
+ * - Device special files: Behavior is device-specific. Some devices support
+ * seeking, others do not. Read size may be constrained by device.
+ *
+ * Race condition: Concurrent reads from the same file description (not just
+ * file descriptor) can race on the file position. Linux 3.14+ provides atomic
+ * position updates for regular files via f_pos_lock, but applications should
+ * use pread() for concurrent positioned reads.
+ *
+ * O_DIRECT reads bypass the page cache and typically require aligned buffers
+ * and positions. Alignment requirements are filesystem-specific; use statx()
+ * with STATX_DIOALIGN (Linux 6.1+) to query. Unaligned O_DIRECT reads fail
+ * with EINVAL on most filesystems.
+ *
+ * For splice(2)-like zero-copy reads, consider using splice(), sendfile(),
+ * or copy_file_range() instead of read() + write().
+ *
+ * since-version: 1.0
+ */
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
return ksys_read(fd, buf, count);
@@ -1821,3 +2121,8 @@ int generic_atomic_write_valid(struct kiocb *iocb, struct iov_iter *iter)
return 0;
}
EXPORT_SYMBOL_GPL(generic_atomic_write_valid);
+
+/* Include auto-generated API specifications from kerneldoc annotations */
+#if IS_ENABLED(CONFIG_KAPI_SPEC)
+#include "read_write.apispec.h"
+#endif
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH 8/9] kernel/api: add API specification for sys_write
2026-03-13 15:09 [PATCH 0/9] Kernel API Specification Framework Sasha Levin
` (6 preceding siblings ...)
2026-03-13 15:09 ` [PATCH 7/9] kernel/api: add API specification for sys_read Sasha Levin
@ 2026-03-13 15:09 ` Sasha Levin
2026-03-13 15:09 ` [PATCH 9/9] kernel/api: add runtime verification selftest Sasha Levin
2026-03-14 18:18 ` [PATCH 0/9] Kernel API Specification Framework Jakub Kicinski
9 siblings, 0 replies; 37+ messages in thread
From: Sasha Levin @ 2026-03-13 15:09 UTC (permalink / raw)
To: linux-api, linux-kernel
Cc: linux-doc, linux-fsdevel, linux-kbuild, linux-kselftest,
workflows, tools, x86, Thomas Gleixner, Paul E. McKenney,
Greg Kroah-Hartman, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann, Sasha Levin
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/read_write.c | 382 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 382 insertions(+)
diff --git a/fs/read_write.c b/fs/read_write.c
index 6b1d2eccbbe28..f121a8d9af3c2 100644
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -1045,6 +1045,388 @@ ssize_t ksys_write(unsigned int fd, const char __user *buf, size_t count)
return ret;
}
+/**
+ * sys_write - Write data to a file descriptor
+ * @fd: File descriptor to write to
+ * @buf: User-space buffer containing data to write
+ * @count: Maximum number of bytes to write
+ *
+ * long-desc: Attempts to write up to count bytes from the buffer starting at
+ * buf to the file referred to by the file descriptor fd. For seekable files
+ * (regular files, block devices), the write begins at the current file offset,
+ * and the file offset is advanced by the number of bytes written. If the file
+ * was opened with O_APPEND, the file offset is first set to the end of the
+ * file before writing. For non-seekable files (pipes, FIFOs, sockets, character
+ * devices), the file offset is not used and writing occurs at the current
+ * position as defined by the device.
+ *
+ * The number of bytes written may be less than count if, for example, there is
+ * insufficient space on the underlying physical medium, or the RLIMIT_FSIZE
+ * resource limit is encountered, or the call was interrupted by a signal
+ * handler after having written less than count bytes. In the event of a
+ * successful partial write, the caller should make another write() call to
+ * transfer the remaining bytes. This behavior is called a "short write."
+ *
+ * On Linux, write() transfers at most MAX_RW_COUNT (0x7ffff000, approximately
+ * 2GB minus one page) bytes per call, regardless of whether the file or
+ * filesystem would allow more. This prevents signed arithmetic overflow.
+ *
+ * For regular files, a successful write() does not guarantee that data has been
+ * committed to disk. Use fsync(2) or fdatasync(2) if durability is required.
+ * For O_SYNC or O_DSYNC files, the kernel automatically syncs data on write.
+ *
+ * POSIX permits writes that are interrupted after partial writes to either
+ * return -1 with errno=EINTR, or to return the count of bytes already written.
+ * Linux implements the latter behavior: if some data has been written before
+ * a signal arrives, write() returns the number of bytes written rather than
+ * failing with EINTR.
+ *
+ * context-flags: KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE
+ *
+ * param: fd
+ * type: KAPI_TYPE_FD
+ * flags: KAPI_PARAM_IN
+ * constraint-type: KAPI_CONSTRAINT_RANGE
+ * range: 0, INT_MAX
+ * cdesc: Must be a valid, open file descriptor with write permission.
+ * The file must have been opened with O_WRONLY or O_RDWR. File descriptors
+ * opened with O_RDONLY, O_PATH, or that have been closed return EBADF.
+ * Standard file descriptors 0 (stdin), 1 (stdout), 2 (stderr) are valid if
+ * open and writable. AT_FDCWD and other special values are not valid.
+ *
+ * param: buf
+ * type: KAPI_TYPE_USER_PTR
+ * flags: KAPI_PARAM_IN | KAPI_PARAM_USER
+ * constraint-type: KAPI_CONSTRAINT_BUFFER
+ * size-param: 2
+ * cdesc: Must point to a valid, readable user-space memory region of at
+ * least count bytes. The buffer is validated via access_ok() before any
+ * write operation. NULL is invalid and returns EFAULT. For O_DIRECT writes,
+ * the buffer may need to be aligned to the filesystem's block size (varies
+ * by filesystem; query with statx() using STATX_DIOALIGN on Linux 6.1+).
+ *
+ * param: count
+ * type: KAPI_TYPE_UINT
+ * flags: KAPI_PARAM_IN
+ * constraint-type: KAPI_CONSTRAINT_RANGE
+ * range: 0, SIZE_MAX
+ * cdesc: Maximum number of bytes to write. Clamped internally to
+ * MAX_RW_COUNT (INT_MAX & PAGE_MASK, approximately 0x7ffff000 bytes) to
+ * prevent signed overflow. A count of 0 returns 0 immediately without any
+ * file operations. Cast to ssize_t must not be negative.
+ *
+ * return:
+ * type: KAPI_TYPE_INT
+ * check-type: KAPI_RETURN_RANGE
+ * success: >= 0
+ * desc: On success, returns the number of bytes written (non-negative). Zero
+ * indicates that nothing was written (count was 0, or no space available
+ * for non-blocking writes). The return value may be less than count due to
+ * resource limits, signal interruption, or device constraints (short write).
+ * On error, returns a negative error code.
+ *
+ * error: EBADF, Bad file descriptor
+ * desc: fd is not a valid file descriptor, or fd was not opened for writing.
+ * This includes file descriptors opened with O_RDONLY, O_PATH, or file
+ * descriptors that have been closed. Also returned if the file structure
+ * does not have FMODE_WRITE or FMODE_CAN_WRITE set.
+ *
+ * error: EFAULT, Bad address
+ * desc: buf points outside the accessible address space. The buffer address
+ * failed access_ok() validation. Can also occur if a fault happens during
+ * copy_from_user() when reading data from user space.
+ *
+ * error: EINVAL, Invalid argument
+ * desc: Returned in several cases: (1) The file descriptor refers to an
+ * object that is not suitable for writing (no write or write_iter method).
+ * (2) The file was opened with O_DIRECT and the buffer alignment, offset,
+ * or count does not meet the filesystem's alignment requirements. (3) The
+ * count argument, when cast to ssize_t, is negative. (4) For IOCB_NOWAIT
+ * operations on non-O_DIRECT files that don't support WASYNC.
+ *
+ * error: EAGAIN, Resource temporarily unavailable
+ * desc: fd refers to a file (pipe, socket, device) that is marked non-blocking
+ * (O_NONBLOCK) and the write would block because the buffer is full. Also
+ * returned with IOCB_NOWAIT when data cannot be written immediately.
+ * Equivalent to EWOULDBLOCK. The application should retry later or use
+ * select/poll/epoll to wait for writability.
+ *
+ * error: EINTR, Interrupted system call
+ * desc: The call was interrupted by a signal before any data was written. This
+ * only occurs if no data has been transferred; if some data was written
+ * before the signal, the call returns the number of bytes written. The
+ * caller should typically restart the write.
+ *
+ * error: EPIPE, Broken pipe
+ * desc: fd refers to a pipe or socket whose reading end has been closed.
+ * When this condition occurs, the calling process also receives a SIGPIPE
+ * signal. If the signal is caught or ignored, EPIPE is still returned.
+ * For sockets, MSG_NOSIGNAL (via send()) suppresses the signal. For
+ * pwritev2(), the RWF_NOSIGNAL flag suppresses it.
+ *
+ * error: EFBIG, File too large
+ * desc: An attempt was made to write a file that exceeds the implementation-
+ * defined maximum file size or the file size limit (RLIMIT_FSIZE) of the
+ * process. When RLIMIT_FSIZE is exceeded, the process also receives SIGXFSZ.
+ * For files not opened with O_LARGEFILE on 32-bit systems, the limit is 2GB.
+ *
+ * error: ENOSPC, No space left on device
+ * desc: The device containing the file has no room for the data. This can
+ * occur mid-write resulting in a short write followed by ENOSPC on retry.
+ *
+ * error: EDQUOT, Disk quota exceeded
+ * desc: The user's quota of disk blocks on the filesystem has been exhausted.
+ * Like ENOSPC, this can result in a short write.
+ *
+ * error: EIO, Input/output error
+ * desc: A low-level I/O error occurred while modifying the inode or writing
+ * data. This typically indicates hardware failure, filesystem corruption,
+ * or network filesystem timeout. Some data may have been written.
+ *
+ * error: EPERM, Operation not permitted
+ * desc: The operation was prevented: (1) by a file seal (F_SEAL_WRITE or
+ * F_SEAL_FUTURE_WRITE on memfd/shmem), (2) writing to an immutable inode
+ * (IS_IMMUTABLE), (3) by an LSM hook denying the operation, or (4) by a
+ * fanotify permission event denying the write.
+ *
+ * error: EOVERFLOW, Value too large for defined data type
+ * desc: The file position plus count would exceed LLONG_MAX. Also returned
+ * when the offset would exceed filesystem limits after the write.
+ *
+ * error: EDESTADDRREQ, Destination address required
+ * desc: fd is a datagram socket for which no peer address has been set using
+ * connect(2). Use sendto(2) to specify the destination address.
+ *
+ * error: ETXTBSY, Text file busy
+ * desc: The file is being used as a swap file (IS_SWAPFILE). Note: unlike
+ * the traditional Unix meaning, Linux does not return ETXTBSY when writing
+ * to an executing binary; that only blocks open() with O_WRONLY/O_RDWR.
+ *
+ * error: EXDEV, Cross-device link
+ * desc: When writing to a pipe that has been configured as a watch queue
+ * (CONFIG_WATCH_QUEUE), direct write() calls are not supported.
+ *
+ * error: ENOMEM, Out of memory
+ * desc: Insufficient kernel memory was available for the write operation.
+ * For pipes, this occurs when allocating pages for the pipe buffer.
+ *
+ * error: ERESTARTSYS, Restart system call (internal)
+ * desc: Internal error code indicating the syscall should be restarted. This
+ * is converted to EINTR if SA_RESTART is not set on the signal handler, or
+ * the syscall is transparently restarted if SA_RESTART is set. User space
+ * should not see this error code directly.
+ *
+ * error: EACCES, Permission denied
+ * desc: The security subsystem (LSM such as SELinux or AppArmor) denied the
+ * write operation via security_file_permission(). This can occur even if
+ * the file was successfully opened.
+ *
+ * lock: file->f_pos_lock
+ * type: KAPI_LOCK_MUTEX
+ * acquired: conditional
+ * released: true
+ * desc: For regular files that require atomic position updates (FMODE_ATOMIC_POS),
+ * the f_pos_lock mutex is acquired by fdget_pos() at syscall entry and released
+ * by fdput_pos() at syscall exit. This serializes concurrent writes sharing
+ * the same file description. Not acquired for stream files (FMODE_STREAM like
+ * pipes and sockets) or when the file is not shared.
+ *
+ * lock: sb->s_writers (freeze protection)
+ * type: KAPI_LOCK_CUSTOM
+ * acquired: conditional
+ * released: true
+ * desc: For regular files, file_start_write() acquires freeze protection on
+ * the superblock via sb_start_write() before the write, and file_end_write()
+ * releases it after. This prevents writes during filesystem freeze. Not
+ * acquired for non-regular files (pipes, sockets, devices).
+ *
+ * lock: inode->i_rwsem
+ * type: KAPI_LOCK_RWLOCK
+ * acquired: conditional
+ * released: true
+ * desc: For regular files using generic_file_write_iter(), the inode's i_rwsem
+ * is acquired in write mode before modifying file data. This is internal to
+ * the filesystem and released before return. Not all filesystems use this
+ * pattern.
+ *
+ * lock: pipe->mutex
+ * type: KAPI_LOCK_MUTEX
+ * acquired: conditional
+ * released: true
+ * desc: For pipes and FIFOs, the pipe's mutex is held while modifying pipe
+ * buffers. Released temporarily while waiting for space, then reacquired.
+ *
+ * lock: RCU read-side
+ * type: KAPI_LOCK_RCU
+ * acquired: conditional
+ * released: true
+ * desc: Used during file descriptor lookup via fdget(). RCU read lock protects
+ * access to the file descriptor table. Released by fdput() at syscall exit.
+ *
+ * signal: SIGPIPE
+ * direction: KAPI_SIGNAL_SEND
+ * action: KAPI_SIGNAL_ACTION_TERMINATE
+ * condition: Writing to a pipe or socket with no readers
+ * desc: When writing to a pipe whose read end is closed, or a socket whose
+ * peer has closed, SIGPIPE is sent to the calling process. The default
+ * action terminates the process. Use signal(SIGPIPE, SIG_IGN) to suppress
+ * for write(). EPIPE is returned regardless of signal disposition.
+ * timing: KAPI_SIGNAL_TIME_DURING
+ *
+ * signal: SIGXFSZ
+ * direction: KAPI_SIGNAL_SEND
+ * action: KAPI_SIGNAL_ACTION_COREDUMP
+ * condition: Writing exceeds RLIMIT_FSIZE
+ * desc: When a write would exceed the soft file size limit (RLIMIT_FSIZE),
+ * SIGXFSZ is sent. The default action terminates with a core dump. The
+ * write returns EFBIG. If RLIMIT_FSIZE is RLIM_INFINITY, no signal is sent.
+ * timing: KAPI_SIGNAL_TIME_DURING
+ *
+ * signal: Any signal
+ * direction: KAPI_SIGNAL_RECEIVE
+ * action: KAPI_SIGNAL_ACTION_RETURN
+ * condition: While blocked waiting for space (pipes, sockets)
+ * desc: The syscall may be interrupted by signals while waiting for buffer
+ * space to become available. If interrupted before any data is written,
+ * returns -EINTR or -ERESTARTSYS. If data was already written, returns the
+ * byte count. Restartable if SA_RESTART is set and no data was written.
+ * error: -EINTR
+ * timing: KAPI_SIGNAL_TIME_DURING
+ * restartable: yes
+ *
+ * side-effect: KAPI_EFFECT_FILE_POSITION
+ * target: file->f_pos
+ * condition: For seekable files when write succeeds (returns > 0)
+ * desc: The file offset (f_pos) is advanced by the number of bytes written.
+ * For files opened with O_APPEND, f_pos is first set to file size. For
+ * stream files (FMODE_STREAM such as pipes and sockets), the offset is not
+ * used or modified. Position updates are protected by f_pos_lock when
+ * shared.
+ * reversible: no
+ *
+ * side-effect: KAPI_EFFECT_MODIFY_STATE
+ * target: inode timestamps (mtime, ctime)
+ * condition: When write succeeds (returns > 0)
+ * desc: Updates the file's modification time (mtime) and change time (ctime)
+ * via file_update_time(). The update precision depends on filesystem mount
+ * options (fine-grained timestamps for multigrain inodes).
+ * reversible: no
+ *
+ * side-effect: KAPI_EFFECT_MODIFY_STATE
+ * target: SUID/SGID bits (mode)
+ * condition: When writing to a setuid/setgid file
+ * desc: The SUID bit is cleared when a non-root user writes to a file with
+ * the bit set. The SGID bit may also be cleared. This is a security feature
+ * to prevent privilege escalation via modified setuid binaries. Done via
+ * file_remove_privs().
+ * reversible: no
+ *
+ * side-effect: KAPI_EFFECT_MODIFY_STATE
+ * target: file data
+ * condition: When write succeeds (returns > 0)
+ * desc: Modifies the file's data content. For regular files, data is written
+ * to the page cache (buffered I/O) or directly to storage (O_DIRECT).
+ * Data may not be persistent until fsync() is called or the file is closed.
+ * reversible: no
+ *
+ * side-effect: KAPI_EFFECT_MODIFY_STATE
+ * target: task I/O accounting
+ * condition: Always
+ * desc: Updates the current task's I/O accounting statistics. The wchar field
+ * (write characters) is incremented by bytes written via add_wchar() only on
+ * successful writes (ret > 0). The syscw field (syscall write count) is
+ * incremented unconditionally via inc_syscw(). These statistics are visible
+ * in /proc/[pid]/io.
+ * reversible: no
+ *
+ * side-effect: KAPI_EFFECT_MODIFY_STATE
+ * target: fsnotify events
+ * condition: When write returns > 0
+ * desc: Generates an FS_MODIFY fsnotify event via fsnotify_modify(), allowing
+ * inotify, fanotify, and dnotify watchers to be notified of the write.
+ *
+ * capability: CAP_DAC_OVERRIDE
+ * type: KAPI_CAP_BYPASS_CHECK
+ * allows: Bypass discretionary access control on write permission
+ * without: Standard DAC checks are enforced
+ * condition: Checked via security_file_permission() during rw_verify_area()
+ *
+ * capability: CAP_FOWNER
+ * type: KAPI_CAP_BYPASS_CHECK
+ * allows: Bypass ownership checks for SUID/SGID clearing
+ * without: SUID/SGID bits are cleared on write by non-owner
+ * condition: Checked during file_remove_privs()
+ *
+ * constraint: MAX_RW_COUNT
+ * desc: The count parameter is silently clamped to MAX_RW_COUNT (INT_MAX &
+ * PAGE_MASK, approximately 2GB minus one page) to prevent integer overflow
+ * in internal calculations. This is transparent to the caller.
+ * expr: actual_count = min(count, MAX_RW_COUNT)
+ *
+ * constraint: File must be open for writing
+ * desc: The file descriptor must have been opened with O_WRONLY or O_RDWR.
+ * Files opened with O_RDONLY or O_PATH cannot be written and return EBADF.
+ * The file must have both FMODE_WRITE and FMODE_CAN_WRITE flags set.
+ * expr: (file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_CAN_WRITE)
+ *
+ * constraint: RLIMIT_FSIZE
+ * desc: The size of data written is constrained by the RLIMIT_FSIZE resource
+ * limit. If writing would exceed this limit, SIGXFSZ is sent and EFBIG is
+ * returned. The limit does not apply to files beyond the limit - only to
+ * writes that would cross it.
+ * expr: pos + count <= rlimit(RLIMIT_FSIZE) || rlimit(RLIMIT_FSIZE) == RLIM_INFINITY
+ *
+ * constraint: File seals
+ * desc: For memfd or shmem files with F_SEAL_WRITE or F_SEAL_FUTURE_WRITE
+ * seals applied, all write operations fail with EPERM. With F_SEAL_GROW,
+ * writes that would extend file size fail with EPERM.
+ *
+ * examples: n = write(fd, buf, sizeof(buf)); // Basic write
+ * n = write(STDOUT_FILENO, msg, strlen(msg)); // Write to stdout
+ * while (total < len) { n = write(fd, buf+total, len-total); if (n<0) break; total += n; } // Handle short writes
+ * if (write(pipefd[1], &byte, 1) < 0 && errno == EPIPE) { handle_broken_pipe(); } // Pipe error handling
+ *
+ * notes: The behavior of write() varies significantly depending on the type of
+ * file descriptor:
+ *
+ * - Regular files: Writes to the page cache (buffered) or directly to storage
+ * (O_DIRECT). Short writes are rare except near RLIMIT_FSIZE or disk full.
+ * O_APPEND is atomic for determining write position.
+ *
+ * - Pipes and FIFOs: Blocking by default. Writes up to PIPE_BUF (4096 bytes
+ * on Linux) are guaranteed atomic. Larger writes may be interleaved with
+ * writes from other processes. Blocks if pipe is full; returns EAGAIN with
+ * O_NONBLOCK. SIGPIPE/EPIPE if no readers.
+ *
+ * - Sockets: Behavior depends on socket type and protocol. Stream sockets
+ * (TCP) may return partial writes. Datagram sockets (UDP) typically write
+ * complete messages or fail. SIGPIPE/EPIPE for broken connections (unless
+ * MSG_NOSIGNAL). EDESTADDRREQ for unconnected datagram sockets.
+ *
+ * - Terminals: May block on flow control. Canonical vs raw mode affects
+ * behavior. Special characters may be interpreted.
+ *
+ * - Device special files: Behavior is device-specific. Block devices behave
+ * similarly to regular files. Character device behavior varies.
+ *
+ * Race condition considerations: Concurrent writes from threads sharing a
+ * file description race on the file position. Linux 3.14+ provides atomic
+ * position updates via f_pos_lock for regular files (FMODE_ATOMIC_POS), but
+ * for maximum safety, use pwrite() for concurrent positioned writes.
+ *
+ * O_DIRECT writes bypass the page cache and typically require buffer and
+ * offset alignment to filesystem block size. Query requirements via statx()
+ * with STATX_DIOALIGN (Linux 6.1+). Unaligned O_DIRECT writes return EINVAL
+ * on most filesystems.
+ *
+ * For zero-copy writes, consider using splice(2), sendfile(2), or vmsplice(2)
+ * instead of copying data through user-space buffers with write().
+ *
+ * Partial writes (short writes) must be handled by application code.
+ * Applications should loop until all data is written or an error occurs.
+ *
+ * since-version: 1.0
+ */
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
size_t, count)
{
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH 9/9] kernel/api: add runtime verification selftest
2026-03-13 15:09 [PATCH 0/9] Kernel API Specification Framework Sasha Levin
` (7 preceding siblings ...)
2026-03-13 15:09 ` [PATCH 8/9] kernel/api: add API specification for sys_write Sasha Levin
@ 2026-03-13 15:09 ` Sasha Levin
2026-03-14 18:18 ` [PATCH 0/9] Kernel API Specification Framework Jakub Kicinski
9 siblings, 0 replies; 37+ messages in thread
From: Sasha Levin @ 2026-03-13 15:09 UTC (permalink / raw)
To: linux-api, linux-kernel
Cc: linux-doc, linux-fsdevel, linux-kbuild, linux-kselftest,
workflows, tools, x86, Thomas Gleixner, Paul E. McKenney,
Greg Kroah-Hartman, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann, Sasha Levin
Add a selftest for CONFIG_KAPI_RUNTIME_CHECKS that exercises
sys_open/sys_read/sys_write/sys_close through raw syscall() and
verifies KAPI pre-validation catches invalid parameters while
allowing valid operations through.
Test cases (TAP output):
1-4: Valid open/read/write/close succeed
5-7: Invalid flags, mode bits, NULL path rejected with EINVAL
8: dmesg contains expected KAPI warning strings
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
MAINTAINERS | 1 +
tools/testing/selftests/kapi/Makefile | 7 +
tools/testing/selftests/kapi/kapi_test_util.h | 31 +
tools/testing/selftests/kapi/test_kapi.c | 1021 +++++++++++++++++
4 files changed, 1060 insertions(+)
create mode 100644 tools/testing/selftests/kapi/Makefile
create mode 100644 tools/testing/selftests/kapi/kapi_test_util.h
create mode 100644 tools/testing/selftests/kapi/test_kapi.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 6fa403d620aab..2bc938fa49759 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13820,6 +13820,7 @@ F: include/linux/syscall_api_spec.h
F: kernel/api/
F: tools/kapi/
F: tools/lib/python/kdoc/kdoc_apispec.py
+F: tools/testing/selftests/kapi/
KERNEL AUTOMOUNTER
M: Ian Kent <raven@themaw.net>
diff --git a/tools/testing/selftests/kapi/Makefile b/tools/testing/selftests/kapi/Makefile
new file mode 100644
index 0000000000000..5f3fdeddcae41
--- /dev/null
+++ b/tools/testing/selftests/kapi/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+
+TEST_GEN_PROGS := test_kapi
+
+CFLAGS += -static -Wall -O2 $(KHDR_INCLUDES)
+
+include ../lib.mk
diff --git a/tools/testing/selftests/kapi/kapi_test_util.h b/tools/testing/selftests/kapi/kapi_test_util.h
new file mode 100644
index 0000000000000..f18b44ff6239d
--- /dev/null
+++ b/tools/testing/selftests/kapi/kapi_test_util.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Compatibility helpers for KAPI selftests.
+ *
+ * __NR_open is not defined on aarch64 and riscv64 (only __NR_openat exists).
+ * Provide a wrapper that uses __NR_openat with AT_FDCWD to achieve the same
+ * behavior as __NR_open on architectures that lack it.
+ */
+#ifndef KAPI_TEST_UTIL_H
+#define KAPI_TEST_UTIL_H
+
+#include <fcntl.h>
+#include <sys/syscall.h>
+
+#ifndef __NR_open
+/*
+ * On architectures without __NR_open (e.g., aarch64, riscv64),
+ * use openat(AT_FDCWD, ...) which is equivalent.
+ */
+static inline long kapi_sys_open(const char *pathname, int flags, int mode)
+{
+ return syscall(__NR_openat, AT_FDCWD, pathname, flags, mode);
+}
+#else
+static inline long kapi_sys_open(const char *pathname, int flags, int mode)
+{
+ return syscall(__NR_open, pathname, flags, mode);
+}
+#endif
+
+#endif /* KAPI_TEST_UTIL_H */
diff --git a/tools/testing/selftests/kapi/test_kapi.c b/tools/testing/selftests/kapi/test_kapi.c
new file mode 100644
index 0000000000000..81aaa4f607073
--- /dev/null
+++ b/tools/testing/selftests/kapi/test_kapi.c
@@ -0,0 +1,1021 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Userspace selftest for KAPI runtime verification of syscall parameters.
+ *
+ * Exercises sys_open, sys_read, sys_write, and sys_close through raw
+ * syscall() to ensure KAPI pre-validation wrappers interact correctly
+ * with normal kernel error handling.
+ *
+ * Requires CONFIG_KAPI_RUNTIME_CHECKS=y for full coverage; many tests
+ * also pass without it.
+ *
+ * TAP output format.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/syscall.h>
+#include <sys/stat.h>
+#include <linux/limits.h>
+#include "kapi_test_util.h"
+
+#define NUM_TESTS 29
+
+static int test_num;
+static int failures;
+static volatile sig_atomic_t got_sigpipe;
+
+static void tap_ok(const char *desc)
+{
+ printf("ok %d - %s\n", ++test_num, desc);
+}
+
+static void tap_fail(const char *desc, const char *reason)
+{
+ printf("not ok %d - %s # %s\n", ++test_num, desc, reason);
+ failures++;
+}
+
+static void sigpipe_handler(int sig)
+{
+ (void)sig;
+ got_sigpipe = 1;
+}
+
+/* ---- Valid operation tests ---- */
+
+/*
+ * Test 1: open a readable file
+ * Returns fd on success.
+ */
+static int test_open_valid(void)
+{
+ errno = 0;
+ long fd = kapi_sys_open("/etc/hostname", O_RDONLY, 0);
+
+ if (fd >= 0) {
+ tap_ok("open valid file");
+ } else {
+ /* /etc/hostname might not exist; try /etc/passwd */
+ errno = 0;
+ fd = kapi_sys_open("/etc/passwd", O_RDONLY, 0);
+ if (fd >= 0)
+ tap_ok("open valid file (fallback /etc/passwd)");
+ else
+ tap_fail("open valid file", strerror(errno));
+ }
+ return (int)fd;
+}
+
+/*
+ * Test 2: read from fd
+ */
+static void test_read_valid(int fd)
+{
+ char buf[256];
+
+ errno = 0;
+ long ret = syscall(__NR_read, fd, buf, sizeof(buf));
+
+ if (ret > 0)
+ tap_ok("read from valid fd");
+ else if (ret == 0)
+ tap_ok("read from valid fd (EOF)");
+ else
+ tap_fail("read from valid fd", strerror(errno));
+}
+
+/*
+ * Test 3: write to /dev/null
+ */
+static void test_write_valid(void)
+{
+ errno = 0;
+ long devnull = kapi_sys_open("/dev/null", O_WRONLY, 0);
+
+ if (devnull < 0) {
+ tap_fail("write to /dev/null (open failed)", strerror(errno));
+ return;
+ }
+
+ errno = 0;
+ long ret = syscall(__NR_write, (int)devnull, "hello", 5);
+
+ if (ret == 5)
+ tap_ok("write to /dev/null");
+ else
+ tap_fail("write to /dev/null",
+ ret < 0 ? strerror(errno) : "short write");
+
+ syscall(__NR_close, (int)devnull);
+}
+
+/*
+ * Test 4: close fd
+ */
+static void test_close_valid(int fd)
+{
+ errno = 0;
+ long ret = syscall(__NR_close, fd);
+
+ if (ret == 0)
+ tap_ok("close valid fd");
+ else
+ tap_fail("close valid fd", strerror(errno));
+}
+
+/* ---- KAPI parameter rejection tests ---- */
+
+/*
+ * Test 5: open with invalid flag bits
+ * 0x10000000 is outside the valid O_* mask, KAPI should reject.
+ */
+static void test_open_invalid_flags(void)
+{
+ errno = 0;
+ long ret = kapi_sys_open("/tmp/kapi_test", 0x10000000, 0);
+
+ if (ret == -1 && errno == EINVAL) {
+ tap_ok("open with invalid flags returns EINVAL");
+ } else if (ret >= 0) {
+ tap_fail("open with invalid flags", "expected EINVAL, got success");
+ syscall(__NR_close, (int)ret);
+ } else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "expected EINVAL, got %s",
+ strerror(errno));
+ tap_fail("open with invalid flags", msg);
+ }
+}
+
+/*
+ * Test 6: open with invalid mode bits
+ * 0xFFFF has bits outside S_IALLUGO (07777), KAPI should reject.
+ */
+static void test_open_invalid_mode(void)
+{
+ errno = 0;
+ long ret = kapi_sys_open("/tmp/kapi_test_mode",
+ O_CREAT | O_WRONLY, 0xFFFF);
+
+ if (ret == -1 && errno == EINVAL) {
+ tap_ok("open with invalid mode returns EINVAL");
+ } else if (ret >= 0) {
+ tap_fail("open with invalid mode", "expected EINVAL, got success");
+ syscall(__NR_close, (int)ret);
+ unlink("/tmp/kapi_test_mode");
+ } else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "expected EINVAL, got %s",
+ strerror(errno));
+ tap_fail("open with invalid mode", msg);
+ }
+}
+
+/*
+ * Test 7: open with NULL path
+ * KAPI USER_PATH constraint should reject NULL.
+ */
+static void test_open_null_path(void)
+{
+ errno = 0;
+ long ret = kapi_sys_open(NULL, O_RDONLY, 0);
+
+ if (ret == -1 && errno == EINVAL) {
+ tap_ok("open with NULL path returns EINVAL");
+ } else if (ret == -1 && errno == EFAULT) {
+ /* Kernel may catch this as EFAULT before KAPI */
+ tap_ok("open with NULL path returns EFAULT (acceptable)");
+ } else if (ret >= 0) {
+ tap_fail("open with NULL path", "expected error, got success");
+ syscall(__NR_close, (int)ret);
+ } else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "got %s", strerror(errno));
+ tap_fail("open with NULL path", msg);
+ }
+}
+
+/*
+ * Test 8: open with flag bit 30 set (0x40000000)
+ * This bit is outside the valid O_* mask, KAPI should reject with EINVAL.
+ */
+static void test_open_flag_bit30(void)
+{
+ errno = 0;
+ long ret = kapi_sys_open("/dev/null", 0x40000000, 0);
+
+ if (ret == -1 && errno == EINVAL)
+ tap_ok("open with flag bit 30 (0x40000000) returns EINVAL");
+ else if (ret >= 0) {
+ tap_fail("open with flag bit 30 (0x40000000) returns EINVAL",
+ "expected EINVAL, got success");
+ syscall(__NR_close, (int)ret);
+ } else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "expected EINVAL, got %s",
+ strerror(errno));
+ tap_fail("open with flag bit 30 (0x40000000) returns EINVAL",
+ msg);
+ }
+}
+
+/* ---- Boundary condition and error path tests ---- */
+
+/*
+ * Test 9: read with fd=-1 should return an error.
+ * With CONFIG_KAPI_RUNTIME_CHECKS=y, KAPI validates the fd first and
+ * rejects negative fds (other than AT_FDCWD) with EINVAL. Without
+ * KAPI, the kernel returns EBADF. Accept either.
+ */
+static void test_read_bad_fd(void)
+{
+ char buf[16];
+
+ errno = 0;
+ long ret = syscall(__NR_read, -1, buf, sizeof(buf));
+
+ if (ret == -1 && (errno == EBADF || errno == EINVAL))
+ tap_ok("read with fd=-1 returns error");
+ else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "expected EBADF/EINVAL, got %s",
+ ret >= 0 ? "success" : strerror(errno));
+ tap_fail("read with fd=-1 returns error", msg);
+ }
+}
+
+/*
+ * Test 10: read with count=0 should return 0
+ */
+static void test_read_zero_count(void)
+{
+ char buf[1];
+ long fd;
+
+ errno = 0;
+ fd = kapi_sys_open("/dev/null", O_RDONLY, 0);
+ if (fd < 0) {
+ tap_fail("read with count=0 returns 0",
+ "cannot open /dev/null");
+ return;
+ }
+
+ errno = 0;
+ long ret = syscall(__NR_read, (int)fd, buf, 0);
+
+ if (ret == 0)
+ tap_ok("read with count=0 returns 0");
+ else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "expected 0, got %ld (errno=%s)",
+ ret, strerror(errno));
+ tap_fail("read with count=0 returns 0", msg);
+ }
+
+ syscall(__NR_close, (int)fd);
+}
+
+/*
+ * Test 11: write with count=0 should return 0
+ */
+static void test_write_zero_count(void)
+{
+ long fd;
+
+ errno = 0;
+ fd = kapi_sys_open("/dev/null", O_WRONLY, 0);
+ if (fd < 0) {
+ tap_fail("write with count=0 returns 0",
+ "cannot open /dev/null");
+ return;
+ }
+
+ errno = 0;
+ long ret = syscall(__NR_write, (int)fd, "x", 0);
+
+ if (ret == 0)
+ tap_ok("write with count=0 returns 0");
+ else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "expected 0, got %ld (errno=%s)",
+ ret, strerror(errno));
+ tap_fail("write with count=0 returns 0", msg);
+ }
+
+ syscall(__NR_close, (int)fd);
+}
+
+/*
+ * Test 12: open with a path longer than PATH_MAX should fail
+ * Expect ENAMETOOLONG or EINVAL.
+ */
+static void test_open_long_path(void)
+{
+ char *longpath;
+ size_t len = PATH_MAX + 256;
+
+ longpath = malloc(len);
+ if (!longpath) {
+ tap_fail("open with path > PATH_MAX", "malloc failed");
+ return;
+ }
+
+ memset(longpath, 'A', len - 1);
+ longpath[0] = '/';
+ longpath[len - 1] = '\0';
+
+ errno = 0;
+ long ret = kapi_sys_open(longpath, O_RDONLY, 0);
+
+ if (ret == -1 && (errno == ENAMETOOLONG || errno == EINVAL))
+ tap_ok("open with path > PATH_MAX returns ENAMETOOLONG/EINVAL");
+ else if (ret >= 0) {
+ tap_fail("open with path > PATH_MAX",
+ "expected error, got success");
+ syscall(__NR_close, (int)ret);
+ } else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg),
+ "expected ENAMETOOLONG/EINVAL, got %s",
+ strerror(errno));
+ tap_fail("open with path > PATH_MAX", msg);
+ }
+
+ free(longpath);
+}
+
+/*
+ * Test 13: read with unmapped user pointer should return EFAULT or EINVAL.
+ * Use a pipe with data so the kernel actually tries to copy to the buffer.
+ */
+static void test_read_unmapped_buf(void)
+{
+ int pipefd[2];
+
+ if (pipe(pipefd) < 0) {
+ tap_fail("read with unmapped buffer returns EFAULT/EINVAL",
+ "pipe() failed");
+ return;
+ }
+
+ /* Write some data so read has something to copy */
+ (void)write(pipefd[1], "hello", 5);
+
+ errno = 0;
+ long ret = syscall(__NR_read, pipefd[0], (void *)0xDEAD0000, 16);
+
+ if (ret == -1 && (errno == EFAULT || errno == EINVAL))
+ tap_ok("read with unmapped buffer returns EFAULT/EINVAL");
+ else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg),
+ "expected EFAULT/EINVAL, got %s",
+ ret >= 0 ? "success" : strerror(errno));
+ tap_fail("read with unmapped buffer returns EFAULT/EINVAL",
+ msg);
+ }
+
+ close(pipefd[0]);
+ close(pipefd[1]);
+}
+
+/*
+ * Test 14: write with unmapped user pointer should return EFAULT or EINVAL.
+ * Use a pipe so the kernel actually tries to copy from the buffer.
+ */
+static void test_write_unmapped_buf(void)
+{
+ int pipefd[2];
+
+ if (pipe(pipefd) < 0) {
+ tap_fail("write with unmapped buffer returns EFAULT/EINVAL",
+ "pipe() failed");
+ return;
+ }
+
+ errno = 0;
+ long ret = syscall(__NR_write, pipefd[1], (void *)0xDEAD0000, 16);
+
+ if (ret == -1 && (errno == EFAULT || errno == EINVAL))
+ tap_ok("write with unmapped buffer returns EFAULT/EINVAL");
+ else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg),
+ "expected EFAULT/EINVAL, got %s",
+ ret >= 0 ? "success" : strerror(errno));
+ tap_fail("write with unmapped buffer returns EFAULT/EINVAL",
+ msg);
+ }
+
+ close(pipefd[0]);
+ close(pipefd[1]);
+}
+
+/*
+ * Test 15: close an already-closed fd should return EBADF
+ */
+static void test_close_already_closed(void)
+{
+ long fd;
+
+ errno = 0;
+ fd = kapi_sys_open("/dev/null", O_RDONLY, 0);
+ if (fd < 0) {
+ tap_fail("close already-closed fd returns EBADF",
+ "cannot open /dev/null");
+ return;
+ }
+
+ /* Close it once - should succeed */
+ syscall(__NR_close, (int)fd);
+
+ /* Close it again - should fail with EBADF */
+ errno = 0;
+ long ret = syscall(__NR_close, (int)fd);
+
+ if (ret == -1 && errno == EBADF)
+ tap_ok("close already-closed fd returns EBADF");
+ else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "expected EBADF, got %s",
+ ret == 0 ? "success" : strerror(errno));
+ tap_fail("close already-closed fd returns EBADF", msg);
+ }
+}
+
+/*
+ * Test 16: open /dev/null with O_RDONLY|O_CLOEXEC should succeed
+ */
+static void test_open_valid_cloexec(void)
+{
+ errno = 0;
+ long fd = kapi_sys_open("/dev/null", O_RDONLY | O_CLOEXEC, 0);
+
+ if (fd >= 0) {
+ tap_ok("open /dev/null with O_RDONLY|O_CLOEXEC succeeds");
+ syscall(__NR_close, (int)fd);
+ } else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "expected success, got %s",
+ strerror(errno));
+ tap_fail("open /dev/null with O_RDONLY|O_CLOEXEC succeeds",
+ msg);
+ }
+}
+
+/*
+ * Test 17: write 0 bytes to /dev/null should return 0
+ */
+static void test_write_zero_devnull(void)
+{
+ long fd;
+
+ errno = 0;
+ fd = kapi_sys_open("/dev/null", O_WRONLY, 0);
+ if (fd < 0) {
+ tap_fail("write 0 bytes to /dev/null returns 0",
+ "cannot open /dev/null");
+ return;
+ }
+
+ errno = 0;
+ long ret = syscall(__NR_write, (int)fd, "", 0);
+
+ if (ret == 0)
+ tap_ok("write 0 bytes to /dev/null returns 0");
+ else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "expected 0, got %ld (errno=%s)",
+ ret, strerror(errno));
+ tap_fail("write 0 bytes to /dev/null returns 0", msg);
+ }
+
+ syscall(__NR_close, (int)fd);
+}
+
+/*
+ * Test 18: read from a write-only fd should return EBADF
+ */
+static void test_read_writeonly_fd(void)
+{
+ long fd;
+
+ errno = 0;
+ fd = kapi_sys_open("/dev/null", O_WRONLY, 0);
+ if (fd < 0) {
+ tap_fail("read from write-only fd returns EBADF",
+ "cannot open /dev/null");
+ return;
+ }
+
+ char buf[16];
+
+ errno = 0;
+ long ret = syscall(__NR_read, (int)fd, buf, sizeof(buf));
+
+ if (ret == -1 && errno == EBADF)
+ tap_ok("read from write-only fd returns EBADF");
+ else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "expected EBADF, got %s",
+ ret >= 0 ? "success" : strerror(errno));
+ tap_fail("read from write-only fd returns EBADF", msg);
+ }
+
+ syscall(__NR_close, (int)fd);
+}
+
+/*
+ * Test 19: write to a read-only fd should return EBADF
+ */
+static void test_write_readonly_fd(void)
+{
+ long fd;
+
+ errno = 0;
+ fd = kapi_sys_open("/dev/null", O_RDONLY, 0);
+ if (fd < 0) {
+ tap_fail("write to read-only fd returns EBADF",
+ "cannot open /dev/null");
+ return;
+ }
+
+ errno = 0;
+ long ret = syscall(__NR_write, (int)fd, "hello", 5);
+
+ if (ret == -1 && errno == EBADF)
+ tap_ok("write to read-only fd returns EBADF");
+ else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "expected EBADF, got %s",
+ ret >= 0 ? "success" : strerror(errno));
+ tap_fail("write to read-only fd returns EBADF", msg);
+ }
+
+ syscall(__NR_close, (int)fd);
+}
+
+/*
+ * Test 20: close fd 9999 (likely invalid) should return EBADF
+ */
+static void test_close_fd_9999(void)
+{
+ errno = 0;
+ long ret = syscall(__NR_close, 9999);
+
+ if (ret == -1 && errno == EBADF)
+ tap_ok("close fd 9999 returns EBADF");
+ else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "expected EBADF, got %s",
+ ret == 0 ? "success" : strerror(errno));
+ tap_fail("close fd 9999 returns EBADF", msg);
+ }
+}
+
+/*
+ * Test 21: read from pipe after write end is closed returns 0 (EOF)
+ */
+static void test_read_closed_pipe(void)
+{
+ int pipefd[2];
+
+ if (pipe(pipefd) < 0) {
+ tap_fail("read from closed pipe returns 0 (EOF)",
+ "pipe() failed");
+ return;
+ }
+
+ /* Close write end */
+ close(pipefd[1]);
+
+ char buf[16];
+
+ errno = 0;
+ long ret = syscall(__NR_read, pipefd[0], buf, sizeof(buf));
+
+ if (ret == 0)
+ tap_ok("read from closed pipe returns 0 (EOF)");
+ else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "expected 0, got %ld (errno=%s)",
+ ret, ret < 0 ? strerror(errno) : "n/a");
+ tap_fail("read from closed pipe returns 0 (EOF)", msg);
+ }
+
+ close(pipefd[0]);
+}
+
+/*
+ * Test 22: write to pipe after read end is closed returns EPIPE + SIGPIPE
+ */
+static void test_write_closed_pipe(void)
+{
+ int pipefd[2];
+ struct sigaction sa, old_sa;
+
+ if (pipe(pipefd) < 0) {
+ tap_fail("write to closed pipe returns EPIPE + SIGPIPE",
+ "pipe() failed");
+ return;
+ }
+
+ /* Install SIGPIPE handler */
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = sigpipe_handler;
+ sigemptyset(&sa.sa_mask);
+ sigaction(SIGPIPE, &sa, &old_sa);
+
+ got_sigpipe = 0;
+
+ /* Close read end */
+ close(pipefd[0]);
+
+ errno = 0;
+ long ret = syscall(__NR_write, pipefd[1], "hello", 5);
+
+ if (ret == -1 && errno == EPIPE && got_sigpipe)
+ tap_ok("write to closed pipe returns EPIPE + SIGPIPE");
+ else if (ret == -1 && errno == EPIPE)
+ tap_ok("write to closed pipe returns EPIPE (SIGPIPE not caught)");
+ else {
+ char msg[128];
+
+ snprintf(msg, sizeof(msg),
+ "expected EPIPE, got %s (sigpipe=%d)",
+ ret >= 0 ? "success" : strerror(errno),
+ got_sigpipe);
+ tap_fail("write to closed pipe returns EPIPE + SIGPIPE", msg);
+ }
+
+ /* Restore SIGPIPE handler */
+ sigaction(SIGPIPE, &old_sa, NULL);
+ close(pipefd[1]);
+}
+
+/*
+ * Test 23: open with O_DIRECTORY on a regular file returns ENOTDIR
+ */
+static void test_open_directory_on_file(void)
+{
+ errno = 0;
+ long ret = kapi_sys_open("/dev/null", O_RDONLY | O_DIRECTORY, 0);
+
+ if (ret == -1 && errno == ENOTDIR)
+ tap_ok("open O_DIRECTORY on regular file returns ENOTDIR");
+ else if (ret >= 0) {
+ tap_fail("open O_DIRECTORY on regular file",
+ "expected ENOTDIR, got success");
+ syscall(__NR_close, (int)ret);
+ } else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "expected ENOTDIR, got %s",
+ strerror(errno));
+ tap_fail("open O_DIRECTORY on regular file", msg);
+ }
+}
+
+/*
+ * Test 24: open nonexistent file without O_CREAT returns ENOENT
+ */
+static void test_open_nonexistent(void)
+{
+ errno = 0;
+ long ret = kapi_sys_open(
+ "/tmp/kapi_nonexistent_file_12345", O_RDONLY, 0);
+
+ if (ret == -1 && errno == ENOENT)
+ tap_ok("open nonexistent file without O_CREAT returns ENOENT");
+ else if (ret >= 0) {
+ tap_fail("open nonexistent file",
+ "expected ENOENT, got success (file exists?)");
+ syscall(__NR_close, (int)ret);
+ } else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "expected ENOENT, got %s",
+ strerror(errno));
+ tap_fail("open nonexistent file", msg);
+ }
+}
+
+/*
+ * Test 25: close stdin (fd 0) should succeed
+ * We dup it first so we can restore it.
+ */
+static void test_close_stdin(void)
+{
+ int saved_stdin = dup(0);
+
+ if (saved_stdin < 0) {
+ tap_fail("close stdin succeeds", "cannot dup stdin");
+ return;
+ }
+
+ errno = 0;
+ long ret = syscall(__NR_close, 0);
+
+ if (ret == 0)
+ tap_ok("close stdin (fd 0) succeeds");
+ else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "expected success, got %s",
+ strerror(errno));
+ tap_fail("close stdin (fd 0) succeeds", msg);
+ }
+
+ /* Restore stdin */
+ dup2(saved_stdin, 0);
+ close(saved_stdin);
+}
+
+/*
+ * Test 26: read after close returns EBADF
+ */
+static void test_read_after_close(void)
+{
+ long fd;
+
+ errno = 0;
+ fd = kapi_sys_open("/dev/null", O_RDONLY, 0);
+ if (fd < 0) {
+ tap_fail("read after close returns EBADF",
+ "cannot open /dev/null");
+ return;
+ }
+
+ syscall(__NR_close, (int)fd);
+
+ char buf[16];
+
+ errno = 0;
+ long ret = syscall(__NR_read, (int)fd, buf, sizeof(buf));
+
+ if (ret == -1 && errno == EBADF)
+ tap_ok("read after close returns EBADF");
+ else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "expected EBADF, got %s",
+ ret >= 0 ? "success" : strerror(errno));
+ tap_fail("read after close returns EBADF", msg);
+ }
+}
+
+/*
+ * Test 27: write with large count
+ * Without KAPI: the kernel clamps count to MAX_RW_COUNT and succeeds.
+ * With KAPI: KAPI validates the buffer against the count and may
+ * return EFAULT/EINVAL since the buffer is smaller than count.
+ * Accept either success or EFAULT/EINVAL.
+ */
+static void test_write_large_count(void)
+{
+ long fd;
+ char buf[64] = "test data";
+
+ errno = 0;
+ fd = kapi_sys_open("/dev/null", O_WRONLY, 0);
+ if (fd < 0) {
+ tap_fail("write with large count handled correctly",
+ "cannot open /dev/null");
+ return;
+ }
+
+ errno = 0;
+ long ret = syscall(__NR_write, (int)fd, buf, (size_t)0x7ffff000UL);
+
+ if (ret > 0)
+ tap_ok("write with large count succeeds (clamped, no KAPI)");
+ else if (ret == -1 && (errno == EFAULT || errno == EINVAL))
+ tap_ok("write with large count returns EFAULT/EINVAL (KAPI validates buffer)");
+ else {
+ char msg[64];
+
+ snprintf(msg, sizeof(msg), "expected success or EFAULT, got %s",
+ ret == 0 ? "zero" : strerror(errno));
+ tap_fail("write with large count handled correctly", msg);
+ }
+
+ syscall(__NR_close, (int)fd);
+}
+
+/* ---- Integration tests ---- */
+
+/*
+ * Test 28: full normal syscall path - open, read, write, close
+ * Verify KAPI does not interfere with normal operations.
+ */
+static void test_normal_path(void)
+{
+ long rd_fd, wr_fd;
+ char buf[128];
+ int ok = 1;
+ char reason[128] = "";
+
+ /* Open a readable file */
+ errno = 0;
+ rd_fd = kapi_sys_open("/etc/hostname", O_RDONLY, 0);
+ if (rd_fd < 0) {
+ errno = 0;
+ rd_fd = kapi_sys_open("/etc/passwd", O_RDONLY, 0);
+ }
+ if (rd_fd < 0) {
+ snprintf(reason, sizeof(reason), "open readable file: %s",
+ strerror(errno));
+ ok = 0;
+ }
+
+ /* Read from it */
+ if (ok) {
+ errno = 0;
+ long n = syscall(__NR_read, (int)rd_fd, buf, sizeof(buf));
+
+ if (n < 0) {
+ snprintf(reason, sizeof(reason), "read: %s",
+ strerror(errno));
+ ok = 0;
+ }
+ }
+
+ /* Open /dev/null for writing */
+ wr_fd = -1;
+ if (ok) {
+ errno = 0;
+ wr_fd = kapi_sys_open("/dev/null", O_WRONLY, 0);
+ if (wr_fd < 0) {
+ snprintf(reason, sizeof(reason),
+ "open /dev/null: %s", strerror(errno));
+ ok = 0;
+ }
+ }
+
+ /* Write to /dev/null */
+ if (ok) {
+ errno = 0;
+ long n = syscall(__NR_write, (int)wr_fd, "test", 4);
+
+ if (n != 4) {
+ snprintf(reason, sizeof(reason), "write: %s",
+ n < 0 ? strerror(errno) : "short write");
+ ok = 0;
+ }
+ }
+
+ /* Close both fds */
+ if (rd_fd >= 0) {
+ errno = 0;
+ if (syscall(__NR_close, (int)rd_fd) != 0 && ok) {
+ snprintf(reason, sizeof(reason), "close read fd: %s",
+ strerror(errno));
+ ok = 0;
+ }
+ }
+
+ if (wr_fd >= 0) {
+ errno = 0;
+ if (syscall(__NR_close, (int)wr_fd) != 0 && ok) {
+ snprintf(reason, sizeof(reason), "close write fd: %s",
+ strerror(errno));
+ ok = 0;
+ }
+ }
+
+ if (ok)
+ tap_ok("normal syscall path (open/read/write/close) works");
+ else
+ tap_fail("normal syscall path (open/read/write/close) works",
+ reason);
+}
+
+/*
+ * Test 29: verify dmesg contains KAPI warnings for the invalid tests
+ */
+static void test_dmesg_warnings(void)
+{
+ int kmsg_fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK);
+
+ if (kmsg_fd < 0) {
+ tap_ok("dmesg check skipped (cannot open /dev/kmsg)");
+ return;
+ }
+
+ /* Read all available kmsg messages from the start */
+ lseek(kmsg_fd, 0, SEEK_DATA);
+
+ char line[4096];
+ int found_invalid_bits = 0;
+ int found_null = 0;
+ ssize_t n;
+
+ while ((n = read(kmsg_fd, line, sizeof(line) - 1)) > 0) {
+ line[n] = '\0';
+ if (strstr(line, "contains invalid bits"))
+ found_invalid_bits++;
+ if (strstr(line, "NULL") && strstr(line, "not allowed"))
+ found_null++;
+ }
+
+ close(kmsg_fd);
+
+ if (found_invalid_bits >= 2 && found_null >= 1)
+ tap_ok("dmesg contains expected KAPI warnings");
+ else if (found_invalid_bits >= 1 || found_null >= 1) {
+ char msg[128];
+
+ snprintf(msg, sizeof(msg),
+ "partial: invalid_bits=%d null=%d",
+ found_invalid_bits, found_null);
+ tap_ok(msg);
+ } else {
+ tap_fail("dmesg KAPI warnings",
+ "no KAPI warnings found in dmesg");
+ }
+}
+
+int main(void)
+{
+ printf("TAP version 13\n");
+ printf("1..%d\n", NUM_TESTS);
+
+ /* Valid operations (1-4) */
+ int fd = test_open_valid();
+
+ if (fd >= 0)
+ test_read_valid(fd);
+ else
+ tap_fail("read from valid fd", "no fd from open");
+
+ test_write_valid();
+
+ if (fd >= 0)
+ test_close_valid(fd);
+ else
+ tap_fail("close valid fd", "no fd from open");
+
+ /* KAPI parameter rejection (5-8) */
+ test_open_invalid_flags();
+ test_open_invalid_mode();
+ test_open_null_path();
+ test_open_flag_bit30();
+
+ /* Boundary conditions and error paths (9-20) */
+ test_read_bad_fd();
+ test_read_zero_count();
+ test_write_zero_count();
+ test_open_long_path();
+ test_read_unmapped_buf();
+ test_write_unmapped_buf();
+ test_close_already_closed();
+ test_open_valid_cloexec();
+ test_write_zero_devnull();
+ test_read_writeonly_fd();
+ test_write_readonly_fd();
+ test_close_fd_9999();
+
+ /* Pipe and lifecycle tests (21-27) */
+ test_read_closed_pipe();
+ test_write_closed_pipe();
+ test_open_directory_on_file();
+ test_open_nonexistent();
+ test_close_stdin();
+ test_read_after_close();
+ test_write_large_count();
+
+ /* Integration (28-29) */
+ test_normal_path();
+ test_dmesg_warnings();
+
+ if (failures)
+ fprintf(stderr, "# %d test(s) failed\n", failures);
+ else
+ fprintf(stderr, "# All tests passed\n");
+
+ return failures ? 1 : 0;
+}
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH 3/9] kernel/api: add debugfs interface for kernel API specifications
2026-03-13 15:09 ` [PATCH 3/9] kernel/api: add debugfs interface for kernel " Sasha Levin
@ 2026-03-13 15:32 ` Greg Kroah-Hartman
2026-03-13 16:27 ` Sasha Levin
0 siblings, 1 reply; 37+ messages in thread
From: Greg Kroah-Hartman @ 2026-03-13 15:32 UTC (permalink / raw)
To: Sasha Levin
Cc: linux-api, linux-kernel, linux-doc, linux-fsdevel, linux-kbuild,
linux-kselftest, workflows, tools, x86, Thomas Gleixner,
Paul E. McKenney, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann
On Fri, Mar 13, 2026 at 11:09:13AM -0400, Sasha Levin wrote:
> Add a debugfs interface to expose kernel API specifications at runtime.
> This allows tools and users to query the complete API specifications
> through the debugfs filesystem.
>
> The interface provides:
> - /sys/kernel/debug/kapi/list - lists all available API specifications
> - /sys/kernel/debug/kapi/specs/<name> - detailed info for each API
>
> Each specification file includes:
> - Function name, version, and descriptions
> - Execution context requirements and flags
> - Parameter details with types, flags, and constraints
> - Return value specifications and success conditions
> - Error codes with descriptions and conditions
> - Locking requirements and constraints
> - Signal handling specifications
> - Examples, notes, and deprecation status
>
> This enables runtime introspection of kernel APIs for documentation
> tools, static analyzers, and debugging purposes.
>
> Signed-off-by: Sasha Levin <sashal@kernel.org>
> ---
> Documentation/dev-tools/kernel-api-spec.rst | 88 ++--
You are removing stuff from the file you created earlier in this patch
series, is that ok?
> --- /dev/null
> +++ b/kernel/api/kapi_debugfs.c
> @@ -0,0 +1,503 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Kernel API specification debugfs interface
> + *
> + * This provides a debugfs interface to expose kernel API specifications
> + * at runtime, allowing tools and users to query the complete API specs.
> + */
No copyright line? :)
And this is, a totally and crazy interface with debugfs, I love it!
Two minor minor nits:
> +static int __init kapi_debugfs_init(void)
> +{
> + struct kernel_api_spec *spec;
> + struct dentry *spec_dir;
> +
> + /* Create main directory */
> + kapi_debugfs_root = debugfs_create_dir("kapi", NULL);
> +
> + /* Create list file */
> + debugfs_create_file("list", 0444, kapi_debugfs_root, NULL, &kapi_list_fops);
> +
> + /* Create specs subdirectory */
> + spec_dir = debugfs_create_dir("specs", kapi_debugfs_root);
> +
> + /* Create a file for each API spec */
> + for (spec = __start_kapi_specs; spec < __stop_kapi_specs; spec++) {
> + debugfs_create_file(spec->name, 0444, spec_dir, spec, &kapi_spec_fops);
> + }
No need for { }
> +
> + pr_info("Kernel API debugfs interface initialized\n");
When code is working properly, it should be quiet, no need for this as
initializing this can not fail.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 5/9] kernel/api: add API specification for sys_open
2026-03-13 15:09 ` [PATCH 5/9] kernel/api: add API specification for sys_open Sasha Levin
@ 2026-03-13 15:33 ` Greg Kroah-Hartman
2026-03-13 16:42 ` Sasha Levin
0 siblings, 1 reply; 37+ messages in thread
From: Greg Kroah-Hartman @ 2026-03-13 15:33 UTC (permalink / raw)
To: Sasha Levin
Cc: linux-api, linux-kernel, linux-doc, linux-fsdevel, linux-kbuild,
linux-kselftest, workflows, tools, x86, Thomas Gleixner,
Paul E. McKenney, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann
On Fri, Mar 13, 2026 at 11:09:15AM -0400, Sasha Levin wrote:
> Signed-off-by: Sasha Levin <sashal@kernel.org>
No changelog?
> + * since-version: 1.0
I think since older versions :)
Anyway, very nice documentation, will be good to have this as part of
the kerneldocs no matter what the result of this patch series is.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 6/9] kernel/api: add API specification for sys_close
2026-03-13 15:09 ` [PATCH 6/9] kernel/api: add API specification for sys_close Sasha Levin
@ 2026-03-13 15:49 ` Greg Kroah-Hartman
2026-03-13 16:46 ` Sasha Levin
2026-03-13 15:52 ` Greg Kroah-Hartman
1 sibling, 1 reply; 37+ messages in thread
From: Greg Kroah-Hartman @ 2026-03-13 15:49 UTC (permalink / raw)
To: Sasha Levin
Cc: linux-api, linux-kernel, linux-doc, linux-fsdevel, linux-kbuild,
linux-kselftest, workflows, tools, x86, Thomas Gleixner,
Paul E. McKenney, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann
On Fri, Mar 13, 2026 at 11:09:16AM -0400, Sasha Levin wrote:
> + * Calling close() on a file descriptor while another thread is using it
> + * (e.g., in a blocking read() or write()) has implementation-defined
> + * behavior. On Linux, the blocked operation continues on the underlying
> + * file and may complete even after close() returns.
I'm guessing this came from the man pages? This is Linux, so we are the
"implementation" here :)
thanks,
greg k-h
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 6/9] kernel/api: add API specification for sys_close
2026-03-13 15:09 ` [PATCH 6/9] kernel/api: add API specification for sys_close Sasha Levin
2026-03-13 15:49 ` Greg Kroah-Hartman
@ 2026-03-13 15:52 ` Greg Kroah-Hartman
2026-03-13 16:55 ` Sasha Levin
1 sibling, 1 reply; 37+ messages in thread
From: Greg Kroah-Hartman @ 2026-03-13 15:52 UTC (permalink / raw)
To: Sasha Levin
Cc: linux-api, linux-kernel, linux-doc, linux-fsdevel, linux-kbuild,
linux-kselftest, workflows, tools, x86, Thomas Gleixner,
Paul E. McKenney, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann
On Fri, Mar 13, 2026 at 11:09:16AM -0400, Sasha Levin wrote:
> + * notes: This syscall has subtle non-POSIX semantics: the fd is ALWAYS closed
> + * regardless of the return value. POSIX specifies that on EINTR, the state
> + * of the fd is unspecified, but Linux always closes it. HP-UX requires
> + * retrying close() on EINTR, but doing so on Linux may close an unrelated
> + * fd that was reassigned by another thread. For portable code, the safest
> + * approach is to check for errors but never retry close().
We don't care about HP-UX :)
> + * Error codes from the flush callback (EIO, ENOSPC, EDQUOT) indicate that
> + * previously written data may have been lost. These errors are particularly
> + * common on NFS where write errors are often deferred to close time.
What flush callback?
> + *
> + * The driver's release() callback errors are explicitly ignored by the
> + * kernel, so device driver cleanup errors are not propagated to userspace.
What "The driver" here? release() callbacks aren't really relevant
here.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 3/9] kernel/api: add debugfs interface for kernel API specifications
2026-03-13 15:32 ` Greg Kroah-Hartman
@ 2026-03-13 16:27 ` Sasha Levin
0 siblings, 0 replies; 37+ messages in thread
From: Sasha Levin @ 2026-03-13 16:27 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: linux-api, linux-kernel, linux-doc, linux-fsdevel, linux-kbuild,
linux-kselftest, workflows, tools, x86, Thomas Gleixner,
Paul E. McKenney, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann
On Fri, Mar 13, 2026 at 04:32:23PM +0100, Greg Kroah-Hartman wrote:
>On Fri, Mar 13, 2026 at 11:09:13AM -0400, Sasha Levin wrote:
>> Add a debugfs interface to expose kernel API specifications at runtime.
>> This allows tools and users to query the complete API specifications
>> through the debugfs filesystem.
>>
>> The interface provides:
>> - /sys/kernel/debug/kapi/list - lists all available API specifications
>> - /sys/kernel/debug/kapi/specs/<name> - detailed info for each API
>>
>> Each specification file includes:
>> - Function name, version, and descriptions
>> - Execution context requirements and flags
>> - Parameter details with types, flags, and constraints
>> - Return value specifications and success conditions
>> - Error codes with descriptions and conditions
>> - Locking requirements and constraints
>> - Signal handling specifications
>> - Examples, notes, and deprecation status
>>
>> This enables runtime introspection of kernel APIs for documentation
>> tools, static analyzers, and debugging purposes.
>>
>> Signed-off-by: Sasha Levin <sashal@kernel.org>
>> ---
>> Documentation/dev-tools/kernel-api-spec.rst | 88 ++--
>
>You are removing stuff from the file you created earlier in this patch
>series, is that ok?
Sorry, just a rebasing artifact from shuffling patches around. I'll fix it.
>> --- /dev/null
>> +++ b/kernel/api/kapi_debugfs.c
>> @@ -0,0 +1,503 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Kernel API specification debugfs interface
>> + *
>> + * This provides a debugfs interface to expose kernel API specifications
>> + * at runtime, allowing tools and users to query the complete API specs.
>> + */
>
>No copyright line? :)
I'll add one.
>And this is, a totally and crazy interface with debugfs, I love it!
Thanks :)
>Two minor minor nits:
>
>> +static int __init kapi_debugfs_init(void)
>> +{
>> + struct kernel_api_spec *spec;
>> + struct dentry *spec_dir;
>> +
>> + /* Create main directory */
>> + kapi_debugfs_root = debugfs_create_dir("kapi", NULL);
>> +
>> + /* Create list file */
>> + debugfs_create_file("list", 0444, kapi_debugfs_root, NULL, &kapi_list_fops);
>> +
>> + /* Create specs subdirectory */
>> + spec_dir = debugfs_create_dir("specs", kapi_debugfs_root);
>> +
>> + /* Create a file for each API spec */
>> + for (spec = __start_kapi_specs; spec < __stop_kapi_specs; spec++) {
>> + debugfs_create_file(spec->name, 0444, spec_dir, spec, &kapi_spec_fops);
>> + }
>
>No need for { }
ack
>> +
>> + pr_info("Kernel API debugfs interface initialized\n");
>
>When code is working properly, it should be quiet, no need for this as
>initializing this can not fail.
ack
--
Thanks,
Sasha
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 5/9] kernel/api: add API specification for sys_open
2026-03-13 15:33 ` Greg Kroah-Hartman
@ 2026-03-13 16:42 ` Sasha Levin
2026-03-17 18:37 ` Jonathan Corbet
0 siblings, 1 reply; 37+ messages in thread
From: Sasha Levin @ 2026-03-13 16:42 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: linux-api, linux-kernel, linux-doc, linux-fsdevel, linux-kbuild,
linux-kselftest, workflows, tools, x86, Thomas Gleixner,
Paul E. McKenney, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann
On Fri, Mar 13, 2026 at 04:33:57PM +0100, Greg Kroah-Hartman wrote:
>On Fri, Mar 13, 2026 at 11:09:15AM -0400, Sasha Levin wrote:
>> Signed-off-by: Sasha Levin <sashal@kernel.org>
>
>No changelog?
I'll add something to all patches.
>> + * since-version: 1.0
>
>I think since older versions :)
Right. I guess that in my mind 1.0 was the first official "release". I'll
update it to 0.01.
>Anyway, very nice documentation, will be good to have this as part of
>the kerneldocs no matter what the result of this patch series is.
Thanks!
--
Thanks,
Sasha
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 6/9] kernel/api: add API specification for sys_close
2026-03-13 15:49 ` Greg Kroah-Hartman
@ 2026-03-13 16:46 ` Sasha Levin
0 siblings, 0 replies; 37+ messages in thread
From: Sasha Levin @ 2026-03-13 16:46 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: linux-api, linux-kernel, linux-doc, linux-fsdevel, linux-kbuild,
linux-kselftest, workflows, tools, x86, Thomas Gleixner,
Paul E. McKenney, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann
On Fri, Mar 13, 2026 at 04:49:28PM +0100, Greg Kroah-Hartman wrote:
>On Fri, Mar 13, 2026 at 11:09:16AM -0400, Sasha Levin wrote:
>> + * Calling close() on a file descriptor while another thread is using it
>> + * (e.g., in a blocking read() or write()) has implementation-defined
>> + * behavior. On Linux, the blocked operation continues on the underlying
>> + * file and may complete even after close() returns.
>
>I'm guessing this came from the man pages? This is Linux, so we are the
>"implementation" here :)
Right :)
I was just trying to make it a comparison to posix. I'll clarify the docs here.
--
Thanks,
Sasha
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 6/9] kernel/api: add API specification for sys_close
2026-03-13 15:52 ` Greg Kroah-Hartman
@ 2026-03-13 16:55 ` Sasha Levin
0 siblings, 0 replies; 37+ messages in thread
From: Sasha Levin @ 2026-03-13 16:55 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: linux-api, linux-kernel, linux-doc, linux-fsdevel, linux-kbuild,
linux-kselftest, workflows, tools, x86, Thomas Gleixner,
Paul E. McKenney, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann
On Fri, Mar 13, 2026 at 04:52:30PM +0100, Greg Kroah-Hartman wrote:
>On Fri, Mar 13, 2026 at 11:09:16AM -0400, Sasha Levin wrote:
>> + * notes: This syscall has subtle non-POSIX semantics: the fd is ALWAYS closed
>> + * regardless of the return value. POSIX specifies that on EINTR, the state
>> + * of the fd is unspecified, but Linux always closes it. HP-UX requires
>> + * retrying close() on EINTR, but doing so on Linux may close an unrelated
>> + * fd that was reassigned by another thread. For portable code, the safest
>> + * approach is to check for errors but never retry close().
>
>We don't care about HP-UX :)
Fair :) The original text was contrasting Linux's "always closed" behavior
with HP-UX. I'll just drop that reference.
>> + * Error codes from the flush callback (EIO, ENOSPC, EDQUOT) indicate that
>> + * previously written data may have been lost. These errors are particularly
>> + * common on NFS where write errors are often deferred to close time.
>
>What flush callback?
This was referring to f_op->flush, which filp_flush() calls during close.
But you're right, that's internal plumbing. I'll reworded to describe the
behavior without referencing internal callbacks:
Error codes like EIO, ENOSPC, and EDQUOT indicate that previously
buffered writes may have failed to reach storage.
>> + *
>> + * The driver's release() callback errors are explicitly ignored by the
>> + * kernel, so device driver cleanup errors are not propagated to userspace.
>
>What "The driver" here? release() callbacks aren't really relevant
>here.
The original text was noting that __fput() ignores the return value of
f_op->release(), so even if a driver's cleanup fails, userspace never
sees it via close(). But agreed, that's an internal implementation detail
not relevant to the syscall specification. Removed.
Thanks for the review!
--
Thanks,
Sasha
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 0/9] Kernel API Specification Framework
2026-03-13 15:09 [PATCH 0/9] Kernel API Specification Framework Sasha Levin
` (8 preceding siblings ...)
2026-03-13 15:09 ` [PATCH 9/9] kernel/api: add runtime verification selftest Sasha Levin
@ 2026-03-14 18:18 ` Jakub Kicinski
2026-03-14 22:44 ` David Laight
` (2 more replies)
9 siblings, 3 replies; 37+ messages in thread
From: Jakub Kicinski @ 2026-03-14 18:18 UTC (permalink / raw)
To: Sasha Levin
Cc: linux-api, linux-kernel, linux-doc, linux-fsdevel, linux-kbuild,
linux-kselftest, workflows, tools, x86, Thomas Gleixner,
Paul E. McKenney, Greg Kroah-Hartman, Jonathan Corbet,
Dmitry Vyukov, Randy Dunlap, Cyril Hrubis, Kees Cook, Jake Edge,
David Laight, Askar Safin, Gabriele Paoloni,
Mauro Carvalho Chehab, Christian Brauner, Alexander Viro,
Andrew Morton, Masahiro Yamada, Shuah Khan, Ingo Molnar,
Arnd Bergmann
On Fri, 13 Mar 2026 11:09:10 -0400 Sasha Levin wrote:
> This enables static analysis tools to verify userspace API usage at compile
> time, test generation based on formal specifications, consistent error handling
> validation, automated documentation generation, and formal verification of
> kernel interfaces.
Could you give some examples? We have machine readable descriptions for
Netlink interfaces, we approached syzbot folks and they did not really
seem to care for those.
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 0/9] Kernel API Specification Framework
2026-03-14 18:18 ` [PATCH 0/9] Kernel API Specification Framework Jakub Kicinski
@ 2026-03-14 22:44 ` David Laight
2026-03-15 6:46 ` Sasha Levin
2026-03-15 6:36 ` Sasha Levin
2026-03-16 7:05 ` Dmitry Vyukov
2 siblings, 1 reply; 37+ messages in thread
From: David Laight @ 2026-03-14 22:44 UTC (permalink / raw)
To: Jakub Kicinski
Cc: Sasha Levin, linux-api, linux-kernel, linux-doc, linux-fsdevel,
linux-kbuild, linux-kselftest, workflows, tools, x86,
Thomas Gleixner, Paul E. McKenney, Greg Kroah-Hartman,
Jonathan Corbet, Dmitry Vyukov, Randy Dunlap, Cyril Hrubis,
Kees Cook, Jake Edge, Askar Safin, Gabriele Paoloni,
Mauro Carvalho Chehab, Christian Brauner, Alexander Viro,
Andrew Morton, Masahiro Yamada, Shuah Khan, Ingo Molnar,
Arnd Bergmann
On Sat, 14 Mar 2026 11:18:22 -0700
Jakub Kicinski <kuba@kernel.org> wrote:
> On Fri, 13 Mar 2026 11:09:10 -0400 Sasha Levin wrote:
> > This enables static analysis tools to verify userspace API usage at compile
> > time, test generation based on formal specifications, consistent error handling
> > validation, automated documentation generation, and formal verification of
> > kernel interfaces.
>
> Could you give some examples? We have machine readable descriptions for
> Netlink interfaces, we approached syzbot folks and they did not really
> seem to care for those.
The whole thing reminds me of doxygen comment blocks.
They tend to make it hard to read the source files, hard to search
the source files (due to all the extra matches) and are pretty much
always out of date.
The kerndoc comment blocks for trivial helper functions are hard enough
to keep up to date.
The only way even parameter descriptions are going to stay correct is if the
compiler is using the definition and only the comment part is extra.
For error returns you'll need the documentation to be at the return site.
David
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 0/9] Kernel API Specification Framework
2026-03-14 18:18 ` [PATCH 0/9] Kernel API Specification Framework Jakub Kicinski
2026-03-14 22:44 ` David Laight
@ 2026-03-15 6:36 ` Sasha Levin
2026-03-18 6:24 ` Mauro Carvalho Chehab
2026-03-16 7:05 ` Dmitry Vyukov
2 siblings, 1 reply; 37+ messages in thread
From: Sasha Levin @ 2026-03-15 6:36 UTC (permalink / raw)
To: Jakub Kicinski
Cc: linux-api, linux-kernel, linux-doc, linux-fsdevel, linux-kbuild,
linux-kselftest, workflows, tools, x86, Thomas Gleixner,
Paul E. McKenney, Greg Kroah-Hartman, Jonathan Corbet,
Dmitry Vyukov, Randy Dunlap, Cyril Hrubis, Kees Cook, Jake Edge,
David Laight, Askar Safin, Gabriele Paoloni,
Mauro Carvalho Chehab, Christian Brauner, Alexander Viro,
Andrew Morton, Masahiro Yamada, Shuah Khan, Ingo Molnar,
Arnd Bergmann
On Sat, Mar 14, 2026 at 11:18:22AM -0700, Jakub Kicinski wrote:
>On Fri, 13 Mar 2026 11:09:10 -0400 Sasha Levin wrote:
>> This enables static analysis tools to verify userspace API usage at compile
>> time, test generation based on formal specifications, consistent error handling
>> validation, automated documentation generation, and formal verification of
>> kernel interfaces.
>
>Could you give some examples? We have machine readable descriptions for
>Netlink interfaces, we approached syzbot folks and they did not really
>seem to care for those.
Once the API is in a machine-readable format, we can write formatters to
output whatever downstream tools need. The kapi tool in the series
already ships with plain text, JSON, and RST formatters, and adding new
output formats is straightforward. We don't need to convince the
syzkaller folks to consume our specs, we can just output them in a
format that syzkaller already understands.
For example, I have a syzlang formatter that produces the following
from the sys_read spec in this series:
# --- read ---
# Read data from a file descriptor
#
# @context process, sleepable
#
# @capability CAP_DAC_OVERRIDE: Bypass discretionary access control on read permission
# @capability CAP_DAC_READ_SEARCH: Bypass read permission checks on regular files
#
# @error EPERM (-1): Returned by fanotify permission events...
# @error EINTR (-4): The call was interrupted by a signal before any data was read.
# @error EIO (-5): A low-level I/O error occurred.
# @error EBADF (-9): fd is not a valid file descriptor, or fd was not opened for reading.
# @error EAGAIN (-11): O_NONBLOCK set and read would block.
# @error EACCES (-13): LSM denied the read operation via security_file_permission().
# @error EFAULT (-14): buf points outside the accessible address space.
# @error EISDIR (-21): fd refers to a directory.
# @error EINVAL (-22): fd not suitable for reading, O_DIRECT misaligned, count negative...
# @error ENODATA (-61): Data not available in cache...
# @error EOVERFLOW (-75): File position plus count would exceed LLONG_MAX.
# @error EOPNOTSUPP (-95): Read not supported for this file type...
# @error ENOBUFS (-105): Buffer too small for complete notification...
#
# @constraint MAX_RW_COUNT: actual_count = min(count, MAX_RW_COUNT)
# @constraint File must be open for reading: (file->f_mode & FMODE_READ) && (file->f_mode & FMODE_CAN_READ)
#
# @signal Any signal: restartable, error=-4
#
# @lock file->f_pos_lock (mutex, internal): For seekable files with FMODE_ATOMIC_POS
# @lock Filesystem-specific locks (rwlock, internal)
# @lock RCU read-side (rwlock, internal): File descriptor lookup protection
#
# @side-effect file->f_pos: f_pos advanced by bytes read [conditional, irreversible]
# @side-effect inode access time: atime updated unless O_NOATIME [conditional, irreversible]
# @side-effect task I/O accounting: rchar and syscr updated [conditional, irreversible]
# @side-effect fsnotify events: FS_ACCESS event generated [conditional, irreversible]
#
# @return ssize_t: bytes read on success, negative errno on error
#
read(fd fd, buf ptr[out, buffer[out]], count len[buf]) intptr
That said, I don't have a strong end-to-end example with the 4 syscalls
proposed here, as open/close/read/write don't take complex structures,
and the subsystems underneath them aren't specced yet. The value becomes
clearer with richer interfaces where the gap between "we know the type
signature" and "we know the full behavioral contract" is much wider.
--
Thanks,
Sasha
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 0/9] Kernel API Specification Framework
2026-03-14 22:44 ` David Laight
@ 2026-03-15 6:46 ` Sasha Levin
0 siblings, 0 replies; 37+ messages in thread
From: Sasha Levin @ 2026-03-15 6:46 UTC (permalink / raw)
To: David Laight
Cc: Jakub Kicinski, linux-api, linux-kernel, linux-doc, linux-fsdevel,
linux-kbuild, linux-kselftest, workflows, tools, x86,
Thomas Gleixner, Paul E. McKenney, Greg Kroah-Hartman,
Jonathan Corbet, Dmitry Vyukov, Randy Dunlap, Cyril Hrubis,
Kees Cook, Jake Edge, Askar Safin, Gabriele Paoloni,
Mauro Carvalho Chehab, Christian Brauner, Alexander Viro,
Andrew Morton, Masahiro Yamada, Shuah Khan, Ingo Molnar,
Arnd Bergmann
On Sat, Mar 14, 2026 at 10:44:35PM +0000, David Laight wrote:
>On Sat, 14 Mar 2026 11:18:22 -0700
>Jakub Kicinski <kuba@kernel.org> wrote:
>
>> On Fri, 13 Mar 2026 11:09:10 -0400 Sasha Levin wrote:
>> > This enables static analysis tools to verify userspace API usage at compile
>> > time, test generation based on formal specifications, consistent error handling
>> > validation, automated documentation generation, and formal verification of
>> > kernel interfaces.
>>
>> Could you give some examples? We have machine readable descriptions for
>> Netlink interfaces, we approached syzbot folks and they did not really
>> seem to care for those.
>
>The whole thing reminds me of doxygen comment blocks.
>They tend to make it hard to read the source files, hard to search
>the source files (due to all the extra matches) and are pretty much
>always out of date.
>
>The kerndoc comment blocks for trivial helper functions are hard enough
>to keep up to date.
>
>The only way even parameter descriptions are going to stay correct is if the
>compiler is using the definition and only the comment part is extra.
>For error returns you'll need the documentation to be at the return site.
When CONFIG_KAPI_RUNTIME_CHECKS is enabled, the specs are enforced at
the syscall boundary. The SYSCALL_DEFINEx macro grows a wrapper that
calls kapi_validate_syscall_params() before the real implementation and
kapi_validate_syscall_return() after it. Parameter constraints (ranges,
valid flag masks, alignment) are checked on every syscall entry, and
return values are validated against the documented success/error ranges
on every exit.
If a spec goes stale, it has runtime consequences. A new flag bit added
without updating the spec's valid_mask means callers using that flag get
EINVAL, which any test exercising that path catches immediately. An
implementation returning an undocumented error code triggers a warning
from the return validation.
The selftest in the series (tools/testing/selftests/kapi/test_kapi.c)
exercises this with real syscalls, both valid and invalid inputs,
verifying the validation layer catches violations.
--
Thanks,
Sasha
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 0/9] Kernel API Specification Framework
2026-03-14 18:18 ` [PATCH 0/9] Kernel API Specification Framework Jakub Kicinski
2026-03-14 22:44 ` David Laight
2026-03-15 6:36 ` Sasha Levin
@ 2026-03-16 7:05 ` Dmitry Vyukov
2026-03-16 22:57 ` Jakub Kicinski
2 siblings, 1 reply; 37+ messages in thread
From: Dmitry Vyukov @ 2026-03-16 7:05 UTC (permalink / raw)
To: Jakub Kicinski, syzkaller
Cc: Sasha Levin, linux-api, linux-kernel, linux-doc, linux-fsdevel,
linux-kbuild, linux-kselftest, workflows, tools, x86,
Thomas Gleixner, Paul E. McKenney, Greg Kroah-Hartman,
Jonathan Corbet, Randy Dunlap, Cyril Hrubis, Kees Cook, Jake Edge,
David Laight, Askar Safin, Gabriele Paoloni,
Mauro Carvalho Chehab, Christian Brauner, Alexander Viro,
Andrew Morton, Masahiro Yamada, Shuah Khan, Ingo Molnar,
Arnd Bergmann
On Sat, 14 Mar 2026 at 19:18, Jakub Kicinski <kuba@kernel.org> wrote:
>
> On Fri, 13 Mar 2026 11:09:10 -0400 Sasha Levin wrote:
> > This enables static analysis tools to verify userspace API usage at compile
> > time, test generation based on formal specifications, consistent error handling
> > validation, automated documentation generation, and formal verification of
> > kernel interfaces.
>
> Could you give some examples? We have machine readable descriptions for
> Netlink interfaces, we approached syzbot folks and they did not really
> seem to care for those.
I think our reasoning wrt syzkaller was that not all interfaces in all
relevant kernels are described with netlink yml descriptions, so we
need to continue using the extraction of interfaces from the source
code. And if we have that code, then using yml as an additional data
source only adds code/complexity. Additionally, we may extract some
extra constraints/info from code that are not present in yml.
Realistically system call descriptions may have the same problem for
us at this point, since we extract lots of info from the source code
already:
https://raw.githubusercontent.com/google/syzkaller/refs/heads/master/sys/linux/auto.txt
(and LLMs obviously can allow us to extract more)
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 0/9] Kernel API Specification Framework
2026-03-16 7:05 ` Dmitry Vyukov
@ 2026-03-16 22:57 ` Jakub Kicinski
2026-03-16 23:29 ` Sasha Levin
0 siblings, 1 reply; 37+ messages in thread
From: Jakub Kicinski @ 2026-03-16 22:57 UTC (permalink / raw)
To: Dmitry Vyukov
Cc: syzkaller, Sasha Levin, linux-api, linux-kernel, linux-doc,
linux-fsdevel, linux-kbuild, linux-kselftest, workflows, tools,
x86, Thomas Gleixner, Paul E. McKenney, Greg Kroah-Hartman,
Jonathan Corbet, Randy Dunlap, Cyril Hrubis, Kees Cook, Jake Edge,
David Laight, Askar Safin, Gabriele Paoloni,
Mauro Carvalho Chehab, Christian Brauner, Alexander Viro,
Andrew Morton, Masahiro Yamada, Shuah Khan, Ingo Molnar,
Arnd Bergmann
On Mon, 16 Mar 2026 08:05:53 +0100 Dmitry Vyukov wrote:
> On Sat, 14 Mar 2026 at 19:18, Jakub Kicinski <kuba@kernel.org> wrote:
> > On Fri, 13 Mar 2026 11:09:10 -0400 Sasha Levin wrote:
> > > This enables static analysis tools to verify userspace API usage at compile
> > > time, test generation based on formal specifications, consistent error handling
> > > validation, automated documentation generation, and formal verification of
> > > kernel interfaces.
> >
> > Could you give some examples? We have machine readable descriptions for
> > Netlink interfaces, we approached syzbot folks and they did not really
> > seem to care for those.
>
> I think our reasoning wrt syzkaller was that not all interfaces in all
> relevant kernels are described with netlink yml descriptions, so we
> need to continue using the extraction of interfaces from the source
> code. And if we have that code, then using yml as an additional data
> source only adds code/complexity. Additionally, we may extract some
> extra constraints/info from code that are not present in yml.
>
> Realistically system call descriptions may have the same problem for
> us at this point, since we extract lots of info from the source code
> already:
> https://raw.githubusercontent.com/google/syzkaller/refs/heads/master/sys/linux/auto.txt
yup! we haven't tried to make the yml spec super useful to syzbot
to be fair. I'm just flagging that example because in our case we
quickly went from "this will obviously be useful to syzbot" to
"although we could, it may not be super practical"
> (and LLMs obviously can allow us to extract more)
Didn't even think of that. LLMs should make short work of this sort of
extraction of information from source code..
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 0/9] Kernel API Specification Framework
2026-03-16 22:57 ` Jakub Kicinski
@ 2026-03-16 23:29 ` Sasha Levin
0 siblings, 0 replies; 37+ messages in thread
From: Sasha Levin @ 2026-03-16 23:29 UTC (permalink / raw)
To: Jakub Kicinski
Cc: Dmitry Vyukov, syzkaller, linux-api, linux-kernel, linux-doc,
linux-fsdevel, linux-kbuild, linux-kselftest, workflows, tools,
x86, Thomas Gleixner, Paul E. McKenney, Greg Kroah-Hartman,
Jonathan Corbet, Randy Dunlap, Cyril Hrubis, Kees Cook, Jake Edge,
David Laight, Askar Safin, Gabriele Paoloni,
Mauro Carvalho Chehab, Christian Brauner, Alexander Viro,
Andrew Morton, Masahiro Yamada, Shuah Khan, Ingo Molnar,
Arnd Bergmann
On Mon, Mar 16, 2026 at 03:57:56PM -0700, Jakub Kicinski wrote:
>Didn't even think of that. LLMs should make short work of this sort of
>extraction of information from source code..
This is the primary reason that this proposal resurfaced :)
I've originally proposed[1] something like this almost a decade ago, but when I
started trying to write the actual specs I hit a brick wall: it was simply not
tractable.
With LLMs, writing the specs is something we can actually pull off, and we can
verify their correctness so LLMs don't get to hallucinate.
The specs you see in the following patches are all LLM generated.
--
Thanks,
Sasha
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 1/9] kernel/api: introduce kernel API specification framework
2026-03-13 15:09 ` [PATCH 1/9] kernel/api: introduce kernel API specification framework Sasha Levin
@ 2026-03-17 17:49 ` Jonathan Corbet
2026-03-18 6:00 ` Mauro Carvalho Chehab
` (2 more replies)
0 siblings, 3 replies; 37+ messages in thread
From: Jonathan Corbet @ 2026-03-17 17:49 UTC (permalink / raw)
To: Sasha Levin, linux-api, linux-kernel
Cc: linux-doc, linux-fsdevel, linux-kbuild, linux-kselftest,
workflows, tools, x86, Thomas Gleixner, Paul E. McKenney,
Greg Kroah-Hartman, Dmitry Vyukov, Randy Dunlap, Cyril Hrubis,
Kees Cook, Jake Edge, David Laight, Askar Safin, Gabriele Paoloni,
Mauro Carvalho Chehab, Christian Brauner, Alexander Viro,
Andrew Morton, Masahiro Yamada, Shuah Khan, Ingo Molnar,
Arnd Bergmann, Sasha Levin
Sasha Levin <sashal@kernel.org> writes:
> Add a framework for formally documenting kernel APIs with inline
> specifications. This framework provides:
>
> - Structured API documentation with parameter specifications, return
> values, error conditions, and execution context requirements
> - Runtime validation capabilities for debugging (CONFIG_KAPI_RUNTIME_CHECKS)
> - Export of specifications via debugfs for tooling integration
> - Support for both internal kernel APIs and system calls
So I'll confess I have only scanned over the implementation, but I have
some thoughts on the earlier stuff.
[...]
> diff --git a/Documentation/dev-tools/kernel-api-spec.rst b/Documentation/dev-tools/kernel-api-spec.rst
> new file mode 100644
> index 0000000000000..7c0c1694f1f4a
> --- /dev/null
> +++ b/Documentation/dev-tools/kernel-api-spec.rst
> @@ -0,0 +1,482 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +======================================
> +Kernel API Specification Framework
> +======================================
> +
> +:Author: Sasha Levin <sashal@kernel.org>
> +:Date: June 2025
Has it not changed since then?
> +.. contents:: Table of Contents
> + :depth: 3
> + :local:
> +
> +Introduction
> +============
[...]
> +Usage Guide
> +===========
> +
> +Basic API Specification
> +-----------------------
> +
> +API specifications are written as KAPI-annotated kerneldoc comments directly in
> +the source file, immediately preceding the function implementation. The ``kapi``
> +tool extracts these annotations to produce structured specifications.
> +
> +.. code-block:: c
> +
> + /**
> + * kmalloc - allocate kernel memory
> + * @size: Number of bytes to allocate
> + * @flags: Allocation flags (GFP_*)
Given that the text thus far has talked about user-space API validation,
it's a bit surprising to see an internal function used as an example.
Also, maybe it should be kmalloc_obj()? <runs away>
> + * context-flags: KAPI_CTX_PROCESS | KAPI_CTX_SOFTIRQ | KAPI_CTX_HARDIRQ
> + * param-count: 2
param-count is two, but you only document one of them?
> + * param: size
> + * type: KAPI_TYPE_UINT
> + * flags: KAPI_PARAM_IN
> + * constraint-type: KAPI_CONSTRAINT_RANGE
> + * range: 0, KMALLOC_MAX_SIZE
> + *
> + * error: ENOMEM, Out of memory
> + * desc: Insufficient memory available for the requested allocation.
> + */
Honest question: can this be made a bit easier for people to create,
with less shift-key wear? My biggest worry with a system like this is
that people won't take the time to create and maintain the entries, so
anything that would ease the task would help. Is there an impediment to
something like:
contexts: process, softirq, hardirq
param: size
type: uint, input
constraint: range(0, KMALLOC_MAX_SIZE)
See what I'm getting at? ISTM that your DSL could be made a bit less
verbose and shouty while being just as well defined, but perhaps I'm
missing something?
Even better, of course, would be to add a "description" field for each
parameter, and allow that rather than the @param description that
kerneldoc currently uses. That would keep all the information together,
at the minor cost of adding another significant complication to the
kernel-doc script. Mauro won't mind :)
> + void *kmalloc(size_t size, gfp_t flags)
> + {
> + /* Implementation */
> + }
> +
> +Alternatively, specifications can be defined using the ``DEFINE_KERNEL_API_SPEC``
> +macro for compiled-in specs that are stored in the ``.kapi_specs`` ELF section:
> +
> +.. code-block:: c
> +
> + #include <linux/kernel_api_spec.h>
> +
> + DEFINE_KERNEL_API_SPEC(sys_open)
> + KAPI_DESCRIPTION("Open or create a file")
> + KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
> + /* ... parameter, error, constraint definitions ... */
> + KAPI_END_SPEC
So the reason for two completely separate mechanisms is not entirely
clear to me. The kerneldoc variant is essentially documentation, while
the macro stuff is to be built into the executable? What if you want
both?
It would be nice to only have one way if at all possible; I'm sure that
crossed your mind at some point :) If there have to be two, having both
examples describe the same function would make the parallels more clear.
> +System Call Specification
> +-------------------------
> +
> +System calls are documented inline in the implementation file (e.g., ``fs/open.c``)
> +using KAPI-annotated kerneldoc comments. When ``CONFIG_KAPI_RUNTIME_CHECKS`` is
> +enabled, the ``SYSCALL_DEFINEx`` macros automatically look up the specification
> +and validate parameters before and after the syscall executes.
> +
> +IOCTL Specification
> +-------------------
> +
> +IOCTLs use the same annotation approach with additional structure field
> +specifications
This might be a really good place for an example
[...]
> +Usage Examples
> +--------------
> +
> +Query specific API::
> +
> + $ cat /sys/kernel/debug/kapi/apis/kmalloc/specification
> + API: kmalloc
> + Version: 3.0
> + Description: Allocate kernel memory
> +
> + Parameters:
> + [0] size (size_t, in): Number of bytes to allocate
> + Range: 0 - 4194304
> + [1] flags (flags, in): Allocation flags (GFP_*)
> + Mask: 0x1ffffff
Ah, you do document that second parameter somewhere :)
> + Returns: pointer - Pointer to allocated memory or NULL
> +
> + Errors:
> + ENOMEM: Out of memory
> +
> + Context: process, softirq, hardirq
> +
> + Side Effects:
> + - Allocates memory from kernel heap
That part wasn't in your example
> +Export all specifications::
> +
> + $ cat /sys/kernel/debug/kapi/export/all.json > kernel-apis.json
> +
> +Enable validation for specific API::
> +
> + $ echo 1 > /sys/kernel/debug/kapi/apis/kmalloc/validate
> +
> +Performance Considerations
> +==========================
> +
> +Memory Overhead
> +---------------
> +
> +Each API specification consumes approximately 400-450KB of memory due to the
> +fixed-size arrays in ``struct kernel_api_spec``. With the current 4 syscall
> +specifications, total memory usage is approximately 1.7MB. Consider:
Ouch.
> +Documentation Generation
> +------------------------
> +
> +The framework exports specifications via debugfs that can be used
> +to generate documentation. Tools for automatic documentation generation
> +from specifications are planned for future development.
Documentation always comes last :)
Interesting stuff.
jon
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 5/9] kernel/api: add API specification for sys_open
2026-03-13 16:42 ` Sasha Levin
@ 2026-03-17 18:37 ` Jonathan Corbet
2026-03-18 14:12 ` Sasha Levin
0 siblings, 1 reply; 37+ messages in thread
From: Jonathan Corbet @ 2026-03-17 18:37 UTC (permalink / raw)
To: Sasha Levin, Greg Kroah-Hartman
Cc: linux-api, linux-kernel, linux-doc, linux-fsdevel, linux-kbuild,
linux-kselftest, workflows, tools, x86, Thomas Gleixner,
Paul E. McKenney, Dmitry Vyukov, Randy Dunlap, Cyril Hrubis,
Kees Cook, Jake Edge, David Laight, Askar Safin, Gabriele Paoloni,
Mauro Carvalho Chehab, Christian Brauner, Alexander Viro,
Andrew Morton, Masahiro Yamada, Shuah Khan, Ingo Molnar,
Arnd Bergmann
Sasha Levin <sashal@kernel.org> writes:
> On Fri, Mar 13, 2026 at 04:33:57PM +0100, Greg Kroah-Hartman wrote:
>>On Fri, Mar 13, 2026 at 11:09:15AM -0400, Sasha Levin wrote:
>>> + * since-version: 1.0
>>
>>I think since older versions :)
>
> Right. I guess that in my mind 1.0 was the first official "release". I'll
> update it to 0.01.
That kind of raises the question of just what since-version means. The
version-0.01 (or 1.0) version of open() surely didn't do everything
described in this specification. So it's saying that some version of
the system call has existed since then?
Thanks,
jon
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 1/9] kernel/api: introduce kernel API specification framework
2026-03-17 17:49 ` Jonathan Corbet
@ 2026-03-18 6:00 ` Mauro Carvalho Chehab
2026-03-18 14:53 ` Sasha Levin
2026-03-18 14:30 ` Sasha Levin
2026-03-18 14:32 ` Sasha Levin
2 siblings, 1 reply; 37+ messages in thread
From: Mauro Carvalho Chehab @ 2026-03-18 6:00 UTC (permalink / raw)
To: Jonathan Corbet
Cc: Sasha Levin, linux-api, linux-kernel, linux-doc, linux-fsdevel,
linux-kbuild, linux-kselftest, workflows, tools, x86,
Thomas Gleixner, Paul E. McKenney, Greg Kroah-Hartman,
Dmitry Vyukov, Randy Dunlap, Cyril Hrubis, Kees Cook, Jake Edge,
David Laight, Askar Safin, Gabriele Paoloni,
Mauro Carvalho Chehab, Christian Brauner, Alexander Viro,
Andrew Morton, Masahiro Yamada, Shuah Khan, Ingo Molnar,
Arnd Bergmann
On Tue, 17 Mar 2026 11:49:27 -0600
Jonathan Corbet <corbet@lwn.net> wrote:
> Sasha Levin <sashal@kernel.org> writes:
>
> > Add a framework for formally documenting kernel APIs with inline
> > specifications. This framework provides:
> >
> > - Structured API documentation with parameter specifications, return
> > values, error conditions, and execution context requirements
> > - Runtime validation capabilities for debugging (CONFIG_KAPI_RUNTIME_CHECKS)
> > - Export of specifications via debugfs for tooling integration
> > - Support for both internal kernel APIs and system calls
>
> So I'll confess I have only scanned over the implementation, but I have
> some thoughts on the earlier stuff.
>
> [...]
>
> > diff --git a/Documentation/dev-tools/kernel-api-spec.rst b/Documentation/dev-tools/kernel-api-spec.rst
> > new file mode 100644
> > index 0000000000000..7c0c1694f1f4a
> > --- /dev/null
> > +++ b/Documentation/dev-tools/kernel-api-spec.rst
> > @@ -0,0 +1,482 @@
> > +.. SPDX-License-Identifier: GPL-2.0
> > +
> > +======================================
> > +Kernel API Specification Framework
> > +======================================
> > +
> > +:Author: Sasha Levin <sashal@kernel.org>
> > +:Date: June 2025
>
> Has it not changed since then?
>
> > +.. contents:: Table of Contents
> > + :depth: 3
> > + :local:
> > +
> > +Introduction
> > +============
>
> [...]
>
> > +Usage Guide
> > +===========
> > +
> > +Basic API Specification
> > +-----------------------
> > +
> > +API specifications are written as KAPI-annotated kerneldoc comments directly in
> > +the source file, immediately preceding the function implementation. The ``kapi``
> > +tool extracts these annotations to produce structured specifications.
> > +
> > +.. code-block:: c
> > +
> > + /**
> > + * kmalloc - allocate kernel memory
> > + * @size: Number of bytes to allocate
> > + * @flags: Allocation flags (GFP_*)
>
> Given that the text thus far has talked about user-space API validation,
> it's a bit surprising to see an internal function used as an example.
>
> Also, maybe it should be kmalloc_obj()? <runs away>
>
> > + * context-flags: KAPI_CTX_PROCESS | KAPI_CTX_SOFTIRQ | KAPI_CTX_HARDIRQ
> > + * param-count: 2
>
> param-count is two, but you only document one of them?
>
> > + * param: size
> > + * type: KAPI_TYPE_UINT
> > + * flags: KAPI_PARAM_IN
> > + * constraint-type: KAPI_CONSTRAINT_RANGE
> > + * range: 0, KMALLOC_MAX_SIZE
> > + *
> > + * error: ENOMEM, Out of memory
> > + * desc: Insufficient memory available for the requested allocation.
> > + */
>
> Honest question: can this be made a bit easier for people to create,
> with less shift-key wear? My biggest worry with a system like this is
> that people won't take the time to create and maintain the entries, so
> anything that would ease the task would help. Is there an impediment to
> something like:
>
> contexts: process, softirq, hardirq
>
> param: size
> type: uint, input
> constraint: range(0, KMALLOC_MAX_SIZE)
>
> See what I'm getting at? ISTM that your DSL could be made a bit less
> verbose and shouty while being just as well defined, but perhaps I'm
> missing something?
>
> Even better, of course, would be to add a "description" field for each
> parameter, and allow that rather than the @param description that
> kerneldoc currently uses. That would keep all the information together,
> at the minor cost of adding another significant complication to the
> kernel-doc script. Mauro won't mind :)
No, I won't ;-)
It sounds a lot better to use kernel-doc also for kAPI than to have an
independent tool.
It is also very confusing if we end with a kernel-doc-like format
that it is not parsed by kernel-doc.
>
> > + void *kmalloc(size_t size, gfp_t flags)
> > + {
> > + /* Implementation */
> > + }
> > +
> > +Alternatively, specifications can be defined using the ``DEFINE_KERNEL_API_SPEC``
> > +macro for compiled-in specs that are stored in the ``.kapi_specs`` ELF section:
> > +
> > +.. code-block:: c
> > +
> > + #include <linux/kernel_api_spec.h>
> > +
> > + DEFINE_KERNEL_API_SPEC(sys_open)
> > + KAPI_DESCRIPTION("Open or create a file")
> > + KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
> > + /* ... parameter, error, constraint definitions ... */
> > + KAPI_END_SPEC
>
> So the reason for two completely separate mechanisms is not entirely
> clear to me. The kerneldoc variant is essentially documentation, while
> the macro stuff is to be built into the executable? What if you want
> both?
You can easily add support at kernel-doc to output such macros.
All you need is to create a new class derived from OutputFormat and
make it produce any different output format, including:
#include <linux/kernel_api_spec.h>
DEFINE_KERNEL_API_SPEC(sys_open)
KAPI_DESCRIPTION("Open or create a file")
KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
/* ... parameter, error, constraint definitions ... */
KAPI_END_SPEC
I'd say that converting from such output to `.kapi_specs`` ELF section
itself and/or to sysfs/debugfs - e.g. something that would require to
compile or be linked with Kernel's compiled binaries should be done by a
separate tool, but we should aim to have a singe tool to process
kernel documentation markups.
It is hard enough to maintain just one tool - and to have people actually
writing documentation. Having a second one to handle it, with a different
format will likely increase a lot the documentation burden.
> It would be nice to only have one way if at all possible; I'm sure that
> crossed your mind at some point :) If there have to be two, having both
> examples describe the same function would make the parallels more clear.
>
> > +System Call Specification
> > +-------------------------
> > +
> > +System calls are documented inline in the implementation file (e.g., ``fs/open.c``)
> > +using KAPI-annotated kerneldoc comments. When ``CONFIG_KAPI_RUNTIME_CHECKS`` is
> > +enabled, the ``SYSCALL_DEFINEx`` macros automatically look up the specification
> > +and validate parameters before and after the syscall executes.
> > +
> > +IOCTL Specification
> > +-------------------
> > +
> > +IOCTLs use the same annotation approach with additional structure field
> > +specifications
>
> This might be a really good place for an example
>
> [...]
>
> > +Usage Examples
> > +--------------
> > +
> > +Query specific API::
> > +
> > + $ cat /sys/kernel/debug/kapi/apis/kmalloc/specification
> > + API: kmalloc
> > + Version: 3.0
> > + Description: Allocate kernel memory
> > +
> > + Parameters:
> > + [0] size (size_t, in): Number of bytes to allocate
> > + Range: 0 - 4194304
> > + [1] flags (flags, in): Allocation flags (GFP_*)
> > + Mask: 0x1ffffff
>
> Ah, you do document that second parameter somewhere :)
>
> > + Returns: pointer - Pointer to allocated memory or NULL
> > +
> > + Errors:
> > + ENOMEM: Out of memory
> > +
> > + Context: process, softirq, hardirq
> > +
> > + Side Effects:
> > + - Allocates memory from kernel heap
>
> That part wasn't in your example
>
> > +Export all specifications::
> > +
> > + $ cat /sys/kernel/debug/kapi/export/all.json > kernel-apis.json
> > +
> > +Enable validation for specific API::
> > +
> > + $ echo 1 > /sys/kernel/debug/kapi/apis/kmalloc/validate
> > +
> > +Performance Considerations
> > +==========================
> > +
> > +Memory Overhead
> > +---------------
> > +
> > +Each API specification consumes approximately 400-450KB of memory due to the
> > +fixed-size arrays in ``struct kernel_api_spec``. With the current 4 syscall
> > +specifications, total memory usage is approximately 1.7MB. Consider:
>
> Ouch.
>
> > +Documentation Generation
> > +------------------------
> > +
> > +The framework exports specifications via debugfs that can be used
> > +to generate documentation. Tools for automatic documentation generation
> > +from specifications are planned for future development.
>
> Documentation always comes last :)
>
> Interesting stuff.
>
> jon
Thanks,
Mauro
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 0/9] Kernel API Specification Framework
2026-03-15 6:36 ` Sasha Levin
@ 2026-03-18 6:24 ` Mauro Carvalho Chehab
2026-03-18 14:14 ` Sasha Levin
0 siblings, 1 reply; 37+ messages in thread
From: Mauro Carvalho Chehab @ 2026-03-18 6:24 UTC (permalink / raw)
To: Sasha Levin
Cc: Jakub Kicinski, linux-api, linux-kernel, linux-doc, linux-fsdevel,
linux-kbuild, linux-kselftest, workflows, tools, x86,
Thomas Gleixner, Paul E. McKenney, Greg Kroah-Hartman,
Jonathan Corbet, Dmitry Vyukov, Randy Dunlap, Cyril Hrubis,
Kees Cook, Jake Edge, David Laight, Askar Safin, Gabriele Paoloni,
Mauro Carvalho Chehab, Christian Brauner, Alexander Viro,
Andrew Morton, Masahiro Yamada, Shuah Khan, Ingo Molnar,
Arnd Bergmann
On Sun, 15 Mar 2026 02:36:51 -0400
Sasha Levin <sashal@kernel.org> wrote:
> On Sat, Mar 14, 2026 at 11:18:22AM -0700, Jakub Kicinski wrote:
> >On Fri, 13 Mar 2026 11:09:10 -0400 Sasha Levin wrote:
> >> This enables static analysis tools to verify userspace API usage at compile
> >> time, test generation based on formal specifications, consistent error handling
> >> validation, automated documentation generation, and formal verification of
> >> kernel interfaces.
> >
> >Could you give some examples? We have machine readable descriptions for
> >Netlink interfaces, we approached syzbot folks and they did not really
> >seem to care for those.
>
> Once the API is in a machine-readable format, we can write formatters to
> output whatever downstream tools need.
Kernel-doc already does that. The way it works is that it handles
kernel-doc markups on two steps:
- first step: parse kernel-doc markups, function prototypes and data
types for variables, typedefs, structs, unions, enums.
This is done inside tools/lib/python/kdoc/kdoc_parser.py.
The documentation is stored in memory as a list of documentation
entries. Each element there belongs to class KdocItem.
It is trivial to output its content in JSON or YAML format. I
submitted a path series a while ago doing exactly that, aiming to help
writing unittests for first step:
https://lore.kernel.org/linux-doc/7648cb5f5a1b501d9ae9a57b4d8dbeb7273d9097.1770128540.git.mchehab+huawei@kernel.org/
I'm planing to rebase such patch series on the top of my latest
kernel-doc patch series.
- second step: output generation. There is an abstract class named
OutputFormat which contains the following output methods:
def out_doc(self, fname, name, args):
"""Outputs a DOC block."""
def out_function(self, fname, name, args):
"""Outputs a function."""
def out_enum(self, fname, name, args):
"""Outputs an enum."""
def out_var(self, fname, name, args):
"""Outputs a variable."""
def out_typedef(self, fname, name, args):
"""Outputs a typedef."""
def out_struct(self, fname, name, args):
"""Outputs a struct."""
Producing a different output is as easy as doing:
class MyFormat(OutputFormat):
...
def out_var(self, fname, name, args):
self.data =+ f"whatever {name}"
...
Thanks,
Mauro
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 5/9] kernel/api: add API specification for sys_open
2026-03-17 18:37 ` Jonathan Corbet
@ 2026-03-18 14:12 ` Sasha Levin
2026-03-18 14:16 ` Jonathan Corbet
0 siblings, 1 reply; 37+ messages in thread
From: Sasha Levin @ 2026-03-18 14:12 UTC (permalink / raw)
To: Jonathan Corbet
Cc: Greg Kroah-Hartman, linux-api, linux-kernel, linux-doc,
linux-fsdevel, linux-kbuild, linux-kselftest, workflows, tools,
x86, Thomas Gleixner, Paul E. McKenney, Dmitry Vyukov,
Randy Dunlap, Cyril Hrubis, Kees Cook, Jake Edge, David Laight,
Askar Safin, Gabriele Paoloni, Mauro Carvalho Chehab,
Christian Brauner, Alexander Viro, Andrew Morton, Masahiro Yamada,
Shuah Khan, Ingo Molnar, Arnd Bergmann
On Tue, Mar 17, 2026 at 12:37:13PM -0600, Jonathan Corbet wrote:
>Sasha Levin <sashal@kernel.org> writes:
>
>> On Fri, Mar 13, 2026 at 04:33:57PM +0100, Greg Kroah-Hartman wrote:
>>>On Fri, Mar 13, 2026 at 11:09:15AM -0400, Sasha Levin wrote:
>
>>>> + * since-version: 1.0
>>>
>>>I think since older versions :)
>>
>> Right. I guess that in my mind 1.0 was the first official "release". I'll
>> update it to 0.01.
>
>That kind of raises the question of just what since-version means. The
>version-0.01 (or 1.0) version of open() surely didn't do everything
>described in this specification. So it's saying that some version of
>the system call has existed since then?
You know, I'm not entierly sure what the definition should be here.
I stole this from man pages where they indicate at which version the API was
introduced in. I'm not sure if it really adds any value.
Any objections to just dropping it?
--
Thanks,
Sasha
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 0/9] Kernel API Specification Framework
2026-03-18 6:24 ` Mauro Carvalho Chehab
@ 2026-03-18 14:14 ` Sasha Levin
0 siblings, 0 replies; 37+ messages in thread
From: Sasha Levin @ 2026-03-18 14:14 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: Jakub Kicinski, linux-api, linux-kernel, linux-doc, linux-fsdevel,
linux-kbuild, linux-kselftest, workflows, tools, x86,
Thomas Gleixner, Paul E. McKenney, Greg Kroah-Hartman,
Jonathan Corbet, Dmitry Vyukov, Randy Dunlap, Cyril Hrubis,
Kees Cook, Jake Edge, David Laight, Askar Safin, Gabriele Paoloni,
Mauro Carvalho Chehab, Christian Brauner, Alexander Viro,
Andrew Morton, Masahiro Yamada, Shuah Khan, Ingo Molnar,
Arnd Bergmann
On Wed, Mar 18, 2026 at 07:24:10AM +0100, Mauro Carvalho Chehab wrote:
>On Sun, 15 Mar 2026 02:36:51 -0400
>Sasha Levin <sashal@kernel.org> wrote:
>
>> On Sat, Mar 14, 2026 at 11:18:22AM -0700, Jakub Kicinski wrote:
>> >On Fri, 13 Mar 2026 11:09:10 -0400 Sasha Levin wrote:
>> >> This enables static analysis tools to verify userspace API usage at compile
>> >> time, test generation based on formal specifications, consistent error handling
>> >> validation, automated documentation generation, and formal verification of
>> >> kernel interfaces.
>> >
>> >Could you give some examples? We have machine readable descriptions for
>> >Netlink interfaces, we approached syzbot folks and they did not really
>> >seem to care for those.
>>
>> Once the API is in a machine-readable format, we can write formatters to
>> output whatever downstream tools need.
>
>Kernel-doc already does that. The way it works is that it handles
>kernel-doc markups on two steps:
Cool, I'll take a look. We could throw away the source parser in the kapi tool
and just use the kerneldoc parse.
--
Thanks,
Sasha
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 5/9] kernel/api: add API specification for sys_open
2026-03-18 14:12 ` Sasha Levin
@ 2026-03-18 14:16 ` Jonathan Corbet
0 siblings, 0 replies; 37+ messages in thread
From: Jonathan Corbet @ 2026-03-18 14:16 UTC (permalink / raw)
To: Sasha Levin
Cc: Greg Kroah-Hartman, linux-api, linux-kernel, linux-doc,
linux-fsdevel, linux-kbuild, linux-kselftest, workflows, tools,
x86, Thomas Gleixner, Paul E. McKenney, Dmitry Vyukov,
Randy Dunlap, Cyril Hrubis, Kees Cook, Jake Edge, David Laight,
Askar Safin, Gabriele Paoloni, Mauro Carvalho Chehab,
Christian Brauner, Alexander Viro, Andrew Morton, Masahiro Yamada,
Shuah Khan, Ingo Molnar, Arnd Bergmann
Sasha Levin <sashal@kernel.org> writes:
> On Tue, Mar 17, 2026 at 12:37:13PM -0600, Jonathan Corbet wrote:
>>Sasha Levin <sashal@kernel.org> writes:
>>
>>> On Fri, Mar 13, 2026 at 04:33:57PM +0100, Greg Kroah-Hartman wrote:
>>>>On Fri, Mar 13, 2026 at 11:09:15AM -0400, Sasha Levin wrote:
>>
>>>>> + * since-version: 1.0
>>>>
>>>>I think since older versions :)
>>>
>>> Right. I guess that in my mind 1.0 was the first official "release". I'll
>>> update it to 0.01.
>>
>>That kind of raises the question of just what since-version means. The
>>version-0.01 (or 1.0) version of open() surely didn't do everything
>>described in this specification. So it's saying that some version of
>>the system call has existed since then?
>
> You know, I'm not entierly sure what the definition should be here.
>
> I stole this from man pages where they indicate at which version the API was
> introduced in. I'm not sure if it really adds any value.
>
> Any objections to just dropping it?
Not here. My general objective with the docs is to describe the kernel
as it is now, without that sort of history unless there's something
specific it can tell current users.
Thanks,
jon
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 1/9] kernel/api: introduce kernel API specification framework
2026-03-17 17:49 ` Jonathan Corbet
2026-03-18 6:00 ` Mauro Carvalho Chehab
@ 2026-03-18 14:30 ` Sasha Levin
2026-03-18 16:50 ` Jonathan Corbet
2026-03-18 14:32 ` Sasha Levin
2 siblings, 1 reply; 37+ messages in thread
From: Sasha Levin @ 2026-03-18 14:30 UTC (permalink / raw)
To: Jonathan Corbet
Cc: linux-api, linux-kernel, linux-doc, linux-fsdevel, linux-kbuild,
linux-kselftest, workflows, tools, x86, Thomas Gleixner,
Paul E. McKenney, Greg Kroah-Hartman, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann
On Tue, Mar 17, 2026 at 11:49:27AM -0600, Jonathan Corbet wrote:
>Sasha Levin <sashal@kernel.org> writes:
>
>> Add a framework for formally documenting kernel APIs with inline
>> specifications. This framework provides:
>>
>> - Structured API documentation with parameter specifications, return
>> values, error conditions, and execution context requirements
>> - Runtime validation capabilities for debugging (CONFIG_KAPI_RUNTIME_CHECKS)
>> - Export of specifications via debugfs for tooling integration
>> - Support for both internal kernel APIs and system calls
>
>So I'll confess I have only scanned over the implementation, but I have
>some thoughts on the earlier stuff.
>
>[...]
>
>> diff --git a/Documentation/dev-tools/kernel-api-spec.rst b/Documentation/dev-tools/kernel-api-spec.rst
>> new file mode 100644
>> index 0000000000000..7c0c1694f1f4a
>> --- /dev/null
>> +++ b/Documentation/dev-tools/kernel-api-spec.rst
>> @@ -0,0 +1,482 @@
>> +.. SPDX-License-Identifier: GPL-2.0
>> +
>> +======================================
>> +Kernel API Specification Framework
>> +======================================
>> +
>> +:Author: Sasha Levin <sashal@kernel.org>
>> +:Date: June 2025
>
>Has it not changed since then?
>
>> +.. contents:: Table of Contents
>> + :depth: 3
>> + :local:
>> +
>> +Introduction
>> +============
>
>[...]
>
>> +Usage Guide
>> +===========
>> +
>> +Basic API Specification
>> +-----------------------
>> +
>> +API specifications are written as KAPI-annotated kerneldoc comments directly in
>> +the source file, immediately preceding the function implementation. The ``kapi``
>> +tool extracts these annotations to produce structured specifications.
>> +
>> +.. code-block:: c
>> +
>> + /**
>> + * kmalloc - allocate kernel memory
>> + * @size: Number of bytes to allocate
>> + * @flags: Allocation flags (GFP_*)
>
>Given that the text thus far has talked about user-space API validation,
>it's a bit surprising to see an internal function used as an example.
Indeed :)
I was playing with adding specs to lower level functions as a way to both
simplify the higher level spec blocks, but also to help validate the
correctness of those upper level specs ("you said that this syscall doesn't
allocate memory, but it's calling kmalloc()!").
>Also, maybe it should be kmalloc_obj()? <runs away>
>
>> + * context-flags: KAPI_CTX_PROCESS | KAPI_CTX_SOFTIRQ | KAPI_CTX_HARDIRQ
>> + * param-count: 2
>
>param-count is two, but you only document one of them?
>
>> + * param: size
>> + * type: KAPI_TYPE_UINT
>> + * flags: KAPI_PARAM_IN
>> + * constraint-type: KAPI_CONSTRAINT_RANGE
>> + * range: 0, KMALLOC_MAX_SIZE
>> + *
>> + * error: ENOMEM, Out of memory
>> + * desc: Insufficient memory available for the requested allocation.
>> + */
>
>Honest question: can this be made a bit easier for people to create,
>with less shift-key wear? My biggest worry with a system like this is
>that people won't take the time to create and maintain the entries, so
>anything that would ease the task would help. Is there an impediment to
>something like:
Before answering your actual question, I honestly think that most of these will
be LLM generated, so at least the skeleton of the kernel-doc will be there and
humans will only end up modifying the proposed spec.
> contexts: process, softirq, hardirq
>
> param: size
> type: uint, input
> constraint: range(0, KMALLOC_MAX_SIZE)
>
>See what I'm getting at? ISTM that your DSL could be made a bit less
>verbose and shouty while being just as well defined, but perhaps I'm
>missing something?
Yup, let me look into that.
>Even better, of course, would be to add a "description" field for each
>parameter, and allow that rather than the @param description that
>kerneldoc currently uses. That would keep all the information together,
>at the minor cost of adding another significant complication to the
>kernel-doc script. Mauro won't mind :)
>
>> + void *kmalloc(size_t size, gfp_t flags)
>> + {
>> + /* Implementation */
>> + }
>> +
>> +Alternatively, specifications can be defined using the ``DEFINE_KERNEL_API_SPEC``
>> +macro for compiled-in specs that are stored in the ``.kapi_specs`` ELF section:
>> +
>> +.. code-block:: c
>> +
>> + #include <linux/kernel_api_spec.h>
>> +
>> + DEFINE_KERNEL_API_SPEC(sys_open)
>> + KAPI_DESCRIPTION("Open or create a file")
>> + KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
>> + /* ... parameter, error, constraint definitions ... */
>> + KAPI_END_SPEC
>
>So the reason for two completely separate mechanisms is not entirely
>clear to me. The kerneldoc variant is essentially documentation, while
>the macro stuff is to be built into the executable? What if you want
>both?
>
>It would be nice to only have one way if at all possible; I'm sure that
>crossed your mind at some point :) If there have to be two, having both
>examples describe the same function would make the parallels more clear.
Under the hood, the
>> +System Call Specification
>> +-------------------------
>> +
>> +System calls are documented inline in the implementation file (e.g., ``fs/open.c``)
>> +using KAPI-annotated kerneldoc comments. When ``CONFIG_KAPI_RUNTIME_CHECKS`` is
>> +enabled, the ``SYSCALL_DEFINEx`` macros automatically look up the specification
>> +and validate parameters before and after the syscall executes.
>> +
>> +IOCTL Specification
>> +-------------------
>> +
>> +IOCTLs use the same annotation approach with additional structure field
>> +specifications
>
>This might be a really good place for an example
I think I'll just drop the IOCTL code for now. I used it in the RFCs to
demonstrate how a similar mechanism could be used throughout different
userspace API, but since I'm not using it in this series it probably makes
sense not to even talk about it.
>[...]
>
>> +Usage Examples
>> +--------------
>> +
>> +Query specific API::
>> +
>> + $ cat /sys/kernel/debug/kapi/apis/kmalloc/specification
>> + API: kmalloc
>> + Version: 3.0
>> + Description: Allocate kernel memory
>> +
>> + Parameters:
>> + [0] size (size_t, in): Number of bytes to allocate
>> + Range: 0 - 4194304
>> + [1] flags (flags, in): Allocation flags (GFP_*)
>> + Mask: 0x1ffffff
>
>Ah, you do document that second parameter somewhere :)
Apparently. I'll update the prior example. Not sure why it's missing :)
>> + Returns: pointer - Pointer to allocated memory or NULL
>> +
>> + Errors:
>> + ENOMEM: Out of memory
>> +
>> + Context: process, softirq, hardirq
>> +
>> + Side Effects:
>> + - Allocates memory from kernel heap
>
>That part wasn't in your example
>
>> +Export all specifications::
>> +
>> + $ cat /sys/kernel/debug/kapi/export/all.json > kernel-apis.json
>> +
>> +Enable validation for specific API::
>> +
>> + $ echo 1 > /sys/kernel/debug/kapi/apis/kmalloc/validate
>> +
>> +Performance Considerations
>> +==========================
>> +
>> +Memory Overhead
>> +---------------
>> +
>> +Each API specification consumes approximately 400-450KB of memory due to the
>> +fixed-size arrays in ``struct kernel_api_spec``. With the current 4 syscall
>> +specifications, total memory usage is approximately 1.7MB. Consider:
>
>Ouch.
It's pretty "dumb" at this point: large static arrays and such, mostly to keep
the code simple to read. If this is a concern for accepting this series, I'm
happy to shrink it down (by a lot).
>> +Documentation Generation
>> +------------------------
>> +
>> +The framework exports specifications via debugfs that can be used
>> +to generate documentation. Tools for automatic documentation generation
>> +from specifications are planned for future development.
>
>Documentation always comes last :)
So this is one thing I wanted to run by the doc maintainers: the kapi tool
already has the capability to export these specs in .rst format. Would it be
interesting to have a manual for kenrel APIs in Documentation/? It can be
automatically generated and updated and would always match the kernel code at
that point.
>Interesting stuff.
Thanks!
--
Thanks,
Sasha
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 1/9] kernel/api: introduce kernel API specification framework
2026-03-17 17:49 ` Jonathan Corbet
2026-03-18 6:00 ` Mauro Carvalho Chehab
2026-03-18 14:30 ` Sasha Levin
@ 2026-03-18 14:32 ` Sasha Levin
2026-03-18 16:51 ` Jonathan Corbet
2 siblings, 1 reply; 37+ messages in thread
From: Sasha Levin @ 2026-03-18 14:32 UTC (permalink / raw)
To: Jonathan Corbet
Cc: linux-api, linux-kernel, linux-doc, linux-fsdevel, linux-kbuild,
linux-kselftest, workflows, tools, x86, Thomas Gleixner,
Paul E. McKenney, Greg Kroah-Hartman, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann
On Tue, Mar 17, 2026 at 11:49:27AM -0600, Jonathan Corbet wrote:
>So the reason for two completely separate mechanisms is not entirely
>clear to me. The kerneldoc variant is essentially documentation, while
>the macro stuff is to be built into the executable? What if you want
>both?
>
>It would be nice to only have one way if at all possible; I'm sure that
>crossed your mind at some point :) If there have to be two, having both
>examples describe the same function would make the parallels more clear.
Woops, I forgot to finish writing my reply to this :)
Under the hood, kerneldoc specs are translated into those macros so they could
be part of the build process and embedded into the resulting binary (both for
documentation as well as the runtime validation).
I don't think anyone would use the macro format directly, but as it's there
anyway I figured I'd offer it as an option. Would it make sense to just hide it
behind the scenes?
--
Thanks,
Sasha
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 1/9] kernel/api: introduce kernel API specification framework
2026-03-18 6:00 ` Mauro Carvalho Chehab
@ 2026-03-18 14:53 ` Sasha Levin
0 siblings, 0 replies; 37+ messages in thread
From: Sasha Levin @ 2026-03-18 14:53 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: Jonathan Corbet, linux-api, linux-kernel, linux-doc,
linux-fsdevel, linux-kbuild, linux-kselftest, workflows, tools,
x86, Thomas Gleixner, Paul E. McKenney, Greg Kroah-Hartman,
Dmitry Vyukov, Randy Dunlap, Cyril Hrubis, Kees Cook, Jake Edge,
David Laight, Askar Safin, Gabriele Paoloni,
Mauro Carvalho Chehab, Christian Brauner, Alexander Viro,
Andrew Morton, Masahiro Yamada, Shuah Khan, Ingo Molnar,
Arnd Bergmann
On Wed, Mar 18, 2026 at 07:00:55AM +0100, Mauro Carvalho Chehab wrote:
>On Tue, 17 Mar 2026 11:49:27 -0600
>Jonathan Corbet <corbet@lwn.net> wrote:
>> So the reason for two completely separate mechanisms is not entirely
>> clear to me. The kerneldoc variant is essentially documentation, while
>> the macro stuff is to be built into the executable? What if you want
>> both?
>
>You can easily add support at kernel-doc to output such macros.
>
>All you need is to create a new class derived from OutputFormat and
>make it produce any different output format, including:
>
> #include <linux/kernel_api_spec.h>
>
> DEFINE_KERNEL_API_SPEC(sys_open)
> KAPI_DESCRIPTION("Open or create a file")
> KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
> /* ... parameter, error, constraint definitions ... */
> KAPI_END_SPEC
>
>I'd say that converting from such output to `.kapi_specs`` ELF section
>itself and/or to sysfs/debugfs - e.g. something that would require to
>compile or be linked with Kernel's compiled binaries should be done by a
>separate tool, but we should aim to have a singe tool to process
>kernel documentation markups.
>
>It is hard enough to maintain just one tool - and to have people actually
>writing documentation. Having a second one to handle it, with a different
>format will likely increase a lot the documentation burden.
So this is exactly what happens under the hood :) kerneldoc outputs these
macros and they get compiled ito the binary.
I exposed the macros as an option since they're there anyway, but I'm happy to
hide them as internal plumbing too.
--
Thanks,
Sasha
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 1/9] kernel/api: introduce kernel API specification framework
2026-03-18 14:30 ` Sasha Levin
@ 2026-03-18 16:50 ` Jonathan Corbet
0 siblings, 0 replies; 37+ messages in thread
From: Jonathan Corbet @ 2026-03-18 16:50 UTC (permalink / raw)
To: Sasha Levin
Cc: linux-api, linux-kernel, linux-doc, linux-fsdevel, linux-kbuild,
linux-kselftest, workflows, tools, x86, Thomas Gleixner,
Paul E. McKenney, Greg Kroah-Hartman, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann
Sasha Levin <sashal@kernel.org> writes:
>>> +The framework exports specifications via debugfs that can be used
>>> +to generate documentation. Tools for automatic documentation generation
>>> +from specifications are planned for future development.
>>
>>Documentation always comes last :)
>
> So this is one thing I wanted to run by the doc maintainers: the kapi tool
> already has the capability to export these specs in .rst format. Would it be
> interesting to have a manual for kenrel APIs in Documentation/? It can be
> automatically generated and updated and would always match the kernel code at
> that point.
I suspect we could certainly find a place for it. Maybe as a subsection
of the user-space API manual, at least for the system-call portion of
it.
Thanks,
jon
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH 1/9] kernel/api: introduce kernel API specification framework
2026-03-18 14:32 ` Sasha Levin
@ 2026-03-18 16:51 ` Jonathan Corbet
0 siblings, 0 replies; 37+ messages in thread
From: Jonathan Corbet @ 2026-03-18 16:51 UTC (permalink / raw)
To: Sasha Levin
Cc: linux-api, linux-kernel, linux-doc, linux-fsdevel, linux-kbuild,
linux-kselftest, workflows, tools, x86, Thomas Gleixner,
Paul E. McKenney, Greg Kroah-Hartman, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann
Sasha Levin <sashal@kernel.org> writes:
> On Tue, Mar 17, 2026 at 11:49:27AM -0600, Jonathan Corbet wrote:
>>So the reason for two completely separate mechanisms is not entirely
>>clear to me. The kerneldoc variant is essentially documentation, while
>>the macro stuff is to be built into the executable? What if you want
>>both?
>>
>>It would be nice to only have one way if at all possible; I'm sure that
>>crossed your mind at some point :) If there have to be two, having both
>>examples describe the same function would make the parallels more clear.
>
> Woops, I forgot to finish writing my reply to this :)
>
> Under the hood, kerneldoc specs are translated into those macros so they could
> be part of the build process and embedded into the resulting binary (both for
> documentation as well as the runtime validation).
>
> I don't think anyone would use the macro format directly, but as it's there
> anyway I figured I'd offer it as an option. Would it make sense to just hide it
> behind the scenes?
If there are two ways of doing it, people will use both ways. My
kneejerk reaction would be to hide the macros as an implementation
detail, but perhaps that's just me.
Thanks,
jon
^ permalink raw reply [flat|nested] 37+ messages in thread
end of thread, other threads:[~2026-03-18 16:51 UTC | newest]
Thread overview: 37+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-13 15:09 [PATCH 0/9] Kernel API Specification Framework Sasha Levin
2026-03-13 15:09 ` [PATCH 1/9] kernel/api: introduce kernel API specification framework Sasha Levin
2026-03-17 17:49 ` Jonathan Corbet
2026-03-18 6:00 ` Mauro Carvalho Chehab
2026-03-18 14:53 ` Sasha Levin
2026-03-18 14:30 ` Sasha Levin
2026-03-18 16:50 ` Jonathan Corbet
2026-03-18 14:32 ` Sasha Levin
2026-03-18 16:51 ` Jonathan Corbet
2026-03-13 15:09 ` [PATCH 2/9] kernel/api: enable kerneldoc-based API specifications Sasha Levin
2026-03-13 15:09 ` [PATCH 3/9] kernel/api: add debugfs interface for kernel " Sasha Levin
2026-03-13 15:32 ` Greg Kroah-Hartman
2026-03-13 16:27 ` Sasha Levin
2026-03-13 15:09 ` [PATCH 4/9] tools/kapi: Add kernel API specification extraction tool Sasha Levin
2026-03-13 15:09 ` [PATCH 5/9] kernel/api: add API specification for sys_open Sasha Levin
2026-03-13 15:33 ` Greg Kroah-Hartman
2026-03-13 16:42 ` Sasha Levin
2026-03-17 18:37 ` Jonathan Corbet
2026-03-18 14:12 ` Sasha Levin
2026-03-18 14:16 ` Jonathan Corbet
2026-03-13 15:09 ` [PATCH 6/9] kernel/api: add API specification for sys_close Sasha Levin
2026-03-13 15:49 ` Greg Kroah-Hartman
2026-03-13 16:46 ` Sasha Levin
2026-03-13 15:52 ` Greg Kroah-Hartman
2026-03-13 16:55 ` Sasha Levin
2026-03-13 15:09 ` [PATCH 7/9] kernel/api: add API specification for sys_read Sasha Levin
2026-03-13 15:09 ` [PATCH 8/9] kernel/api: add API specification for sys_write Sasha Levin
2026-03-13 15:09 ` [PATCH 9/9] kernel/api: add runtime verification selftest Sasha Levin
2026-03-14 18:18 ` [PATCH 0/9] Kernel API Specification Framework Jakub Kicinski
2026-03-14 22:44 ` David Laight
2026-03-15 6:46 ` Sasha Levin
2026-03-15 6:36 ` Sasha Levin
2026-03-18 6:24 ` Mauro Carvalho Chehab
2026-03-18 14:14 ` Sasha Levin
2026-03-16 7:05 ` Dmitry Vyukov
2026-03-16 22:57 ` Jakub Kicinski
2026-03-16 23:29 ` Sasha Levin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox