Linux userland API discussions
 help / color / mirror / Atom feed
* [RFC 10/19] mm/mlock: add API specification for mlock
From: Sasha Levin @ 2025-06-14 13:48 UTC (permalink / raw)
  To: linux-kernel; +Cc: linux-api, workflows, tools, Sasha Levin
In-Reply-To: <20250614134858.790460-1-sashal@kernel.org>

Add kernel API specification for the mlock() system call.

Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 mm/mlock.c | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 105 insertions(+)

diff --git a/mm/mlock.c b/mm/mlock.c
index 3cb72b579ffd3..a37102df54b01 100644
--- a/mm/mlock.c
+++ b/mm/mlock.c
@@ -25,6 +25,7 @@
 #include <linux/memcontrol.h>
 #include <linux/mm_inline.h>
 #include <linux/secretmem.h>
+#include <linux/syscall_api_spec.h>
 
 #include "internal.h"
 
@@ -658,6 +659,110 @@ static __must_check int do_mlock(unsigned long start, size_t len, vm_flags_t fla
 	return 0;
 }
 
+
+DEFINE_KERNEL_API_SPEC(sys_mlock)
+	KAPI_DESCRIPTION("Lock pages in memory")
+	KAPI_LONG_DESC("Locks pages in the specified address range into RAM, "
+		       "preventing them from being paged to swap. Requires "
+		       "CAP_IPC_LOCK capability or RLIMIT_MEMLOCK resource limit.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	KAPI_PARAM(0, "start", "unsigned long", "Starting address of memory range to lock")
+		.type = KAPI_TYPE_UINT,
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.constraint_type = KAPI_CONSTRAINT_NONE,
+		.constraints = "Rounded down to page boundary",
+	KAPI_PARAM_END
+	KAPI_PARAM(1, "len", "size_t", "Length of memory range to lock in bytes")
+		.type = KAPI_TYPE_UINT,
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		KAPI_PARAM_RANGE(0, LONG_MAX)
+		.constraints = "Rounded up to page boundary",
+	KAPI_PARAM_END
+
+	.return_spec = {
+		.type_name = "long",
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.success_value = 0,
+		.description = "0 on success, negative error code on failure",
+	},
+
+	KAPI_ERROR(0, -ENOMEM, "ENOMEM", "Address range issue",
+		   "Some of the specified range is not mapped, has unmapped gaps, "
+		   "or the lock would cause the number of mapped regions to exceed the limit.")
+	KAPI_ERROR(1, -EPERM, "EPERM", "Insufficient privileges",
+		   "The caller is not privileged (no CAP_IPC_LOCK) and RLIMIT_MEMLOCK is 0.")
+	KAPI_ERROR(2, -EINVAL, "EINVAL", "Address overflow",
+		   "The result of the addition start+len was less than start (arithmetic overflow).")
+	KAPI_ERROR(3, -EAGAIN, "EAGAIN", "Some or all memory could not be locked",
+		   "Some or all of the specified address range could not be locked.")
+	KAPI_ERROR(4, -EINTR, "EINTR", "Interrupted by signal",
+		   "The operation was interrupted by a fatal signal before completion.")
+
+	.error_count = 5,
+	.param_count = 2,
+	.since_version = "2.0",
+
+	.locks[0] = {
+		.lock_name = "mmap_lock",
+		.lock_type = KAPI_LOCK_RWLOCK,
+		.acquired = true,
+		.released = true,
+		.description = "Process memory map write lock",
+	},
+	.lock_count = 1,
+
+	/* Signal specifications */
+	.signal_count = 1,
+
+	/* Fatal signals can interrupt mmap_write_lock_killable */
+	KAPI_SIGNAL(0, 0, "FATAL", KAPI_SIGNAL_RECEIVE, KAPI_SIGNAL_ACTION_RETURN)
+		KAPI_SIGNAL_CONDITION("Fatal signal pending")
+		KAPI_SIGNAL_DESC("Fatal signals (SIGKILL, etc.) can interrupt the operation "
+				 "when acquiring mmap_write_lock_killable(), causing -EINTR return")
+	KAPI_SIGNAL_END
+
+	.examples = "mlock(addr, 4096);  // Lock one page\n"
+		    "mlock(addr, len);   // Lock range of pages",
+	.notes = "Memory locks do not stack - multiple calls on the same range can be "
+		 "undone by a single munlock. Locks are not inherited by child processes. "
+		 "Pages are locked on whole page boundaries.",
+
+	/* Side effects */
+	KAPI_SIDE_EFFECT(0, KAPI_EFFECT_MODIFY_STATE | KAPI_EFFECT_ALLOC_MEMORY,
+			 "process memory",
+			 "Locks pages into physical memory, preventing swapping")
+		KAPI_EFFECT_REVERSIBLE
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(1, KAPI_EFFECT_MODIFY_STATE,
+			 "mm->locked_vm",
+			 "Increases process locked memory counter")
+		KAPI_EFFECT_REVERSIBLE
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(2, KAPI_EFFECT_ALLOC_MEMORY,
+			 "physical pages",
+			 "May allocate and populate page table entries")
+		KAPI_EFFECT_CONDITION("Pages not already present")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT_COUNT(3)
+
+	/* State transitions */
+	KAPI_STATE_TRANS(0, "memory pages", "swappable", "locked in RAM",
+			 "Pages become non-swappable and pinned in physical memory")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(1, "VMA flags", "unlocked", "VM_LOCKED set",
+			 "Virtual memory area marked as locked")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS_COUNT(2)
+KAPI_END_SPEC;
+
 SYSCALL_DEFINE2(mlock, unsigned long, start, size_t, len)
 {
 	return do_mlock(start, len, VM_LOCKED);
-- 
2.39.5


^ permalink raw reply related

* [RFC 11/19] mm/mlock: add API specification for mlock2
From: Sasha Levin @ 2025-06-14 13:48 UTC (permalink / raw)
  To: linux-kernel; +Cc: linux-api, workflows, tools, Sasha Levin
In-Reply-To: <20250614134858.790460-1-sashal@kernel.org>

Add kernel API specification for the mlock2() system call.

Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 mm/mlock.c | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 148 insertions(+)

diff --git a/mm/mlock.c b/mm/mlock.c
index a37102df54b01..af2ab78acc226 100644
--- a/mm/mlock.c
+++ b/mm/mlock.c
@@ -768,6 +768,154 @@ SYSCALL_DEFINE2(mlock, unsigned long, start, size_t, len)
 	return do_mlock(start, len, VM_LOCKED);
 }
 
+
+DEFINE_KERNEL_API_SPEC(sys_mlock2)
+	KAPI_DESCRIPTION("Lock pages in memory with flags")
+	KAPI_LONG_DESC("Enhanced version of mlock() that supports flags. "
+		       "MLOCK_ONFAULT flag allows locking pages on fault rather than immediately.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	/* Parameters */
+	KAPI_PARAM(0, "start", "unsigned long", "Starting address of memory range to lock")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_NONE,
+		.constraints = "Rounded down to page boundary",
+	KAPI_PARAM_END
+
+	KAPI_PARAM(1, "len", "size_t", "Length of memory range to lock in bytes")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		KAPI_PARAM_RANGE(0, LONG_MAX)
+		.constraints = "Rounded up to page boundary",
+	KAPI_PARAM_END
+
+	KAPI_PARAM(2, "flags", "int", "Flags controlling lock behavior")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.type = KAPI_TYPE_INT,
+		.constraint_type = KAPI_CONSTRAINT_MASK,
+		.valid_mask = MLOCK_ONFAULT,
+		.constraints = "Only MLOCK_ONFAULT flag is currently supported",
+	KAPI_PARAM_END
+
+	/* Return specification */
+	KAPI_RETURN("long", "0 on success, negative error code on failure")
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.success_value = 0,
+	KAPI_RETURN_END
+
+	/* Error codes */
+	KAPI_ERROR(0, -EINVAL, "EINVAL", "Invalid flags", "Unknown flags were specified (flags & ~MLOCK_ONFAULT).")
+	KAPI_ERROR(1, -ENOMEM, "ENOMEM", "Address range issue", "Some of the specified range is not mapped, has unmapped gaps, or the lock would cause the number of mapped regions to exceed the limit.")
+	KAPI_ERROR(2, -EPERM, "EPERM", "Insufficient privileges", "The caller is not privileged (no CAP_IPC_LOCK) and RLIMIT_MEMLOCK is 0.")
+	KAPI_ERROR(3, -EAGAIN, "EAGAIN", "Some or all memory could not be locked", "Some or all of the specified address range could not be locked.")
+	KAPI_ERROR(4, -EINTR, "EINTR", "Interrupted by signal", "The operation was interrupted by a fatal signal before completion.")
+
+	/* Signal specifications */
+	KAPI_SIGNAL(0, 0, "FATAL_SIGNALS", KAPI_SIGNAL_RECEIVE, KAPI_SIGNAL_ACTION_RETURN)
+		KAPI_SIGNAL_CONDITION("Fatal signal pending during mmap_write_lock_killable")
+		KAPI_SIGNAL_DESC("Fatal signals (SIGKILL, SIGTERM, etc.) can interrupt the operation when acquiring mmap_write_lock_killable(), causing -EINTR return")
+		KAPI_SIGNAL_RESTARTABLE
+	KAPI_SIGNAL_END
+
+	KAPI_SIGNAL(1, SIGBUS, "SIGBUS", KAPI_SIGNAL_SEND, KAPI_SIGNAL_ACTION_DEFAULT)
+		KAPI_SIGNAL_TARGET("Current process")
+		KAPI_SIGNAL_CONDITION("Memory access to locked page fails")
+		KAPI_SIGNAL_DESC("Can be generated if accessing a locked page that cannot be brought into memory (e.g., truncated file mapping)")
+	KAPI_SIGNAL_END
+
+	/* Side effects */
+	KAPI_SIDE_EFFECT(0, KAPI_EFFECT_MODIFY_STATE | KAPI_EFFECT_ALLOC_MEMORY,
+			 "process memory",
+			 "Locks pages into physical memory, preventing swapping")
+		KAPI_EFFECT_REVERSIBLE
+		KAPI_EFFECT_CONDITION("Pages become resident in RAM")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(1, KAPI_EFFECT_MODIFY_STATE,
+			 "mm->locked_vm",
+			 "Increases process locked memory counter")
+		KAPI_EFFECT_REVERSIBLE
+		KAPI_EFFECT_CONDITION("Counted against RLIMIT_MEMLOCK")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(2, KAPI_EFFECT_ALLOC_MEMORY,
+			 "page tables",
+			 "May allocate and populate page table entries")
+		KAPI_EFFECT_CONDITION("Pages not already present")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(3, KAPI_EFFECT_MODIFY_STATE,
+			 "VMA flags",
+			 "Sets VM_LOCKED and optionally VM_LOCKONFAULT on affected VMAs")
+		KAPI_EFFECT_REVERSIBLE
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(4, KAPI_EFFECT_FILESYSTEM,
+			 "page fault behavior",
+			 "With MLOCK_ONFAULT, changes how future page faults are handled")
+		KAPI_EFFECT_CONDITION("MLOCK_ONFAULT flag specified")
+	KAPI_SIDE_EFFECT_END
+
+	/* State transitions */
+	KAPI_STATE_TRANS(0, "memory pages",
+			 "swappable", "locked in RAM",
+			 "Pages become non-swappable and pinned in physical memory")
+		KAPI_STATE_TRANS_COND("Without MLOCK_ONFAULT")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(1, "VMA flags",
+			 "unlocked", "VM_LOCKED set",
+			 "Virtual memory area marked as locked")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(2, "VMA flags",
+			 "normal fault", "VM_LOCKONFAULT set",
+			 "VMA marked to lock pages on future faults")
+		KAPI_STATE_TRANS_COND("MLOCK_ONFAULT flag specified")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(3, "page residency",
+			 "may be swapped", "resident in memory",
+			 "Pages brought into RAM and kept there")
+		KAPI_STATE_TRANS_COND("Without MLOCK_ONFAULT")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(4, "process statistics",
+			 "normal memory accounting", "locked memory accounting",
+			 "Memory counted against RLIMIT_MEMLOCK")
+	KAPI_STATE_TRANS_END
+
+	/* Locking information */
+	KAPI_LOCK(0, "mmap_lock", KAPI_LOCK_RWLOCK)
+		KAPI_LOCK_DESC("Process memory map write lock")
+		KAPI_LOCK_ACQUIRED
+		KAPI_LOCK_RELEASED
+		KAPI_LOCK_DESC("Protects VMA modifications during lock operation")
+	KAPI_LOCK_END
+
+	KAPI_LOCK(1, "lru_lock", KAPI_LOCK_SPINLOCK)
+		KAPI_LOCK_DESC("Per-memcg LRU list lock")
+		KAPI_LOCK_ACQUIRED
+		KAPI_LOCK_RELEASED
+		KAPI_LOCK_DESC("Taken when moving pages to unevictable list when locking pages")
+	KAPI_LOCK_END
+
+	.error_count = 5,
+	.param_count = 3,
+	.since_version = "4.4",
+	.signal_count = 2,
+	.side_effect_count = 5,
+	.state_trans_count = 5,
+	.lock_count = 2,
+	.examples = "mlock2(addr, len, 0);            // Same as mlock()\n"
+		    "mlock2(addr, len, MLOCK_ONFAULT); // Lock on fault",
+	.notes = "MLOCK_ONFAULT flag defers actual page locking until pages are accessed. "
+		 "Memory locks do not stack. Locks are not inherited by child processes.",
+KAPI_END_SPEC;
+
 SYSCALL_DEFINE3(mlock2, unsigned long, start, size_t, len, int, flags)
 {
 	vm_flags_t vm_flags = VM_LOCKED;
-- 
2.39.5


^ permalink raw reply related

* [RFC 12/19] mm/mlock: add API specification for mlockall
From: Sasha Levin @ 2025-06-14 13:48 UTC (permalink / raw)
  To: linux-kernel; +Cc: linux-api, workflows, tools, Sasha Levin
In-Reply-To: <20250614134858.790460-1-sashal@kernel.org>

Add kernel API specification for the mlockall() system call.

Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 mm/mlock.c | 144 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 144 insertions(+)

diff --git a/mm/mlock.c b/mm/mlock.c
index af2ab78acc226..95ee707c5922f 100644
--- a/mm/mlock.c
+++ b/mm/mlock.c
@@ -997,6 +997,150 @@ static int apply_mlockall_flags(int flags)
 	return 0;
 }
 
+
+DEFINE_KERNEL_API_SPEC(sys_mlockall)
+	KAPI_DESCRIPTION("Lock all process pages in memory")
+	KAPI_LONG_DESC("Locks all pages mapped into the process address space. "
+		       "MCL_CURRENT locks current pages, MCL_FUTURE locks future mappings, "
+		       "MCL_ONFAULT defers locking until page fault.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	/* Parameters */
+	KAPI_PARAM(0, "flags", "int", "Flags controlling which pages to lock")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.type = KAPI_TYPE_INT,
+		.constraint_type = KAPI_CONSTRAINT_MASK,
+		.valid_mask = MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT,
+		.constraints = "Must specify MCL_CURRENT and/or MCL_FUTURE; MCL_ONFAULT can be OR'd",
+	KAPI_PARAM_END
+
+	/* Return specification */
+	KAPI_RETURN("long", "0 on success, negative error code on failure")
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.success_value = 0,
+	KAPI_RETURN_END
+
+	/* Error codes */
+	KAPI_ERROR(0, -EINVAL, "EINVAL", "Invalid flags", "Invalid combination of flags specified, or no flags set, or only MCL_ONFAULT without MCL_CURRENT or MCL_FUTURE.")
+	KAPI_ERROR(1, -EPERM, "EPERM", "Insufficient privileges", "The caller is not privileged (no CAP_IPC_LOCK) and RLIMIT_MEMLOCK is 0.")
+	KAPI_ERROR(2, -ENOMEM, "ENOMEM", "Insufficient resources", "MCL_CURRENT is set and total VM size exceeds RLIMIT_MEMLOCK and caller lacks CAP_IPC_LOCK.")
+	KAPI_ERROR(3, -EINTR, "EINTR", "Interrupted by signal", "The operation was interrupted by a signal before completion.")
+	KAPI_ERROR(4, -EAGAIN, "EAGAIN", "Some memory could not be locked", "Some pages could not be locked, possibly due to memory pressure.")
+
+	/* Signal specifications */
+	KAPI_SIGNAL(0, 0, "FATAL_SIGNALS", KAPI_SIGNAL_RECEIVE, KAPI_SIGNAL_ACTION_RETURN)
+		KAPI_SIGNAL_CONDITION("Fatal signal pending during mmap_write_lock_killable")
+		KAPI_SIGNAL_DESC("Fatal signals (SIGKILL, SIGTERM, etc.) can interrupt the operation when acquiring mmap_write_lock_killable(), causing -EINTR return")
+		KAPI_SIGNAL_RESTARTABLE
+	KAPI_SIGNAL_END
+
+	KAPI_SIGNAL(1, SIGBUS, "SIGBUS", KAPI_SIGNAL_SEND, KAPI_SIGNAL_ACTION_DEFAULT)
+		KAPI_SIGNAL_TARGET("Current process")
+		KAPI_SIGNAL_CONDITION("Memory access to locked page fails")
+		KAPI_SIGNAL_DESC("Can be generated later if accessing a locked page that cannot be brought into memory (e.g., truncated file mapping)")
+	KAPI_SIGNAL_END
+
+	/* Side effects */
+	KAPI_SIDE_EFFECT(0, KAPI_EFFECT_MODIFY_STATE | KAPI_EFFECT_ALLOC_MEMORY,
+			 "all process memory",
+			 "Locks all current pages into physical memory, preventing swapping")
+		KAPI_EFFECT_REVERSIBLE
+		KAPI_EFFECT_CONDITION("MCL_CURRENT flag set")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(1, KAPI_EFFECT_MODIFY_STATE,
+			 "mm->def_flags",
+			 "Sets VM_LOCKED in default flags for future mappings")
+		KAPI_EFFECT_REVERSIBLE
+		KAPI_EFFECT_CONDITION("MCL_FUTURE flag set")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(2, KAPI_EFFECT_MODIFY_STATE,
+			 "mm->locked_vm",
+			 "Increases process locked memory counter for entire address space")
+		KAPI_EFFECT_REVERSIBLE
+		KAPI_EFFECT_CONDITION("MCL_CURRENT flag set")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(3, KAPI_EFFECT_ALLOC_MEMORY,
+			 "page tables",
+			 "May allocate and populate page table entries for all mappings")
+		KAPI_EFFECT_CONDITION("MCL_CURRENT without MCL_ONFAULT")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(4, KAPI_EFFECT_MODIFY_STATE,
+			 "VMA flags",
+			 "Sets VM_LOCKED on all existing VMAs")
+		KAPI_EFFECT_REVERSIBLE
+		KAPI_EFFECT_CONDITION("MCL_CURRENT flag set")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(5, KAPI_EFFECT_SCHEDULE,
+			 "mm_populate",
+			 "Triggers population of entire address space")
+		KAPI_EFFECT_CONDITION("MCL_CURRENT without MCL_ONFAULT")
+	KAPI_SIDE_EFFECT_END
+
+	/* State transitions */
+	KAPI_STATE_TRANS(0, "all memory pages",
+			 "swappable", "locked in RAM",
+			 "All pages in process become non-swappable")
+		KAPI_STATE_TRANS_COND("MCL_CURRENT flag set")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(1, "future mappings",
+			 "normal", "auto-locked",
+			 "New mappings will be automatically locked")
+		KAPI_STATE_TRANS_COND("MCL_FUTURE flag set")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(2, "VMA flags",
+			 "varied", "all VM_LOCKED",
+			 "All virtual memory areas marked as locked")
+		KAPI_STATE_TRANS_COND("MCL_CURRENT flag set")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(3, "page fault behavior",
+			 "normal faulting", "lock on fault",
+			 "Pages locked when faulted in rather than immediately")
+		KAPI_STATE_TRANS_COND("MCL_ONFAULT flag set")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(4, "process statistics",
+			 "partial locked memory", "all memory locked",
+			 "Entire VM size counted against RLIMIT_MEMLOCK")
+		KAPI_STATE_TRANS_COND("MCL_CURRENT flag set")
+	KAPI_STATE_TRANS_END
+
+	/* Locking information */
+	KAPI_LOCK(0, "mmap_lock", KAPI_LOCK_RWLOCK)
+		KAPI_LOCK_DESC("Process memory map write lock")
+		KAPI_LOCK_ACQUIRED
+		KAPI_LOCK_RELEASED
+		KAPI_LOCK_DESC("Protects VMA modifications during mlockall operation")
+	KAPI_LOCK_END
+
+	KAPI_LOCK(1, "lru_lock", KAPI_LOCK_SPINLOCK)
+		KAPI_LOCK_DESC("Per-memcg LRU list lock")
+		KAPI_LOCK_ACQUIRED
+		KAPI_LOCK_RELEASED
+		KAPI_LOCK_DESC("Taken when moving pages to unevictable list for all locked pages")
+	KAPI_LOCK_END
+
+	.error_count = 5,
+	.param_count = 1,
+	.since_version = "2.0",
+	.signal_count = 2,
+	.side_effect_count = 6,
+	.state_trans_count = 5,
+	.lock_count = 2,
+	.examples = "mlockall(MCL_CURRENT);                    // Lock current mappings\n"
+		    "mlockall(MCL_CURRENT | MCL_FUTURE);       // Lock current and future\n"
+		    "mlockall(MCL_CURRENT | MCL_ONFAULT);      // Lock current on fault",
+	.notes = "Affects all current VMAs and optionally future mappings via mm->def_flags",
+KAPI_END_SPEC;
+
 SYSCALL_DEFINE1(mlockall, int, flags)
 {
 	unsigned long lock_limit;
-- 
2.39.5


^ permalink raw reply related

* [RFC 13/19] mm/mlock: add API specification for munlock
From: Sasha Levin @ 2025-06-14 13:48 UTC (permalink / raw)
  To: linux-kernel; +Cc: linux-api, workflows, tools, Sasha Levin
In-Reply-To: <20250614134858.790460-1-sashal@kernel.org>

Add kernel API specification for the munlock() system call.

Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 mm/mlock.c | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 129 insertions(+)

diff --git a/mm/mlock.c b/mm/mlock.c
index 95ee707c5922f..ef691adc78ad7 100644
--- a/mm/mlock.c
+++ b/mm/mlock.c
@@ -929,6 +929,135 @@ SYSCALL_DEFINE3(mlock2, unsigned long, start, size_t, len, int, flags)
 	return do_mlock(start, len, vm_flags);
 }
 
+
+DEFINE_KERNEL_API_SPEC(sys_munlock)
+	KAPI_DESCRIPTION("Unlock pages in memory")
+	KAPI_LONG_DESC("Unlocks pages in the specified address range, allowing them "
+		       "to be paged out to swap if needed.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	/* Parameters */
+	KAPI_PARAM(0, "start", "unsigned long", "Starting address of memory range to unlock")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_NONE,
+		.constraints = "Rounded down to page boundary",
+	KAPI_PARAM_END
+
+	KAPI_PARAM(1, "len", "size_t", "Length of memory range to unlock in bytes")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		KAPI_PARAM_RANGE(0, LONG_MAX)
+		.constraints = "Rounded up to page boundary",
+	KAPI_PARAM_END
+
+	/* Return specification */
+	KAPI_RETURN("long", "0 on success, negative error code on failure")
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.success_value = 0,
+	KAPI_RETURN_END
+
+	/* Error codes */
+	KAPI_ERROR(0, -ENOMEM, "ENOMEM", "Memory range not mapped", "(Linux 2.6.9 and later) Some of the specified address range does not correspond to mapped pages in the process address space.")
+	KAPI_ERROR(1, -EINTR, "EINTR", "Interrupted by signal", "The operation was interrupted by a signal before completion.")
+	KAPI_ERROR(2, -EINVAL, "EINVAL", "Address overflow", "The result of the addition start+len was less than start (arithmetic overflow).")
+
+	/* Signal specifications */
+	KAPI_SIGNAL(0, 0, "FATAL_SIGNALS", KAPI_SIGNAL_RECEIVE, KAPI_SIGNAL_ACTION_RETURN)
+		KAPI_SIGNAL_CONDITION("Fatal signal pending during mmap_write_lock_killable")
+		KAPI_SIGNAL_DESC("Fatal signals (SIGKILL, SIGTERM, etc.) can interrupt the operation when acquiring mmap_write_lock_killable(), causing -EINTR return")
+		KAPI_SIGNAL_RESTARTABLE
+	KAPI_SIGNAL_END
+
+	/* Side effects */
+	KAPI_SIDE_EFFECT(0, KAPI_EFFECT_MODIFY_STATE,
+			 "process memory",
+			 "Unlocks pages, making them eligible for swapping")
+		KAPI_EFFECT_REVERSIBLE
+		KAPI_EFFECT_CONDITION("Pages were previously locked")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(1, KAPI_EFFECT_MODIFY_STATE,
+			 "mm->locked_vm",
+			 "Decreases process locked memory counter")
+		KAPI_EFFECT_REVERSIBLE
+		KAPI_EFFECT_CONDITION("Pages were counted in locked_vm")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(2, KAPI_EFFECT_MODIFY_STATE,
+			 "VMA flags",
+			 "Clears VM_LOCKED and VM_LOCKONFAULT from affected VMAs")
+		KAPI_EFFECT_REVERSIBLE
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(3, KAPI_EFFECT_MODIFY_STATE,
+			 "page flags",
+			 "Clears PG_mlocked flag from unlocked pages")
+		KAPI_EFFECT_CONDITION("Pages had PG_mlocked set")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(4, KAPI_EFFECT_MODIFY_STATE,
+			 "LRU lists",
+			 "Moves pages from unevictable to appropriate LRU list")
+		KAPI_EFFECT_CONDITION("Pages were on unevictable list")
+	KAPI_SIDE_EFFECT_END
+
+	/* State transitions */
+	KAPI_STATE_TRANS(0, "memory pages",
+			 "locked in RAM", "swappable",
+			 "Pages become eligible for swap out")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(1, "VMA flags",
+			 "VM_LOCKED set", "VM_LOCKED cleared",
+			 "Virtual memory areas no longer marked as locked")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(2, "page residency",
+			 "guaranteed resident", "may be swapped",
+			 "Pages can now be evicted under memory pressure")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(3, "process statistics",
+			 "locked memory accounted", "normal memory accounting",
+			 "Memory no longer counted against RLIMIT_MEMLOCK")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(4, "page LRU status",
+			 "unevictable list", "active/inactive list",
+			 "Pages moved to normal LRU lists for reclaim")
+		KAPI_STATE_TRANS_COND("Pages were mlocked")
+	KAPI_STATE_TRANS_END
+
+	/* Locking information */
+	KAPI_LOCK(0, "mmap_lock", KAPI_LOCK_RWLOCK)
+		KAPI_LOCK_DESC("Process memory map write lock")
+		KAPI_LOCK_ACQUIRED
+		KAPI_LOCK_RELEASED
+		KAPI_LOCK_DESC("Protects VMA modifications during unlock operation")
+	KAPI_LOCK_END
+
+	KAPI_LOCK(1, "lru_lock", KAPI_LOCK_SPINLOCK)
+		KAPI_LOCK_DESC("Per-memcg LRU list lock")
+		KAPI_LOCK_ACQUIRED
+		KAPI_LOCK_RELEASED
+		KAPI_LOCK_DESC("Taken when moving pages from unevictable to normal LRU lists")
+	KAPI_LOCK_END
+
+	.error_count = 3,
+	.param_count = 2,
+	.since_version = "2.0",
+	.signal_count = 1,
+	.side_effect_count = 5,
+	.state_trans_count = 5,
+	.lock_count = 2,
+	.examples = "munlock(addr, 4096);  // Unlock one page\n"
+		    "munlock(addr, len);   // Unlock range of pages",
+	.notes = "No special permissions required to unlock memory",
+KAPI_END_SPEC;
+
 SYSCALL_DEFINE2(munlock, unsigned long, start, size_t, len)
 {
 	int ret;
-- 
2.39.5


^ permalink raw reply related

* [RFC 14/19] mm/mlock: add API specification for munlockall
From: Sasha Levin @ 2025-06-14 13:48 UTC (permalink / raw)
  To: linux-kernel; +Cc: linux-api, workflows, tools, Sasha Levin
In-Reply-To: <20250614134858.790460-1-sashal@kernel.org>

Add kernel API specification for the munlockall() system call.

Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 mm/mlock.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 120 insertions(+)

diff --git a/mm/mlock.c b/mm/mlock.c
index ef691adc78ad7..80f51e932aa95 100644
--- a/mm/mlock.c
+++ b/mm/mlock.c
@@ -1299,6 +1299,126 @@ SYSCALL_DEFINE1(mlockall, int, flags)
 	return ret;
 }
 
+
+DEFINE_KERNEL_API_SPEC(sys_munlockall)
+	KAPI_DESCRIPTION("Unlock all process pages")
+	KAPI_LONG_DESC("Unlocks all pages mapped into the process address space and "
+		       "clears the MCL_FUTURE flag if set.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	/* No parameters - this is a SYSCALL_DEFINE0 */
+	.param_count = 0,
+
+	/* Return specification */
+	KAPI_RETURN("long", "0 on success, negative error code on failure")
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.success_value = 0,
+	KAPI_RETURN_END
+
+	/* Error codes */
+	KAPI_ERROR(0, -EINTR, "EINTR", "Interrupted by signal", "The operation was interrupted by a signal before completion.")
+	KAPI_ERROR(1, -ENOMEM, "ENOMEM", "Memory operation failed", "Failed to modify memory mappings (should not normally occur).")
+
+	/* Signal specifications */
+	KAPI_SIGNAL(0, 0, "FATAL_SIGNALS", KAPI_SIGNAL_RECEIVE, KAPI_SIGNAL_ACTION_RETURN)
+		KAPI_SIGNAL_CONDITION("Fatal signal pending during mmap_write_lock_killable")
+		KAPI_SIGNAL_DESC("Fatal signals (SIGKILL, SIGTERM, etc.) can interrupt the operation when acquiring mmap_write_lock_killable(), causing -EINTR return")
+		KAPI_SIGNAL_RESTARTABLE
+	KAPI_SIGNAL_END
+
+	/* Side effects */
+	KAPI_SIDE_EFFECT(0, KAPI_EFFECT_MODIFY_STATE,
+			 "all process memory",
+			 "Unlocks all pages, making entire address space swappable")
+		KAPI_EFFECT_REVERSIBLE
+		KAPI_EFFECT_CONDITION("Process had locked pages")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(1, KAPI_EFFECT_MODIFY_STATE,
+			 "mm->def_flags",
+			 "Clears VM_LOCKED from default flags for future mappings")
+		KAPI_EFFECT_REVERSIBLE
+		KAPI_EFFECT_CONDITION("MCL_FUTURE was previously set")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(2, KAPI_EFFECT_MODIFY_STATE,
+			 "mm->locked_vm",
+			 "Resets process locked memory counter to zero")
+		KAPI_EFFECT_REVERSIBLE
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(3, KAPI_EFFECT_MODIFY_STATE,
+			 "all VMA flags",
+			 "Clears VM_LOCKED and VM_LOCKONFAULT from all VMAs")
+		KAPI_EFFECT_REVERSIBLE
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(4, KAPI_EFFECT_MODIFY_STATE,
+			 "page flags",
+			 "Clears PG_mlocked flag from all locked pages")
+		KAPI_EFFECT_CONDITION("Pages had PG_mlocked set")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(5, KAPI_EFFECT_MODIFY_STATE,
+			 "LRU lists",
+			 "Moves all pages from unevictable to normal LRU lists")
+		KAPI_EFFECT_CONDITION("Pages were on unevictable list")
+	KAPI_SIDE_EFFECT_END
+
+	/* State transitions */
+	KAPI_STATE_TRANS(0, "all memory pages",
+			 "locked in RAM", "swappable",
+			 "All pages in process become eligible for swap out")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(1, "future mappings",
+			 "auto-locked", "normal",
+			 "New mappings will no longer be automatically locked")
+		KAPI_STATE_TRANS_COND("MCL_FUTURE was set")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(2, "all VMA flags",
+			 "VM_LOCKED set", "VM_LOCKED cleared",
+			 "All virtual memory areas no longer marked as locked")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(3, "process statistics",
+			 "all memory locked", "no memory locked",
+			 "Entire locked memory accounting reset to zero")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(4, "page LRU status",
+			 "unevictable list", "active/inactive list",
+			 "All pages moved to normal LRU lists for reclaim")
+		KAPI_STATE_TRANS_COND("Pages were mlocked")
+	KAPI_STATE_TRANS_END
+
+	/* Locking information */
+	KAPI_LOCK(0, "mmap_lock", KAPI_LOCK_RWLOCK)
+		KAPI_LOCK_DESC("Process memory map write lock")
+		KAPI_LOCK_ACQUIRED
+		KAPI_LOCK_RELEASED
+		KAPI_LOCK_DESC("Protects VMA modifications during munlockall operation")
+	KAPI_LOCK_END
+
+	KAPI_LOCK(1, "lru_lock", KAPI_LOCK_SPINLOCK)
+		KAPI_LOCK_DESC("Per-memcg LRU list lock")
+		KAPI_LOCK_ACQUIRED
+		KAPI_LOCK_RELEASED
+		KAPI_LOCK_DESC("Taken when moving all pages from unevictable to normal LRU lists")
+	KAPI_LOCK_END
+
+	.error_count = 2,
+	.since_version = "2.0",
+	.signal_count = 1,
+	.side_effect_count = 6,
+	.state_trans_count = 5,
+	.lock_count = 2,
+	.examples = "munlockall();  // Unlock all pages",
+	.notes = "Clears VM_LOCKED and VM_LOCKONFAULT from all VMAs and mm->def_flags",
+KAPI_END_SPEC;
+
 SYSCALL_DEFINE0(munlockall)
 {
 	int ret;
-- 
2.39.5


^ permalink raw reply related

* [RFC 15/19] kernel/api: add debugfs interface for kernel API specifications
From: Sasha Levin @ 2025-06-14 13:48 UTC (permalink / raw)
  To: linux-kernel; +Cc: linux-api, workflows, tools, Sasha Levin
In-Reply-To: <20250614134858.790460-1-sashal@kernel.org>

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>
---
 kernel/api/Kconfig        |  20 +++
 kernel/api/Makefile       |   5 +-
 kernel/api/kapi_debugfs.c | 340 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 364 insertions(+), 1 deletion(-)
 create mode 100644 kernel/api/kapi_debugfs.c

diff --git a/kernel/api/Kconfig b/kernel/api/Kconfig
index fde25ec70e134..d2754b21acc43 100644
--- a/kernel/api/Kconfig
+++ b/kernel/api/Kconfig
@@ -33,3 +33,23 @@ config KAPI_RUNTIME_CHECKS
 	  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
+	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.
diff --git a/kernel/api/Makefile b/kernel/api/Makefile
index 4120ded7e5cf1..07b8c007ec156 100644
--- a/kernel/api/Makefile
+++ b/kernel/api/Makefile
@@ -4,4 +4,7 @@
 #
 
 # Core API specification framework
-obj-$(CONFIG_KAPI_SPEC)		+= kernel_api_spec.o
\ No newline at end of file
+obj-$(CONFIG_KAPI_SPEC)		+= kernel_api_spec.o
+
+# Debugfs interface for kernel API specs
+obj-$(CONFIG_KAPI_SPEC_DEBUGFS)	+= kapi_debugfs.o
\ No newline at end of file
diff --git a/kernel/api/kapi_debugfs.c b/kernel/api/kapi_debugfs.c
new file mode 100644
index 0000000000000..bf65ea6a49205
--- /dev/null
+++ b/kernel/api/kapi_debugfs.c
@@ -0,0 +1,340 @@
+// 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 *param_type_str(enum kapi_param_type type)
+{
+	switch (type) {
+	case KAPI_TYPE_INT: return "int";
+	case KAPI_TYPE_UINT: return "uint";
+	case KAPI_TYPE_PTR: return "ptr";
+	case KAPI_TYPE_STRUCT: return "struct";
+	case KAPI_TYPE_UNION: return "union";
+	case KAPI_TYPE_ARRAY: return "array";
+	case KAPI_TYPE_FD: return "fd";
+	case KAPI_TYPE_ENUM: return "enum";
+	case KAPI_TYPE_USER_PTR: return "user_ptr";
+	case KAPI_TYPE_PATH: return "path";
+	case KAPI_TYPE_FUNC_PTR: return "func_ptr";
+	case KAPI_TYPE_CUSTOM: return "custom";
+	default: return "unknown";
+	}
+}
+
+/* Helper to print parameter flags */
+static void print_param_flags(struct seq_file *m, u32 flags)
+{
+	seq_printf(m, "    flags: ");
+	if (flags & KAPI_PARAM_IN) seq_printf(m, "IN ");
+	if (flags & KAPI_PARAM_OUT) seq_printf(m, "OUT ");
+	if (flags & KAPI_PARAM_INOUT) seq_printf(m, "INOUT ");
+	if (flags & KAPI_PARAM_OPTIONAL) seq_printf(m, "OPTIONAL ");
+	if (flags & KAPI_PARAM_CONST) seq_printf(m, "CONST ");
+	if (flags & KAPI_PARAM_USER) seq_printf(m, "USER ");
+	if (flags & KAPI_PARAM_VOLATILE) seq_printf(m, "VOLATILE ");
+	if (flags & KAPI_PARAM_DMA) seq_printf(m, "DMA ");
+	if (flags & KAPI_PARAM_ALIGNED) seq_printf(m, "ALIGNED ");
+	seq_printf(m, "\n");
+}
+
+/* Helper to print context flags */
+static void print_context_flags(struct seq_file *m, u32 flags)
+{
+	seq_printf(m, "Context flags: ");
+	if (flags & KAPI_CTX_PROCESS) seq_printf(m, "PROCESS ");
+	if (flags & KAPI_CTX_HARDIRQ) seq_printf(m, "HARDIRQ ");
+	if (flags & KAPI_CTX_SOFTIRQ) seq_printf(m, "SOFTIRQ ");
+	if (flags & KAPI_CTX_NMI) seq_printf(m, "NMI ");
+	if (flags & KAPI_CTX_SLEEPABLE) seq_printf(m, "SLEEPABLE ");
+	if (flags & KAPI_CTX_ATOMIC) seq_printf(m, "ATOMIC ");
+	if (flags & KAPI_CTX_PREEMPT_DISABLED) seq_printf(m, "PREEMPT_DISABLED ");
+	if (flags & KAPI_CTX_IRQ_DISABLED) seq_printf(m, "IRQ_DISABLED ");
+	seq_printf(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_printf(m, "Kernel API Specification\n");
+	seq_printf(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 (strlen(spec->long_description) > 0)
+		seq_printf(m, "Long description: %s\n", spec->long_description);
+
+	/* Context */
+	print_context_flags(m, spec->context_flags);
+	seq_printf(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++) {
+			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 (strlen(param->description) > 0)
+				seq_printf(m, "    description: %s\n", 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_printf(m, "    constraints:\n");
+				switch (param->constraint_type) {
+				case KAPI_CONSTRAINT_RANGE:
+					seq_printf(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_printf(m, "      type: mask\n");
+					seq_printf(m, "      valid_bits: 0x%llx\n", param->valid_mask);
+					break;
+				case KAPI_CONSTRAINT_ENUM:
+					seq_printf(m, "      type: enum\n");
+					seq_printf(m, "      count: %u\n", param->enum_count);
+					break;
+				case KAPI_CONSTRAINT_CUSTOM:
+					seq_printf(m, "      type: custom\n");
+					if (strlen(param->constraints) > 0)
+						seq_printf(m, "      description: %s\n",
+							   param->constraints);
+					break;
+				default:
+					break;
+				}
+			}
+			seq_printf(m, "\n");
+		}
+	}
+
+	/* Return value */
+	seq_printf(m, "Return value:\n");
+	seq_printf(m, "  type: %s\n", spec->return_spec.type_name);
+	if (strlen(spec->return_spec.description) > 0)
+		seq_printf(m, "  description: %s\n", 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_printf(m, "  success: valid file descriptor (>= 0)\n");
+		break;
+	case KAPI_RETURN_ERROR_CHECK:
+		seq_printf(m, "  success: error check\n");
+		break;
+	case KAPI_RETURN_CUSTOM:
+		seq_printf(m, "  success: custom check\n");
+		break;
+	default:
+		break;
+	}
+	seq_printf(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++) {
+			struct kapi_error_spec *err = &spec->errors[i];
+			seq_printf(m, "  %s (%d): %s\n",
+				   err->name, err->error_code, err->description);
+			if (strlen(err->condition) > 0)
+				seq_printf(m, "    condition: %s\n", err->condition);
+		}
+		seq_printf(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++) {
+			struct kapi_lock_spec *lock = &spec->locks[i];
+			const char *type_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;
+			}
+			seq_printf(m, "  %s (%s): %s\n",
+				   lock->lock_name, type_str, lock->description);
+			if (lock->acquired)
+				seq_printf(m, "    acquired by function\n");
+			if (lock->released)
+				seq_printf(m, "    released by function\n");
+		}
+		seq_printf(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++) {
+			seq_printf(m, "  - %s\n", spec->constraints[i].description);
+		}
+		seq_printf(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++) {
+			struct kapi_signal_spec *sig = &spec->signals[i];
+			seq_printf(m, "  %s (%d):\n", sig->signal_name, sig->signal_num);
+			seq_printf(m, "    direction: ");
+			if (sig->direction & KAPI_SIGNAL_SEND) seq_printf(m, "send ");
+			if (sig->direction & KAPI_SIGNAL_RECEIVE) seq_printf(m, "receive ");
+			if (sig->direction & KAPI_SIGNAL_HANDLE) seq_printf(m, "handle ");
+			if (sig->direction & KAPI_SIGNAL_BLOCK) seq_printf(m, "block ");
+			if (sig->direction & KAPI_SIGNAL_IGNORE) seq_printf(m, "ignore ");
+			seq_printf(m, "\n");
+			seq_printf(m, "    action: ");
+			switch (sig->action) {
+			case KAPI_SIGNAL_ACTION_DEFAULT: seq_printf(m, "default"); break;
+			case KAPI_SIGNAL_ACTION_TERMINATE: seq_printf(m, "terminate"); break;
+			case KAPI_SIGNAL_ACTION_COREDUMP: seq_printf(m, "coredump"); break;
+			case KAPI_SIGNAL_ACTION_STOP: seq_printf(m, "stop"); break;
+			case KAPI_SIGNAL_ACTION_CONTINUE: seq_printf(m, "continue"); break;
+			case KAPI_SIGNAL_ACTION_CUSTOM: seq_printf(m, "custom"); break;
+			case KAPI_SIGNAL_ACTION_RETURN: seq_printf(m, "return"); break;
+			case KAPI_SIGNAL_ACTION_RESTART: seq_printf(m, "restart"); break;
+			default: seq_printf(m, "unknown"); break;
+			}
+			seq_printf(m, "\n");
+			if (strlen(sig->description) > 0)
+				seq_printf(m, "    description: %s\n", sig->description);
+		}
+		seq_printf(m, "\n");
+	}
+
+	/* Additional info */
+	if (strlen(spec->examples) > 0) {
+		seq_printf(m, "Examples:\n%s\n\n", spec->examples);
+	}
+	if (strlen(spec->notes) > 0) {
+		seq_printf(m, "Notes:\n%s\n\n", spec->notes);
+	}
+	if (strlen(spec->since_version) > 0) {
+		seq_printf(m, "Since: %s\n", spec->since_version);
+	}
+	if (spec->deprecated) {
+		seq_printf(m, "DEPRECATED");
+		if (strlen(spec->replacement) > 0)
+			seq_printf(m, " - use %s instead", spec->replacement);
+		seq_printf(m, "\n");
+	}
+
+	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 */
+static int kapi_list_show(struct seq_file *m, void *v)
+{
+	struct kernel_api_spec *spec;
+	int count = 0;
+
+	seq_printf(m, "Available Kernel API Specifications\n");
+	seq_printf(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;
+}
+
+static void __exit kapi_debugfs_exit(void)
+{
+	debugfs_remove_recursive(kapi_debugfs_root);
+}
+
+/* Initialize as part of kernel, not as a module */
+fs_initcall(kapi_debugfs_init);
\ No newline at end of file
-- 
2.39.5


^ permalink raw reply related

* [RFC 16/19] kernel/api: add IOCTL specification infrastructure
From: Sasha Levin @ 2025-06-14 13:48 UTC (permalink / raw)
  To: linux-kernel; +Cc: linux-api, workflows, tools, Sasha Levin
In-Reply-To: <20250614134858.790460-1-sashal@kernel.org>

Add IOCTL API specification support to the kernel API specification
framework. This enables detailed documentation and runtime validation of
IOCTL interfaces.

Key features:
- IOCTL specification structure with command info and parameter details
- Registration/unregistration functions for IOCTL specs
- Helper macros for defining IOCTL specifications
- KAPI_IOCTL_SPEC_DRIVER macro for simplified driver integration
- Runtime validation support with KAPI_DEFINE_FOPS wrapper
- Validation of IOCTL parameters and return values
- Integration with existing kernel API spec infrastructure

The validation framework checks:
- Parameter constraints (ranges, enums, masks)
- User pointer validity
- Buffer size constraints
- Return value correctness against specification

Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 include/linux/ioctl_api_spec.h  | 540 ++++++++++++++++++++++++++++++++
 include/linux/kernel_api_spec.h |   2 +-
 kernel/api/Makefile             |   5 +-
 kernel/api/ioctl_validation.c   | 360 +++++++++++++++++++++
 kernel/api/kernel_api_spec.c    |  90 +++++-
 5 files changed, 994 insertions(+), 3 deletions(-)
 create mode 100644 include/linux/ioctl_api_spec.h
 create mode 100644 kernel/api/ioctl_validation.c

diff --git a/include/linux/ioctl_api_spec.h b/include/linux/ioctl_api_spec.h
new file mode 100644
index 0000000000000..ab3337449ad77
--- /dev/null
+++ b/include/linux/ioctl_api_spec.h
@@ -0,0 +1,540 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ioctl_api_spec.h - IOCTL API specification framework
+ *
+ * Extends the kernel API specification framework to support ioctl validation
+ * and documentation.
+ */
+
+#ifndef _LINUX_IOCTL_API_SPEC_H
+#define _LINUX_IOCTL_API_SPEC_H
+
+#include <linux/kernel_api_spec.h>
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+/* Forward declarations */
+struct file;
+
+/**
+ * struct kapi_ioctl_spec - IOCTL-specific API specification
+ * @api_spec: Base API specification
+ * @cmd: IOCTL command number
+ * @cmd_name: Human-readable command name
+ * @input_size: Size of input structure (0 if none)
+ * @output_size: Size of output structure (0 if none)
+ * @file_ops_name: Name of the file_operations structure
+ */
+struct kapi_ioctl_spec {
+	struct kernel_api_spec api_spec;
+	unsigned int cmd;
+	const char *cmd_name;
+	size_t input_size;
+	size_t output_size;
+	const char *file_ops_name;
+};
+
+/* Registry functions for IOCTL specifications */
+#ifdef CONFIG_KAPI_SPEC
+int kapi_register_ioctl_spec(const struct kapi_ioctl_spec *spec);
+void kapi_unregister_ioctl_spec(unsigned int cmd);
+const struct kapi_ioctl_spec *kapi_get_ioctl_spec(unsigned int cmd);
+
+/* IOCTL validation functions */
+#ifdef CONFIG_KAPI_RUNTIME_CHECKS
+int kapi_validate_ioctl(struct file *filp, unsigned int cmd, void __user *arg);
+int kapi_validate_ioctl_struct(const struct kapi_ioctl_spec *spec,
+			       const void *data, size_t size);
+#else
+static inline int kapi_validate_ioctl(struct file *filp, unsigned int cmd,
+				      void __user *arg)
+{
+	return 0;
+}
+#endif /* CONFIG_KAPI_RUNTIME_CHECKS */
+
+#else /* !CONFIG_KAPI_SPEC */
+static inline int kapi_register_ioctl_spec(const struct kapi_ioctl_spec *spec)
+{
+	return 0;
+}
+static inline void kapi_unregister_ioctl_spec(unsigned int cmd) {}
+static inline const struct kapi_ioctl_spec *kapi_get_ioctl_spec(unsigned int cmd)
+{
+	return NULL;
+}
+#endif /* CONFIG_KAPI_SPEC */
+
+/* Helper macros for IOCTL specification */
+
+/**
+ * DEFINE_IOCTL_API_SPEC - Start an IOCTL API specification
+ * @name: Unique identifier for the specification
+ * @cmd: IOCTL command number
+ * @cmd_name_str: String name of the command
+ */
+#define DEFINE_IOCTL_API_SPEC(name, cmd, cmd_name_str)			\
+static const struct kapi_ioctl_spec name##_spec = {			\
+	.cmd = cmd,							\
+	.cmd_name = cmd_name_str,					\
+	.api_spec = {							\
+		.name = #name,
+
+/**
+ * KAPI_IOCTL_SIZE - Specify input/output structure sizes
+ * @in_size: Size of input structure
+ * @out_size: Size of output structure
+ */
+#define KAPI_IOCTL_SIZE(in_size, out_size)				\
+	},								\
+	.input_size = in_size,						\
+	.output_size = out_size,
+
+/**
+ * KAPI_IOCTL_FILE_OPS - Specify the file_operations structure name
+ * @ops_name: Name of the file_operations structure
+ */
+#define KAPI_IOCTL_FILE_OPS(ops_name)					\
+	.file_ops_name = #ops_name,
+
+/**
+ * Common IOCTL parameter specifications
+ */
+#define KAPI_IOCTL_PARAM_SIZE							\
+	KAPI_PARAM(0, "size", "__u32", "Size of the structure")		\
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)					\
+		.type = KAPI_TYPE_UINT,						\
+		.constraint_type = KAPI_CONSTRAINT_CUSTOM,			\
+		.constraints = "Must match sizeof(struct)",			\
+	KAPI_PARAM_END
+
+#define KAPI_IOCTL_PARAM_FLAGS							\
+	KAPI_PARAM(1, "flags", "__u32", "Feature flags")			\
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)					\
+		.type = KAPI_TYPE_UINT,						\
+		.constraint_type = KAPI_CONSTRAINT_MASK,			\
+		.valid_mask = 0,	/* 0 means no flags currently */	\
+	KAPI_PARAM_END
+
+/**
+ * KAPI_IOCTL_PARAM_USER_BUF - User buffer parameter
+ * @idx: Parameter index
+ * @name: Parameter name
+ * @desc: Parameter description
+ * @len_idx: Index of the length parameter
+ */
+#define KAPI_IOCTL_PARAM_USER_BUF(idx, name, desc, len_idx)		\
+	KAPI_PARAM(idx, name, "__aligned_u64", desc)			\
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN | KAPI_PARAM_USER_PTR)	\
+		.type = KAPI_TYPE_USER_PTR,				\
+		.size_param_idx = len_idx,				\
+	KAPI_PARAM_END
+
+/**
+ * KAPI_IOCTL_PARAM_USER_OUT_BUF - User output buffer parameter
+ * @idx: Parameter index
+ * @name: Parameter name
+ * @desc: Parameter description
+ * @len_idx: Index of the length parameter
+ */
+#define KAPI_IOCTL_PARAM_USER_OUT_BUF(idx, name, desc, len_idx)	\
+	KAPI_PARAM(idx, name, "__aligned_u64", desc)			\
+		KAPI_PARAM_FLAGS(KAPI_PARAM_OUT | KAPI_PARAM_USER_PTR)	\
+		.type = KAPI_TYPE_USER_PTR,				\
+		.size_param_idx = len_idx,				\
+	KAPI_PARAM_END
+
+/**
+ * KAPI_IOCTL_PARAM_LEN - Buffer length parameter
+ * @idx: Parameter index
+ * @name: Parameter name
+ * @desc: Parameter description
+ * @max_size: Maximum allowed size
+ */
+#define KAPI_IOCTL_PARAM_LEN(idx, name, desc, max_size)		\
+	KAPI_PARAM(idx, name, "__u32", desc)				\
+		KAPI_PARAM_FLAGS(KAPI_PARAM_INOUT)			\
+		.type = KAPI_TYPE_UINT,					\
+		.constraint_type = KAPI_CONSTRAINT_RANGE,		\
+		.min_value = 0,						\
+		.max_value = max_size,					\
+	KAPI_PARAM_END
+
+/* End the IOCTL specification */
+#define KAPI_IOCTL_END_SPEC						\
+};									\
+									\
+static int __init name##_spec_init(void)				\
+{									\
+	return kapi_register_ioctl_spec(&name##_spec);			\
+}									\
+									\
+static void __exit name##_spec_exit(void)				\
+{									\
+	kapi_unregister_ioctl_spec(name##_spec.cmd);			\
+}									\
+									\
+module_init(name##_spec_init);						\
+module_exit(name##_spec_exit);
+
+/* Inline IOCTL specification support */
+
+/* Forward declaration */
+struct fwctl_ucmd;
+
+/**
+ * struct kapi_ioctl_handler - IOCTL handler with inline specification
+ * @spec: IOCTL specification
+ * @handler: Original IOCTL handler function
+ */
+struct kapi_ioctl_handler {
+	struct kapi_ioctl_spec spec;
+	int (*handler)(struct fwctl_ucmd *ucmd);
+};
+
+/**
+ * DEFINE_IOCTL_HANDLER - Define an IOCTL handler with inline specification
+ * @name: Handler name
+ * @cmd: IOCTL command number
+ * @handler_func: Handler function
+ * @struct_type: Structure type for this IOCTL
+ * @last_field: Last field in the structure
+ */
+#define DEFINE_IOCTL_HANDLER(name, cmd, handler_func, struct_type, last_field)	\
+static const struct kapi_ioctl_handler name = {				\
+	.spec = {							\
+		.cmd = cmd,						\
+		.cmd_name = #cmd,					\
+		.input_size = sizeof(struct_type),			\
+		.output_size = sizeof(struct_type),			\
+		.api_spec = {						\
+			.name = #name,
+
+#define KAPI_IOCTL_HANDLER_END						\
+		},							\
+	},								\
+	.handler = handler_func,					\
+}
+
+/**
+ * kapi_ioctl_wrapper - Wrapper function for transparent IOCTL validation
+ * @filp: File pointer
+ * @cmd: IOCTL command
+ * @arg: User argument
+ * @real_ioctl: The real ioctl handler
+ *
+ * This wrapper performs validation before and after the actual IOCTL call
+ */
+static inline long kapi_ioctl_wrapper(struct file *filp, unsigned int cmd,
+				      unsigned long arg,
+				      long (*real_ioctl)(struct file *, unsigned int, unsigned long))
+{
+	long ret;
+
+#ifdef CONFIG_KAPI_RUNTIME_CHECKS
+	/* Pre-validation */
+	ret = kapi_validate_ioctl(filp, cmd, (void __user *)arg);
+	if (ret)
+		return ret;
+#endif
+
+	/* Call the real IOCTL handler */
+	ret = real_ioctl(filp, cmd, arg);
+
+#ifdef CONFIG_KAPI_RUNTIME_CHECKS
+	/* Post-validation could be added here if needed */
+	/* For example, validating output parameters */
+#endif
+
+	return ret;
+}
+
+/**
+ * KAPI_IOCTL_OPS - Define file_operations with transparent validation
+ * @name: Name of the file_operations structure
+ * @real_ioctl: The real ioctl handler function
+ * @... : Other file operation handlers
+ */
+#define KAPI_IOCTL_OPS(name, real_ioctl, ...)				\
+static long name##_validated_ioctl(struct file *filp, unsigned int cmd, \
+				   unsigned long arg)			\
+{									\
+	return kapi_ioctl_wrapper(filp, cmd, arg, real_ioctl);		\
+}									\
+									\
+static const struct file_operations name = {				\
+	.unlocked_ioctl = name##_validated_ioctl,			\
+	__VA_ARGS__							\
+}
+
+/**
+ * KAPI_IOCTL_OP_ENTRY - Define an IOCTL operation table entry with spec
+ * @_ioctl: IOCTL command macro
+ * @_handler: Handler structure (defined with DEFINE_IOCTL_HANDLER)
+ * @_struct: Structure type
+ * @_last: Last field name
+ */
+#define KAPI_IOCTL_OP_ENTRY(_ioctl, _handler, _struct, _last)		\
+	[_IOC_NR(_ioctl) - FWCTL_CMD_BASE] = {				\
+		.size = sizeof(_struct) +				\
+			BUILD_BUG_ON_ZERO(sizeof(union fwctl_ucmd_buffer) < \
+					  sizeof(_struct)),		\
+		.min_size = offsetofend(_struct, _last),		\
+		.ioctl_num = _ioctl,					\
+		.execute = _handler.handler,				\
+	}
+
+/* Helper to register all handlers in a module */
+#define KAPI_REGISTER_IOCTL_HANDLERS(handlers, count)			\
+static int __init kapi_ioctl_handlers_init(void)			\
+{									\
+	int i, ret;							\
+	for (i = 0; i < count; i++) {					\
+		ret = kapi_register_ioctl_spec(&handlers[i].spec);	\
+		if (ret) {						\
+			while (--i >= 0)				\
+				kapi_unregister_ioctl_spec(handlers[i].spec.cmd); \
+			return ret;					\
+		}							\
+	}								\
+	return 0;							\
+}									\
+									\
+static void __exit kapi_ioctl_handlers_exit(void)			\
+{									\
+	int i;								\
+	for (i = 0; i < count; i++)					\
+		kapi_unregister_ioctl_spec(handlers[i].spec.cmd);	\
+}									\
+									\
+module_init(kapi_ioctl_handlers_init);					\
+module_exit(kapi_ioctl_handlers_exit)
+
+/**
+ * KAPI_REGISTER_IOCTL_SPECS - Register an array of IOCTL specifications
+ * @specs: Array of pointers to kapi_ioctl_spec
+ * @count: Number of specifications
+ *
+ * This macro generates init/exit functions to register/unregister
+ * the IOCTL specifications. The functions return 0 on success or
+ * negative error code on failure.
+ *
+ * Usage:
+ *   static const struct kapi_ioctl_spec *my_ioctl_specs[] = {
+ *       &spec1, &spec2, &spec3,
+ *   };
+ *   KAPI_REGISTER_IOCTL_SPECS(my_ioctl_specs, ARRAY_SIZE(my_ioctl_specs))
+ *
+ * Then call the generated functions in your module init/exit:
+ *   ret = kapi_register_##name();
+ *   kapi_unregister_##name();
+ */
+#define KAPI_REGISTER_IOCTL_SPECS(name, specs)				\
+static int kapi_register_##name(void)					\
+{									\
+	int i, ret;							\
+	for (i = 0; i < ARRAY_SIZE(specs); i++) {			\
+		ret = kapi_register_ioctl_spec(specs[i]);		\
+		if (ret) {						\
+			pr_warn("Failed to register IOCTL spec for %s: %d\n", \
+				specs[i]->cmd_name, ret);		\
+			while (--i >= 0)				\
+				kapi_unregister_ioctl_spec(specs[i]->cmd); \
+			return ret;					\
+		}							\
+	}								\
+	pr_info("Registered %zu IOCTL specifications\n", 		\
+		ARRAY_SIZE(specs));					\
+	return 0;							\
+}									\
+									\
+static void kapi_unregister_##name(void)				\
+{									\
+	int i;								\
+	for (i = 0; i < ARRAY_SIZE(specs); i++)			\
+		kapi_unregister_ioctl_spec(specs[i]->cmd);		\
+}
+
+/**
+ * KAPI_DEFINE_IOCTL_SPEC - Define a single IOCTL specification
+ * @name: Name of the specification variable
+ * @cmd: IOCTL command number
+ * @cmd_name: String name of the command
+ * @in_size: Input structure size
+ * @out_size: Output structure size
+ * @fops_name: Name of the file_operations structure
+ *
+ * This macro starts the definition of an IOCTL specification.
+ * It must be followed by the API specification details and
+ * ended with KAPI_END_IOCTL_SPEC.
+ *
+ * Example:
+ *   KAPI_DEFINE_IOCTL_SPEC(my_ioctl_spec, MY_IOCTL, "MY_IOCTL",
+ *                          sizeof(struct my_input), sizeof(struct my_output),
+ *                          "my_fops")
+ *   KAPI_DESCRIPTION("Description here")
+ *   ...
+ *   KAPI_END_IOCTL_SPEC;
+ */
+#define KAPI_DEFINE_IOCTL_SPEC(name, cmd, cmd_name_str, in_size, out_size, fops) \
+static const struct kapi_ioctl_spec name = {				\
+	.cmd = (cmd),							\
+	.cmd_name = cmd_name_str,					\
+	.input_size = in_size,						\
+	.output_size = out_size,					\
+	.file_ops_name = fops,						\
+	.api_spec = {							\
+		.name = #name,
+
+#define KAPI_END_IOCTL_SPEC						\
+	},								\
+}
+
+/**
+ * KAPI_IOCTL_SPEC_DRIVER - Complete IOCTL specification for a driver
+ * @driver_name: Name of the driver (used for logging)
+ * @specs_array: Name of the array containing IOCTL spec pointers
+ *
+ * This macro provides everything needed for IOCTL spec registration:
+ * 1. Generates the specs array declaration
+ * 2. Creates init/exit functions for registration
+ * 3. Provides simple function names to call from module init/exit
+ *
+ * Usage:
+ *   // Define individual specs
+ *   KAPI_DEFINE_IOCTL_SPEC(spec1, ...) ... KAPI_END_IOCTL_SPEC;
+ *   KAPI_DEFINE_IOCTL_SPEC(spec2, ...) ... KAPI_END_IOCTL_SPEC;
+ *
+ *   // Create the driver registration (at end of file)
+ *   KAPI_IOCTL_SPEC_DRIVER("my_driver", {
+ *       &spec1,
+ *       &spec2,
+ *   })
+ *
+ *   // In module init: ret = kapi_ioctl_specs_init();
+ *   // In module exit: kapi_ioctl_specs_exit();
+ */
+#define KAPI_IOCTL_SPEC_DRIVER(driver_name, ...)			\
+static const struct kapi_ioctl_spec *__kapi_ioctl_specs[] = __VA_ARGS__;									\
+									\
+static int __init kapi_ioctl_specs_init(void)				\
+{									\
+	int i, ret;							\
+	for (i = 0; i < ARRAY_SIZE(__kapi_ioctl_specs); i++) {		\
+		ret = kapi_register_ioctl_spec(__kapi_ioctl_specs[i]);	\
+		if (ret) {						\
+			pr_warn("%s: Failed to register %s: %d\n",	\
+				driver_name,				\
+				__kapi_ioctl_specs[i]->cmd_name, ret);	\
+			while (--i >= 0)				\
+				kapi_unregister_ioctl_spec(		\
+					__kapi_ioctl_specs[i]->cmd);	\
+			return ret;					\
+		}							\
+	}								\
+	pr_info("%s: Registered %zu IOCTL specifications\n",		\
+		driver_name, ARRAY_SIZE(__kapi_ioctl_specs));		\
+	return 0;							\
+}									\
+									\
+static void kapi_ioctl_specs_exit(void)				\
+{									\
+	int i;								\
+	for (i = 0; i < ARRAY_SIZE(__kapi_ioctl_specs); i++)		\
+		kapi_unregister_ioctl_spec(__kapi_ioctl_specs[i]->cmd);\
+}
+
+/* Transparent IOCTL validation wrapper support */
+
+#ifdef CONFIG_KAPI_RUNTIME_CHECKS
+
+/**
+ * struct kapi_fops_wrapper - Wrapper for file_operations with validation
+ * @real_fops: Original file_operations
+ * @wrapped_fops: Modified file_operations with validation wrapper
+ * @real_ioctl: Original unlocked_ioctl handler
+ */
+struct kapi_fops_wrapper {
+	const struct file_operations *real_fops;
+	const struct file_operations *wrapped_fops;
+	long (*real_ioctl)(struct file *, unsigned int, unsigned long);
+};
+
+/* Forward declarations */
+long kapi_ioctl_validation_wrapper(struct file *filp, unsigned int cmd,
+				   unsigned long arg);
+void kapi_register_wrapper(struct kapi_fops_wrapper *wrapper);
+
+/**
+ * kapi_wrap_file_operations - Wrap file_operations for transparent validation
+ * @fops: Original file_operations to wrap
+ *
+ * This creates a wrapper that intercepts ioctl calls for validation.
+ * The wrapper is stored in a static variable in the calling module.
+ */
+#define kapi_wrap_file_operations(fops)					\
+({										\
+	static struct kapi_fops_wrapper __kapi_wrapper = {		\
+		.real_fops = &(fops),					\
+	};								\
+	if (__kapi_wrapper.real_fops->unlocked_ioctl) {		\
+		__kapi_wrapper.wrapped_fops = (fops);			\
+		__kapi_wrapper.real_ioctl = (fops).unlocked_ioctl;	\
+		__kapi_wrapper.wrapped_fops.unlocked_ioctl = 		\
+			kapi_ioctl_validation_wrapper;			\
+		&__kapi_wrapper.wrapped_fops;				\
+	} else {							\
+		&(fops);						\
+	}								\
+})
+
+
+/**
+ * KAPI_DEFINE_FOPS - Define file_operations with automatic validation
+ * @name: Name of the file_operations structure
+ * @... : File operation handlers
+ *
+ * Usage:
+ *   KAPI_DEFINE_FOPS(my_fops,
+ *       .owner = THIS_MODULE,
+ *       .open = my_open,
+ *       .unlocked_ioctl = my_ioctl,
+ *   );
+ *
+ * Then in your module init, call: kapi_init_fops_##name()
+ */
+#define KAPI_DEFINE_FOPS(name, ...)					\
+static const struct file_operations __kapi_real_##name = {		\
+	__VA_ARGS__							\
+};									\
+static struct file_operations __kapi_wrapped_##name;			\
+static struct kapi_fops_wrapper __kapi_wrapper_##name;			\
+static const struct file_operations *name;				\
+static void kapi_init_fops_##name(void)				\
+{									\
+	if (__kapi_real_##name.unlocked_ioctl) {			\
+		__kapi_wrapped_##name = __kapi_real_##name;		\
+		__kapi_wrapper_##name.real_fops = &__kapi_real_##name;	\
+		__kapi_wrapper_##name.wrapped_fops = &__kapi_wrapped_##name; \
+		__kapi_wrapper_##name.real_ioctl = 			\
+			__kapi_real_##name.unlocked_ioctl;		\
+		__kapi_wrapped_##name.unlocked_ioctl = 		\
+			kapi_ioctl_validation_wrapper;			\
+		kapi_register_wrapper(&__kapi_wrapper_##name);		\
+		name = &__kapi_wrapped_##name;				\
+	} else {							\
+		name = &__kapi_real_##name;				\
+	}								\
+}
+
+#else /* !CONFIG_KAPI_RUNTIME_CHECKS */
+
+/* When runtime checks are disabled, no wrapping occurs */
+#define kapi_wrap_file_operations(fops) (&(fops))
+#define KAPI_DEFINE_FOPS(name, ...) \
+static const struct file_operations name = { __VA_ARGS__ }; \
+static inline void kapi_init_fops_##name(void) {}
+
+#endif /* CONFIG_KAPI_RUNTIME_CHECKS */
+
+#endif /* _LINUX_IOCTL_API_SPEC_H */
\ No newline at end of file
diff --git a/include/linux/kernel_api_spec.h b/include/linux/kernel_api_spec.h
index 04df5892bc6d6..9590fe3bb007c 100644
--- a/include/linux/kernel_api_spec.h
+++ b/include/linux/kernel_api_spec.h
@@ -849,7 +849,7 @@ struct kernel_api_spec {
 #define KAPI_PARAM_OUT		(KAPI_PARAM_OUT)
 #define KAPI_PARAM_INOUT	(KAPI_PARAM_IN | KAPI_PARAM_OUT)
 #define KAPI_PARAM_OPTIONAL	(KAPI_PARAM_OPTIONAL)
-#define KAPI_PARAM_USER_PTR	(KAPI_PARAM_USER | KAPI_PARAM_PTR)
+#define KAPI_PARAM_USER_PTR	(KAPI_PARAM_USER)
 
 /* Validation and runtime checking */
 
diff --git a/kernel/api/Makefile b/kernel/api/Makefile
index 07b8c007ec156..9d2daf38f0029 100644
--- a/kernel/api/Makefile
+++ b/kernel/api/Makefile
@@ -6,5 +6,8 @@
 # Core API specification framework
 obj-$(CONFIG_KAPI_SPEC)		+= kernel_api_spec.o
 
+# IOCTL validation framework
+obj-$(CONFIG_KAPI_SPEC)		+= ioctl_validation.o
+
 # Debugfs interface for kernel API specs
-obj-$(CONFIG_KAPI_SPEC_DEBUGFS)	+= kapi_debugfs.o
\ No newline at end of file
+obj-$(CONFIG_KAPI_SPEC_DEBUGFS)	+= kapi_debugfs.o
diff --git a/kernel/api/ioctl_validation.c b/kernel/api/ioctl_validation.c
new file mode 100644
index 0000000000000..25f6db8cb33eb
--- /dev/null
+++ b/kernel/api/ioctl_validation.c
@@ -0,0 +1,360 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ioctl_validation.c - Runtime validation for IOCTL API specifications
+ *
+ * Provides functions to validate ioctl parameters against their specifications
+ * at runtime when CONFIG_KAPI_RUNTIME_CHECKS is enabled.
+ */
+
+#include <linux/kernel.h>
+#include <linux/ioctl_api_spec.h>
+#include <linux/kernel_api_spec.h>
+#include <linux/uaccess.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/container_of.h>
+#include <linux/export.h>
+#include <uapi/fwctl/fwctl.h>
+
+#ifdef CONFIG_KAPI_RUNTIME_CHECKS
+
+/**
+ * kapi_validate_ioctl - Validate an ioctl call against its specification
+ * @filp: File pointer
+ * @cmd: IOCTL command
+ * @arg: IOCTL argument
+ *
+ * Return: 0 if valid, negative errno if validation fails
+ */
+int kapi_validate_ioctl(struct file *filp, unsigned int cmd, void __user *arg)
+{
+	const struct kapi_ioctl_spec *spec;
+	const struct kernel_api_spec *api_spec;
+	void *data = NULL;
+	size_t copy_size;
+	int ret = 0;
+	int i;
+
+	spec = kapi_get_ioctl_spec(cmd);
+	if (!spec)
+		return 0; /* No spec, can't validate */
+
+	api_spec = &spec->api_spec;
+
+	pr_debug("kapi: validating ioctl %s (0x%x)\n", spec->cmd_name, cmd);
+
+	/* Check if this ioctl requires specific capabilities */
+	if (api_spec->param_count > 0) {
+		for (i = 0; i < api_spec->param_count; i++) {
+			const struct kapi_param_spec *param = &api_spec->params[i];
+
+			/* Check for capability requirements in constraints */
+			if (param->constraint_type == KAPI_CONSTRAINT_CUSTOM &&
+			    param->constraints[0] && strstr(param->constraints, "CAP_")) {
+				/* Could add capability checks here if needed */
+			}
+		}
+	}
+
+	/* For ioctls with input/output structures, copy and validate */
+	if (spec->input_size > 0 || spec->output_size > 0) {
+		copy_size = max(spec->input_size, spec->output_size);
+
+		/* Allocate temporary buffer for validation */
+		data = kzalloc(copy_size, GFP_KERNEL);
+		if (!data)
+			return -ENOMEM;
+
+		/* Copy input data from user */
+		if (spec->input_size > 0) {
+			ret = copy_from_user(data, arg, spec->input_size);
+			if (ret) {
+				ret = -EFAULT;
+				goto out;
+			}
+		}
+
+		/* Validate structure fields */
+		ret = kapi_validate_ioctl_struct(spec, data, copy_size);
+		if (ret)
+			goto out;
+	}
+
+out:
+	kfree(data);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(kapi_validate_ioctl);
+
+/**
+ * struct field_offset - Maps structure fields to their offsets
+ * @field_idx: Parameter index
+ * @offset: Offset in structure
+ * @size: Size of field
+ */
+struct field_offset {
+	int field_idx;
+	size_t offset;
+	size_t size;
+};
+
+/* Common ioctl structure layouts */
+static const struct field_offset fwctl_info_offsets[] = {
+	{0, 0, sizeof(u32)},  /* size */
+	{1, 4, sizeof(u32)},  /* flags */
+	{2, 8, sizeof(u32)},  /* out_device_type */
+	{3, 12, sizeof(u32)}, /* device_data_len */
+	{4, 16, sizeof(u64)}, /* out_device_data */
+};
+
+static const struct field_offset fwctl_rpc_offsets[] = {
+	{0, 0, sizeof(u32)},  /* size */
+	{1, 4, sizeof(u32)},  /* scope */
+	{2, 8, sizeof(u32)},  /* in_len */
+	{3, 12, sizeof(u32)}, /* out_len */
+	{4, 16, sizeof(u64)}, /* in */
+	{5, 24, sizeof(u64)}, /* out */
+};
+
+/**
+ * get_field_offsets - Get field offset information for an ioctl
+ * @cmd: IOCTL command
+ * @count: Returns number of fields
+ *
+ * Return: Array of field offsets or NULL
+ */
+static const struct field_offset *get_field_offsets(unsigned int cmd, int *count)
+{
+	switch (cmd) {
+	case FWCTL_INFO:
+		*count = ARRAY_SIZE(fwctl_info_offsets);
+		return fwctl_info_offsets;
+	case FWCTL_RPC:
+		*count = ARRAY_SIZE(fwctl_rpc_offsets);
+		return fwctl_rpc_offsets;
+	default:
+		*count = 0;
+		return NULL;
+	}
+}
+
+/**
+ * extract_field_value - Extract a field value from structure
+ * @data: Structure data
+ * @param: Parameter specification
+ * @offset_info: Field offset information
+ *
+ * Return: Field value or 0 on error
+ */
+static s64 extract_field_value(const void *data,
+			       const struct kapi_param_spec *param,
+			       const struct field_offset *offset_info)
+{
+	const void *field = data + offset_info->offset;
+
+	switch (param->type) {
+	case KAPI_TYPE_UINT:
+		if (offset_info->size == sizeof(u32))
+			return *(u32 *)field;
+		else if (offset_info->size == sizeof(u64))
+			return *(u64 *)field;
+		break;
+	case KAPI_TYPE_INT:
+		if (offset_info->size == sizeof(s32))
+			return *(s32 *)field;
+		else if (offset_info->size == sizeof(s64))
+			return *(s64 *)field;
+		break;
+	case KAPI_TYPE_USER_PTR:
+		/* User pointers are typically u64 in ioctl structures */
+		return (s64)(*(u64 *)field);
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+/**
+ * kapi_validate_ioctl_struct - Validate an ioctl structure against specification
+ * @spec: IOCTL specification
+ * @data: Structure data
+ * @size: Size of the structure
+ *
+ * Return: 0 if valid, negative errno if validation fails
+ */
+int kapi_validate_ioctl_struct(const struct kapi_ioctl_spec *spec,
+				const void *data, size_t size)
+{
+	const struct kernel_api_spec *api_spec = &spec->api_spec;
+	const struct field_offset *offsets;
+	int offset_count;
+	int i, j;
+
+	if (!spec || !data)
+		return -EINVAL;
+
+	/* Get field offset information for this ioctl */
+	offsets = get_field_offsets(spec->cmd, &offset_count);
+
+	/* Validate each parameter in the structure */
+	for (i = 0; i < api_spec->param_count && i < KAPI_MAX_PARAMS; i++) {
+		const struct kapi_param_spec *param = &api_spec->params[i];
+		const struct field_offset *offset_info = NULL;
+		s64 value;
+
+		/* Find offset information for this parameter */
+		if (offsets) {
+			for (j = 0; j < offset_count; j++) {
+				if (offsets[j].field_idx == i) {
+					offset_info = &offsets[j];
+					break;
+				}
+			}
+		}
+
+		if (!offset_info) {
+			pr_debug("kapi: no offset info for param %d\n", i);
+			continue;
+		}
+
+		/* Extract field value */
+		value = extract_field_value(data, param, offset_info);
+
+		/* Special handling for user pointers */
+		if (param->type == KAPI_TYPE_USER_PTR) {
+			/* Check if pointer looks valid (non-kernel address) */
+			if (value && (value >= TASK_SIZE)) {
+				pr_warn("ioctl %s: parameter %s has kernel pointer %llx\n",
+					spec->cmd_name, param->name, value);
+				return -EINVAL;
+			}
+
+			/* For size validation, check against size_param_idx */
+			if (param->size_param_idx >= 0 &&
+			    param->size_param_idx < offset_count) {
+				const struct field_offset *size_offset = NULL;
+
+				for (j = 0; j < offset_count; j++) {
+					if (offsets[j].field_idx == param->size_param_idx) {
+						size_offset = &offsets[j];
+						break;
+					}
+				}
+
+				if (size_offset) {
+					s64 buf_size = extract_field_value(data,
+						&api_spec->params[param->size_param_idx],
+						size_offset);
+
+					/* Validate buffer size constraints */
+					if (buf_size > 0 &&
+					    !kapi_validate_param(&api_spec->params[param->size_param_idx],
+								 buf_size)) {
+						pr_warn("ioctl %s: buffer size %lld invalid for %s\n",
+							spec->cmd_name, buf_size, param->name);
+						return -EINVAL;
+					}
+				}
+			}
+		} else {
+			/* Validate using the standard parameter validation */
+			if (!kapi_validate_param(param, value)) {
+				pr_warn("ioctl %s: parameter %s validation failed (value=%lld)\n",
+					spec->cmd_name, param->name, value);
+				return -EINVAL;
+			}
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(kapi_validate_ioctl_struct);
+
+/* Global registry of wrappers - in real implementation this would be per-module */
+static struct kapi_fops_wrapper *kapi_global_wrapper;
+
+/**
+ * kapi_register_wrapper - Register a wrapper (called from macro)
+ * @wrapper: Wrapper to register
+ */
+void kapi_register_wrapper(struct kapi_fops_wrapper *wrapper)
+{
+	/* Simple implementation - just store the last one */
+	kapi_global_wrapper = wrapper;
+}
+EXPORT_SYMBOL_GPL(kapi_register_wrapper);
+
+/**
+ * kapi_find_wrapper - Find wrapper for given file_operations
+ * @fops: File operations structure to check
+ *
+ * Return: Wrapper structure or NULL if not wrapped
+ */
+static struct kapi_fops_wrapper *kapi_find_wrapper(const struct file_operations *fops)
+{
+	/* Simple implementation - just return the global one if it matches */
+	if (kapi_global_wrapper && kapi_global_wrapper->wrapped_fops == fops)
+		return kapi_global_wrapper;
+	return NULL;
+}
+
+/**
+ * kapi_ioctl_validation_wrapper - Wrapper function for transparent validation
+ * @filp: File pointer
+ * @cmd: IOCTL command
+ * @arg: User argument
+ *
+ * This function is called instead of the real ioctl handler when validation
+ * is enabled. It performs pre-validation, calls the real handler, then does
+ * post-validation.
+ *
+ * Return: Result from the real ioctl handler or error
+ */
+long kapi_ioctl_validation_wrapper(struct file *filp, unsigned int cmd,
+				   unsigned long arg)
+{
+	struct kapi_fops_wrapper *wrapper;
+	const struct kapi_ioctl_spec *spec;
+	long ret;
+
+	wrapper = kapi_find_wrapper(filp->f_op);
+	if (!wrapper || !wrapper->real_ioctl)
+		return -EINVAL;
+
+	/* Pre-validation */
+	spec = kapi_get_ioctl_spec(cmd);
+	if (spec) {
+		ret = kapi_validate_ioctl(filp, cmd, (void __user *)arg);
+		if (ret)
+			return ret;
+	}
+
+	/* Call the real ioctl handler */
+	ret = wrapper->real_ioctl(filp, cmd, arg);
+
+	/* Post-validation - check return value against spec */
+	if (spec && spec->api_spec.error_count > 0) {
+		/* Validate that returned error is in the spec */
+		if (ret < 0) {
+			int i;
+			bool found = false;
+			for (i = 0; i < spec->api_spec.error_count; i++) {
+				if (ret == spec->api_spec.errors[i].error_code) {
+					found = true;
+					break;
+				}
+			}
+			if (!found) {
+				pr_warn("IOCTL %s returned unexpected error %ld\n",
+					spec->cmd_name, ret);
+			}
+		}
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(kapi_ioctl_validation_wrapper);
+
+#endif /* CONFIG_KAPI_RUNTIME_CHECKS */
diff --git a/kernel/api/kernel_api_spec.c b/kernel/api/kernel_api_spec.c
index 29c0c84d87f7c..70e16a49f5dbe 100644
--- a/kernel/api/kernel_api_spec.c
+++ b/kernel/api/kernel_api_spec.c
@@ -1166,4 +1166,92 @@ static int __init kapi_debugfs_init(void)
 
 late_initcall(kapi_debugfs_init);
 
-#endif /* CONFIG_DEBUG_FS */
\ No newline at end of file
+#endif /* CONFIG_DEBUG_FS */
+
+/* IOCTL specification registry */
+#ifdef CONFIG_KAPI_SPEC
+
+#include <linux/ioctl_api_spec.h>
+
+static DEFINE_MUTEX(ioctl_spec_mutex);
+static LIST_HEAD(ioctl_specs);
+
+struct ioctl_spec_entry {
+	struct list_head list;
+	const struct kapi_ioctl_spec *spec;
+};
+
+/**
+ * kapi_register_ioctl_spec - Register an IOCTL API specification
+ * @spec: IOCTL specification to register
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int kapi_register_ioctl_spec(const struct kapi_ioctl_spec *spec)
+{
+	struct ioctl_spec_entry *entry;
+
+	if (!spec || !spec->cmd_name)
+		return -EINVAL;
+
+	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+	if (!entry)
+		return -ENOMEM;
+
+	entry->spec = spec;
+
+	mutex_lock(&ioctl_spec_mutex);
+	list_add_tail(&entry->list, &ioctl_specs);
+	mutex_unlock(&ioctl_spec_mutex);
+
+	pr_debug("Registered IOCTL spec: %s (0x%x)\n", spec->cmd_name, spec->cmd);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(kapi_register_ioctl_spec);
+
+/**
+ * kapi_unregister_ioctl_spec - Unregister an IOCTL API specification
+ * @cmd: IOCTL command number to unregister
+ */
+void kapi_unregister_ioctl_spec(unsigned int cmd)
+{
+	struct ioctl_spec_entry *entry, *tmp;
+
+	mutex_lock(&ioctl_spec_mutex);
+	list_for_each_entry_safe(entry, tmp, &ioctl_specs, list) {
+		if (entry->spec->cmd == cmd) {
+			list_del(&entry->list);
+			kfree(entry);
+			pr_debug("Unregistered IOCTL spec for cmd 0x%x\n", cmd);
+			break;
+		}
+	}
+	mutex_unlock(&ioctl_spec_mutex);
+}
+EXPORT_SYMBOL_GPL(kapi_unregister_ioctl_spec);
+
+/**
+ * kapi_get_ioctl_spec - Retrieve IOCTL specification by command number
+ * @cmd: IOCTL command number
+ *
+ * Return: Pointer to the specification or NULL if not found
+ */
+const struct kapi_ioctl_spec *kapi_get_ioctl_spec(unsigned int cmd)
+{
+	struct ioctl_spec_entry *entry;
+	const struct kapi_ioctl_spec *spec = NULL;
+
+	mutex_lock(&ioctl_spec_mutex);
+	list_for_each_entry(entry, &ioctl_specs, list) {
+		if (entry->spec->cmd == cmd) {
+			spec = entry->spec;
+			break;
+		}
+	}
+	mutex_unlock(&ioctl_spec_mutex);
+
+	return spec;
+}
+EXPORT_SYMBOL_GPL(kapi_get_ioctl_spec);
+
+#endif /* CONFIG_KAPI_SPEC */
-- 
2.39.5


^ permalink raw reply related

* [RFC 17/19] fwctl: add detailed IOCTL API specifications
From: Sasha Levin @ 2025-06-14 13:48 UTC (permalink / raw)
  To: linux-kernel; +Cc: linux-api, workflows, tools, Sasha Levin
In-Reply-To: <20250614134858.790460-1-sashal@kernel.org>

Add kernel API specifications to the fwctl driver using the IOCTL
specification framework. This provides detailed documentation and
enables runtime validation of the fwctl IOCTL interface.

Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 drivers/fwctl/main.c | 295 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 293 insertions(+), 2 deletions(-)

diff --git a/drivers/fwctl/main.c b/drivers/fwctl/main.c
index bc6378506296c..fc85d54ecb6a0 100644
--- a/drivers/fwctl/main.c
+++ b/drivers/fwctl/main.c
@@ -10,6 +10,8 @@
 #include <linux/module.h>
 #include <linux/sizes.h>
 #include <linux/slab.h>
+#include <linux/ioctl_api_spec.h>
+#include <linux/kernel_api_spec.h>
 
 #include <uapi/fwctl/fwctl.h>
 
@@ -261,13 +263,291 @@ static int fwctl_fops_release(struct inode *inode, struct file *filp)
 	return 0;
 }
 
-static const struct file_operations fwctl_fops = {
+/* Use KAPI_DEFINE_FOPS for automatic validation wrapping */
+KAPI_DEFINE_FOPS(fwctl_fops,
 	.owner = THIS_MODULE,
 	.open = fwctl_fops_open,
 	.release = fwctl_fops_release,
 	.unlocked_ioctl = fwctl_fops_ioctl,
+);
+
+/* IOCTL API Specifications */
+
+static const struct kapi_ioctl_spec fwctl_info_spec = {
+	.cmd = FWCTL_INFO,
+	.cmd_name = "FWCTL_INFO",
+	.input_size = sizeof(struct fwctl_info),
+	.output_size = sizeof(struct fwctl_info),
+	.file_ops_name = "fwctl_fops",
+	.api_spec = {
+		.name = "fwctl_info",
+	KAPI_DESCRIPTION("Query device information and capabilities")
+	KAPI_LONG_DESC("Returns basic information about the fwctl instance, "
+		       "including the device type and driver-specific data. "
+		       "The driver-specific data format depends on the device type.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	/* Parameters */
+	KAPI_IOCTL_PARAM_SIZE
+	KAPI_IOCTL_PARAM_FLAGS
+
+	KAPI_PARAM(2, "out_device_type", "__u32", "Device type from enum fwctl_device_type")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_OUT)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_ENUM,
+		.enum_values = (const s64[]){FWCTL_DEVICE_TYPE_ERROR,
+					     FWCTL_DEVICE_TYPE_MLX5,
+					     FWCTL_DEVICE_TYPE_CXL,
+					     FWCTL_DEVICE_TYPE_PDS},
+		.enum_count = 4,
+	KAPI_PARAM_END
+
+	KAPI_PARAM(3, "device_data_len", "__u32", "Length of device data buffer")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_INOUT)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		.min_value = 0,
+		.max_value = SZ_1M,	/* Reasonable limit for device info */
+	KAPI_PARAM_END
+
+	KAPI_IOCTL_PARAM_USER_OUT_BUF(4, "out_device_data",
+				      "Driver-specific device data", 3)
+
+	/* Return value */
+	KAPI_RETURN("int", "0 on success, negative errno on failure")
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.error_values = (const s64[]){-EFAULT, -EOPNOTSUPP, -ENODEV},
+		.error_count = 3,
+	KAPI_RETURN_END
+
+	/* Errors */
+	KAPI_ERROR(0, -EFAULT, "EFAULT", "Failed to copy data to/from user space",
+		   "Check that provided pointers are valid user space addresses")
+	KAPI_ERROR(1, -EOPNOTSUPP, "EOPNOTSUPP", "Invalid flags provided",
+		   "Currently flags must be 0")
+	KAPI_ERROR(2, -ENODEV, "ENODEV", "Device has been hot-unplugged",
+		   "The underlying device is no longer available")
+
+	.error_count = 3,
+	.param_count = 5,
+	.since_version = "6.13",
+
+	/* Structure specifications */
+	KAPI_STRUCT_SPEC(0, fwctl_info, "Device information query structure")
+		KAPI_STRUCT_SIZE(sizeof(struct fwctl_info), __alignof__(struct fwctl_info))
+		KAPI_STRUCT_FIELD_COUNT(4)
+
+		KAPI_STRUCT_FIELD(0, "size", KAPI_TYPE_UINT, "__u32",
+				  "Structure size for versioning")
+			KAPI_FIELD_OFFSET(offsetof(struct fwctl_info, size))
+			KAPI_FIELD_SIZE(sizeof(__u32))
+		KAPI_STRUCT_FIELD_END
+
+		KAPI_STRUCT_FIELD(1, "flags", KAPI_TYPE_UINT, "__u32",
+				  "Must be 0, reserved for future use")
+			KAPI_FIELD_OFFSET(offsetof(struct fwctl_info, flags))
+			KAPI_FIELD_SIZE(sizeof(__u32))
+			KAPI_FIELD_CONSTRAINT_RANGE(0, 0)
+		KAPI_STRUCT_FIELD_END
+
+		KAPI_STRUCT_FIELD(2, "out_device_type", KAPI_TYPE_UINT, "__u32",
+				  "Device type identifier")
+			KAPI_FIELD_OFFSET(offsetof(struct fwctl_info, out_device_type))
+			KAPI_FIELD_SIZE(sizeof(__u32))
+		KAPI_STRUCT_FIELD_END
+
+		KAPI_STRUCT_FIELD(3, "device_data_len", KAPI_TYPE_UINT, "__u32",
+				  "Length of device-specific data")
+			KAPI_FIELD_OFFSET(offsetof(struct fwctl_info, device_data_len))
+			KAPI_FIELD_SIZE(sizeof(__u32))
+		KAPI_STRUCT_FIELD_END
+	KAPI_STRUCT_SPEC_END
+
+	KAPI_STRUCT_SPEC_COUNT(1)
+
+	/* Side effects */
+	KAPI_SIDE_EFFECT(0, KAPI_EFFECT_NONE,
+			 "none",
+			 "Read-only operation with no side effects")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT_COUNT(1)
+
+	/* State transitions */
+	KAPI_STATE_TRANS_COUNT(0)	/* No state transitions for query operation */
+	},
 };
 
+static const struct kapi_ioctl_spec fwctl_rpc_spec = {
+	.cmd = FWCTL_RPC,
+	.cmd_name = "FWCTL_RPC",
+	.input_size = sizeof(struct fwctl_rpc),
+	.output_size = sizeof(struct fwctl_rpc),
+	.file_ops_name = "fwctl_fops",
+	.api_spec = {
+		.name = "fwctl_rpc",
+	KAPI_DESCRIPTION("Execute a Remote Procedure Call to device firmware")
+	KAPI_LONG_DESC("Delivers an RPC to the device firmware and returns the response. "
+		       "The RPC format is device-specific and determined by out_device_type "
+		       "from FWCTL_INFO. Different scopes have different permission requirements.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	/* Parameters */
+	KAPI_IOCTL_PARAM_SIZE
+
+	KAPI_PARAM(1, "scope", "__u32", "Access scope from enum fwctl_rpc_scope")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_ENUM,
+		.enum_values = (const s64[]){FWCTL_RPC_CONFIGURATION,
+					     FWCTL_RPC_DEBUG_READ_ONLY,
+					     FWCTL_RPC_DEBUG_WRITE,
+					     FWCTL_RPC_DEBUG_WRITE_FULL},
+		.enum_count = 4,
+		.constraints = "FWCTL_RPC_DEBUG_WRITE_FULL requires CAP_SYS_RAWIO",
+	KAPI_PARAM_END
+
+	KAPI_PARAM(2, "in_len", "__u32", "Length of input buffer")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		.min_value = 0,
+		.max_value = MAX_RPC_LEN,
+	KAPI_PARAM_END
+
+	KAPI_PARAM(3, "out_len", "__u32", "Length of output buffer")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_INOUT)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		.min_value = 0,
+		.max_value = MAX_RPC_LEN,
+	KAPI_PARAM_END
+
+	KAPI_IOCTL_PARAM_USER_BUF(4, "in", "RPC request in device-specific format", 2)
+	KAPI_IOCTL_PARAM_USER_OUT_BUF(5, "out", "RPC response in device-specific format", 3)
+
+	/* Return value */
+	KAPI_RETURN("int", "0 on success, negative errno on failure")
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.error_values = (const s64[]){-EMSGSIZE, -EOPNOTSUPP, -EPERM,
+					      -ENOMEM, -EFAULT, -ENODEV},
+		.error_count = 6,
+	KAPI_RETURN_END
+
+	/* Errors */
+	KAPI_ERROR(0, -EMSGSIZE, "EMSGSIZE", "RPC message too large",
+		   "in_len or out_len exceeds MAX_RPC_LEN (2MB)")
+	KAPI_ERROR(1, -EOPNOTSUPP, "EOPNOTSUPP", "Invalid scope value",
+		   "scope must be one of the defined fwctl_rpc_scope values")
+	KAPI_ERROR(2, -EPERM, "EPERM", "Insufficient permissions",
+		   "FWCTL_RPC_DEBUG_WRITE_FULL requires CAP_SYS_RAWIO")
+	KAPI_ERROR(3, -ENOMEM, "ENOMEM", "Memory allocation failed",
+		   "Unable to allocate buffers for RPC")
+	KAPI_ERROR(4, -EFAULT, "EFAULT", "Failed to copy data to/from user space",
+		   "Check that provided pointers are valid user space addresses")
+	KAPI_ERROR(5, -ENODEV, "ENODEV", "Device has been hot-unplugged",
+		   "The underlying device is no longer available")
+
+	.error_count = 6,
+	.param_count = 6,
+	.since_version = "6.13",
+	.notes = "FWCTL_RPC_DEBUG_WRITE and FWCTL_RPC_DEBUG_WRITE_FULL will "
+		 "taint the kernel with TAINT_FWCTL on first use",
+
+	/* Structure specifications */
+	KAPI_STRUCT_SPEC(0, fwctl_rpc, "RPC request/response structure")
+		KAPI_STRUCT_SIZE(sizeof(struct fwctl_rpc), __alignof__(struct fwctl_rpc))
+		KAPI_STRUCT_FIELD_COUNT(6)
+
+		KAPI_STRUCT_FIELD(0, "size", KAPI_TYPE_UINT, "__u32",
+				  "Structure size for versioning")
+			KAPI_FIELD_OFFSET(offsetof(struct fwctl_rpc, size))
+			KAPI_FIELD_SIZE(sizeof(__u32))
+		KAPI_STRUCT_FIELD_END
+
+		KAPI_STRUCT_FIELD(1, "scope", KAPI_TYPE_UINT, "__u32",
+				  "Access scope level")
+			KAPI_FIELD_OFFSET(offsetof(struct fwctl_rpc, scope))
+			KAPI_FIELD_SIZE(sizeof(__u32))
+			KAPI_FIELD_CONSTRAINT_ENUM((const s64[]){FWCTL_RPC_CONFIGURATION,
+								  FWCTL_RPC_DEBUG_READ_ONLY,
+								  FWCTL_RPC_DEBUG_WRITE,
+								  FWCTL_RPC_DEBUG_WRITE_FULL}, 4)
+		KAPI_STRUCT_FIELD_END
+
+		KAPI_STRUCT_FIELD(2, "in_len", KAPI_TYPE_UINT, "__u32",
+				  "Input data length")
+			KAPI_FIELD_OFFSET(offsetof(struct fwctl_rpc, in_len))
+			KAPI_FIELD_SIZE(sizeof(__u32))
+		KAPI_STRUCT_FIELD_END
+
+		KAPI_STRUCT_FIELD(3, "out_len", KAPI_TYPE_UINT, "__u32",
+				  "Output buffer length")
+			KAPI_FIELD_OFFSET(offsetof(struct fwctl_rpc, out_len))
+			KAPI_FIELD_SIZE(sizeof(__u32))
+		KAPI_STRUCT_FIELD_END
+
+		KAPI_STRUCT_FIELD(4, "in", KAPI_TYPE_PTR, "__aligned_u64",
+				  "Pointer to input data")
+			KAPI_FIELD_OFFSET(offsetof(struct fwctl_rpc, in))
+			KAPI_FIELD_SIZE(sizeof(__aligned_u64))
+		KAPI_STRUCT_FIELD_END
+
+		KAPI_STRUCT_FIELD(5, "out", KAPI_TYPE_PTR, "__aligned_u64",
+				  "Pointer to output buffer")
+			KAPI_FIELD_OFFSET(offsetof(struct fwctl_rpc, out))
+			KAPI_FIELD_SIZE(sizeof(__aligned_u64))
+		KAPI_STRUCT_FIELD_END
+	KAPI_STRUCT_SPEC_END
+
+	KAPI_STRUCT_SPEC_COUNT(1)
+
+	/* Side effects */
+	KAPI_SIDE_EFFECT(0, KAPI_EFFECT_HARDWARE | KAPI_EFFECT_MODIFY_STATE,
+			 "device firmware",
+			 "May modify device configuration or firmware state")
+		KAPI_EFFECT_CONDITION("scope >= FWCTL_RPC_DEBUG_WRITE")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(1, KAPI_EFFECT_MODIFY_STATE,
+			 "kernel taint",
+			 "Taints kernel with TAINT_FWCTL on first debug write")
+		KAPI_EFFECT_CONDITION("scope >= FWCTL_RPC_DEBUG_WRITE && first use")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(2, KAPI_EFFECT_SCHEDULE,
+			 "process",
+			 "May block while firmware processes the RPC")
+		KAPI_EFFECT_CONDITION("firmware operation takes time")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT_COUNT(3)
+
+	/* State transitions */
+	KAPI_STATE_TRANS(0, "device state",
+			 "current configuration", "modified configuration",
+			 "Device configuration changed by RPC command")
+		KAPI_STATE_TRANS_COND("RPC modifies device settings")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(1, "kernel taint state",
+			 "untainted", "TAINT_FWCTL set",
+			 "Kernel marked as tainted due to firmware modification")
+		KAPI_STATE_TRANS_COND("First debug write operation")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS_COUNT(2)
+	},
+};
+
+/* Register all fwctl IOCTL specifications */
+KAPI_IOCTL_SPEC_DRIVER("fwctl", {
+	&fwctl_info_spec,
+	&fwctl_rpc_spec,
+})
+
 static void fwctl_device_release(struct device *device)
 {
 	struct fwctl_device *fwctl =
@@ -325,7 +605,7 @@ struct fwctl_device *_fwctl_alloc_device(struct device *parent,
 	if (!fwctl)
 		return NULL;
 
-	cdev_init(&fwctl->cdev, &fwctl_fops);
+	cdev_init(&fwctl->cdev, fwctl_fops);
 	/*
 	 * The driver module is protected by fwctl_register/unregister(),
 	 * unregister won't complete until we are done with the driver's module.
@@ -395,6 +675,9 @@ static int __init fwctl_init(void)
 {
 	int ret;
 
+	/* Initialize the wrapped file_operations */
+	kapi_init_fops_fwctl_fops();
+
 	ret = alloc_chrdev_region(&fwctl_dev, 0, FWCTL_MAX_DEVICES, "fwctl");
 	if (ret)
 		return ret;
@@ -402,8 +685,15 @@ static int __init fwctl_init(void)
 	ret = class_register(&fwctl_class);
 	if (ret)
 		goto err_chrdev;
+
+	ret = kapi_ioctl_specs_init();
+	if (ret)
+		goto err_class;
+
 	return 0;
 
+err_class:
+	class_unregister(&fwctl_class);
 err_chrdev:
 	unregister_chrdev_region(fwctl_dev, FWCTL_MAX_DEVICES);
 	return ret;
@@ -411,6 +701,7 @@ static int __init fwctl_init(void)
 
 static void __exit fwctl_exit(void)
 {
+	kapi_ioctl_specs_exit();
 	class_unregister(&fwctl_class);
 	unregister_chrdev_region(fwctl_dev, FWCTL_MAX_DEVICES);
 }
-- 
2.39.5


^ permalink raw reply related

* [RFC 18/19] binder: add detailed IOCTL API specifications
From: Sasha Levin @ 2025-06-14 13:48 UTC (permalink / raw)
  To: linux-kernel; +Cc: linux-api, workflows, tools, Sasha Levin
In-Reply-To: <20250614134858.790460-1-sashal@kernel.org>

Add kernel API specifications to the binder driver using the IOCTL
specification framework. This provides detailed documentation and
enables runtime validation of all binder IOCTL interfaces.

Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 drivers/android/binder.c | 758 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 758 insertions(+)

diff --git a/drivers/android/binder.c b/drivers/android/binder.c
index c463ca4a8fff8..975f07216724b 100644
--- a/drivers/android/binder.c
+++ b/drivers/android/binder.c
@@ -67,6 +67,8 @@
 #include <linux/task_work.h>
 #include <linux/sizes.h>
 #include <linux/ktime.h>
+#include <linux/ioctl_api_spec.h>
+#include <linux/kernel_api_spec.h>
 
 #include <uapi/linux/android/binder.h>
 
@@ -6930,6 +6932,7 @@ static int transaction_log_show(struct seq_file *m, void *unused)
 	return 0;
 }
 
+/* Define the actual binder_fops structure */
 const struct file_operations binder_fops = {
 	.owner = THIS_MODULE,
 	.poll = binder_poll,
@@ -6941,6 +6944,751 @@ const struct file_operations binder_fops = {
 	.release = binder_release,
 };
 
+/* Define wrapper for KAPI validation */
+#ifdef CONFIG_KAPI_RUNTIME_CHECKS
+static struct file_operations __kapi_wrapped_binder_fops;
+static struct kapi_fops_wrapper __kapi_wrapper_binder_fops;
+
+static void kapi_init_fops_binder_fops(void)
+{
+	if (binder_fops.unlocked_ioctl) {
+		__kapi_wrapped_binder_fops = binder_fops;
+		__kapi_wrapper_binder_fops.real_fops = &binder_fops;
+		__kapi_wrapper_binder_fops.wrapped_fops = &__kapi_wrapped_binder_fops;
+		__kapi_wrapper_binder_fops.real_ioctl = binder_fops.unlocked_ioctl;
+		__kapi_wrapped_binder_fops.unlocked_ioctl = kapi_ioctl_validation_wrapper;
+		kapi_register_wrapper(&__kapi_wrapper_binder_fops);
+	}
+}
+#else
+static inline void kapi_init_fops_binder_fops(void) {}
+#endif
+
+/* IOCTL API Specifications for Binder */
+
+static const struct kapi_ioctl_spec binder_write_read_spec = {
+	.cmd = BINDER_WRITE_READ,
+	.cmd_name = "BINDER_WRITE_READ",
+	.input_size = sizeof(struct binder_write_read),
+	.output_size = sizeof(struct binder_write_read),
+	.file_ops_name = "binder_fops",
+	.api_spec = {
+		.name = "binder_write_read",
+	KAPI_DESCRIPTION("Perform read/write operations on binder")
+	KAPI_LONG_DESC("Main workhorse of binder IPC. Allows writing commands to "
+		       "binder driver and reading responses. Commands are encoded "
+		       "in a special protocol format. Both read and write operations "
+		       "can be performed in a single ioctl call.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	/* Parameters */
+	KAPI_PARAM(0, "write_size", "binder_size_t", "Bytes to write")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		.min_value = 0,
+		.max_value = SZ_4M,	/* Reasonable limit for IPC */
+	KAPI_PARAM_END
+
+	KAPI_PARAM(1, "write_consumed", "binder_size_t", "Bytes consumed by driver")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_OUT)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		.min_value = 0,
+		.max_value = SZ_4M,
+	KAPI_PARAM_END
+
+	KAPI_PARAM(2, "write_buffer", "binder_uintptr_t", "User buffer with commands")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN | KAPI_PARAM_USER)
+		.type = KAPI_TYPE_USER_PTR,
+		.size_param_idx = 0,
+	KAPI_PARAM_END
+
+	KAPI_PARAM(3, "read_size", "binder_size_t", "Bytes to read")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		.min_value = 0,
+		.max_value = SZ_4M,
+	KAPI_PARAM_END
+
+	KAPI_PARAM(4, "read_consumed", "binder_size_t", "Bytes consumed by driver")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_OUT)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		.min_value = 0,
+		.max_value = SZ_4M,
+	KAPI_PARAM_END
+
+	KAPI_PARAM(5, "read_buffer", "binder_uintptr_t", "User buffer for responses")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_OUT | KAPI_PARAM_USER)
+		.type = KAPI_TYPE_USER_PTR,
+		.size_param_idx = 3,
+	KAPI_PARAM_END
+
+	/* Return value */
+	KAPI_RETURN("int", "0 on success, negative errno on failure")
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.error_values = (const s64[]){-EFAULT, -EINVAL, -EAGAIN, -EINTR,
+					      -ENOMEM, -ECONNREFUSED},
+		.error_count = 6,
+	KAPI_RETURN_END
+
+	/* Errors */
+	KAPI_ERROR(0, -EFAULT, "EFAULT", "Failed to copy data to/from user space",
+		   "Check buffer pointers are valid user space addresses")
+	KAPI_ERROR(1, -EINVAL, "EINVAL", "Invalid parameters",
+		   "Buffer sizes or commands are invalid")
+	KAPI_ERROR(2, -EAGAIN, "EAGAIN", "Try again",
+		   "Non-blocking read with no data available")
+	KAPI_ERROR(3, -EINTR, "EINTR", "Interrupted by signal",
+		   "Operation interrupted, should be retried")
+	KAPI_ERROR(4, -ENOMEM, "ENOMEM", "Out of memory",
+		   "Unable to allocate memory for operation")
+	KAPI_ERROR(5, -ECONNREFUSED, "ECONNREFUSED", "Connection refused",
+		   "Process is being destroyed, no further operations allowed")
+
+	.error_count = 6,
+	.param_count = 6,
+	.since_version = "3.0",
+	.notes = "This is the primary interface for binder IPC. Most other "
+		 "ioctls are for configuration and management.",
+
+	/* Structure specifications */
+	KAPI_STRUCT_SPEC(0, binder_write_read, "Read/write operation structure")
+		KAPI_STRUCT_SIZE(sizeof(struct binder_write_read), __alignof__(struct binder_write_read))
+		KAPI_STRUCT_FIELD_COUNT(6)
+
+		KAPI_STRUCT_FIELD(0, "write_size", KAPI_TYPE_UINT, "binder_size_t",
+				  "Number of bytes to write")
+			KAPI_FIELD_OFFSET(offsetof(struct binder_write_read, write_size))
+			KAPI_FIELD_SIZE(sizeof(binder_size_t))
+		KAPI_STRUCT_FIELD_END
+
+		KAPI_STRUCT_FIELD(1, "write_consumed", KAPI_TYPE_UINT, "binder_size_t",
+				  "Number of bytes consumed by driver")
+			KAPI_FIELD_OFFSET(offsetof(struct binder_write_read, write_consumed))
+			KAPI_FIELD_SIZE(sizeof(binder_size_t))
+		KAPI_STRUCT_FIELD_END
+
+		KAPI_STRUCT_FIELD(2, "write_buffer", KAPI_TYPE_PTR, "binder_uintptr_t",
+				  "Pointer to write buffer")
+			KAPI_FIELD_OFFSET(offsetof(struct binder_write_read, write_buffer))
+			KAPI_FIELD_SIZE(sizeof(binder_uintptr_t))
+		KAPI_STRUCT_FIELD_END
+
+		KAPI_STRUCT_FIELD(3, "read_size", KAPI_TYPE_UINT, "binder_size_t",
+				  "Number of bytes to read")
+			KAPI_FIELD_OFFSET(offsetof(struct binder_write_read, read_size))
+			KAPI_FIELD_SIZE(sizeof(binder_size_t))
+		KAPI_STRUCT_FIELD_END
+
+		KAPI_STRUCT_FIELD(4, "read_consumed", KAPI_TYPE_UINT, "binder_size_t",
+				  "Number of bytes consumed by driver")
+			KAPI_FIELD_OFFSET(offsetof(struct binder_write_read, read_consumed))
+			KAPI_FIELD_SIZE(sizeof(binder_size_t))
+		KAPI_STRUCT_FIELD_END
+
+		KAPI_STRUCT_FIELD(5, "read_buffer", KAPI_TYPE_PTR, "binder_uintptr_t",
+				  "Pointer to read buffer")
+			KAPI_FIELD_OFFSET(offsetof(struct binder_write_read, read_buffer))
+			KAPI_FIELD_SIZE(sizeof(binder_uintptr_t))
+		KAPI_STRUCT_FIELD_END
+	KAPI_STRUCT_SPEC_END
+
+	KAPI_STRUCT_SPEC_COUNT(1)
+
+	/* Side effects */
+	KAPI_SIDE_EFFECT(0, KAPI_EFFECT_MODIFY_STATE | KAPI_EFFECT_NETWORK,
+			 "binder transaction queue",
+			 "Enqueues transactions or commands to target process")
+		KAPI_EFFECT_CONDITION("write_size > 0")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(1, KAPI_EFFECT_MODIFY_STATE | KAPI_EFFECT_SCHEDULE,
+			 "process state",
+			 "May block waiting for incoming transactions")
+		KAPI_EFFECT_CONDITION("read_size > 0 && no data available")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(2, KAPI_EFFECT_RESOURCE_CREATE,
+			 "binder nodes/refs",
+			 "May create or destroy binder nodes and references")
+		KAPI_EFFECT_CONDITION("specific commands")
+		KAPI_EFFECT_REVERSIBLE
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT(3, KAPI_EFFECT_SIGNAL_SEND,
+			 "target process",
+			 "May trigger death notifications to linked processes")
+		KAPI_EFFECT_CONDITION("death notification")
+	KAPI_SIDE_EFFECT_END
+
+	KAPI_SIDE_EFFECT_COUNT(4)
+
+	/* State transitions */
+	KAPI_STATE_TRANS(0, "transaction",
+			 "pending in sender", "queued in target",
+			 "Transaction moves from sender to target's queue")
+		KAPI_STATE_TRANS_COND("BC_TRANSACTION command")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(1, "thread state",
+			 "running", "waiting for work",
+			 "Thread blocks waiting for incoming transactions")
+		KAPI_STATE_TRANS_COND("read with no work available")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS(2, "binder ref",
+			 "active", "released",
+			 "Reference count decremented, may trigger cleanup")
+		KAPI_STATE_TRANS_COND("BC_RELEASE command")
+	KAPI_STATE_TRANS_END
+
+	KAPI_STATE_TRANS_COUNT(3)
+	},
+};
+
+static const struct kapi_ioctl_spec binder_set_max_threads_spec = {
+	.cmd = BINDER_SET_MAX_THREADS,
+	.cmd_name = "BINDER_SET_MAX_THREADS",
+	.input_size = sizeof(__u32),
+	.output_size = 0,
+	.file_ops_name = "binder_fops",
+	.api_spec = {
+		.name = "binder_set_max_threads",
+	KAPI_DESCRIPTION("Set maximum number of binder threads")
+	KAPI_LONG_DESC("Sets the maximum number of threads that the binder driver "
+		       "will request this process to spawn for handling incoming "
+		       "transactions. The driver sends BR_SPAWN_LOOPER when it needs "
+		       "more threads.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	/* Parameters */
+	KAPI_PARAM(0, "max_threads", "__u32", "Maximum number of threads")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		.min_value = 0,
+		.max_value = INT_MAX,
+	KAPI_PARAM_END
+
+	/* Return value */
+	KAPI_RETURN("int", "0 on success, negative errno on failure")
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.error_values = (const s64[]){-EINVAL, -EFAULT},
+		.error_count = 2,
+	KAPI_RETURN_END
+
+	/* Errors */
+	KAPI_ERROR(0, -EINVAL, "EINVAL", "Invalid thread count",
+		   "Thread count exceeds system limits")
+	KAPI_ERROR(1, -EFAULT, "EFAULT", "Failed to copy from user",
+		   "Invalid user pointer provided")
+
+	.error_count = 2,
+	.param_count = 1,
+	.since_version = "3.0",
+	},
+};
+
+static const struct kapi_ioctl_spec binder_set_context_mgr_spec = {
+	.cmd = BINDER_SET_CONTEXT_MGR,
+	.cmd_name = "BINDER_SET_CONTEXT_MGR",
+	.input_size = 0,
+	.output_size = 0,
+	.file_ops_name = "binder_fops",
+	.api_spec = {
+		.name = "binder_set_context_mgr",
+	KAPI_DESCRIPTION("Become the context manager (handle 0)")
+	KAPI_LONG_DESC("Registers the calling process as the context manager for "
+		       "this binder domain. The context manager has special handle 0 "
+		       "and typically implements the service manager. Only one process "
+		       "per binder domain can be the context manager.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	/* Return value */
+	KAPI_RETURN("int", "0 on success, negative errno on failure")
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.error_values = (const s64[]){-EBUSY, -EPERM, -ENOMEM},
+		.error_count = 3,
+	KAPI_RETURN_END
+
+	/* Errors */
+	KAPI_ERROR(0, -EBUSY, "EBUSY", "Context manager already set",
+		   "Another process is already the context manager")
+	KAPI_ERROR(1, -EPERM, "EPERM", "Permission denied",
+		   "Caller lacks permission or wrong UID")
+	KAPI_ERROR(2, -ENOMEM, "ENOMEM", "Out of memory",
+		   "Unable to allocate context manager node")
+
+	.error_count = 3,
+	.param_count = 0,
+	.since_version = "3.0",
+	.notes = "Requires CAP_SYS_NICE or proper SELinux permissions",
+	},
+};
+
+static const struct kapi_ioctl_spec binder_set_context_mgr_ext_spec = {
+	.cmd = BINDER_SET_CONTEXT_MGR_EXT,
+	.cmd_name = "BINDER_SET_CONTEXT_MGR_EXT",
+	.input_size = sizeof(struct flat_binder_object),
+	.output_size = 0,
+	.file_ops_name = "binder_fops",
+	.api_spec = {
+		.name = "binder_set_context_mgr_ext",
+	KAPI_DESCRIPTION("Become context manager with extended info")
+	KAPI_LONG_DESC("Extended version of BINDER_SET_CONTEXT_MGR that allows "
+		       "specifying additional properties of the context manager "
+		       "through a flat_binder_object structure.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	/* Parameters */
+	KAPI_PARAM(0, "object", "struct flat_binder_object", "Context manager properties")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.type = KAPI_TYPE_STRUCT,
+		.size = sizeof(struct flat_binder_object),
+	KAPI_PARAM_END
+
+	/* Return value */
+	KAPI_RETURN("int", "0 on success, negative errno on failure")
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.error_values = (const s64[]){-EINVAL, -EFAULT, -EBUSY, -EPERM, -ENOMEM},
+		.error_count = 5,
+	KAPI_RETURN_END
+
+	/* Errors */
+	KAPI_ERROR(0, -EINVAL, "EINVAL", "Invalid parameters",
+		   "Invalid flat_binder_object structure")
+	KAPI_ERROR(1, -EFAULT, "EFAULT", "Failed to copy from user",
+		   "Invalid user pointer provided")
+	KAPI_ERROR(2, -EBUSY, "EBUSY", "Context manager already set",
+		   "Another process is already the context manager")
+	KAPI_ERROR(3, -EPERM, "EPERM", "Permission denied",
+		   "Caller lacks permission or wrong UID")
+	KAPI_ERROR(4, -ENOMEM, "ENOMEM", "Out of memory",
+		   "Unable to allocate context manager node")
+
+	.error_count = 5,
+	.param_count = 1,
+	.since_version = "4.14",
+	},
+};
+
+static const struct kapi_ioctl_spec binder_thread_exit_spec = {
+	.cmd = BINDER_THREAD_EXIT,
+	.cmd_name = "BINDER_THREAD_EXIT",
+	.input_size = 0,
+	.output_size = 0,
+	.file_ops_name = "binder_fops",
+	.api_spec = {
+		.name = "binder_thread_exit",
+	KAPI_DESCRIPTION("Exit binder thread")
+	KAPI_LONG_DESC("Notifies the binder driver that this thread is exiting. "
+		       "The driver will clean up any pending transactions and "
+		       "remove the thread from the thread pool.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	/* Return value */
+	KAPI_RETURN("int", "0 on success, negative errno on failure")
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.error_values = (const s64[]){},
+		.error_count = 0,
+	KAPI_RETURN_END
+
+	.error_count = 0,
+	.param_count = 0,
+	.since_version = "3.0",
+	.notes = "Should be called before thread termination to ensure clean shutdown",
+	},
+};
+
+static const struct kapi_ioctl_spec binder_version_spec = {
+	.cmd = BINDER_VERSION,
+	.cmd_name = "BINDER_VERSION",
+	.input_size = 0,
+	.output_size = sizeof(struct binder_version),
+	.file_ops_name = "binder_fops",
+	.api_spec = {
+		.name = "binder_version",
+	KAPI_DESCRIPTION("Get binder protocol version")
+	KAPI_LONG_DESC("Returns the current binder protocol version supported "
+		       "by the driver. Used for compatibility checking.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	/* Parameters */
+	KAPI_PARAM(0, "protocol_version", "__s32", "Binder protocol version")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_OUT)
+		.type = KAPI_TYPE_INT,
+		.constraint_type = KAPI_CONSTRAINT_ENUM,
+		.enum_values = (const s64[]){BINDER_CURRENT_PROTOCOL_VERSION},
+		.enum_count = 1,
+	KAPI_PARAM_END
+
+	/* Return value */
+	KAPI_RETURN("int", "0 on success, negative errno on failure")
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.error_values = (const s64[]){-EINVAL, -EFAULT},
+		.error_count = 2,
+	KAPI_RETURN_END
+
+	/* Errors */
+	KAPI_ERROR(0, -EINVAL, "EINVAL", "Invalid version structure",
+		   "Invalid user pointer for version structure")
+	KAPI_ERROR(1, -EFAULT, "EFAULT", "Failed to copy to user",
+		   "Unable to write version to user space")
+
+	.error_count = 2,
+	.param_count = 1,
+	.since_version = "3.0",
+	},
+};
+
+static const struct kapi_ioctl_spec binder_get_node_info_for_ref_spec = {
+	.cmd = BINDER_GET_NODE_INFO_FOR_REF,
+	.cmd_name = "BINDER_GET_NODE_INFO_FOR_REF",
+	.input_size = sizeof(struct binder_node_info_for_ref),
+	.output_size = sizeof(struct binder_node_info_for_ref),
+	.file_ops_name = "binder_fops",
+	.api_spec = {
+		.name = "binder_get_node_info_for_ref",
+	KAPI_DESCRIPTION("Get node information for a reference")
+	KAPI_LONG_DESC("Retrieves information about a binder node given its handle. "
+		       "Returns the current strong and weak reference counts.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	/* Parameters */
+	KAPI_PARAM(0, "handle", "__u32", "Binder handle")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.type = KAPI_TYPE_UINT,
+	KAPI_PARAM_END
+
+	KAPI_PARAM(1, "strong_count", "__u32", "Strong reference count")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_OUT)
+		.type = KAPI_TYPE_UINT,
+	KAPI_PARAM_END
+
+	KAPI_PARAM(2, "weak_count", "__u32", "Weak reference count")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_OUT)
+		.type = KAPI_TYPE_UINT,
+	KAPI_PARAM_END
+
+	/* Return value */
+	KAPI_RETURN("int", "0 on success, negative errno on failure")
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.error_values = (const s64[]){-EINVAL, -EFAULT, -ENOENT},
+		.error_count = 3,
+	KAPI_RETURN_END
+
+	/* Errors */
+	KAPI_ERROR(0, -EINVAL, "EINVAL", "Invalid parameters",
+		   "Reserved fields must be zero")
+	KAPI_ERROR(1, -EFAULT, "EFAULT", "Failed to copy data",
+		   "Invalid user pointer provided")
+	KAPI_ERROR(2, -ENOENT, "ENOENT", "Handle not found",
+		   "No node exists for the given handle")
+
+	.error_count = 3,
+	.param_count = 3,
+	.since_version = "4.14",
+	},
+};
+
+static const struct kapi_ioctl_spec binder_get_node_debug_info_spec = {
+	.cmd = BINDER_GET_NODE_DEBUG_INFO,
+	.cmd_name = "BINDER_GET_NODE_DEBUG_INFO",
+	.input_size = sizeof(struct binder_node_debug_info),
+	.output_size = sizeof(struct binder_node_debug_info),
+	.file_ops_name = "binder_fops",
+	.api_spec = {
+		.name = "binder_get_node_debug_info",
+	KAPI_DESCRIPTION("Get debug info for binder nodes")
+	KAPI_LONG_DESC("Iterates through all binder nodes in the process. "
+		       "Start with ptr=NULL to get first node, then use "
+		       "returned ptr for next call. Returns ptr=0 when done.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	/* Parameters */
+	KAPI_PARAM(0, "ptr", "binder_uintptr_t", "Node pointer (NULL for first)")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_INOUT)
+		.type = KAPI_TYPE_PTR,
+	KAPI_PARAM_END
+
+	KAPI_PARAM(1, "cookie", "binder_uintptr_t", "Node cookie value")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_OUT)
+		.type = KAPI_TYPE_UINT,
+	KAPI_PARAM_END
+
+	KAPI_PARAM(2, "has_strong_ref", "__u32", "Has strong references")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_OUT)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		.min_value = 0,
+		.max_value = 1,
+	KAPI_PARAM_END
+
+	KAPI_PARAM(3, "has_weak_ref", "__u32", "Has weak references")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_OUT)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		.min_value = 0,
+		.max_value = 1,
+	KAPI_PARAM_END
+
+	/* Return value */
+	KAPI_RETURN("int", "0 on success, negative errno on failure")
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.error_values = (const s64[]){-EFAULT, -EINVAL},
+		.error_count = 2,
+	KAPI_RETURN_END
+
+	/* Errors */
+	KAPI_ERROR(0, -EFAULT, "EFAULT", "Failed to copy data",
+		   "Invalid user pointer provided")
+	KAPI_ERROR(1, -EINVAL, "EINVAL", "Invalid node pointer",
+		   "Provided ptr is not a valid node")
+
+	.error_count = 2,
+	.param_count = 4,
+	.since_version = "4.14",
+	},
+};
+
+static const struct kapi_ioctl_spec binder_freeze_spec = {
+	.cmd = BINDER_FREEZE,
+	.cmd_name = "BINDER_FREEZE",
+	.input_size = sizeof(struct binder_freeze_info),
+	.output_size = 0,
+	.file_ops_name = "binder_fops",
+	.api_spec = {
+		.name = "binder_freeze",
+	KAPI_DESCRIPTION("Freeze or unfreeze a binder process")
+	KAPI_LONG_DESC("Controls whether a process can receive binder transactions. "
+		       "When frozen, new transactions are blocked. Can wait for "
+		       "existing transactions to complete with timeout.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	/* Parameters */
+	KAPI_PARAM(0, "pid", "__u32", "Process ID to freeze/unfreeze")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		.min_value = 1,
+		.max_value = PID_MAX_LIMIT,
+	KAPI_PARAM_END
+
+	KAPI_PARAM(1, "enable", "__u32", "1 to freeze, 0 to unfreeze")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		.min_value = 0,
+		.max_value = 1,
+	KAPI_PARAM_END
+
+	KAPI_PARAM(2, "timeout_ms", "__u32", "Timeout in milliseconds (0 = no wait)")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		.min_value = 0,
+		.max_value = 60000,	/* 1 minute max */
+	KAPI_PARAM_END
+
+	/* Return value */
+	KAPI_RETURN("int", "0 on success, negative errno on failure")
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.error_values = (const s64[]){-EINVAL, -EAGAIN, -EFAULT, -ENOMEM},
+		.error_count = 4,
+	KAPI_RETURN_END
+
+	/* Errors */
+	KAPI_ERROR(0, -EINVAL, "EINVAL", "Invalid process",
+		   "Process not found or invalid parameters")
+	KAPI_ERROR(1, -EAGAIN, "EAGAIN", "Timeout waiting for transactions",
+		   "Existing transactions did not complete within timeout")
+	KAPI_ERROR(2, -EFAULT, "EFAULT", "Failed to copy from user",
+		   "Invalid user pointer provided")
+	KAPI_ERROR(3, -ENOMEM, "ENOMEM", "Out of memory",
+		   "Unable to allocate memory for freeze operation")
+
+	.error_count = 4,
+	.param_count = 3,
+	.since_version = "5.9",
+	.notes = "Requires appropriate permissions to freeze other processes",
+	},
+};
+
+static const struct kapi_ioctl_spec binder_get_frozen_info_spec = {
+	.cmd = BINDER_GET_FROZEN_INFO,
+	.cmd_name = "BINDER_GET_FROZEN_INFO",
+	.input_size = sizeof(struct binder_frozen_status_info),
+	.output_size = sizeof(struct binder_frozen_status_info),
+	.file_ops_name = "binder_fops",
+	.api_spec = {
+		.name = "binder_get_frozen_info",
+	KAPI_DESCRIPTION("Get frozen status of a process")
+	KAPI_LONG_DESC("Queries whether a process is frozen and if it has "
+		       "received transactions while frozen. Useful for "
+		       "debugging frozen process issues.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	/* Parameters */
+	KAPI_PARAM(0, "pid", "__u32", "Process ID to query")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		.min_value = 1,
+		.max_value = PID_MAX_LIMIT,
+	KAPI_PARAM_END
+
+	KAPI_PARAM(1, "sync_recv", "__u32", "Sync transactions received while frozen")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_OUT)
+		.type = KAPI_TYPE_UINT,
+		.constraints = "Bit 0: received after frozen, Bit 1: pending during freeze",
+	KAPI_PARAM_END
+
+	KAPI_PARAM(2, "async_recv", "__u32", "Async transactions received while frozen")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_OUT)
+		.type = KAPI_TYPE_UINT,
+	KAPI_PARAM_END
+
+	/* Return value */
+	KAPI_RETURN("int", "0 on success, negative errno on failure")
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.error_values = (const s64[]){-EINVAL, -EFAULT},
+		.error_count = 2,
+	KAPI_RETURN_END
+
+	/* Errors */
+	KAPI_ERROR(0, -EINVAL, "EINVAL", "Process not found",
+		   "No binder process found with given PID")
+	KAPI_ERROR(1, -EFAULT, "EFAULT", "Failed to copy data",
+		   "Invalid user pointer provided")
+
+	.error_count = 2,
+	.param_count = 3,
+	.since_version = "5.9",
+	},
+};
+
+static const struct kapi_ioctl_spec binder_enable_oneway_spam_detection_spec = {
+	.cmd = BINDER_ENABLE_ONEWAY_SPAM_DETECTION,
+	.cmd_name = "BINDER_ENABLE_ONEWAY_SPAM_DETECTION",
+	.input_size = sizeof(__u32),
+	.output_size = 0,
+	.file_ops_name = "binder_fops",
+	.api_spec = {
+		.name = "binder_enable_oneway_spam_detection",
+	KAPI_DESCRIPTION("Enable/disable oneway spam detection")
+	KAPI_LONG_DESC("Controls whether the driver monitors for excessive "
+		       "oneway transactions that might indicate spam or abuse. "
+		       "When enabled, BR_ONEWAY_SPAM_SUSPECT is sent when threshold exceeded.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	/* Parameters */
+	KAPI_PARAM(0, "enable", "__u32", "1 to enable, 0 to disable")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_IN)
+		.type = KAPI_TYPE_UINT,
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		.min_value = 0,
+		.max_value = 1,
+	KAPI_PARAM_END
+
+	/* Return value */
+	KAPI_RETURN("int", "0 on success, negative errno on failure")
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.error_values = (const s64[]){-EFAULT},
+		.error_count = 1,
+	KAPI_RETURN_END
+
+	/* Errors */
+	KAPI_ERROR(0, -EFAULT, "EFAULT", "Failed to copy from user",
+		   "Invalid user pointer provided")
+
+	.error_count = 1,
+	.param_count = 1,
+	.since_version = "5.13",
+	},
+};
+
+static const struct kapi_ioctl_spec binder_get_extended_error_spec = {
+	.cmd = BINDER_GET_EXTENDED_ERROR,
+	.cmd_name = "BINDER_GET_EXTENDED_ERROR",
+	.input_size = 0,
+	.output_size = sizeof(struct binder_extended_error),
+	.file_ops_name = "binder_fops",
+	.api_spec = {
+		.name = "binder_get_extended_error",
+	KAPI_DESCRIPTION("Get extended error information")
+	KAPI_LONG_DESC("Retrieves detailed error information from the last "
+		       "failed binder operation on this thread. Clears the "
+		       "error after reading.")
+	KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+	/* Parameters */
+	KAPI_PARAM(0, "id", "__u32", "Error identifier")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_OUT)
+		.type = KAPI_TYPE_UINT,
+	KAPI_PARAM_END
+
+	KAPI_PARAM(1, "command", "__u32", "Binder command that failed")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_OUT)
+		.type = KAPI_TYPE_UINT,
+	KAPI_PARAM_END
+
+	KAPI_PARAM(2, "param", "__s32", "Error parameter (negative errno)")
+		KAPI_PARAM_FLAGS(KAPI_PARAM_OUT)
+		.type = KAPI_TYPE_INT,
+		.constraint_type = KAPI_CONSTRAINT_RANGE,
+		.min_value = -MAX_ERRNO,
+		.max_value = 0,
+	KAPI_PARAM_END
+
+	/* Return value */
+	KAPI_RETURN("int", "0 on success, negative errno on failure")
+		.type = KAPI_TYPE_INT,
+		.check_type = KAPI_RETURN_ERROR_CHECK,
+		.error_values = (const s64[]){-EFAULT},
+		.error_count = 1,
+	KAPI_RETURN_END
+
+	/* Errors */
+	KAPI_ERROR(0, -EFAULT, "EFAULT", "Failed to copy to user",
+		   "Invalid user pointer provided")
+
+	.error_count = 1,
+	.param_count = 3,
+	.since_version = "5.16",
+	.notes = "Error is cleared after reading, subsequent calls return BR_OK",
+	},
+};
+
+/* Register all binder IOCTL specifications */
+KAPI_IOCTL_SPEC_DRIVER("binder", {
+	&binder_write_read_spec,
+	&binder_set_max_threads_spec,
+	&binder_set_context_mgr_spec,
+	&binder_set_context_mgr_ext_spec,
+	&binder_thread_exit_spec,
+	&binder_version_spec,
+	&binder_get_node_info_for_ref_spec,
+	&binder_get_node_debug_info_spec,
+	&binder_freeze_spec,
+	&binder_get_frozen_info_spec,
+	&binder_enable_oneway_spam_detection_spec,
+	&binder_get_extended_error_spec,
+})
+
 DEFINE_SHOW_ATTRIBUTE(state);
 DEFINE_SHOW_ATTRIBUTE(state_hashed);
 DEFINE_SHOW_ATTRIBUTE(stats);
@@ -7050,6 +7798,13 @@ static int __init binder_init(void)
 	if (ret)
 		return ret;
 
+	/* Initialize the wrapped file_operations */
+	kapi_init_fops_binder_fops();
+
+	ret = kapi_ioctl_specs_init();
+	if (ret)
+		goto err_kapi_init;
+
 	atomic_set(&binder_transaction_log.cur, ~0U);
 	atomic_set(&binder_transaction_log_failed.cur, ~0U);
 
@@ -7102,6 +7857,9 @@ static int __init binder_init(void)
 
 err_alloc_device_names_failed:
 	debugfs_remove_recursive(binder_debugfs_dir_entry_root);
+	kapi_ioctl_specs_exit();
+
+err_kapi_init:
 	binder_alloc_shrinker_exit();
 
 	return ret;
-- 
2.39.5


^ permalink raw reply related

* [RFC 19/19] tools/kapi: Add kernel API specification extraction tool
From: Sasha Levin @ 2025-06-14 13:48 UTC (permalink / raw)
  To: linux-kernel; +Cc: linux-api, workflows, tools, Sasha Levin
In-Reply-To: <20250614134858.790460-1-sashal@kernel.org>

The kapi tool extracts and displays kernel API specifications.

Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 Documentation/admin-guide/kernel-api-spec.rst | 198 ++++++-
 tools/kapi/.gitignore                         |   4 +
 tools/kapi/Cargo.toml                         |  19 +
 tools/kapi/src/extractor/debugfs.rs           | 204 ++++++++
 tools/kapi/src/extractor/mod.rs               |  95 ++++
 tools/kapi/src/extractor/source_parser.rs     | 488 ++++++++++++++++++
 .../src/extractor/vmlinux/binary_utils.rs     | 130 +++++
 tools/kapi/src/extractor/vmlinux/mod.rs       | 372 +++++++++++++
 tools/kapi/src/formatter/json.rs              | 170 ++++++
 tools/kapi/src/formatter/mod.rs               |  68 +++
 tools/kapi/src/formatter/plain.rs             |  99 ++++
 tools/kapi/src/formatter/rst.rs               | 144 ++++++
 tools/kapi/src/main.rs                        | 121 +++++
 13 files changed, 2109 insertions(+), 3 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/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/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/admin-guide/kernel-api-spec.rst b/Documentation/admin-guide/kernel-api-spec.rst
index 3a63f6711e27b..9b452753111ad 100644
--- a/Documentation/admin-guide/kernel-api-spec.rst
+++ b/Documentation/admin-guide/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.
@@ -71,6 +73,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
 ----------
 
@@ -344,8 +353,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``)
+- IOCTLs (``DEFINE_IOCTL_API_SPEC``)
+- Kernel functions (``KAPI_DEFINE_SPEC``)
 
 IDE Integration
 ---------------
@@ -357,6 +535,11 @@ Modern IDEs can use the JSON export for:
 - Context validation
 - Error code documentation
 
+Example IDE integration::
+
+    # Generate IDE completion data
+    $ kapi --format json > .vscode/kernel-apis.json
+
 Testing Framework
 -----------------
 
@@ -367,6 +550,15 @@ The framework includes test helpers::
     kapi_test_api("kmalloc", test_cases);
     #endif
 
+The kapi tool can verify specifications against implementations::
+
+    # Run consistency tests
+    $ cd tools/kapi
+    $ ./test_consistency.sh
+
+    # Compare source vs binary specifications
+    $ ./compare_all_syscalls.sh
+
 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..4e6bcb10d132f
--- /dev/null
+++ b/tools/kapi/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "kapi"
+version = "0.1.0"
+edition = "2024"
+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"
diff --git a/tools/kapi/src/extractor/debugfs.rs b/tools/kapi/src/extractor/debugfs.rs
new file mode 100644
index 0000000000000..91775dea223f5
--- /dev/null
+++ b/tools/kapi/src/extractor/debugfs.rs
@@ -0,0 +1,204 @@
+use anyhow::{Context, Result, bail};
+use std::fs;
+use std::io::Write;
+use std::path::PathBuf;
+use crate::formatter::OutputFormatter;
+
+use super::{ApiExtractor, ApiSpec, display_api_spec};
+
+/// 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)
+    }
+
+    /// 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()))?;
+
+        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,
+        };
+
+        // Parse the content
+        let mut collecting_multiline = false;
+        let mut multiline_buffer = String::new();
+        let mut multiline_field = "";
+
+        for line in content.lines() {
+            // 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 {
+                if 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();
+                } 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(|s| s.to_string())
+                    .collect();
+            }
+        }
+
+        // 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 {
+            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 spec for {}: {}", 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 '{}' not found in debugfs", api_name)?;
+        }
+
+        Ok(())
+    }
+}
\ No newline at end of file
diff --git a/tools/kapi/src/extractor/mod.rs b/tools/kapi/src/extractor/mod.rs
new file mode 100644
index 0000000000000..bc55201152e3e
--- /dev/null
+++ b/tools/kapi/src/extractor/mod.rs
@@ -0,0 +1,95 @@
+use anyhow::Result;
+use std::io::Write;
+use crate::formatter::OutputFormatter;
+
+pub mod vmlinux;
+pub mod source_parser;
+pub mod debugfs;
+
+pub use vmlinux::VmlinuxExtractor;
+pub use source_parser::SourceExtractor;
+pub use debugfs::DebugfsExtractor;
+
+/// Common API specification information that all extractors should provide
+#[derive(Debug, Clone)]
+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>,
+}
+
+/// 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 let Some(param_count) = spec.param_count {
+        formatter.begin_parameters(writer, param_count)?;
+        formatter.end_parameters(writer)?;
+    }
+
+    if let Some(error_count) = spec.error_count {
+        formatter.begin_errors(writer, error_count)?;
+        formatter.end_errors(writer)?;
+    }
+
+    if let Some(notes) = &spec.notes {
+        formatter.notes(writer, notes)?;
+    }
+
+    if let Some(examples) = &spec.examples {
+        formatter.examples(writer, examples)?;
+    }
+
+    formatter.end_api_details(writer)?;
+
+    Ok(())
+}
\ No newline at end of file
diff --git a/tools/kapi/src/extractor/source_parser.rs b/tools/kapi/src/extractor/source_parser.rs
new file mode 100644
index 0000000000000..8de35f5a73916
--- /dev/null
+++ b/tools/kapi/src/extractor/source_parser.rs
@@ -0,0 +1,488 @@
+use anyhow::{Context, Result};
+use regex::Regex;
+use std::fs;
+use std::path::Path;
+use std::collections::HashMap;
+use walkdir::WalkDir;
+use std::io::Write;
+use crate::formatter::OutputFormatter;
+use super::{ApiExtractor, ApiSpec, display_api_spec};
+
+#[derive(Debug, Clone)]
+pub struct SourceApiSpec {
+    pub name: String,
+    pub api_type: ApiType,
+    pub parsed_fields: HashMap<String, String>,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum ApiType {
+    Syscall,
+    Ioctl,
+    Function,
+    Unknown,
+}
+
+impl ApiType {
+    fn from_name(name: &str) -> Self {
+        if name.starts_with("sys_") {
+            ApiType::Syscall
+        } else if name.contains("ioctl") || name.contains("IOCTL") {
+            ApiType::Ioctl
+        } else if name.starts_with("do_") || name.starts_with("__") {
+            ApiType::Function
+        } else {
+            ApiType::Unknown
+        }
+    }
+}
+
+pub struct SourceParser {
+    // Regex patterns for matching KAPI specifications
+    spec_start_pattern: Regex,
+    spec_end_pattern: Regex,
+    ioctl_spec_pattern: Regex,
+}
+
+impl SourceParser {
+    pub fn new() -> Result<Self> {
+        Ok(SourceParser {
+            // Match DEFINE_KERNEL_API_SPEC(function_name)
+            spec_start_pattern: Regex::new(r"DEFINE_KERNEL_API_SPEC\s*\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)")?,
+            // Match KAPI_END_SPEC
+            spec_end_pattern: Regex::new(r"KAPI_END_SPEC")?,
+            // Match IOCTL specifications
+            ioctl_spec_pattern: Regex::new(r#"DEFINE_IOCTL_API_SPEC\s*\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*,\s*([^,]+)\s*,\s*"([^"]+)"\s*\)"#)?,
+        })
+    }
+
+    /// Parse a single source file for KAPI specifications
+    pub fn parse_file(&self, path: &Path) -> Result<Vec<SourceApiSpec>> {
+        let content = fs::read_to_string(path)
+            .with_context(|| format!("Failed to read file: {}", path.display()))?;
+
+        self.parse_content(&content, path)
+    }
+
+    /// Parse file content for KAPI specifications
+    pub fn parse_content(&self, content: &str, _file_path: &Path) -> Result<Vec<SourceApiSpec>> {
+        let mut specs = Vec::new();
+        let lines: Vec<&str> = content.lines().collect();
+
+        // First, look for standard KAPI specs
+        for (i, line) in lines.iter().enumerate() {
+            if let Some(captures) = self.spec_start_pattern.captures(line) {
+                let api_name = captures.get(1).unwrap().as_str().to_string();
+
+                // Find the end of this specification
+                if let Some(spec_content) = self.extract_spec_block(&lines, i) {
+                    let mut spec = SourceApiSpec {
+                        name: api_name.clone(),
+                        api_type: ApiType::from_name(&api_name),
+                        parsed_fields: HashMap::new(),
+                    };
+
+                    // Parse the fields
+                    self.parse_spec_fields(&spec_content, &mut spec.parsed_fields)?;
+
+                    specs.push(spec);
+                }
+            }
+
+            // Also look for IOCTL specs
+            if let Some(captures) = self.ioctl_spec_pattern.captures(line) {
+                let spec_name = captures.get(1).unwrap().as_str().to_string();
+                let cmd = captures.get(2).unwrap().as_str().to_string();
+                let cmd_name = captures.get(3).unwrap().as_str().to_string();
+
+                // Find the end of this IOCTL specification
+                if let Some(spec_content) = self.extract_ioctl_spec_block(&lines, i) {
+                    let mut spec = SourceApiSpec {
+                        name: spec_name,
+                        api_type: ApiType::Ioctl,
+                        parsed_fields: HashMap::new(),
+                    };
+
+                    // Add IOCTL-specific fields
+                    spec.parsed_fields.insert("cmd".to_string(), cmd);
+                    spec.parsed_fields.insert("cmd_name".to_string(), cmd_name);
+
+                    // Parse other fields
+                    self.parse_spec_fields(&spec_content, &mut spec.parsed_fields)?;
+
+                    specs.push(spec);
+                }
+            }
+        }
+
+        Ok(specs)
+    }
+
+    /// Extract a complete KAPI specification block from the source
+    fn extract_spec_block(&self, lines: &[&str], start_idx: usize) -> Option<String> {
+        let mut spec_lines = Vec::new();
+        let mut brace_count = 0;
+        let mut in_spec = false;
+
+        for (_i, line) in lines.iter().enumerate().skip(start_idx) {
+            spec_lines.push(line.to_string());
+
+            // Count braces to handle nested structures
+            for ch in line.chars() {
+                match ch {
+                    '{' => {
+                        brace_count += 1;
+                        in_spec = true;
+                    }
+                    '}' => {
+                        brace_count -= 1;
+                    }
+                    _ => {}
+                }
+            }
+
+            // Check for end of spec
+            if self.spec_end_pattern.is_match(line) {
+                return Some(spec_lines.join("\n"));
+            }
+
+            // Alternative end: closing brace with semicolon
+            if in_spec && brace_count == 0 && line.contains("};") {
+                return Some(spec_lines.join("\n"));
+            }
+        }
+
+        None
+    }
+
+    /// Extract a complete IOCTL specification block
+    fn extract_ioctl_spec_block(&self, lines: &[&str], start_idx: usize) -> Option<String> {
+        let mut spec_lines = Vec::new();
+        let mut brace_count = 0;
+
+        for (i, line) in lines.iter().enumerate().skip(start_idx) {
+            spec_lines.push(line.to_string());
+
+            // Count braces
+            for ch in line.chars() {
+                match ch {
+                    '{' => brace_count += 1,
+                    '}' => brace_count -= 1,
+                    _ => {}
+                }
+            }
+
+            // Check for end patterns
+            if line.contains("KAPI_END_IOCTL_SPEC") || line.contains("KAPI_IOCTL_END_SPEC") {
+                return Some(spec_lines.join("\n"));
+            }
+
+            // Alternative end: closing brace with semicolon at top level
+            if brace_count == 0 && line.contains("};") && i > start_idx {
+                return Some(spec_lines.join("\n"));
+            }
+        }
+
+        None
+    }
+
+    /// Parse individual KAPI fields from the specification
+    fn parse_spec_fields(&self, content: &str, fields: &mut HashMap<String, String>) -> Result<()> {
+        // Parse KAPI_DESCRIPTION
+        if let Some(captures) = Regex::new(r#"KAPI_DESCRIPTION\s*\(\s*"([^"]*)"\s*\)"#)?.captures(content) {
+            fields.insert("description".to_string(), captures.get(1).unwrap().as_str().to_string());
+        }
+
+        // Parse KAPI_LONG_DESC (handle multi-line)
+        if let Some(captures) = Regex::new(r#"KAPI_LONG_DESC\s*\(\s*"([^"]*(?:\s*"[^"]*)*?)"\s*\)"#)?.captures(content) {
+            let long_desc = captures.get(1).unwrap().as_str()
+                .replace("\"\n\t\t       \"", " ")
+                .replace("\"\n\t\t    \"", " ")
+                .replace("\"\n\t\t   \"", " ")
+                .replace("\"\n\t\t  \"", " ")
+                .replace("\"\n\t\t \"", " ")
+                .replace("\"\n\t\t\"", " ");
+            fields.insert("long_description".to_string(), long_desc);
+        }
+
+        // Parse KAPI_CONTEXT
+        if let Some(captures) = Regex::new(r"KAPI_CONTEXT\s*\(([^)]+)\)")?.captures(content) {
+            fields.insert("context".to_string(), captures.get(1).unwrap().as_str().to_string());
+        }
+
+        // Parse KAPI_NOTES (handle multi-line)
+        if let Some(captures) = Regex::new(r#"KAPI_NOTES\s*\(\s*"([^"]*(?:\s*"[^"]*)*?)"\s*\)"#)?.captures(content) {
+            let notes = captures.get(1).unwrap().as_str()
+                .replace("\"\n\t\t       \"", " ")
+                .replace("\"\n\t\t    \"", " ")
+                .replace("\"\n\t\t   \"", " ")
+                .replace("\"\n\t\t  \"", " ")
+                .replace("\"\n\t\t \"", " ")
+                .replace("\"\n\t\t\"", " ")
+                .trim()
+                .to_string();
+            fields.insert("notes".to_string(), notes);
+        }
+
+        // Parse KAPI_EXAMPLES (handle multi-line)
+        if let Some(captures) = Regex::new(r#"KAPI_EXAMPLES\s*\(\s*"([^"]*(?:\s*"[^"]*)*?)"\s*\)"#)?.captures(content) {
+            let examples = captures.get(1).unwrap().as_str()
+                .replace("\\n\"\n\t\t    \"", "\n")
+                .replace("\\n\"\n\t\t   \"", "\n")
+                .replace("\\n\"\n\t\t  \"", "\n")
+                .replace("\\n\"\n\t\t \"", "\n")
+                .replace("\\n\"\n\t\t\"", "\n")
+                .replace("\\n", "\n")
+                .trim()
+                .to_string();
+            fields.insert("examples".to_string(), examples);
+        }
+
+        // Parse KAPI_SINCE_VERSION
+        if let Some(captures) = Regex::new(r#"KAPI_SINCE_VERSION\s*\(\s*"([^"]*)"\s*\)"#)?.captures(content) {
+            fields.insert("since_version".to_string(), captures.get(1).unwrap().as_str().to_string());
+        }
+
+        // Parse parameter count
+        let param_regex = Regex::new(r"KAPI_PARAM\s*\(\s*(\d+)\s*,")?;
+        let mut max_param_idx = 0;
+        for captures in param_regex.captures_iter(content) {
+            if let Ok(idx) = captures.get(1).unwrap().as_str().parse::<usize>() {
+                max_param_idx = max_param_idx.max(idx + 1);
+            }
+        }
+        if max_param_idx > 0 {
+            fields.insert("param_count".to_string(), max_param_idx.to_string());
+        }
+
+        // Parse error count
+        let error_regex = Regex::new(r"KAPI_ERROR\s*\(\s*(\d+)\s*,")?;
+        let mut max_error_idx = 0;
+        for captures in error_regex.captures_iter(content) {
+            if let Ok(idx) = captures.get(1).unwrap().as_str().parse::<usize>() {
+                max_error_idx = max_error_idx.max(idx + 1);
+            }
+        }
+        if max_error_idx > 0 {
+            fields.insert("error_count".to_string(), max_error_idx.to_string());
+        }
+
+        // Parse other counts
+        if content.contains(".error_count =") {
+            if let Some(captures) = Regex::new(r"\.error_count\s*=\s*(\d+)")?.captures(content) {
+                fields.insert("error_count".to_string(), captures.get(1).unwrap().as_str().to_string());
+            }
+        }
+
+        if content.contains(".param_count =") {
+            if let Some(captures) = Regex::new(r"\.param_count\s*=\s*(\d+)")?.captures(content) {
+                fields.insert("param_count".to_string(), captures.get(1).unwrap().as_str().to_string());
+            }
+        }
+
+        // Parse .since_version
+        if let Some(captures) = Regex::new(r#"\.since_version\s*=\s*"([^"]*)""#)?.captures(content) {
+            fields.insert("since_version".to_string(), captures.get(1).unwrap().as_str().to_string());
+        }
+
+        // Parse .notes (handle multi-line)
+        if let Some(captures) = Regex::new(r#"\.notes\s*=\s*"([^"]*(?:\s*"[^"]*)*?)""#)?.captures(content) {
+            let notes = captures.get(1).unwrap().as_str()
+                .replace("\"\n\t\t \"", " ")
+                .replace("\"\n\t\t\"", " ")
+                .replace("\"\n\t \"", " ")  // Handle single tab + space
+                .trim()
+                .to_string();
+            fields.insert("notes".to_string(), notes);
+        }
+
+        // Parse .examples (handle multi-line)
+        if let Some(captures) = Regex::new(r#"\.examples\s*=\s*"([^"]*(?:\s*"[^"]*)*?)""#)?.captures(content) {
+            let examples = captures.get(1).unwrap().as_str()
+                .replace("\\n\"\n\t\t    \"", "\n")
+                .replace("\\n", "\n");
+            fields.insert("examples".to_string(), examples);
+        }
+
+        Ok(())
+    }
+
+    /// Scan a directory tree for files containing KAPI specifications
+    pub fn scan_directory(&self, dir: &Path, extensions: &[&str]) -> Result<Vec<SourceApiSpec>> {
+        let mut all_specs = Vec::new();
+
+        for entry in WalkDir::new(dir)
+            .follow_links(true)
+            .into_iter()
+            .filter_map(|e| e.ok())
+        {
+            let path = entry.path();
+
+            // Skip non-files
+            if !path.is_file() {
+                continue;
+            }
+
+            // Check file extension
+            if let Some(ext) = path.extension() {
+                if extensions.iter().any(|&e| ext == e) {
+                    // Try to parse the file
+                    match self.parse_file(path) {
+                        Ok(specs) => {
+                            if !specs.is_empty() {
+                                all_specs.extend(specs);
+                            }
+                        }
+                        Err(e) => {
+                            eprintln!("Warning: Failed to parse {}: {}", path.display(), e);
+                        }
+                    }
+                }
+            }
+        }
+
+        Ok(all_specs)
+    }
+
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::io::Write;
+    use tempfile::NamedTempFile;
+
+    #[test]
+    fn test_parse_syscall_spec() {
+        let parser = SourceParser::new().unwrap();
+
+        let content = r#"
+DEFINE_KERNEL_API_SPEC(sys_mlock)
+    KAPI_DESCRIPTION("Lock pages in memory")
+    KAPI_LONG_DESC("Locks pages in the specified address range into RAM")
+    KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+    KAPI_PARAM(0, "start", "unsigned long", "Starting address")
+    KAPI_PARAM_END
+
+    KAPI_PARAM(1, "len", "size_t", "Length of range")
+    KAPI_PARAM_END
+
+    .param_count = 2,
+    .error_count = 3,
+
+KAPI_END_SPEC
+"#;
+
+        let mut temp_file = NamedTempFile::new().unwrap();
+        write!(temp_file, "{}", content).unwrap();
+
+        let specs = parser.parse_content(content, temp_file.path()).unwrap();
+
+        assert_eq!(specs.len(), 1);
+        assert_eq!(specs[0].name, "sys_mlock");
+        assert_eq!(specs[0].api_type, ApiType::Syscall);
+        assert_eq!(specs[0].parsed_fields.get("description").unwrap(), "Lock pages in memory");
+        assert_eq!(specs[0].parsed_fields.get("param_count").unwrap(), "2");
+    }
+
+    #[test]
+    fn test_parse_ioctl_spec() {
+        let parser = SourceParser::new().unwrap();
+
+        let content = r#"
+DEFINE_IOCTL_API_SPEC(binder_write_read, BINDER_WRITE_READ, "BINDER_WRITE_READ")
+    KAPI_DESCRIPTION("Perform read/write operations on binder")
+    KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+    KAPI_PARAM(0, "write_size", "binder_size_t", "Bytes to write")
+    KAPI_PARAM_END
+
+KAPI_END_IOCTL_SPEC
+"#;
+
+        let mut temp_file = NamedTempFile::new().unwrap();
+        write!(temp_file, "{}", content).unwrap();
+
+        let specs = parser.parse_content(content, temp_file.path()).unwrap();
+
+        assert_eq!(specs.len(), 1);
+        assert_eq!(specs[0].name, "binder_write_read");
+        assert_eq!(specs[0].api_type, ApiType::Ioctl);
+        assert_eq!(specs[0].parsed_fields.get("cmd_name").unwrap(), "BINDER_WRITE_READ");
+    }
+}
+
+// SourceExtractor implementation
+pub struct SourceExtractor {
+    specs: Vec<SourceApiSpec>,
+}
+
+impl SourceExtractor {
+    pub fn new(path: String) -> Result<Self> {
+        let parser = SourceParser::new()?;
+        let path_obj = Path::new(&path);
+
+        let specs = if path_obj.is_file() {
+            parser.parse_file(path_obj)?
+        } else if path_obj.is_dir() {
+            parser.scan_directory(path_obj, &["c", "h"])?
+        } else {
+            anyhow::bail!("Path does not exist: {}", path_obj.display())
+        };
+
+        Ok(SourceExtractor { specs })
+    }
+
+    fn convert_to_api_spec(&self, source_spec: &SourceApiSpec) -> ApiSpec {
+        ApiSpec {
+            name: source_spec.name.clone(),
+            api_type: match source_spec.api_type {
+                ApiType::Syscall => "syscall".to_string(),
+                ApiType::Ioctl => "ioctl".to_string(),
+                ApiType::Function => "function".to_string(),
+                ApiType::Unknown => "unknown".to_string(),
+            },
+            description: source_spec.parsed_fields.get("description").cloned(),
+            long_description: source_spec.parsed_fields.get("long_description").cloned(),
+            version: source_spec.parsed_fields.get("version").cloned(),
+            context_flags: source_spec.parsed_fields.get("context")
+                .map(|c| vec![c.clone()])
+                .unwrap_or_default(),
+            param_count: source_spec.parsed_fields.get("param_count")
+                .and_then(|s| s.parse::<u32>().ok()),
+            error_count: source_spec.parsed_fields.get("error_count")
+                .and_then(|s| s.parse::<u32>().ok()),
+            examples: source_spec.parsed_fields.get("examples").cloned(),
+            notes: source_spec.parsed_fields.get("notes").cloned(),
+            since_version: source_spec.parsed_fields.get("since_version").cloned(),
+        }
+    }
+}
+
+impl ApiExtractor for SourceExtractor {
+    fn extract_all(&self) -> Result<Vec<ApiSpec>> {
+        Ok(self.specs.iter()
+            .map(|s| self.convert_to_api_spec(s))
+            .collect())
+    }
+
+    fn extract_by_name(&self, name: &str) -> Result<Option<ApiSpec>> {
+        Ok(self.specs.iter()
+            .find(|s| s.name == name)
+            .map(|s| self.convert_to_api_spec(s)))
+    }
+
+    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 = self.convert_to_api_spec(spec);
+            display_api_spec(&api_spec, formatter, writer)?;
+        }
+        Ok(())
+    }
+}
\ No newline at end of file
diff --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..02c8e3b8eda77
--- /dev/null
+++ b/tools/kapi/src/extractor/vmlinux/binary_utils.rs
@@ -0,0 +1,130 @@
+use anyhow::Result;
+use std::io::Write;
+use crate::formatter::OutputFormatter;
+
+// 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 = 16;
+}
+
+// Helper for reading data at specific offsets
+pub struct DataReader<'a> {
+    data: &'a [u8],
+    pos: usize,
+}
+
+impl<'a> DataReader<'a> {
+    pub fn new(data: &'a [u8], offset: usize) -> Self {
+        Self { data, pos: offset }
+    }
+
+    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 bytes = self.read_bytes(4)?;
+        Some(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
+    }
+
+    pub fn skip(&mut self, len: usize) {
+        self.pos = (self.pos + len).min(self.data.len());
+    }
+}
+
+#[allow(dead_code)]
+pub fn parse_context_flags(flags: u32, formatter: &mut dyn OutputFormatter, w: &mut dyn Write) -> Result<()> {
+    // Context flags from kernel headers
+    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_USER: u32 = 1 << 4;
+    const KAPI_CTX_KERNEL: u32 = 1 << 5;
+    const KAPI_CTX_SLEEPABLE: u32 = 1 << 6;
+    const KAPI_CTX_ATOMIC: u32 = 1 << 7;
+    const KAPI_CTX_PREEMPTIBLE: u32 = 1 << 8;
+    const KAPI_CTX_MIGRATION_DISABLED: u32 = 1 << 9;
+
+    if flags & KAPI_CTX_PROCESS != 0 { formatter.context_flag(w, "Process context")?; }
+    if flags & KAPI_CTX_SOFTIRQ != 0 { formatter.context_flag(w, "Softirq context")?; }
+    if flags & KAPI_CTX_HARDIRQ != 0 { formatter.context_flag(w, "Hardirq context")?; }
+    if flags & KAPI_CTX_NMI != 0 { formatter.context_flag(w, "NMI context")?; }
+    if flags & KAPI_CTX_USER != 0 { formatter.context_flag(w, "User mode")?; }
+    if flags & KAPI_CTX_KERNEL != 0 { formatter.context_flag(w, "Kernel mode")?; }
+    if flags & KAPI_CTX_SLEEPABLE != 0 { formatter.context_flag(w, "May sleep")?; }
+    if flags & KAPI_CTX_ATOMIC != 0 { formatter.context_flag(w, "Atomic context")?; }
+    if flags & KAPI_CTX_PREEMPTIBLE != 0 { formatter.context_flag(w, "Preemptible")?; }
+    if flags & KAPI_CTX_MIGRATION_DISABLED != 0 { formatter.context_flag(w, "Migration disabled")?; }
+
+    Ok(())
+}
+
+// Structure layout definitions for calculating sizes
+pub fn param_spec_layout_size() -> usize {
+    // Packed structure
+    sizes::NAME * 2 + // name, type_name
+    4 + 4 + // type, flags
+    8 + 8 + // size, alignment
+    8 + 8 + // min_value, max_value
+    8 + // valid_mask
+    8 + // enum_values pointer
+    4 + 4 + // enum_count, constraint_type
+    8 + // validate pointer
+    sizes::DESC * 2 + // description, constraints
+    4 + 8 // size_param_idx, size_multiplier
+}
+
+pub fn return_spec_layout_size() -> usize {
+    // Packed structure
+    sizes::NAME + // type_name
+    4 + 4 + // type, check_type
+    8 + 8 + 8 + // success_value, success_min, success_max
+    8 + // error_values pointer
+    4 + // error_count
+    8 + // is_success pointer
+    sizes::DESC // description
+}
+
+pub fn error_spec_layout_size() -> usize {
+    // Packed structure
+    4 + // code
+    sizes::NAME + // name
+    sizes::DESC * 2 // condition, description
+}
+
+pub fn lock_spec_layout_size() -> usize {
+    // Packed structure
+    sizes::NAME + // name
+    4 + // lock_type
+    1 + 1 + 1 + 1 + // bools
+    sizes::DESC // description
+}
+
+pub fn constraint_spec_layout_size() -> usize {
+    // Packed structure
+    sizes::NAME + // name
+    sizes::DESC * 2 // description, expression
+}
\ No newline at end of file
diff --git a/tools/kapi/src/extractor/vmlinux/mod.rs b/tools/kapi/src/extractor/vmlinux/mod.rs
new file mode 100644
index 0000000000000..5d5ca413d77a2
--- /dev/null
+++ b/tools/kapi/src/extractor/vmlinux/mod.rs
@@ -0,0 +1,372 @@
+use anyhow::{Context, Result};
+use goblin::elf::Elf;
+use std::fs;
+use std::io::Write;
+use crate::formatter::OutputFormatter;
+use super::{ApiExtractor, ApiSpec};
+
+mod binary_utils;
+use binary_utils::{sizes, DataReader,
+    param_spec_layout_size, return_spec_layout_size, error_spec_layout_size,
+    lock_spec_layout_size, constraint_spec_layout_size};
+
+pub struct VmlinuxExtractor {
+    kapi_data: Vec<u8>,
+    specs: Vec<KapiSpec>,
+}
+
+#[derive(Debug)]
+struct KapiSpec {
+    name: String,
+    api_type: String,
+    offset: usize,
+}
+
+impl VmlinuxExtractor {
+    pub fn new(vmlinux_path: String) -> 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")?;
+
+        // Find the .kapi_specs section
+        let kapi_section = elf.section_headers
+            .iter()
+            .find(|sh| {
+                if let Some(name) = elf.shdr_strtab.get_at(sh.sh_name) {
+                    name == ".kapi_specs"
+                } else {
+                    false
+                }
+            })
+            .context("Could not find .kapi_specs section in vmlinux")?;
+
+        // Find __start_kapi_specs and __stop_kapi_specs symbols
+        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");
+        }
+
+        // Calculate the offset within the file
+        let section_vaddr = kapi_section.sh_addr;
+        let file_offset = kapi_section.sh_offset + (start - section_vaddr);
+        let data_size = (stop - start) as usize;
+
+        if file_offset as 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 as usize..(file_offset as usize + data_size)].to_vec();
+
+        // Parse the specifications
+        let specs = parse_kapi_specs(&kapi_data)?;
+
+        Ok(VmlinuxExtractor {
+            kapi_data,
+            specs,
+        })
+    }
+
+}
+
+impl ApiExtractor for VmlinuxExtractor {
+    fn extract_all(&self) -> Result<Vec<ApiSpec>> {
+        // For vmlinux extractor, we return basic info only
+        // Detailed parsing happens in display_api_details
+        Ok(self.specs.iter().map(|spec| {
+            ApiSpec {
+                name: spec.name.clone(),
+                api_type: spec.api_type.clone(),
+                description: None,
+                long_description: None,
+                version: None,
+                context_flags: vec![],
+                param_count: None,
+                error_count: None,
+                examples: None,
+                notes: None,
+                since_version: None,
+            }
+        }).collect())
+    }
+
+    fn extract_by_name(&self, name: &str) -> Result<Option<ApiSpec>> {
+        Ok(self.specs.iter()
+            .find(|s| s.name == name)
+            .map(|spec| ApiSpec {
+                name: spec.name.clone(),
+                api_type: spec.api_type.clone(),
+                description: None,
+                long_description: None,
+                version: None,
+                context_flags: vec![],
+                param_count: None,
+                error_count: None,
+                examples: None,
+                notes: None,
+                since_version: 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) {
+            // Parse the binary data into an ApiSpec
+            let api_spec = parse_binary_to_api_spec(&self.kapi_data, spec.offset)?;
+            // Use the common display function
+            super::display_api_spec(&api_spec, formatter, writer)?;
+        }
+        Ok(())
+    }
+}
+
+fn parse_kapi_specs(data: &[u8]) -> Result<Vec<KapiSpec>> {
+    let mut specs = Vec::new();
+
+    // The kernel_api_spec struct size in the kernel is 308064 bytes
+    // This is calculated as sizeof(struct kernel_api_spec) which includes:
+    // - Basic fields (name, version, description, etc.)
+    // - Arrays for parameters, errors, locks, constraints
+    // - Additional metadata fields
+    // TODO: This should ideally be read from kernel headers or made configurable
+    let struct_size = 308064;
+
+    let mut offset = 0;
+    while offset + struct_size <= data.len() {
+        // Try to read the name at this offset
+        if let Some(name) = read_cstring(data, offset, 128) {
+            if is_valid_api_name(&name) {
+                let api_type = if name.starts_with("sys_") {
+                    "syscall"
+                } else if name.contains("ioctl") || name.contains("IOCTL") {
+                    "ioctl"
+                } else {
+                    "other"
+                };
+
+                specs.push(KapiSpec {
+                    name: name.to_string(),
+                    api_type: api_type.to_string(),
+                    offset,
+                });
+            }
+        }
+
+        offset += struct_size;
+    }
+
+    // Handle any remaining data that might be a partial spec
+    if offset < data.len() && data.len() - offset >= 128 {
+        if let Some(name) = read_cstring(data, offset, 128) {
+            if is_valid_api_name(&name) {
+                let api_type = if name.starts_with("sys_") {
+                    "syscall"
+                } else if name.contains("ioctl") || name.contains("IOCTL") {
+                    "ioctl"
+                } else {
+                    "other"
+                };
+
+                specs.push(KapiSpec {
+                    name: name.to_string(),
+                    api_type: api_type.to_string(),
+                    offset,
+                });
+            }
+        }
+    }
+
+    Ok(specs)
+}
+
+fn read_cstring(data: &[u8], offset: usize, max_len: usize) -> Option<String> {
+    if offset + max_len > data.len() {
+        return None;
+    }
+
+    let bytes = &data[offset..offset + 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
+}
+
+fn is_valid_api_name(name: &str) -> bool {
+    if name.is_empty() || name.len() > 100 {
+        return false;
+    }
+
+    name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
+        && (name.starts_with("sys_")
+            || name.contains("ioctl")
+            || name.contains("IOCTL")
+            || name.starts_with("do_")
+            || name.starts_with("__"))
+}
+
+fn parse_binary_to_api_spec(data: &[u8], offset: usize) -> Result<ApiSpec> {
+    let mut reader = DataReader::new(data, offset);
+
+    // Read name
+    let name = reader.read_cstring(sizes::NAME)
+        .ok_or_else(|| anyhow::anyhow!("Failed to read API name"))?;
+
+    // Read version
+    let version = reader.read_u32()
+        .map(|v| v.to_string());
+
+    // Read description
+    let description = reader.read_cstring(sizes::DESC)
+        .filter(|s| !s.is_empty());
+
+    // Read long description
+    let long_description = reader.read_cstring(sizes::DESC * 4)
+        .filter(|s| !s.is_empty());
+
+    // Read context flags
+    let context_flags = if let Some(flags) = reader.read_u32() {
+        let mut flag_strings = Vec::new();
+
+        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_USER: u32 = 1 << 4;
+        const KAPI_CTX_KERNEL: u32 = 1 << 5;
+        const KAPI_CTX_SLEEPABLE: u32 = 1 << 6;
+        const KAPI_CTX_ATOMIC: u32 = 1 << 7;
+        const KAPI_CTX_PREEMPTIBLE: u32 = 1 << 8;
+        const KAPI_CTX_MIGRATION_DISABLED: u32 = 1 << 9;
+
+        // Build the flag string similar to source format
+        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_USER != 0 { parts.push("KAPI_CTX_USER"); }
+        if flags & KAPI_CTX_KERNEL != 0 { parts.push("KAPI_CTX_KERNEL"); }
+        if flags & KAPI_CTX_SLEEPABLE != 0 { parts.push("KAPI_CTX_SLEEPABLE"); }
+        if flags & KAPI_CTX_ATOMIC != 0 { parts.push("KAPI_CTX_ATOMIC"); }
+        if flags & KAPI_CTX_PREEMPTIBLE != 0 { parts.push("KAPI_CTX_PREEMPTIBLE"); }
+        if flags & KAPI_CTX_MIGRATION_DISABLED != 0 { parts.push("KAPI_CTX_MIGRATION_DISABLED"); }
+
+        if !parts.is_empty() {
+            flag_strings.push(parts.join(" | "));
+        }
+        flag_strings
+    } else {
+        vec![]
+    };
+
+    // Read parameter count
+    let param_count = reader.read_u32();
+
+    // Skip parameters for now (to match source output)
+    if let Some(count) = param_count {
+        if count > 0 && count <= sizes::MAX_PARAMS as u32 {
+            reader.skip(param_spec_layout_size() * count as usize);
+            reader.skip(param_spec_layout_size() * (sizes::MAX_PARAMS - count as usize));
+        } else {
+            reader.skip(param_spec_layout_size() * sizes::MAX_PARAMS);
+        }
+    }
+
+    // Skip return spec
+    reader.skip(return_spec_layout_size());
+
+    // Read error count
+    let error_count = reader.read_u32();
+
+    // Skip errors
+    if let Some(count) = error_count {
+        if count > 0 && count <= sizes::MAX_ERRORS as u32 {
+            reader.skip(error_spec_layout_size() * count as usize);
+            reader.skip(error_spec_layout_size() * (sizes::MAX_ERRORS - count as usize));
+        } else {
+            reader.skip(error_spec_layout_size() * sizes::MAX_ERRORS);
+        }
+    }
+
+    // Skip locks
+    if let Some(lock_count) = reader.read_u32() {
+        if lock_count > 0 && lock_count <= sizes::MAX_CONSTRAINTS as u32 {
+            reader.skip(lock_spec_layout_size() * lock_count as usize);
+            reader.skip(lock_spec_layout_size() * (sizes::MAX_CONSTRAINTS - lock_count as usize));
+        } else {
+            reader.skip(lock_spec_layout_size() * sizes::MAX_CONSTRAINTS);
+        }
+    }
+
+    // Skip constraints
+    if let Some(constraint_count) = reader.read_u32() {
+        if constraint_count > 0 && constraint_count <= sizes::MAX_CONSTRAINTS as u32 {
+            reader.skip(constraint_spec_layout_size() * constraint_count as usize);
+            reader.skip(constraint_spec_layout_size() * (sizes::MAX_CONSTRAINTS - constraint_count as usize));
+        } else {
+            reader.skip(constraint_spec_layout_size() * sizes::MAX_CONSTRAINTS);
+        }
+    }
+
+    // Read examples
+    let examples = reader.read_cstring(sizes::DESC * 2)
+        .filter(|s| !s.is_empty());
+
+    // Read notes
+    let notes = reader.read_cstring(sizes::DESC)
+        .filter(|s| !s.is_empty());
+
+    // Read since_version
+    let since_version = reader.read_cstring(32)
+        .filter(|s| !s.is_empty());
+
+    // Determine API type from name
+    let api_type = if name.starts_with("sys_") {
+        "syscall"
+    } else if name.contains("ioctl") || name.contains("IOCTL") {
+        "ioctl"
+    } else {
+        "other"
+    }.to_string();
+
+    Ok(ApiSpec {
+        name,
+        api_type,
+        description,
+        long_description,
+        version,
+        context_flags,
+        param_count,
+        error_count,
+        examples,
+        notes,
+        since_version,
+    })
+}
+
+// Old display_api_details_from_binary function removed - now using parse_binary_to_api_spec + display_api_spec
\ 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..44d2bbfc91133
--- /dev/null
+++ b/tools/kapi/src/formatter/json.rs
@@ -0,0 +1,170 @@
+use super::OutputFormatter;
+use std::io::Write;
+use serde::Serialize;
+
+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>,
+}
+
+
+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<()> {
+        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,
+        });
+        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(())
+    }
+}
\ No newline at end of file
diff --git a/tools/kapi/src/formatter/mod.rs b/tools/kapi/src/formatter/mod.rs
new file mode 100644
index 0000000000000..6eb42e8b404d0
--- /dev/null
+++ b/tools/kapi/src/formatter/mod.rs
@@ -0,0 +1,68 @@
+use std::io::Write;
+
+mod plain;
+mod json;
+mod rst;
+
+pub use plain::PlainFormatter;
+pub use json::JsonFormatter;
+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 end_parameters(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+    fn begin_errors(&mut self, w: &mut dyn Write, count: u32) -> 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<()>;
+}
+
+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()),
+    }
+}
\ No newline at end of file
diff --git a/tools/kapi/src/formatter/plain.rs b/tools/kapi/src/formatter/plain.rs
new file mode 100644
index 0000000000000..4ccbfcbbc8416
--- /dev/null
+++ b/tools/kapi/src/formatter/plain.rs
@@ -0,0 +1,99 @@
+use super::OutputFormatter;
+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 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<()> {
+        writeln!(w, "\nPossible Errors ({}):", count)
+    }
+
+    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)
+    }
+}
\ No newline at end of file
diff --git a/tools/kapi/src/formatter/rst.rs b/tools/kapi/src/formatter/rst.rs
new file mode 100644
index 0000000000000..96be83bf208dd
--- /dev/null
+++ b/tools/kapi/src/formatter/rst.rs
@@ -0,0 +1,144 @@
+use super::OutputFormatter;
+use std::io::Write;
+
+pub struct RstFormatter {
+    current_section_level: usize,
+}
+
+impl RstFormatter {
+    pub fn new() -> Self {
+        RstFormatter {
+            current_section_level: 0,
+        }
+    }
+
+    fn section_char(&self, 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<()> {
+        self.current_section_level = 0;
+        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<()> {
+        self.current_section_level = 1;
+        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<()> {
+        self.current_section_level = 1;
+        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<()> {
+        self.current_section_level = 1;
+        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<()> {
+        self.current_section_level = 1;
+        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<()> {
+        self.current_section_level = 1;
+        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)
+    }
+}
\ No newline at end of file
diff --git a/tools/kapi/src/main.rs b/tools/kapi/src/main.rs
new file mode 100644
index 0000000000000..9d6533cbc7dd1
--- /dev/null
+++ b/tools/kapi/src/main.rs
@@ -0,0 +1,121 @@
+//! 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 formatter;
+mod extractor;
+
+use formatter::{OutputFormat, create_formatter};
+use extractor::{ApiExtractor, VmlinuxExtractor, SourceExtractor, DebugfsExtractor};
+
+#[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.clone()) {
+        (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, None, None) => {
+            // If debugfs is specified or no input is provided, use debugfs
+            Box::new(DebugfsExtractor::new(args.debugfs)?)
+        }
+        _ => {
+            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 if output_format == OutputFormat::Plain {
+            writeln!(stdout, "\nAPI '{}' not found.", api_name_req)?;
+            writeln!(stdout, "\nAvailable APIs:")?;
+            for spec in extractor.extract_all()? {
+                writeln!(stdout, "  {} ({})", spec.name, spec.api_type)?;
+            }
+        }
+    } else {
+        // Display list of APIs using the extractor
+        let all_specs = extractor.extract_all()?;
+        let syscalls: Vec<_> = all_specs.iter().filter(|s| s.api_type == "syscall").collect();
+        let ioctls: Vec<_> = all_specs.iter().filter(|s| s.api_type == "ioctl").collect();
+        let functions: Vec<_> = all_specs.iter().filter(|s| s.api_type == "function").collect();
+
+        if !syscalls.is_empty() {
+            formatter.begin_api_list(&mut stdout, "System Calls")?;
+            for spec in syscalls {
+                formatter.api_item(&mut stdout, &spec.name, &spec.api_type)?;
+            }
+            formatter.end_api_list(&mut stdout)?;
+        }
+
+        if !ioctls.is_empty() {
+            formatter.begin_api_list(&mut stdout, "IOCTLs")?;
+            for spec in ioctls {
+                formatter.api_item(&mut stdout, &spec.name, &spec.api_type)?;
+            }
+            formatter.end_api_list(&mut stdout)?;
+        }
+
+        if !functions.is_empty() {
+            formatter.begin_api_list(&mut stdout, "Functions")?;
+            for spec in functions {
+                formatter.api_item(&mut stdout, &spec.name, &spec.api_type)?;
+            }
+            formatter.end_api_list(&mut stdout)?;
+        }
+
+        formatter.total_specs(&mut stdout, all_specs.len())?;
+    }
+
+    formatter.end_document(&mut stdout)?;
+
+    Ok(())
+}
+
-- 
2.39.5


^ permalink raw reply related

* Re: [RFC 08/19] exec: add API specification for execve
From: Florian Weimer @ 2025-06-16 21:39 UTC (permalink / raw)
  To: Sasha Levin; +Cc: linux-kernel, linux-api, workflows, tools
In-Reply-To: <20250614134858.790460-9-sashal@kernel.org>

* Sasha Levin:

> +	KAPI_RETURN("long", "Does not return on success; returns -1 on error")
> +		.type = KAPI_TYPE_INT,
> +		.check_type = KAPI_RETURN_ERROR_CHECK,
> +	KAPI_RETURN_END

Is the -1 part correct?

Many later errors during execve are not recoverable and result in execve
succeeding (nominally) and a fatal signal being delivered to the process
instead.  Not sure if the description covers that.

What about the effect of unblocking a parent thread that has vfork'ed?

Thanks,
Florian


^ permalink raw reply

* Re: [RFC 08/19] exec: add API specification for execve
From: Sasha Levin @ 2025-06-17  1:51 UTC (permalink / raw)
  To: Florian Weimer; +Cc: linux-kernel, linux-api, workflows, tools
In-Reply-To: <87ikkvv018.fsf@oldenburg.str.redhat.com>

On Mon, Jun 16, 2025 at 11:39:31PM +0200, Florian Weimer wrote:
>* Sasha Levin:
>
>> +	KAPI_RETURN("long", "Does not return on success; returns -1 on error")
>> +		.type = KAPI_TYPE_INT,
>> +		.check_type = KAPI_RETURN_ERROR_CHECK,
>> +	KAPI_RETURN_END
>
>Is the -1 part correct?

Maybe :) That's one of the things I wasn't sure about: we're documenting
the execve syscall rather than the function itself. A user calling
execve() will end up with -1 on failure, and errno set with the error
code.

You could argue that it's libc that sets errno and we're trying to spec
the kernel here, not the userspace interface to it.

At the end I managed to lawyer myself into a decision that I liked: I
figured that since klibc is really a kernel library that is merely
packaged seperately from the kernel, it is really a kernel interface,
and so I followed the libc convention.

Open for suggestions...

>Many later errors during execve are not recoverable and result in execve
>succeeding (nominally) and a fatal signal being delivered to the process
>instead.  Not sure if the description covers that.

I was afraid of the "signals" rabit hole: from what I recall, you can
have fatal signals pending past the point of no return but before
execve() completes from both execve() failures as well as external
sources.

There's definitely room for a longer explanation of how all of this
works together.

I'd suggest that we tackle signal specs in the near future, and see how
we can tie those into the rest of the API specs. Right now I'm pretty
unhappy with the vague KAPI_SIGNAL().

>What about the effect of unblocking a parent thread that has vfork'ed?

In my mind it's vfork() that is waiting for the execve to complete (via
wait_for_vfork_done()) rather than execve() actively waking up the
vfork() parent.

We can list it as a side effect of execve()? I suppose that its similar
to something like read() in one process waking up a different process
from epoll_wait(), so we should probably be documenting those as well...


Thanks for the comments!

-- 
Thanks,
Sasha

^ permalink raw reply

* Re: [RFC 08/19] exec: add API specification for execve
From: Florian Weimer @ 2025-06-17  7:13 UTC (permalink / raw)
  To: Sasha Levin; +Cc: linux-kernel, linux-api, workflows, tools
In-Reply-To: <aFDKHhaqB75Qi212@lappy>

* Sasha Levin:

> On Mon, Jun 16, 2025 at 11:39:31PM +0200, Florian Weimer wrote:
>>* Sasha Levin:
>>
>>> +	KAPI_RETURN("long", "Does not return on success; returns -1 on error")
>>> +		.type = KAPI_TYPE_INT,
>>> +		.check_type = KAPI_RETURN_ERROR_CHECK,
>>> +	KAPI_RETURN_END
>>
>>Is the -1 part correct?
>
> Maybe :) That's one of the things I wasn't sure about: we're documenting
> the execve syscall rather than the function itself. A user calling
> execve() will end up with -1 on failure, and errno set with the error
> code.

Well, it doesn't say execve, it says sys_execve.

> You could argue that it's libc that sets errno and we're trying to spec
> the kernel here, not the userspace interface to it.

And I think this would be appropriate.

Note that in the future, the glibc version of execve will not be a
straightforward system call wrapper because we need to obtain a
consistent snapshot of the environment array.  That is actually pretty
hard because we cannot atomically replace the process image, unblock
signals, and unmap a copy of the environment.

So I think it's best for the kernel to stick with the system call
interface and not try to document what libcs are doing.

An even more thorny example are the setuid family of system calls, where
the kernel is extremely far away from what POSIX requires, and we have
to fix it in userspace.

Thanks,
Florian


^ permalink raw reply

* Re: [RFC 00/19] Kernel API Specification Framework
From: David Laight @ 2025-06-17 12:08 UTC (permalink / raw)
  To: Sasha Levin; +Cc: linux-kernel, linux-api, workflows, tools
In-Reply-To: <20250614134858.790460-1-sashal@kernel.org>

On Sat, 14 Jun 2025 09:48:39 -0400
Sasha Levin <sashal@kernel.org> wrote:

> This patch series introduces a framework for formally specifying kernel
> APIs, addressing the long-standing challenge of maintaining stable
> interfaces between the kernel and user-space programs. As outlined in
> previous discussions about kernel ABI stability, the lack of
> machine-readable API specifications has led to inadvertent breakages and
> inconsistent validation across system calls and IOCTLs.

Ugg, looks horrid.
Going to be worse than things like doxygen for getting out of step with
the actual code and grep searches are going to hit the comment blocks.

	David

^ permalink raw reply

* [PATCH RESEND v4 1/7] selftests/futex: Add ASSERT_ macros
From: André Almeida @ 2025-06-17 18:34 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Peter Zijlstra, Darren Hart,
	Davidlohr Bueso, Shuah Khan, Arnd Bergmann,
	Sebastian Andrzej Siewior, Waiman Long
  Cc: linux-kernel, linux-kselftest, linux-api, kernel-dev,
	André Almeida
In-Reply-To: <20250617-tonyk-robust_futex-v4-0-6586f5fb9d33@igalia.com>

Create ASSERT_{EQ, NE, TRUE, FALSE} macros to make test creation easier.

Signed-off-by: André Almeida <andrealmeid@igalia.com>
---
 tools/testing/selftests/futex/include/logging.h | 38 +++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/tools/testing/selftests/futex/include/logging.h b/tools/testing/selftests/futex/include/logging.h
index 874c69ce5cce9efa3a9d6de246f5972a75437dbf..a19755622a877932884570c8f58aaee7371d5f8f 100644
--- a/tools/testing/selftests/futex/include/logging.h
+++ b/tools/testing/selftests/futex/include/logging.h
@@ -23,6 +23,44 @@
 #include <linux/futex.h>
 #include "kselftest.h"
 
+#define ASSERT_EQ(var, value)	\
+do {				\
+	if (var != value) {	\
+		ksft_test_result_fail("%s: expected %ld, but %s has %ld\n", \
+				      __func__, (long) value, #var, \
+				      (long) var); \
+		return;		\
+	}			\
+} while (0)
+
+#define ASSERT_NE(var, value)	\
+do {				\
+	if (var == value) {	\
+		ksft_test_result_fail("%s: expected not %ld, but %s has %ld\n", \
+				      __func__, (long) value, #var, \
+				      (long) var); \
+		return; \
+	}		\
+} while (0)
+
+#define ASSERT_TRUE(var)	\
+do {				\
+	if ((var) == 0) {	\
+		ksft_test_result_fail("%s: expected %s to be true\n", \
+				      __func__, #var); \
+		return;		\
+	}			\
+} while (0)
+
+#define ASSERT_FALSE(var)	\
+do {				\
+	if (var) {		\
+		ksft_test_result_fail("%s: expected %s to be false\n", \
+				      __func__, #var); \
+		return;		\
+	}			\
+} while (0)
+
 /*
  * Define PASS, ERROR, and FAIL strings with and without color escape
  * sequences, default to no color.

-- 
2.49.0


^ permalink raw reply related

* [PATCH RESEND v4 0/7] futex: Create set_robust_list2
From: André Almeida @ 2025-06-17 18:34 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Peter Zijlstra, Darren Hart,
	Davidlohr Bueso, Shuah Khan, Arnd Bergmann,
	Sebastian Andrzej Siewior, Waiman Long
  Cc: linux-kernel, linux-kselftest, linux-api, kernel-dev,
	André Almeida

This patch adds a new robust_list() syscall. The current syscall
can't be expanded to cover the following use case, so a new one is
needed. This new syscall allows users to set multiple robust lists per
process and to have either 32bit or 64bit pointers in the list.

* Use case

FEX-Emu[1] is an application that runs x86 and x86-64 binaries on an
AArch64 Linux host. One of the tasks of FEX-Emu is to translate syscalls
from one platform to another. Existing set_robust_list() can't be easily
translated because of two limitations:

1) x86 apps can have 32bit pointers robust lists. For a x86-64 kernel
   this is not a problem, because of the compat entry point. But there's
   no such compat entry point for AArch64, so the kernel would do the
   pointer arithmetic wrongly. Is also unviable to userspace to keep
   track every addition/removal to the robust list and keep a 64bit
   version of it somewhere else to feed the kernel. Thus, the new
   interface has an option of telling the kernel if the list is filled
   with 32bit or 64bit pointers.

2) Apps can set just one robust list (in theory, x86-64 can set two if
   they also use the compat entry point). That means that when a x86 app
   asks FEX-Emu to call set_robust_list(), FEX have two options: to
   overwrite their own robust list pointer and make the app robust, or
   to ignore the app robust list and keep the emulator robust. The new
   interface allows for multiple robust lists per application, solving
   this.

* Interface

This is the proposed interface:

	long set_robust_list2(void *head, int index, unsigned int flags)

`head` is the head of the userspace struct robust_list_head, just as old
set_robust_list(). It needs to be a void pointer since it can point to a normal
robust_list_head or a compat_robust_list_head.

`flags` can be used for defining the list type:

	enum robust_list_type {
	 	ROBUST_LIST_32BIT,
		ROBUST_LIST_64BIT,
	 };

`index` is the index in the internal robust_list's linked list (the naming
starts to get confusing, I reckon). If `index == -1`, that means that user wants
to set a new robust_list, and the kernel will append it in the end of the list,
assign a new index and return this index to the user. If `index >= 0`, that
means that user wants to re-set `*head` of an already existing list (similarly
to what happens when you call set_robust_list() twice with different `*head`).

If `index` is out of range, or it points to a non-existing robust_list, or if
the internal list is full, an error is returned.

* Implementation

The implementation re-uses most of the existing robust list interface as
possible. The new task_struct member `struct list_head robust_list2` is just a
linked list where new lists are appended as the user requests more lists, and by
futex_cleanup(), the kernel walks through the internal list feeding
exit_robust_list() with the robust_list's.

This implementation supports up to 10 lists (defined at ROBUST_LISTS_PER_TASK),
but it was an arbitrary number for this RFC. For the described use case above, 4
should be enough, I'm not sure which should be the limit.

It doesn't support list removal (should it support?). It doesn't have a proper
get_robust_list2() yet as well, but I can add it in a next revision. We could
also have a generic robust_list() syscall that can be used to set/get and be
controlled by flags.

The new interface has a `unsigned int flags` argument, making it
extensible for future use cases as well.

It refuses unaligned `head` addresses. It doesn't have a limit for elements in a
single list (like ROBUST_LIST_LIMIT), it destroys the list as it is parsed to be
safe against circular lists.

* Testing

This patcheset has a selftest patch that expands this one:
https://lore.kernel.org/lkml/20250212131123.37431-1-andrealmeid@igalia.com/

Also, FEX-Emu added support for this interface to validate it:
https://github.com/FEX-Emu/FEX/pull/3966

Feedback is very welcomed!

Thanks,
	André

[1] https://github.com/FEX-Emu/FEX

Changelog:
- Rebased on top of new futex work (private hash)
v4: https://lore.kernel.org/lkml/20250225183531.682556-1-andrealmeid@igalia.com/

- Refuse unaligned head pointers
- Ignore ROBUST_LIST_LIMIT for lists created with this interface and make it
  robust against circular lists
- Fix a get_robust_list() syscall bug for getting the list from another thread
- Adapt selftest to use the new interface
v3: https://lore.kernel.org/lkml/20241217174958.477692-1-andrealmeid@igalia.com/

- Old syscall set_robust_list() adds new head to the internal linked list of
  robust lists pointers, instead of having a field just for them. Remove
  tsk->robust_list and use only tsk->robust_list2
v2: https://lore.kernel.org/lkml/20241101162147.284993-1-andrealmeid@igalia.com/

- Added a patch to properly deal with exit_robust_list() in 64bit vs 32bit
- Wired-up syscall for all archs
- Added more of the cover letter to the commit message
v1: https://lore.kernel.org/lkml/20241024145735.162090-1-andrealmeid@igalia.com/

---
André Almeida (7):
      selftests/futex: Add ASSERT_ macros
      selftests/futex: Create test for robust list
      futex: Use explicit sizes for compat_exit_robust_list
      futex: Create set_robust_list2
      futex: Wire up set_robust_list2 syscall
      futex: Remove the limit of elements for sys_set_robust_list2 lists
      selftests: futex: Expand robust list test for the new interface

 arch/alpha/kernel/syscalls/syscall.tbl             |   1 +
 arch/arm/tools/syscall.tbl                         |   1 +
 arch/m68k/kernel/syscalls/syscall.tbl              |   1 +
 arch/microblaze/kernel/syscalls/syscall.tbl        |   1 +
 arch/mips/kernel/syscalls/syscall_n32.tbl          |   1 +
 arch/mips/kernel/syscalls/syscall_n64.tbl          |   1 +
 arch/mips/kernel/syscalls/syscall_o32.tbl          |   1 +
 arch/parisc/kernel/syscalls/syscall.tbl            |   1 +
 arch/powerpc/kernel/syscalls/syscall.tbl           |   1 +
 arch/s390/kernel/syscalls/syscall.tbl              |   1 +
 arch/sh/kernel/syscalls/syscall.tbl                |   1 +
 arch/sparc/kernel/syscalls/syscall.tbl             |   1 +
 arch/x86/entry/syscalls/syscall_32.tbl             |   1 +
 arch/x86/entry/syscalls/syscall_64.tbl             |   1 +
 arch/xtensa/kernel/syscalls/syscall.tbl            |   1 +
 include/linux/compat.h                             |  12 +-
 include/linux/futex.h                              |  16 +-
 include/linux/sched.h                              |   5 +-
 include/uapi/asm-generic/unistd.h                  |   2 +
 include/uapi/linux/futex.h                         |  24 +
 kernel/futex/core.c                                | 165 ++++-
 kernel/futex/futex.h                               |   5 +
 kernel/futex/syscalls.c                            |  85 ++-
 kernel/sys_ni.c                                    |   1 +
 scripts/syscall.tbl                                |   1 +
 .../testing/selftests/futex/functional/.gitignore  |   1 +
 tools/testing/selftests/futex/functional/Makefile  |   3 +-
 .../selftests/futex/functional/robust_list.c       | 706 +++++++++++++++++++++
 tools/testing/selftests/futex/include/logging.h    |  38 ++
 29 files changed, 1026 insertions(+), 53 deletions(-)
---
base-commit: 3ee84e3dd88e39b55b534e17a7b9a181f1d46809
change-id: 20250225-tonyk-robust_futex-60adeedac695

Best regards,
-- 
André Almeida <andrealmeid@igalia.com>


^ permalink raw reply

* [PATCH RESEND v4 6/7] futex: Remove the limit of elements for sys_set_robust_list2 lists
From: André Almeida @ 2025-06-17 18:34 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Peter Zijlstra, Darren Hart,
	Davidlohr Bueso, Shuah Khan, Arnd Bergmann,
	Sebastian Andrzej Siewior, Waiman Long
  Cc: linux-kernel, linux-kselftest, linux-api, kernel-dev,
	André Almeida
In-Reply-To: <20250617-tonyk-robust_futex-v4-0-6586f5fb9d33@igalia.com>

Remove the limit of ROBUST_LIST_LIMIT elements that a robust list can
have, for the ones created with the new interface. This is done by
overwritten the list as it's proceeded in a way that we avoid circular
lists.

For the old interface, we keep the limited behavior to avoid changing
the API.

Signed-off-by: André Almeida <andrealmeid@igalia.com>
---
 kernel/futex/core.c | 50 ++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 36 insertions(+), 14 deletions(-)

diff --git a/kernel/futex/core.c b/kernel/futex/core.c
index 49b3bc592948a811f995017027f33ad8f285531f..61f0b48a2bcd8ab926754980ab3454b9ec13a344 100644
--- a/kernel/futex/core.c
+++ b/kernel/futex/core.c
@@ -1152,7 +1152,8 @@ static inline int fetch_robust_entry(struct robust_list __user **entry,
  * We silently return on any sign of list-walking problem.
  */
 static void exit_robust_list64(struct task_struct *curr,
-			       struct robust_list_head __user *head)
+			       struct robust_list_head __user *head,
+			       bool destroyable)
 {
 	struct robust_list __user *entry, *next_entry, *pending;
 	unsigned int limit = ROBUST_LIST_LIMIT, pi, pip;
@@ -1196,13 +1197,17 @@ static void exit_robust_list64(struct task_struct *curr,
 		}
 		if (rc)
 			return;
-		entry = next_entry;
-		pi = next_pi;
+
 		/*
 		 * Avoid excessively long or circular lists:
 		 */
-		if (!--limit)
+		if (!destroyable && !--limit)
 			break;
+		else
+			put_user(&head->list, &entry->next);
+
+		entry = next_entry;
+		pi = next_pi;
 
 		cond_resched();
 	}
@@ -1214,7 +1219,8 @@ static void exit_robust_list64(struct task_struct *curr,
 }
 #else
 static void exit_robust_list64(struct task_struct *curr,
-			      struct robust_list_head __user *head)
+			      struct robust_list_head __user *head,
+			      bool destroyable)
 {
 	pr_warn("32bit kernel should not allow ROBUST_LIST_64BIT");
 }
@@ -1252,7 +1258,8 @@ fetch_robust_entry32(u32 *uentry, struct robust_list __user **entry,
  * We silently return on any sign of list-walking problem.
  */
 static void exit_robust_list32(struct task_struct *curr,
-			       struct robust_list_head32 __user *head)
+			       struct robust_list_head32 __user *head,
+			       bool destroyable)
 {
 	struct robust_list __user *entry, *next_entry, *pending;
 	unsigned int limit = ROBUST_LIST_LIMIT, pi, pip;
@@ -1301,14 +1308,17 @@ static void exit_robust_list32(struct task_struct *curr,
 		}
 		if (rc)
 			return;
-		uentry = next_uentry;
-		entry = next_entry;
-		pi = next_pi;
 		/*
 		 * Avoid excessively long or circular lists:
 		 */
-		if (!--limit)
+		if (!destroyable && !--limit)
 			break;
+		else
+			put_user((struct robust_list __user *) &head->list, &entry->next);
+
+		uentry = next_uentry;
+		entry = next_entry;
+		pi = next_pi;
 
 		cond_resched();
 	}
@@ -1474,26 +1484,38 @@ static void exit_pi_state_list(struct task_struct *curr)
 static inline void exit_pi_state_list(struct task_struct *curr) { }
 #endif
 
+/*
+ * futex_cleanup - After the task exists, process the robust lists
+ *
+ * Walk through the linked list, parsing robust lists and freeing the
+ * allocated lists. Lists created with the set_robust_list2 don't have a limit
+ * for sizing and can be destroyed while we walk on it to avoid circular list.
+ */
 static void futex_cleanup(struct task_struct *tsk)
 {
 	struct robust_list2_entry *curr, *n;
 	struct list_head *list2 = &tsk->robust_list2;
+	bool destroyable = true;
+	int i = 0;
 
 	/*
-	 * Walk through the linked list, parsing robust lists and freeing the
-	 * allocated lists
 	 */
 	if (unlikely(!list_empty(list2))) {
 		list_for_each_entry_safe(curr, n, list2, list) {
+			destroyable = true;
+			if (tsk->robust_list_index == i)
+				destroyable = false;
+
 			if (curr->head != NULL) {
 				if (curr->list_type == ROBUST_LIST_64BIT)
-					exit_robust_list64(tsk, curr->head);
+					exit_robust_list64(tsk, curr->head, destroyable);
 				else if (curr->list_type == ROBUST_LIST_32BIT)
-					exit_robust_list32(tsk, curr->head);
+					exit_robust_list32(tsk, curr->head, destroyable);
 				curr->head = NULL;
 			}
 			list_del_init(&curr->list);
 			kfree(curr);
+			i++;
 		}
 	}
 

-- 
2.49.0


^ permalink raw reply related

* [PATCH RESEND v4 2/7] selftests/futex: Create test for robust list
From: André Almeida @ 2025-06-17 18:34 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Peter Zijlstra, Darren Hart,
	Davidlohr Bueso, Shuah Khan, Arnd Bergmann,
	Sebastian Andrzej Siewior, Waiman Long
  Cc: linux-kernel, linux-kselftest, linux-api, kernel-dev,
	André Almeida
In-Reply-To: <20250617-tonyk-robust_futex-v4-0-6586f5fb9d33@igalia.com>

Create a test for the robust list mechanism. Test the following uAPI
operations:

- Creating a robust mutex where the lock waiter is wake by the kernel
  when the lock owner died
- Setting a robust list to the current task
- Getting a robust list from the current task
- Getting a robust list from another task
- Using the list_op_pending field from robust_list_head struct to test
  robustness when the lock owner dies before completing the locking
- Setting a invalid size for syscall argument `len`
- Adding multiple elements to a robust list wait waiting for each of
  them
- Creating a circular list and checking that the kernel does not get
  stuck in an infinity loop

This is the expected output:

 TAP version 13
 1..7
 ok 1 test_robustness
 ok 2 test_set_robust_list_invalid_size
 ok 3 test_get_robust_list_self
 ok 4 test_get_robust_list_child
 ok 5 test_set_list_op_pending
 ok 6 test_robust_list_multiple_elements
 ok 7 test_circular_list
 # Totals: pass:7 fail:0 xfail:0 xpass:0 skip:0 error:0

Signed-off-by: André Almeida <andrealmeid@igalia.com>
---
 .../testing/selftests/futex/functional/.gitignore  |   1 +
 tools/testing/selftests/futex/functional/Makefile  |   3 +-
 .../selftests/futex/functional/robust_list.c       | 554 +++++++++++++++++++++
 3 files changed, 557 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/futex/functional/.gitignore b/tools/testing/selftests/futex/functional/.gitignore
index 7b24ae89594a9db211d4b8469ebcef8d1f7012d8..7f447ebfbc62bbad9add0dc86a75abcdb8a4d9a7 100644
--- a/tools/testing/selftests/futex/functional/.gitignore
+++ b/tools/testing/selftests/futex/functional/.gitignore
@@ -11,3 +11,4 @@ futex_wait_timeout
 futex_wait_uninitialized_heap
 futex_wait_wouldblock
 futex_waitv
+robust_list
diff --git a/tools/testing/selftests/futex/functional/Makefile b/tools/testing/selftests/futex/functional/Makefile
index 8cfb87f7f7c5059c82f1e6290c076d3f13f5ea41..e6fa66e622dee4de74c31c8b9b486ca01de35737 100644
--- a/tools/testing/selftests/futex/functional/Makefile
+++ b/tools/testing/selftests/futex/functional/Makefile
@@ -20,7 +20,8 @@ TEST_GEN_PROGS := \
 	futex_priv_hash \
 	futex_numa_mpol \
 	futex_waitv \
-	futex_numa
+	futex_numa \
+	robust_list
 
 TEST_PROGS := run.sh
 
diff --git a/tools/testing/selftests/futex/functional/robust_list.c b/tools/testing/selftests/futex/functional/robust_list.c
new file mode 100644
index 0000000000000000000000000000000000000000..42690b2440fd29a9b12c46f67f9645ccc93d1147
--- /dev/null
+++ b/tools/testing/selftests/futex/functional/robust_list.c
@@ -0,0 +1,554 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2024 Igalia S.L.
+ *
+ * Robust list test by André Almeida <andrealmeid@igalia.com>
+ *
+ * The robust list uAPI allows userspace to create "robust" locks, in the sense
+ * that if the lock holder thread dies, the remaining threads that are waiting
+ * for the lock won't block forever, waiting for a lock that will never be
+ * released.
+ *
+ * This is achieve by userspace setting a list where a thread can enter all the
+ * locks (futexes) that it is holding. The robust list is a linked list, and
+ * userspace register the start of the list with the syscall set_robust_list().
+ * If such thread eventually dies, the kernel will walk this list, waking up one
+ * thread waiting for each futex and marking the futex word with the flag
+ * FUTEX_OWNER_DIED.
+ *
+ * See also
+ *	man set_robust_list
+ *	Documententation/locking/robust-futex-ABI.rst
+ *	Documententation/locking/robust-futexes.rst
+ */
+
+#define _GNU_SOURCE
+
+#include "futextest.h"
+#include "logging.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdatomic.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+
+#define STACK_SIZE (1024 * 1024)
+
+#define FUTEX_TIMEOUT 3
+
+static pthread_barrier_t barrier, barrier2;
+
+int set_robust_list(struct robust_list_head *head, size_t len)
+{
+	return syscall(SYS_set_robust_list, head, len);
+}
+
+int get_robust_list(int pid, struct robust_list_head **head, size_t *len_ptr)
+{
+	return syscall(SYS_get_robust_list, pid, head, len_ptr);
+}
+
+/*
+ * Basic lock struct, contains just the futex word and the robust list element
+ * Real implementations have also a *prev to easily walk in the list
+ */
+struct lock_struct {
+	_Atomic(unsigned int) futex;
+	struct robust_list list;
+};
+
+/*
+ * Helper function to spawn a child thread. Returns -1 on error, pid on success
+ */
+static int create_child(int (*fn)(void *arg), void *arg)
+{
+	char *stack;
+	pid_t pid;
+
+	stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE,
+		     MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
+	if (stack == MAP_FAILED)
+		return -1;
+
+	stack += STACK_SIZE;
+
+	pid = clone(fn, stack, CLONE_VM | SIGCHLD, arg);
+
+	if (pid == -1)
+		return -1;
+
+	return pid;
+}
+
+/*
+ * Helper function to prepare and register a robust list
+ */
+static int set_list(struct robust_list_head *head)
+{
+	int ret;
+
+	ret = set_robust_list(head, sizeof(struct robust_list_head));
+	if (ret)
+		return ret;
+
+	head->futex_offset = (size_t) offsetof(struct lock_struct, futex) -
+			     (size_t) offsetof(struct lock_struct, list);
+	head->list.next = &head->list;
+	head->list_op_pending = NULL;
+
+	return 0;
+}
+
+/*
+ * A basic (and incomplete) mutex lock function with robustness
+ */
+static int mutex_lock(struct lock_struct *lock, struct robust_list_head *head, bool error_inject)
+{
+	_Atomic(unsigned int) *futex = &lock->futex;
+	unsigned int zero = 0;
+	int ret = -1;
+	pid_t tid = gettid();
+
+	/*
+	 * Set list_op_pending before starting the lock, so the kernel can catch
+	 * the case where the thread died during the lock operation
+	 */
+	head->list_op_pending = &lock->list;
+
+	if (atomic_compare_exchange_strong(futex, &zero, tid)) {
+		/*
+		 * We took the lock, insert it in the robust list
+		 */
+		struct robust_list *list = &head->list;
+
+		/* Error injection to test list_op_pending */
+		if (error_inject)
+			return 0;
+
+		while (list->next != &head->list)
+			list = list->next;
+
+		list->next = &lock->list;
+		lock->list.next = &head->list;
+
+		ret = 0;
+	} else {
+		/*
+		 * We didn't take the lock, wait until the owner wakes (or dies)
+		 */
+		struct timespec to;
+
+		to.tv_sec = FUTEX_TIMEOUT;
+		to.tv_nsec = 0;
+
+		tid = atomic_load(futex);
+		/* Kernel ignores futexes without the waiters flag */
+		tid |= FUTEX_WAITERS;
+		atomic_store(futex, tid);
+
+		ret = futex_wait((futex_t *) futex, tid, &to, 0);
+
+		/*
+		 * A real mutex_lock() implementation would loop here to finally
+		 * take the lock. We don't care about that, so we stop here.
+		 */
+	}
+
+	head->list_op_pending = NULL;
+
+	return ret;
+}
+
+/*
+ * This child thread will succeed taking the lock, and then will exit holding it
+ */
+static int child_fn_lock(void *arg)
+{
+	struct lock_struct *lock = (struct lock_struct *) arg;
+	struct robust_list_head head;
+	int ret;
+
+	ret = set_list(&head);
+	if (ret)
+		ksft_test_result_fail("set_robust_list error\n");
+
+	ret = mutex_lock(lock, &head, false);
+	if (ret)
+		ksft_test_result_fail("mutex_lock error\n");
+
+	pthread_barrier_wait(&barrier);
+
+	/*
+	 * There's a race here: the parent thread needs to be inside
+	 * futex_wait() before the child thread dies, otherwise it will miss the
+	 * wakeup from handle_futex_death() that this child will emit. We wait a
+	 * little bit just to make sure that this happens.
+	 */
+	sleep(1);
+
+	return 0;
+}
+
+/*
+ * Spawns a child thread that will set a robust list, take the lock, register it
+ * in the robust list and die. The parent thread will wait on this futex, and
+ * should be waken up when the child exits.
+ */
+static void test_robustness(void)
+{
+	struct lock_struct lock = { .futex = 0 };
+	struct robust_list_head head;
+	_Atomic(unsigned int) *futex = &lock.futex;
+	int ret;
+
+	ret = set_list(&head);
+	ASSERT_EQ(ret, 0);
+
+	/*
+	 * Lets use a barrier to ensure that the child thread takes the lock
+	 * before the parent
+	 */
+	ret = pthread_barrier_init(&barrier, NULL, 2);
+	ASSERT_EQ(ret, 0);
+
+	ret = create_child(&child_fn_lock, &lock);
+	ASSERT_NE(ret, -1);
+
+	pthread_barrier_wait(&barrier);
+	ret = mutex_lock(&lock, &head, false);
+
+	/*
+	 * futex_wait() should return 0 and the futex word should be marked with
+	 * FUTEX_OWNER_DIED
+	 */
+	ASSERT_EQ(ret, 0);
+	if (ret != 0)
+		printf("futex wait returned %d", errno);
+
+	ASSERT_TRUE(*futex | FUTEX_OWNER_DIED);
+
+	wait(NULL);
+	pthread_barrier_destroy(&barrier);
+
+	ksft_test_result_pass("%s\n", __func__);
+}
+
+/*
+ * The only valid value for len is sizeof(*head)
+ */
+static void test_set_robust_list_invalid_size(void)
+{
+	struct robust_list_head head;
+	size_t head_size = sizeof(struct robust_list_head);
+	int ret;
+
+	ret = set_robust_list(&head, head_size);
+	ASSERT_EQ(ret, 0);
+
+	ret = set_robust_list(&head, head_size * 2);
+	ASSERT_EQ(ret, -1);
+	ASSERT_EQ(errno, EINVAL);
+
+	ret = set_robust_list(&head, head_size - 1);
+	ASSERT_EQ(ret, -1);
+	ASSERT_EQ(errno, EINVAL);
+
+	ret = set_robust_list(&head, 0);
+	ASSERT_EQ(ret, -1);
+	ASSERT_EQ(errno, EINVAL);
+
+	ksft_test_result_pass("%s\n", __func__);
+}
+
+/*
+ * Test get_robust_list with pid = 0, getting the list of the running thread
+ */
+static void test_get_robust_list_self(void)
+{
+	struct robust_list_head head, head2, *get_head;
+	size_t head_size = sizeof(struct robust_list_head), len_ptr;
+	int ret;
+
+	ret = set_robust_list(&head, head_size);
+	ASSERT_EQ(ret, 0);
+
+	ret = get_robust_list(0, &get_head, &len_ptr);
+	ASSERT_EQ(ret, 0);
+	ASSERT_EQ(get_head, &head);
+	ASSERT_EQ(head_size, len_ptr);
+
+	ret = set_robust_list(&head2, head_size);
+	ASSERT_EQ(ret, 0);
+
+	ret = get_robust_list(0, &get_head, &len_ptr);
+	ASSERT_EQ(ret, 0);
+	ASSERT_EQ(get_head, &head2);
+	ASSERT_EQ(head_size, len_ptr);
+
+	ksft_test_result_pass("%s\n", __func__);
+}
+
+static int child_list(void *arg)
+{
+	struct robust_list_head *head = (struct robust_list_head *) arg;
+	int ret;
+
+	ret = set_robust_list(head, sizeof(struct robust_list_head));
+	if (ret)
+		ksft_test_result_fail("set_robust_list error\n");
+
+	pthread_barrier_wait(&barrier);
+	pthread_barrier_wait(&barrier2);
+
+	return 0;
+}
+
+/*
+ * Test get_robust_list from another thread. We use two barriers here to ensure
+ * that:
+ *   1) the child thread set the list before we try to get it from the
+ * parent
+ *   2) the child thread still alive when we try to get the list from it
+ */
+static void test_get_robust_list_child(void)
+{
+	pid_t tid;
+	int ret;
+	struct robust_list_head head, *get_head;
+	size_t len_ptr;
+
+	ret = pthread_barrier_init(&barrier, NULL, 2);
+	ret = pthread_barrier_init(&barrier2, NULL, 2);
+	ASSERT_EQ(ret, 0);
+
+	tid = create_child(&child_list, &head);
+	ASSERT_NE(tid, -1);
+
+	pthread_barrier_wait(&barrier);
+
+	ret = get_robust_list(tid, &get_head, &len_ptr);
+	ASSERT_EQ(ret, 0);
+	ASSERT_EQ(&head, get_head);
+
+	pthread_barrier_wait(&barrier2);
+
+	wait(NULL);
+	pthread_barrier_destroy(&barrier);
+	pthread_barrier_destroy(&barrier2);
+
+	ksft_test_result_pass("%s\n", __func__);
+}
+
+static int child_fn_lock_with_error(void *arg)
+{
+	struct lock_struct *lock = (struct lock_struct *) arg;
+	struct robust_list_head head;
+	int ret;
+
+	ret = set_list(&head);
+	if (ret)
+		ksft_test_result_fail("set_robust_list error\n");
+
+	ret = mutex_lock(lock, &head, true);
+	if (ret)
+		ksft_test_result_fail("mutex_lock error\n");
+
+	pthread_barrier_wait(&barrier);
+
+	sleep(1);
+
+	return 0;
+}
+
+/*
+ * Same as robustness test, but inject an error where the mutex_lock() exits
+ * earlier, just after setting list_op_pending and taking the lock, to test the
+ * list_op_pending mechanism
+ */
+static void test_set_list_op_pending(void)
+{
+	struct lock_struct lock = { .futex = 0 };
+	struct robust_list_head head;
+	_Atomic(unsigned int) *futex = &lock.futex;
+	int ret;
+
+	ret = set_list(&head);
+	ASSERT_EQ(ret, 0);
+
+	ret = pthread_barrier_init(&barrier, NULL, 2);
+	ASSERT_EQ(ret, 0);
+
+	ret = create_child(&child_fn_lock_with_error, &lock);
+	ASSERT_NE(ret, -1);
+
+	pthread_barrier_wait(&barrier);
+	ret = mutex_lock(&lock, &head, false);
+
+	ASSERT_EQ(ret, 0);
+	if (ret != 0)
+		printf("futex wait returned %d", errno);
+
+	ASSERT_TRUE(*futex | FUTEX_OWNER_DIED);
+
+	wait(NULL);
+	pthread_barrier_destroy(&barrier);
+
+	ksft_test_result_pass("%s\n", __func__);
+}
+
+#define CHILD_NR 10
+
+static int child_lock_holder(void *arg)
+{
+	struct lock_struct *locks = (struct lock_struct *) arg;
+	struct robust_list_head head;
+	int i;
+
+	set_list(&head);
+
+	for (i = 0; i < CHILD_NR; i++) {
+		locks[i].futex = 0;
+		mutex_lock(&locks[i], &head, false);
+	}
+
+	pthread_barrier_wait(&barrier);
+	pthread_barrier_wait(&barrier2);
+
+	sleep(1);
+	return 0;
+}
+
+static int child_wait_lock(void *arg)
+{
+	struct lock_struct *lock = (struct lock_struct *) arg;
+	struct robust_list_head head;
+	int ret;
+
+	pthread_barrier_wait(&barrier2);
+	ret = mutex_lock(lock, &head, false);
+
+	if (ret)
+		ksft_test_result_fail("mutex_lock error\n");
+
+	if (!(lock->futex | FUTEX_OWNER_DIED))
+		ksft_test_result_fail("futex not marked with FUTEX_OWNER_DIED\n");
+
+	return 0;
+}
+
+/*
+ * Test a robust list of more than one element. All the waiters should wake when
+ * the holder dies
+ */
+static void test_robust_list_multiple_elements(void)
+{
+	struct lock_struct locks[CHILD_NR];
+	int i, ret;
+
+	ret = pthread_barrier_init(&barrier, NULL, 2);
+	ASSERT_EQ(ret, 0);
+	ret = pthread_barrier_init(&barrier2, NULL, CHILD_NR + 1);
+	ASSERT_EQ(ret, 0);
+
+	create_child(&child_lock_holder, &locks);
+
+	/* Wait until the locker thread takes the look */
+	pthread_barrier_wait(&barrier);
+
+	for (i = 0; i < CHILD_NR; i++)
+		create_child(&child_wait_lock, &locks[i]);
+
+	/* Wait for all children to return */
+	while (wait(NULL) > 0);
+
+	pthread_barrier_destroy(&barrier);
+	pthread_barrier_destroy(&barrier2);
+
+	ksft_test_result_pass("%s\n", __func__);
+}
+
+static int child_circular_list(void *arg)
+{
+	static struct robust_list_head head;
+	struct lock_struct a, b, c;
+	int ret;
+
+	ret = set_list(&head);
+	if (ret)
+		ksft_test_result_fail("set_list error\n");
+
+	head.list.next = &a.list;
+
+	/*
+	 * The last element should point to head list, but we short circuit it
+	 */
+	a.list.next = &b.list;
+	b.list.next = &c.list;
+	c.list.next = &a.list;
+
+	return 0;
+}
+
+/*
+ * Create a circular robust list. The kernel should be able to destroy the list
+ * while processing it so it won't be trapped in an infinite loop while handling
+ * a process exit
+ */
+static void test_circular_list(void)
+{
+	create_child(child_circular_list, NULL);
+
+	wait(NULL);
+
+	ksft_test_result_pass("%s\n", __func__);
+}
+
+void usage(char *prog)
+{
+	printf("Usage: %s\n", prog);
+	printf("  -c	Use color\n");
+	printf("  -h	Display this help message\n");
+	printf("  -v L	Verbosity level: %d=QUIET %d=CRITICAL %d=INFO\n",
+	       VQUIET, VCRITICAL, VINFO);
+}
+
+int main(int argc, char *argv[])
+{
+	int c;
+
+	while ((c = getopt(argc, argv, "cht:v:")) != -1) {
+		switch (c) {
+		case 'c':
+			log_color(1);
+			break;
+		case 'h':
+			usage(basename(argv[0]));
+			exit(0);
+		case 'v':
+			log_verbosity(atoi(optarg));
+			break;
+		default:
+			usage(basename(argv[0]));
+			exit(1);
+		}
+	}
+
+	ksft_print_header();
+	ksft_set_plan(7);
+
+	test_robustness();
+
+	test_set_robust_list_invalid_size();
+	test_get_robust_list_self();
+	test_get_robust_list_child();
+	test_set_list_op_pending();
+	test_robust_list_multiple_elements();
+	test_circular_list();
+
+	ksft_print_cnts();
+	return 0;
+}

-- 
2.49.0


^ permalink raw reply related

* [PATCH RESEND v4 4/7] futex: Create set_robust_list2
From: André Almeida @ 2025-06-17 18:34 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Peter Zijlstra, Darren Hart,
	Davidlohr Bueso, Shuah Khan, Arnd Bergmann,
	Sebastian Andrzej Siewior, Waiman Long
  Cc: linux-kernel, linux-kselftest, linux-api, kernel-dev,
	André Almeida
In-Reply-To: <20250617-tonyk-robust_futex-v4-0-6586f5fb9d33@igalia.com>

Create a new robust_list() syscall. The current syscall can't be
expanded to cover the following use case, so a new one is needed. This
new syscall allows users to set multiple robust lists per process and to
have either 32bit or 64bit pointers in the list.

* Interface

This is the proposed interface:

	long set_robust_list2(void *head, int index, unsigned int flags)

`head` is the head of the userspace struct robust_list_head, just as old
set_robust_list(). It needs to be a void pointer since it can point to a
normal robust_list_head or a compat_robust_list_head.

`flags` can be used for defining the list type:

	enum robust_list_type {
	 	ROBUST_LIST_32BIT,
		ROBUST_LIST_64BIT,
	 };

`index` is the index in the internal robust_list's linked list (the
naming starts to get confusing, I reckon). If `index == -1`, that means
that user wants to set a new robust_list, and the kernel will append it
in the end of the list, assign a new index and return this index to the
user. If `index >= 0`, that means that user wants to re-set `*head` of
an already existing list (similarly to what happens when you call
set_robust_list() twice with different `*head`).

If `index` is out of range, or it points to a non-existing robust_list,
or if the internal list is full, an error is returned.

Unaligned `head` addresses are refused by the kernel with -EINVAL.

User cannot remove lists.

* Implementation

The old syscall's set/get_robust_list() are converted to use the linked
list as well. When using only the old syscalls user shouldn't any
difference as the internal code will handle the linked list insertion as
usual. When mixing old and new interfaces users should be aware that one
of the elements of the list was created by another syscall and they
should have special care handling this element index.

On exit, the linked list is parsed and all robust lists regardless of
which interface it was used to create them are handled.

Signed-off-by: André Almeida <andrealmeid@igalia.com>
---
 include/linux/futex.h             |   5 +-
 include/linux/sched.h             |   5 +-
 include/uapi/asm-generic/unistd.h |   2 +
 include/uapi/linux/futex.h        |  24 +++++++++
 kernel/futex/core.c               | 111 ++++++++++++++++++++++++++++++--------
 kernel/futex/futex.h              |   5 ++
 kernel/futex/syscalls.c           |  81 ++++++++++++++++++++++++++--
 7 files changed, 204 insertions(+), 29 deletions(-)

diff --git a/include/linux/futex.h b/include/linux/futex.h
index cd7c5d12c846566c56f3f3ea74b95e437a6e8193..7721629926535c775bd7b05b5283a3d0b51262d6 100644
--- a/include/linux/futex.h
+++ b/include/linux/futex.h
@@ -75,10 +75,11 @@ enum {
 
 static inline void futex_init_task(struct task_struct *tsk)
 {
-	tsk->robust_list = NULL;
+	tsk->robust_list_index = -1;
 #ifdef CONFIG_COMPAT
-	tsk->compat_robust_list = NULL;
+	tsk->compat_robust_list_index = -1;
 #endif
+	INIT_LIST_HEAD(&tsk->robust_list2);
 	INIT_LIST_HEAD(&tsk->pi_state_list);
 	tsk->pi_state_cache = NULL;
 	tsk->futex_state = FUTEX_STATE_OK;
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 51e5d05a9fcd407dcd53b7b7cb8c59783660a826..a37c55cf0a4d942ec1fbedb8bcd4be5a3ebb20bb 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1322,10 +1322,11 @@ struct task_struct {
 	u32				rmid;
 #endif
 #ifdef CONFIG_FUTEX
-	struct robust_list_head __user	*robust_list;
+	int				robust_list_index;
 #ifdef CONFIG_COMPAT
-	struct robust_list_head32 __user *compat_robust_list;
+	int				compat_robust_list_index;
 #endif
+	struct list_head		robust_list2;
 	struct list_head		pi_state_list;
 	struct futex_pi_state		*pi_state_cache;
 	struct mutex			futex_exit_mutex;
diff --git a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/unistd.h
index 2892a45023af6d3eb941623d4fed04841ab07e02..ebe68c2c88eb5390dda184ce9268a8d3a606c9e5 100644
--- a/include/uapi/asm-generic/unistd.h
+++ b/include/uapi/asm-generic/unistd.h
@@ -852,6 +852,8 @@ __SYSCALL(__NR_removexattrat, sys_removexattrat)
 #define __NR_open_tree_attr 467
 __SYSCALL(__NR_open_tree_attr, sys_open_tree_attr)
 
+#define __NR_set_robust_list2 467
+
 #undef __NR_syscalls
 #define __NR_syscalls 468
 
diff --git a/include/uapi/linux/futex.h b/include/uapi/linux/futex.h
index 7e2744ec89336a260e89883e95222eda199eeb7f..cbd321eca03afb6bdcf47e9534761d82f9de7e43 100644
--- a/include/uapi/linux/futex.h
+++ b/include/uapi/linux/futex.h
@@ -153,6 +153,30 @@ struct robust_list_head {
 	struct robust_list __user *list_op_pending;
 };
 
+#define ROBUST_LISTS_PER_TASK 10
+
+enum robust_list2_type {
+	ROBUST_LIST_32BIT,
+	ROBUST_LIST_64BIT,
+};
+
+#define ROBUST_LIST_TYPE_MASK (ROBUST_LIST_32BIT | ROBUST_LIST_64BIT)
+
+/*
+ * This is an entry of a linked list of robust lists.
+ *
+ * @head: can point to a 64bit list or a 32bit list
+ * @list_type: determine the size of the futex pointers in the list
+ * @index: the index of this entry in the list
+ * @list: linked list element
+ */
+struct robust_list2_entry {
+	void __user *head;
+	enum robust_list2_type list_type;
+	unsigned int index;
+	struct list_head list;
+};
+
 /*
  * Are there any waiters for this robust futex:
  */
diff --git a/kernel/futex/core.c b/kernel/futex/core.c
index 8640770aadc611b7341a3abb41bdb740e6394479..49b3bc592948a811f995017027f33ad8f285531f 100644
--- a/kernel/futex/core.c
+++ b/kernel/futex/core.c
@@ -1151,9 +1151,9 @@ static inline int fetch_robust_entry(struct robust_list __user **entry,
  *
  * We silently return on any sign of list-walking problem.
  */
-static void exit_robust_list64(struct task_struct *curr)
+static void exit_robust_list64(struct task_struct *curr,
+			       struct robust_list_head __user *head)
 {
-	struct robust_list_head __user *head = curr->robust_list;
 	struct robust_list __user *entry, *next_entry, *pending;
 	unsigned int limit = ROBUST_LIST_LIMIT, pi, pip;
 	unsigned int next_pi;
@@ -1213,7 +1213,8 @@ static void exit_robust_list64(struct task_struct *curr)
 	}
 }
 #else
-static void exit_robust_list64(struct task_struct *curr)
+static void exit_robust_list64(struct task_struct *curr,
+			      struct robust_list_head __user *head)
 {
 	pr_warn("32bit kernel should not allow ROBUST_LIST_64BIT");
 }
@@ -1250,9 +1251,9 @@ fetch_robust_entry32(u32 *uentry, struct robust_list __user **entry,
  *
  * We silently return on any sign of list-walking problem.
  */
-static void exit_robust_list32(struct task_struct *curr)
+static void exit_robust_list32(struct task_struct *curr,
+			       struct robust_list_head32 __user *head)
 {
-	struct robust_list_head32 __user *head = curr->compat_robust_list;
 	struct robust_list __user *entry, *next_entry, *pending;
 	unsigned int limit = ROBUST_LIST_LIMIT, pi, pip;
 	unsigned int next_pi;
@@ -1318,6 +1319,70 @@ static void exit_robust_list32(struct task_struct *curr)
 	}
 }
 
+long do_set_robust_list2(struct robust_list_head __user *head,
+			 int index, unsigned int type)
+{
+	struct list_head *list2 = &current->robust_list2;
+	struct robust_list2_entry *prev, *new = NULL;
+
+	if (index == -1) {
+		if (list_empty(list2)) {
+			index = 0;
+		} else {
+			prev = list_last_entry(list2, struct robust_list2_entry, list);
+			index = prev->index + 1;
+		}
+
+		if (index >= ROBUST_LISTS_PER_TASK)
+			return -EINVAL;
+
+		new = kmalloc(sizeof(struct robust_list2_entry), GFP_KERNEL);
+		if (!new)
+			return -ENOMEM;
+
+		list_add_tail(&new->list, list2);
+		new->index = index;
+
+	} else if (index >= 0) {
+		struct robust_list2_entry *curr;
+
+		if (list_empty(list2))
+			return -ENOENT;
+
+		list_for_each_entry(curr, list2, list) {
+			if (index == curr->index) {
+				new = curr;
+				break;
+			}
+		}
+
+		if (!new)
+			return -ENOENT;
+	}
+
+	BUG_ON(!new);
+	new->head = head;
+	new->list_type = type;
+
+	return index;
+}
+
+struct robust_list_head __user *get_robust_list2(int index, struct task_struct *task)
+{
+	struct list_head *list2 = &task->robust_list2;
+	struct robust_list2_entry *curr;
+
+	if (list_empty(list2) || index == -1)
+		return NULL;
+
+	list_for_each_entry(curr, list2, list) {
+		if (index == curr->index)
+			return curr->head;
+	}
+
+	return NULL;
+}
+
 #ifdef CONFIG_FUTEX_PI
 
 /*
@@ -1411,24 +1476,28 @@ static inline void exit_pi_state_list(struct task_struct *curr) { }
 
 static void futex_cleanup(struct task_struct *tsk)
 {
-#ifdef CONFIG_64BIT
-	if (unlikely(tsk->robust_list)) {
-		exit_robust_list64(tsk);
-		tsk->robust_list = NULL;
-	}
-#else
-	if (unlikely(tsk->robust_list)) {
-		exit_robust_list32(tsk);
-		tsk->robust_list = NULL;
-	}
-#endif
+	struct robust_list2_entry *curr, *n;
+	struct list_head *list2 = &tsk->robust_list2;
 
-#ifdef CONFIG_COMPAT
-	if (unlikely(tsk->compat_robust_list)) {
-		exit_robust_list32(tsk);
-		tsk->compat_robust_list = NULL;
+	/*
+	 * Walk through the linked list, parsing robust lists and freeing the
+	 * allocated lists
+	 */
+	if (unlikely(!list_empty(list2))) {
+		list_for_each_entry_safe(curr, n, list2, list) {
+			if (curr->head != NULL) {
+				if (curr->list_type == ROBUST_LIST_64BIT)
+					exit_robust_list64(tsk, curr->head);
+				else if (curr->list_type == ROBUST_LIST_32BIT)
+					exit_robust_list32(tsk, curr->head);
+				curr->head = NULL;
+			}
+			list_del_init(&curr->list);
+			kfree(curr);
+		}
 	}
-#endif
+
+	tsk->robust_list_index = -1;
 
 	if (unlikely(!list_empty(&tsk->pi_state_list)))
 		exit_pi_state_list(tsk);
diff --git a/kernel/futex/futex.h b/kernel/futex/futex.h
index fcd1617212eed0e3c2367d2b463a0e019eda6d13..67201e51fa1798a21ff68f60b1e35977b9bd267b 100644
--- a/kernel/futex/futex.h
+++ b/kernel/futex/futex.h
@@ -467,6 +467,11 @@ extern int __futex_wait(u32 __user *uaddr, unsigned int flags, u32 val,
 extern int futex_wait(u32 __user *uaddr, unsigned int flags, u32 val,
 		      ktime_t *abs_time, u32 bitset);
 
+extern long do_set_robust_list2(struct robust_list_head __user *head,
+			 int index, unsigned int type);
+
+extern struct robust_list_head __user *get_robust_list2(int index, struct task_struct *task);
+
 /**
  * struct futex_vector - Auxiliary struct for futex_waitv()
  * @w: Userspace provided data
diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c
index dba193dfd216cc929c8f4d979aa2bcd99237e2d8..56ee1123cbd8ea26c8d22aa74e5faed2974ec577 100644
--- a/kernel/futex/syscalls.c
+++ b/kernel/futex/syscalls.c
@@ -20,6 +20,18 @@
  * the list. There can only be one such pending lock.
  */
 
+#ifdef CONFIG_64BIT
+static inline int robust_list_native_type(void)
+{
+	return ROBUST_LIST_64BIT;
+}
+#else
+static inline int robust_list_native_type(void)
+{
+	return ROBUST_LIST_32BIT;
+}
+#endif
+
 /**
  * sys_set_robust_list() - Set the robust-futex list head of a task
  * @head:	pointer to the list-head
@@ -28,17 +40,63 @@
 SYSCALL_DEFINE2(set_robust_list, struct robust_list_head __user *, head,
 		size_t, len)
 {
+	unsigned int type = robust_list_native_type();
+	int ret;
+
 	/*
 	 * The kernel knows only one size for now:
 	 */
 	if (unlikely(len != sizeof(*head)))
 		return -EINVAL;
 
-	current->robust_list = head;
+	ret = do_set_robust_list2(head, current->robust_list_index, type);
+	if (ret < 0)
+		return ret;
+
+	current->robust_list_index = ret;
 
 	return 0;
 }
 
+#define ROBUST_LIST_FLAGS ROBUST_LIST_TYPE_MASK
+
+/*
+ * sys_set_robust_list2()
+ *
+ * When index == -1, create a new list for user. When index >= 0, try to find
+ * the corresponding list and re-set the head there.
+ *
+ * Return values:
+ *  >= 0: success, index of the robust list
+ *  -EINVAL: invalid flags, invalid index
+ *  -ENOENT: requested index no where to be found
+ *  -ENOMEM: error allocating new list
+ *  -ESRCH: too many allocated lists
+ */
+SYSCALL_DEFINE3(set_robust_list2, struct robust_list_head __user *, head,
+		int, index, unsigned int, flags)
+{
+	unsigned int type;
+
+	type = flags & ROBUST_LIST_TYPE_MASK;
+
+	if (index < -1 || index >= ROBUST_LISTS_PER_TASK)
+		return -EINVAL;
+
+	if ((flags & ~ROBUST_LIST_FLAGS) != 0)
+		return -EINVAL;
+
+	if (((uintptr_t) head % sizeof(u32)) != 0)
+		return -EINVAL;
+
+#ifndef CONFIG_64BIT
+	if (type == ROBUST_LIST_64BIT)
+		return -EINVAL;
+#endif
+
+	return do_set_robust_list2(head, index, type);
+}
+
 /**
  * sys_get_robust_list() - Get the robust-futex list head of a task
  * @pid:	pid of the process [zero for current task]
@@ -52,6 +110,7 @@ SYSCALL_DEFINE3(get_robust_list, int, pid,
 	struct robust_list_head __user *head;
 	unsigned long ret;
 	struct task_struct *p;
+	int index;
 
 	rcu_read_lock();
 
@@ -68,9 +127,11 @@ SYSCALL_DEFINE3(get_robust_list, int, pid,
 	if (!ptrace_may_access(p, PTRACE_MODE_READ_REALCREDS))
 		goto err_unlock;
 
-	head = p->robust_list;
+	index = p->robust_list_index;
 	rcu_read_unlock();
 
+	head = get_robust_list2(index, p);
+
 	if (put_user(sizeof(*head), len_ptr))
 		return -EFAULT;
 	return put_user(head, head_ptr);
@@ -443,10 +504,19 @@ COMPAT_SYSCALL_DEFINE2(set_robust_list,
 		struct robust_list_head32 __user *, head,
 		compat_size_t, len)
 {
+	unsigned int type = ROBUST_LIST_32BIT;
+	int ret;
+
 	if (unlikely(len != sizeof(*head)))
 		return -EINVAL;
 
-	current->compat_robust_list = head;
+	ret = do_set_robust_list2((struct robust_list_head __user *) head,
+				  current->robust_list_index, type);
+	if (ret < 0)
+		return ret;
+
+	current->robust_list_index = ret;
+
 
 	return 0;
 }
@@ -458,6 +528,7 @@ COMPAT_SYSCALL_DEFINE3(get_robust_list, int, pid,
 	struct robust_list_head32 __user *head;
 	unsigned long ret;
 	struct task_struct *p;
+	int index;
 
 	rcu_read_lock();
 
@@ -474,9 +545,11 @@ COMPAT_SYSCALL_DEFINE3(get_robust_list, int, pid,
 	if (!ptrace_may_access(p, PTRACE_MODE_READ_REALCREDS))
 		goto err_unlock;
 
-	head = p->compat_robust_list;
+	index = p->compat_robust_list_index;
 	rcu_read_unlock();
 
+	head = (struct robust_list_head32 __user *) get_robust_list2(index, p);
+
 	if (put_user(sizeof(*head), len_ptr))
 		return -EFAULT;
 	return put_user(ptr_to_compat(head), head_ptr);

-- 
2.49.0


^ permalink raw reply related

* [PATCH RESEND v4 3/7] futex: Use explicit sizes for compat_exit_robust_list
From: André Almeida @ 2025-06-17 18:34 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Peter Zijlstra, Darren Hart,
	Davidlohr Bueso, Shuah Khan, Arnd Bergmann,
	Sebastian Andrzej Siewior, Waiman Long
  Cc: linux-kernel, linux-kselftest, linux-api, kernel-dev,
	André Almeida
In-Reply-To: <20250617-tonyk-robust_futex-v4-0-6586f5fb9d33@igalia.com>

There are two functions for handling robust lists during the task
exit: exit_robust_list() and compat_exit_robust_list(). The first one
handles either 64bit or 32bit lists, depending if it's a 64bit or 32bit
kernel. The compat_exit_robust_list() only exists in 64bit kernels that
supports 32bit syscalls, and handles 32bit lists.

For the new syscall set_robust_list2(), 64bit kernels need to be able to
handle 32bit lists despite having or not support for 32bit syscalls, so
make compat_exit_robust_list() exist regardless of compat_ config.

Also, use explicitly sizing, otherwise in a 32bit kernel both
exit_robust_list() and compat_exit_robust_list() would be the exactly
same function, with none of them dealing with 64bit robust lists.

Signed-off-by: André Almeida <andrealmeid@igalia.com>
---
 include/linux/compat.h  | 12 +-----------
 include/linux/futex.h   | 11 +++++++++++
 include/linux/sched.h   |  2 +-
 kernel/futex/core.c     | 44 ++++++++++++++++++++++++++++----------------
 kernel/futex/syscalls.c |  4 ++--
 5 files changed, 43 insertions(+), 30 deletions(-)

diff --git a/include/linux/compat.h b/include/linux/compat.h
index 56cebaff0c910fda853a0e2b3d6d0517e55f8b38..968a9135ff486cf9c8be2a18b80cd4c46e890236 100644
--- a/include/linux/compat.h
+++ b/include/linux/compat.h
@@ -385,16 +385,6 @@ struct compat_ifconf {
 	compat_caddr_t  ifcbuf;
 };
 
-struct compat_robust_list {
-	compat_uptr_t			next;
-};
-
-struct compat_robust_list_head {
-	struct compat_robust_list	list;
-	compat_long_t			futex_offset;
-	compat_uptr_t			list_op_pending;
-};
-
 #ifdef CONFIG_COMPAT_OLD_SIGACTION
 struct compat_old_sigaction {
 	compat_uptr_t			sa_handler;
@@ -672,7 +662,7 @@ asmlinkage long compat_sys_waitid(int, compat_pid_t,
 		struct compat_siginfo __user *, int,
 		struct compat_rusage __user *);
 asmlinkage long
-compat_sys_set_robust_list(struct compat_robust_list_head __user *head,
+compat_sys_set_robust_list(struct robust_list_head32 __user *head,
 			   compat_size_t len);
 asmlinkage long
 compat_sys_get_robust_list(int pid, compat_uptr_t __user *head_ptr,
diff --git a/include/linux/futex.h b/include/linux/futex.h
index 168ffd5996b4808491c05bdc7c8d0aeca1d37ee5..cd7c5d12c846566c56f3f3ea74b95e437a6e8193 100644
--- a/include/linux/futex.h
+++ b/include/linux/futex.h
@@ -56,6 +56,17 @@ union futex_key {
 #define FUTEX_KEY_INIT (union futex_key) { .both = { .ptr = 0ULL } }
 
 #ifdef CONFIG_FUTEX
+
+struct robust_list32 {
+	u32 next;
+};
+
+struct robust_list_head32 {
+	struct robust_list32	list;
+	s32			futex_offset;
+	u32			list_op_pending;
+};
+
 enum {
 	FUTEX_STATE_OK,
 	FUTEX_STATE_EXITING,
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 45e5953b8f326c2ff5e19de469d6cba27cc4c17d..51e5d05a9fcd407dcd53b7b7cb8c59783660a826 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1324,7 +1324,7 @@ struct task_struct {
 #ifdef CONFIG_FUTEX
 	struct robust_list_head __user	*robust_list;
 #ifdef CONFIG_COMPAT
-	struct compat_robust_list_head __user *compat_robust_list;
+	struct robust_list_head32 __user *compat_robust_list;
 #endif
 	struct list_head		pi_state_list;
 	struct futex_pi_state		*pi_state_cache;
diff --git a/kernel/futex/core.c b/kernel/futex/core.c
index 19a2c65f3d373c0b60c864a6fe0604787221d342..8640770aadc611b7341a3abb41bdb740e6394479 100644
--- a/kernel/futex/core.c
+++ b/kernel/futex/core.c
@@ -1144,13 +1144,14 @@ static inline int fetch_robust_entry(struct robust_list __user **entry,
 	return 0;
 }
 
+#ifdef CONFIG_64BIT
 /*
  * Walk curr->robust_list (very carefully, it's a userspace list!)
  * and mark any locks found there dead, and notify any waiters.
  *
  * We silently return on any sign of list-walking problem.
  */
-static void exit_robust_list(struct task_struct *curr)
+static void exit_robust_list64(struct task_struct *curr)
 {
 	struct robust_list_head __user *head = curr->robust_list;
 	struct robust_list __user *entry, *next_entry, *pending;
@@ -1211,8 +1212,13 @@ static void exit_robust_list(struct task_struct *curr)
 				   curr, pip, HANDLE_DEATH_PENDING);
 	}
 }
+#else
+static void exit_robust_list64(struct task_struct *curr)
+{
+	pr_warn("32bit kernel should not allow ROBUST_LIST_64BIT");
+}
+#endif
 
-#ifdef CONFIG_COMPAT
 static void __user *futex_uaddr(struct robust_list __user *entry,
 				compat_long_t futex_offset)
 {
@@ -1226,13 +1232,13 @@ static void __user *futex_uaddr(struct robust_list __user *entry,
  * Fetch a robust-list pointer. Bit 0 signals PI futexes:
  */
 static inline int
-compat_fetch_robust_entry(compat_uptr_t *uentry, struct robust_list __user **entry,
-		   compat_uptr_t __user *head, unsigned int *pi)
+fetch_robust_entry32(u32 *uentry, struct robust_list __user **entry,
+		     u32 __user *head, unsigned int *pi)
 {
 	if (get_user(*uentry, head))
 		return -EFAULT;
 
-	*entry = compat_ptr((*uentry) & ~1);
+	*entry = (void __user *)(unsigned long)((*uentry) & ~1);
 	*pi = (unsigned int)(*uentry) & 1;
 
 	return 0;
@@ -1244,21 +1250,21 @@ compat_fetch_robust_entry(compat_uptr_t *uentry, struct robust_list __user **ent
  *
  * We silently return on any sign of list-walking problem.
  */
-static void compat_exit_robust_list(struct task_struct *curr)
+static void exit_robust_list32(struct task_struct *curr)
 {
-	struct compat_robust_list_head __user *head = curr->compat_robust_list;
+	struct robust_list_head32 __user *head = curr->compat_robust_list;
 	struct robust_list __user *entry, *next_entry, *pending;
 	unsigned int limit = ROBUST_LIST_LIMIT, pi, pip;
 	unsigned int next_pi;
-	compat_uptr_t uentry, next_uentry, upending;
-	compat_long_t futex_offset;
+	u32 uentry, next_uentry, upending;
+	s32 futex_offset;
 	int rc;
 
 	/*
 	 * Fetch the list head (which was registered earlier, via
 	 * sys_set_robust_list()):
 	 */
-	if (compat_fetch_robust_entry(&uentry, &entry, &head->list.next, &pi))
+	if (fetch_robust_entry32((u32 *)&uentry, &entry, (u32 *)&head->list.next, &pi))
 		return;
 	/*
 	 * Fetch the relative futex offset:
@@ -1269,7 +1275,7 @@ static void compat_exit_robust_list(struct task_struct *curr)
 	 * Fetch any possibly pending lock-add first, and handle it
 	 * if it exists:
 	 */
-	if (compat_fetch_robust_entry(&upending, &pending,
+	if (fetch_robust_entry32(&upending, &pending,
 			       &head->list_op_pending, &pip))
 		return;
 
@@ -1279,8 +1285,8 @@ static void compat_exit_robust_list(struct task_struct *curr)
 		 * Fetch the next entry in the list before calling
 		 * handle_futex_death:
 		 */
-		rc = compat_fetch_robust_entry(&next_uentry, &next_entry,
-			(compat_uptr_t __user *)&entry->next, &next_pi);
+		rc = fetch_robust_entry32(&next_uentry, &next_entry,
+			(u32 __user *)&entry->next, &next_pi);
 		/*
 		 * A pending lock might already be on the list, so
 		 * dont process it twice:
@@ -1311,7 +1317,6 @@ static void compat_exit_robust_list(struct task_struct *curr)
 		handle_futex_death(uaddr, curr, pip, HANDLE_DEATH_PENDING);
 	}
 }
-#endif
 
 #ifdef CONFIG_FUTEX_PI
 
@@ -1406,14 +1411,21 @@ static inline void exit_pi_state_list(struct task_struct *curr) { }
 
 static void futex_cleanup(struct task_struct *tsk)
 {
+#ifdef CONFIG_64BIT
 	if (unlikely(tsk->robust_list)) {
-		exit_robust_list(tsk);
+		exit_robust_list64(tsk);
 		tsk->robust_list = NULL;
 	}
+#else
+	if (unlikely(tsk->robust_list)) {
+		exit_robust_list32(tsk);
+		tsk->robust_list = NULL;
+	}
+#endif
 
 #ifdef CONFIG_COMPAT
 	if (unlikely(tsk->compat_robust_list)) {
-		compat_exit_robust_list(tsk);
+		exit_robust_list32(tsk);
 		tsk->compat_robust_list = NULL;
 	}
 #endif
diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c
index 4b6da9116aa6c33db9796e3055ce0c90b02d7b91..dba193dfd216cc929c8f4d979aa2bcd99237e2d8 100644
--- a/kernel/futex/syscalls.c
+++ b/kernel/futex/syscalls.c
@@ -440,7 +440,7 @@ SYSCALL_DEFINE4(futex_requeue,
 
 #ifdef CONFIG_COMPAT
 COMPAT_SYSCALL_DEFINE2(set_robust_list,
-		struct compat_robust_list_head __user *, head,
+		struct robust_list_head32 __user *, head,
 		compat_size_t, len)
 {
 	if (unlikely(len != sizeof(*head)))
@@ -455,7 +455,7 @@ COMPAT_SYSCALL_DEFINE3(get_robust_list, int, pid,
 			compat_uptr_t __user *, head_ptr,
 			compat_size_t __user *, len_ptr)
 {
-	struct compat_robust_list_head __user *head;
+	struct robust_list_head32 __user *head;
 	unsigned long ret;
 	struct task_struct *p;
 

-- 
2.49.0


^ permalink raw reply related

* [PATCH RESEND v4 5/7] futex: Wire up set_robust_list2 syscall
From: André Almeida @ 2025-06-17 18:34 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Peter Zijlstra, Darren Hart,
	Davidlohr Bueso, Shuah Khan, Arnd Bergmann,
	Sebastian Andrzej Siewior, Waiman Long
  Cc: linux-kernel, linux-kselftest, linux-api, kernel-dev,
	André Almeida
In-Reply-To: <20250617-tonyk-robust_futex-v4-0-6586f5fb9d33@igalia.com>

Wire up the new set_robust_list2 syscall in all available architectures.

Signed-off-by: André Almeida <andrealmeid@igalia.com>
---
 arch/alpha/kernel/syscalls/syscall.tbl      | 1 +
 arch/arm/tools/syscall.tbl                  | 1 +
 arch/m68k/kernel/syscalls/syscall.tbl       | 1 +
 arch/microblaze/kernel/syscalls/syscall.tbl | 1 +
 arch/mips/kernel/syscalls/syscall_n32.tbl   | 1 +
 arch/mips/kernel/syscalls/syscall_n64.tbl   | 1 +
 arch/mips/kernel/syscalls/syscall_o32.tbl   | 1 +
 arch/parisc/kernel/syscalls/syscall.tbl     | 1 +
 arch/powerpc/kernel/syscalls/syscall.tbl    | 1 +
 arch/s390/kernel/syscalls/syscall.tbl       | 1 +
 arch/sh/kernel/syscalls/syscall.tbl         | 1 +
 arch/sparc/kernel/syscalls/syscall.tbl      | 1 +
 arch/x86/entry/syscalls/syscall_32.tbl      | 1 +
 arch/x86/entry/syscalls/syscall_64.tbl      | 1 +
 arch/xtensa/kernel/syscalls/syscall.tbl     | 1 +
 kernel/sys_ni.c                             | 1 +
 scripts/syscall.tbl                         | 1 +
 17 files changed, 17 insertions(+)

diff --git a/arch/alpha/kernel/syscalls/syscall.tbl b/arch/alpha/kernel/syscalls/syscall.tbl
index 2dd6340de6b4efddc406f0c235701c15cf02f650..aecc167ac7706d25da73db8099f0813e268b820c 100644
--- a/arch/alpha/kernel/syscalls/syscall.tbl
+++ b/arch/alpha/kernel/syscalls/syscall.tbl
@@ -507,3 +507,4 @@
 575	common	listxattrat			sys_listxattrat
 576	common	removexattrat			sys_removexattrat
 577	common	open_tree_attr			sys_open_tree_attr
+578	common	set_robust_list2		sys_robust_list2
diff --git a/arch/arm/tools/syscall.tbl b/arch/arm/tools/syscall.tbl
index 27c1d5ebcd91c8c296dc6676307f66bfdf4ab78d..2e47ae5dc9a426d8e5e9dacf29caa54223cf2f5a 100644
--- a/arch/arm/tools/syscall.tbl
+++ b/arch/arm/tools/syscall.tbl
@@ -482,3 +482,4 @@
 465	common	listxattrat			sys_listxattrat
 466	common	removexattrat			sys_removexattrat
 467	common	open_tree_attr			sys_open_tree_attr
+468	common	set_robust_list2		sys_set_robust_list2
diff --git a/arch/m68k/kernel/syscalls/syscall.tbl b/arch/m68k/kernel/syscalls/syscall.tbl
index 9fe47112c586f152662af38a9a7f90957cb96cf8..7bcc8cc628c80a44fea2b53d5c69ab5e5f10a1d2 100644
--- a/arch/m68k/kernel/syscalls/syscall.tbl
+++ b/arch/m68k/kernel/syscalls/syscall.tbl
@@ -467,3 +467,4 @@
 465	common	listxattrat			sys_listxattrat
 466	common	removexattrat			sys_removexattrat
 467	common	open_tree_attr			sys_open_tree_attr
+468	common  set_robust_list2		sys_set_robust_list2
diff --git a/arch/microblaze/kernel/syscalls/syscall.tbl b/arch/microblaze/kernel/syscalls/syscall.tbl
index 7b6e97828e552d4da90046ddfcd4a55723e522bb..cd23608afe7e7dadfbf8e21df0486b85bfcb99ce 100644
--- a/arch/microblaze/kernel/syscalls/syscall.tbl
+++ b/arch/microblaze/kernel/syscalls/syscall.tbl
@@ -473,3 +473,4 @@
 465	common	listxattrat			sys_listxattrat
 466	common	removexattrat			sys_removexattrat
 467	common	open_tree_attr			sys_open_tree_attr
+468	common	set_robust_list2		sys_set_robust_list2
diff --git a/arch/mips/kernel/syscalls/syscall_n32.tbl b/arch/mips/kernel/syscalls/syscall_n32.tbl
index aa70e371bb54ab5d9c8dd8923b6ecf9693ee914d..0a31452ef6ed8fee8f1e2ead5d44acfbbe275fe9 100644
--- a/arch/mips/kernel/syscalls/syscall_n32.tbl
+++ b/arch/mips/kernel/syscalls/syscall_n32.tbl
@@ -406,3 +406,4 @@
 465	n32	listxattrat			sys_listxattrat
 466	n32	removexattrat			sys_removexattrat
 467	n32	open_tree_attr			sys_open_tree_attr
+468	n32	set_robust_list2		sys_set_robust_list2
diff --git a/arch/mips/kernel/syscalls/syscall_n64.tbl b/arch/mips/kernel/syscalls/syscall_n64.tbl
index 1e8c44c7b61492eabf00c777831e457a7a6e579c..4cb5a72256338f6fb407f940f1883d523113d609 100644
--- a/arch/mips/kernel/syscalls/syscall_n64.tbl
+++ b/arch/mips/kernel/syscalls/syscall_n64.tbl
@@ -382,3 +382,4 @@
 465	n64	listxattrat			sys_listxattrat
 466	n64	removexattrat			sys_removexattrat
 467	n64	open_tree_attr			sys_open_tree_attr
+468	n64	set_robust_list2		sys_set_robust_list2
diff --git a/arch/mips/kernel/syscalls/syscall_o32.tbl b/arch/mips/kernel/syscalls/syscall_o32.tbl
index 114a5a1a62302e32dd74d1679ff423a2d57c3c6b..c46238e9edd00d2861edcfa87c5ce7a62bfdc3d4 100644
--- a/arch/mips/kernel/syscalls/syscall_o32.tbl
+++ b/arch/mips/kernel/syscalls/syscall_o32.tbl
@@ -455,3 +455,4 @@
 465	o32	listxattrat			sys_listxattrat
 466	o32	removexattrat			sys_removexattrat
 467	o32	open_tree_attr			sys_open_tree_attr
+468	o32	set_robust_list2		sys_set_robust_list2
diff --git a/arch/parisc/kernel/syscalls/syscall.tbl b/arch/parisc/kernel/syscalls/syscall.tbl
index 94df3cb957e9d547d192e8732c0cf23ef2b5ce5d..71071489a18375013bbfbe26578a634283c1e07b 100644
--- a/arch/parisc/kernel/syscalls/syscall.tbl
+++ b/arch/parisc/kernel/syscalls/syscall.tbl
@@ -466,3 +466,4 @@
 465	common	listxattrat			sys_listxattrat
 466	common	removexattrat			sys_removexattrat
 467	common	open_tree_attr			sys_open_tree_attr
+468	common	set_robust_list2		sys_set_robust_list2
diff --git a/arch/powerpc/kernel/syscalls/syscall.tbl b/arch/powerpc/kernel/syscalls/syscall.tbl
index 9a084bdb892694bc562f514b55212d167cbac12f..edc4d0bef3f1c7ab826ea8180e7f5ceba4774c07 100644
--- a/arch/powerpc/kernel/syscalls/syscall.tbl
+++ b/arch/powerpc/kernel/syscalls/syscall.tbl
@@ -558,3 +558,4 @@
 465	common	listxattrat			sys_listxattrat
 466	common	removexattrat			sys_removexattrat
 467	common	open_tree_attr			sys_open_tree_attr
+468	common	set_robust_list2		sys_set_robust_list2
diff --git a/arch/s390/kernel/syscalls/syscall.tbl b/arch/s390/kernel/syscalls/syscall.tbl
index a4569b96ef06c54ce7aa795d039541c90a38284f..ff8c594073ec8c3486cc61544d14a338d3f3a906 100644
--- a/arch/s390/kernel/syscalls/syscall.tbl
+++ b/arch/s390/kernel/syscalls/syscall.tbl
@@ -470,3 +470,4 @@
 465  common	listxattrat		sys_listxattrat			sys_listxattrat
 466  common	removexattrat		sys_removexattrat		sys_removexattrat
 467  common	open_tree_attr		sys_open_tree_attr		sys_open_tree_attr
+468  common	set_robust_list2	sys_set_robust_list2		sys_set_robust_list2
diff --git a/arch/sh/kernel/syscalls/syscall.tbl b/arch/sh/kernel/syscalls/syscall.tbl
index 52a7652fcff6394b96ace1f3b0ed72250ee5e669..507789194570a9e7b492b210be30bb41021be289 100644
--- a/arch/sh/kernel/syscalls/syscall.tbl
+++ b/arch/sh/kernel/syscalls/syscall.tbl
@@ -471,3 +471,4 @@
 465	common	listxattrat			sys_listxattrat
 466	common	removexattrat			sys_removexattrat
 467	common	open_tree_attr			sys_open_tree_attr
+468	common	set_robust_list2		sys_set_robust_list2
diff --git a/arch/sparc/kernel/syscalls/syscall.tbl b/arch/sparc/kernel/syscalls/syscall.tbl
index 83e45eb6c095a36baaf749927628e6052fe900e6..8d1122c2235b8d5082a11392e68787efe55f58be 100644
--- a/arch/sparc/kernel/syscalls/syscall.tbl
+++ b/arch/sparc/kernel/syscalls/syscall.tbl
@@ -513,3 +513,4 @@
 465	common	listxattrat			sys_listxattrat
 466	common	removexattrat			sys_removexattrat
 467	common	open_tree_attr			sys_open_tree_attr
+468	common	set_robust_list2		sys_set_robust_list2
diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
index ac007ea00979dc28b0ef7c002a0615ce86dd3101..cbc0c469e66ecf7b8a61e82c38b07ecc63f6fe23 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -473,3 +473,4 @@
 465	i386	listxattrat		sys_listxattrat
 466	i386	removexattrat		sys_removexattrat
 467	i386	open_tree_attr		sys_open_tree_attr
+468	i386	set_robust_list2	sys_set_robust_list2
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index cfb5ca41e30de1a4e073750096f5b51a2ec137d2..b420217c72fc50ad90f291812972019606c5ff69 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -391,6 +391,7 @@
 465	common	listxattrat		sys_listxattrat
 466	common	removexattrat		sys_removexattrat
 467	common	open_tree_attr		sys_open_tree_attr
+468	common	set_robust_list2	sys_set_robust_list2
 
 #
 # Due to a historical design error, certain syscalls are numbered differently
diff --git a/arch/xtensa/kernel/syscalls/syscall.tbl b/arch/xtensa/kernel/syscalls/syscall.tbl
index f657a77314f8667fa019a01e10c84ea270024adc..6b852ee8a1621c7dd24f6cd37fd990f5ff8d8527 100644
--- a/arch/xtensa/kernel/syscalls/syscall.tbl
+++ b/arch/xtensa/kernel/syscalls/syscall.tbl
@@ -438,3 +438,4 @@
 465	common	listxattrat			sys_listxattrat
 466	common	removexattrat			sys_removexattrat
 467	common	open_tree_attr			sys_open_tree_attr
+468	common	set_robust_list2		sys_set_robust_list2
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index c00a86931f8c6cb30d35a9d56cbcc5994add90e1..71fbac6176c8886f4fa8dd437b0aedd5f14e9f74 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -195,6 +195,7 @@ COND_SYSCALL(move_pages);
 COND_SYSCALL(set_mempolicy_home_node);
 COND_SYSCALL(cachestat);
 COND_SYSCALL(mseal);
+COND_SYSCALL(set_robust_list2);
 
 COND_SYSCALL(perf_event_open);
 COND_SYSCALL(accept4);
diff --git a/scripts/syscall.tbl b/scripts/syscall.tbl
index 580b4e246aecd5f07d542943ba68fc4ed5961660..07d7e776d0329659e70a9a55ffff7ac18eb3ff87 100644
--- a/scripts/syscall.tbl
+++ b/scripts/syscall.tbl
@@ -408,3 +408,4 @@
 465	common	listxattrat			sys_listxattrat
 466	common	removexattrat			sys_removexattrat
 467	common	open_tree_attr			sys_open_tree_attr
+468	common	set_robust_list2		sys_set_robust_list2

-- 
2.49.0


^ permalink raw reply related

* [PATCH RESEND v4 7/7] selftests: futex: Expand robust list test for the new interface
From: André Almeida @ 2025-06-17 18:34 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Peter Zijlstra, Darren Hart,
	Davidlohr Bueso, Shuah Khan, Arnd Bergmann,
	Sebastian Andrzej Siewior, Waiman Long
  Cc: linux-kernel, linux-kselftest, linux-api, kernel-dev,
	André Almeida
In-Reply-To: <20250617-tonyk-robust_futex-v4-0-6586f5fb9d33@igalia.com>

Expand the current robust list test for the new set_robust_list2
syscall. Create an option to make it possible to run the same tests
using the new syscall, and also add two new relevant test: test long
lists (bigger than ROBUST_LIST_LIMIT) and for unaligned addresses.

Signed-off-by: André Almeida <andrealmeid@igalia.com>
---
 .../selftests/futex/functional/robust_list.c       | 160 ++++++++++++++++++++-
 1 file changed, 156 insertions(+), 4 deletions(-)

diff --git a/tools/testing/selftests/futex/functional/robust_list.c b/tools/testing/selftests/futex/functional/robust_list.c
index 42690b2440fd29a9b12c46f67f9645ccc93d1147..004ad79ff6171c411fd47e699e3c38889544218e 100644
--- a/tools/testing/selftests/futex/functional/robust_list.c
+++ b/tools/testing/selftests/futex/functional/robust_list.c
@@ -35,16 +35,45 @@
 #include <stddef.h>
 #include <sys/mman.h>
 #include <sys/wait.h>
+#include <stdint.h>
 
 #define STACK_SIZE (1024 * 1024)
 
 #define FUTEX_TIMEOUT 3
 
+#define SYS_set_robust_list2 468
+
+enum robust_list2_type {
+        ROBUST_LIST_32BIT,
+        ROBUST_LIST_64BIT,
+};
+
 static pthread_barrier_t barrier, barrier2;
 
+bool robust2 = false;
+
 int set_robust_list(struct robust_list_head *head, size_t len)
 {
-	return syscall(SYS_set_robust_list, head, len);
+	int ret, flags;
+
+	if (!robust2) {
+		return syscall(SYS_set_robust_list, head, len);
+	}
+
+	if (sizeof(head) == 8)
+		flags = ROBUST_LIST_64BIT;
+	else
+		flags = ROBUST_LIST_32BIT;
+
+	/*
+	 * We act as we have just one list here. We try to use the first slot,
+	 * but if it hasn't been alocated yet we allocate it.
+	 */
+	ret = syscall(SYS_set_robust_list2, head, 0, flags);
+	if (ret == -1 && errno == ENOENT)
+		ret = syscall(SYS_set_robust_list2, head, -1, flags);
+
+	return ret;
 }
 
 int get_robust_list(int pid, struct robust_list_head **head, size_t *len_ptr)
@@ -246,6 +275,11 @@ static void test_set_robust_list_invalid_size(void)
 	size_t head_size = sizeof(struct robust_list_head);
 	int ret;
 
+	if (robust2) {
+		ksft_test_result_skip("This test is only for old robust interface\n");
+		return;
+	}
+
 	ret = set_robust_list(&head, head_size);
 	ASSERT_EQ(ret, 0);
 
@@ -321,6 +355,11 @@ static void test_get_robust_list_child(void)
 	struct robust_list_head head, *get_head;
 	size_t len_ptr;
 
+	if (robust2) {
+		ksft_test_result_skip("Not implemented in the new robust interface\n");
+		return;
+	}
+
 	ret = pthread_barrier_init(&barrier, NULL, 2);
 	ret = pthread_barrier_init(&barrier2, NULL, 2);
 	ASSERT_EQ(ret, 0);
@@ -332,7 +371,7 @@ static void test_get_robust_list_child(void)
 
 	ret = get_robust_list(tid, &get_head, &len_ptr);
 	ASSERT_EQ(ret, 0);
-	ASSERT_EQ(&head, get_head);
+	ASSERT_EQ(get_head, &head);
 
 	pthread_barrier_wait(&barrier2);
 
@@ -507,11 +546,119 @@ static void test_circular_list(void)
 	ksft_test_result_pass("%s\n", __func__);
 }
 
+#define ROBUST_LIST_LIMIT	2048
+#define CHILD_LIST_LIMIT (ROBUST_LIST_LIMIT + 10)
+
+static int child_robust_list_limit(void *arg)
+{
+	struct lock_struct *locks;
+	struct robust_list *list;
+	struct robust_list_head head;
+	int ret, i;
+
+	locks = (struct lock_struct *) arg;
+
+	ret = set_list(&head);
+	if (ret)
+		ksft_test_result_fail("set_list error\n");
+
+	/*
+	 * Create a very long list of locks
+	 */
+	head.list.next = &locks[0].list;
+
+	list = head.list.next;
+	for (i = 0; i < CHILD_LIST_LIMIT - 1; i++) {
+		list->next = &locks[i+1].list;
+		list = list->next;
+	}
+	list->next = &head.list;
+
+	/*
+	 * Grab the lock in the last one, and die without releasing it
+	 */
+	mutex_lock(&locks[CHILD_LIST_LIMIT], &head, false);
+	pthread_barrier_wait(&barrier);
+
+	sleep(1);
+
+	return 0;
+}
+
+/*
+ * The old robust list used to have a limit of 2048 items from the kernel side.
+ * After this limit the kernel stops walking the list and ignore the other
+ * futexes, causing deadlocks.
+ *
+ * For the new interface, test if we can wait for a list of more than 2048
+ * elements.
+ */
+static void test_robust_list_limit(void)
+{
+	struct lock_struct locks[CHILD_LIST_LIMIT + 1];
+	_Atomic(unsigned int) *futex = &locks[CHILD_LIST_LIMIT].futex;
+	struct robust_list_head head;
+	int ret;
+
+	if (!robust2) {
+		ksft_test_result_skip("This test is only for new robust interface\n");
+		return;
+	}
+
+	*futex = 0;
+
+	ret = set_list(&head);
+	ASSERT_EQ(ret, 0);
+
+	ret = pthread_barrier_init(&barrier, NULL, 2);
+	ASSERT_EQ(ret, 0);
+
+	create_child(child_robust_list_limit, locks);
+
+	/*
+	 * After the child thread creates the very long list of locks, wait on
+	 * the last one.
+	 */
+	pthread_barrier_wait(&barrier);
+	ret = mutex_lock(&locks[CHILD_LIST_LIMIT], &head, false);
+
+	if (ret != 0)
+		printf("futex wait returned %d\n", errno);
+	ASSERT_EQ(ret, 0);
+
+	ASSERT_TRUE(*futex | FUTEX_OWNER_DIED);
+
+	wait(NULL);
+	pthread_barrier_destroy(&barrier);
+
+	ksft_test_result_pass("%s\n", __func__);
+}
+
+/*
+ * The kernel should refuse an unaligned head pointer
+ */
+static void test_unaligned_address(void)
+{
+	struct robust_list_head head, *h;
+	int ret;
+
+	if (!robust2) {
+		ksft_test_result_skip("This test is only for new robust interface\n");
+		return;
+	}
+
+	h = (struct robust_list_head *) ((uintptr_t) &head + 1);
+	ret = set_list(h);
+	ASSERT_EQ(ret, -1);
+	ASSERT_EQ(errno, EINVAL);
+}
+
 void usage(char *prog)
 {
 	printf("Usage: %s\n", prog);
 	printf("  -c	Use color\n");
 	printf("  -h	Display this help message\n");
+	printf("  -n	Use robust2 syscall\n");
 	printf("  -v L	Verbosity level: %d=QUIET %d=CRITICAL %d=INFO\n",
 	       VQUIET, VCRITICAL, VINFO);
 }
@@ -520,7 +667,7 @@ int main(int argc, char *argv[])
 {
 	int c;
 
-	while ((c = getopt(argc, argv, "cht:v:")) != -1) {
+	while ((c = getopt(argc, argv, "chnt:v:")) != -1) {
 		switch (c) {
 		case 'c':
 			log_color(1);
@@ -531,6 +678,9 @@ int main(int argc, char *argv[])
 		case 'v':
 			log_verbosity(atoi(optarg));
 			break;
+		case 'n':
+			robust2 = true;
+			break;
 		default:
 			usage(basename(argv[0]));
 			exit(1);
@@ -538,7 +688,7 @@ int main(int argc, char *argv[])
 	}
 
 	ksft_print_header();
-	ksft_set_plan(7);
+	ksft_set_plan(8);
 
 	test_robustness();
 
@@ -548,6 +698,8 @@ int main(int argc, char *argv[])
 	test_set_list_op_pending();
 	test_robust_list_multiple_elements();
 	test_circular_list();
+	test_robust_list_limit();
+	test_unaligned_address();
 
 	ksft_print_cnts();
 	return 0;

-- 
2.49.0


^ permalink raw reply related

* Re: [RFC 08/19] exec: add API specification for execve
From: Sasha Levin @ 2025-06-17 22:58 UTC (permalink / raw)
  To: Florian Weimer; +Cc: linux-kernel, linux-api, workflows, tools
In-Reply-To: <87y0tqu9g7.fsf@oldenburg.str.redhat.com>

On Tue, Jun 17, 2025 at 09:13:44AM +0200, Florian Weimer wrote:
>* Sasha Levin:
>
>> On Mon, Jun 16, 2025 at 11:39:31PM +0200, Florian Weimer wrote:
>>>* Sasha Levin:
>>>
>>>> +	KAPI_RETURN("long", "Does not return on success; returns -1 on error")
>>>> +		.type = KAPI_TYPE_INT,
>>>> +		.check_type = KAPI_RETURN_ERROR_CHECK,
>>>> +	KAPI_RETURN_END
>>>
>>>Is the -1 part correct?
>>
>> Maybe :) That's one of the things I wasn't sure about: we're documenting
>> the execve syscall rather than the function itself. A user calling
>> execve() will end up with -1 on failure, and errno set with the error
>> code.
>
>Well, it doesn't say execve, it says sys_execve.
>
>> You could argue that it's libc that sets errno and we're trying to spec
>> the kernel here, not the userspace interface to it.
>
>And I think this would be appropriate.
>
>Note that in the future, the glibc version of execve will not be a
>straightforward system call wrapper because we need to obtain a
>consistent snapshot of the environment array.  That is actually pretty
>hard because we cannot atomically replace the process image, unblock
>signals, and unmap a copy of the environment.
>
>So I think it's best for the kernel to stick with the system call
>interface and not try to document what libcs are doing.

I hear you - it sounds like the "right" solution technically.


Switching back to signals, how does something like the below look as far
as expanding the execve() spec:

+       /* SIGSEGV sent on point of no return failure */
+       KAPI_SIGNAL(9, SIGSEGV, "SIGSEGV", KAPI_SIGNAL_SEND, KAPI_SIGNAL_ACTION_COREDUMP)
+               KAPI_SIGNAL_TARGET("Current process")
+               KAPI_SIGNAL_CONDITION("Exec fails after point of no return")
+               KAPI_SIGNAL_DESC("If exec fails after the point of no return (when the old "
+                                "process image has been destroyed), force_fatal_sig(SIGSEGV) "
+                                "is called to terminate the process since it cannot continue.")
+               KAPI_SIGNAL_TIMING(KAPI_SIGNAL_TIME_EXIT)
+               KAPI_SIGNAL_PRIORITY(0)
+               KAPI_SIGNAL_STATE_FORBID(KAPI_SIGNAL_STATE_ZOMBIE | KAPI_SIGNAL_STATE_DEAD)
+       KAPI_SIGNAL_END
+
+       /* Signal mask preserved */
+       KAPI_SIGNAL(10, 0, "SIGNAL_MASK", KAPI_SIGNAL_HANDLE, KAPI_SIGNAL_ACTION_CUSTOM)
+               KAPI_SIGNAL_CONDITION("Process has blocked signals")
+               KAPI_SIGNAL_DESC("The signal mask (blocked signals) is preserved across exec. "
+                                "This allows processes to block signals before exec and have "
+                                "them remain blocked in the new program.")
+               KAPI_SIGNAL_TIMING(KAPI_SIGNAL_TIME_DURING)
+       KAPI_SIGNAL_END
+
+       /* Realtime signal queues cleared */
+       KAPI_SIGNAL(11, 0, "REALTIME_SIGNALS", KAPI_SIGNAL_HANDLE, KAPI_SIGNAL_ACTION_DISCARD)
+               KAPI_SIGNAL_CONDITION("Realtime signals queued")
+               KAPI_SIGNAL_DESC("All queued realtime signals (SIGRTMIN to SIGRTMAX) are "
+                                "discarded during exec. The realtime signal queue is cleared.")
+               KAPI_SIGNAL_TIMING(KAPI_SIGNAL_TIME_DURING)
+               KAPI_SIGNAL_QUEUE(KAPI_SIGNAL_QUEUE_REALTIME)
+       KAPI_SIGNAL_END

What's missing for me is that while we now go into more detail, we
should also check this during runtime, but I'm still trying to come up
with something that is not ugly.

-- 
Thanks,
Sasha

^ permalink raw reply

* Re: [PATCH RESEND v4 0/7] futex: Create set_robust_list2
From: Sebastian Andrzej Siewior @ 2025-06-18  7:08 UTC (permalink / raw)
  To: André Almeida
  Cc: Thomas Gleixner, Ingo Molnar, Peter Zijlstra, Darren Hart,
	Davidlohr Bueso, Shuah Khan, Arnd Bergmann, Waiman Long,
	linux-kernel, linux-kselftest, linux-api, kernel-dev
In-Reply-To: <20250617-tonyk-robust_futex-v4-0-6586f5fb9d33@igalia.com>

On 2025-06-17 15:34:17 [-0300], André Almeida wrote:
> This patch adds a new robust_list() syscall. The current syscall
> can't be expanded to cover the following use case, so a new one is
> needed. This new syscall allows users to set multiple robust lists per
> process and to have either 32bit or 64bit pointers in the list.

Thank you for the reminder. It was on my list, it slipped. Two
questions:
- there was a bot warning for v3 but this v4 is a RESEND. It the warning
  addressed in any way?

- You say 64bit x86-64 does not have the problem due the compat syscall.
  Arm64 has this problem. New arm64 do not provide arm32 facility. You
  introduce the syscall here. Why not introduce the compat syscall
  instead? I'm sorry if this has been answered somewhere below but this
  was one question I had while I initially skimmed over the patches.

Sebastian

^ permalink raw reply

* Re: [PATCH RESEND v4 0/7] futex: Create set_robust_list2
From: André Almeida @ 2025-06-18 16:39 UTC (permalink / raw)
  To: Sebastian Andrzej Siewior
  Cc: Thomas Gleixner, Ingo Molnar, Peter Zijlstra, Darren Hart,
	Davidlohr Bueso, Shuah Khan, Arnd Bergmann, Waiman Long,
	linux-kernel, linux-kselftest, linux-api, kernel-dev
In-Reply-To: <20250618070833._qeCcHLx@linutronix.de>

Hi Sebastian,

Thanks for the feedback!

Em 18/06/2025 04:08, Sebastian Andrzej Siewior escreveu:
> On 2025-06-17 15:34:17 [-0300], André Almeida wrote:
>> This patch adds a new robust_list() syscall. The current syscall
>> can't be expanded to cover the following use case, so a new one is
>> needed. This new syscall allows users to set multiple robust lists per
>> process and to have either 32bit or 64bit pointers in the list.
> 
> Thank you for the reminder. It was on my list, it slipped. Two
> questions:
> - there was a bot warning for v3 but this v4 is a RESEND. It the warning
>    addressed in any way?
> 

Ops, I forgot to address them. I will do it for v5.

> - You say 64bit x86-64 does not have the problem due the compat syscall.
>    Arm64 has this problem. New arm64 do not provide arm32 facility. You
>    introduce the syscall here. Why not introduce the compat syscall
>    instead? I'm sorry if this has been answered somewhere below but this
>    was one question I had while I initially skimmed over the patches.
> 

The main target for this new syscall is Arm64, that can't handle 32 
pointers in the current syscall, so this new interface allows the robust 
list mechanism to know if it needs to do 64 or 32 bit pointer arithmetic 
operations to walk in the list.

Introducing a compat syscall won't fix this, giving that it only works 
in x86-64. We need an entry point for Arm64 that can handle 32 bit pointers.

I hope that it's clear now, let me know if you have more questions :)

> Sebastian

^ permalink raw reply


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