* [PATCH v5 09/13] liveupdate: Remove limit on the number of sessions
From: Pasha Tatashin @ 2026-06-02 3:17 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: <20260602031717.197696-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 | 24 +------
kernel/liveupdate/luo_session.c | 115 +++++++++++++++-----------------
2 files changed, 58 insertions(+), 81 deletions(-)
diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 9a4fe491812b..79758d92ed5f 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,15 @@
#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_COMPAT_BASE "luo-v3"
+#define LUO_ABI_COMPATIBLE LUO_COMPAT_BASE "-" KHO_BLOCK_ABI_COMPATIBLE
#define LUO_ABI_COMPAT_LEN ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
/**
@@ -118,21 +115,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 43342916d314..f6eeb965b3c1 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_grow(&sh->block_set, sh->count);
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_shrink(&sh->block_set, sh->count);
}
static int luo_session_finish_one(struct luo_session *session)
@@ -553,15 +532,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;
}
@@ -603,6 +584,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_it it;
static int saved_err;
int err;
@@ -629,18 +612,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_it_init(&it, &sh->block_set);
+ while ((ser = kho_block_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;
}
@@ -649,36 +633,47 @@ int luo_session_serialize(void)
{
struct luo_session_header *sh = &luo_session_global.outgoing;
struct luo_session *session;
- int i = 0;
+ struct kho_block_it it;
int err;
down_write(&luo_session_serialize_rwsem);
down_write(&sh->rwsem);
*sh->sessions_pa = 0;
+ kho_block_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_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_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));
}
+
+ kho_block_it_finalize(&it);
+
+ 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_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 v5 10/13] liveupdate: Remove limit on the number of files per session
From: Pasha Tatashin @ 2026-06-02 3:17 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: <20260602031717.197696-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>
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
include/linux/kho/abi/luo.h | 13 +--
kernel/liveupdate/luo_file.c | 139 +++++++++++++++----------------
kernel/liveupdate/luo_internal.h | 6 +-
3 files changed, 75 insertions(+), 83 deletions(-)
diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 79758d92ed5f..16df550ef143 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_COMPAT_BASE "luo-v3"
+#define LUO_COMPAT_BASE "luo-v4"
#define LUO_ABI_COMPATIBLE LUO_COMPAT_BASE "-" KHO_BLOCK_ABI_COMPATIBLE
#define LUO_ABI_COMPAT_LEN ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
@@ -103,9 +103,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..695e99aaba20 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_grow(&file_set->block_set, file_set->count);
+ 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_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_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_it it;
int err;
- int i;
if (!file_set->count)
return 0;
- if (WARN_ON(!file_ser))
- return -EINVAL;
+ kho_block_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_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,15 @@ 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;
}
+ kho_block_it_finalize(&it);
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 +707,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_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 +786,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_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 +814,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_it_init(&it, &file_set->block_set);
+ while ((file_ser = kho_block_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 v5 11/13] selftests/liveupdate: Test session and file limit removal
From: Pasha Tatashin @ 2026-06-02 3:17 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: <20260602031717.197696-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 v5 12/13] selftests/liveupdate: Add stress-sessions kexec test
From: Pasha Tatashin @ 2026-06-02 3:17 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: <20260602031717.197696-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 v5 13/13] selftests/liveupdate: Add stress-files kexec test
From: Pasha Tatashin @ 2026-06-02 3:17 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: <20260602031717.197696-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: [RFC PATCH 0/5] mm, swap: Virtual Swap Space (Swap Table Edition)
From: Kairui Song @ 2026-06-02 3:24 UTC (permalink / raw)
To: Nhat Pham
Cc: 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=PmwzaJhfjBrho3+kQ8HXFUC0WiegQrsguBc-_pmn5bSA@mail.gmail.com>
On Tue, Jun 2, 2026 at 2:06 AM Nhat Pham <nphamcs@gmail.com> wrote:
>
> On Mon, Jun 1, 2026 at 10:45 AM Kairui Song <ryncsn@gmail.com> wrote:
> >
> > On Mon, Jun 1, 2026 at 11:57 PM Nhat Pham <nphamcs@gmail.com> wrote:
> > >
> > > Are you suggesting we merge the virtual table with main swap table?
> > >
> > > Man, I'd love to do this. There is a problem though - we have a case
> > > where we occupy both backing physical swap AND swap cache. Do you
> > > think we can fit both the physical swap slot handle and the swap cache
> > > PFN into the same slot in virtual table? Maybe with some expanding...?
> >
> > I don't really get why we would need to do that? If you put the PFN
> > info in the virtual / upper layer, then the count info, locking, and
> > all swap IO synchronization (via folio lock), dup (current protected
> > by ci lock / folio lock), and allocation (folio_alloc_swap), are all
> > handled in this layer.
> >
> > The physical / lower layer will just hold a reverse entry on
> > folio_realloc_swap, or no entry at all (no physical layer used, zswap,
> > or after swap allocation but before IO) right?
> >
> > Looking up the actual folio from the physical layer will be a bit
> > slower since it needs to resolve the reverse entry, but the only place
> > we need to do that is things like migrate, compaction (none of them
> > exist yet) which seems totally fine?
>
> All of this is correct, but consider swaping in a vswap entry backed
> by pswap. There are cases where you still want to maintain the pswap
> slots around backing vswap entry, while having the swap cache folio as
> well.
>
> For e.g, at swap in time, we add the folio into the swap cache. First
> of all, we need to hold on to the physical swap slot for IO step. But
> even after IO succeeds, there are cases where you would still like to
> keep physical swap slots around (for e.g, to avoid swapping out again
> if the folio is only speculatively fetched).
A reverse entry is enough to hold the physical swap, just like how the
current hibernation works with a fake shadow, you don't need a PFN
just for holding that.
>
> So you have to make sure we have space for both the physical swap
> slot, and the swap cache folio's PFN at the same time for each vswap
> entry. So we still need the vtable extension (well maybe the other
> approach I mentioned could work, but I'm not 100% sure).
Right, vtable extension is fine, there is no redundant data. I just
mean you don't need to set the PFN twice (for vswap & pswap). So
simply reusing the PFN format in the vswap layer and solving
everything there should be enough.
> > Thanks. Not too complicated, actually our internal kernel
> > implementation still using si->percpu cluster, and use a counter for
> > the rotation and each order have a counter :P, it's a bit ugly but
> > works fine. It still serves pretty well just like the global percpu
> > cluster, YoungJun's previous per ci percpu cluster also still provides
> > the fast path, many ways to do that.
>
> Sounds like something that should be upstreamed? ;)
I'd love to :), there is a lot of work going on as you can see and
people seem to have many different proposals about this so I didn't
prioritize it. I'll try as things settle down.
> > > >
> > > > For patch 2, a few routines like vswap_can_swapin_thp seems not
> > > > needed or should be moved to __swap_cache_alloc? VSWAP_FOLIO is
> > > > same as swap cache folio check, which is already covered. Same for
> > > > zero checking, and VSWAP_NONE which is same as swap count check
> > > > I think. That way we not only save a lot of code, we also no
> > > > longer need to treat vswap specially.
> > >
> > > Unfortunately, I think a lot of this complexity is still needed. Vswap
> > > adds a new layer, which means new complications :)
> > >
> > > For instance, I think you still need vswap_can_swapin_thp. It
> > > basically enforces that the backend must be something
> > > swap_read_folio() can handle. That means:
> > >
> > > 1. No zswap.
> > >
> > > 2. No mixed backend.
> >
> > If mixed backend means phys vs zero vs zswap, then we already have
> > part of that covered with the current swap cache except for the phys
> > part (zswap part seems very doable with fujunjie's work).
> > swap_cache_alloc_folio will ensure there is no mixed zerobit, it can
> > be easily extended to ensure there is no mixed zswap as well
> > (according to what I've learned from fujunjie's code). Similar logic
> > for phys detection I think.
>
> Yeah it's basically generalizing that check, and handle the case where
> we can have indirection.
>
> I mean I can open-code it, but it has to be there :) And I figure it
> might be useful to check this opportunistically (at swap_pte_batch,
> even if it's not guaranteed to be correct down the line) before we
> even attempt to allocate a large folio etc. to avoid large folio
> allocation.
Right, but swap_cache_alloc_folio with orders=<large order> won't
attempt a large allocation if the batch check fails, so that's fine.
> > > Basically:
> > >
> > > 1. For vswap entry, not backed by phys swap: record swap memcg, hold
> > > reference to pin the memcg, but not charging towards swap.current.
> >
> > Maybe you don't need to record memcg here since folio->memcg already
> > have that info?
> >
> > I previously had a patch:
> > https://lore.kernel.org/linux-mm/20260220-swap-table-p4-v1-7-104795d19815@tencent.com/
> >
> > The defers the recording of memcg, the behavior is almost identical to
> > before, but charging & recording should be cleaner and you don't need
> > to record memcg at allocation time hence maybe reduce the possibility
> > of pinning a memcg. I didn't include that in P4 just to reduce LOC,
> > maybe can be resent or included.
>
> That works-ish when the folio is sitll in swap cache, but say if it's
> vswap backed by zswap (and the swap cache folio has been reclaimed),
> you need a place to store the memcg, no?
"Backed by zswap" means the actual swapout already happened, which is
the case where we always have to record the memcg info because the
folio is gone, seems still fit in the model.
> Just seems cleaner to centralize this info at vswap layer when it is
> presented, for now anyway, rather than juggling this on a per-backend
> basis.
Zswap charge could be merged with vswap I think but pswap we just
discussed that we might want to charge it differently? And actually
vswap charge is still quite different from zswap charge if you want to
make vswap infinitely large? I think we can figure out this part as we
progress; it's not a major problem at this point.
^ permalink raw reply
* Re: [PATCH v8 3/6] mm/memory-failure: report MF_MSG_KERNEL for unrecoverable kernel pages
From: Miaohe Lin @ 2026-06-02 3:31 UTC (permalink / raw)
To: Breno Leitao
Cc: linux-mm, linux-kernel, linux-doc, linux-kselftest,
linux-trace-kernel, kernel-team, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Shuah Khan, Naoya Horiguchi,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Jonathan Corbet, Shuah Khan, Liam R. Howlett
In-Reply-To: <20260527-ecc_panic-v8-3-9ea0cfa16bb0@debian.org>
On 2026/5/27 22:06, Breno Leitao wrote:
> The previous patch teaches get_any_page() to return -ENOTRECOVERABLE
> for stable unhandlable kernel pages (PG_reserved, slab, page tables,
> large-kmalloc). memory_failure() still folds every negative return
> into MF_MSG_GET_HWPOISON, so callers that want to react to the
> unrecoverable cases (a panic option, smarter logging) cannot tell
> them apart from transient page-allocator races.
>
> Turn the post-call branch into a switch over the get_hwpoison_page()
> return code: map -ENOTRECOVERABLE to MF_MSG_KERNEL and any other
> negative return to MF_MSG_GET_HWPOISON. case 0 keeps the existing
> free-buddy / kernel-high-order handling and case 1 falls through to
> the rest of memory_failure() unchanged.
>
> The MF_MSG_KERNEL label and tracepoint string are kept as
> "reserved kernel page" to avoid breaking userspace tools that match
> on those literals; the enum value still adequately tags the failure
> even though it now also covers slab, page tables and large-kmalloc
> pages.
>
> Suggested-by: David Hildenbrand <david@kernel.org>
> Signed-off-by: Breno Leitao <leitao@debian.org>
Acked-by: Miaohe Lin <linmiaohe@huawei.com>
Thanks.
.
^ permalink raw reply
* configurable block error injection
From: Christoph Hellwig @ 2026-06-02 5:45 UTC (permalink / raw)
To: Jens Axboe; +Cc: Jonathan Corbet, linux-block, linux-doc, bpf, linux-kselftest
Hi all,
this series adds a new configurable block error injection facility.
We already have a few to inject block errors, but unfortunately most
of them are either not very useful or hard to use, or both:
- The fail_make_request failure injection point can't distinguish
different commands, different ranges in the file and can only injection
plain I/O errors.
- the should_fail_bio 'dynamic' failure injection has all the same issues
as fail_make_request
- dm-error can only fail all command in the table using BLK_STS_IOERR
and requires setting up a new block device
- dm-flakey and dm-dust allow all kinds of configurability, but still
don't have good error selection, no good support for non-read/write
commands and are limited to the dm table alignment requirements,
which for zoned devices enforces setting them up for an entire zone.
They also once again require setting up a stacked block device,
which is really annoying in harnesses like xfstests
This series adds a new debugfs-based block layer error injection
that allows to configure what operations and ranges the injection
applied to, and what status to return. It also allows to configure a
failure ratio similar to the xfs errortag injection.
As part of this the should_fail_bio is removed, as the should_fail_bio
function really gets in the way in it's current form, and the injection
of an errno which always gets turned into BLK_STS_IOERR doesn't make
much sense. This required adjusting the bpf test suite as it attached
to this function as it assumed it to be present.
Diffstat:
Documentation/block/error-injection.rst | 59 ++
Documentation/block/index.rst | 1
block/Kconfig | 6
block/Makefile | 1
block/blk-core.c | 128 ++---
block/blk-merge.c | 5
block/blk-mq.c | 3
block/blk-sysfs.c | 4
block/blk.h | 24
block/error-injection.c | 329 +++++++++++++
block/genhd.c | 4
include/linux/blk_types.h | 2
include/linux/blkdev.h | 5
lib/Kconfig.debug | 6
tools/testing/selftests/bpf/prog_tests/kprobe_multi_test.c | 7
15 files changed, 507 insertions(+), 77 deletions(-)
^ permalink raw reply
* [PATCH 1/9] block: remove ALLOW_ERROR_INJECTION for should_fail_bio
From: Christoph Hellwig @ 2026-06-02 5:45 UTC (permalink / raw)
To: Jens Axboe; +Cc: Jonathan Corbet, linux-block, linux-doc, bpf, linux-kselftest
In-Reply-To: <20260602054615.3788425-1-hch@lst.de>
Allow error injection for should_fail_bio is a bit misguided. It allows
inserting an errno, which is then ignored, but it forced and out of line
call for something that should not exist when error injection is disabled.
Remove the error injection flag in preparation for adding better block
layer error injection, and switch the bpf test to use a MM error
injection site instead.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
block/blk-core.c | 1 -
tools/testing/selftests/bpf/prog_tests/kprobe_multi_test.c | 7 ++++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/block/blk-core.c b/block/blk-core.c
index b0f0a304ea0b..3a23af3e26a9 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -545,7 +545,6 @@ int should_fail_bio(struct bio *bio)
return -EIO;
return 0;
}
-ALLOW_ERROR_INJECTION(should_fail_bio, ERRNO);
/*
* Check whether this bio extends beyond the end of the device or partition.
diff --git a/tools/testing/selftests/bpf/prog_tests/kprobe_multi_test.c b/tools/testing/selftests/bpf/prog_tests/kprobe_multi_test.c
index 2e0ddef77ba5..6c8b161cdd7b 100644
--- a/tools/testing/selftests/bpf/prog_tests/kprobe_multi_test.c
+++ b/tools/testing/selftests/bpf/prog_tests/kprobe_multi_test.c
@@ -588,12 +588,13 @@ static void test_attach_override(void)
goto cleanup;
}
- /* The should_fail_bio function is on error injection list,
+ /* The __filemap_add_folio function is on error injection list,
* attach should succeed.
*/
link = bpf_program__attach_kprobe_multi_opts(skel->progs.test_override,
- "should_fail_bio", NULL);
- if (!ASSERT_OK_PTR(link, "override_attached_should_fail_bio"))
+ "__filemap_add_folio,",
+ NULL);
+ if (!ASSERT_OK_PTR(link, "override_attached___filemap_add_folio,"))
goto cleanup;
bpf_link__destroy(link);
--
2.53.0
^ permalink raw reply related
* [PATCH 2/9] block: consolidate the calls to should_fail_bio
From: Christoph Hellwig @ 2026-06-02 5:45 UTC (permalink / raw)
To: Jens Axboe; +Cc: Jonathan Corbet, linux-block, linux-doc, bpf, linux-kselftest
In-Reply-To: <20260602054615.3788425-1-hch@lst.de>
Delay the point of error injection a bit so that we have a single site
in blk-core.c after more of the submission side checks and blkcg
throttling. This allows to make should_fail_bio static in blk-core.c.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
block/blk-core.c | 6 +++++-
block/blk-merge.c | 5 +----
block/blk.h | 1 -
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/block/blk-core.c b/block/blk-core.c
index 3a23af3e26a9..f35e0d3fb127 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -539,7 +539,7 @@ static inline void bio_check_ro(struct bio *bio)
}
}
-int should_fail_bio(struct bio *bio)
+static int should_fail_bio(struct bio *bio)
{
if (should_fail_request(bdev_whole(bio->bi_bdev), bio->bi_iter.bi_size))
return -EIO;
@@ -723,6 +723,10 @@ static void __submit_bio_noacct_mq(struct bio *bio)
void submit_bio_noacct_nocheck(struct bio *bio, bool split)
{
+ if (should_fail_bio(bio)) {
+ bio_io_error(bio);
+ return;
+ }
blk_cgroup_bio_start(bio);
if (!bio_flagged(bio, BIO_TRACE_COMPLETION)) {
diff --git a/block/blk-merge.c b/block/blk-merge.c
index 7cc82a7a6f4e..b44f8ae849b8 100644
--- a/block/blk-merge.c
+++ b/block/blk-merge.c
@@ -130,11 +130,8 @@ struct bio *bio_submit_split_bioset(struct bio *bio, unsigned int split_sectors,
trace_block_split(split, bio->bi_iter.bi_sector);
WARN_ON_ONCE(bio_zone_write_plugging(bio));
- if (should_fail_bio(bio))
- bio_io_error(bio);
- else if (!blk_throtl_bio(bio))
+ if (!blk_throtl_bio(bio))
submit_bio_noacct_nocheck(bio, true);
-
return split;
}
EXPORT_SYMBOL_GPL(bio_submit_split_bioset);
diff --git a/block/blk.h b/block/blk.h
index bf1a80493ff1..889a39589356 100644
--- a/block/blk.h
+++ b/block/blk.h
@@ -646,7 +646,6 @@ extern const struct address_space_operations def_blk_aops;
int disk_register_independent_access_ranges(struct gendisk *disk);
void disk_unregister_independent_access_ranges(struct gendisk *disk);
-int should_fail_bio(struct bio *bio);
#ifdef CONFIG_FAIL_MAKE_REQUEST
bool should_fail_request(struct block_device *part, unsigned int bytes);
#else /* CONFIG_FAIL_MAKE_REQUEST */
--
2.53.0
^ permalink raw reply related
* [PATCH 3/9] block: refactor should_fail_bio and should_fail_request
From: Christoph Hellwig @ 2026-06-02 5:45 UTC (permalink / raw)
To: Jens Axboe; +Cc: Jonathan Corbet, linux-block, linux-doc, bpf, linux-kselftest
In-Reply-To: <20260602054615.3788425-1-hch@lst.de>
Move the bdev flag checks into a helper and the blk-mq clone
insers so that we can do only a single actual error injection for
I/O to partitions instead of doing it twice.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
block/blk-core.c | 28 ++++++++++++++--------------
block/blk-mq.c | 3 ++-
block/blk.h | 5 ++---
include/linux/blk_types.h | 2 --
4 files changed, 18 insertions(+), 20 deletions(-)
diff --git a/block/blk-core.c b/block/blk-core.c
index f35e0d3fb127..644888b66f33 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -502,10 +502,9 @@ static int __init setup_fail_make_request(char *str)
}
__setup("fail_make_request=", setup_fail_make_request);
-bool should_fail_request(struct block_device *part, unsigned int bytes)
+bool should_fail_request(unsigned int bytes)
{
- return bdev_test_flag(part, BD_MAKE_IT_FAIL) &&
- should_fail(&fail_make_request, bytes);
+ return should_fail(&fail_make_request, bytes);
}
static int __init fail_make_request_debugfs(void)
@@ -539,11 +538,13 @@ static inline void bio_check_ro(struct bio *bio)
}
}
-static int should_fail_bio(struct bio *bio)
+static inline bool may_fail_bio(struct bio *bio)
{
- if (should_fail_request(bdev_whole(bio->bi_bdev), bio->bi_iter.bi_size))
- return -EIO;
- return 0;
+ if (!IS_ENABLED(CONFIG_FAIL_MAKE_REQUEST))
+ return false;
+ return bdev_test_flag(bio->bi_bdev, BD_MAKE_IT_FAIL) ||
+ (bio_flagged(bio, BIO_REMAPPED) &&
+ bdev_test_flag(bdev_whole(bio->bi_bdev), BD_MAKE_IT_FAIL));
}
/*
@@ -577,8 +578,6 @@ static int blk_partition_remap(struct bio *bio)
{
struct block_device *p = bio->bi_bdev;
- if (unlikely(should_fail_request(p, bio->bi_iter.bi_size)))
- return -EIO;
if (bio_sectors(bio)) {
bio->bi_iter.bi_sector += p->bd_start_sect;
trace_block_bio_remap(bio, p->bd_dev,
@@ -723,10 +722,13 @@ static void __submit_bio_noacct_mq(struct bio *bio)
void submit_bio_noacct_nocheck(struct bio *bio, bool split)
{
- if (should_fail_bio(bio)) {
- bio_io_error(bio);
- return;
+ if (unlikely(may_fail_bio(bio))) {
+ if (should_fail_request(bio->bi_iter.bi_size)) {
+ bio_io_error(bio);
+ return;
+ }
}
+
blk_cgroup_bio_start(bio);
if (!bio_flagged(bio, BIO_TRACE_COMPLETION)) {
@@ -799,8 +801,6 @@ void submit_bio_noacct(struct bio *bio)
goto not_supported;
}
- if (should_fail_bio(bio))
- goto end_io;
bio_check_ro(bio);
if (!bio_flagged(bio, BIO_REMAPPED)) {
if (unlikely(bio_check_eod(bio)))
diff --git a/block/blk-mq.c b/block/blk-mq.c
index 629e16003eb7..bf66645622df 100644
--- a/block/blk-mq.c
+++ b/block/blk-mq.c
@@ -3275,7 +3275,8 @@ blk_status_t blk_insert_cloned_request(struct request *rq)
return BLK_STS_IOERR;
}
- if (q->disk && should_fail_request(q->disk->part0, blk_rq_bytes(rq)))
+ if (q->disk && bdev_test_flag(q->disk->part0, BD_MAKE_IT_FAIL) &&
+ should_fail_request(blk_rq_bytes(rq)))
return BLK_STS_IOERR;
ret = blk_crypto_rq_get_keyslot(rq);
diff --git a/block/blk.h b/block/blk.h
index 889a39589356..250a6eee700a 100644
--- a/block/blk.h
+++ b/block/blk.h
@@ -647,10 +647,9 @@ int disk_register_independent_access_ranges(struct gendisk *disk);
void disk_unregister_independent_access_ranges(struct gendisk *disk);
#ifdef CONFIG_FAIL_MAKE_REQUEST
-bool should_fail_request(struct block_device *part, unsigned int bytes);
+bool should_fail_request(unsigned int bytes);
#else /* CONFIG_FAIL_MAKE_REQUEST */
-static inline bool should_fail_request(struct block_device *part,
- unsigned int bytes)
+static inline bool should_fail_request(unsigned int bytes)
{
return false;
}
diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h
index 8808ee76e73c..4a3cfa857637 100644
--- a/include/linux/blk_types.h
+++ b/include/linux/blk_types.h
@@ -51,9 +51,7 @@ struct block_device {
#define BD_WRITE_HOLDER (1u<<9)
#define BD_HAS_SUBMIT_BIO (1u<<10)
#define BD_RO_WARNED (1u<<11)
-#ifdef CONFIG_FAIL_MAKE_REQUEST
#define BD_MAKE_IT_FAIL (1u<<12)
-#endif
dev_t bd_dev;
struct address_space *bd_mapping; /* page cache */
--
2.53.0
^ permalink raw reply related
* [PATCH 4/9] block: move the FAIL_MAKE_REQUEST symbol from lib/ to block/
From: Christoph Hellwig @ 2026-06-02 5:45 UTC (permalink / raw)
To: Jens Axboe; +Cc: Jonathan Corbet, linux-block, linux-doc, bpf, linux-kselftest
In-Reply-To: <20260602054615.3788425-1-hch@lst.de>
Keep the Kconfig symbol together with the code that it guards.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
block/Kconfig | 6 ++++++
lib/Kconfig.debug | 6 ------
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/block/Kconfig b/block/Kconfig
index 15027963472d..6c942391f65e 100644
--- a/block/Kconfig
+++ b/block/Kconfig
@@ -209,6 +209,12 @@ config BLK_INLINE_ENCRYPTION_FALLBACK
by falling back to the kernel crypto API when inline
encryption hardware is not present.
+config FAIL_MAKE_REQUEST
+ bool "Fault-injection capability for disk IO"
+ depends on FAULT_INJECTION
+ help
+ Provide fault-injection capability for disk IO.
+
source "block/partitions/Kconfig"
config BLK_PM
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 8ff5adcfe1e0..fb085963ec5e 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2116,12 +2116,6 @@ config FAULT_INJECTION_USERCOPY
Provides fault-injection capability to inject failures
in usercopy functions (copy_from_user(), get_user(), ...).
-config FAIL_MAKE_REQUEST
- bool "Fault-injection capability for disk IO"
- depends on FAULT_INJECTION && BLOCK
- help
- Provide fault-injection capability for disk IO.
-
config FAIL_IO_TIMEOUT
bool "Fault-injection capability for faking disk interrupts"
depends on FAULT_INJECTION && BLOCK
--
2.53.0
^ permalink raw reply related
* [PATCH 5/9] block: add a macro to initialize the status table
From: Christoph Hellwig @ 2026-06-02 5:45 UTC (permalink / raw)
To: Jens Axboe; +Cc: Jonathan Corbet, linux-block, linux-doc, bpf, linux-kselftest
In-Reply-To: <20260602054615.3788425-1-hch@lst.de>
Prepare for adding a new value to the error table by adding a macro
to fill it.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
block/blk-core.c | 45 +++++++++++++++++++++++++--------------------
1 file changed, 25 insertions(+), 20 deletions(-)
diff --git a/block/blk-core.c b/block/blk-core.c
index 644888b66f33..1ab666fc2e27 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -132,39 +132,44 @@ inline const char *blk_op_str(enum req_op op)
}
EXPORT_SYMBOL_GPL(blk_op_str);
+#define ENT(_tag, _errno, _desc) \
+[BLK_STS_##_tag] = { \
+ .errno = _errno, \
+ .name = _desc, \
+}
static const struct {
int errno;
const char *name;
} blk_errors[] = {
- [BLK_STS_OK] = { 0, "" },
- [BLK_STS_NOTSUPP] = { -EOPNOTSUPP, "operation not supported" },
- [BLK_STS_TIMEOUT] = { -ETIMEDOUT, "timeout" },
- [BLK_STS_NOSPC] = { -ENOSPC, "critical space allocation" },
- [BLK_STS_TRANSPORT] = { -ENOLINK, "recoverable transport" },
- [BLK_STS_TARGET] = { -EREMOTEIO, "critical target" },
- [BLK_STS_RESV_CONFLICT] = { -EBADE, "reservation conflict" },
- [BLK_STS_MEDIUM] = { -ENODATA, "critical medium" },
- [BLK_STS_PROTECTION] = { -EILSEQ, "protection" },
- [BLK_STS_RESOURCE] = { -ENOMEM, "kernel resource" },
- [BLK_STS_DEV_RESOURCE] = { -EBUSY, "device resource" },
- [BLK_STS_AGAIN] = { -EAGAIN, "nonblocking retry" },
- [BLK_STS_OFFLINE] = { -ENODEV, "device offline" },
+ ENT(OK, 0, ""),
+ ENT(NOTSUPP, -EOPNOTSUPP, "operation not supported"),
+ ENT(TIMEOUT, -ETIMEDOUT, "timeout"),
+ ENT(NOSPC, -ENOSPC, "critical space allocation"),
+ ENT(TRANSPORT, -ENOLINK, "recoverable transport"),
+ ENT(TARGET, -EREMOTEIO, "critical target"),
+ ENT(RESV_CONFLICT, -EBADE, "reservation conflict"),
+ ENT(MEDIUM, -ENODATA, "critical medium"),
+ ENT(PROTECTION, -EILSEQ, "protection"),
+ ENT(RESOURCE, -ENOMEM, "kernel resource"),
+ ENT(DEV_RESOURCE, -EBUSY, "device resource"),
+ ENT(AGAIN, -EAGAIN, "nonblocking retry"),
+ ENT(OFFLINE, -ENODEV, "device offline"),
/* device mapper special case, should not leak out: */
- [BLK_STS_DM_REQUEUE] = { -EREMCHG, "dm internal retry" },
+ ENT(DM_REQUEUE, -EREMCHG, "dm internal retry"),
/* zone device specific errors */
- [BLK_STS_ZONE_OPEN_RESOURCE] = { -ETOOMANYREFS, "open zones exceeded" },
- [BLK_STS_ZONE_ACTIVE_RESOURCE] = { -EOVERFLOW, "active zones exceeded" },
+ ENT(ZONE_OPEN_RESOURCE, -ETOOMANYREFS, "open zones exceeded"),
+ ENT(ZONE_ACTIVE_RESOURCE, -EOVERFLOW, "active zones exceeded"),
/* Command duration limit device-side timeout */
- [BLK_STS_DURATION_LIMIT] = { -ETIME, "duration limit exceeded" },
-
- [BLK_STS_INVAL] = { -EINVAL, "invalid" },
+ ENT(DURATION_LIMIT, -ETIME, "duration limit exceeded"),
+ ENT(INVAL, -EINVAL, "invalid"),
/* everything else not covered above: */
- [BLK_STS_IOERR] = { -EIO, "I/O" },
+ ENT(IOERR, -EIO, "I/O"),
};
+#undef ENT
blk_status_t errno_to_blk_status(int errno)
{
--
2.53.0
^ permalink raw reply related
* [PATCH 6/9] block: add a "tag" for block status codes
From: Christoph Hellwig @ 2026-06-02 5:45 UTC (permalink / raw)
To: Jens Axboe; +Cc: Jonathan Corbet, linux-block, linux-doc, bpf, linux-kselftest
In-Reply-To: <20260602054615.3788425-1-hch@lst.de>
The full name of the status codes is not good for user interfaces as it
can contain white spaces. Add the name of the status code without the
BLK_STS_ prefix as a tag so that it can be used for user interfaces.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
block/blk-core.c | 24 ++++++++++++++++++++++++
block/blk.h | 2 ++
2 files changed, 26 insertions(+)
diff --git a/block/blk-core.c b/block/blk-core.c
index 1ab666fc2e27..19a4d0672b3d 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -135,10 +135,12 @@ EXPORT_SYMBOL_GPL(blk_op_str);
#define ENT(_tag, _errno, _desc) \
[BLK_STS_##_tag] = { \
.errno = _errno, \
+ .tag = __stringify(_tag), \
.name = _desc, \
}
static const struct {
int errno;
+ const char *tag;
const char *name;
} blk_errors[] = {
ENT(OK, 0, ""),
@@ -203,6 +205,28 @@ const char *blk_status_to_str(blk_status_t status)
return blk_errors[idx].name;
}
+const char *blk_status_to_tag(blk_status_t status)
+{
+ int idx = (__force int)status;
+
+ if (WARN_ON_ONCE(idx >= ARRAY_SIZE(blk_errors)))
+ return "<null>";
+ return blk_errors[idx].tag;
+}
+
+blk_status_t tag_to_blk_status(const char *tag)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(blk_errors); i++) {
+ if (blk_errors[i].tag &&
+ !strcmp(blk_errors[i].tag, tag))
+ return (__force blk_status_t)i;
+ }
+
+ return BLK_STS_OK;
+}
+
/**
* blk_sync_queue - cancel any pending callbacks on a queue
* @q: the queue
diff --git a/block/blk.h b/block/blk.h
index 250a6eee700a..1e80338af858 100644
--- a/block/blk.h
+++ b/block/blk.h
@@ -50,6 +50,8 @@ struct blk_flush_queue *blk_alloc_flush_queue(int node, int cmd_size,
void blk_free_flush_queue(struct blk_flush_queue *q);
const char *blk_status_to_str(blk_status_t status);
+const char *blk_status_to_tag(blk_status_t status);
+blk_status_t tag_to_blk_status(const char *tag);
bool __blk_mq_unfreeze_queue(struct request_queue *q, bool force_atomic);
bool blk_queue_start_drain(struct request_queue *q);
--
2.53.0
^ permalink raw reply related
* [PATCH 7/9] block: add a str_to_blk_op helper
From: Christoph Hellwig @ 2026-06-02 5:45 UTC (permalink / raw)
To: Jens Axboe; +Cc: Jonathan Corbet, linux-block, linux-doc, bpf, linux-kselftest
In-Reply-To: <20260602054615.3788425-1-hch@lst.de>
Add a helper to find the REQ_OP_XYZ constant from the "XYZ" string.
This will be used for the error injection debugfs interface.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
block/blk-core.c | 13 +++++++++++++
block/blk.h | 1 +
2 files changed, 14 insertions(+)
diff --git a/block/blk-core.c b/block/blk-core.c
index 19a4d0672b3d..8bbc03ce924f 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -132,6 +132,19 @@ inline const char *blk_op_str(enum req_op op)
}
EXPORT_SYMBOL_GPL(blk_op_str);
+enum req_op str_to_blk_op(const char *op)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(blk_op_name); i++) {
+ if (blk_op_name[i] &&
+ !strcmp(blk_op_name[i], op))
+ return i;
+ }
+
+ return REQ_OP_LAST;
+}
+
#define ENT(_tag, _errno, _desc) \
[BLK_STS_##_tag] = { \
.errno = _errno, \
diff --git a/block/blk.h b/block/blk.h
index 1e80338af858..4857b899e2b6 100644
--- a/block/blk.h
+++ b/block/blk.h
@@ -52,6 +52,7 @@ void blk_free_flush_queue(struct blk_flush_queue *q);
const char *blk_status_to_str(blk_status_t status);
const char *blk_status_to_tag(blk_status_t status);
blk_status_t tag_to_blk_status(const char *tag);
+enum req_op str_to_blk_op(const char *op);
bool __blk_mq_unfreeze_queue(struct request_queue *q, bool force_atomic);
bool blk_queue_start_drain(struct request_queue *q);
--
2.53.0
^ permalink raw reply related
* [PATCH 8/9] block: add configurable error injection
From: Christoph Hellwig @ 2026-06-02 5:45 UTC (permalink / raw)
To: Jens Axboe; +Cc: Jonathan Corbet, linux-block, linux-doc, bpf, linux-kselftest
In-Reply-To: <20260602054615.3788425-1-hch@lst.de>
Add a new block error injection interface that allows to inject specific
status code for specific ranges.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
Documentation/block/error-injection.rst | 59 +++++
Documentation/block/index.rst | 1 +
block/Makefile | 1 +
block/blk-core.c | 2 +
block/blk-sysfs.c | 4 +
block/blk.h | 15 ++
block/error-injection.c | 299 ++++++++++++++++++++++++
block/genhd.c | 4 +
include/linux/blkdev.h | 5 +
9 files changed, 390 insertions(+)
create mode 100644 Documentation/block/error-injection.rst
create mode 100644 block/error-injection.c
diff --git a/Documentation/block/error-injection.rst b/Documentation/block/error-injection.rst
new file mode 100644
index 000000000000..be87091b5330
--- /dev/null
+++ b/Documentation/block/error-injection.rst
@@ -0,0 +1,59 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+============================
+Configurable Error Injection
+============================
+
+Overview
+--------
+
+Configurable error injection allows injecting specific block layer status codes
+for ranges of a block device. Error can be injected unconditional, or with a
+given probability.
+
+To use configurable error injection, CONFIG_FAIL_MAKE_REQUEST must be enabled.
+
+The only interface is the error_injection debugfs file, which is created for
+each registered gendisk. Writes to this file are used to create or delete rules
+and reads return a list of the current error injection sites.
+
+Options
+-------
+
+The following options specify the operations:
+
+=================== =======================================================
+add add a new rule
+removeall remove all existing rules
+=================== =======================================================
+
+The following options specify the details of the rule for the add operation:
+
+=================== =======================================================
+op=%s block layer operation this rule applies to, e.g. READ
+ or WRITE.
+ Mandatory.
+start=%u First block layer sector the rule applies to.
+ Optional, defaults to 0.
+nr_sectors=%u Number of sectors this rule applies.
+ Optional, defaults to the remainder of the device.
+status=%s Status to return.
+ Mandatory.
+chance=%u Only return a failure with a likelihood of 1/chance.
+ Optional, defaults to 1 (always).
+=================== =======================================================
+
+Example
+-------
+
+Return BLK_STS_IOERR for one in 10 reads of sector 0 of /dev/nvme0n1:
+
+ $ echo 'add,op=READ,start=0,status=IOERR,chance=10' > /sys/kernel/debug/block/nvme0n1/error_injection
+
+Return BLK_STS_MEDIUM for every write to /dev/nvme0n1:
+
+ $ echo 'add,op=WRITE,start=0,status=MEDIUM' > /sys/kernel/debug/block/nvme0n1/error_injection
+
+Remove all rules for /dev/nvme0n1:
+
+ $ echo 'removeall' > /sys/kernel/debug/block/nvme0n1/error_injection
diff --git a/Documentation/block/index.rst b/Documentation/block/index.rst
index 9fea696f9daa..bfa1bbd31ddf 100644
--- a/Documentation/block/index.rst
+++ b/Documentation/block/index.rst
@@ -22,3 +22,4 @@ Block
switching-sched
writeback_cache_control
ublk
+ error-injection
diff --git a/block/Makefile b/block/Makefile
index 7dce2e44276c..d223b6b7d72f 100644
--- a/block/Makefile
+++ b/block/Makefile
@@ -11,6 +11,7 @@ obj-y := bdev.o fops.o bio.o elevator.o blk-core.o blk-sysfs.o \
genhd.o ioprio.o badblocks.o partitions/ blk-rq-qos.o \
disk-events.o blk-ia-ranges.o early-lookup.o
+obj-$(CONFIG_FAIL_MAKE_REQUEST) += error-injection.o
obj-$(CONFIG_BLK_DEV_BSG_COMMON) += bsg.o
obj-$(CONFIG_BLK_DEV_BSGLIB) += bsg-lib.o
obj-$(CONFIG_BLK_CGROUP) += blk-cgroup.o
diff --git a/block/blk-core.c b/block/blk-core.c
index 8bbc03ce924f..04a392849ab0 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -765,6 +765,8 @@ static void __submit_bio_noacct_mq(struct bio *bio)
void submit_bio_noacct_nocheck(struct bio *bio, bool split)
{
if (unlikely(may_fail_bio(bio))) {
+ if (blk_error_inject(bio))
+ return;
if (should_fail_request(bio->bi_iter.bi_size)) {
bio_io_error(bio);
return;
diff --git a/block/blk-sysfs.c b/block/blk-sysfs.c
index f22c1f253eb3..43f909c7f0c9 100644
--- a/block/blk-sysfs.c
+++ b/block/blk-sysfs.c
@@ -933,6 +933,8 @@ static void blk_debugfs_remove(struct gendisk *disk)
blk_debugfs_lock_nomemsave(q);
blk_trace_shutdown(q);
+ if (IS_ENABLED(CONFIG_FAIL_MAKE_REQUEST))
+ blk_error_injection_exit(disk);
debugfs_remove_recursive(q->debugfs_dir);
q->debugfs_dir = NULL;
q->sched_debugfs_dir = NULL;
@@ -963,6 +965,8 @@ int blk_register_queue(struct gendisk *disk)
memflags = blk_debugfs_lock(q);
q->debugfs_dir = debugfs_create_dir(disk->disk_name, blk_debugfs_root);
+ if (IS_ENABLED(CONFIG_FAIL_MAKE_REQUEST))
+ blk_error_injection_init(disk);
if (queue_is_mq(q))
blk_mq_debugfs_register(q);
blk_debugfs_unlock(q, memflags);
diff --git a/block/blk.h b/block/blk.h
index 4857b899e2b6..19f925d8f39d 100644
--- a/block/blk.h
+++ b/block/blk.h
@@ -781,4 +781,19 @@ static inline void blk_debugfs_unlock(struct request_queue *q,
memalloc_noio_restore(memflags);
}
+void blk_error_injection_init(struct gendisk *disk);
+void blk_error_injection_exit(struct gendisk *disk);
+
+bool __blk_error_inject(struct bio *bio);
+static inline bool blk_error_inject(struct bio *bio)
+{
+#ifdef CONFIG_FAIL_MAKE_REQUEST
+ struct gendisk *disk = bio->bi_bdev->bd_disk;
+
+ if (!list_empty_careful(&disk->error_injection_list))
+ return __blk_error_inject(bio);
+#endif
+ return false;
+}
+
#endif /* BLK_INTERNAL_H */
diff --git a/block/error-injection.c b/block/error-injection.c
new file mode 100644
index 000000000000..dc0420c4eb58
--- /dev/null
+++ b/block/error-injection.c
@@ -0,0 +1,299 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Christoph Hellwig.
+ */
+#include <linux/debugfs.h>
+#include <linux/blkdev.h>
+#include <linux/parser.h>
+#include <linux/seq_file.h>
+#include "blk.h"
+
+struct blk_error_inject {
+ struct list_head entry;
+ sector_t start;
+ sector_t end;
+ enum req_op op;
+ blk_status_t status;
+
+ /* only inject every 1 / chance times */
+ unsigned int chance;
+};
+
+bool __blk_error_inject(struct bio *bio)
+{
+ struct gendisk *disk = bio->bi_bdev->bd_disk;
+ struct blk_error_inject *inj;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(inj, &disk->error_injection_list, entry) {
+ if (bio->bi_iter.bi_sector <= inj->end &&
+ bio_end_sector(bio) >= inj->start &&
+ bio_op(bio) == inj->op) {
+ blk_status_t status = inj->status;
+
+ if (inj->chance > 1 &&
+ (get_random_u32() % inj->chance) != 0)
+ continue;
+
+ rcu_read_unlock();
+ pr_info_ratelimited("%pg: injecting %s error for %s at sector %llu:%u\n",
+ disk->part0,
+ blk_status_to_str(status),
+ blk_op_str(inj->op),
+ bio->bi_iter.bi_sector,
+ bio_sectors(bio));
+ bio_endio_status(bio, status);
+ return true;
+ }
+ }
+ rcu_read_unlock();
+ return false;
+}
+
+static int error_inject_add(struct gendisk *disk, enum req_op op,
+ sector_t start, u64 nr_sectors, blk_status_t status,
+ unsigned int chance)
+{
+ struct blk_error_inject *inj;
+
+ if (op == REQ_OP_LAST)
+ return -EINVAL;
+ if (status == BLK_STS_OK)
+ return -EINVAL;
+ if (U64_MAX - nr_sectors < start)
+ return -EINVAL;
+
+ if (!nr_sectors)
+ nr_sectors = U64_MAX;
+
+ inj = kzalloc_obj(*inj);
+ if (!inj)
+ return -ENOMEM;
+
+ pr_debug_ratelimited("%pg: adding %s injection for %s at sector %llu:%llu\n",
+ disk->part0, blk_status_to_str(status),
+ blk_op_str(op),
+ start, nr_sectors);
+
+ inj->op = op;
+ inj->start = start;
+ inj->end = start + nr_sectors - 1;
+ inj->status = status;
+ inj->chance = chance;
+
+ /*
+ * Add to the front of the list so that newer entries can partially
+ * override other entries. This also intentional allows duplicate
+ * entries as there is no real reason to reject them.
+ */
+ mutex_lock(&disk->error_injection_lock);
+ if (!disk_live(disk)) {
+ mutex_unlock(&disk->error_injection_lock);
+ return -EINVAL;
+ }
+ list_add(&inj->entry, &disk->error_injection_list);
+ mutex_unlock(&disk->error_injection_lock);
+
+ bdev_set_flag(disk->part0, BD_MAKE_IT_FAIL);
+ return 0;
+}
+
+static void error_inject_removall(struct gendisk *disk)
+{
+ struct blk_error_inject *inj;
+
+ mutex_lock(&disk->error_injection_lock);
+ while ((inj = list_first_entry_or_null(&disk->error_injection_list,
+ struct blk_error_inject, entry))) {
+ list_del_rcu(&inj->entry);
+ mutex_unlock(&disk->error_injection_lock);
+
+ kfree_rcu_mightsleep(inj);
+
+ mutex_lock(&disk->error_injection_lock);
+ }
+
+ mutex_unlock(&disk->error_injection_lock);
+
+ bdev_clear_flag(disk->part0, BD_MAKE_IT_FAIL);
+}
+
+enum options {
+ Opt_add = (1u << 0),
+ Opt_removeall = (1u << 1),
+
+ Opt_op = (1u << 16),
+ Opt_start = (1u << 17),
+ Opt_nr_sectors = (1u << 18),
+ Opt_status = (1u << 19),
+ Opt_chance = (1u << 20),
+
+ Opt_invalid,
+};
+
+static const match_table_t opt_tokens = {
+ { Opt_add, "add", },
+ { Opt_removeall, "removeall", },
+ { Opt_op, "op=%s", },
+ { Opt_start, "start=%u" },
+ { Opt_nr_sectors, "nr_sectors=%u" },
+ { Opt_status, "status=%s" },
+ { Opt_chance, "chance=%u" },
+ { Opt_invalid, NULL, },
+};
+
+static int match_op(substring_t *args, enum req_op *op)
+{
+ const char *tag;
+
+ tag = match_strdup(args);
+ if (!tag)
+ return -ENOMEM;
+ *op = str_to_blk_op(tag);
+ if (*op == REQ_OP_LAST)
+ pr_warn("invalid op '%s'\n", tag);
+ kfree(tag);
+ return 0;
+}
+
+static int match_status(substring_t *args, blk_status_t *status)
+{
+ const char *tag;
+
+ tag = match_strdup(args);
+ if (!tag)
+ return -ENOMEM;
+ *status = tag_to_blk_status(tag);
+ if (!*status)
+ pr_warn("invalid status '%s'\n", tag);
+ kfree(tag);
+ return 0;
+}
+
+static ssize_t blk_error_injection_write(struct file *file,
+ const char __user *ubuf, size_t count, loff_t *pos)
+{
+ struct gendisk *disk = file_inode(file)->i_private;
+ enum { Unset, Add, Removeall } action = Unset;
+ unsigned int option_mask = 0, chance = 1;
+ enum req_op op = REQ_OP_LAST;
+ u64 start = 0, nr_sectors = 0;
+ blk_status_t status = BLK_STS_OK;
+ substring_t args[MAX_OPT_ARGS];
+ char *options, *o, *p;
+ ssize_t token, ret = 0;
+
+ options = memdup_user_nul(ubuf, count);
+ if (!options)
+ return -ENOMEM;
+
+ o = options;
+ while ((p = strsep(&o, ",\n")) != NULL) {
+ if (!*p)
+ continue;
+ token = match_token(p, opt_tokens, args);
+ option_mask |= token;
+ switch (token) {
+ case Opt_add:
+ if (action == Unset)
+ action = Add;
+ else
+ ret = -EINVAL;
+ break;
+ case Opt_removeall:
+ if (action == Unset)
+ action = Removeall;
+ else
+ ret = -EINVAL;
+ break;
+ case Opt_op:
+ ret = match_op(args, &op);
+ break;
+ case Opt_start:
+ ret = match_u64(args, &start);
+ break;
+ case Opt_nr_sectors:
+ ret = match_u64(args, &nr_sectors);
+ break;
+ case Opt_status:
+ ret = match_status(args, &status);
+ break;
+ case Opt_chance:
+ ret = match_uint(args, &chance);
+ if (!ret && chance == 0)
+ ret = -EINVAL;
+ break;
+ default:
+ pr_warn("unknown parameter or missing value '%s'\n", p);
+ ret = -EINVAL;
+ break;
+ }
+ if (ret)
+ goto out_free_options;
+ }
+
+ switch (action) {
+ case Add:
+ ret = error_inject_add(disk, op, start, nr_sectors, status,
+ chance);
+ break;
+ case Removeall:
+ if (option_mask & ~Opt_removeall)
+ return -EINVAL;
+ error_inject_removall(disk);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ if (!ret)
+ ret = count;
+out_free_options:
+ kfree(options);
+ return ret;
+}
+
+static int blk_error_injection_show(struct seq_file *s, void *private)
+{
+ struct gendisk *disk = s->private;
+ struct blk_error_inject *inj;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(inj, &disk->error_injection_list, entry) {
+ seq_printf(s, "%llu:%llu status=%s,chance=%u",
+ inj->start, inj->end,
+ blk_status_to_tag(inj->status), inj->chance);
+ seq_putc(s, '\n');
+ }
+ rcu_read_unlock();
+ return 0;
+}
+
+static int blk_error_injection_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, blk_error_injection_show, inode->i_private);
+}
+
+static int blk_error_injection_release(struct inode *inode, struct file *file)
+{
+ return single_release(inode, file);
+}
+
+static const struct file_operations blk_error_injection_fops = {
+ .owner = THIS_MODULE,
+ .write = blk_error_injection_write,
+ .read = seq_read,
+ .open = blk_error_injection_open,
+ .release = blk_error_injection_release,
+};
+
+void blk_error_injection_init(struct gendisk *disk)
+{
+ debugfs_create_file("error_injection", 0600, disk->queue->debugfs_dir,
+ disk, &blk_error_injection_fops);
+}
+
+void blk_error_injection_exit(struct gendisk *disk)
+{
+ error_inject_removall(disk);
+}
diff --git a/block/genhd.c b/block/genhd.c
index 7d6854fd28e9..30f42461d895 100644
--- a/block/genhd.c
+++ b/block/genhd.c
@@ -1485,6 +1485,10 @@ struct gendisk *__alloc_disk_node(struct request_queue *q, int node_id,
lockdep_init_map(&disk->lockdep_map, "(bio completion)", lkclass, 0);
#ifdef CONFIG_BLOCK_HOLDER_DEPRECATED
INIT_LIST_HEAD(&disk->slave_bdevs);
+#endif
+#ifdef CONFIG_FAIL_MAKE_REQUEST
+ mutex_init(&disk->error_injection_lock);
+ INIT_LIST_HEAD(&disk->error_injection_list);
#endif
mutex_init(&disk->rqos_state_mutex);
kobject_init(&disk->queue_kobj, &blk_queue_ktype);
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 17270a28c66d..8743ad616b7f 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -227,6 +227,11 @@ struct gendisk {
*/
struct blk_independent_access_ranges *ia_ranges;
+#ifdef CONFIG_FAIL_MAKE_REQUEST
+ struct mutex error_injection_lock;
+ struct list_head error_injection_list;
+#endif
+
struct mutex rqos_state_mutex; /* rqos state change mutex */
};
--
2.53.0
^ permalink raw reply related
* [PATCH 9/9] block: move the fail request code
From: Christoph Hellwig @ 2026-06-02 5:45 UTC (permalink / raw)
To: Jens Axboe; +Cc: Jonathan Corbet, linux-block, linux-doc, bpf, linux-kselftest
In-Reply-To: <20260602054615.3788425-1-hch@lst.de>
Keep all error injection in one place, and out of line for the main
I/O submission fast path.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
block/blk-core.c | 37 ++-----------------------------------
block/error-injection.c | 30 ++++++++++++++++++++++++++++++
2 files changed, 32 insertions(+), 35 deletions(-)
diff --git a/block/blk-core.c b/block/blk-core.c
index 04a392849ab0..7465dd291272 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -29,7 +29,6 @@
#include <linux/swap.h>
#include <linux/writeback.h>
#include <linux/task_io_accounting_ops.h>
-#include <linux/fault-inject.h>
#include <linux/list_sort.h>
#include <linux/delay.h>
#include <linux/ratelimit.h>
@@ -534,32 +533,6 @@ bool blk_get_queue(struct request_queue *q)
}
EXPORT_SYMBOL(blk_get_queue);
-#ifdef CONFIG_FAIL_MAKE_REQUEST
-
-static DECLARE_FAULT_ATTR(fail_make_request);
-
-static int __init setup_fail_make_request(char *str)
-{
- return setup_fault_attr(&fail_make_request, str);
-}
-__setup("fail_make_request=", setup_fail_make_request);
-
-bool should_fail_request(unsigned int bytes)
-{
- return should_fail(&fail_make_request, bytes);
-}
-
-static int __init fail_make_request_debugfs(void)
-{
- struct dentry *dir = fault_create_debugfs_attr("fail_make_request",
- NULL, &fail_make_request);
-
- return PTR_ERR_OR_ZERO(dir);
-}
-
-late_initcall(fail_make_request_debugfs);
-#endif /* CONFIG_FAIL_MAKE_REQUEST */
-
static inline void bio_check_ro(struct bio *bio)
{
if (op_is_write(bio_op(bio)) && bdev_read_only(bio->bi_bdev)) {
@@ -764,14 +737,8 @@ static void __submit_bio_noacct_mq(struct bio *bio)
void submit_bio_noacct_nocheck(struct bio *bio, bool split)
{
- if (unlikely(may_fail_bio(bio))) {
- if (blk_error_inject(bio))
- return;
- if (should_fail_request(bio->bi_iter.bi_size)) {
- bio_io_error(bio);
- return;
- }
- }
+ if (unlikely(may_fail_bio(bio)) && blk_error_inject(bio))
+ return;
blk_cgroup_bio_start(bio);
diff --git a/block/error-injection.c b/block/error-injection.c
index dc0420c4eb58..45f2454d0bca 100644
--- a/block/error-injection.c
+++ b/block/error-injection.c
@@ -4,6 +4,7 @@
*/
#include <linux/debugfs.h>
#include <linux/blkdev.h>
+#include <linux/fault-inject.h>
#include <linux/parser.h>
#include <linux/seq_file.h>
#include "blk.h"
@@ -47,6 +48,13 @@ bool __blk_error_inject(struct bio *bio)
}
}
rcu_read_unlock();
+
+ /* legacy I/O error injection */
+ if (should_fail_request(bio->bi_iter.bi_size)) {
+ bio_io_error(bio);
+ return true;
+ }
+
return false;
}
@@ -297,3 +305,25 @@ void blk_error_injection_exit(struct gendisk *disk)
{
error_inject_removall(disk);
}
+
+static DECLARE_FAULT_ATTR(fail_make_request);
+
+bool should_fail_request(unsigned int bytes)
+{
+ return should_fail(&fail_make_request, bytes);
+}
+
+static int __init setup_fail_make_request(char *str)
+{
+ return setup_fault_attr(&fail_make_request, str);
+}
+__setup("fail_make_request=", setup_fail_make_request);
+
+static int __init fail_make_request_debugfs(void)
+{
+ struct dentry *dir = fault_create_debugfs_attr("fail_make_request",
+ NULL, &fail_make_request);
+
+ return PTR_ERR_OR_ZERO(dir);
+}
+late_initcall(fail_make_request_debugfs);
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v4 0/2] Delete task_euid()
From: Alice Ryhl @ 2026-06-02 6:15 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: <CAHC9VhR5Ca+WyP2OiNGtL1SqHn0SwLe=M9SB8D2bxtdS52quhg@mail.gmail.com>
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.
> > 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. 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.
Alice
^ permalink raw reply
* [PATCH][v2] mm/mempool: Untangle CONFIG_SLUB_DEBUG_ON abuse and switch to static key
From: lirongqing @ 2026-06-02 6:21 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Vlastimil Babka, Harry Yoo,
Andrew Morton, Hao Li, Christoph Lameter, David Rientjes,
Roman Gushchin, linux-doc, linux-kernel, linux-mm
Cc: Li RongQing, Matthew Wilcox, Usama Arif
From: Li RongQing <lirongqing@baidu.com>
The mempool subsystem historically wrapped its debugging logic inside an
merely defines compile-time defaults for SLUB and caused two flaws:
1. On production kernels where CONFIG_SLUB_DEBUG=y but
CONFIG_SLUB_DEBUG_ON=n, mempool debugging was completely compiled out
at compile time.
2. On kernels with CONFIG_SLUB_DEBUG_ON=y, mempool debugging stayed active
even if a user explicitly disabled slub debugging at boot time.
Clean up this mess by removing the #ifdef and switching to a runtime static
key (mempool_debug_enabled), allowing mempool debugging to be toggled
cleanly via its own boot parameter.
Suggested-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
Signed-off-by: Li RongQing <lirongqing@baidu.com>
Cc: Vlastimil Babka <vbabka@kernel.org>
Cc: Harry Yoo <harry@kernel.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Hao Li <hao.li@linux.dev>
Cc: Christoph Lameter <cl@gentwo.org>
Cc: David Rientjes <rientjes@google.com>
Cc: Roman Gushchin <roman.gushchin@linux.dev>
Cc: Matthew Wilcox <willy@infradead.org>
Cc: Usama Arif <usama.arif@linux.dev>
---
Diff with v1:
Rewrite commit message, change early_param to __setup
Documentation/admin-guide/kernel-parameters.txt | 5 ++++
mm/mempool.c | 32 ++++++++++++++++++-------
2 files changed, 28 insertions(+), 9 deletions(-)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 642659b..89b5994 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -3980,6 +3980,11 @@ Kernel parameters
Note that even when enabled, there are a few cases where
the feature is not effective.
+ mempool_debug [MM]
+ Enable mempool debugging. This enables element
+ poison checking when freeing elements back to the
+ pool. Useful for debugging mempool corruption.
+
memtest= [KNL,X86,ARM,M68K,PPC,RISCV,EARLY] Enable memtest
Format: <integer>
default : 0 <disable>
diff --git a/mm/mempool.c b/mm/mempool.c
index db23e0e..71e4b54 100644
--- a/mm/mempool.c
+++ b/mm/mempool.c
@@ -16,11 +16,28 @@
#include <linux/export.h>
#include <linux/mempool.h>
#include <linux/writeback.h>
+#include <linux/static_key.h>
+#include <linux/init.h>
#include "slab.h"
static DECLARE_FAULT_ATTR(fail_mempool_alloc);
static DECLARE_FAULT_ATTR(fail_mempool_alloc_bulk);
+/*
+ * Debugging support for mempool using static key.
+ *
+ * This allows enabling mempool debug at boot time via:
+ * mempool_debug
+ */
+static DEFINE_STATIC_KEY_FALSE(mempool_debug_enabled);
+
+static int __init mempool_debug_setup(char *str)
+{
+ static_branch_enable(&mempool_debug_enabled);
+ return 1;
+}
+__setup("mempool_debug", mempool_debug_setup);
+
static int __init mempool_faul_inject_init(void)
{
int error;
@@ -37,7 +54,6 @@ static int __init mempool_faul_inject_init(void)
}
late_initcall(mempool_faul_inject_init);
-#ifdef CONFIG_SLUB_DEBUG_ON
static void poison_error(struct mempool *pool, void *element, size_t size,
size_t byte)
{
@@ -73,6 +89,9 @@ static void __check_element(struct mempool *pool, void *element, size_t size)
static void check_element(struct mempool *pool, void *element)
{
+ if (!static_branch_unlikely(&mempool_debug_enabled))
+ return;
+
/* Skip checking: KASAN might save its metadata in the element. */
if (kasan_enabled())
return;
@@ -112,6 +131,9 @@ static void __poison_element(void *element, size_t size)
static void poison_element(struct mempool *pool, void *element)
{
+ if (!static_branch_unlikely(&mempool_debug_enabled))
+ return;
+
/* Skip poisoning: KASAN might save its metadata in the element. */
if (kasan_enabled())
return;
@@ -140,14 +162,6 @@ static void poison_element(struct mempool *pool, void *element)
#endif
}
}
-#else /* CONFIG_SLUB_DEBUG_ON */
-static inline void check_element(struct mempool *pool, void *element)
-{
-}
-static inline void poison_element(struct mempool *pool, void *element)
-{
-}
-#endif /* CONFIG_SLUB_DEBUG_ON */
static __always_inline bool kasan_poison_element(struct mempool *pool,
void *element)
--
2.9.4
^ permalink raw reply related
* Re: [PATCH v8 4/6] mm/memory-failure: add panic option for unrecoverable pages
From: Miaohe Lin @ 2026-06-02 7:05 UTC (permalink / raw)
To: Breno Leitao
Cc: linux-mm, linux-kernel, linux-doc, linux-kselftest,
linux-trace-kernel, kernel-team, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Shuah Khan, Naoya Horiguchi,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Jonathan Corbet, Shuah Khan, Liam R. Howlett
In-Reply-To: <20260527-ecc_panic-v8-4-9ea0cfa16bb0@debian.org>
On 2026/5/27 22:06, Breno Leitao wrote:
> Add a sysctl panic_on_unrecoverable_memory_failure (disabled by
> default) that triggers a kernel panic when memory_failure()
> encounters pages that cannot be recovered. This provides a clean
> crash with useful debug information rather than allowing silent
> data corruption or a delayed crash at an unrelated code path.
>
> Panic eligibility is intentionally narrow: only MF_MSG_KERNEL with
> result == MF_IGNORED panics. After the previous patch, MF_MSG_KERNEL
> covers PG_reserved pages and the kernel-owned pages promoted from
> get_hwpoison_page() via -ENOTRECOVERABLE (slab, page tables,
> large-kmalloc).
>
> All other action types are excluded:
>
> - MF_MSG_GET_HWPOISON and MF_MSG_KERNEL_HIGH_ORDER can be reached by
> transient refcount races with the page allocator (an in-flight buddy
> allocation has refcount 0 and is no longer on the buddy free list,
> briefly), and panicking on them would risk killing the box for what
> is actually a recoverable userspace page.
>
> - MF_MSG_UNKNOWN means identify_page_state() could not classify the
> page; that is precisely the wrong basis for a panic decision.
>
> Signed-off-by: Breno Leitao <leitao@debian.org>
> ---
> mm/memory-failure.c | 23 +++++++++++++++++++++++
> 1 file changed, 23 insertions(+)
>
> diff --git a/mm/memory-failure.c b/mm/memory-failure.c
> index 14c0a958638c..dcd53dbc6aec 100644
> --- a/mm/memory-failure.c
> +++ b/mm/memory-failure.c
> @@ -74,6 +74,8 @@ static int sysctl_memory_failure_recovery __read_mostly = 1;
>
> static int sysctl_enable_soft_offline __read_mostly = 1;
>
> +static int sysctl_panic_on_unrecoverable_mf __read_mostly;
> +
> atomic_long_t num_poisoned_pages __read_mostly = ATOMIC_LONG_INIT(0);
>
> static bool hw_memory_failure __read_mostly = false;
> @@ -155,6 +157,15 @@ static const struct ctl_table memory_failure_table[] = {
> .proc_handler = proc_dointvec_minmax,
> .extra1 = SYSCTL_ZERO,
> .extra2 = SYSCTL_ONE,
> + },
> + {
> + .procname = "panic_on_unrecoverable_memory_failure",
> + .data = &sysctl_panic_on_unrecoverable_mf,
> + .maxlen = sizeof(sysctl_panic_on_unrecoverable_mf),
> + .mode = 0644,
> + .proc_handler = proc_dointvec_minmax,
> + .extra1 = SYSCTL_ZERO,
> + .extra2 = SYSCTL_ONE,
> }
> };
>
> @@ -1255,6 +1266,15 @@ static void update_per_node_mf_stats(unsigned long pfn,
> ++mf_stats->total;
> }
>
> +static bool panic_on_unrecoverable_mf(enum mf_action_page_type type,
> + enum mf_result result)
> +{
> + if (!sysctl_panic_on_unrecoverable_mf || result != MF_IGNORED)
> + return false;
> +
> + return type == MF_MSG_KERNEL;
Would it be more straightforward to write as something like:
if (!sysctl_panic_on_unrecoverable_mf)
return false;
return (type == MF_MSG_KERNEL && result == MF_IGNORED);
Thanks.
.
^ permalink raw reply
* Re: [PATCH] nios2: remove the architecture
From: Simon Schuster @ 2026-06-02 7:13 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Miguel Ojeda, Ethan Nelson-Moore, Wolfram Sang, Peter Zijlstra,
Arnd Bergmann, Dinh Nguyen, linux-doc, devicetree, workflows,
Linux-Arch, dmaengine, linux-i2c, linux-iio, Netdev, linux-pci,
linux-pwm, linux-hardening, linux-kbuild,
linux-csky@vger.kernel.org, Jonathan Corbet, Shuah Khan,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Daniel Lezcano,
Thomas Gleixner, Alex Shi, Yanteng Si, Dongliang Mu, Hu Haowen,
Kees Cook, Oleg Nesterov, Will Deacon, Aneesh Kumar K.V (Arm),
Andrew Morton, Nicholas Piggin, Vinod Koul, Frank Li,
Dave Penkler, Andi Shyti, Jonathan Cameron, David Lechner,
Nuno Sá, Andy Shevchenko, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Lorenzo Pieralisi,
Krzysztof WilczyDski, Andreas Oetken
In-Reply-To: <ah3TN93e7lRpVihW@ashevche-desk.local>
Hello Andy,
On Mon, Jun 01, 2026 at 09:45:11PM +0300, Andy Shevchenko wrote:
> Supported implies that one gets real money for the job. Is this the case here?
Thank you for caring about NIOSII. I'm doing so as part of my
employment at Siemens Energy; if this is real enough then yes :)
Best regards
Simon
^ permalink raw reply
* Re: [PATCH] nios2: remove the architecture
From: Andy Shevchenko @ 2026-06-02 8:00 UTC (permalink / raw)
To: Simon Schuster
Cc: Miguel Ojeda, Ethan Nelson-Moore, Wolfram Sang, Peter Zijlstra,
Arnd Bergmann, Dinh Nguyen, linux-doc, devicetree, workflows,
Linux-Arch, dmaengine, linux-i2c, linux-iio, Netdev, linux-pci,
linux-pwm, linux-hardening, linux-kbuild,
linux-csky@vger.kernel.org, Jonathan Corbet, Shuah Khan,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Daniel Lezcano,
Thomas Gleixner, Alex Shi, Yanteng Si, Dongliang Mu, Hu Haowen,
Kees Cook, Oleg Nesterov, Will Deacon, Aneesh Kumar K.V (Arm),
Andrew Morton, Nicholas Piggin, Vinod Koul, Frank Li,
Dave Penkler, Andi Shyti, Jonathan Cameron, David Lechner,
Nuno Sá, Andy Shevchenko, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Lorenzo Pieralisi,
Krzysztof WilczyDski, Andreas Oetken
In-Reply-To: <20260602071311.gtvif43wcjyulphh@dev-vm-schuster>
On Tue, Jun 02, 2026 at 09:13:11AM +0200, Simon Schuster wrote:
> On Mon, Jun 01, 2026 at 09:45:11PM +0300, Andy Shevchenko wrote:
> > Supported implies that one gets real money for the job. Is this the case here?
>
> Thank you for caring about NIOSII. I'm doing so as part of my
> employment at Siemens Energy; if this is real enough then yes :)
Then Supported is exactly what a record in the MAINTAINERS needs and it
facilitates the existence of the code in kernel for as long as this kept
true.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH v4 01/13] liveupdate: change file_set->count type to u64 for type safety
From: Mike Rapoport @ 2026-06-02 8:13 UTC (permalink / raw)
To: Pasha Tatashin
Cc: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
linux-kernel, corbet, dmatlack, kexec, pratyush, skhawaja, graf
In-Reply-To: <20260530221938.115978-2-pasha.tatashin@soleen.com>
On Sat, 30 May 2026 22:19:26 +0000, Pasha Tatashin <pasha.tatashin@soleen.com> wrote:
> This improves type safety and aligns the in-memory file_set->count with
> the serialized count type. It avoids potential truncation or sign
> conversion mismatch issues.
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
--
Sincerely yours,
Mike.
^ permalink raw reply
* Re: [PATCH v4 02/13] liveupdate: avoid mixing cleanup guards with goto in luo_session_retrieve_fd
From: Mike Rapoport @ 2026-06-02 8:13 UTC (permalink / raw)
To: Pasha Tatashin
Cc: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
linux-kernel, corbet, dmatlack, kexec, pratyush, skhawaja, graf
In-Reply-To: <20260530221938.115978-3-pasha.tatashin@soleen.com>
On Sat, 30 May 2026 22:19:27 +0000, Pasha Tatashin <pasha.tatashin@soleen.com> wrote:
> diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
> index 146414933977..8d9201c25412 100644
> --- a/kernel/liveupdate/luo_session.c
> +++ b/kernel/liveupdate/luo_session.c
> @@ -291,25 +291,24 @@ static int luo_session_retrieve_fd(struct luo_session *session,
> if (argp->fd < 0)
> return argp->fd;
>
> - guard(mutex)(&session->mutex);
> - err = luo_retrieve_file(&session->file_set, argp->token, &file);
> - if (err < 0)
> - goto err_put_fd;
> + scoped_guard(mutex, &session->mutex) {
> + err = luo_retrieve_file(&session->file_set, argp->token, &file);
> + if (err < 0) {
> + put_unused_fd(argp->fd);
> + return err;
I don't like piling up error handling inside if (err) statements.
As we only need the lock only for luo_retrieve_file() I think it's better
drop the guard and use goto:
mutex_lock(&session->mutex);
err = luo_retrieve_file(&session->file_set, argp->token, &file);
mutex_unlock(&session->mutex);
if (err)
...
--
Sincerely yours,
Mike.
^ permalink raw reply
* Re: [PATCH v4 03/13] liveupdate: centralize state management into struct luo_ser
From: Mike Rapoport @ 2026-06-02 8:13 UTC (permalink / raw)
To: Pasha Tatashin
Cc: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
linux-kernel, corbet, dmatlack, kexec, pratyush, skhawaja, graf
In-Reply-To: <20260530221938.115978-4-pasha.tatashin@soleen.com>
On Sat, 30 May 2026 22:19:28 +0000, Pasha Tatashin <pasha.tatashin@soleen.com> wrote:
> diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
> index 8f5c5dd01cd0..c8dd30b41238 100644
> --- a/kernel/liveupdate/luo_flb.c
> +++ b/kernel/liveupdate/luo_flb.c
> @@ -579,53 +565,18 @@ int __init luo_flb_setup_outgoing(void *fdt_out)
> [ ... skip 18 lines ... ]
> - offset = fdt_subnode_offset(fdt_in, 0, LUO_FDT_FLB_NODE_NAME);
> - if (offset < 0) {
> - pr_err("Unable to get FLB node [%s]\n", LUO_FDT_FLB_NODE_NAME);
> -
> - return -ENOENT;
> + if (flbs_pa) {
I like
if (!flbs_pa)
return;
more
>
> diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
> index 8d9201c25412..3b760fefa7b9 100644
> --- a/kernel/liveupdate/luo_session.c
> +++ b/kernel/liveupdate/luo_session.c
> @@ -497,75 +494,34 @@ int luo_session_retrieve(const char *name, struct file **filep)
> [ ... skip 58 lines ... ]
> + 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;
> }
Ditto
--
Sincerely yours,
Mike.
^ permalink raw reply
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