* [PATCH v7 08/13] liveupdate: defer session block allocation and physical address setting
From: Pasha Tatashin @ 2026-06-03 15:43 UTC (permalink / raw)
To: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
linux-kernel, corbet, pasha.tatashin, dmatlack, kexec, pratyush,
skhawaja, graf
In-Reply-To: <20260603154402.468928-1-pasha.tatashin@soleen.com>
Currently, luo_session_setup_outgoing() allocates the session block and
sets its physical address in the header immediately. With upcoming
dynamic block-based session management, this makes the first block
different from the rest. Move the allocation to where it is first needed.
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/liveupdate/luo_core.c | 4 +-
kernel/liveupdate/luo_internal.h | 2 +-
kernel/liveupdate/luo_session.c | 68 ++++++++++++++++++++------------
3 files changed, 45 insertions(+), 29 deletions(-)
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 69b00e7d0f8f..1b2bda22902d 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -165,9 +165,7 @@ static int __init luo_state_setup(void)
strscpy(luo_ser->compatible, LUO_ABI_COMPATIBLE, sizeof(luo_ser->compatible));
luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
- err = luo_session_setup_outgoing(&luo_ser->sessions_pa);
- if (err)
- goto exit_free_luo_ser;
+ luo_session_setup_outgoing(&luo_ser->sessions_pa);
err = luo_flb_setup_outgoing(&luo_ser->flbs_pa);
if (err)
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index fe22086bfbeb..ee18f9a11b91 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -79,7 +79,7 @@ extern struct rw_semaphore luo_register_rwlock;
int luo_session_create(const char *name, struct file **filep);
int luo_session_retrieve(const char *name, struct file **filep);
-int __init luo_session_setup_outgoing(u64 *sessions_pa);
+void __init luo_session_setup_outgoing(u64 *sessions_pa);
int __init luo_session_setup_incoming(u64 sessions_pa);
int luo_session_serialize(void);
int luo_session_deserialize(void);
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 1cd315e0f6de..2411849a34e3 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -108,15 +108,16 @@ static DECLARE_RWSEM(luo_session_serialize_rwsem);
/**
* struct luo_session_header - Header struct for managing LUO sessions.
- * @count: The number of sessions currently tracked in the @list.
- * @list: The head of the linked list of `struct luo_session` instances.
- * @rwsem: A read-write semaphore providing synchronized access to the
- * session list and other fields in this structure.
- * @header_ser: The header data of serialization array.
- * @ser: The serialized session data (an array of
- * `struct luo_session_ser`).
- * @active: Set to true when first initialized. If previous kernel did not
- * send session data, active stays false for incoming.
+ * @count: The number of sessions currently tracked in the @list.
+ * @list: The head of the linked list of `struct luo_session` instances.
+ * @rwsem: A read-write semaphore providing synchronized access to the
+ * session list and other fields in this structure.
+ * @header_ser: The header data of serialization array.
+ * @ser: The serialized session data (an array of
+ * `struct luo_session_ser`).
+ * @sessions_pa: Points to the location of sessions_pa within struct luo_ser.
+ * @active: Set to true when first initialized. If previous kernel did not
+ * send session data, active stays false for incoming.
*/
struct luo_session_header {
long count;
@@ -124,6 +125,7 @@ struct luo_session_header {
struct rw_semaphore rwsem;
struct luo_session_header_ser *header_ser;
struct luo_session_ser *ser;
+ u64 *sessions_pa;
bool active;
};
@@ -171,10 +173,30 @@ static void luo_session_free(struct luo_session *session)
kfree(session);
}
+static int luo_session_grow_ser(struct luo_session_header *sh)
+{
+ struct luo_session_header_ser *header_ser;
+
+ if (sh->count == LUO_SESSION_MAX)
+ return -ENOMEM;
+
+ if (sh->header_ser)
+ return 0;
+
+ header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
+ if (IS_ERR(header_ser))
+ return PTR_ERR(header_ser);
+
+ sh->header_ser = header_ser;
+ sh->ser = (void *)(header_ser + 1);
+ return 0;
+}
+
static int luo_session_insert(struct luo_session_header *sh,
struct luo_session *session)
{
struct luo_session *it;
+ int err;
guard(rwsem_write)(&sh->rwsem);
@@ -183,8 +205,9 @@ static int luo_session_insert(struct luo_session_header *sh,
* for new session.
*/
if (sh == &luo_session_global.outgoing) {
- if (sh->count == LUO_SESSION_MAX)
- return -ENOMEM;
+ err = luo_session_grow_ser(sh);
+ if (err)
+ return err;
}
/*
@@ -524,21 +547,10 @@ int luo_session_retrieve(const char *name, struct file **filep)
return err;
}
-int __init luo_session_setup_outgoing(u64 *sessions_pa)
+void __init luo_session_setup_outgoing(u64 *sessions_pa)
{
- struct luo_session_header_ser *header_ser;
-
- header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
- if (IS_ERR(header_ser))
- return PTR_ERR(header_ser);
-
- *sessions_pa = virt_to_phys(header_ser);
-
- luo_session_global.outgoing.header_ser = header_ser;
- luo_session_global.outgoing.ser = (void *)(header_ser + 1);
+ luo_session_global.outgoing.sessions_pa = sessions_pa;
luo_session_global.outgoing.active = true;
-
- return 0;
}
int __init luo_session_setup_incoming(u64 sessions_pa)
@@ -644,6 +656,8 @@ int luo_session_serialize(void)
down_write(&luo_session_serialize_rwsem);
down_write(&sh->rwsem);
+ *sh->sessions_pa = 0;
+
list_for_each_entry(session, &sh->list, list) {
err = luo_session_freeze_one(session, &sh->ser[i]);
if (err)
@@ -653,7 +667,11 @@ int luo_session_serialize(void)
sizeof(sh->ser[i].name));
i++;
}
- sh->header_ser->count = sh->count;
+
+ if (sh->header_ser && sh->count > 0) {
+ sh->header_ser->count = sh->count;
+ *sh->sessions_pa = virt_to_phys(sh->header_ser);
+ }
up_write(&sh->rwsem);
return 0;
--
2.53.0
^ permalink raw reply related
* [PATCH v7 09/13] liveupdate: Remove limit on the number of sessions
From: Pasha Tatashin @ 2026-06-03 15:43 UTC (permalink / raw)
To: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
linux-kernel, corbet, pasha.tatashin, dmatlack, kexec, pratyush,
skhawaja, graf
In-Reply-To: <20260603154402.468928-1-pasha.tatashin@soleen.com>
Currently, the number of LUO sessions is limited by a fixed number of
pre-allocated pages for serialization (16 pages, allowing for ~819
sessions).
This limitation is problematic if LUO is used to support things such as
systemd file descriptor store, and would be used not just as VM memory
but to save other states on the machine.
Remove this limit by transitioning to a linked-block approach for
session metadata serialization. Instead of a single contiguous block,
session metadata is now stored in a chain of 16-page blocks. Each block
starts with a header containing the physical address of the next block
and the number of session entries in the current block.
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
include/linux/kho/abi/luo.h | 23 +------
kernel/liveupdate/luo_session.c | 113 +++++++++++++++-----------------
2 files changed, 55 insertions(+), 81 deletions(-)
diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 9a4fe491812b..03d940d0f9bb 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -33,11 +33,6 @@
* It includes the compatibility string, the liveupdate-number, and pointers
* to sessions and FLBs.
*
- * - struct luo_session_header_ser:
- * Header for the session array. Contains the total page count of the
- * preserved memory block and the number of `struct luo_session_ser`
- * entries that follow.
- *
* - struct luo_session_ser:
* Metadata for a single session, including its name and a physical pointer
* to another preserved memory block containing an array of
@@ -63,13 +58,14 @@
#define _LINUX_KHO_ABI_LUO_H
#include <linux/align.h>
+#include <linux/kho/abi/block.h>
#include <uapi/linux/liveupdate.h>
/*
* The LUO state is registered under this KHO entry name.
*/
#define LUO_KHO_ENTRY_NAME "LUO"
-#define LUO_ABI_COMPATIBLE "luo-v3"
+#define LUO_ABI_COMPATIBLE "luo-v4"
#define LUO_ABI_COMPAT_LEN ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
/**
@@ -118,21 +114,6 @@ struct luo_file_set_ser {
u64 count;
} __packed;
-/**
- * struct luo_session_header_ser - Header for the serialized session data block.
- * @count: The number of `struct luo_session_ser` entries that immediately
- * follow this header in the memory block.
- *
- * This structure is located at the beginning of a contiguous block of
- * physical memory preserved across the kexec. It provides the necessary
- * metadata to interpret the array of session entries that follow.
- *
- * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
- */
-struct luo_session_header_ser {
- u64 count;
-} __packed;
-
/**
* struct luo_session_ser - Represents the serialized metadata for a LUO session.
* @name: The unique name of the session, provided by the userspace at
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 2411849a34e3..b79b2a488974 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -24,9 +24,10 @@
* ioctls on /dev/liveupdate.
*
* - Serialization: Session metadata is preserved using the KHO framework. When
- * a live update is triggered via kexec, an array of `struct luo_session_ser`
- * is populated and placed in a preserved memory region. The physical address
- * of this array is stored in the centralized `struct luo_ser` structure.
+ * a live update is triggered via kexec, session metadata is serialized into
+ * a chain of linked-blocks and placed in a preserved memory region. The
+ * physical address of the first block header is stored in the centralized
+ * `struct luo_ser` structure.
*
* Session Lifecycle:
*
@@ -89,6 +90,7 @@
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/kexec_handover.h>
+#include <linux/kho_block.h>
#include <linux/kho/abi/luo.h>
#include <linux/list.h>
#include <linux/liveupdate.h>
@@ -98,23 +100,14 @@
#include <uapi/linux/liveupdate.h>
#include "luo_internal.h"
-/* 16 4K pages, give space for 744 sessions */
-#define LUO_SESSION_PGCNT 16ul
-#define LUO_SESSION_MAX (((LUO_SESSION_PGCNT << PAGE_SHIFT) - \
- sizeof(struct luo_session_header_ser)) / \
- sizeof(struct luo_session_ser))
-
static DECLARE_RWSEM(luo_session_serialize_rwsem);
-
/**
* struct luo_session_header - Header struct for managing LUO sessions.
* @count: The number of sessions currently tracked in the @list.
* @list: The head of the linked list of `struct luo_session` instances.
* @rwsem: A read-write semaphore providing synchronized access to the
* session list and other fields in this structure.
- * @header_ser: The header data of serialization array.
- * @ser: The serialized session data (an array of
- * `struct luo_session_ser`).
+ * @block_set: The set of serialization blocks.
* @sessions_pa: Points to the location of sessions_pa within struct luo_ser.
* @active: Set to true when first initialized. If previous kernel did not
* send session data, active stays false for incoming.
@@ -123,8 +116,7 @@ struct luo_session_header {
long count;
struct list_head list;
struct rw_semaphore rwsem;
- struct luo_session_header_ser *header_ser;
- struct luo_session_ser *ser;
+ struct kho_block_set block_set;
u64 *sessions_pa;
bool active;
};
@@ -143,10 +135,14 @@ static struct luo_session_global luo_session_global = {
.incoming = {
.list = LIST_HEAD_INIT(luo_session_global.incoming.list),
.rwsem = __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem),
+ .block_set = KHO_BLOCK_SET_INIT(luo_session_global.incoming.block_set,
+ sizeof(struct luo_session_ser)),
},
.outgoing = {
.list = LIST_HEAD_INIT(luo_session_global.outgoing.list),
.rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem),
+ .block_set = KHO_BLOCK_SET_INIT(luo_session_global.outgoing.block_set,
+ sizeof(struct luo_session_ser)),
},
};
@@ -173,25 +169,6 @@ static void luo_session_free(struct luo_session *session)
kfree(session);
}
-static int luo_session_grow_ser(struct luo_session_header *sh)
-{
- struct luo_session_header_ser *header_ser;
-
- if (sh->count == LUO_SESSION_MAX)
- return -ENOMEM;
-
- if (sh->header_ser)
- return 0;
-
- header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
- if (IS_ERR(header_ser))
- return PTR_ERR(header_ser);
-
- sh->header_ser = header_ser;
- sh->ser = (void *)(header_ser + 1);
- return 0;
-}
-
static int luo_session_insert(struct luo_session_header *sh,
struct luo_session *session)
{
@@ -205,7 +182,7 @@ static int luo_session_insert(struct luo_session_header *sh,
* for new session.
*/
if (sh == &luo_session_global.outgoing) {
- err = luo_session_grow_ser(sh);
+ err = kho_block_set_grow(&sh->block_set, sh->count + 1);
if (err)
return err;
}
@@ -232,6 +209,8 @@ static void luo_session_remove(struct luo_session_header *sh,
guard(rwsem_write)(&sh->rwsem);
list_del(&session->list);
sh->count--;
+ if (sh == &luo_session_global.outgoing)
+ kho_block_set_shrink(&sh->block_set, sh->count);
}
static int luo_session_finish_one(struct luo_session *session)
@@ -555,15 +534,17 @@ void __init luo_session_setup_outgoing(u64 *sessions_pa)
int __init luo_session_setup_incoming(u64 sessions_pa)
{
- struct luo_session_header_ser *header_ser;
+ struct luo_session_header *sh = &luo_session_global.incoming;
+ int err;
- if (sessions_pa) {
- header_ser = phys_to_virt(sessions_pa);
- luo_session_global.incoming.header_ser = header_ser;
- luo_session_global.incoming.ser = (void *)(header_ser + 1);
- luo_session_global.incoming.active = true;
- }
+ if (!sessions_pa)
+ return 0;
+ err = kho_block_set_restore(&sh->block_set, sessions_pa);
+ if (err)
+ return err;
+
+ sh->active = true;
return 0;
}
@@ -605,6 +586,8 @@ int luo_session_deserialize(void)
{
struct luo_session_header *sh = &luo_session_global.incoming;
static bool is_deserialized;
+ struct luo_session_ser *ser;
+ struct kho_block_set_it it;
static int saved_err;
int err;
@@ -631,18 +614,19 @@ int luo_session_deserialize(void)
* userspace to detect the failure and trigger a reboot, which will
* reliably reset devices and reclaim memory.
*/
- for (int i = 0; i < sh->header_ser->count; i++) {
- err = luo_session_deserialize_one(sh, &sh->ser[i]);
+ kho_block_set_it_init(&it, &sh->block_set);
+ while ((ser = kho_block_set_it_read_entry(&it))) {
+ err = luo_session_deserialize_one(sh, ser);
if (err)
goto save_err;
}
- kho_restore_free(sh->header_ser);
- sh->header_ser = NULL;
- sh->ser = NULL;
+ kho_block_set_destroy(&sh->block_set);
return 0;
+
save_err:
+ kho_block_set_destroy(&sh->block_set);
saved_err = err;
return err;
}
@@ -651,36 +635,45 @@ int luo_session_serialize(void)
{
struct luo_session_header *sh = &luo_session_global.outgoing;
struct luo_session *session;
- int i = 0;
+ struct kho_block_set_it it;
int err;
down_write(&luo_session_serialize_rwsem);
down_write(&sh->rwsem);
*sh->sessions_pa = 0;
+ kho_block_set_it_init(&it, &sh->block_set);
+
list_for_each_entry(session, &sh->list, list) {
- err = luo_session_freeze_one(session, &sh->ser[i]);
- if (err)
+ struct luo_session_ser *ser = kho_block_set_it_reserve_entry(&it);
+
+ /* This should not fail normally as blocks were pre-allocated */
+ if (WARN_ON_ONCE(!ser)) {
+ err = -ENOSPC;
goto err_undo;
+ }
- strscpy(sh->ser[i].name, session->name,
- sizeof(sh->ser[i].name));
- i++;
- }
+ err = luo_session_freeze_one(session, ser);
+ if (err) {
+ kho_block_set_it_prev(&it);
+ goto err_undo;
+ }
- if (sh->header_ser && sh->count > 0) {
- sh->header_ser->count = sh->count;
- *sh->sessions_pa = virt_to_phys(sh->header_ser);
+ strscpy(ser->name, session->name, sizeof(ser->name));
}
+
+ if (sh->count > 0)
+ *sh->sessions_pa = kho_block_set_head_pa(&sh->block_set);
up_write(&sh->rwsem);
return 0;
err_undo:
list_for_each_entry_continue_reverse(session, &sh->list, list) {
- i--;
- luo_session_unfreeze_one(session, &sh->ser[i]);
- memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name));
+ struct luo_session_ser *ser = kho_block_set_it_prev(&it);
+
+ luo_session_unfreeze_one(session, ser);
+ memset(ser->name, 0, sizeof(ser->name));
}
up_write(&sh->rwsem);
up_write(&luo_session_serialize_rwsem);
--
2.53.0
^ permalink raw reply related
* [PATCH v7 10/13] liveupdate: Remove limit on the number of files per session
From: Pasha Tatashin @ 2026-06-03 15:43 UTC (permalink / raw)
To: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
linux-kernel, corbet, pasha.tatashin, dmatlack, kexec, pratyush,
skhawaja, graf
In-Reply-To: <20260603154402.468928-1-pasha.tatashin@soleen.com>
To remove the fixed limit on the number of preserved files per session,
transition the file metadata serialization from a single contiguous
memory block to a chain of linked blocks.
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
include/linux/kho/abi/luo.h | 13 +--
kernel/liveupdate/luo_file.c | 138 ++++++++++++++-----------------
kernel/liveupdate/luo_internal.h | 6 +-
3 files changed, 74 insertions(+), 83 deletions(-)
diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 03d940d0f9bb..288076de6d4a 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -35,8 +35,8 @@
*
* - struct luo_session_ser:
* Metadata for a single session, including its name and a physical pointer
- * to another preserved memory block containing an array of
- * `struct luo_file_ser` for all files in that session.
+ * to the first `struct kho_block_header_ser` for all files in that session.
+ * Multiple blocks are linked via the `next` field in the header.
*
* - struct luo_file_ser:
* Metadata for a single preserved file. Contains the `compatible` string to
@@ -65,7 +65,7 @@
* The LUO state is registered under this KHO entry name.
*/
#define LUO_KHO_ENTRY_NAME "LUO"
-#define LUO_ABI_COMPATIBLE "luo-v4"
+#define LUO_ABI_COMPATIBLE "luo-v5"
#define LUO_ABI_COMPAT_LEN ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
/**
@@ -102,9 +102,10 @@ struct luo_file_ser {
/**
* struct luo_file_set_ser - Represents the serialized metadata for file set
- * @files: The physical address of a contiguous memory block that holds
- * the serialized state of files (array of luo_file_ser) in this file
- * set.
+ * @files: The physical address of the first `struct kho_block_header_ser`.
+ * This structure is the header for a block of memory containing
+ * an array of `struct luo_file_ser` entries. Multiple blocks are
+ * linked via the `next` field in the header.
* @count: The total number of files that were part of this session during
* serialization. Used for iteration and validation during
* restoration.
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index 9eec07a9e9fc..c39f96961a85 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -118,11 +118,6 @@ static LIST_HEAD(luo_file_handler_list);
/* Keep track of files being preserved by LUO */
static DEFINE_XARRAY(luo_preserved_files);
-/* 2 4K pages, give space for 128 files per file_set */
-#define LUO_FILE_PGCNT 2ul
-#define LUO_FILE_MAX \
- ((LUO_FILE_PGCNT << PAGE_SHIFT) / sizeof(struct luo_file_ser))
-
/**
* struct luo_file - Represents a single preserved file instance.
* @fh: Pointer to the &struct liveupdate_file_handler that manages
@@ -174,39 +169,6 @@ struct luo_file {
u64 token;
};
-static int luo_alloc_files_mem(struct luo_file_set *file_set)
-{
- size_t size;
- void *mem;
-
- if (file_set->files)
- return 0;
-
- WARN_ON_ONCE(file_set->count);
-
- size = LUO_FILE_PGCNT << PAGE_SHIFT;
- mem = kho_alloc_preserve(size);
- if (IS_ERR(mem))
- return PTR_ERR(mem);
-
- file_set->files = mem;
-
- return 0;
-}
-
-static void luo_free_files_mem(struct luo_file_set *file_set)
-{
- /* If file_set has files, no need to free preservation memory */
- if (file_set->count)
- return;
-
- if (!file_set->files)
- return;
-
- kho_unpreserve_free(file_set->files);
- file_set->files = NULL;
-}
-
static unsigned long luo_get_id(struct liveupdate_file_handler *fh,
struct file *file)
{
@@ -276,16 +238,15 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
if (luo_token_is_used(file_set, token))
return -EEXIST;
- if (file_set->count == LUO_FILE_MAX)
- return -ENOSPC;
+ err = kho_block_set_grow(&file_set->block_set, file_set->count + 1);
+ if (err)
+ return err;
file = fget(fd);
- if (!file)
- return -EBADF;
-
- err = luo_alloc_files_mem(file_set);
- if (err)
- goto err_fput;
+ if (!file) {
+ err = -EBADF;
+ goto err_shrink;
+ }
err = -ENOENT;
down_read(&luo_register_rwlock);
@@ -300,7 +261,7 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
/* err is still -ENOENT if no handler was found */
if (err)
- goto err_free_files_mem;
+ goto err_fput;
err = xa_insert(&luo_preserved_files, luo_get_id(fh, file),
file, GFP_KERNEL);
@@ -343,10 +304,10 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
xa_erase(&luo_preserved_files, luo_get_id(fh, file));
err_module_put:
module_put(fh->ops->owner);
-err_free_files_mem:
- luo_free_files_mem(file_set);
err_fput:
fput(file);
+err_shrink:
+ kho_block_set_shrink(&file_set->block_set, file_set->count);
return err;
}
@@ -392,13 +353,14 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set)
list_del(&luo_file->list);
file_set->count--;
+ kho_block_set_shrink(&file_set->block_set, file_set->count);
fput(luo_file->file);
mutex_destroy(&luo_file->mutex);
kfree(luo_file);
}
- luo_free_files_mem(file_set);
+ kho_block_set_destroy(&file_set->block_set);
}
static int luo_file_freeze_one(struct luo_file_set *file_set,
@@ -454,7 +416,7 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
luo_file_unfreeze_one(file_set, luo_file);
}
- memset(file_set->files, 0, LUO_FILE_PGCNT << PAGE_SHIFT);
+ kho_block_set_clear(&file_set->block_set);
}
/**
@@ -493,19 +455,24 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
int luo_file_freeze(struct luo_file_set *file_set,
struct luo_file_set_ser *file_set_ser)
{
- struct luo_file_ser *file_ser = file_set->files;
struct luo_file *luo_file;
+ struct kho_block_set_it it;
int err;
- int i;
if (!file_set->count)
return 0;
- if (WARN_ON(!file_ser))
- return -EINVAL;
+ kho_block_set_it_init(&it, &file_set->block_set);
- i = 0;
list_for_each_entry(luo_file, &file_set->files_list, list) {
+ struct luo_file_ser *file_ser = kho_block_set_it_reserve_entry(&it);
+
+ /* This should not fail normally as blocks were pre-allocated */
+ if (WARN_ON_ONCE(!file_ser)) {
+ err = -ENOSPC;
+ goto err_unfreeze;
+ }
+
err = luo_file_freeze_one(file_set, luo_file);
if (err < 0) {
pr_warn("Freeze failed for token[%#0llx] handler[%s] err[%pe]\n",
@@ -514,16 +481,14 @@ int luo_file_freeze(struct luo_file_set *file_set,
goto err_unfreeze;
}
- strscpy(file_ser[i].compatible, luo_file->fh->compatible,
- sizeof(file_ser[i].compatible));
- file_ser[i].data = luo_file->serialized_data;
- file_ser[i].token = luo_file->token;
- i++;
+ strscpy(file_ser->compatible, luo_file->fh->compatible,
+ sizeof(file_ser->compatible));
+ file_ser->data = luo_file->serialized_data;
+ file_ser->token = luo_file->token;
}
file_set_ser->count = file_set->count;
- if (file_set->files)
- file_set_ser->files = virt_to_phys(file_set->files);
+ file_set_ser->files = kho_block_set_head_pa(&file_set->block_set);
return 0;
@@ -741,14 +706,12 @@ int luo_file_finish(struct luo_file_set *file_set)
module_put(luo_file->fh->ops->owner);
list_del(&luo_file->list);
file_set->count--;
+ kho_block_set_shrink(&file_set->block_set, file_set->count);
mutex_destroy(&luo_file->mutex);
kfree(luo_file);
}
- if (file_set->files) {
- kho_restore_free(file_set->files);
- file_set->files = NULL;
- }
+ kho_block_set_destroy(&file_set->block_set);
return 0;
}
@@ -822,16 +785,18 @@ int luo_file_deserialize(struct luo_file_set *file_set,
struct luo_file_set_ser *file_set_ser)
{
struct luo_file_ser *file_ser;
+ struct kho_block_set_it it;
int err;
- u64 i;
if (!file_set_ser->files) {
WARN_ON(file_set_ser->count);
return 0;
}
- file_set->count = file_set_ser->count;
- file_set->files = phys_to_virt(file_set_ser->files);
+ file_set->count = 0;
+ err = kho_block_set_restore(&file_set->block_set, file_set_ser->files);
+ if (err)
+ return err;
/*
* Note on error handling:
@@ -848,25 +813,50 @@ int luo_file_deserialize(struct luo_file_set *file_set,
* userspace to detect the failure and trigger a reboot, which will
* reliably reset devices and reclaim memory.
*/
- file_ser = file_set->files;
- for (i = 0; i < file_set->count; i++) {
- err = luo_file_deserialize_one(file_set, &file_ser[i]);
+ kho_block_set_it_init(&it, &file_set->block_set);
+ while ((file_ser = kho_block_set_it_read_entry(&it))) {
+ err = luo_file_deserialize_one(file_set, file_ser);
if (err)
- return err;
+ goto err_destroy_blocks;
+ file_set->count++;
+ }
+
+ if (file_set->count != file_set_ser->count) {
+ pr_warn("File count mismatch: expected %llu, found %llu\n",
+ file_set_ser->count, file_set->count);
+ err = -EINVAL;
+ goto err_destroy_blocks;
}
return 0;
+
+err_destroy_blocks:
+ while (!list_empty(&file_set->files_list)) {
+ struct luo_file *luo_file;
+
+ luo_file = list_first_entry(&file_set->files_list,
+ struct luo_file, list);
+ list_del(&luo_file->list);
+ module_put(luo_file->fh->ops->owner);
+ mutex_destroy(&luo_file->mutex);
+ kfree(luo_file);
+ }
+ file_set->count = 0;
+ kho_block_set_destroy(&file_set->block_set);
+ return err;
}
void luo_file_set_init(struct luo_file_set *file_set)
{
INIT_LIST_HEAD(&file_set->files_list);
+ kho_block_set_init(&file_set->block_set, sizeof(struct luo_file_ser));
}
void luo_file_set_destroy(struct luo_file_set *file_set)
{
WARN_ON(file_set->count);
WARN_ON(!list_empty(&file_set->files_list));
+ WARN_ON(!kho_block_set_is_empty(&file_set->block_set));
}
/**
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index ee18f9a11b91..64879ffe7378 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -10,6 +10,7 @@
#include <linux/liveupdate.h>
#include <linux/uaccess.h>
+#include <linux/kho_block.h>
struct luo_ucmd {
void __user *ubuffer;
@@ -44,14 +45,13 @@ static inline int luo_ucmd_respond(struct luo_ucmd *ucmd,
* struct luo_file_set - A set of files that belong to the same sessions.
* @files_list: An ordered list of files associated with this session, it is
* ordered by preservation time.
- * @files: The physically contiguous memory block that holds the serialized
- * state of files.
+ * @block_set: The set of serialization blocks.
* @count: A counter tracking the number of files currently stored in the
* @files_list for this session.
*/
struct luo_file_set {
struct list_head files_list;
- struct luo_file_ser *files;
+ struct kho_block_set block_set;
u64 count;
};
--
2.53.0
^ permalink raw reply related
* [PATCH v7 11/13] selftests/liveupdate: Test session and file limit removal
From: Pasha Tatashin @ 2026-06-03 15:44 UTC (permalink / raw)
To: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
linux-kernel, corbet, pasha.tatashin, dmatlack, kexec, pratyush,
skhawaja, graf
In-Reply-To: <20260603154402.468928-1-pasha.tatashin@soleen.com>
With the removal of static limits on the number of sessions and files per
session, the orchestrator now uses dynamic allocation.
Add new test cases to verify that the system can handle a large number of
sessions and files. These tests ensure that the dynamic block allocation
and reuse logic for session metadata and outgoing files work correctly
beyond the previous static limits.
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
.../testing/selftests/liveupdate/liveupdate.c | 75 +++++++++++++++++++
.../selftests/liveupdate/luo_test_utils.c | 24 ++++++
.../selftests/liveupdate/luo_test_utils.h | 2 +
3 files changed, 101 insertions(+)
diff --git a/tools/testing/selftests/liveupdate/liveupdate.c b/tools/testing/selftests/liveupdate/liveupdate.c
index c7d94b9181e1..502fb3567e38 100644
--- a/tools/testing/selftests/liveupdate/liveupdate.c
+++ b/tools/testing/selftests/liveupdate/liveupdate.c
@@ -26,6 +26,7 @@
#include <linux/liveupdate.h>
+#include "luo_test_utils.h"
#include "../kselftest.h"
#include "../kselftest_harness.h"
@@ -499,4 +500,78 @@ TEST_F(liveupdate_device, get_session_name_max_length)
ASSERT_EQ(close(session_fd), 0);
}
+/*
+ * Test Case: Manage Many Sessions
+ *
+ * Verifies that a large number of sessions can be created and then
+ * destroyed during normal system operation. This specifically tests the
+ * dynamic block allocation and reuse logic for session metadata management
+ * without preserving any files.
+ */
+TEST_F(liveupdate_device, preserve_many_sessions)
+{
+#define MANY_SESSIONS 2000
+ int session_fds[MANY_SESSIONS];
+ int ret, i;
+
+ self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+ if (self->fd1 < 0 && errno == ENOENT)
+ SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+ ASSERT_GE(self->fd1, 0);
+
+ ret = luo_ensure_nofile_limit(MANY_SESSIONS);
+ if (ret == -EPERM)
+ SKIP(return, "Insufficient privileges to set RLIMIT_NOFILE");
+ ASSERT_EQ(ret, 0);
+
+ for (i = 0; i < MANY_SESSIONS; i++) {
+ char name[64];
+
+ snprintf(name, sizeof(name), "many-session-%d", i);
+ session_fds[i] = create_session(self->fd1, name);
+ ASSERT_GE(session_fds[i], 0);
+ }
+
+ for (i = 0; i < MANY_SESSIONS; i++)
+ ASSERT_EQ(close(session_fds[i]), 0);
+}
+
+/*
+ * Test Case: Preserve Many Files
+ *
+ * Verifies that a large number of files can be preserved in a single session
+ * and then destroyed during normal system operation. This tests the dynamic
+ * block allocation and management for outgoing files.
+ */
+TEST_F(liveupdate_device, preserve_many_files)
+{
+#define MANY_FILES 500
+ int mem_fds[MANY_FILES];
+ int session_fd, ret, i;
+
+ self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+ if (self->fd1 < 0 && errno == ENOENT)
+ SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+ ASSERT_GE(self->fd1, 0);
+
+ session_fd = create_session(self->fd1, "many-files-test");
+ ASSERT_GE(session_fd, 0);
+
+ ret = luo_ensure_nofile_limit(MANY_FILES + 10);
+ if (ret == -EPERM)
+ SKIP(return, "Insufficient privileges to set RLIMIT_NOFILE");
+ ASSERT_EQ(ret, 0);
+
+ for (i = 0; i < MANY_FILES; i++) {
+ mem_fds[i] = memfd_create("test-memfd", 0);
+ ASSERT_GE(mem_fds[i], 0);
+ ASSERT_EQ(preserve_fd(session_fd, mem_fds[i], i), 0);
+ }
+
+ for (i = 0; i < MANY_FILES; i++)
+ ASSERT_EQ(close(mem_fds[i]), 0);
+
+ ASSERT_EQ(close(session_fd), 0);
+}
+
TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.c b/tools/testing/selftests/liveupdate/luo_test_utils.c
index 3c8721c505df..333a3530051b 100644
--- a/tools/testing/selftests/liveupdate/luo_test_utils.c
+++ b/tools/testing/selftests/liveupdate/luo_test_utils.c
@@ -17,6 +17,7 @@
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/types.h>
+#include <sys/resource.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdarg.h>
@@ -28,6 +29,29 @@ int luo_open_device(void)
return open(LUO_DEVICE, O_RDWR);
}
+int luo_ensure_nofile_limit(long min_limit)
+{
+ struct rlimit hl;
+
+ /* Allow to extra files to be used by test itself */
+ min_limit += 32;
+
+ if (getrlimit(RLIMIT_NOFILE, &hl) < 0)
+ return -errno;
+
+ if (hl.rlim_cur >= min_limit)
+ return 0;
+
+ hl.rlim_cur = min_limit;
+ if (hl.rlim_cur > hl.rlim_max)
+ hl.rlim_max = hl.rlim_cur;
+
+ if (setrlimit(RLIMIT_NOFILE, &hl) < 0)
+ return -errno;
+
+ return 0;
+}
+
int luo_create_session(int luo_fd, const char *name)
{
struct liveupdate_ioctl_create_session arg = { .size = sizeof(arg) };
diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.h b/tools/testing/selftests/liveupdate/luo_test_utils.h
index 90099bf49577..6a0d85386613 100644
--- a/tools/testing/selftests/liveupdate/luo_test_utils.h
+++ b/tools/testing/selftests/liveupdate/luo_test_utils.h
@@ -26,6 +26,8 @@ int luo_create_session(int luo_fd, const char *name);
int luo_retrieve_session(int luo_fd, const char *name);
int luo_session_finish(int session_fd);
+int luo_ensure_nofile_limit(long min_limit);
+
int create_and_preserve_memfd(int session_fd, int token, const char *data);
int restore_and_verify_memfd(int session_fd, int token, const char *expected_data);
--
2.53.0
^ permalink raw reply related
* [PATCH v7 12/13] selftests/liveupdate: Add stress-sessions kexec test
From: Pasha Tatashin @ 2026-06-03 15:44 UTC (permalink / raw)
To: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
linux-kernel, corbet, pasha.tatashin, dmatlack, kexec, pratyush,
skhawaja, graf
In-Reply-To: <20260603154402.468928-1-pasha.tatashin@soleen.com>
Add a new test that creates 2000 LUO sessions before a kexec
reboot and verifies their presence after the reboot. This ensures
that the linked-block serialization mechanism works correctly for
a large number of sessions.
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
tools/testing/selftests/liveupdate/Makefile | 1 +
.../liveupdate/luo_stress_sessions.c | 102 ++++++++++++++++++
2 files changed, 103 insertions(+)
create mode 100644 tools/testing/selftests/liveupdate/luo_stress_sessions.c
diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile
index 080754787ede..ed7534468386 100644
--- a/tools/testing/selftests/liveupdate/Makefile
+++ b/tools/testing/selftests/liveupdate/Makefile
@@ -6,6 +6,7 @@ TEST_GEN_PROGS += liveupdate
TEST_GEN_PROGS_EXTENDED += luo_kexec_simple
TEST_GEN_PROGS_EXTENDED += luo_multi_session
+TEST_GEN_PROGS_EXTENDED += luo_stress_sessions
TEST_FILES += do_kexec.sh
diff --git a/tools/testing/selftests/liveupdate/luo_stress_sessions.c b/tools/testing/selftests/liveupdate/luo_stress_sessions.c
new file mode 100644
index 000000000000..f201b1839d1d
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/luo_stress_sessions.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ *
+ * Validate that LUO can handle a large number of sessions across a kexec
+ * reboot.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include "luo_test_utils.h"
+
+#define NUM_SESSIONS 2000
+#define STATE_SESSION_NAME "kexec_many_state"
+#define STATE_MEMFD_TOKEN 999
+
+/* Stage 1: Executed before the kexec reboot. */
+static void run_stage_1(int luo_fd)
+{
+ int ret, i;
+
+ ksft_print_msg("[STAGE 1] Increasing ulimit for open files...\n");
+ ret = luo_ensure_nofile_limit(NUM_SESSIONS);
+ if (ret == -EPERM)
+ ksft_exit_skip("Insufficient privileges to set RLIMIT_NOFILE\n");
+ if (ret < 0)
+ ksft_exit_fail_msg("luo_ensure_nofile_limit failed: %s\n", strerror(-ret));
+
+ ksft_print_msg("[STAGE 1] Creating state file for next stage (2)...\n");
+ create_state_file(luo_fd, STATE_SESSION_NAME, STATE_MEMFD_TOKEN, 2);
+
+ ksft_print_msg("[STAGE 1] Creating %d sessions...\n", NUM_SESSIONS);
+
+ for (i = 0; i < NUM_SESSIONS; i++) {
+ char name[LIVEUPDATE_SESSION_NAME_LENGTH];
+ int s_fd;
+
+ snprintf(name, sizeof(name), "many-test-%d", i);
+ s_fd = luo_create_session(luo_fd, name);
+ if (s_fd < 0) {
+ fail_exit("luo_create_session for '%s' at index %d",
+ name, i);
+ }
+ }
+
+ ksft_print_msg("[STAGE 1] Successfully created %d sessions.\n",
+ NUM_SESSIONS);
+
+ close(luo_fd);
+ daemonize_and_wait();
+}
+
+/* Stage 2: Executed after the kexec reboot. */
+static void run_stage_2(int luo_fd, int state_session_fd)
+{
+ int i, stage;
+
+ ksft_print_msg("[STAGE 2] Starting post-kexec verification...\n");
+
+ restore_and_read_stage(state_session_fd, STATE_MEMFD_TOKEN, &stage);
+ if (stage != 2) {
+ fail_exit("Expected stage 2, but state file contains %d",
+ stage);
+ }
+
+ ksft_print_msg("[STAGE 2] Retrieving and finishing %d sessions...\n",
+ NUM_SESSIONS);
+
+ for (i = 0; i < NUM_SESSIONS; i++) {
+ char name[LIVEUPDATE_SESSION_NAME_LENGTH];
+ int s_fd;
+
+ snprintf(name, sizeof(name), "many-test-%d", i);
+ s_fd = luo_retrieve_session(luo_fd, name);
+ if (s_fd < 0) {
+ fail_exit("luo_retrieve_session for '%s' at index %d",
+ name, i);
+ }
+
+ if (luo_session_finish(s_fd) < 0) {
+ fail_exit("luo_session_finish for '%s' at index %d",
+ name, i);
+ }
+ close(s_fd);
+ }
+
+ ksft_print_msg("[STAGE 2] Finalizing state session...\n");
+ if (luo_session_finish(state_session_fd) < 0)
+ fail_exit("luo_session_finish for state session");
+ close(state_session_fd);
+
+ ksft_print_msg("\n--- MANY-SESSIONS KEXEC TEST PASSED (%d sessions) ---\n",
+ NUM_SESSIONS);
+}
+
+int main(int argc, char *argv[])
+{
+ return luo_test(argc, argv, STATE_SESSION_NAME,
+ run_stage_1, run_stage_2);
+}
--
2.53.0
^ permalink raw reply related
* [PATCH v7 13/13] selftests/liveupdate: Add stress-files kexec test
From: Pasha Tatashin @ 2026-06-03 15:44 UTC (permalink / raw)
To: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
linux-kernel, corbet, pasha.tatashin, dmatlack, kexec, pratyush,
skhawaja, graf
In-Reply-To: <20260603154402.468928-1-pasha.tatashin@soleen.com>
Add a new luo_stress_files kexec test that verifies preserving and
retrieving 500 files across a kexec reboot.
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
tools/testing/selftests/liveupdate/Makefile | 1 +
.../selftests/liveupdate/luo_stress_files.c | 97 +++++++++++++++++++
2 files changed, 98 insertions(+)
create mode 100644 tools/testing/selftests/liveupdate/luo_stress_files.c
diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile
index ed7534468386..30689d22cb02 100644
--- a/tools/testing/selftests/liveupdate/Makefile
+++ b/tools/testing/selftests/liveupdate/Makefile
@@ -7,6 +7,7 @@ TEST_GEN_PROGS += liveupdate
TEST_GEN_PROGS_EXTENDED += luo_kexec_simple
TEST_GEN_PROGS_EXTENDED += luo_multi_session
TEST_GEN_PROGS_EXTENDED += luo_stress_sessions
+TEST_GEN_PROGS_EXTENDED += luo_stress_files
TEST_FILES += do_kexec.sh
diff --git a/tools/testing/selftests/liveupdate/luo_stress_files.c b/tools/testing/selftests/liveupdate/luo_stress_files.c
new file mode 100644
index 000000000000..0cdf9cd4bac7
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/luo_stress_files.c
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ *
+ * Validate that LUO can handle a large number of files per session across
+ * a kexec reboot.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include "luo_test_utils.h"
+
+#define NUM_FILES 500
+#define STATE_SESSION_NAME "kexec_many_files_state"
+#define STATE_MEMFD_TOKEN 9999
+#define TEST_SESSION_NAME "many_files_session"
+
+/* Stage 1: Executed before the kexec reboot. */
+static void run_stage_1(int luo_fd)
+{
+ int session_fd, i;
+
+ ksft_print_msg("[STAGE 1] Creating state file for next stage (2)...\n");
+ create_state_file(luo_fd, STATE_SESSION_NAME, STATE_MEMFD_TOKEN, 2);
+
+ ksft_print_msg("[STAGE 1] Creating test session '%s'...\n", TEST_SESSION_NAME);
+ session_fd = luo_create_session(luo_fd, TEST_SESSION_NAME);
+ if (session_fd < 0)
+ fail_exit("luo_create_session");
+
+ ksft_print_msg("[STAGE 1] Preserving %d files...\n", NUM_FILES);
+ for (i = 0; i < NUM_FILES; i++) {
+ char data[64];
+
+ snprintf(data, sizeof(data), "file-data-%d", i);
+ if (create_and_preserve_memfd(session_fd, i, data) < 0)
+ fail_exit("create_and_preserve_memfd for index %d", i);
+ }
+
+ ksft_print_msg("[STAGE 1] Successfully preserved %d files.\n", NUM_FILES);
+
+ close(luo_fd);
+ daemonize_and_wait();
+}
+
+/* Stage 2: Executed after the kexec reboot. */
+static void run_stage_2(int luo_fd, int state_session_fd)
+{
+ int session_fd;
+ int i, stage;
+
+ ksft_print_msg("[STAGE 2] Starting post-kexec verification...\n");
+
+ restore_and_read_stage(state_session_fd, STATE_MEMFD_TOKEN, &stage);
+ if (stage != 2) {
+ fail_exit("Expected stage 2, but state file contains %d",
+ stage);
+ }
+
+ ksft_print_msg("[STAGE 2] Retrieving test session '%s'...\n", TEST_SESSION_NAME);
+ session_fd = luo_retrieve_session(luo_fd, TEST_SESSION_NAME);
+ if (session_fd < 0)
+ fail_exit("luo_retrieve_session");
+
+ ksft_print_msg("[STAGE 2] Verifying %d files...\n", NUM_FILES);
+ for (i = 0; i < NUM_FILES; i++) {
+ char data[64];
+ int fd;
+
+ snprintf(data, sizeof(data), "file-data-%d", i);
+ fd = restore_and_verify_memfd(session_fd, i, data);
+ if (fd < 0)
+ fail_exit("restore_and_verify_memfd for index %d", i);
+ close(fd);
+ }
+
+ ksft_print_msg("[STAGE 2] Finishing test session...\n");
+ if (luo_session_finish(session_fd) < 0)
+ fail_exit("luo_session_finish for test session");
+ close(session_fd);
+
+ ksft_print_msg("[STAGE 2] Finalizing state session...\n");
+ if (luo_session_finish(state_session_fd) < 0)
+ fail_exit("luo_session_finish for state session");
+ close(state_session_fd);
+
+ ksft_print_msg("\n--- MANY-FILES KEXEC TEST PASSED (%d files) ---\n",
+ NUM_FILES);
+}
+
+int main(int argc, char *argv[])
+{
+ return luo_test(argc, argv, STATE_SESSION_NAME,
+ run_stage_1, run_stage_2);
+}
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v2 05/18] perf test: Add deterministic workload
From: James Clark @ 2026-06-03 15:53 UTC (permalink / raw)
To: Leo Yan
Cc: Suzuki K Poulose, Mike Leach, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Amir Ayupov, Jonathan Corbet,
Shuah Khan, Paschalis Mpeis, coresight, linux-perf-users,
linux-kernel, Arnaldo Carvalho de Melo, linux-doc
In-Reply-To: <20260603134339.GU101133@e132581.arm.com>
On 03/06/2026 2:43 pm, Leo Yan wrote:
> On Wed, Jun 03, 2026 at 02:10:37PM +0100, James Clark wrote:
>>
>>
>> On 03/06/2026 12:27 pm, Leo Yan wrote:
>>> On Tue, Jun 02, 2026 at 03:26:47PM +0100, James Clark wrote:
>>>
>>> [...]
>>>
>>>> @@ -22,3 +23,4 @@ CFLAGS_brstack.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE
>>>> CFLAGS_datasym.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE
>>>> CFLAGS_traploop.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE
>>>> CFLAGS_inlineloop.o = -g -O2
>>>> +CFLAGS_deterministic.o = -g -O0
>>>
>>> I have no strong opinion for using 'noinline' in source or using the
>>> global option '-fno-inline', just thought this is not easy to follow
>>> up if anyone (likely myself) will write a new workload for disabling
>>> inline. Could we have consistent style for this?
>>>
>>> For the patch itself:
>>>
>>> Reviewed-by: Leo Yan <leo.yan@arm.com>
>>
>> Actually it's a fair question why some have -fno-inline and others do it in
>> the code, it could just be copied from when these were built by their shell
>> script tests. From a quick look I would say we can easily drop the
>> -fno-inline and do it in the code, and it's better to only noinline what's
>> needed rather than everything. But that's probably a change for another
>> time.
>
> Seems to me, `-fno-inline` is more reliable.
>
> I.e., in this patch deterministic() has no 'noinline' annotation, my
> understanding is the test expects it is not inlined. With `-fno-inline`
> flag, we don't need to worry anything is missed.
>
> Thanks,
> Leo
Ok I can replace the noinlines with -fno-inline then.
^ permalink raw reply
* Re: [PATCH v2 06/18] perf test cs-etm: Replace unroll loop thread with deterministic decode test
From: James Clark @ 2026-06-03 16:01 UTC (permalink / raw)
To: Leo Yan
Cc: Suzuki K Poulose, Mike Leach, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Amir Ayupov, Jonathan Corbet,
Shuah Khan, Paschalis Mpeis, coresight, linux-perf-users,
linux-kernel, Arnaldo Carvalho de Melo, linux-doc
In-Reply-To: <20260603140803.GV101133@e132581.arm.com>
On 03/06/2026 3:08 pm, Leo Yan wrote:
> On Tue, Jun 02, 2026 at 03:26:48PM +0100, James Clark wrote:
>
> [...]
>
>> +# Remove open brace lines as they may not be hit depending on the compiler
>> +sed -i \
>> + -e '/deterministic.c:8$/d' \
>> + -e '/deterministic.c:15$/d' \
>> + -e '/deterministic.c:23$/d' \
>> + "$tmpdir/script"
>
> Is this related to the function definition?
>
> I can see the brace lines with change below. It might be more reliable
> if adding unused function argument, which can give chance for hit
> function entry.
>
> static int function1(void)
> {
> ...
>
> return 0;
> }
>
> Thanks,
> Leo
Originally I included the brace lines in the test and it was working
even without function arguments, but Sashiko mentioned that they may not
always be hit.
I think its point was that there is no hard rule about debug symbols for
open braces and the behavior might change from one version of the
compiler to the next, or whether there is a function prologue or
inlining or not etc.
I don't think it's important to the test at all though? So to err on the
side of caution it makes sense to not test for them. Unless there's a
reason you think testing for open braces is important? Surely just
testing for actual lines of code appearing in a certain order is enough.
^ permalink raw reply
* Re: [PATCH v4 0/2] Delete task_euid()
From: Paul Moore @ 2026-06-03 16:04 UTC (permalink / raw)
To: Alice Ryhl
Cc: Serge Hallyn, Jonathan Corbet, Greg Kroah-Hartman, Shuah Khan,
Alex Shi, Yanteng Si, Dongliang Mu, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Danilo Krummrich, Jann Horn, linux-security-module,
linux-doc, linux-kernel, rust-for-linux
In-Reply-To: <ah51DY5yfaNZejBd@google.com>
On Tue, Jun 2, 2026 at 2:15 AM Alice Ryhl <aliceryhl@google.com> wrote:
> On Mon, Jun 01, 2026 at 07:13:37PM -0400, Paul Moore wrote:
> > On Fri, May 29, 2026 at 5:33 AM Alice Ryhl <aliceryhl@google.com> wrote:
> > >
> > > The task_euid() method is a very weird method, and Binder was the only
> > > user. As of commit 65b672152289 ("binder: use current_euid() for
> > > transaction sender identity") Binder doesn't use task_euid() anymore,
> > > so we can delete this method.
> >
> > Given the problems from last time, it seems like it might be prudent
> > to let the commit have some time to "breathe" in a proper release, I'd
> > suggest merging this not for the upcoming v7.2 merge window but
> > instead waiting for v7.3.
>
> Sure, that makes sense. I'll resend after the merge window.
No need to resend if there are no changes (see below), it's in
patchwork and I'm tracking it so you're all set. I'll send another
notice when I merge it.
> > > My suggestion would be to merge this through the LSM tree.
> >
> > That's fine with me. I'd also suggest updating the commit description
> > in patch 1/2 to indicate that binder is no longer using task_euid();
> > it currently reads like it is still being used.
>
> I guess this occurred because when patch 1 was written, it really *was*
> still being used.
Yeah, I understand the world has changed since patch 1/2 was written,
which is okay, we just need to update the commit description ... which
should be a trivial task.
> Perhaps we could pick up only patch 1 now since even
> if we run into problems and Binder has to go back to using task_euid(),
> clarifying the docs is still useful.
I assumed that was one of the reasons for splitting the changes across
two patches (reverting patch 2/2 leaves patch 1/2 intact).
Regardless, we're at -rc6 and with patch 1/2 being purely a comment
update I don't see an urgent rush on this, especially considering that
if I did pick it up now, it would be for the v7.2 merge window and the
binder/current_euid() change will ship in v7.1.
Let's update the commit description - you've got a couple of weeks to
do that - and then we'll merge everything once the v7.2 merge window
closes.
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH v2 08/18] perf test cs-etm: Replace memcpy test with raw dump stress test
From: James Clark @ 2026-06-03 16:11 UTC (permalink / raw)
To: Leo Yan
Cc: Suzuki K Poulose, Mike Leach, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Amir Ayupov, Jonathan Corbet,
Shuah Khan, Paschalis Mpeis, coresight, linux-perf-users,
linux-kernel, Arnaldo Carvalho de Melo, linux-doc
In-Reply-To: <20260603143622.GX101133@e132581.arm.com>
On 03/06/2026 3:36 pm, Leo Yan wrote:
> On Tue, Jun 02, 2026 at 03:26:50PM +0100, James Clark wrote:
>
> [...]
>
>> +# Use exit snapshot to record 2M of trace to make about 80MB of raw dump data.
>> +echo "Recording..."
>> +perf record -e cs_etm/timestamp=0/u -m,2M -Se -o "$tmpdir/data" -- \
>> + perf test -w brstack 20000 > /dev/null 2>&1
>
> ...
>
>> +size=$(stat -c%s "$tmpdir/rawdump")
>> +if [ $size -gt $((50 * 1024 * 1024)) ]; then
>> + echo "PASS: Raw dump file is larger than 50MB"
>> + cleanup
>> + exit 0
>> +fi
>
> I am not sure how we can map 2MiB trace data to 50MiB+ raw dump. This
Why not? Decoding it is roughly equal to decompressing it, and with that
amount of trace the small differences in compressibility average out and
you do get the same amount every time. I think if we got less than half
the amount expected then it would be worth investigating.
> is not to verify perf stuffs but just expect how verbose output from
> decoder.
>
> Can we simply verify the trace data is ~2MiB with the command:
>
> perf report -D | grep ". ... CoreSight .* Trace data: size .* bytes"
>
> We might relax the check as the trace size >= 90% * 2MiB
>
> Thanks,
> Leo
That would work, but that wouldn't be as thorough. The first thing it
prints is " CoreSight .* Trace data: size .* bytes". If it stops working
half way through or prints nothing then the test will still pass.
The reason I wanted to add a stress test is because all of the other
tests have been reduced to just a few kb of trace so we have nothing
that opens a file with a more reasonable amount of data.
I suppose with your suggestion we'd still check the exit code, but
that's about it.
^ permalink raw reply
* Re: [PATCH v2 09/18] perf test: Add named_threads workload
From: James Clark @ 2026-06-03 16:12 UTC (permalink / raw)
To: Leo Yan
Cc: Suzuki K Poulose, Mike Leach, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Amir Ayupov, Jonathan Corbet,
Shuah Khan, Paschalis Mpeis, coresight, linux-perf-users,
linux-kernel, Arnaldo Carvalho de Melo, linux-doc
In-Reply-To: <20260603145415.GY101133@e132581.arm.com>
On 03/06/2026 3:54 pm, Leo Yan wrote:
> On Tue, Jun 02, 2026 at 03:26:51PM +0100, James Clark wrote:
>
>> +#define DEFINE_THREAD(n) \
>> +noinline void *named_threads_thread##n(void *arg __maybe_unused) \
>> +{ \
>> + pthread_setname_np(pthread_self(), "thread" #n); \
>> + for (int i = 0; i < iterations; i++) \
>> + named_threads_work *= 3; \
>> + \
>> + return NULL; \
>> +}
>
>> +static int named_threads(int argc, const char **argv)
>> +{
>> + pthread_t threads[MAX_THREADS];
>> + int nr_threads = 1;
>> + int err = 0;
>> +
>> + if (argc > 0)
>> + nr_threads = atoi(argv[0]);
>> +
>> + if (nr_threads <= 0 || nr_threads > MAX_THREADS) {
>> + fprintf(stderr, "Error: num threads must be 1 - %d\n", MAX_THREADS);
>> + return 1;
>> + }
>> +
>> + if (argc > 1)
>> + iterations = atoi(argv[1]);
>> +
>> + if (iterations < 0) {
>> + fprintf(stderr, "Error: iterations must be non-negative\n");
>> + return 1;
>> + }
>> +
>> + for (int i = 0; i < nr_threads; i++) {
>> + int ret;
>> +
>> + ret = pthread_create(&threads[i], NULL, thread_fns[i], NULL);
>
> Just curious this can be simplified to a thread function, like:
>
> noinline void *named_thread(void *arg)
> {
> char name[16];
>
> snprintf(name, sizeof(name), "thread%d", int(arg));
>
> pthread_setname_np(pthread_self(), name);
> ...
> return NULL;
> }
>
> Thanks,
> Leo
Only if you don't want to check for symbols as well. I thought if we
were going to spawn a load of threads and look for thread names we might
as well check that the symbols match at the same time.
If all of the threads run the same function you can't do that.
^ permalink raw reply
* [PATCH net-next] docs: exclude driver and netdevsim bugs
From: Jakub Kicinski @ 2026-06-03 16:29 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, johannes,
Jakub Kicinski, corbet, skhan, workflows, linux-doc
Initial wave of AI-generated fixes was mostly for core and protocols
we care about. But the number of irrelevant driver fixes is slowly
increasing. Add a section of explicit exclusions to our maintainer
profile.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
CC: corbet@lwn.net
CC: skhan@linuxfoundation.org
CC: workflows@vger.kernel.org
CC: linux-doc@vger.kernel.org
---
Documentation/process/maintainer-netdev.rst | 28 +++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/Documentation/process/maintainer-netdev.rst b/Documentation/process/maintainer-netdev.rst
index ec7b9aa2877f..cc4b5fa3b5c1 100644
--- a/Documentation/process/maintainer-netdev.rst
+++ b/Documentation/process/maintainer-netdev.rst
@@ -272,6 +272,34 @@ the case today. Please follow the standard stable rules in
:ref:`Documentation/process/stable-kernel-rules.rst <stable_kernel_rules>`,
and make sure you include appropriate Fixes tags!
+Bug fixes
+~~~~~~~~~
+
+Unless explicitly excluded all bug fixes should be targeting the ``net``
+tree and contain an appropriate Fixes tag.
+
+Obvious exclusions:
+
+ - fixes for bugs which only exist in ``net-next`` should target ``net-next``
+ (please still include the Fixes tag in the commit message)
+ - bugs which cannot be reached, e.g. in code paths not executed given
+ current in-tree callers
+ - fixes for compiler warnings and typos
+
+Fixes for selftests and selftest-related infrastructure (most notably
+including the ``netdevsim`` driver) are only considered ``net``-worthy
+if they substantially reduce the flakiness of the test.
+
+Additionally, netdev does not consider bugs to be ``net``-worthy
+if they fulfill **all** of the following criteria:
+ - bug is in a hardware device driver;
+ - bug is either a missing error handling or is part of the error handling flow;
+ - bug was discovered by a static analysis / AI tool;
+ - bug was triggered/observed only with kernel changes or fault injection.
+Fixes for such bugs should default to ``net-next`` and should **not** contain
+a Fixes tag. Networking or driver maintainers may redirect such fixes to ``net``
+at their discretion if they consider the condition to be relevant enough.
+
Security fixes
~~~~~~~~~~~~~~
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v4 0/2] Delete task_euid()
From: Alice Ryhl @ 2026-06-03 17:05 UTC (permalink / raw)
To: Paul Moore
Cc: Serge Hallyn, Jonathan Corbet, Greg Kroah-Hartman, Shuah Khan,
Alex Shi, Yanteng Si, Dongliang Mu, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Danilo Krummrich, Jann Horn, linux-security-module,
linux-doc, linux-kernel, rust-for-linux
In-Reply-To: <CAHC9VhQyNzxJgdMkmEsOeAQ7Wt2L+eW6aNLjeoYmnCQLmcYRnw@mail.gmail.com>
On Wed, Jun 3, 2026 at 6:05 PM Paul Moore <paul@paul-moore.com> wrote:
>
> On Tue, Jun 2, 2026 at 2:15 AM Alice Ryhl <aliceryhl@google.com> wrote:
> > On Mon, Jun 01, 2026 at 07:13:37PM -0400, Paul Moore wrote:
> > > On Fri, May 29, 2026 at 5:33 AM Alice Ryhl <aliceryhl@google.com> wrote:
> > > >
> > > > The task_euid() method is a very weird method, and Binder was the only
> > > > user. As of commit 65b672152289 ("binder: use current_euid() for
> > > > transaction sender identity") Binder doesn't use task_euid() anymore,
> > > > so we can delete this method.
> > >
> > > Given the problems from last time, it seems like it might be prudent
> > > to let the commit have some time to "breathe" in a proper release, I'd
> > > suggest merging this not for the upcoming v7.2 merge window but
> > > instead waiting for v7.3.
> >
> > Sure, that makes sense. I'll resend after the merge window.
>
> No need to resend if there are no changes (see below), it's in
> patchwork and I'm tracking it so you're all set. I'll send another
> notice when I merge it.
>
> > > > My suggestion would be to merge this through the LSM tree.
> > >
> > > That's fine with me. I'd also suggest updating the commit description
> > > in patch 1/2 to indicate that binder is no longer using task_euid();
> > > it currently reads like it is still being used.
> >
> > I guess this occurred because when patch 1 was written, it really *was*
> > still being used.
>
> Yeah, I understand the world has changed since patch 1/2 was written,
> which is okay, we just need to update the commit description ... which
> should be a trivial task.
>
> > Perhaps we could pick up only patch 1 now since even
> > if we run into problems and Binder has to go back to using task_euid(),
> > clarifying the docs is still useful.
>
> I assumed that was one of the reasons for splitting the changes across
> two patches (reverting patch 2/2 leaves patch 1/2 intact).
> Regardless, we're at -rc6 and with patch 1/2 being purely a comment
> update I don't see an urgent rush on this, especially considering that
> if I did pick it up now, it would be for the v7.2 merge window and the
> binder/current_euid() change will ship in v7.1.
>
> Let's update the commit description - you've got a couple of weeks to
> do that - and then we'll merge everything once the v7.2 merge window
> closes.
Sounds good, thanks!
Alice
^ permalink raw reply
* Re: [PATCH v2 06/18] perf test cs-etm: Replace unroll loop thread with deterministic decode test
From: Leo Yan @ 2026-06-03 17:08 UTC (permalink / raw)
To: James Clark
Cc: Suzuki K Poulose, Mike Leach, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Amir Ayupov, Jonathan Corbet,
Shuah Khan, Paschalis Mpeis, coresight, linux-perf-users,
linux-kernel, Arnaldo Carvalho de Melo, linux-doc
In-Reply-To: <2f1db2b3-93e8-4c32-b207-304e3a43ce77@linaro.org>
On Wed, Jun 03, 2026 at 05:01:02PM +0100, James Clark wrote:
[...]
> > > +# Remove open brace lines as they may not be hit depending on the compiler
> > > +sed -i \
> > > + -e '/deterministic.c:8$/d' \
> > > + -e '/deterministic.c:15$/d' \
> > > + -e '/deterministic.c:23$/d' \
> > > + "$tmpdir/script"
> >
> > Is this related to the function definition?
> >
> > I can see the brace lines with change below. It might be more reliable
> > if adding unused function argument, which can give chance for hit
> > function entry.
> >
> > static int function1(void)
> > {
> > ...
> >
> > return 0;
> > }
>
> Originally I included the brace lines in the test and it was working even
> without function arguments, but Sashiko mentioned that they may not always
> be hit.
I tried Clang to build the program and can see the brace lines are
missed for function1() / function2().
Does Sashiko mention any reasons causing the issue?
> I think its point was that there is no hard rule about debug symbols for
> open braces and the behavior might change from one version of the compiler
> to the next, or whether there is a function prologue or inlining or not etc.
>
> I don't think it's important to the test at all though? So to err on the
> side of caution it makes sense to not test for them. Unless there's a reason
> you think testing for open braces is important? Surely just testing for
> actual lines of code appearing in a certain order is enough.
As the test program is named as "deterministic", wouldn't we expect the
test to hit every code line run in the program?
It is fine for me to skip some checks _if_ we know the reason. I dumped
the disassembly, it shows function entry is a distinguished position
from the first calculation sentence (same for both GCC and Clang). And
there have no difference for a function entry after I tweaked the
function return type from "void" to "int". I still have no clue why
brace lines are misses.
Thanks,
Leo
^ permalink raw reply
* Re: [PATCH v2 11/18] perf test cs-etm: Remove duplicate branch tests
From: Leo Yan @ 2026-06-03 17:11 UTC (permalink / raw)
To: James Clark
Cc: Suzuki K Poulose, Mike Leach, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Amir Ayupov, Jonathan Corbet,
Shuah Khan, Paschalis Mpeis, coresight, linux-perf-users,
linux-kernel, Arnaldo Carvalho de Melo, linux-doc
In-Reply-To: <20260602-james-cs-context-tracking-fix-v2-11-85b5ce6f55c6@linaro.org>
On Tue, Jun 02, 2026 at 03:26:53PM +0100, James Clark wrote:
> We already test branch output in perf script mode, but then retest it in
> Perf report mode. This is more of a test of Perf itself than Coresight
> because Perf uses the same samples to generate both outputs. Also we're
> already testing instruction output in Perf report mode.
>
> Remove this test for a speedup. On the systemwide test also remove the
> Perf report test because systemwide mode records a lot more data so
> running multiple tests on it has a big runtime impact.
>
> Signed-off-by: James Clark <james.clark@linaro.org>
Reviewed-by: Leo Yan <leo.yan@arm.com>
^ permalink raw reply
* Re: [PATCH net-next V3 0/2] devlink: add generic device max_sfs parameter
From: Nikolay Aleksandrov @ 2026-06-03 17:12 UTC (permalink / raw)
To: Tariq Toukan, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, David S. Miller
Cc: Jiri Pirko, Simon Horman, Jonathan Corbet, Shuah Khan,
Saeed Mahameed, Leon Romanovsky, Mark Bloch, Vlad Dumitrescu,
Daniel Zahka, Aleksandr Loktionov, Przemek Kitszel,
Arthur Kiyanovski, Petr Machata, Ratheesh Kannoth, David Ahern,
netdev, linux-doc, linux-kernel, linux-rdma, Gal Pressman,
Amery Hung
In-Reply-To: <20260603102646.404797-1-tariqt@nvidia.com>
On 03/06/2026 13:26, Tariq Toukan wrote:
> Hi,
>
> This series by Nikolay introduces a new generic devlink device
> parameter, max_sfs, to control the number of light-weight NIC
> subfunctions (SFs) that can be created on a device.
>
> The first patch adds the generic devlink parameter and infrastructure
> support.
> The second patch implements support for the parameter in the mlx5
> driver.
>
> With this addition, users can enable or disable SF creation directly via
> devlink, without relying on external vendor-specific tools.
>
> Regards,
> Tariq
>
>
Need to rebase due to commit d603517771d8 ("devlink: pass param values by
pointer"). Sorry for the noise.
^ permalink raw reply
* Re: [PATCH v2 12/18] perf test cs-etm: Reduce snapshot size
From: Leo Yan @ 2026-06-03 17:12 UTC (permalink / raw)
To: James Clark
Cc: Suzuki K Poulose, Mike Leach, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Amir Ayupov, Jonathan Corbet,
Shuah Khan, Paschalis Mpeis, coresight, linux-perf-users,
linux-kernel, Arnaldo Carvalho de Melo, linux-doc
In-Reply-To: <20260602-james-cs-context-tracking-fix-v2-12-85b5ce6f55c6@linaro.org>
On Tue, Jun 02, 2026 at 03:26:54PM +0100, James Clark wrote:
> The default buffer size for root is 4MB which is very slow to decode. We
> only need a few KB to verify that the dd process is hit so reduce the
> size to 128KB.
>
> Signed-off-by: James Clark <james.clark@linaro.org>
Reviewed-by: Leo Yan <leo.yan@arm.com>
^ permalink raw reply
* Re: [RFC PATCH 0/5] mm, swap: Virtual Swap Space (Swap Table Edition)
From: Nhat Pham @ 2026-06-03 17:12 UTC (permalink / raw)
To: Yosry Ahmed
Cc: kasong, Liam.Howlett, akpm, apopple, axelrasmussen, baohua,
baolin.wang, bhe, byungchul, cgroups, chengming.zhou, chrisl,
corbet, david, dev.jain, gourry, hannes, hughd, jannh,
joshua.hahnjy, lance.yang, lenb, linux-doc, linux-kernel,
linux-mm, linux-pm, lorenzo.stoakes, matthew.brost, mhocko,
muchun.song, npache, pavel, peterx, peterz, pfalcato, rafael,
rakie.kim, roman.gushchin, rppt, ryan.roberts, shakeel.butt,
shikemeng, surenb, tglx, vbabka, weixugc, ying.huang, yosry.ahmed,
yuanchu, zhengqi.arch, ziy, kernel-team, riel, haowenchao22
In-Reply-To: <ah-A2gQ0GPgerXop@google.com>
On Tue, Jun 2, 2026 at 6:29 PM Yosry Ahmed <yosry@kernel.org> wrote:
>
> > II. Design
> >
> > With vswap, pages are assigned virtual swap entries on a ghost device
> > with no backing storage. These entries are backed by zswap, zero pages,
> > or (lazily) physical swap slots. Physical backing is allocated only
> > when needed — on zswap writeback or reclaim writeout, after the rmap
> > step.
> >
> > Compared to the standalone v6 implementation [1], which introduces a
> > 24-byte per-entry swap descriptor and its own cluster allocator, this
> > edition uses swap_table infrastructure, and share a lot of the allocator
> > logic. Per-slot metadata is stored in a tag-encoded virtual_table
> > (atomic_long_t, 8 bytes per slot), and physical clusters store
> > Pointer-tagged rmap entries in the swap_table for reverse lookup back to
> > the virtual cluster.
> >
> > Here are some data layout diagrams:
> >
> > Case 1: vswap entry (virtualized)
> >
> > PTE swap_cluster_info_dynamic
> > vswap_entry +-------------------------+
> > (swp_entry_t) ------>| swap_cluster_info (ci) |
> > | +--------------------+ |
> > | | swap_table | |
> > | | PFN / Shadow | |
> > | | memcg_table | |
> > | | count,flags,order | |
> > | | lock, list | |
> > | +--------------------+ |
> > | |
> > | virtual_table |
> > | +--------------------+ |
> > | | NONE | |
> > | | PHYS | |
> > | | ZERO | |
> > | | ZSWAP(entry*) | |
> > | | FOLIO(folio*) | |
> > | +--------------------+ |
> > +-------------------------+
> > |
> > | PHYS resolves to
> > v
> > PHYSICAL CLUSTER (swap_cluster_info)
> > +--------------------------+
> > | swap_table per-slot: |
> > | NULL - free |
> > | PFN - cached folio |
> > | Shadow - swapped out |
> > | Pointer- vswap rmap |
> > | Bad - unusable |
> > | |
> > | Vswap-backing slot: |
> > | Pointer(C|swp_entry_t) |
> > | rmap back to vswap |
> > +--------------------------+
> >
> > Case 2: direct-mapped physical entry (no vswap)
> >
> > PTE PHYSICAL CLUSTER (swap_cluster_info)
> > phys_entry +--------------------------+
> > (swp_entry_t) ------>| swap_table per-slot: |
> > | NULL - free |
> > | PFN - cached folio |
> > | Shadow - swapped out |
> > | Bad - unusable |
> > +--------------------------+
> >
> > struct swap_cluster_info_dynamic {
> > struct swap_cluster_info ci; /* swap_table, lock, etc. */
> > unsigned int index; /* position in xarray */
> > struct rcu_head rcu; /* kfree_rcu deferred free */
> > atomic_long_t *virtual_table; /* backend info, 8 B/slot */
> > };
> >
> > Each vswap cluster (swap_cluster_info_dynamic) extends the classic
> > swap_cluster_info struct with a virtual_table array that stores the
> > backend information for each virtual swap entry in the cluster. Each
> > entry is tag-encoded in the low 3 bits to indicate backend types:
> >
> > NONE: |----- 0000 ------|000| free / unbacked
> > PHYS: |-- (type:5,off:N)|001| on a physical swapfile (shifted)
> > ZERO: |----- 0000 ------|010| zero-filled page
> > ZSWAP: |--- zswap_entry* |011| compressed in zswap
> > FOLIO: |--- folio* ------|100| in-memory folio
> >
> > We still have room for 3 more future backend types, for e.g. CRAM, i.e
> > compressed-CXL-as-swap, which is laid out in [10] and [11]. Worst
> > case scenario, we can add more fields to this extended struct.
> >
> > Other design points:
> > - Both vswap entries (Case 1) and directly-mapped physical entries
> > (Case 2) coexist as first-class citizens. All the common swap
> > code paths — swapout, swapin, swap freeing, swapoff, zswap
> > writeback, THP swapin, etc. work for both. When CONFIG_VSWAP=n,
> > the vswap branches compile out and behavior should be identical to
> > today's swap-table P4 (at least that is my intention).
> > - Pointer-tagged swap_table on physical clusters for rmap (physical
> > -> virtual) lookup.
> > - Virtual swap slots not backed by physical swap are not charged to
> > memcg swap counters — only physical backing is charged (I made the
> > case for this in [7]).
> > - Careful separation of vswap and physical swap allocation paths and
> > structures adds a lot of complexity, but is crucial to make sure
> > both paths are efficient and do not conflict with each other (for
> > correctness and performance). I do re-use a lot of the allocation
> > logic wherever possible though.
>
> Thanks for working on this! I mostly looked at the high-level design and
Thank you for initiating this effort in LSFMMBPF 2023 (god, time
flies). I was very excited by your presentation and decided to take a
stab at it :)
(I'll be sure to mention the full context in a non-RFC version - it
has a lot of gems in our technical discussions).
> the zswap parts, as the swap code has changed a lot since I was familiar
> with it :)
It has changed a lot since 6.19, when I was working on v6. Very
exciting time to be a (z)swap developer right now - we have new ideas
and new features every other week :) Reviewing code has been quite a
joy (albeit a lot of work).
>
> It seems like the direction being taken here is that we have one
> (massive) vswap swap device, and we keep normal physical swap devices
> around as well.
Yep.
>
> A vswap entry can point at a physical swap entry, or zswap, or zeromap.
> If a vswap entry points at a physical swap entry, then the physical swap
> entry points back at the vswap entry (a reverse mapping).
Yep.
>
> I assume the main reason here is to avoid the extra overhead if
> everything uses vswap, which would mainly be the reverse mapping
> overhead? I guess there's also some simplicity that comes from reusing
> the swap info infra as a whole, including the swap table.
Yeah it helps a lot that we don't have to rewrite the whole allocator
and swap entry reference counting logic again :)
>
> I don't like that the code bifurcates for vswap vs. normal swap entries
> though. Not sure if this is an issue that can be fixed with proper
> abstractions to hide it, or if the design needs modifications. I was
> honestly really hoping we don't end up with this. I was hoping that the
> physical swap device no longer uses a full swap table and all, and
> everything goes through vswap.
>
> I hoping that if redirection isn't needed (e.g. zswap is disabled),
> vswap can directly encode the physical swap slot so that the reverse
> mapping isn't needed -- so we avoid the overhead without keeping the
> physical swap device using a fully-fledged swap table.
Can you expand on "vswap can directly encode the physical swap slot"?
I'm not sure I follow here.
>
> All that being said, perhaps I am too out of touch with the code to
> realize it's simply not possible.
>
> Honestly, if the main reason we can't have a single swap table for vswap
> is saving 8 bytes on the reverse mapping, it sounds like a weak-ish
> argument, even if we can't optimize the reverse mapping away. But maybe
> I am also out of touch with RAM prices :)
In terms of the space overhead I do agree, FWIW :)
I think the other concern is the indirection overhead with going
through the xarray for every swap operation, hence the per-CPU vswap
cluster lookup caching idea:
https://lore.kernel.org/all/20260505153854.1612033-23-nphamcs@gmail.com/
>
> I at least hope that, the current design is not painting us into a
> corner (e.g. through userspace interfaces), and we can still achieve a
> vswap-for-all implementation in the future (maybe that's what you have
> in mind already?).
That's still my plan. Operationally speaking, I want to make this
completely transparent to users, with minimal to no performance
overhead.
The next action item is to optimize for vswap-on-fast-swapfile case -
that was Kairui's main concerns regarding performance. I spent a lot
of time perfing and fixing issues for this case in v6. The issues with
the most egregious effects and simplest fix (vswap-less
swap-cache-only check for e.g) are already fixed in this new design,
and eventually I will move the rest (lookup caching) and more to here.
>
> Aside from the swap code, the only sticking point for me is the logic
> bifurcation in zswap. Why does zswap need to handle vswap vs. not vswap?
> I thought the point of the design is to use vswap when zswap is used,
> and otherwise use a normal swap table. In a way, one of the goals is to
> make zswap a first class swap citizen, but it doesn't seem like we are
> achieving that?
We already have all the machinery to make zswap completely
independent. Right now, if you use vswap, you'll skip the zswap's
internal xarray entirely, and just store a zswap entry in the virtual
swap cluster's vtable.
I just haven't removed the old code for 2 reasons:
1. Reduce the delta on this RFC, to ease the burden for reviewers (and
definitely not because I'm lazy :P)
2. The only other practical reason is so that we can let users compile
with !CONFIG_VSWAP and still uses zswap on top of the old swapfile
setup during the transition/experimentation period for now.
But logically and conceptually speaking, there is no reason I can come
up with to use zswap on without vswap. The CPU indirection overhead is
already partially there (since zswap uses an xarray) and further
optimized (cluster loopup caching etc.), as well as the space overhead
(vswap replaces the zswap xarray). I actually wrote a whole paragraph
about how we should always go for vswap if we're using zswap, but then
decide to remove it since there's no code for it yet.
If folks like it, what I can do is have CONFIG_ZSWAP depends on
CONFIG_VSWAP, removes all the non-vswap logic, and call it a day? :)
Then, on the swap allocation side, if vswap allocation fail and zswap
writeback is disabled, we can error out early.
^ permalink raw reply
* Re: [PATCH v2 13/18] perf test cs-etm: Speed up basic test
From: Leo Yan @ 2026-06-03 17:17 UTC (permalink / raw)
To: James Clark
Cc: Suzuki K Poulose, Mike Leach, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Amir Ayupov, Jonathan Corbet,
Shuah Khan, Paschalis Mpeis, coresight, linux-perf-users,
linux-kernel, Arnaldo Carvalho de Melo, linux-doc
In-Reply-To: <20260602-james-cs-context-tracking-fix-v2-13-85b5ce6f55c6@linaro.org>
On Tue, Jun 02, 2026 at 03:26:55PM +0100, James Clark wrote:
> Like the name says, this should be the most basic test possible. Kernel
> recording is slow and already has coverage on the systemwide test. Perf
> report output also has coverage elsewhere. 'ls' also produces more trace
> than 'true'.
>
> We only want to test if the combination of recording options works at
> all, so fix all of these things to make it as fast as possible.
>
> Signed-off-by: James Clark <james.clark@linaro.org>
Reviewed-by: Leo Yan <leo.yan@arm.com>
^ permalink raw reply
* Re: [RFC PATCH 0/5] mm, swap: Virtual Swap Space (Swap Table Edition)
From: Nhat Pham @ 2026-06-03 17:22 UTC (permalink / raw)
To: Yosry Ahmed
Cc: kasong, Liam.Howlett, akpm, apopple, axelrasmussen, baohua,
baolin.wang, bhe, byungchul, cgroups, chengming.zhou, chrisl,
corbet, david, dev.jain, gourry, hannes, hughd, jannh,
joshua.hahnjy, lance.yang, lenb, linux-doc, linux-kernel,
linux-mm, linux-pm, lorenzo.stoakes, matthew.brost, mhocko,
muchun.song, npache, pavel, peterx, peterz, pfalcato, rafael,
rakie.kim, roman.gushchin, rppt, ryan.roberts, shakeel.butt,
shikemeng, surenb, tglx, vbabka, weixugc, ying.huang, yosry.ahmed,
yuanchu, zhengqi.arch, ziy, kernel-team, riel, haowenchao22
In-Reply-To: <CAKEwX=MWX9KkSFAoN4xEMg3b+gZUN9=yd7rirAWG5NOBf26eAg@mail.gmail.com>
On Wed, Jun 3, 2026 at 10:12 AM Nhat Pham <nphamcs@gmail.com> wrote:
>
> On Tue, Jun 2, 2026 at 6:29 PM Yosry Ahmed <yosry@kernel.org> wrote:
> >
> > > II. Design
> > >
> > > With vswap, pages are assigned virtual swap entries on a ghost device
> > > with no backing storage. These entries are backed by zswap, zero pages,
> > > or (lazily) physical swap slots. Physical backing is allocated only
> > > when needed — on zswap writeback or reclaim writeout, after the rmap
> > > step.
> > >
> > > Compared to the standalone v6 implementation [1], which introduces a
> > > 24-byte per-entry swap descriptor and its own cluster allocator, this
> > > edition uses swap_table infrastructure, and share a lot of the allocator
> > > logic. Per-slot metadata is stored in a tag-encoded virtual_table
> > > (atomic_long_t, 8 bytes per slot), and physical clusters store
> > > Pointer-tagged rmap entries in the swap_table for reverse lookup back to
> > > the virtual cluster.
> > >
> > > Here are some data layout diagrams:
> > >
> > > Case 1: vswap entry (virtualized)
> > >
> > > PTE swap_cluster_info_dynamic
> > > vswap_entry +-------------------------+
> > > (swp_entry_t) ------>| swap_cluster_info (ci) |
> > > | +--------------------+ |
> > > | | swap_table | |
> > > | | PFN / Shadow | |
> > > | | memcg_table | |
> > > | | count,flags,order | |
> > > | | lock, list | |
> > > | +--------------------+ |
> > > | |
> > > | virtual_table |
> > > | +--------------------+ |
> > > | | NONE | |
> > > | | PHYS | |
> > > | | ZERO | |
> > > | | ZSWAP(entry*) | |
> > > | | FOLIO(folio*) | |
> > > | +--------------------+ |
> > > +-------------------------+
> > > |
> > > | PHYS resolves to
> > > v
> > > PHYSICAL CLUSTER (swap_cluster_info)
> > > +--------------------------+
> > > | swap_table per-slot: |
> > > | NULL - free |
> > > | PFN - cached folio |
> > > | Shadow - swapped out |
> > > | Pointer- vswap rmap |
> > > | Bad - unusable |
> > > | |
> > > | Vswap-backing slot: |
> > > | Pointer(C|swp_entry_t) |
> > > | rmap back to vswap |
> > > +--------------------------+
> > >
> > > Case 2: direct-mapped physical entry (no vswap)
> > >
> > > PTE PHYSICAL CLUSTER (swap_cluster_info)
> > > phys_entry +--------------------------+
> > > (swp_entry_t) ------>| swap_table per-slot: |
> > > | NULL - free |
> > > | PFN - cached folio |
> > > | Shadow - swapped out |
> > > | Bad - unusable |
> > > +--------------------------+
> > >
> > > struct swap_cluster_info_dynamic {
> > > struct swap_cluster_info ci; /* swap_table, lock, etc. */
> > > unsigned int index; /* position in xarray */
> > > struct rcu_head rcu; /* kfree_rcu deferred free */
> > > atomic_long_t *virtual_table; /* backend info, 8 B/slot */
> > > };
> > >
> > > Each vswap cluster (swap_cluster_info_dynamic) extends the classic
> > > swap_cluster_info struct with a virtual_table array that stores the
> > > backend information for each virtual swap entry in the cluster. Each
> > > entry is tag-encoded in the low 3 bits to indicate backend types:
> > >
> > > NONE: |----- 0000 ------|000| free / unbacked
> > > PHYS: |-- (type:5,off:N)|001| on a physical swapfile (shifted)
> > > ZERO: |----- 0000 ------|010| zero-filled page
> > > ZSWAP: |--- zswap_entry* |011| compressed in zswap
> > > FOLIO: |--- folio* ------|100| in-memory folio
> > >
> > > We still have room for 3 more future backend types, for e.g. CRAM, i.e
> > > compressed-CXL-as-swap, which is laid out in [10] and [11]. Worst
> > > case scenario, we can add more fields to this extended struct.
> > >
> > > Other design points:
> > > - Both vswap entries (Case 1) and directly-mapped physical entries
> > > (Case 2) coexist as first-class citizens. All the common swap
> > > code paths — swapout, swapin, swap freeing, swapoff, zswap
> > > writeback, THP swapin, etc. work for both. When CONFIG_VSWAP=n,
> > > the vswap branches compile out and behavior should be identical to
> > > today's swap-table P4 (at least that is my intention).
> > > - Pointer-tagged swap_table on physical clusters for rmap (physical
> > > -> virtual) lookup.
> > > - Virtual swap slots not backed by physical swap are not charged to
> > > memcg swap counters — only physical backing is charged (I made the
> > > case for this in [7]).
> > > - Careful separation of vswap and physical swap allocation paths and
> > > structures adds a lot of complexity, but is crucial to make sure
> > > both paths are efficient and do not conflict with each other (for
> > > correctness and performance). I do re-use a lot of the allocation
> > > logic wherever possible though.
> >
> > Thanks for working on this! I mostly looked at the high-level design and
>
> Thank you for initiating this effort in LSFMMBPF 2023 (god, time
> flies). I was very excited by your presentation and decided to take a
> stab at it :)
>
> (I'll be sure to mention the full context in a non-RFC version - it
> has a lot of gems in our technical discussions).
>
> > the zswap parts, as the swap code has changed a lot since I was familiar
> > with it :)
>
> It has changed a lot since 6.19, when I was working on v6. Very
> exciting time to be a (z)swap developer right now - we have new ideas
> and new features every other week :) Reviewing code has been quite a
> joy (albeit a lot of work).
>
> >
> > It seems like the direction being taken here is that we have one
> > (massive) vswap swap device, and we keep normal physical swap devices
> > around as well.
>
> Yep.
>
> >
> > A vswap entry can point at a physical swap entry, or zswap, or zeromap.
> > If a vswap entry points at a physical swap entry, then the physical swap
> > entry points back at the vswap entry (a reverse mapping).
>
> Yep.
>
> >
> > I assume the main reason here is to avoid the extra overhead if
> > everything uses vswap, which would mainly be the reverse mapping
> > overhead? I guess there's also some simplicity that comes from reusing
> > the swap info infra as a whole, including the swap table.
>
> Yeah it helps a lot that we don't have to rewrite the whole allocator
> and swap entry reference counting logic again :)
>
> >
> > I don't like that the code bifurcates for vswap vs. normal swap entries
> > though. Not sure if this is an issue that can be fixed with proper
> > abstractions to hide it, or if the design needs modifications. I was
> > honestly really hoping we don't end up with this. I was hoping that the
> > physical swap device no longer uses a full swap table and all, and
> > everything goes through vswap.
> >
> > I hoping that if redirection isn't needed (e.g. zswap is disabled),
> > vswap can directly encode the physical swap slot so that the reverse
> > mapping isn't needed -- so we avoid the overhead without keeping the
> > physical swap device using a fully-fledged swap table.
>
> Can you expand on "vswap can directly encode the physical swap slot"?
> I'm not sure I follow here.
>
> >
> > All that being said, perhaps I am too out of touch with the code to
> > realize it's simply not possible.
> >
> > Honestly, if the main reason we can't have a single swap table for vswap
> > is saving 8 bytes on the reverse mapping, it sounds like a weak-ish
> > argument, even if we can't optimize the reverse mapping away. But maybe
> > I am also out of touch with RAM prices :)
>
> In terms of the space overhead I do agree, FWIW :)
>
> I think the other concern is the indirection overhead with going
> through the xarray for every swap operation, hence the per-CPU vswap
> cluster lookup caching idea:
>
> https://lore.kernel.org/all/20260505153854.1612033-23-nphamcs@gmail.com/
>
> >
> > I at least hope that, the current design is not painting us into a
> > corner (e.g. through userspace interfaces), and we can still achieve a
> > vswap-for-all implementation in the future (maybe that's what you have
> > in mind already?).
>
> That's still my plan. Operationally speaking, I want to make this
> completely transparent to users, with minimal to no performance
> overhead.
I do want to add that, even without achieving this, the current design
already enables a lot of use cases. I think it is a good compromise to
maintain both virtual and directly mapped physical swap entries for
now, and revisit the conversation of whether we can afford a mandatory
vswap layer once all the optimizations have been done :)
We should strive to simplify the codebase, and it will naturally
happen when the original overhead concern is no longer there. A
swap-related example: a few years ago, everyone thought swap slot
cache was needed. But then, Kairui optimized the swap allocator's lock
contention issue away, and that swap slot cache is suddenly redundant.
That finally allowed us to get rid of it. Similar thing happened (or
is happening?) with the SWP_SYNCHRONOUS_IO swapcache-skipping
heuristics.
^ permalink raw reply
* Re: [PATCH v2 14/18] perf test cs-etm: Remove unused Coresight workloads
From: Leo Yan @ 2026-06-03 17:25 UTC (permalink / raw)
To: James Clark
Cc: Suzuki K Poulose, Mike Leach, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Amir Ayupov, Jonathan Corbet,
Shuah Khan, Paschalis Mpeis, coresight, linux-perf-users,
linux-kernel, Arnaldo Carvalho de Melo, linux-doc
In-Reply-To: <20260602-james-cs-context-tracking-fix-v2-14-85b5ce6f55c6@linaro.org>
On Tue, Jun 02, 2026 at 03:26:56PM +0100, James Clark wrote:
[...]
> -When you run perf test, it will do a lot of self tests. Some of those
> -tests will cover CoreSight (only if enabled and on ARM64). You
> -generally would run perf test from the tools/perf directory in the
> -kernel tree. Some tests will check some internal perf support like:
> -
> - Check Arm CoreSight trace data recording and synthesized samples
> - Check Arm SPE trace data recording and synthesized samples
Should we keep the chunk for but suggest a perf command as
perf test "CoreSight" ?
> diff --git a/MAINTAINERS b/MAINTAINERS
> index b539be153f6a..7efb893edcbb 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2751,7 +2751,6 @@ F: tools/perf/arch/arm/util/cs-etm.h
> F: tools/perf/arch/arm/util/pmu.c
> F: tools/perf/tests/shell/*coresight*
> F: tools/perf/tests/shell/coresight/*
> -F: tools/perf/tests/shell/lib/*coresight*
Need to delete tools/perf/tests/shell/coresight/* ?
Thanks,
Leo
^ permalink raw reply
* Re: [PATCH v2 15/18] perf test cs-etm: Make disassembly test use kcore
From: Leo Yan @ 2026-06-03 17:32 UTC (permalink / raw)
To: James Clark
Cc: Suzuki K Poulose, Mike Leach, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Amir Ayupov, Jonathan Corbet,
Shuah Khan, Paschalis Mpeis, coresight, linux-perf-users,
linux-kernel, Arnaldo Carvalho de Melo, linux-doc
In-Reply-To: <20260602-james-cs-context-tracking-fix-v2-15-85b5ce6f55c6@linaro.org>
On Tue, Jun 02, 2026 at 03:26:57PM +0100, James Clark wrote:
> Hits in modules return empty disassembly with vmlinux as an input to
> objdump. Make the disassembly test more reliable by always using kcore.
> And update the comments to say that this is supported by the script.
Even without kcore, .ko files should have been saved into buildid
folder. Before switch to kcore, can we firstly fix the issue that why
.ko does not work?
Thanks,
Leo
^ permalink raw reply
* Re: [PATCH v2 09/18] perf test: Add named_threads workload
From: Leo Yan @ 2026-06-03 17:36 UTC (permalink / raw)
To: James Clark
Cc: Suzuki K Poulose, Mike Leach, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Amir Ayupov, Jonathan Corbet,
Shuah Khan, Paschalis Mpeis, coresight, linux-perf-users,
linux-kernel, Arnaldo Carvalho de Melo, linux-doc
In-Reply-To: <5e29907a-2533-4540-833f-977c5f0fb90b@linaro.org>
On Wed, Jun 03, 2026 at 05:12:59PM +0100, James Clark wrote:
[...]
> > Just curious this can be simplified to a thread function, like:
> >
> > noinline void *named_thread(void *arg)
> > {
> > char name[16];
> >
> > snprintf(name, sizeof(name), "thread%d", int(arg));
> >
> > pthread_setname_np(pthread_self(), name);
> > ...
> > return NULL;
> > }
>
> Only if you don't want to check for symbols as well. I thought if we were
> going to spawn a load of threads and look for thread names we might as well
> check that the symbols match at the same time.
>
> If all of the threads run the same function you can't do that.
Okay, if so please ignore my comment.
Thanks,
Leo
^ permalink raw reply
* [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries
From: Yunseong Kim @ 2026-06-03 17:43 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Dmitry Vyukov,
Andrey Konovalov, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, Nicolas Schier,
Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Jonathan Corbet, Shuah Khan
Cc: Yunseong Kim, linux-kernel, kasan-dev, llvm, linux-kbuild,
rust-for-linux, workflows, linux-doc, Yunseong Kim
Introduces a new KCOV exetened feature that captures function arguments and
return values at kernel function boundaries, enabling per-process visibility
into runtime dataflow.
Motivation
==========
Even for highly experienced developers, it is not straightforward to
determine, at any given moment, which specific kernel paths a user
process is executing or how function arguments and return values evolve
during execution. This lack of visibility makes debugging and security
auditing significantly more challenging.
Limitations of existing tools in per-task dataflow extraction:
- ftrace/kprobes provide dynamic tracing at specific probe points
- eBPF enables programmable in-kernel analysis but requires manual
specification of struct layouts rather than automatic extraction
from compiler debug metadata
- perf provides statistical sampling of hardware/software events,
inherently lossy and designed for performance profiling rather
than deterministic data-flow capture
This is NOT a performance tool. The purpose is auditing and contract
verification — confirming that kernel functions receive and return
expected values at runtime.
Real-World Result: Android Binder Vulnerabilities
=================================================
Using kcov-dataflow with CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL, I
audited the Android binder driver (both C and Rust implementations)
and discovered two exploitable logic bugs:
Bug 1: BINDER_SET_MAX_THREADS accepts 0xFFFFFFFF without validation.
kcov-dataflow showed: set_max_threads(max=0xffffffff) → return 0
Impact: bypasses RLIMIT_NPROC, OOM from unprivileged userspace.
Bug 2: BC_ENTER_LOOPER accepted twice without error.
kcov-dataflow showed: looper_enter() called with ENTERED already set,
no rejection, return=0 on both calls.
Impact: thread pool state corruption.
These bugs are invisible to:
- KASAN: no memory corruption occurs
- Edge coverage: same code paths for valid/invalid values
- ftrace: shows "function called" but not argument values
Only by capturing the actual runtime values at function boundaries
could I detect that 0xFFFFFFFF passes through without rejection, or
that the same state-mutating command succeeds twice.
The fixes are submitted separately:
[PATCH 1/4] binder: cap BINDER_SET_MAX_THREADS at RLIMIT_NPROC
[PATCH 2/4] binder: reject duplicate BC_ENTER_LOOPER commands
[PATCH 3/4] rust_binder: cap set_max_threads at RLIMIT_NPROC
[PATCH 4/4] rust_binder: reject duplicate BC_ENTER_LOOPER in looper_enter
Approach
========
Rather than tracing individual probe points, this patch set enables
continuous per-task extraction of data flow across all instrumented
function boundaries — capturing how argument values enter and return
values exit each function as execution progresses through a subsystem.
The key insight is that function boundaries are natural observation
points: arguments at entry reveal what data enters a subsystem, and
return values reveal what comes out.
The compiler (clang with a SanitizerCoverage extension) inserts
callbacks at function entry/exit that record argument values into a
per-task mmap'd buffer. The kernel backend reads struct fields safely
via copy_from_kernel_nofault(). When not enabled for a task, the
overhead is a single boolean check per instrumented function.
Design
======
- Completely independent from legacy /sys/kernel/debug/kcov
- Separate device: /sys/kernel/debug/kcov_dataflow
- Separate ioctl namespace ('d'), separate per-task buffer
- Per-module opt-in: KCOV_DATAFLOW_file.o := y
- Optional global enablement: CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL
- Supports both C and Rust kernel modules
- Safe in process context; rejects interrupt/NMI via in_task() guard
- Recursion guard via sequence counter bit 31
- Requires clang with -fsanitize-coverage=dataflow-args,dataflow-ret
(Kconfig uses cc-option to verify compiler support)
Performance Note
================
This feature is designed for auditing and security analysis, NOT for
production use or performance measurement. It should not be compared
to runtime tracing tools optimized for low overhead.
Per-module instrumentation (recording active):
~27ns per callback (dominated by LOCK XADD + copy_from_kernel_nofault)
Global instrumentation (INSTRUMENT_ALL, recording disabled):
.text: +9.5%, .data: +44%, boot: +71%, syscall latency: +133%
CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL instruments every function in the
kernel. This incurs significant overhead comparable to KMSAN and is
intended exclusively for:
- Fuzzer-driven whole-kernel auditing (syzkaller integration)
- Full-subsystem contract verification (as demonstrated with binder)
- Capturing complete call-flow data for post-mortem analysis
For targeted auditing, use per-module opt-in (KCOV_DATAFLOW_file.o := y)
which limits overhead to the specific subsystem under investigation.
Patches
=======
1/6: Core kernel implementation (kernel/kcov.c, sched.h, Kconfig)
2/6: Build system support (Makefile.kcov, Makefile.lib)
3/6: CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL and NO_INLINE
4/6: Userspace tools and test modules
5/6: Harden kcov_df_write() against interrupt reentry
6/6: Recursion guard and documentation
Prerequisites / Toolchain
=========================
This kernel patch relies on a custom LLVM SanitizerCoverage pass that
emits __sanitizer_cov_trace_args() and __sanitizer_cov_trace_ret()
callbacks at function boundaries, extracting struct field layouts from
DWARF debug metadata at compile time.
To build and test this patchset, compile the kernel using the modified
toolchain:
1. LLVM/Clang (adds -fsanitize-coverage=dataflow-args,dataflow-ret):
https://github.com/llvm/llvm-project/pull/201410
2. Rust (rustc 1.98 built against the above LLVM 23, for Rust module support):
https://github.com/yskzalloc/rust
Build instructions:
# Build the modified clang
cd llvm-project && cmake -G Ninja -S llvm -B build \
-DLLVM_ENABLE_PROJECTS="clang;lld" -DCMAKE_BUILD_TYPE=Release
ninja -C build clang
# Build the kernel with dataflow support
export PATH=$HOME/llvm-project/build/bin:$PATH
export RUSTC=$HOME/rust/build/x86_64-unknown-linux-gnu/stage1/bin/rustc
export RUST_LIB_SRC=$HOME/rust/library
make LLVM=1 defconfig
scripts/config --enable KCOV \
--enable KCOV_DATAFLOW_ARGS \
--enable KCOV_DATAFLOW_RET
make LLVM=1 olddefconfig
make LLVM=1 -j$(nproc)
Note: CONFIG_KCOV_DATAFLOW_ARGS and CONFIG_KCOV_DATAFLOW_RET depend on
CONFIG_KCOV and use $(cc-option) to verify the compiler supports the
new flags. With standard (unpatched) clang, these options will not
appear in menuconfig and silently remain disabled.
Optional configs:
--enable KCOV_DATAFLOW_INSTRUMENT_ALL (instrument entire kernel)
--enable KCOV_DATAFLOW_NO_INLINE (enabled by default)
--set-val FRAME_WARN 4096 (needed for INSTRUMENT_ALL)
--disable KASAN (conflicts with INSTRUMENT_ALL)
Testing
=======
Tested on linux-next 7.1.0-rc5 with custom clang/LLVM 23 and
rustc 1.98-nightly (built against the same LLVM). Verified under
virtme-ng (QEMU/KVM, 1GB RAM):
- Per-module C (eight_args_mod): all 8 functions captured
- Per-module C (deep_chain_mod): 10-deep call chain captured
- Per-module Rust (eight_args_rust): all 8 rfunc functions captured
- Interrupt safety: in_task() + recursion guard prevents corruption
- Binder auditing: discovered 2 exploitable bugs (patches separate)
- Standard clang (without patch): Kconfig options correctly hidden
- Global enablement (INSTRUMENT_ALL): kernel boots, 104K records
captured during single copy.fail exploit reproduction
https://github.com/yskzalloc/kcov-dataflow/blob/main/copy.fail/origin/converted.txt
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
Changes in v2:
- EDITME: describe what is new in this series revision.
- EDITME: use bulletpoints and terse descriptions.
- Link to v1: https://patch.msgid.link/20260603-kcov-dataflow-next-20260603-v1-0-e64300bf17cf@est.tech
To: Ingo Molnar <mingo@redhat.com>
To: Peter Zijlstra <peterz@infradead.org>
To: Juri Lelli <juri.lelli@redhat.com>
To: Vincent Guittot <vincent.guittot@linaro.org>
To: Dietmar Eggemann <dietmar.eggemann@arm.com>
To: Steven Rostedt <rostedt@goodmis.org>
To: Ben Segall <bsegall@google.com>
To: Mel Gorman <mgorman@suse.de>
To: Valentin Schneider <vschneid@redhat.com>
To: K Prateek Nayak <kprateek.nayak@amd.com>
To: Dmitry Vyukov <dvyukov@google.com>
To: Andrey Konovalov <andreyknvl@gmail.com>
To: Andrew Morton <akpm@linux-foundation.org>
To: Nathan Chancellor <nathan@kernel.org>
To: Nick Desaulniers <nick.desaulniers+lkml@gmail.com>
To: Bill Wendling <morbo@google.com>
To: Justin Stitt <justinstitt@google.com>
To: Nicolas Schier <nsc@kernel.org>
To: Miguel Ojeda <ojeda@kernel.org>
To: Boqun Feng <boqun@kernel.org>
To: Gary Guo <gary@garyguo.net>
To: Björn Roy Baron <bjorn3_gh@protonmail.com>
To: Benno Lossin <lossin@kernel.org>
To: Andreas Hindborg <a.hindborg@kernel.org>
To: Alice Ryhl <aliceryhl@google.com>
To: Trevor Gross <tmgross@umich.edu>
To: Danilo Krummrich <dakr@kernel.org>
To: Jonathan Corbet <corbet@lwn.net>
To: Shuah Khan <skhan@linuxfoundation.org>
Cc: linux-kernel@vger.kernel.org
Cc: kasan-dev@googlegroups.com
Cc: llvm@lists.linux.dev
Cc: linux-kbuild@vger.kernel.org
Cc: rust-for-linux@vger.kernel.org
Cc: workflows@vger.kernel.org
Cc: linux-doc@vger.kernel.org
---
Yunseong Kim (6):
kcov: add per-task dataflow tracking for function arguments/return values
kcov: add build system support for dataflow instrumentation
kcov: add CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL and NO_INLINE
tools/kcov-dataflow: add userspace consumer and test modules
kcov: add interrupt context guard to kcov_df_write()
kcov: add recursion guard and documentation for kcov-dataflow
Documentation/dev-tools/kcov-dataflow.rst | 282 +++++++++++++++++++
include/linux/sched.h | 8 +
kernel/Makefile | 3 +
kernel/kcov.c | 307 +++++++++++++++++++++
lib/Kconfig.debug | 43 +++
rust/Makefile | 1 +
scripts/Makefile.kcov | 6 +
scripts/Makefile.lib | 7 +
tools/kcov-dataflow/.gitignore | 12 +
tools/kcov-dataflow/deep_module/Makefile | 2 +
tools/kcov-dataflow/deep_module/deep_chain_mod.c | 224 +++++++++++++++
tools/kcov-dataflow/eight_args_c/Makefile | 3 +
tools/kcov-dataflow/eight_args_c/eight_args_mod.c | 95 +++++++
tools/kcov-dataflow/eight_args_rust/Makefile | 2 +
.../eight_args_rust/eight_args_rust.rs | 114 ++++++++
tools/kcov-dataflow/kcov-view.py | 272 ++++++++++++++++++
tools/kcov-dataflow/trigger.c | 125 +++++++++
17 files changed, 1506 insertions(+)
---
base-commit: f7af91adc230aa99e23330ecf85bc9badd9780ad
change-id: 20260603-kcov-dataflow-next-20260603-8bf628f98086
Best regards,
--
Yunseong Kim <yunseong.kim@est.tech>
^ permalink raw reply
* [RFC PATCH v2 1/6] kcov: add per-task dataflow tracking for function arguments/return values
From: Yunseong Kim @ 2026-06-03 17:43 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Dmitry Vyukov,
Andrey Konovalov, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, Nicolas Schier,
Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Jonathan Corbet, Shuah Khan
Cc: Yunseong Kim, linux-kernel, kasan-dev, llvm, linux-kbuild,
rust-for-linux, workflows, linux-doc, Yunseong Kim
In-Reply-To: <20260603-kcov-dataflow-next-20260603-v2-0-fee0939de2c4@est.tech>
Add a new KCOV subsystem that captures function arguments at entry and
return values at exit, with automatic struct field expansion using
compiler-generated DebugInfo metadata.
Key components:
- CONFIG_KCOV_DATAFLOW_ARGS: enables argument capture
- CONFIG_KCOV_DATAFLOW_RET: enables return value capture
- /sys/kernel/debug/kcov_dataflow: separate device from legacy kcov
- Ioctl namespace 'd' (KCOV_DF_INIT_TRACE, KCOV_DF_ENABLE, KCOV_DF_DISABLE)
- Per-task buffer: task->kcov_df_area with atomic xadd reservation
- Fault-tolerant: all reads via copy_from_kernel_nofault()
- Recursion-safe: notrace __no_sanitize_coverage noinline
- ERR_PTR aware: skips struct expansion for error pointers
The callbacks (__sanitizer_cov_trace_args/ret) are inserted by the
compiler when -fsanitize-coverage=dataflow-args,dataflow-ret is used.
The Kconfig options depend on cc-option to verify compiler support.
Buffer format (TLV records, all u64):
area[0]: atomic word count
[pos+0]: type_and_seq (0xE=entry, 0xF=return in upper 4 bits)
[pos+1]: PC
[pos+2]: meta (arg_idx | arg_size | ptr)
[pos+3..N]: field values read via copy_from_kernel_nofault()
This is completely independent from legacy /sys/kernel/debug/kcov.
Existing users (syzkaller, oss-fuzz) are unaffected.
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
include/linux/sched.h | 8 ++
kernel/kcov.c | 291 ++++++++++++++++++++++++++++++++++++++++++++++++++
lib/Kconfig.debug | 22 ++++
3 files changed, 321 insertions(+)
diff --git a/include/linux/sched.h b/include/linux/sched.h
index c4433c185ad8..03be4b495f70 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1533,6 +1533,14 @@ struct task_struct {
/* KCOV sequence number: */
int kcov_sequence;
+ /* KCOV dataflow per-task sequence counter for TLV records: */
+ u32 kcov_dataflow_seq;
+
+ /* KCOV dataflow: separate buffer for trace-args/trace-ret */
+ unsigned int kcov_df_size;
+ void *kcov_df_area;
+ bool kcov_df_enabled;
+
/* Collect coverage from softirq context: */
unsigned int kcov_softirq;
#endif
diff --git a/kernel/kcov.c b/kernel/kcov.c
index 1df373fb562b..d3c9c0efe961 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -353,6 +353,288 @@ void notrace __sanitizer_cov_trace_switch(kcov_u64 val, void *arg)
EXPORT_SYMBOL(__sanitizer_cov_trace_switch);
#endif /* ifdef CONFIG_KCOV_ENABLE_COMPARISONS */
+#if defined(CONFIG_KCOV_DATAFLOW_ARGS) || defined(CONFIG_KCOV_DATAFLOW_RET)
+/*
+ * KCOV Dataflow: /sys/kernel/debug/kcov_dataflow
+ *
+ * Completely separate from legacy /sys/kernel/debug/kcov.
+ * Own buffer, own ioctl, own mmap. No printk — buffer only.
+ *
+ * TLV record layout (all u64):
+ * area[0]: total u64 words written (atomic counter)
+ * [pos+0]: type_and_seq (0xE=entry|0xF=return in upper 4 bits, seq in lower 24)
+ * [pos+1]: PC
+ * [pos+2]: raw pointer | (arg_idx << 56) | (arg_size << 48) for entry
+ * [pos+3..N]: field values (or scalar value if num_fields=0)
+ */
+#define KCOV_DF_TYPE_ENTRY 0xE0000000ULL
+#define KCOV_DF_TYPE_RET 0xF0000000ULL
+#define KCOV_DF_MAGIC_BAD 0xBADADD85ULL
+#define KCOV_DF_IS_ERR(p) ((unsigned long)(p) >= (unsigned long)-4095UL)
+
+/* Ioctl commands for /sys/kernel/debug/kcov_dataflow */
+#define KCOV_DF_INIT_TRACE _IOR('d', 1, unsigned long)
+#define KCOV_DF_ENABLE _IO('d', 100)
+#define KCOV_DF_DISABLE _IO('d', 101)
+
+struct kcov_dataflow {
+ refcount_t refcount;
+ spinlock_t lock;
+ unsigned int size; /* in u64 words */
+ void *area;
+ struct task_struct *t;
+};
+
+static void kcov_df_put(struct kcov_dataflow *df)
+{
+ if (refcount_dec_and_test(&df->refcount)) {
+ vfree(df->area);
+ kfree(df);
+ }
+}
+
+/*
+ * Core write function — no printk, no locks, just atomic buffer write.
+ * Called from __sanitizer_cov_trace_args/ret in instrumented code.
+ */
+static noinline notrace __no_sanitize_coverage void
+kcov_df_write(u64 type_marker, u64 pc, u64 meta, void *ptr,
+ u64 *offsets, u32 num_fields)
+{
+ struct task_struct *t = current;
+ u64 *area;
+ unsigned long pos, max_pos;
+ u32 record_len, seq, i;
+
+ if (!t->kcov_df_enabled)
+ return;
+
+ area = (u64 *)t->kcov_df_area;
+ if (!area)
+ return;
+
+ max_pos = t->kcov_df_size;
+
+ /* Record: header(1) + pc(1) + meta(1) + fields or scalar(max 1) */
+ record_len = 3 + (num_fields > 0 ? num_fields : 1);
+
+ /* Atomic reservation */
+ pos = 1 + xadd((unsigned long *)&area[0], record_len);
+ if (unlikely(pos + record_len > max_pos)) {
+ xadd((unsigned long *)&area[0], -(long)record_len);
+ return;
+ }
+
+ seq = ++t->kcov_dataflow_seq;
+ area[pos] = type_marker | (seq & 0x00FFFFFFULL);
+ area[pos + 1] = pc;
+ area[pos + 2] = meta;
+
+ if (num_fields == 0) {
+ /* Scalar: read value from ptr using size from meta */
+ u64 val = 0;
+ u32 sz = (meta >> 48) & 0xFF;
+
+ if (sz > sizeof(val))
+ sz = sizeof(val);
+ if (ptr && !KCOV_DF_IS_ERR(ptr))
+ copy_from_kernel_nofault(&val, ptr, sz);
+ area[pos + 3] = val;
+ } else {
+ /* Struct fields */
+ if (KCOV_DF_IS_ERR(ptr)) {
+ for (i = 0; i < num_fields; i++)
+ area[pos + 3 + i] = KCOV_DF_MAGIC_BAD;
+ return;
+ }
+ for (i = 0; i < num_fields; i++) {
+ u64 off, sz, val = KCOV_DF_MAGIC_BAD;
+ void *fa;
+
+ if (copy_from_kernel_nofault(&off, &offsets[i * 2], sizeof(off)) ||
+ copy_from_kernel_nofault(&sz, &offsets[i * 2 + 1], sizeof(sz))) {
+ area[pos + 3 + i] = KCOV_DF_MAGIC_BAD;
+ continue;
+ }
+ fa = (void *)((unsigned long)ptr + off);
+ val = 0;
+ if (sz <= sizeof(val))
+ copy_from_kernel_nofault(&val, fa, sz);
+ else
+ copy_from_kernel_nofault(&val, fa, sizeof(val));
+ area[pos + 3 + i] = val;
+ }
+ }
+}
+
+#ifdef CONFIG_KCOV_DATAFLOW_ARGS
+noinline void notrace __no_sanitize_coverage
+__sanitizer_cov_trace_args(u64 pc, u32 arg_idx, u32 arg_size, void *arg_ptr,
+ u64 *offsets, u32 num_fields);
+
+noinline void notrace __no_sanitize_coverage
+__sanitizer_cov_trace_args(u64 pc, u32 arg_idx, u32 arg_size, void *arg_ptr,
+ u64 *offsets, u32 num_fields)
+{
+ /* meta: [arg_idx(8) | arg_size(8) | ptr(48)] */
+ u64 meta = ((u64)arg_idx << 56) | ((u64)arg_size << 48) |
+ ((u64)(unsigned long)arg_ptr & 0xFFFFFFFFFFFFULL);
+ kcov_df_write(KCOV_DF_TYPE_ENTRY, pc, meta, arg_ptr,
+ offsets, num_fields);
+}
+EXPORT_SYMBOL(__sanitizer_cov_trace_args);
+#endif
+
+#ifdef CONFIG_KCOV_DATAFLOW_RET
+noinline void notrace __no_sanitize_coverage
+__sanitizer_cov_trace_ret(u64 pc, u32 ret_size, void *ret_val,
+ u64 *offsets, u32 num_fields);
+
+noinline void notrace __no_sanitize_coverage
+__sanitizer_cov_trace_ret(u64 pc, u32 ret_size, void *ret_val,
+ u64 *offsets, u32 num_fields)
+{
+ u64 meta = ((u64)ret_size << 48) |
+ ((u64)(unsigned long)ret_val & 0xFFFFFFFFFFFFULL);
+ kcov_df_write(KCOV_DF_TYPE_RET, pc, meta, ret_val,
+ offsets, num_fields);
+}
+EXPORT_SYMBOL(__sanitizer_cov_trace_ret);
+#endif
+
+/* --- /sys/kernel/debug/kcov_dataflow file operations --- */
+
+static int kcov_df_open(struct inode *inode, struct file *filep)
+{
+ struct kcov_dataflow *df;
+
+ df = kzalloc(sizeof(*df), GFP_KERNEL);
+ if (!df)
+ return -ENOMEM;
+ spin_lock_init(&df->lock);
+ refcount_set(&df->refcount, 1);
+ filep->private_data = df;
+ return nonseekable_open(inode, filep);
+}
+
+static int kcov_df_close(struct inode *inode, struct file *filep)
+{
+ struct kcov_dataflow *df = filep->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&df->lock, flags);
+ if (df->t == current) {
+ current->kcov_df_enabled = false;
+ current->kcov_df_area = NULL;
+ current->kcov_df_size = 0;
+ df->t = NULL;
+ }
+ spin_unlock_irqrestore(&df->lock, flags);
+ kcov_df_put(df);
+ return 0;
+}
+
+static int kcov_df_mmap(struct file *filep, struct vm_area_struct *vma)
+{
+ struct kcov_dataflow *df = filep->private_data;
+ unsigned long size, off;
+ struct page *page;
+ unsigned long flags;
+ void *area;
+ int res = 0;
+
+ spin_lock_irqsave(&df->lock, flags);
+ size = df->size * sizeof(u64);
+ if (!df->area || vma->vm_pgoff != 0 ||
+ vma->vm_end - vma->vm_start != size) {
+ res = -EINVAL;
+ goto out;
+ }
+ area = df->area;
+ spin_unlock_irqrestore(&df->lock, flags);
+
+ vm_flags_set(vma, VM_DONTEXPAND);
+ for (off = 0; off < size; off += PAGE_SIZE) {
+ page = vmalloc_to_page(area + off);
+ res = vm_insert_page(vma, vma->vm_start + off, page);
+ if (res)
+ return res;
+ }
+ return 0;
+out:
+ spin_unlock_irqrestore(&df->lock, flags);
+ return res;
+}
+
+static long kcov_df_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
+{
+ struct kcov_dataflow *df = filep->private_data;
+ unsigned long flags;
+ unsigned long size;
+ int res = 0;
+
+ spin_lock_irqsave(&df->lock, flags);
+ switch (cmd) {
+ case KCOV_DF_INIT_TRACE:
+ if (df->area) {
+ res = -EBUSY;
+ break;
+ }
+ size = arg;
+ if (size < 2 || size > (128 << 20) / sizeof(u64)) {
+ res = -EINVAL;
+ break;
+ }
+ spin_unlock_irqrestore(&df->lock, flags);
+ df->area = vmalloc_user(size * sizeof(u64));
+ if (!df->area)
+ return -ENOMEM;
+ spin_lock_irqsave(&df->lock, flags);
+ df->size = size;
+ break;
+
+ case KCOV_DF_ENABLE:
+ if (!df->area || df->t) {
+ res = -EINVAL;
+ break;
+ }
+ df->t = current;
+ current->kcov_df_area = df->area;
+ current->kcov_df_size = df->size;
+ current->kcov_dataflow_seq = 0;
+ /* Barrier before enabling */
+ barrier();
+ current->kcov_df_enabled = true;
+ break;
+
+ case KCOV_DF_DISABLE:
+ if (df->t != current) {
+ res = -EINVAL;
+ break;
+ }
+ current->kcov_df_enabled = false;
+ barrier();
+ current->kcov_df_area = NULL;
+ current->kcov_df_size = 0;
+ df->t = NULL;
+ break;
+
+ default:
+ res = -ENOTTY;
+ }
+ spin_unlock_irqrestore(&df->lock, flags);
+ return res;
+}
+
+static const struct file_operations kcov_df_fops = {
+ .open = kcov_df_open,
+ .unlocked_ioctl = kcov_df_ioctl,
+ .compat_ioctl = kcov_df_ioctl,
+ .mmap = kcov_df_mmap,
+ .release = kcov_df_close,
+};
+#endif /* CONFIG_KCOV_DATAFLOW_ARGS || CONFIG_KCOV_DATAFLOW_RET */
+
static void kcov_start(struct task_struct *t, struct kcov *kcov,
unsigned int size, void *area, enum kcov_mode mode,
int sequence)
@@ -1146,6 +1428,15 @@ static int __init kcov_init(void)
*/
debugfs_create_file_unsafe("kcov", 0600, NULL, NULL, &kcov_fops);
+#if defined(CONFIG_KCOV_DATAFLOW_ARGS) || defined(CONFIG_KCOV_DATAFLOW_RET)
+ /*
+ * Toggle verbose printk: echo 1 > /sys/kernel/debug/kcov_dataflow_verbose
+ * Default off — zero overhead when not debugging.
+ */
+ debugfs_create_file_unsafe("kcov_dataflow", 0600, NULL, NULL,
+ &kcov_df_fops);
+#endif
+
#ifdef CONFIG_KCOV_SELFTEST
selftest();
#endif
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index e2f976c3301b..abd1a94589aa 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2261,6 +2261,28 @@ config KCOV_SELFTEST
On test failure, causes the kernel to panic. Recommended to be
enabled, ensuring critical functionality works as intended.
+
+config KCOV_DATAFLOW_ARGS
+ bool "Enable KCOV dataflow: function argument capture"
+ depends on KCOV
+ depends on $(cc-option,-fsanitize-coverage=dataflow-args)
+ help
+ Captures function arguments at entry via /sys/kernel/debug/kcov_dataflow.
+ Struct pointer arguments are auto-expanded using compiler DebugInfo
+ metadata, recording individual field values at runtime.
+ Enable per-module with: KCOV_DATAFLOW_file.o := y in the Makefile.
+ Requires clang with -fsanitize-coverage=dataflow-args support.
+
+config KCOV_DATAFLOW_RET
+ bool "Enable KCOV dataflow: return value capture"
+ depends on KCOV
+ depends on $(cc-option,-fsanitize-coverage=dataflow-ret)
+ help
+ Captures function return values via /sys/kernel/debug/kcov_dataflow.
+ Struct pointer returns are auto-expanded using compiler DebugInfo
+ metadata, recording individual field values at runtime.
+ Enable per-module with: KCOV_DATAFLOW_file.o := y in the Makefile.
+ Requires clang with -fsanitize-coverage=dataflow-ret support.
config DEBUG_AID_FOR_SYZBOT
bool "Additional debug code for syzbot"
default n
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox