* [RFC PATCH v2 1/4] liveupdate: Rename handler compatible to name
2026-10-21 15:25 [RFC PATCH v2 0/4] liveupdate: Feature header ABI and named handler Tarun Sahu
@ 2026-10-21 15:25 ` Tarun Sahu
2026-10-21 17:11 ` Pasha Tatashin
2026-10-21 15:25 ` [RFC PATCH v2 2/4] liveupdate: Select file handler by name in the preserve ioctl Tarun Sahu
` (2 subsequent siblings)
3 siblings, 1 reply; 27+ messages in thread
From: Tarun Sahu @ 2026-10-21 15:25 UTC (permalink / raw)
To: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
David Matlack, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Pasha Tatashin,
Alexander Graf, Bjorn Helgaas, baolu.lu
Cc: linux-arm-kernel, linux-arch, sagis, linux-mm, kvmarm, iommu,
linux-pci, kexec, skhawaja, kvm, linux-kernel, Tarun Sahu
File handler and File-Lifecycle-Bound (FLB) identifiers name the
registered handler or FLB rather than a monolithic compatibility version.
Rename compatible to name across LUO headers, structures, and consumers:
- struct liveupdate_file_handler and struct luo_file_ser name field
- LIVEUPDATE_HNDL_NAME_LENGTH (in include/uapi/linux/liveupdate.h) and
MEMFD_LUO_FH_NAME
- struct liveupdate_flb name field, LIVEUPDATE_FLB_NAME_LENGTH,
PCI_LUO_FLB_NAME, and LIVEUPDATE_TEST_FLB_NAME
Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
drivers/pci/liveupdate.c | 10 ++++----
include/linux/kho/abi/luo.h | 26 +++++++++----------
include/linux/kho/abi/memfd.h | 4 +--
include/linux/kho/abi/pci.h | 2 +-
include/linux/liveupdate.h | 18 +++++--------
include/uapi/linux/liveupdate.h | 3 +++
kernel/liveupdate/luo_file.c | 45 +++++++++++++++++----------------
kernel/liveupdate/luo_flb.c | 16 ++++++------
lib/tests/liveupdate.c | 22 ++++++++--------
mm/memfd_luo.c | 2 +-
10 files changed, 73 insertions(+), 75 deletions(-)
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 51695782ed42..3cf01d7d615c 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -424,7 +424,7 @@ static struct liveupdate_flb_ops pci_liveupdate_flb_ops = {
static struct liveupdate_flb pci_liveupdate_flb = {
.ops = &pci_liveupdate_flb_ops,
- .compatible = PCI_LUO_FLB_COMPATIBLE,
+ .name = PCI_LUO_FLB_NAME,
};
static void pci_liveupdate_flb_put_outgoing(void)
@@ -667,13 +667,13 @@ static struct pci_flb_incoming *pci_liveupdate_flb_get_incoming(void)
/*
* Live Update is enabled and there is incoming FLB data, but none of it
- * matches pci_liveupdate_flb.compatible.
+ * matches pci_liveupdate_flb.name.
*/
if (ret == -ENOENT)
return NULL;
/*
- * There is incoming FLB data that matches pci_liveupdate_flb.compatible
+ * There is incoming FLB data that matches pci_liveupdate_flb.name
* but retrieve failed (pci_flb_retrieve() returned an error or LUO
* failed to acquire a reference to pci_liveupdate_flb_ops.owner).
*/
@@ -998,7 +998,7 @@ EXPORT_SYMBOL_GPL(pci_liveupdate_is_incoming);
*/
int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
{
- pr_debug("Registering file handler \"%s\"\n", fh->compatible);
+ pr_debug("Registering file handler \"%s\"\n", fh->name);
return liveupdate_register_flb(fh, &pci_liveupdate_flb);
}
EXPORT_SYMBOL_GPL(pci_liveupdate_register_flb);
@@ -1009,7 +1009,7 @@ EXPORT_SYMBOL_GPL(pci_liveupdate_register_flb);
*/
void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh)
{
- pr_debug("Unregistering file handler \"%s\"\n", fh->compatible);
+ pr_debug("Unregistering file handler \"%s\"\n", fh->name);
liveupdate_unregister_flb(fh, &pci_liveupdate_flb);
}
EXPORT_SYMBOL_GPL(pci_liveupdate_unregister_flb);
diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 288076de6d4a..5fb2f46425d7 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -39,9 +39,9 @@
* 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
- * find the correct handler in the new kernel, a user-provided `token` for
- * identification, and an opaque `data` handle for the handler to use.
+ * Metadata for a single preserved file. Contains the handler `name` string
+ * to find the correct handler in the new kernel, a user-provided `token`
+ * for identification, and an opaque `data` handle for the handler to use.
*
* - struct luo_flb_header_ser:
* Header for the FLB array. Contains the total page count of the
@@ -49,9 +49,9 @@
* that follow.
*
* - struct luo_flb_ser:
- * Metadata for a single preserved global object. Contains its `name`
- * (compatible string), an opaque `data` handle, and the `count`
- * number of files depending on it.
+ * Metadata for a single preserved global object. Contains its FLB `name`
+ * string, an opaque `data` handle, and the `count` number of files
+ * depending on it.
*/
#ifndef _LINUX_KHO_ABI_LUO_H
@@ -84,18 +84,16 @@ struct luo_ser {
u64 flbs_pa;
} __packed;
-#define LIVEUPDATE_HNDL_COMPAT_LENGTH 48
-
/**
* struct luo_file_ser - Represents the serialized preserves files.
- * @compatible: File handler compatible string.
+ * @name: File handler name string.
* @data: Private data
* @token: User provided token for this file
*
* If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
*/
struct luo_file_ser {
- char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
+ char name[LIVEUPDATE_HNDL_NAME_LENGTH];
u64 data;
u64 token;
} __packed;
@@ -134,7 +132,7 @@ struct luo_session_ser {
} __packed;
/* The max size is set so it can be reliably used during in serialization */
-#define LIVEUPDATE_FLB_COMPAT_LENGTH 48
+#define LIVEUPDATE_FLB_NAME_LENGTH 48
/**
* struct luo_flb_header_ser - Header for the serialized FLB data block.
@@ -156,7 +154,7 @@ struct luo_flb_header_ser {
/**
* struct luo_flb_ser - Represents the serialized state of a single FLB object.
- * @name: The unique compatibility string of the FLB object, used to find the
+ * @name: The unique name string of the FLB object, used to find the
* corresponding &struct liveupdate_flb handler in the new kernel.
* @data: The opaque u64 handle returned by the FLB's .preserve() operation
* in the old kernel. This handle encapsulates the entire state needed
@@ -172,14 +170,14 @@ struct luo_flb_header_ser {
* If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
*/
struct luo_flb_ser {
- char name[LIVEUPDATE_FLB_COMPAT_LENGTH];
+ char name[LIVEUPDATE_FLB_NAME_LENGTH];
u64 data;
u64 count;
} __packed;
/* Kernel Live Update Test ABI */
#ifdef CONFIG_LIVEUPDATE_TEST
-#define LIVEUPDATE_TEST_FLB_COMPATIBLE(i) "liveupdate-test-flb-v" #i
+#define LIVEUPDATE_TEST_FLB_NAME(i) "liveupdate-test-flb-v" #i
#endif
#endif /* _LINUX_KHO_ABI_LUO_H */
diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h
index 08b10fea2afc..371ffd108a79 100644
--- a/include/linux/kho/abi/memfd.h
+++ b/include/linux/kho/abi/memfd.h
@@ -87,7 +87,7 @@ struct memfd_luo_ser {
struct kho_vmalloc folios;
} __packed;
-/* The compatibility string for memfd file handler */
-#define MEMFD_LUO_FH_COMPATIBLE "memfd-v2"
+/* The name string for memfd file handler */
+#define MEMFD_LUO_FH_NAME "memfd-v2"
#endif /* _LINUX_KHO_ABI_MEMFD_H */
diff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h
index 9485ed73c351..973ca5b26dbd 100644
--- a/include/linux/kho/abi/pci.h
+++ b/include/linux/kho/abi/pci.h
@@ -23,7 +23,7 @@
* incrementing the version number in the PCI_LUO_FLB_VERSION number.
*/
-#define PCI_LUO_FLB_COMPATIBLE "pci"
+#define PCI_LUO_FLB_NAME "pci"
#define PCI_LUO_FLB_VERSION 2
/**
diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
index 6051abc0612c..475da5fc22ee 100644
--- a/include/linux/liveupdate.h
+++ b/include/linux/liveupdate.h
@@ -90,10 +90,8 @@ struct liveupdate_file_ops {
/**
* struct liveupdate_file_handler - Represents a handler for a live-updatable file type.
* @ops: Callback functions
- * @compatible: The compatibility string (e.g., "memfd-v1", "vfiofd-v1")
- * that uniquely identifies the file type this handler
- * supports. This is matched against the compatible string
- * associated with individual &struct file instances.
+ * @name: Unique handler name string (e.g., "memfd-v2"), matched
+ * against &struct luo_file_ser.name in the target kernel.
*
* Modules that want to support live update for specific file types should
* register an instance of this structure. LUO uses this registration to
@@ -102,7 +100,7 @@ struct liveupdate_file_ops {
*/
struct liveupdate_file_handler {
const struct liveupdate_file_ops *ops;
- const char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
+ const char name[LIVEUPDATE_HNDL_NAME_LENGTH];
/* private: */
@@ -209,11 +207,9 @@ struct luo_flb_private {
/**
* struct liveupdate_flb - A global definition for a shared data object.
* @ops: Callback functions
- * @compatible: The compatibility string (e.g., "iommu-core-v1"
- * that uniquely identifies the FLB type this handler
- * supports. This is matched against the compatible string
- * associated with individual &struct liveupdate_flb
- * instances.
+ * @name: The FLB name string (e.g., "iommu-core-v1") that uniquely
+ * identifies this FLB type. In the target kernel, it is
+ * matched against the name stored in &struct luo_flb_ser.
*
* This struct is the "template" that a driver registers to define a shared,
* file-lifecycle-bound object. The actual runtime state (the live object,
@@ -221,7 +217,7 @@ struct luo_flb_private {
*/
struct liveupdate_flb {
const struct liveupdate_flb_ops *ops;
- const char compatible[LIVEUPDATE_FLB_COMPAT_LENGTH];
+ const char name[LIVEUPDATE_FLB_NAME_LENGTH];
/* private: */
struct luo_flb_private __private private;
diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
index 4043d4038712..509c6236720d 100644
--- a/include/uapi/linux/liveupdate.h
+++ b/include/uapi/linux/liveupdate.h
@@ -46,6 +46,9 @@
/* The maximum length of session name including null termination */
#define LIVEUPDATE_SESSION_NAME_LENGTH 64
+/* The maximum length of file handler name including null termination */
+#define LIVEUPDATE_HNDL_NAME_LENGTH 48
+
/* The /dev/liveupdate ioctl commands */
enum {
LIVEUPDATE_CMD_BASE = 0x00,
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index a39992da504f..fb81239bedf3 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -50,7 +50,7 @@
*
* 2. Freeze (Pre-Reboot): Just before the kexec, luo_file_freeze() is called.
* It iterates through all preserved files, calls their respective .freeze()
- * operation, and serializes their final metadata (compatible string, token,
+ * operation, and serializes their final metadata (handler name, token,
* and data handle) into a contiguous memory block for KHO.
*
* 3. Deserialize: After kexec, luo_file_deserialize() runs when session gets
@@ -220,7 +220,7 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
* 1. Validates that the @token is not already in use within the file_set.
* 2. Ensures the file_set's memory for files serialization is allocated
* (allocates if needed).
- * 3. Iterates through registered handlers, calling can_preserve() to find one
+ * 3. Iterates through registered handlers, calling .can_preserve() to find one
* compatible with the given @fd.
* 4. Calls the handler's .preserve() operation, which saves the file's state
* and returns an opaque private data handle.
@@ -238,7 +238,7 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
* -EBUSY if the file descriptor is already preserved by another session.
* -EBADF if the file descriptor is invalid.
* -ENOSPC if the file_set is full.
- * -ENOENT if no compatible handler is found.
+ * -ENOENT if no compatible handler is found for the file type.
* -ENOMEM on memory allocation failure.
* Other erros might be returned by .preserve().
*/
@@ -456,9 +456,10 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
* private data handle during this step.
*
* 2. Serializes Metadata: After a successful freeze, it copies the final file
- * metadata—the handler's compatible string, the user token, and the final
- * private data handle—into the pre-allocated contiguous memory buffer
- * (file_set->files) that will be handed over to the next kernel via KHO.
+ * metadata - the handler's name string, the user token, and the final
+ * private data handle - into the pre-allocated contiguous memory buffer
+ * (file_set->files) that will be handed over to the target kernel via
+ * KHO.
*
* Error Handling (Rollback):
* This function is atomic. If any handler's .freeze() operation fails, the
@@ -495,13 +496,13 @@ int luo_file_freeze(struct luo_file_set *file_set,
err = luo_file_freeze_one(file_set, luo_file);
if (err < 0) {
pr_warn("Freeze failed for token[%#0llx] handler[%s] err[%pe]\n",
- luo_file->token, luo_file->fh->compatible,
+ luo_file->token, luo_file->fh->name,
ERR_PTR(err));
goto err_unfreeze;
}
- strscpy(file_ser->compatible, luo_file->fh->compatible,
- sizeof(file_ser->compatible));
+ strscpy(file_ser->name, luo_file->fh->name,
+ sizeof(file_ser->name));
file_ser->data = luo_file->serialized_data;
file_ser->token = luo_file->token;
}
@@ -747,7 +748,7 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
down_read(&luo_register_rwlock);
list_private_for_each_entry(fh, &luo_file_handler_list, list) {
- if (!strcmp(fh->compatible, ser->compatible)) {
+ if (!strcmp(fh->name, ser->name)) {
if (try_module_get(fh->ops->owner))
handler_found = true;
break;
@@ -756,9 +757,9 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
up_read(&luo_register_rwlock);
if (!handler_found) {
- pr_warn("No registered handler for compatible '%.*s'\n",
- (int)sizeof(ser->compatible),
- ser->compatible);
+ pr_warn("No registered handler for name '%.*s'\n",
+ (int)sizeof(ser->name),
+ ser->name);
return -ENOENT;
}
@@ -789,9 +790,9 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
* in-memory linked list of 'struct luo_file' instances.
*
* For each serialized entry, it performs the following steps:
- * 1. Reads the 'compatible' string.
+ * 1. Reads the handler 'name' string.
* 2. Searches the global list of registered file handlers for one that
- * matches the compatible string.
+ * matches the handler name.
* 3. Allocates a new 'struct luo_file'.
* 4. Populates the new structure with the deserialized data (token, private
* data handle) and links it to the found handler. The 'file' pointer is
@@ -884,9 +885,9 @@ void luo_file_set_destroy(struct luo_file_set *file_set)
/**
* liveupdate_register_file_handler - Register a file handler with LUO.
* @fh: Pointer to a caller-allocated &struct liveupdate_file_handler.
- * The caller must initialize this structure, including a unique
- * 'compatible' string and a valid 'fh' callbacks. This function adds the
- * handler to the global list of supported file handlers.
+ * The caller must initialize this structure, including a unique 'name'
+ * string and valid 'fh' callbacks. This function adds the handler to the
+ * global list of supported file handlers.
*
* Context: Typically called during module initialization for file types that
* support live update preservation.
@@ -908,11 +909,11 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
}
down_write(&luo_register_rwlock);
- /* Check for duplicate compatible strings */
+ /* Check for duplicate handler names */
list_private_for_each_entry(fh_iter, &luo_file_handler_list, list) {
- if (!strcmp(fh_iter->compatible, fh->compatible)) {
- pr_err("File handler registration failed: Compatible string '%s' already registered.\n",
- fh->compatible);
+ if (!strcmp(fh_iter->name, fh->name)) {
+ pr_err("File handler registration failed: Handler name '%s' already registered.\n",
+ fh->name);
err = -EEXIST;
goto err_unlock;
}
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index cd715a7c1d99..222c6d7f821e 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -178,7 +178,7 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
return -ENODATA;
for (int i = 0; i < fh->header_ser->count; i++) {
- if (!strcmp(fh->ser[i].name, flb->compatible)) {
+ if (!strcmp(fh->ser[i].name, flb->name)) {
private->incoming.data = fh->ser[i].data;
refcount_set(&private->incoming.count, fh->ser[i].count);
found = true;
@@ -337,7 +337,7 @@ static void luo_flb_unregister_one(struct liveupdate_file_handler *fh,
if (!found) {
pr_warn("Failed to unregister FLB '%s': not found in file handler '%s'\n",
- flb->compatible, fh->compatible);
+ flb->name, fh->name);
return;
}
@@ -442,9 +442,9 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
if (luo_flb_global.count == LUO_FLB_MAX)
return -ENOSPC;
- /* Check that compatible string is unique in global list */
+ /* Check that FLB name is unique in global list */
list_private_for_each_entry(gflb, &luo_flb_global.list, private.list) {
- if (!strcmp(gflb->compatible, flb->compatible))
+ if (!strcmp(gflb->name, flb->name))
return -EEXIST;
}
@@ -596,9 +596,9 @@ void __init luo_flb_setup_incoming(u64 flbs_pa)
* preserved (i.e., its reference count is greater than zero), it writes its
* metadata into the memory region designated for Kexec Handover.
*
- * The serialized data includes the FLB's compatibility string, its opaque
- * data handle, and the final reference count. This allows the new kernel to
- * find the appropriate handler and reconstruct the FLB's state.
+ * The serialized data includes the FLB's name string, its opaque
+ * data handle, and the final reference count. This allows the target
+ * kernel to find the appropriate FLB and reconstruct its state.
*
* Context: Called from liveupdate_reboot() just before kho_finalize().
*/
@@ -614,7 +614,7 @@ void luo_flb_serialize(void)
long count = refcount_read(&private->outgoing.count);
if (count > 0) {
- strscpy(fh->ser[i].name, gflb->compatible,
+ strscpy(fh->ser[i].name, gflb->name,
sizeof(fh->ser[i].name));
fh->ser[i].data = private->outgoing.data;
fh->ser[i].count = count;
diff --git a/lib/tests/liveupdate.c b/lib/tests/liveupdate.c
index 4c08a7c6fb78..e4df09eed1f3 100644
--- a/lib/tests/liveupdate.c
+++ b/lib/tests/liveupdate.c
@@ -17,7 +17,7 @@
static const struct liveupdate_flb_ops test_flb_ops;
#define DEFINE_TEST_FLB(i) { \
.ops = &test_flb_ops, \
- .compatible = LIVEUPDATE_TEST_FLB_COMPATIBLE(i), \
+ .name = LIVEUPDATE_TEST_FLB_NAME(i), \
}
/* Number of Test FLBs to register with every file handler */
@@ -34,7 +34,7 @@ static int test_flb_preserve(struct liveupdate_flb_op_args *argp)
{
ptrdiff_t index = argp->flb - test_flbs;
- pr_info("%s: preserve was triggered\n", argp->flb->compatible);
+ pr_info("%s: preserve was triggered\n", argp->flb->name);
argp->data = TEST_FLB_MAGIC_BASE + index;
return 0;
@@ -42,7 +42,7 @@ static int test_flb_preserve(struct liveupdate_flb_op_args *argp)
static void test_flb_unpreserve(struct liveupdate_flb_op_args *argp)
{
- pr_info("%s: unpreserve was triggered\n", argp->flb->compatible);
+ pr_info("%s: unpreserve was triggered\n", argp->flb->name);
}
static int test_flb_retrieve(struct liveupdate_flb_op_args *argp)
@@ -52,11 +52,11 @@ static int test_flb_retrieve(struct liveupdate_flb_op_args *argp)
if (argp->data == expected_data) {
pr_info("%s: found flb data from the previous boot\n",
- argp->flb->compatible);
+ argp->flb->name);
argp->obj = (void *)argp->data;
} else {
pr_err("%s: ERROR - incorrect data handle: %llx, expected %llx\n",
- argp->flb->compatible, argp->data, expected_data);
+ argp->flb->name, argp->data, expected_data);
return -EINVAL;
}
@@ -69,10 +69,10 @@ static void test_flb_finish(struct liveupdate_flb_op_args *argp)
void *expected_obj = (void *)(TEST_FLB_MAGIC_BASE + index);
if (argp->obj == expected_obj) {
- pr_info("%s: finish was triggered\n", argp->flb->compatible);
+ pr_info("%s: finish was triggered\n", argp->flb->name);
} else {
pr_err("%s: ERROR - finish called with invalid object\n",
- argp->flb->compatible);
+ argp->flb->name);
}
}
@@ -103,7 +103,7 @@ static void liveupdate_test_init(void)
err = liveupdate_flb_get_incoming(flb, &obj);
if (err && err != -ENODATA && err != -ENOENT) {
pr_err("liveupdate_flb_get_incoming for %s failed: %pe\n",
- flb->compatible, ERR_PTR(err));
+ flb->name, ERR_PTR(err));
}
if (!err)
@@ -124,18 +124,18 @@ void liveupdate_test_register(struct liveupdate_file_handler *fh)
err = liveupdate_register_flb(fh, flb);
if (err) {
pr_err("Failed to register %s %pe\n",
- flb->compatible, ERR_PTR(err));
+ flb->name, ERR_PTR(err));
}
}
err = liveupdate_register_flb(fh, &test_flbs[0]);
if (!err || err != -EEXIST) {
pr_err("Failed: %s should be already registered, but got err: %pe\n",
- test_flbs[0].compatible, ERR_PTR(err));
+ test_flbs[0].name, ERR_PTR(err));
}
pr_info("Registered %d FLBs with file handler: [%s]\n",
- TEST_NFLBS, fh->compatible);
+ TEST_NFLBS, fh->name);
}
MODULE_LICENSE("GPL");
diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
index 6b68220275eb..64f8b8cb75bd 100644
--- a/mm/memfd_luo.c
+++ b/mm/memfd_luo.c
@@ -602,7 +602,7 @@ static const struct liveupdate_file_ops memfd_luo_file_ops = {
static struct liveupdate_file_handler memfd_luo_handler = {
.ops = &memfd_luo_file_ops,
- .compatible = MEMFD_LUO_FH_COMPATIBLE,
+ .name = MEMFD_LUO_FH_NAME,
};
static int __init memfd_luo_init(void)
--
2.56.0.212.g129ede20d9-goog
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 1/4] liveupdate: Rename handler compatible to name
2026-10-21 15:25 ` [RFC PATCH v2 1/4] liveupdate: Rename handler compatible to name Tarun Sahu
@ 2026-10-21 17:11 ` Pasha Tatashin
2026-10-21 18:16 ` tarunsahu
0 siblings, 1 reply; 27+ messages in thread
From: Pasha Tatashin @ 2026-10-21 17:11 UTC (permalink / raw)
To: Tarun Sahu
Cc: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
David Matlack, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
On Wed, Oct 21, 2026 at 11:25 AM Tarun Sahu <tarunsahu@google.com> wrote:
>
> File handler and File-Lifecycle-Bound (FLB) identifiers name the
Please use FLB directly, no need to spell it out.
> registered handler or FLB rather than a monolithic compatibility version.
> Rename compatible to name across LUO headers, structures, and consumers:
> - struct liveupdate_file_handler and struct luo_file_ser name field
> - LIVEUPDATE_HNDL_NAME_LENGTH (in include/uapi/linux/liveupdate.h) and
> MEMFD_LUO_FH_NAME
> - struct liveupdate_flb name field, LIVEUPDATE_FLB_NAME_LENGTH,
> PCI_LUO_FLB_NAME, and LIVEUPDATE_TEST_FLB_NAME
>
> Signed-off-by: Tarun Sahu <tarunsahu@google.com>
> ---
> drivers/pci/liveupdate.c | 10 ++++----
> include/linux/kho/abi/luo.h | 26 +++++++++----------
> include/linux/kho/abi/memfd.h | 4 +--
> include/linux/kho/abi/pci.h | 2 +-
> include/linux/liveupdate.h | 18 +++++--------
> include/uapi/linux/liveupdate.h | 3 +++
> kernel/liveupdate/luo_file.c | 45 +++++++++++++++++----------------
> kernel/liveupdate/luo_flb.c | 16 ++++++------
> lib/tests/liveupdate.c | 22 ++++++++--------
> mm/memfd_luo.c | 2 +-
> 10 files changed, 73 insertions(+), 75 deletions(-)
>
> diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
> index 51695782ed42..3cf01d7d615c 100644
> --- a/drivers/pci/liveupdate.c
> +++ b/drivers/pci/liveupdate.c
> @@ -424,7 +424,7 @@ static struct liveupdate_flb_ops pci_liveupdate_flb_ops = {
>
> static struct liveupdate_flb pci_liveupdate_flb = {
> .ops = &pci_liveupdate_flb_ops,
> - .compatible = PCI_LUO_FLB_COMPATIBLE,
> + .name = PCI_LUO_FLB_NAME,
> };
>
> static void pci_liveupdate_flb_put_outgoing(void)
> @@ -667,13 +667,13 @@ static struct pci_flb_incoming *pci_liveupdate_flb_get_incoming(void)
>
> /*
> * Live Update is enabled and there is incoming FLB data, but none of it
> - * matches pci_liveupdate_flb.compatible.
> + * matches pci_liveupdate_flb.name.
> */
> if (ret == -ENOENT)
> return NULL;
>
> /*
> - * There is incoming FLB data that matches pci_liveupdate_flb.compatible
> + * There is incoming FLB data that matches pci_liveupdate_flb.name
> * but retrieve failed (pci_flb_retrieve() returned an error or LUO
> * failed to acquire a reference to pci_liveupdate_flb_ops.owner).
> */
> @@ -998,7 +998,7 @@ EXPORT_SYMBOL_GPL(pci_liveupdate_is_incoming);
> */
> int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
> {
> - pr_debug("Registering file handler \"%s\"\n", fh->compatible);
> + pr_debug("Registering file handler \"%s\"\n", fh->name);
> return liveupdate_register_flb(fh, &pci_liveupdate_flb);
> }
> EXPORT_SYMBOL_GPL(pci_liveupdate_register_flb);
> @@ -1009,7 +1009,7 @@ EXPORT_SYMBOL_GPL(pci_liveupdate_register_flb);
> */
> void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh)
> {
> - pr_debug("Unregistering file handler \"%s\"\n", fh->compatible);
> + pr_debug("Unregistering file handler \"%s\"\n", fh->name);
> liveupdate_unregister_flb(fh, &pci_liveupdate_flb);
> }
> EXPORT_SYMBOL_GPL(pci_liveupdate_unregister_flb);
> diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
> index 288076de6d4a..5fb2f46425d7 100644
> --- a/include/linux/kho/abi/luo.h
> +++ b/include/linux/kho/abi/luo.h
> @@ -39,9 +39,9 @@
> * 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
> - * find the correct handler in the new kernel, a user-provided `token` for
> - * identification, and an opaque `data` handle for the handler to use.
> + * Metadata for a single preserved file. Contains the handler `name` string
> + * to find the correct handler in the new kernel, a user-provided `token`
> + * for identification, and an opaque `data` handle for the handler to use.
> *
> * - struct luo_flb_header_ser:
> * Header for the FLB array. Contains the total page count of the
> @@ -49,9 +49,9 @@
> * that follow.
> *
> * - struct luo_flb_ser:
> - * Metadata for a single preserved global object. Contains its `name`
> - * (compatible string), an opaque `data` handle, and the `count`
> - * number of files depending on it.
> + * Metadata for a single preserved global object. Contains its FLB `name`
> + * string, an opaque `data` handle, and the `count` number of files
> + * depending on it.
> */
>
> #ifndef _LINUX_KHO_ABI_LUO_H
> @@ -84,18 +84,16 @@ struct luo_ser {
> u64 flbs_pa;
> } __packed;
>
> -#define LIVEUPDATE_HNDL_COMPAT_LENGTH 48
> -
> /**
> * struct luo_file_ser - Represents the serialized preserves files.
> - * @compatible: File handler compatible string.
> + * @name: File handler name string.
We are renaming from cpmaptible to name; I am not sure that is
necessary. We can still use compatible for the same purpose as name.
Unless it really bothers people, but for me 'compatible' works just
the same.
> * @data: Private data
> * @token: User provided token for this file
> *
> * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
> */
> struct luo_file_ser {
> - char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
> + char name[LIVEUPDATE_HNDL_NAME_LENGTH];
> u64 data;
> u64 token;
> } __packed;
> @@ -134,7 +132,7 @@ struct luo_session_ser {
> } __packed;
Unreleated to this patch, but should be handled by this work, let's
remove all the '__packed' attributes, the are not needed, we will
explicitly fill in all the holes, with reserved fields.
>
> /* The max size is set so it can be reliably used during in serialization */
> -#define LIVEUPDATE_FLB_COMPAT_LENGTH 48
> +#define LIVEUPDATE_FLB_NAME_LENGTH 48
>
> /**
> * struct luo_flb_header_ser - Header for the serialized FLB data block.
> @@ -156,7 +154,7 @@ struct luo_flb_header_ser {
>
> /**
> * struct luo_flb_ser - Represents the serialized state of a single FLB object.
> - * @name: The unique compatibility string of the FLB object, used to find the
> + * @name: The unique name string of the FLB object, used to find the
> * corresponding &struct liveupdate_flb handler in the new kernel.
> * @data: The opaque u64 handle returned by the FLB's .preserve() operation
> * in the old kernel. This handle encapsulates the entire state needed
> @@ -172,14 +170,14 @@ struct luo_flb_header_ser {
> * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
> */
> struct luo_flb_ser {
> - char name[LIVEUPDATE_FLB_COMPAT_LENGTH];
> + char name[LIVEUPDATE_FLB_NAME_LENGTH];
> u64 data;
> u64 count;
> } __packed;
>
> /* Kernel Live Update Test ABI */
> #ifdef CONFIG_LIVEUPDATE_TEST
> -#define LIVEUPDATE_TEST_FLB_COMPATIBLE(i) "liveupdate-test-flb-v" #i
> +#define LIVEUPDATE_TEST_FLB_NAME(i) "liveupdate-test-flb-v" #i
> #endif
>
> #endif /* _LINUX_KHO_ABI_LUO_H */
> diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h
> index 08b10fea2afc..371ffd108a79 100644
> --- a/include/linux/kho/abi/memfd.h
> +++ b/include/linux/kho/abi/memfd.h
> @@ -87,7 +87,7 @@ struct memfd_luo_ser {
> struct kho_vmalloc folios;
> } __packed;
>
> -/* The compatibility string for memfd file handler */
> -#define MEMFD_LUO_FH_COMPATIBLE "memfd-v2"
> +/* The name string for memfd file handler */
> +#define MEMFD_LUO_FH_NAME "memfd-v2"
>
> #endif /* _LINUX_KHO_ABI_MEMFD_H */
> diff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h
> index 9485ed73c351..973ca5b26dbd 100644
> --- a/include/linux/kho/abi/pci.h
> +++ b/include/linux/kho/abi/pci.h
> @@ -23,7 +23,7 @@
> * incrementing the version number in the PCI_LUO_FLB_VERSION number.
> */
>
> -#define PCI_LUO_FLB_COMPATIBLE "pci"
> +#define PCI_LUO_FLB_NAME "pci"
> #define PCI_LUO_FLB_VERSION 2
>
> /**
> diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
> index 6051abc0612c..475da5fc22ee 100644
> --- a/include/linux/liveupdate.h
> +++ b/include/linux/liveupdate.h
> @@ -90,10 +90,8 @@ struct liveupdate_file_ops {
> /**
> * struct liveupdate_file_handler - Represents a handler for a live-updatable file type.
> * @ops: Callback functions
> - * @compatible: The compatibility string (e.g., "memfd-v1", "vfiofd-v1")
> - * that uniquely identifies the file type this handler
> - * supports. This is matched against the compatible string
> - * associated with individual &struct file instances.
> + * @name: Unique handler name string (e.g., "memfd-v2"), matched
> + * against &struct luo_file_ser.name in the target kernel.
> *
> * Modules that want to support live update for specific file types should
> * register an instance of this structure. LUO uses this registration to
> @@ -102,7 +100,7 @@ struct liveupdate_file_ops {
> */
> struct liveupdate_file_handler {
> const struct liveupdate_file_ops *ops;
> - const char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
> + const char name[LIVEUPDATE_HNDL_NAME_LENGTH];
>
> /* private: */
>
> @@ -209,11 +207,9 @@ struct luo_flb_private {
> /**
> * struct liveupdate_flb - A global definition for a shared data object.
> * @ops: Callback functions
> - * @compatible: The compatibility string (e.g., "iommu-core-v1"
> - * that uniquely identifies the FLB type this handler
> - * supports. This is matched against the compatible string
> - * associated with individual &struct liveupdate_flb
> - * instances.
> + * @name: The FLB name string (e.g., "iommu-core-v1") that uniquely
> + * identifies this FLB type. In the target kernel, it is
> + * matched against the name stored in &struct luo_flb_ser.
> *
> * This struct is the "template" that a driver registers to define a shared,
> * file-lifecycle-bound object. The actual runtime state (the live object,
> @@ -221,7 +217,7 @@ struct luo_flb_private {
> */
> struct liveupdate_flb {
> const struct liveupdate_flb_ops *ops;
> - const char compatible[LIVEUPDATE_FLB_COMPAT_LENGTH];
> + const char name[LIVEUPDATE_FLB_NAME_LENGTH];
>
> /* private: */
> struct luo_flb_private __private private;
> diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
> index 4043d4038712..509c6236720d 100644
> --- a/include/uapi/linux/liveupdate.h
> +++ b/include/uapi/linux/liveupdate.h
> @@ -46,6 +46,9 @@
> /* The maximum length of session name including null termination */
> #define LIVEUPDATE_SESSION_NAME_LENGTH 64
>
> +/* The maximum length of file handler name including null termination */
> +#define LIVEUPDATE_HNDL_NAME_LENGTH 48
> +
> /* The /dev/liveupdate ioctl commands */
> enum {
> LIVEUPDATE_CMD_BASE = 0x00,
> diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
> index a39992da504f..fb81239bedf3 100644
> --- a/kernel/liveupdate/luo_file.c
> +++ b/kernel/liveupdate/luo_file.c
> @@ -50,7 +50,7 @@
> *
> * 2. Freeze (Pre-Reboot): Just before the kexec, luo_file_freeze() is called.
> * It iterates through all preserved files, calls their respective .freeze()
> - * operation, and serializes their final metadata (compatible string, token,
> + * operation, and serializes their final metadata (handler name, token,
> * and data handle) into a contiguous memory block for KHO.
> *
> * 3. Deserialize: After kexec, luo_file_deserialize() runs when session gets
> @@ -220,7 +220,7 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
> * 1. Validates that the @token is not already in use within the file_set.
> * 2. Ensures the file_set's memory for files serialization is allocated
> * (allocates if needed).
> - * 3. Iterates through registered handlers, calling can_preserve() to find one
> + * 3. Iterates through registered handlers, calling .can_preserve() to find one
> * compatible with the given @fd.
> * 4. Calls the handler's .preserve() operation, which saves the file's state
> * and returns an opaque private data handle.
> @@ -238,7 +238,7 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
> * -EBUSY if the file descriptor is already preserved by another session.
> * -EBADF if the file descriptor is invalid.
> * -ENOSPC if the file_set is full.
> - * -ENOENT if no compatible handler is found.
> + * -ENOENT if no compatible handler is found for the file type.
> * -ENOMEM on memory allocation failure.
> * Other erros might be returned by .preserve().
> */
> @@ -456,9 +456,10 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
> * private data handle during this step.
> *
> * 2. Serializes Metadata: After a successful freeze, it copies the final file
> - * metadata—the handler's compatible string, the user token, and the final
> - * private data handle—into the pre-allocated contiguous memory buffer
> - * (file_set->files) that will be handed over to the next kernel via KHO.
> + * metadata - the handler's name string, the user token, and the final
> + * private data handle - into the pre-allocated contiguous memory buffer
> + * (file_set->files) that will be handed over to the target kernel via
> + * KHO.
> *
> * Error Handling (Rollback):
> * This function is atomic. If any handler's .freeze() operation fails, the
> @@ -495,13 +496,13 @@ int luo_file_freeze(struct luo_file_set *file_set,
> err = luo_file_freeze_one(file_set, luo_file);
> if (err < 0) {
> pr_warn("Freeze failed for token[%#0llx] handler[%s] err[%pe]\n",
> - luo_file->token, luo_file->fh->compatible,
> + luo_file->token, luo_file->fh->name,
> ERR_PTR(err));
> goto err_unfreeze;
> }
>
> - strscpy(file_ser->compatible, luo_file->fh->compatible,
> - sizeof(file_ser->compatible));
> + strscpy(file_ser->name, luo_file->fh->name,
> + sizeof(file_ser->name));
> file_ser->data = luo_file->serialized_data;
> file_ser->token = luo_file->token;
> }
> @@ -747,7 +748,7 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
>
> down_read(&luo_register_rwlock);
> list_private_for_each_entry(fh, &luo_file_handler_list, list) {
> - if (!strcmp(fh->compatible, ser->compatible)) {
> + if (!strcmp(fh->name, ser->name)) {
> if (try_module_get(fh->ops->owner))
> handler_found = true;
> break;
> @@ -756,9 +757,9 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
> up_read(&luo_register_rwlock);
>
> if (!handler_found) {
> - pr_warn("No registered handler for compatible '%.*s'\n",
> - (int)sizeof(ser->compatible),
> - ser->compatible);
> + pr_warn("No registered handler for name '%.*s'\n",
> + (int)sizeof(ser->name),
> + ser->name);
> return -ENOENT;
> }
>
> @@ -789,9 +790,9 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
> * in-memory linked list of 'struct luo_file' instances.
> *
> * For each serialized entry, it performs the following steps:
> - * 1. Reads the 'compatible' string.
> + * 1. Reads the handler 'name' string.
> * 2. Searches the global list of registered file handlers for one that
> - * matches the compatible string.
> + * matches the handler name.
> * 3. Allocates a new 'struct luo_file'.
> * 4. Populates the new structure with the deserialized data (token, private
> * data handle) and links it to the found handler. The 'file' pointer is
> @@ -884,9 +885,9 @@ void luo_file_set_destroy(struct luo_file_set *file_set)
> /**
> * liveupdate_register_file_handler - Register a file handler with LUO.
> * @fh: Pointer to a caller-allocated &struct liveupdate_file_handler.
> - * The caller must initialize this structure, including a unique
> - * 'compatible' string and a valid 'fh' callbacks. This function adds the
> - * handler to the global list of supported file handlers.
> + * The caller must initialize this structure, including a unique 'name'
> + * string and valid 'fh' callbacks. This function adds the handler to the
> + * global list of supported file handlers.
> *
> * Context: Typically called during module initialization for file types that
> * support live update preservation.
> @@ -908,11 +909,11 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
> }
>
> down_write(&luo_register_rwlock);
> - /* Check for duplicate compatible strings */
> + /* Check for duplicate handler names */
> list_private_for_each_entry(fh_iter, &luo_file_handler_list, list) {
> - if (!strcmp(fh_iter->compatible, fh->compatible)) {
> - pr_err("File handler registration failed: Compatible string '%s' already registered.\n",
> - fh->compatible);
> + if (!strcmp(fh_iter->name, fh->name)) {
> + pr_err("File handler registration failed: Handler name '%s' already registered.\n",
> + fh->name);
> err = -EEXIST;
> goto err_unlock;
> }
> diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
> index cd715a7c1d99..222c6d7f821e 100644
> --- a/kernel/liveupdate/luo_flb.c
> +++ b/kernel/liveupdate/luo_flb.c
> @@ -178,7 +178,7 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
> return -ENODATA;
>
> for (int i = 0; i < fh->header_ser->count; i++) {
> - if (!strcmp(fh->ser[i].name, flb->compatible)) {
> + if (!strcmp(fh->ser[i].name, flb->name)) {
> private->incoming.data = fh->ser[i].data;
> refcount_set(&private->incoming.count, fh->ser[i].count);
> found = true;
> @@ -337,7 +337,7 @@ static void luo_flb_unregister_one(struct liveupdate_file_handler *fh,
>
> if (!found) {
> pr_warn("Failed to unregister FLB '%s': not found in file handler '%s'\n",
> - flb->compatible, fh->compatible);
> + flb->name, fh->name);
> return;
> }
>
> @@ -442,9 +442,9 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
> if (luo_flb_global.count == LUO_FLB_MAX)
> return -ENOSPC;
>
> - /* Check that compatible string is unique in global list */
> + /* Check that FLB name is unique in global list */
> list_private_for_each_entry(gflb, &luo_flb_global.list, private.list) {
> - if (!strcmp(gflb->compatible, flb->compatible))
> + if (!strcmp(gflb->name, flb->name))
> return -EEXIST;
> }
>
> @@ -596,9 +596,9 @@ void __init luo_flb_setup_incoming(u64 flbs_pa)
> * preserved (i.e., its reference count is greater than zero), it writes its
> * metadata into the memory region designated for Kexec Handover.
> *
> - * The serialized data includes the FLB's compatibility string, its opaque
> - * data handle, and the final reference count. This allows the new kernel to
> - * find the appropriate handler and reconstruct the FLB's state.
> + * The serialized data includes the FLB's name string, its opaque
> + * data handle, and the final reference count. This allows the target
> + * kernel to find the appropriate FLB and reconstruct its state.
> *
> * Context: Called from liveupdate_reboot() just before kho_finalize().
> */
> @@ -614,7 +614,7 @@ void luo_flb_serialize(void)
> long count = refcount_read(&private->outgoing.count);
>
> if (count > 0) {
> - strscpy(fh->ser[i].name, gflb->compatible,
> + strscpy(fh->ser[i].name, gflb->name,
> sizeof(fh->ser[i].name));
> fh->ser[i].data = private->outgoing.data;
> fh->ser[i].count = count;
> diff --git a/lib/tests/liveupdate.c b/lib/tests/liveupdate.c
> index 4c08a7c6fb78..e4df09eed1f3 100644
> --- a/lib/tests/liveupdate.c
> +++ b/lib/tests/liveupdate.c
> @@ -17,7 +17,7 @@
> static const struct liveupdate_flb_ops test_flb_ops;
> #define DEFINE_TEST_FLB(i) { \
> .ops = &test_flb_ops, \
> - .compatible = LIVEUPDATE_TEST_FLB_COMPATIBLE(i), \
> + .name = LIVEUPDATE_TEST_FLB_NAME(i), \
> }
>
> /* Number of Test FLBs to register with every file handler */
> @@ -34,7 +34,7 @@ static int test_flb_preserve(struct liveupdate_flb_op_args *argp)
> {
> ptrdiff_t index = argp->flb - test_flbs;
>
> - pr_info("%s: preserve was triggered\n", argp->flb->compatible);
> + pr_info("%s: preserve was triggered\n", argp->flb->name);
> argp->data = TEST_FLB_MAGIC_BASE + index;
>
> return 0;
> @@ -42,7 +42,7 @@ static int test_flb_preserve(struct liveupdate_flb_op_args *argp)
>
> static void test_flb_unpreserve(struct liveupdate_flb_op_args *argp)
> {
> - pr_info("%s: unpreserve was triggered\n", argp->flb->compatible);
> + pr_info("%s: unpreserve was triggered\n", argp->flb->name);
> }
>
> static int test_flb_retrieve(struct liveupdate_flb_op_args *argp)
> @@ -52,11 +52,11 @@ static int test_flb_retrieve(struct liveupdate_flb_op_args *argp)
>
> if (argp->data == expected_data) {
> pr_info("%s: found flb data from the previous boot\n",
> - argp->flb->compatible);
> + argp->flb->name);
> argp->obj = (void *)argp->data;
> } else {
> pr_err("%s: ERROR - incorrect data handle: %llx, expected %llx\n",
> - argp->flb->compatible, argp->data, expected_data);
> + argp->flb->name, argp->data, expected_data);
> return -EINVAL;
> }
>
> @@ -69,10 +69,10 @@ static void test_flb_finish(struct liveupdate_flb_op_args *argp)
> void *expected_obj = (void *)(TEST_FLB_MAGIC_BASE + index);
>
> if (argp->obj == expected_obj) {
> - pr_info("%s: finish was triggered\n", argp->flb->compatible);
> + pr_info("%s: finish was triggered\n", argp->flb->name);
> } else {
> pr_err("%s: ERROR - finish called with invalid object\n",
> - argp->flb->compatible);
> + argp->flb->name);
> }
> }
>
> @@ -103,7 +103,7 @@ static void liveupdate_test_init(void)
> err = liveupdate_flb_get_incoming(flb, &obj);
> if (err && err != -ENODATA && err != -ENOENT) {
> pr_err("liveupdate_flb_get_incoming for %s failed: %pe\n",
> - flb->compatible, ERR_PTR(err));
> + flb->name, ERR_PTR(err));
> }
>
> if (!err)
> @@ -124,18 +124,18 @@ void liveupdate_test_register(struct liveupdate_file_handler *fh)
> err = liveupdate_register_flb(fh, flb);
> if (err) {
> pr_err("Failed to register %s %pe\n",
> - flb->compatible, ERR_PTR(err));
> + flb->name, ERR_PTR(err));
> }
> }
>
> err = liveupdate_register_flb(fh, &test_flbs[0]);
> if (!err || err != -EEXIST) {
> pr_err("Failed: %s should be already registered, but got err: %pe\n",
> - test_flbs[0].compatible, ERR_PTR(err));
> + test_flbs[0].name, ERR_PTR(err));
> }
>
> pr_info("Registered %d FLBs with file handler: [%s]\n",
> - TEST_NFLBS, fh->compatible);
> + TEST_NFLBS, fh->name);
> }
>
> MODULE_LICENSE("GPL");
> diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
> index 6b68220275eb..64f8b8cb75bd 100644
> --- a/mm/memfd_luo.c
> +++ b/mm/memfd_luo.c
> @@ -602,7 +602,7 @@ static const struct liveupdate_file_ops memfd_luo_file_ops = {
>
> static struct liveupdate_file_handler memfd_luo_handler = {
> .ops = &memfd_luo_file_ops,
> - .compatible = MEMFD_LUO_FH_COMPATIBLE,
> + .name = MEMFD_LUO_FH_NAME,
> };
>
> static int __init memfd_luo_init(void)
> --
> 2.56.0.212.g129ede20d9-goog
>
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 1/4] liveupdate: Rename handler compatible to name
2026-10-21 17:11 ` Pasha Tatashin
@ 2026-10-21 18:16 ` tarunsahu
2026-10-21 19:11 ` Pasha Tatashin
0 siblings, 1 reply; 27+ messages in thread
From: tarunsahu @ 2026-10-21 18:16 UTC (permalink / raw)
To: Pasha Tatashin
Cc: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
David Matlack, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
Pasha Tatashin <pasha.tatashin@soleen.com> writes:
> On Wed, Oct 21, 2026 at 11:25 AM Tarun Sahu <tarunsahu@google.com> wrote:
>>
>> File handler and File-Lifecycle-Bound (FLB) identifiers name the
>
> Please use FLB directly, no need to spell it out.
>
>> registered handler or FLB rather than a monolithic compatibility version.
>> Rename compatible to name across LUO headers, structures, and consumers:
>> - struct liveupdate_file_handler and struct luo_file_ser name field
>> - LIVEUPDATE_HNDL_NAME_LENGTH (in include/uapi/linux/liveupdate.h) and
>> MEMFD_LUO_FH_NAME
>> - struct liveupdate_flb name field, LIVEUPDATE_FLB_NAME_LENGTH,
>> PCI_LUO_FLB_NAME, and LIVEUPDATE_TEST_FLB_NAME
>>
>> Signed-off-by: Tarun Sahu <tarunsahu@google.com>
>> ---
>> drivers/pci/liveupdate.c | 10 ++++----
>> include/linux/kho/abi/luo.h | 26 +++++++++----------
>> include/linux/kho/abi/memfd.h | 4 +--
>> include/linux/kho/abi/pci.h | 2 +-
>> include/linux/liveupdate.h | 18 +++++--------
>> include/uapi/linux/liveupdate.h | 3 +++
>> kernel/liveupdate/luo_file.c | 45 +++++++++++++++++----------------
>> kernel/liveupdate/luo_flb.c | 16 ++++++------
>> lib/tests/liveupdate.c | 22 ++++++++--------
>> mm/memfd_luo.c | 2 +-
>> 10 files changed, 73 insertions(+), 75 deletions(-)
>>
>> diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
>> index 51695782ed42..3cf01d7d615c 100644
>> --- a/drivers/pci/liveupdate.c
>> +++ b/drivers/pci/liveupdate.c
>> @@ -424,7 +424,7 @@ static struct liveupdate_flb_ops pci_liveupdate_flb_ops = {
>>
>> static struct liveupdate_flb pci_liveupdate_flb = {
>> .ops = &pci_liveupdate_flb_ops,
>> - .compatible = PCI_LUO_FLB_COMPATIBLE,
>> + .name = PCI_LUO_FLB_NAME,
>> };
>>
>> static void pci_liveupdate_flb_put_outgoing(void)
>> @@ -667,13 +667,13 @@ static struct pci_flb_incoming *pci_liveupdate_flb_get_incoming(void)
>>
>> /*
>> * Live Update is enabled and there is incoming FLB data, but none of it
>> - * matches pci_liveupdate_flb.compatible.
>> + * matches pci_liveupdate_flb.name.
>> */
>> if (ret == -ENOENT)
>> return NULL;
>>
>> /*
>> - * There is incoming FLB data that matches pci_liveupdate_flb.compatible
>> + * There is incoming FLB data that matches pci_liveupdate_flb.name
>> * but retrieve failed (pci_flb_retrieve() returned an error or LUO
>> * failed to acquire a reference to pci_liveupdate_flb_ops.owner).
>> */
>> @@ -998,7 +998,7 @@ EXPORT_SYMBOL_GPL(pci_liveupdate_is_incoming);
>> */
>> int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
>> {
>> - pr_debug("Registering file handler \"%s\"\n", fh->compatible);
>> + pr_debug("Registering file handler \"%s\"\n", fh->name);
>> return liveupdate_register_flb(fh, &pci_liveupdate_flb);
>> }
>> EXPORT_SYMBOL_GPL(pci_liveupdate_register_flb);
>> @@ -1009,7 +1009,7 @@ EXPORT_SYMBOL_GPL(pci_liveupdate_register_flb);
>> */
>> void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh)
>> {
>> - pr_debug("Unregistering file handler \"%s\"\n", fh->compatible);
>> + pr_debug("Unregistering file handler \"%s\"\n", fh->name);
>> liveupdate_unregister_flb(fh, &pci_liveupdate_flb);
>> }
>> EXPORT_SYMBOL_GPL(pci_liveupdate_unregister_flb);
>> diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
>> index 288076de6d4a..5fb2f46425d7 100644
>> --- a/include/linux/kho/abi/luo.h
>> +++ b/include/linux/kho/abi/luo.h
>> @@ -39,9 +39,9 @@
>> * 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
>> - * find the correct handler in the new kernel, a user-provided `token` for
>> - * identification, and an opaque `data` handle for the handler to use.
>> + * Metadata for a single preserved file. Contains the handler `name` string
>> + * to find the correct handler in the new kernel, a user-provided `token`
>> + * for identification, and an opaque `data` handle for the handler to use.
>> *
>> * - struct luo_flb_header_ser:
>> * Header for the FLB array. Contains the total page count of the
>> @@ -49,9 +49,9 @@
>> * that follow.
>> *
>> * - struct luo_flb_ser:
>> - * Metadata for a single preserved global object. Contains its `name`
>> - * (compatible string), an opaque `data` handle, and the `count`
>> - * number of files depending on it.
>> + * Metadata for a single preserved global object. Contains its FLB `name`
>> + * string, an opaque `data` handle, and the `count` number of files
>> + * depending on it.
>> */
>>
>> #ifndef _LINUX_KHO_ABI_LUO_H
>> @@ -84,18 +84,16 @@ struct luo_ser {
>> u64 flbs_pa;
>> } __packed;
>>
>> -#define LIVEUPDATE_HNDL_COMPAT_LENGTH 48
>> -
>> /**
>> * struct luo_file_ser - Represents the serialized preserves files.
>> - * @compatible: File handler compatible string.
>> + * @name: File handler name string.
>
> We are renaming from cpmaptible to name; I am not sure that is
> necessary. We can still use compatible for the same purpose as name.
> Unless it really bothers people, but for me 'compatible' works just
> the same.
Yes it is necessary, For example if we have two handler for a single
subsystem. We should refer it by handle this or handler that, not like
handler with compatible string this and handler with compatible string
that.
It ease the conversation and people will understand it quickly with
name.
~Tarun
>
>> * @data: Private data
>> * @token: User provided token for this file
>> *
>> * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
>> */
>> struct luo_file_ser {
>> - char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
>> + char name[LIVEUPDATE_HNDL_NAME_LENGTH];
>> u64 data;
>> u64 token;
>> } __packed;
>> @@ -134,7 +132,7 @@ struct luo_session_ser {
>> } __packed;
>
> Unreleated to this patch, but should be handled by this work, let's
> remove all the '__packed' attributes, the are not needed, we will
> explicitly fill in all the holes, with reserved fields.
>
>>
>> /* The max size is set so it can be reliably used during in serialization */
>> -#define LIVEUPDATE_FLB_COMPAT_LENGTH 48
>> +#define LIVEUPDATE_FLB_NAME_LENGTH 48
>>
>> /**
>> * struct luo_flb_header_ser - Header for the serialized FLB data block.
>> @@ -156,7 +154,7 @@ struct luo_flb_header_ser {
>>
>> /**
>> * struct luo_flb_ser - Represents the serialized state of a single FLB object.
>> - * @name: The unique compatibility string of the FLB object, used to find the
>> + * @name: The unique name string of the FLB object, used to find the
>> * corresponding &struct liveupdate_flb handler in the new kernel.
>> * @data: The opaque u64 handle returned by the FLB's .preserve() operation
>> * in the old kernel. This handle encapsulates the entire state needed
>> @@ -172,14 +170,14 @@ struct luo_flb_header_ser {
>> * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
>> */
>> struct luo_flb_ser {
>> - char name[LIVEUPDATE_FLB_COMPAT_LENGTH];
>> + char name[LIVEUPDATE_FLB_NAME_LENGTH];
>> u64 data;
>> u64 count;
>> } __packed;
>>
>> /* Kernel Live Update Test ABI */
>> #ifdef CONFIG_LIVEUPDATE_TEST
>> -#define LIVEUPDATE_TEST_FLB_COMPATIBLE(i) "liveupdate-test-flb-v" #i
>> +#define LIVEUPDATE_TEST_FLB_NAME(i) "liveupdate-test-flb-v" #i
>> #endif
>>
>> #endif /* _LINUX_KHO_ABI_LUO_H */
>> diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h
>> index 08b10fea2afc..371ffd108a79 100644
>> --- a/include/linux/kho/abi/memfd.h
>> +++ b/include/linux/kho/abi/memfd.h
>> @@ -87,7 +87,7 @@ struct memfd_luo_ser {
>> struct kho_vmalloc folios;
>> } __packed;
>>
>> -/* The compatibility string for memfd file handler */
>> -#define MEMFD_LUO_FH_COMPATIBLE "memfd-v2"
>> +/* The name string for memfd file handler */
>> +#define MEMFD_LUO_FH_NAME "memfd-v2"
>>
>> #endif /* _LINUX_KHO_ABI_MEMFD_H */
>> diff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h
>> index 9485ed73c351..973ca5b26dbd 100644
>> --- a/include/linux/kho/abi/pci.h
>> +++ b/include/linux/kho/abi/pci.h
>> @@ -23,7 +23,7 @@
>> * incrementing the version number in the PCI_LUO_FLB_VERSION number.
>> */
>>
>> -#define PCI_LUO_FLB_COMPATIBLE "pci"
>> +#define PCI_LUO_FLB_NAME "pci"
>> #define PCI_LUO_FLB_VERSION 2
>>
>> /**
>> diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
>> index 6051abc0612c..475da5fc22ee 100644
>> --- a/include/linux/liveupdate.h
>> +++ b/include/linux/liveupdate.h
>> @@ -90,10 +90,8 @@ struct liveupdate_file_ops {
>> /**
>> * struct liveupdate_file_handler - Represents a handler for a live-updatable file type.
>> * @ops: Callback functions
>> - * @compatible: The compatibility string (e.g., "memfd-v1", "vfiofd-v1")
>> - * that uniquely identifies the file type this handler
>> - * supports. This is matched against the compatible string
>> - * associated with individual &struct file instances.
>> + * @name: Unique handler name string (e.g., "memfd-v2"), matched
>> + * against &struct luo_file_ser.name in the target kernel.
>> *
>> * Modules that want to support live update for specific file types should
>> * register an instance of this structure. LUO uses this registration to
>> @@ -102,7 +100,7 @@ struct liveupdate_file_ops {
>> */
>> struct liveupdate_file_handler {
>> const struct liveupdate_file_ops *ops;
>> - const char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
>> + const char name[LIVEUPDATE_HNDL_NAME_LENGTH];
>>
>> /* private: */
>>
>> @@ -209,11 +207,9 @@ struct luo_flb_private {
>> /**
>> * struct liveupdate_flb - A global definition for a shared data object.
>> * @ops: Callback functions
>> - * @compatible: The compatibility string (e.g., "iommu-core-v1"
>> - * that uniquely identifies the FLB type this handler
>> - * supports. This is matched against the compatible string
>> - * associated with individual &struct liveupdate_flb
>> - * instances.
>> + * @name: The FLB name string (e.g., "iommu-core-v1") that uniquely
>> + * identifies this FLB type. In the target kernel, it is
>> + * matched against the name stored in &struct luo_flb_ser.
>> *
>> * This struct is the "template" that a driver registers to define a shared,
>> * file-lifecycle-bound object. The actual runtime state (the live object,
>> @@ -221,7 +217,7 @@ struct luo_flb_private {
>> */
>> struct liveupdate_flb {
>> const struct liveupdate_flb_ops *ops;
>> - const char compatible[LIVEUPDATE_FLB_COMPAT_LENGTH];
>> + const char name[LIVEUPDATE_FLB_NAME_LENGTH];
>>
>> /* private: */
>> struct luo_flb_private __private private;
>> diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
>> index 4043d4038712..509c6236720d 100644
>> --- a/include/uapi/linux/liveupdate.h
>> +++ b/include/uapi/linux/liveupdate.h
>> @@ -46,6 +46,9 @@
>> /* The maximum length of session name including null termination */
>> #define LIVEUPDATE_SESSION_NAME_LENGTH 64
>>
>> +/* The maximum length of file handler name including null termination */
>> +#define LIVEUPDATE_HNDL_NAME_LENGTH 48
>> +
>> /* The /dev/liveupdate ioctl commands */
>> enum {
>> LIVEUPDATE_CMD_BASE = 0x00,
>> diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
>> index a39992da504f..fb81239bedf3 100644
>> --- a/kernel/liveupdate/luo_file.c
>> +++ b/kernel/liveupdate/luo_file.c
>> @@ -50,7 +50,7 @@
>> *
>> * 2. Freeze (Pre-Reboot): Just before the kexec, luo_file_freeze() is called.
>> * It iterates through all preserved files, calls their respective .freeze()
>> - * operation, and serializes their final metadata (compatible string, token,
>> + * operation, and serializes their final metadata (handler name, token,
>> * and data handle) into a contiguous memory block for KHO.
>> *
>> * 3. Deserialize: After kexec, luo_file_deserialize() runs when session gets
>> @@ -220,7 +220,7 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
>> * 1. Validates that the @token is not already in use within the file_set.
>> * 2. Ensures the file_set's memory for files serialization is allocated
>> * (allocates if needed).
>> - * 3. Iterates through registered handlers, calling can_preserve() to find one
>> + * 3. Iterates through registered handlers, calling .can_preserve() to find one
>> * compatible with the given @fd.
>> * 4. Calls the handler's .preserve() operation, which saves the file's state
>> * and returns an opaque private data handle.
>> @@ -238,7 +238,7 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
>> * -EBUSY if the file descriptor is already preserved by another session.
>> * -EBADF if the file descriptor is invalid.
>> * -ENOSPC if the file_set is full.
>> - * -ENOENT if no compatible handler is found.
>> + * -ENOENT if no compatible handler is found for the file type.
>> * -ENOMEM on memory allocation failure.
>> * Other erros might be returned by .preserve().
>> */
>> @@ -456,9 +456,10 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
>> * private data handle during this step.
>> *
>> * 2. Serializes Metadata: After a successful freeze, it copies the final file
>> - * metadata—the handler's compatible string, the user token, and the final
>> - * private data handle—into the pre-allocated contiguous memory buffer
>> - * (file_set->files) that will be handed over to the next kernel via KHO.
>> + * metadata - the handler's name string, the user token, and the final
>> + * private data handle - into the pre-allocated contiguous memory buffer
>> + * (file_set->files) that will be handed over to the target kernel via
>> + * KHO.
>> *
>> * Error Handling (Rollback):
>> * This function is atomic. If any handler's .freeze() operation fails, the
>> @@ -495,13 +496,13 @@ int luo_file_freeze(struct luo_file_set *file_set,
>> err = luo_file_freeze_one(file_set, luo_file);
>> if (err < 0) {
>> pr_warn("Freeze failed for token[%#0llx] handler[%s] err[%pe]\n",
>> - luo_file->token, luo_file->fh->compatible,
>> + luo_file->token, luo_file->fh->name,
>> ERR_PTR(err));
>> goto err_unfreeze;
>> }
>>
>> - strscpy(file_ser->compatible, luo_file->fh->compatible,
>> - sizeof(file_ser->compatible));
>> + strscpy(file_ser->name, luo_file->fh->name,
>> + sizeof(file_ser->name));
>> file_ser->data = luo_file->serialized_data;
>> file_ser->token = luo_file->token;
>> }
>> @@ -747,7 +748,7 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
>>
>> down_read(&luo_register_rwlock);
>> list_private_for_each_entry(fh, &luo_file_handler_list, list) {
>> - if (!strcmp(fh->compatible, ser->compatible)) {
>> + if (!strcmp(fh->name, ser->name)) {
>> if (try_module_get(fh->ops->owner))
>> handler_found = true;
>> break;
>> @@ -756,9 +757,9 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
>> up_read(&luo_register_rwlock);
>>
>> if (!handler_found) {
>> - pr_warn("No registered handler for compatible '%.*s'\n",
>> - (int)sizeof(ser->compatible),
>> - ser->compatible);
>> + pr_warn("No registered handler for name '%.*s'\n",
>> + (int)sizeof(ser->name),
>> + ser->name);
>> return -ENOENT;
>> }
>>
>> @@ -789,9 +790,9 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
>> * in-memory linked list of 'struct luo_file' instances.
>> *
>> * For each serialized entry, it performs the following steps:
>> - * 1. Reads the 'compatible' string.
>> + * 1. Reads the handler 'name' string.
>> * 2. Searches the global list of registered file handlers for one that
>> - * matches the compatible string.
>> + * matches the handler name.
>> * 3. Allocates a new 'struct luo_file'.
>> * 4. Populates the new structure with the deserialized data (token, private
>> * data handle) and links it to the found handler. The 'file' pointer is
>> @@ -884,9 +885,9 @@ void luo_file_set_destroy(struct luo_file_set *file_set)
>> /**
>> * liveupdate_register_file_handler - Register a file handler with LUO.
>> * @fh: Pointer to a caller-allocated &struct liveupdate_file_handler.
>> - * The caller must initialize this structure, including a unique
>> - * 'compatible' string and a valid 'fh' callbacks. This function adds the
>> - * handler to the global list of supported file handlers.
>> + * The caller must initialize this structure, including a unique 'name'
>> + * string and valid 'fh' callbacks. This function adds the handler to the
>> + * global list of supported file handlers.
>> *
>> * Context: Typically called during module initialization for file types that
>> * support live update preservation.
>> @@ -908,11 +909,11 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
>> }
>>
>> down_write(&luo_register_rwlock);
>> - /* Check for duplicate compatible strings */
>> + /* Check for duplicate handler names */
>> list_private_for_each_entry(fh_iter, &luo_file_handler_list, list) {
>> - if (!strcmp(fh_iter->compatible, fh->compatible)) {
>> - pr_err("File handler registration failed: Compatible string '%s' already registered.\n",
>> - fh->compatible);
>> + if (!strcmp(fh_iter->name, fh->name)) {
>> + pr_err("File handler registration failed: Handler name '%s' already registered.\n",
>> + fh->name);
>> err = -EEXIST;
>> goto err_unlock;
>> }
>> diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
>> index cd715a7c1d99..222c6d7f821e 100644
>> --- a/kernel/liveupdate/luo_flb.c
>> +++ b/kernel/liveupdate/luo_flb.c
>> @@ -178,7 +178,7 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
>> return -ENODATA;
>>
>> for (int i = 0; i < fh->header_ser->count; i++) {
>> - if (!strcmp(fh->ser[i].name, flb->compatible)) {
>> + if (!strcmp(fh->ser[i].name, flb->name)) {
>> private->incoming.data = fh->ser[i].data;
>> refcount_set(&private->incoming.count, fh->ser[i].count);
>> found = true;
>> @@ -337,7 +337,7 @@ static void luo_flb_unregister_one(struct liveupdate_file_handler *fh,
>>
>> if (!found) {
>> pr_warn("Failed to unregister FLB '%s': not found in file handler '%s'\n",
>> - flb->compatible, fh->compatible);
>> + flb->name, fh->name);
>> return;
>> }
>>
>> @@ -442,9 +442,9 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
>> if (luo_flb_global.count == LUO_FLB_MAX)
>> return -ENOSPC;
>>
>> - /* Check that compatible string is unique in global list */
>> + /* Check that FLB name is unique in global list */
>> list_private_for_each_entry(gflb, &luo_flb_global.list, private.list) {
>> - if (!strcmp(gflb->compatible, flb->compatible))
>> + if (!strcmp(gflb->name, flb->name))
>> return -EEXIST;
>> }
>>
>> @@ -596,9 +596,9 @@ void __init luo_flb_setup_incoming(u64 flbs_pa)
>> * preserved (i.e., its reference count is greater than zero), it writes its
>> * metadata into the memory region designated for Kexec Handover.
>> *
>> - * The serialized data includes the FLB's compatibility string, its opaque
>> - * data handle, and the final reference count. This allows the new kernel to
>> - * find the appropriate handler and reconstruct the FLB's state.
>> + * The serialized data includes the FLB's name string, its opaque
>> + * data handle, and the final reference count. This allows the target
>> + * kernel to find the appropriate FLB and reconstruct its state.
>> *
>> * Context: Called from liveupdate_reboot() just before kho_finalize().
>> */
>> @@ -614,7 +614,7 @@ void luo_flb_serialize(void)
>> long count = refcount_read(&private->outgoing.count);
>>
>> if (count > 0) {
>> - strscpy(fh->ser[i].name, gflb->compatible,
>> + strscpy(fh->ser[i].name, gflb->name,
>> sizeof(fh->ser[i].name));
>> fh->ser[i].data = private->outgoing.data;
>> fh->ser[i].count = count;
>> diff --git a/lib/tests/liveupdate.c b/lib/tests/liveupdate.c
>> index 4c08a7c6fb78..e4df09eed1f3 100644
>> --- a/lib/tests/liveupdate.c
>> +++ b/lib/tests/liveupdate.c
>> @@ -17,7 +17,7 @@
>> static const struct liveupdate_flb_ops test_flb_ops;
>> #define DEFINE_TEST_FLB(i) { \
>> .ops = &test_flb_ops, \
>> - .compatible = LIVEUPDATE_TEST_FLB_COMPATIBLE(i), \
>> + .name = LIVEUPDATE_TEST_FLB_NAME(i), \
>> }
>>
>> /* Number of Test FLBs to register with every file handler */
>> @@ -34,7 +34,7 @@ static int test_flb_preserve(struct liveupdate_flb_op_args *argp)
>> {
>> ptrdiff_t index = argp->flb - test_flbs;
>>
>> - pr_info("%s: preserve was triggered\n", argp->flb->compatible);
>> + pr_info("%s: preserve was triggered\n", argp->flb->name);
>> argp->data = TEST_FLB_MAGIC_BASE + index;
>>
>> return 0;
>> @@ -42,7 +42,7 @@ static int test_flb_preserve(struct liveupdate_flb_op_args *argp)
>>
>> static void test_flb_unpreserve(struct liveupdate_flb_op_args *argp)
>> {
>> - pr_info("%s: unpreserve was triggered\n", argp->flb->compatible);
>> + pr_info("%s: unpreserve was triggered\n", argp->flb->name);
>> }
>>
>> static int test_flb_retrieve(struct liveupdate_flb_op_args *argp)
>> @@ -52,11 +52,11 @@ static int test_flb_retrieve(struct liveupdate_flb_op_args *argp)
>>
>> if (argp->data == expected_data) {
>> pr_info("%s: found flb data from the previous boot\n",
>> - argp->flb->compatible);
>> + argp->flb->name);
>> argp->obj = (void *)argp->data;
>> } else {
>> pr_err("%s: ERROR - incorrect data handle: %llx, expected %llx\n",
>> - argp->flb->compatible, argp->data, expected_data);
>> + argp->flb->name, argp->data, expected_data);
>> return -EINVAL;
>> }
>>
>> @@ -69,10 +69,10 @@ static void test_flb_finish(struct liveupdate_flb_op_args *argp)
>> void *expected_obj = (void *)(TEST_FLB_MAGIC_BASE + index);
>>
>> if (argp->obj == expected_obj) {
>> - pr_info("%s: finish was triggered\n", argp->flb->compatible);
>> + pr_info("%s: finish was triggered\n", argp->flb->name);
>> } else {
>> pr_err("%s: ERROR - finish called with invalid object\n",
>> - argp->flb->compatible);
>> + argp->flb->name);
>> }
>> }
>>
>> @@ -103,7 +103,7 @@ static void liveupdate_test_init(void)
>> err = liveupdate_flb_get_incoming(flb, &obj);
>> if (err && err != -ENODATA && err != -ENOENT) {
>> pr_err("liveupdate_flb_get_incoming for %s failed: %pe\n",
>> - flb->compatible, ERR_PTR(err));
>> + flb->name, ERR_PTR(err));
>> }
>>
>> if (!err)
>> @@ -124,18 +124,18 @@ void liveupdate_test_register(struct liveupdate_file_handler *fh)
>> err = liveupdate_register_flb(fh, flb);
>> if (err) {
>> pr_err("Failed to register %s %pe\n",
>> - flb->compatible, ERR_PTR(err));
>> + flb->name, ERR_PTR(err));
>> }
>> }
>>
>> err = liveupdate_register_flb(fh, &test_flbs[0]);
>> if (!err || err != -EEXIST) {
>> pr_err("Failed: %s should be already registered, but got err: %pe\n",
>> - test_flbs[0].compatible, ERR_PTR(err));
>> + test_flbs[0].name, ERR_PTR(err));
>> }
>>
>> pr_info("Registered %d FLBs with file handler: [%s]\n",
>> - TEST_NFLBS, fh->compatible);
>> + TEST_NFLBS, fh->name);
>> }
>>
>> MODULE_LICENSE("GPL");
>> diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
>> index 6b68220275eb..64f8b8cb75bd 100644
>> --- a/mm/memfd_luo.c
>> +++ b/mm/memfd_luo.c
>> @@ -602,7 +602,7 @@ static const struct liveupdate_file_ops memfd_luo_file_ops = {
>>
>> static struct liveupdate_file_handler memfd_luo_handler = {
>> .ops = &memfd_luo_file_ops,
>> - .compatible = MEMFD_LUO_FH_COMPATIBLE,
>> + .name = MEMFD_LUO_FH_NAME,
>> };
>>
>> static int __init memfd_luo_init(void)
>> --
>> 2.56.0.212.g129ede20d9-goog
>>
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 1/4] liveupdate: Rename handler compatible to name
2026-10-21 18:16 ` tarunsahu
@ 2026-10-21 19:11 ` Pasha Tatashin
2026-10-21 19:35 ` tarunsahu
0 siblings, 1 reply; 27+ messages in thread
From: Pasha Tatashin @ 2026-10-21 19:11 UTC (permalink / raw)
To: tarunsahu
Cc: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
David Matlack, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
On Wed, Oct 21, 2026 at 2:16 PM <tarunsahu@google.com> wrote:
>
> Pasha Tatashin <pasha.tatashin@soleen.com> writes:
>
> > On Wed, Oct 21, 2026 at 11:25 AM Tarun Sahu <tarunsahu@google.com> wrote:
> >>
> >> File handler and File-Lifecycle-Bound (FLB) identifiers name the
> >
> > Please use FLB directly, no need to spell it out.
> >
> >> registered handler or FLB rather than a monolithic compatibility version.
> >> Rename compatible to name across LUO headers, structures, and consumers:
> >> - struct liveupdate_file_handler and struct luo_file_ser name field
> >> - LIVEUPDATE_HNDL_NAME_LENGTH (in include/uapi/linux/liveupdate.h) and
> >> MEMFD_LUO_FH_NAME
> >> - struct liveupdate_flb name field, LIVEUPDATE_FLB_NAME_LENGTH,
> >> PCI_LUO_FLB_NAME, and LIVEUPDATE_TEST_FLB_NAME
> >>
> >> Signed-off-by: Tarun Sahu <tarunsahu@google.com>
> >> ---
> >> drivers/pci/liveupdate.c | 10 ++++----
> >> include/linux/kho/abi/luo.h | 26 +++++++++----------
> >> include/linux/kho/abi/memfd.h | 4 +--
> >> include/linux/kho/abi/pci.h | 2 +-
> >> include/linux/liveupdate.h | 18 +++++--------
> >> include/uapi/linux/liveupdate.h | 3 +++
> >> kernel/liveupdate/luo_file.c | 45 +++++++++++++++++----------------
> >> kernel/liveupdate/luo_flb.c | 16 ++++++------
> >> lib/tests/liveupdate.c | 22 ++++++++--------
> >> mm/memfd_luo.c | 2 +-
> >> 10 files changed, 73 insertions(+), 75 deletions(-)
> >>
> >> diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
> >> index 51695782ed42..3cf01d7d615c 100644
> >> --- a/drivers/pci/liveupdate.c
> >> +++ b/drivers/pci/liveupdate.c
> >> @@ -424,7 +424,7 @@ static struct liveupdate_flb_ops pci_liveupdate_flb_ops = {
> >>
> >> static struct liveupdate_flb pci_liveupdate_flb = {
> >> .ops = &pci_liveupdate_flb_ops,
> >> - .compatible = PCI_LUO_FLB_COMPATIBLE,
> >> + .name = PCI_LUO_FLB_NAME,
> >> };
> >>
> >> static void pci_liveupdate_flb_put_outgoing(void)
> >> @@ -667,13 +667,13 @@ static struct pci_flb_incoming *pci_liveupdate_flb_get_incoming(void)
> >>
> >> /*
> >> * Live Update is enabled and there is incoming FLB data, but none of it
> >> - * matches pci_liveupdate_flb.compatible.
> >> + * matches pci_liveupdate_flb.name.
> >> */
> >> if (ret == -ENOENT)
> >> return NULL;
> >>
> >> /*
> >> - * There is incoming FLB data that matches pci_liveupdate_flb.compatible
> >> + * There is incoming FLB data that matches pci_liveupdate_flb.name
> >> * but retrieve failed (pci_flb_retrieve() returned an error or LUO
> >> * failed to acquire a reference to pci_liveupdate_flb_ops.owner).
> >> */
> >> @@ -998,7 +998,7 @@ EXPORT_SYMBOL_GPL(pci_liveupdate_is_incoming);
> >> */
> >> int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
> >> {
> >> - pr_debug("Registering file handler \"%s\"\n", fh->compatible);
> >> + pr_debug("Registering file handler \"%s\"\n", fh->name);
> >> return liveupdate_register_flb(fh, &pci_liveupdate_flb);
> >> }
> >> EXPORT_SYMBOL_GPL(pci_liveupdate_register_flb);
> >> @@ -1009,7 +1009,7 @@ EXPORT_SYMBOL_GPL(pci_liveupdate_register_flb);
> >> */
> >> void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh)
> >> {
> >> - pr_debug("Unregistering file handler \"%s\"\n", fh->compatible);
> >> + pr_debug("Unregistering file handler \"%s\"\n", fh->name);
> >> liveupdate_unregister_flb(fh, &pci_liveupdate_flb);
> >> }
> >> EXPORT_SYMBOL_GPL(pci_liveupdate_unregister_flb);
> >> diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
> >> index 288076de6d4a..5fb2f46425d7 100644
> >> --- a/include/linux/kho/abi/luo.h
> >> +++ b/include/linux/kho/abi/luo.h
> >> @@ -39,9 +39,9 @@
> >> * 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
> >> - * find the correct handler in the new kernel, a user-provided `token` for
> >> - * identification, and an opaque `data` handle for the handler to use.
> >> + * Metadata for a single preserved file. Contains the handler `name` string
> >> + * to find the correct handler in the new kernel, a user-provided `token`
> >> + * for identification, and an opaque `data` handle for the handler to use.
> >> *
> >> * - struct luo_flb_header_ser:
> >> * Header for the FLB array. Contains the total page count of the
> >> @@ -49,9 +49,9 @@
> >> * that follow.
> >> *
> >> * - struct luo_flb_ser:
> >> - * Metadata for a single preserved global object. Contains its `name`
> >> - * (compatible string), an opaque `data` handle, and the `count`
> >> - * number of files depending on it.
> >> + * Metadata for a single preserved global object. Contains its FLB `name`
> >> + * string, an opaque `data` handle, and the `count` number of files
> >> + * depending on it.
> >> */
> >>
> >> #ifndef _LINUX_KHO_ABI_LUO_H
> >> @@ -84,18 +84,16 @@ struct luo_ser {
> >> u64 flbs_pa;
> >> } __packed;
> >>
> >> -#define LIVEUPDATE_HNDL_COMPAT_LENGTH 48
> >> -
> >> /**
> >> * struct luo_file_ser - Represents the serialized preserves files.
> >> - * @compatible: File handler compatible string.
> >> + * @name: File handler name string.
> >
> > We are renaming from cpmaptible to name; I am not sure that is
> > necessary. We can still use compatible for the same purpose as name.
> > Unless it really bothers people, but for me 'compatible' works just
> > the same.
>
> Yes it is necessary, For example if we have two handler for a single
> subsystem. We should refer it by handle this or handler that, not like
> handler with compatible string this and handler with compatible string
> that.
I still feel like compatability makes more sense to me. Take an example:
We have two file handlers for memfd (i.e., FH_OLD and FH_NEW): one has
compatibility = "memfd", and the other has compatibility =
"memfd_next_generation". The user chooses one of them to preserve the
memfd data, and the compatibility string is saved alongside the FD.
When the next kernel scans through the registered file handlers, it
must find exactly one that is compatible with the preserved data.
Pasha
>
> It ease the conversation and people will understand it quickly with
> name.
>
> ~Tarun
>
> >
> >> * @data: Private data
> >> * @token: User provided token for this file
> >> *
> >> * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
> >> */
> >> struct luo_file_ser {
> >> - char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
> >> + char name[LIVEUPDATE_HNDL_NAME_LENGTH];
> >> u64 data;
> >> u64 token;
> >> } __packed;
> >> @@ -134,7 +132,7 @@ struct luo_session_ser {
> >> } __packed;
> >
> > Unreleated to this patch, but should be handled by this work, let's
> > remove all the '__packed' attributes, the are not needed, we will
> > explicitly fill in all the holes, with reserved fields.
> >
> >>
> >> /* The max size is set so it can be reliably used during in serialization */
> >> -#define LIVEUPDATE_FLB_COMPAT_LENGTH 48
> >> +#define LIVEUPDATE_FLB_NAME_LENGTH 48
> >>
> >> /**
> >> * struct luo_flb_header_ser - Header for the serialized FLB data block.
> >> @@ -156,7 +154,7 @@ struct luo_flb_header_ser {
> >>
> >> /**
> >> * struct luo_flb_ser - Represents the serialized state of a single FLB object.
> >> - * @name: The unique compatibility string of the FLB object, used to find the
> >> + * @name: The unique name string of the FLB object, used to find the
> >> * corresponding &struct liveupdate_flb handler in the new kernel.
> >> * @data: The opaque u64 handle returned by the FLB's .preserve() operation
> >> * in the old kernel. This handle encapsulates the entire state needed
> >> @@ -172,14 +170,14 @@ struct luo_flb_header_ser {
> >> * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
> >> */
> >> struct luo_flb_ser {
> >> - char name[LIVEUPDATE_FLB_COMPAT_LENGTH];
> >> + char name[LIVEUPDATE_FLB_NAME_LENGTH];
> >> u64 data;
> >> u64 count;
> >> } __packed;
> >>
> >> /* Kernel Live Update Test ABI */
> >> #ifdef CONFIG_LIVEUPDATE_TEST
> >> -#define LIVEUPDATE_TEST_FLB_COMPATIBLE(i) "liveupdate-test-flb-v" #i
> >> +#define LIVEUPDATE_TEST_FLB_NAME(i) "liveupdate-test-flb-v" #i
> >> #endif
> >>
> >> #endif /* _LINUX_KHO_ABI_LUO_H */
> >> diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h
> >> index 08b10fea2afc..371ffd108a79 100644
> >> --- a/include/linux/kho/abi/memfd.h
> >> +++ b/include/linux/kho/abi/memfd.h
> >> @@ -87,7 +87,7 @@ struct memfd_luo_ser {
> >> struct kho_vmalloc folios;
> >> } __packed;
> >>
> >> -/* The compatibility string for memfd file handler */
> >> -#define MEMFD_LUO_FH_COMPATIBLE "memfd-v2"
> >> +/* The name string for memfd file handler */
> >> +#define MEMFD_LUO_FH_NAME "memfd-v2"
> >>
> >> #endif /* _LINUX_KHO_ABI_MEMFD_H */
> >> diff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h
> >> index 9485ed73c351..973ca5b26dbd 100644
> >> --- a/include/linux/kho/abi/pci.h
> >> +++ b/include/linux/kho/abi/pci.h
> >> @@ -23,7 +23,7 @@
> >> * incrementing the version number in the PCI_LUO_FLB_VERSION number.
> >> */
> >>
> >> -#define PCI_LUO_FLB_COMPATIBLE "pci"
> >> +#define PCI_LUO_FLB_NAME "pci"
> >> #define PCI_LUO_FLB_VERSION 2
> >>
> >> /**
> >> diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
> >> index 6051abc0612c..475da5fc22ee 100644
> >> --- a/include/linux/liveupdate.h
> >> +++ b/include/linux/liveupdate.h
> >> @@ -90,10 +90,8 @@ struct liveupdate_file_ops {
> >> /**
> >> * struct liveupdate_file_handler - Represents a handler for a live-updatable file type.
> >> * @ops: Callback functions
> >> - * @compatible: The compatibility string (e.g., "memfd-v1", "vfiofd-v1")
> >> - * that uniquely identifies the file type this handler
> >> - * supports. This is matched against the compatible string
> >> - * associated with individual &struct file instances.
> >> + * @name: Unique handler name string (e.g., "memfd-v2"), matched
> >> + * against &struct luo_file_ser.name in the target kernel.
> >> *
> >> * Modules that want to support live update for specific file types should
> >> * register an instance of this structure. LUO uses this registration to
> >> @@ -102,7 +100,7 @@ struct liveupdate_file_ops {
> >> */
> >> struct liveupdate_file_handler {
> >> const struct liveupdate_file_ops *ops;
> >> - const char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
> >> + const char name[LIVEUPDATE_HNDL_NAME_LENGTH];
> >>
> >> /* private: */
> >>
> >> @@ -209,11 +207,9 @@ struct luo_flb_private {
> >> /**
> >> * struct liveupdate_flb - A global definition for a shared data object.
> >> * @ops: Callback functions
> >> - * @compatible: The compatibility string (e.g., "iommu-core-v1"
> >> - * that uniquely identifies the FLB type this handler
> >> - * supports. This is matched against the compatible string
> >> - * associated with individual &struct liveupdate_flb
> >> - * instances.
> >> + * @name: The FLB name string (e.g., "iommu-core-v1") that uniquely
> >> + * identifies this FLB type. In the target kernel, it is
> >> + * matched against the name stored in &struct luo_flb_ser.
> >> *
> >> * This struct is the "template" that a driver registers to define a shared,
> >> * file-lifecycle-bound object. The actual runtime state (the live object,
> >> @@ -221,7 +217,7 @@ struct luo_flb_private {
> >> */
> >> struct liveupdate_flb {
> >> const struct liveupdate_flb_ops *ops;
> >> - const char compatible[LIVEUPDATE_FLB_COMPAT_LENGTH];
> >> + const char name[LIVEUPDATE_FLB_NAME_LENGTH];
> >>
> >> /* private: */
> >> struct luo_flb_private __private private;
> >> diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
> >> index 4043d4038712..509c6236720d 100644
> >> --- a/include/uapi/linux/liveupdate.h
> >> +++ b/include/uapi/linux/liveupdate.h
> >> @@ -46,6 +46,9 @@
> >> /* The maximum length of session name including null termination */
> >> #define LIVEUPDATE_SESSION_NAME_LENGTH 64
> >>
> >> +/* The maximum length of file handler name including null termination */
> >> +#define LIVEUPDATE_HNDL_NAME_LENGTH 48
> >> +
> >> /* The /dev/liveupdate ioctl commands */
> >> enum {
> >> LIVEUPDATE_CMD_BASE = 0x00,
> >> diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
> >> index a39992da504f..fb81239bedf3 100644
> >> --- a/kernel/liveupdate/luo_file.c
> >> +++ b/kernel/liveupdate/luo_file.c
> >> @@ -50,7 +50,7 @@
> >> *
> >> * 2. Freeze (Pre-Reboot): Just before the kexec, luo_file_freeze() is called.
> >> * It iterates through all preserved files, calls their respective .freeze()
> >> - * operation, and serializes their final metadata (compatible string, token,
> >> + * operation, and serializes their final metadata (handler name, token,
> >> * and data handle) into a contiguous memory block for KHO.
> >> *
> >> * 3. Deserialize: After kexec, luo_file_deserialize() runs when session gets
> >> @@ -220,7 +220,7 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
> >> * 1. Validates that the @token is not already in use within the file_set.
> >> * 2. Ensures the file_set's memory for files serialization is allocated
> >> * (allocates if needed).
> >> - * 3. Iterates through registered handlers, calling can_preserve() to find one
> >> + * 3. Iterates through registered handlers, calling .can_preserve() to find one
> >> * compatible with the given @fd.
> >> * 4. Calls the handler's .preserve() operation, which saves the file's state
> >> * and returns an opaque private data handle.
> >> @@ -238,7 +238,7 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
> >> * -EBUSY if the file descriptor is already preserved by another session.
> >> * -EBADF if the file descriptor is invalid.
> >> * -ENOSPC if the file_set is full.
> >> - * -ENOENT if no compatible handler is found.
> >> + * -ENOENT if no compatible handler is found for the file type.
> >> * -ENOMEM on memory allocation failure.
> >> * Other erros might be returned by .preserve().
> >> */
> >> @@ -456,9 +456,10 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
> >> * private data handle during this step.
> >> *
> >> * 2. Serializes Metadata: After a successful freeze, it copies the final file
> >> - * metadata—the handler's compatible string, the user token, and the final
> >> - * private data handle—into the pre-allocated contiguous memory buffer
> >> - * (file_set->files) that will be handed over to the next kernel via KHO.
> >> + * metadata - the handler's name string, the user token, and the final
> >> + * private data handle - into the pre-allocated contiguous memory buffer
> >> + * (file_set->files) that will be handed over to the target kernel via
> >> + * KHO.
> >> *
> >> * Error Handling (Rollback):
> >> * This function is atomic. If any handler's .freeze() operation fails, the
> >> @@ -495,13 +496,13 @@ int luo_file_freeze(struct luo_file_set *file_set,
> >> err = luo_file_freeze_one(file_set, luo_file);
> >> if (err < 0) {
> >> pr_warn("Freeze failed for token[%#0llx] handler[%s] err[%pe]\n",
> >> - luo_file->token, luo_file->fh->compatible,
> >> + luo_file->token, luo_file->fh->name,
> >> ERR_PTR(err));
> >> goto err_unfreeze;
> >> }
> >>
> >> - strscpy(file_ser->compatible, luo_file->fh->compatible,
> >> - sizeof(file_ser->compatible));
> >> + strscpy(file_ser->name, luo_file->fh->name,
> >> + sizeof(file_ser->name));
> >> file_ser->data = luo_file->serialized_data;
> >> file_ser->token = luo_file->token;
> >> }
> >> @@ -747,7 +748,7 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
> >>
> >> down_read(&luo_register_rwlock);
> >> list_private_for_each_entry(fh, &luo_file_handler_list, list) {
> >> - if (!strcmp(fh->compatible, ser->compatible)) {
> >> + if (!strcmp(fh->name, ser->name)) {
> >> if (try_module_get(fh->ops->owner))
> >> handler_found = true;
> >> break;
> >> @@ -756,9 +757,9 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
> >> up_read(&luo_register_rwlock);
> >>
> >> if (!handler_found) {
> >> - pr_warn("No registered handler for compatible '%.*s'\n",
> >> - (int)sizeof(ser->compatible),
> >> - ser->compatible);
> >> + pr_warn("No registered handler for name '%.*s'\n",
> >> + (int)sizeof(ser->name),
> >> + ser->name);
> >> return -ENOENT;
> >> }
> >>
> >> @@ -789,9 +790,9 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
> >> * in-memory linked list of 'struct luo_file' instances.
> >> *
> >> * For each serialized entry, it performs the following steps:
> >> - * 1. Reads the 'compatible' string.
> >> + * 1. Reads the handler 'name' string.
> >> * 2. Searches the global list of registered file handlers for one that
> >> - * matches the compatible string.
> >> + * matches the handler name.
> >> * 3. Allocates a new 'struct luo_file'.
> >> * 4. Populates the new structure with the deserialized data (token, private
> >> * data handle) and links it to the found handler. The 'file' pointer is
> >> @@ -884,9 +885,9 @@ void luo_file_set_destroy(struct luo_file_set *file_set)
> >> /**
> >> * liveupdate_register_file_handler - Register a file handler with LUO.
> >> * @fh: Pointer to a caller-allocated &struct liveupdate_file_handler.
> >> - * The caller must initialize this structure, including a unique
> >> - * 'compatible' string and a valid 'fh' callbacks. This function adds the
> >> - * handler to the global list of supported file handlers.
> >> + * The caller must initialize this structure, including a unique 'name'
> >> + * string and valid 'fh' callbacks. This function adds the handler to the
> >> + * global list of supported file handlers.
> >> *
> >> * Context: Typically called during module initialization for file types that
> >> * support live update preservation.
> >> @@ -908,11 +909,11 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
> >> }
> >>
> >> down_write(&luo_register_rwlock);
> >> - /* Check for duplicate compatible strings */
> >> + /* Check for duplicate handler names */
> >> list_private_for_each_entry(fh_iter, &luo_file_handler_list, list) {
> >> - if (!strcmp(fh_iter->compatible, fh->compatible)) {
> >> - pr_err("File handler registration failed: Compatible string '%s' already registered.\n",
> >> - fh->compatible);
> >> + if (!strcmp(fh_iter->name, fh->name)) {
> >> + pr_err("File handler registration failed: Handler name '%s' already registered.\n",
> >> + fh->name);
> >> err = -EEXIST;
> >> goto err_unlock;
> >> }
> >> diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
> >> index cd715a7c1d99..222c6d7f821e 100644
> >> --- a/kernel/liveupdate/luo_flb.c
> >> +++ b/kernel/liveupdate/luo_flb.c
> >> @@ -178,7 +178,7 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
> >> return -ENODATA;
> >>
> >> for (int i = 0; i < fh->header_ser->count; i++) {
> >> - if (!strcmp(fh->ser[i].name, flb->compatible)) {
> >> + if (!strcmp(fh->ser[i].name, flb->name)) {
> >> private->incoming.data = fh->ser[i].data;
> >> refcount_set(&private->incoming.count, fh->ser[i].count);
> >> found = true;
> >> @@ -337,7 +337,7 @@ static void luo_flb_unregister_one(struct liveupdate_file_handler *fh,
> >>
> >> if (!found) {
> >> pr_warn("Failed to unregister FLB '%s': not found in file handler '%s'\n",
> >> - flb->compatible, fh->compatible);
> >> + flb->name, fh->name);
> >> return;
> >> }
> >>
> >> @@ -442,9 +442,9 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
> >> if (luo_flb_global.count == LUO_FLB_MAX)
> >> return -ENOSPC;
> >>
> >> - /* Check that compatible string is unique in global list */
> >> + /* Check that FLB name is unique in global list */
> >> list_private_for_each_entry(gflb, &luo_flb_global.list, private.list) {
> >> - if (!strcmp(gflb->compatible, flb->compatible))
> >> + if (!strcmp(gflb->name, flb->name))
> >> return -EEXIST;
> >> }
> >>
> >> @@ -596,9 +596,9 @@ void __init luo_flb_setup_incoming(u64 flbs_pa)
> >> * preserved (i.e., its reference count is greater than zero), it writes its
> >> * metadata into the memory region designated for Kexec Handover.
> >> *
> >> - * The serialized data includes the FLB's compatibility string, its opaque
> >> - * data handle, and the final reference count. This allows the new kernel to
> >> - * find the appropriate handler and reconstruct the FLB's state.
> >> + * The serialized data includes the FLB's name string, its opaque
> >> + * data handle, and the final reference count. This allows the target
> >> + * kernel to find the appropriate FLB and reconstruct its state.
> >> *
> >> * Context: Called from liveupdate_reboot() just before kho_finalize().
> >> */
> >> @@ -614,7 +614,7 @@ void luo_flb_serialize(void)
> >> long count = refcount_read(&private->outgoing.count);
> >>
> >> if (count > 0) {
> >> - strscpy(fh->ser[i].name, gflb->compatible,
> >> + strscpy(fh->ser[i].name, gflb->name,
> >> sizeof(fh->ser[i].name));
> >> fh->ser[i].data = private->outgoing.data;
> >> fh->ser[i].count = count;
> >> diff --git a/lib/tests/liveupdate.c b/lib/tests/liveupdate.c
> >> index 4c08a7c6fb78..e4df09eed1f3 100644
> >> --- a/lib/tests/liveupdate.c
> >> +++ b/lib/tests/liveupdate.c
> >> @@ -17,7 +17,7 @@
> >> static const struct liveupdate_flb_ops test_flb_ops;
> >> #define DEFINE_TEST_FLB(i) { \
> >> .ops = &test_flb_ops, \
> >> - .compatible = LIVEUPDATE_TEST_FLB_COMPATIBLE(i), \
> >> + .name = LIVEUPDATE_TEST_FLB_NAME(i), \
> >> }
> >>
> >> /* Number of Test FLBs to register with every file handler */
> >> @@ -34,7 +34,7 @@ static int test_flb_preserve(struct liveupdate_flb_op_args *argp)
> >> {
> >> ptrdiff_t index = argp->flb - test_flbs;
> >>
> >> - pr_info("%s: preserve was triggered\n", argp->flb->compatible);
> >> + pr_info("%s: preserve was triggered\n", argp->flb->name);
> >> argp->data = TEST_FLB_MAGIC_BASE + index;
> >>
> >> return 0;
> >> @@ -42,7 +42,7 @@ static int test_flb_preserve(struct liveupdate_flb_op_args *argp)
> >>
> >> static void test_flb_unpreserve(struct liveupdate_flb_op_args *argp)
> >> {
> >> - pr_info("%s: unpreserve was triggered\n", argp->flb->compatible);
> >> + pr_info("%s: unpreserve was triggered\n", argp->flb->name);
> >> }
> >>
> >> static int test_flb_retrieve(struct liveupdate_flb_op_args *argp)
> >> @@ -52,11 +52,11 @@ static int test_flb_retrieve(struct liveupdate_flb_op_args *argp)
> >>
> >> if (argp->data == expected_data) {
> >> pr_info("%s: found flb data from the previous boot\n",
> >> - argp->flb->compatible);
> >> + argp->flb->name);
> >> argp->obj = (void *)argp->data;
> >> } else {
> >> pr_err("%s: ERROR - incorrect data handle: %llx, expected %llx\n",
> >> - argp->flb->compatible, argp->data, expected_data);
> >> + argp->flb->name, argp->data, expected_data);
> >> return -EINVAL;
> >> }
> >>
> >> @@ -69,10 +69,10 @@ static void test_flb_finish(struct liveupdate_flb_op_args *argp)
> >> void *expected_obj = (void *)(TEST_FLB_MAGIC_BASE + index);
> >>
> >> if (argp->obj == expected_obj) {
> >> - pr_info("%s: finish was triggered\n", argp->flb->compatible);
> >> + pr_info("%s: finish was triggered\n", argp->flb->name);
> >> } else {
> >> pr_err("%s: ERROR - finish called with invalid object\n",
> >> - argp->flb->compatible);
> >> + argp->flb->name);
> >> }
> >> }
> >>
> >> @@ -103,7 +103,7 @@ static void liveupdate_test_init(void)
> >> err = liveupdate_flb_get_incoming(flb, &obj);
> >> if (err && err != -ENODATA && err != -ENOENT) {
> >> pr_err("liveupdate_flb_get_incoming for %s failed: %pe\n",
> >> - flb->compatible, ERR_PTR(err));
> >> + flb->name, ERR_PTR(err));
> >> }
> >>
> >> if (!err)
> >> @@ -124,18 +124,18 @@ void liveupdate_test_register(struct liveupdate_file_handler *fh)
> >> err = liveupdate_register_flb(fh, flb);
> >> if (err) {
> >> pr_err("Failed to register %s %pe\n",
> >> - flb->compatible, ERR_PTR(err));
> >> + flb->name, ERR_PTR(err));
> >> }
> >> }
> >>
> >> err = liveupdate_register_flb(fh, &test_flbs[0]);
> >> if (!err || err != -EEXIST) {
> >> pr_err("Failed: %s should be already registered, but got err: %pe\n",
> >> - test_flbs[0].compatible, ERR_PTR(err));
> >> + test_flbs[0].name, ERR_PTR(err));
> >> }
> >>
> >> pr_info("Registered %d FLBs with file handler: [%s]\n",
> >> - TEST_NFLBS, fh->compatible);
> >> + TEST_NFLBS, fh->name);
> >> }
> >>
> >> MODULE_LICENSE("GPL");
> >> diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
> >> index 6b68220275eb..64f8b8cb75bd 100644
> >> --- a/mm/memfd_luo.c
> >> +++ b/mm/memfd_luo.c
> >> @@ -602,7 +602,7 @@ static const struct liveupdate_file_ops memfd_luo_file_ops = {
> >>
> >> static struct liveupdate_file_handler memfd_luo_handler = {
> >> .ops = &memfd_luo_file_ops,
> >> - .compatible = MEMFD_LUO_FH_COMPATIBLE,
> >> + .name = MEMFD_LUO_FH_NAME,
> >> };
> >>
> >> static int __init memfd_luo_init(void)
> >> --
> >> 2.56.0.212.g129ede20d9-goog
> >>
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 1/4] liveupdate: Rename handler compatible to name
2026-10-21 19:11 ` Pasha Tatashin
@ 2026-10-21 19:35 ` tarunsahu
0 siblings, 0 replies; 27+ messages in thread
From: tarunsahu @ 2026-10-21 19:35 UTC (permalink / raw)
To: Pasha Tatashin
Cc: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
David Matlack, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
Pasha Tatashin <pasha.tatashin@soleen.com> writes:
> On Wed, Oct 21, 2026 at 2:16 PM <tarunsahu@google.com> wrote:
>>
>> Pasha Tatashin <pasha.tatashin@soleen.com> writes:
>>
>> > On Wed, Oct 21, 2026 at 11:25 AM Tarun Sahu <tarunsahu@google.com> wrote:
>> >>
>> >> File handler and File-Lifecycle-Bound (FLB) identifiers name the
>> >
>> > Please use FLB directly, no need to spell it out.
>> >
>> >> registered handler or FLB rather than a monolithic compatibility version.
>> >> Rename compatible to name across LUO headers, structures, and consumers:
>> >> - struct liveupdate_file_handler and struct luo_file_ser name field
>> >> - LIVEUPDATE_HNDL_NAME_LENGTH (in include/uapi/linux/liveupdate.h) and
>> >> MEMFD_LUO_FH_NAME
>> >> - struct liveupdate_flb name field, LIVEUPDATE_FLB_NAME_LENGTH,
>> >> PCI_LUO_FLB_NAME, and LIVEUPDATE_TEST_FLB_NAME
>> >>
>> >> Signed-off-by: Tarun Sahu <tarunsahu@google.com>
>> >> ---
>> >> drivers/pci/liveupdate.c | 10 ++++----
>> >> include/linux/kho/abi/luo.h | 26 +++++++++----------
>> >> include/linux/kho/abi/memfd.h | 4 +--
>> >> include/linux/kho/abi/pci.h | 2 +-
>> >> include/linux/liveupdate.h | 18 +++++--------
>> >> include/uapi/linux/liveupdate.h | 3 +++
>> >> kernel/liveupdate/luo_file.c | 45 +++++++++++++++++----------------
>> >> kernel/liveupdate/luo_flb.c | 16 ++++++------
>> >> lib/tests/liveupdate.c | 22 ++++++++--------
>> >> mm/memfd_luo.c | 2 +-
>> >> 10 files changed, 73 insertions(+), 75 deletions(-)
>> >>
>> >> diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
>> >> index 51695782ed42..3cf01d7d615c 100644
>> >> --- a/drivers/pci/liveupdate.c
>> >> +++ b/drivers/pci/liveupdate.c
>> >> @@ -424,7 +424,7 @@ static struct liveupdate_flb_ops pci_liveupdate_flb_ops = {
>> >>
>> >> static struct liveupdate_flb pci_liveupdate_flb = {
>> >> .ops = &pci_liveupdate_flb_ops,
>> >> - .compatible = PCI_LUO_FLB_COMPATIBLE,
>> >> + .name = PCI_LUO_FLB_NAME,
>> >> };
>> >>
>> >> static void pci_liveupdate_flb_put_outgoing(void)
>> >> @@ -667,13 +667,13 @@ static struct pci_flb_incoming *pci_liveupdate_flb_get_incoming(void)
>> >>
>> >> /*
>> >> * Live Update is enabled and there is incoming FLB data, but none of it
>> >> - * matches pci_liveupdate_flb.compatible.
>> >> + * matches pci_liveupdate_flb.name.
>> >> */
>> >> if (ret == -ENOENT)
>> >> return NULL;
>> >>
>> >> /*
>> >> - * There is incoming FLB data that matches pci_liveupdate_flb.compatible
>> >> + * There is incoming FLB data that matches pci_liveupdate_flb.name
>> >> * but retrieve failed (pci_flb_retrieve() returned an error or LUO
>> >> * failed to acquire a reference to pci_liveupdate_flb_ops.owner).
>> >> */
>> >> @@ -998,7 +998,7 @@ EXPORT_SYMBOL_GPL(pci_liveupdate_is_incoming);
>> >> */
>> >> int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
>> >> {
>> >> - pr_debug("Registering file handler \"%s\"\n", fh->compatible);
>> >> + pr_debug("Registering file handler \"%s\"\n", fh->name);
>> >> return liveupdate_register_flb(fh, &pci_liveupdate_flb);
>> >> }
>> >> EXPORT_SYMBOL_GPL(pci_liveupdate_register_flb);
>> >> @@ -1009,7 +1009,7 @@ EXPORT_SYMBOL_GPL(pci_liveupdate_register_flb);
>> >> */
>> >> void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh)
>> >> {
>> >> - pr_debug("Unregistering file handler \"%s\"\n", fh->compatible);
>> >> + pr_debug("Unregistering file handler \"%s\"\n", fh->name);
>> >> liveupdate_unregister_flb(fh, &pci_liveupdate_flb);
>> >> }
>> >> EXPORT_SYMBOL_GPL(pci_liveupdate_unregister_flb);
>> >> diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
>> >> index 288076de6d4a..5fb2f46425d7 100644
>> >> --- a/include/linux/kho/abi/luo.h
>> >> +++ b/include/linux/kho/abi/luo.h
>> >> @@ -39,9 +39,9 @@
>> >> * 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
>> >> - * find the correct handler in the new kernel, a user-provided `token` for
>> >> - * identification, and an opaque `data` handle for the handler to use.
>> >> + * Metadata for a single preserved file. Contains the handler `name` string
>> >> + * to find the correct handler in the new kernel, a user-provided `token`
>> >> + * for identification, and an opaque `data` handle for the handler to use.
>> >> *
>> >> * - struct luo_flb_header_ser:
>> >> * Header for the FLB array. Contains the total page count of the
>> >> @@ -49,9 +49,9 @@
>> >> * that follow.
>> >> *
>> >> * - struct luo_flb_ser:
>> >> - * Metadata for a single preserved global object. Contains its `name`
>> >> - * (compatible string), an opaque `data` handle, and the `count`
>> >> - * number of files depending on it.
>> >> + * Metadata for a single preserved global object. Contains its FLB `name`
>> >> + * string, an opaque `data` handle, and the `count` number of files
>> >> + * depending on it.
>> >> */
>> >>
>> >> #ifndef _LINUX_KHO_ABI_LUO_H
>> >> @@ -84,18 +84,16 @@ struct luo_ser {
>> >> u64 flbs_pa;
>> >> } __packed;
>> >>
>> >> -#define LIVEUPDATE_HNDL_COMPAT_LENGTH 48
>> >> -
>> >> /**
>> >> * struct luo_file_ser - Represents the serialized preserves files.
>> >> - * @compatible: File handler compatible string.
>> >> + * @name: File handler name string.
>> >
>> > We are renaming from cpmaptible to name; I am not sure that is
>> > necessary. We can still use compatible for the same purpose as name.
>> > Unless it really bothers people, but for me 'compatible' works just
>> > the same.
>>
>> Yes it is necessary, For example if we have two handler for a single
>> subsystem. We should refer it by handle this or handler that, not like
>> handler with compatible string this and handler with compatible string
>> that.
>
> I still feel like compatability makes more sense to me. Take an example:
>
> We have two file handlers for memfd (i.e., FH_OLD and FH_NEW): one has
> compatibility = "memfd", and the other has compatibility =
> "memfd_next_generation". The user chooses one of them to preserve the
> memfd data, and the compatibility string is saved alongside the FD.
> When the next kernel scans through the registered file handlers, it
> must find exactly one that is compatible with the preserved data.
I will open this for vote.
I can debate this in the following way:
We have two file handlers for memfd (i.e., FH_OLD and FH_NEW): one has
name = "memfd", and the other has name = "memfd_next_generation". The
user chooses one of them to preserve the memfd data, and the name
string is saved alongside the FD.
When the next kernel scans through the registered file handlers, it
must find exactly one whose name matches with the preserved data.
name here is the identity of the handler which feels like "In target
kernel, Find this handler to operate on the payload." and compatiblility
feels like "find the handler in the target kernel for which the payload is
compatible with."
Based on LPC discussion, if current handler gives up to support
anything new without breaking backward compatiblity. Then only, we
should have a new handler.
So New handler is new identity for a same subsystem. While compatiblity
string sounds feasible when comparing among all different kind of
subsystem handlers.
~Tarun
>
> Pasha
>
>>
>> It ease the conversation and people will understand it quickly with
>> name.
>>
>> ~Tarun
>>
>> >
>> >> * @data: Private data
>> >> * @token: User provided token for this file
>> >> *
>> >> * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
>> >> */
>> >> struct luo_file_ser {
>> >> - char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
>> >> + char name[LIVEUPDATE_HNDL_NAME_LENGTH];
>> >> u64 data;
>> >> u64 token;
>> >> } __packed;
>> >> @@ -134,7 +132,7 @@ struct luo_session_ser {
>> >> } __packed;
>> >
>> > Unreleated to this patch, but should be handled by this work, let's
>> > remove all the '__packed' attributes, the are not needed, we will
>> > explicitly fill in all the holes, with reserved fields.
>> >
>> >>
>> >> /* The max size is set so it can be reliably used during in serialization */
>> >> -#define LIVEUPDATE_FLB_COMPAT_LENGTH 48
>> >> +#define LIVEUPDATE_FLB_NAME_LENGTH 48
>> >>
>> >> /**
>> >> * struct luo_flb_header_ser - Header for the serialized FLB data block.
>> >> @@ -156,7 +154,7 @@ struct luo_flb_header_ser {
>> >>
>> >> /**
>> >> * struct luo_flb_ser - Represents the serialized state of a single FLB object.
>> >> - * @name: The unique compatibility string of the FLB object, used to find the
>> >> + * @name: The unique name string of the FLB object, used to find the
>> >> * corresponding &struct liveupdate_flb handler in the new kernel.
>> >> * @data: The opaque u64 handle returned by the FLB's .preserve() operation
>> >> * in the old kernel. This handle encapsulates the entire state needed
>> >> @@ -172,14 +170,14 @@ struct luo_flb_header_ser {
>> >> * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
>> >> */
>> >> struct luo_flb_ser {
>> >> - char name[LIVEUPDATE_FLB_COMPAT_LENGTH];
>> >> + char name[LIVEUPDATE_FLB_NAME_LENGTH];
>> >> u64 data;
>> >> u64 count;
>> >> } __packed;
>> >>
>> >> /* Kernel Live Update Test ABI */
>> >> #ifdef CONFIG_LIVEUPDATE_TEST
>> >> -#define LIVEUPDATE_TEST_FLB_COMPATIBLE(i) "liveupdate-test-flb-v" #i
>> >> +#define LIVEUPDATE_TEST_FLB_NAME(i) "liveupdate-test-flb-v" #i
>> >> #endif
>> >>
>> >> #endif /* _LINUX_KHO_ABI_LUO_H */
>> >> diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h
>> >> index 08b10fea2afc..371ffd108a79 100644
>> >> --- a/include/linux/kho/abi/memfd.h
>> >> +++ b/include/linux/kho/abi/memfd.h
>> >> @@ -87,7 +87,7 @@ struct memfd_luo_ser {
>> >> struct kho_vmalloc folios;
>> >> } __packed;
>> >>
>> >> -/* The compatibility string for memfd file handler */
>> >> -#define MEMFD_LUO_FH_COMPATIBLE "memfd-v2"
>> >> +/* The name string for memfd file handler */
>> >> +#define MEMFD_LUO_FH_NAME "memfd-v2"
>> >>
>> >> #endif /* _LINUX_KHO_ABI_MEMFD_H */
>> >> diff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h
>> >> index 9485ed73c351..973ca5b26dbd 100644
>> >> --- a/include/linux/kho/abi/pci.h
>> >> +++ b/include/linux/kho/abi/pci.h
>> >> @@ -23,7 +23,7 @@
>> >> * incrementing the version number in the PCI_LUO_FLB_VERSION number.
>> >> */
>> >>
>> >> -#define PCI_LUO_FLB_COMPATIBLE "pci"
>> >> +#define PCI_LUO_FLB_NAME "pci"
>> >> #define PCI_LUO_FLB_VERSION 2
>> >>
>> >> /**
>> >> diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
>> >> index 6051abc0612c..475da5fc22ee 100644
>> >> --- a/include/linux/liveupdate.h
>> >> +++ b/include/linux/liveupdate.h
>> >> @@ -90,10 +90,8 @@ struct liveupdate_file_ops {
>> >> /**
>> >> * struct liveupdate_file_handler - Represents a handler for a live-updatable file type.
>> >> * @ops: Callback functions
>> >> - * @compatible: The compatibility string (e.g., "memfd-v1", "vfiofd-v1")
>> >> - * that uniquely identifies the file type this handler
>> >> - * supports. This is matched against the compatible string
>> >> - * associated with individual &struct file instances.
>> >> + * @name: Unique handler name string (e.g., "memfd-v2"), matched
>> >> + * against &struct luo_file_ser.name in the target kernel.
>> >> *
>> >> * Modules that want to support live update for specific file types should
>> >> * register an instance of this structure. LUO uses this registration to
>> >> @@ -102,7 +100,7 @@ struct liveupdate_file_ops {
>> >> */
>> >> struct liveupdate_file_handler {
>> >> const struct liveupdate_file_ops *ops;
>> >> - const char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
>> >> + const char name[LIVEUPDATE_HNDL_NAME_LENGTH];
>> >>
>> >> /* private: */
>> >>
>> >> @@ -209,11 +207,9 @@ struct luo_flb_private {
>> >> /**
>> >> * struct liveupdate_flb - A global definition for a shared data object.
>> >> * @ops: Callback functions
>> >> - * @compatible: The compatibility string (e.g., "iommu-core-v1"
>> >> - * that uniquely identifies the FLB type this handler
>> >> - * supports. This is matched against the compatible string
>> >> - * associated with individual &struct liveupdate_flb
>> >> - * instances.
>> >> + * @name: The FLB name string (e.g., "iommu-core-v1") that uniquely
>> >> + * identifies this FLB type. In the target kernel, it is
>> >> + * matched against the name stored in &struct luo_flb_ser.
>> >> *
>> >> * This struct is the "template" that a driver registers to define a shared,
>> >> * file-lifecycle-bound object. The actual runtime state (the live object,
>> >> @@ -221,7 +217,7 @@ struct luo_flb_private {
>> >> */
>> >> struct liveupdate_flb {
>> >> const struct liveupdate_flb_ops *ops;
>> >> - const char compatible[LIVEUPDATE_FLB_COMPAT_LENGTH];
>> >> + const char name[LIVEUPDATE_FLB_NAME_LENGTH];
>> >>
>> >> /* private: */
>> >> struct luo_flb_private __private private;
>> >> diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
>> >> index 4043d4038712..509c6236720d 100644
>> >> --- a/include/uapi/linux/liveupdate.h
>> >> +++ b/include/uapi/linux/liveupdate.h
>> >> @@ -46,6 +46,9 @@
>> >> /* The maximum length of session name including null termination */
>> >> #define LIVEUPDATE_SESSION_NAME_LENGTH 64
>> >>
>> >> +/* The maximum length of file handler name including null termination */
>> >> +#define LIVEUPDATE_HNDL_NAME_LENGTH 48
>> >> +
>> >> /* The /dev/liveupdate ioctl commands */
>> >> enum {
>> >> LIVEUPDATE_CMD_BASE = 0x00,
>> >> diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
>> >> index a39992da504f..fb81239bedf3 100644
>> >> --- a/kernel/liveupdate/luo_file.c
>> >> +++ b/kernel/liveupdate/luo_file.c
>> >> @@ -50,7 +50,7 @@
>> >> *
>> >> * 2. Freeze (Pre-Reboot): Just before the kexec, luo_file_freeze() is called.
>> >> * It iterates through all preserved files, calls their respective .freeze()
>> >> - * operation, and serializes their final metadata (compatible string, token,
>> >> + * operation, and serializes their final metadata (handler name, token,
>> >> * and data handle) into a contiguous memory block for KHO.
>> >> *
>> >> * 3. Deserialize: After kexec, luo_file_deserialize() runs when session gets
>> >> @@ -220,7 +220,7 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
>> >> * 1. Validates that the @token is not already in use within the file_set.
>> >> * 2. Ensures the file_set's memory for files serialization is allocated
>> >> * (allocates if needed).
>> >> - * 3. Iterates through registered handlers, calling can_preserve() to find one
>> >> + * 3. Iterates through registered handlers, calling .can_preserve() to find one
>> >> * compatible with the given @fd.
>> >> * 4. Calls the handler's .preserve() operation, which saves the file's state
>> >> * and returns an opaque private data handle.
>> >> @@ -238,7 +238,7 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
>> >> * -EBUSY if the file descriptor is already preserved by another session.
>> >> * -EBADF if the file descriptor is invalid.
>> >> * -ENOSPC if the file_set is full.
>> >> - * -ENOENT if no compatible handler is found.
>> >> + * -ENOENT if no compatible handler is found for the file type.
>> >> * -ENOMEM on memory allocation failure.
>> >> * Other erros might be returned by .preserve().
>> >> */
>> >> @@ -456,9 +456,10 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
>> >> * private data handle during this step.
>> >> *
>> >> * 2. Serializes Metadata: After a successful freeze, it copies the final file
>> >> - * metadata—the handler's compatible string, the user token, and the final
>> >> - * private data handle—into the pre-allocated contiguous memory buffer
>> >> - * (file_set->files) that will be handed over to the next kernel via KHO.
>> >> + * metadata - the handler's name string, the user token, and the final
>> >> + * private data handle - into the pre-allocated contiguous memory buffer
>> >> + * (file_set->files) that will be handed over to the target kernel via
>> >> + * KHO.
>> >> *
>> >> * Error Handling (Rollback):
>> >> * This function is atomic. If any handler's .freeze() operation fails, the
>> >> @@ -495,13 +496,13 @@ int luo_file_freeze(struct luo_file_set *file_set,
>> >> err = luo_file_freeze_one(file_set, luo_file);
>> >> if (err < 0) {
>> >> pr_warn("Freeze failed for token[%#0llx] handler[%s] err[%pe]\n",
>> >> - luo_file->token, luo_file->fh->compatible,
>> >> + luo_file->token, luo_file->fh->name,
>> >> ERR_PTR(err));
>> >> goto err_unfreeze;
>> >> }
>> >>
>> >> - strscpy(file_ser->compatible, luo_file->fh->compatible,
>> >> - sizeof(file_ser->compatible));
>> >> + strscpy(file_ser->name, luo_file->fh->name,
>> >> + sizeof(file_ser->name));
>> >> file_ser->data = luo_file->serialized_data;
>> >> file_ser->token = luo_file->token;
>> >> }
>> >> @@ -747,7 +748,7 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
>> >>
>> >> down_read(&luo_register_rwlock);
>> >> list_private_for_each_entry(fh, &luo_file_handler_list, list) {
>> >> - if (!strcmp(fh->compatible, ser->compatible)) {
>> >> + if (!strcmp(fh->name, ser->name)) {
>> >> if (try_module_get(fh->ops->owner))
>> >> handler_found = true;
>> >> break;
>> >> @@ -756,9 +757,9 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
>> >> up_read(&luo_register_rwlock);
>> >>
>> >> if (!handler_found) {
>> >> - pr_warn("No registered handler for compatible '%.*s'\n",
>> >> - (int)sizeof(ser->compatible),
>> >> - ser->compatible);
>> >> + pr_warn("No registered handler for name '%.*s'\n",
>> >> + (int)sizeof(ser->name),
>> >> + ser->name);
>> >> return -ENOENT;
>> >> }
>> >>
>> >> @@ -789,9 +790,9 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
>> >> * in-memory linked list of 'struct luo_file' instances.
>> >> *
>> >> * For each serialized entry, it performs the following steps:
>> >> - * 1. Reads the 'compatible' string.
>> >> + * 1. Reads the handler 'name' string.
>> >> * 2. Searches the global list of registered file handlers for one that
>> >> - * matches the compatible string.
>> >> + * matches the handler name.
>> >> * 3. Allocates a new 'struct luo_file'.
>> >> * 4. Populates the new structure with the deserialized data (token, private
>> >> * data handle) and links it to the found handler. The 'file' pointer is
>> >> @@ -884,9 +885,9 @@ void luo_file_set_destroy(struct luo_file_set *file_set)
>> >> /**
>> >> * liveupdate_register_file_handler - Register a file handler with LUO.
>> >> * @fh: Pointer to a caller-allocated &struct liveupdate_file_handler.
>> >> - * The caller must initialize this structure, including a unique
>> >> - * 'compatible' string and a valid 'fh' callbacks. This function adds the
>> >> - * handler to the global list of supported file handlers.
>> >> + * The caller must initialize this structure, including a unique 'name'
>> >> + * string and valid 'fh' callbacks. This function adds the handler to the
>> >> + * global list of supported file handlers.
>> >> *
>> >> * Context: Typically called during module initialization for file types that
>> >> * support live update preservation.
>> >> @@ -908,11 +909,11 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
>> >> }
>> >>
>> >> down_write(&luo_register_rwlock);
>> >> - /* Check for duplicate compatible strings */
>> >> + /* Check for duplicate handler names */
>> >> list_private_for_each_entry(fh_iter, &luo_file_handler_list, list) {
>> >> - if (!strcmp(fh_iter->compatible, fh->compatible)) {
>> >> - pr_err("File handler registration failed: Compatible string '%s' already registered.\n",
>> >> - fh->compatible);
>> >> + if (!strcmp(fh_iter->name, fh->name)) {
>> >> + pr_err("File handler registration failed: Handler name '%s' already registered.\n",
>> >> + fh->name);
>> >> err = -EEXIST;
>> >> goto err_unlock;
>> >> }
>> >> diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
>> >> index cd715a7c1d99..222c6d7f821e 100644
>> >> --- a/kernel/liveupdate/luo_flb.c
>> >> +++ b/kernel/liveupdate/luo_flb.c
>> >> @@ -178,7 +178,7 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
>> >> return -ENODATA;
>> >>
>> >> for (int i = 0; i < fh->header_ser->count; i++) {
>> >> - if (!strcmp(fh->ser[i].name, flb->compatible)) {
>> >> + if (!strcmp(fh->ser[i].name, flb->name)) {
>> >> private->incoming.data = fh->ser[i].data;
>> >> refcount_set(&private->incoming.count, fh->ser[i].count);
>> >> found = true;
>> >> @@ -337,7 +337,7 @@ static void luo_flb_unregister_one(struct liveupdate_file_handler *fh,
>> >>
>> >> if (!found) {
>> >> pr_warn("Failed to unregister FLB '%s': not found in file handler '%s'\n",
>> >> - flb->compatible, fh->compatible);
>> >> + flb->name, fh->name);
>> >> return;
>> >> }
>> >>
>> >> @@ -442,9 +442,9 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
>> >> if (luo_flb_global.count == LUO_FLB_MAX)
>> >> return -ENOSPC;
>> >>
>> >> - /* Check that compatible string is unique in global list */
>> >> + /* Check that FLB name is unique in global list */
>> >> list_private_for_each_entry(gflb, &luo_flb_global.list, private.list) {
>> >> - if (!strcmp(gflb->compatible, flb->compatible))
>> >> + if (!strcmp(gflb->name, flb->name))
>> >> return -EEXIST;
>> >> }
>> >>
>> >> @@ -596,9 +596,9 @@ void __init luo_flb_setup_incoming(u64 flbs_pa)
>> >> * preserved (i.e., its reference count is greater than zero), it writes its
>> >> * metadata into the memory region designated for Kexec Handover.
>> >> *
>> >> - * The serialized data includes the FLB's compatibility string, its opaque
>> >> - * data handle, and the final reference count. This allows the new kernel to
>> >> - * find the appropriate handler and reconstruct the FLB's state.
>> >> + * The serialized data includes the FLB's name string, its opaque
>> >> + * data handle, and the final reference count. This allows the target
>> >> + * kernel to find the appropriate FLB and reconstruct its state.
>> >> *
>> >> * Context: Called from liveupdate_reboot() just before kho_finalize().
>> >> */
>> >> @@ -614,7 +614,7 @@ void luo_flb_serialize(void)
>> >> long count = refcount_read(&private->outgoing.count);
>> >>
>> >> if (count > 0) {
>> >> - strscpy(fh->ser[i].name, gflb->compatible,
>> >> + strscpy(fh->ser[i].name, gflb->name,
>> >> sizeof(fh->ser[i].name));
>> >> fh->ser[i].data = private->outgoing.data;
>> >> fh->ser[i].count = count;
>> >> diff --git a/lib/tests/liveupdate.c b/lib/tests/liveupdate.c
>> >> index 4c08a7c6fb78..e4df09eed1f3 100644
>> >> --- a/lib/tests/liveupdate.c
>> >> +++ b/lib/tests/liveupdate.c
>> >> @@ -17,7 +17,7 @@
>> >> static const struct liveupdate_flb_ops test_flb_ops;
>> >> #define DEFINE_TEST_FLB(i) { \
>> >> .ops = &test_flb_ops, \
>> >> - .compatible = LIVEUPDATE_TEST_FLB_COMPATIBLE(i), \
>> >> + .name = LIVEUPDATE_TEST_FLB_NAME(i), \
>> >> }
>> >>
>> >> /* Number of Test FLBs to register with every file handler */
>> >> @@ -34,7 +34,7 @@ static int test_flb_preserve(struct liveupdate_flb_op_args *argp)
>> >> {
>> >> ptrdiff_t index = argp->flb - test_flbs;
>> >>
>> >> - pr_info("%s: preserve was triggered\n", argp->flb->compatible);
>> >> + pr_info("%s: preserve was triggered\n", argp->flb->name);
>> >> argp->data = TEST_FLB_MAGIC_BASE + index;
>> >>
>> >> return 0;
>> >> @@ -42,7 +42,7 @@ static int test_flb_preserve(struct liveupdate_flb_op_args *argp)
>> >>
>> >> static void test_flb_unpreserve(struct liveupdate_flb_op_args *argp)
>> >> {
>> >> - pr_info("%s: unpreserve was triggered\n", argp->flb->compatible);
>> >> + pr_info("%s: unpreserve was triggered\n", argp->flb->name);
>> >> }
>> >>
>> >> static int test_flb_retrieve(struct liveupdate_flb_op_args *argp)
>> >> @@ -52,11 +52,11 @@ static int test_flb_retrieve(struct liveupdate_flb_op_args *argp)
>> >>
>> >> if (argp->data == expected_data) {
>> >> pr_info("%s: found flb data from the previous boot\n",
>> >> - argp->flb->compatible);
>> >> + argp->flb->name);
>> >> argp->obj = (void *)argp->data;
>> >> } else {
>> >> pr_err("%s: ERROR - incorrect data handle: %llx, expected %llx\n",
>> >> - argp->flb->compatible, argp->data, expected_data);
>> >> + argp->flb->name, argp->data, expected_data);
>> >> return -EINVAL;
>> >> }
>> >>
>> >> @@ -69,10 +69,10 @@ static void test_flb_finish(struct liveupdate_flb_op_args *argp)
>> >> void *expected_obj = (void *)(TEST_FLB_MAGIC_BASE + index);
>> >>
>> >> if (argp->obj == expected_obj) {
>> >> - pr_info("%s: finish was triggered\n", argp->flb->compatible);
>> >> + pr_info("%s: finish was triggered\n", argp->flb->name);
>> >> } else {
>> >> pr_err("%s: ERROR - finish called with invalid object\n",
>> >> - argp->flb->compatible);
>> >> + argp->flb->name);
>> >> }
>> >> }
>> >>
>> >> @@ -103,7 +103,7 @@ static void liveupdate_test_init(void)
>> >> err = liveupdate_flb_get_incoming(flb, &obj);
>> >> if (err && err != -ENODATA && err != -ENOENT) {
>> >> pr_err("liveupdate_flb_get_incoming for %s failed: %pe\n",
>> >> - flb->compatible, ERR_PTR(err));
>> >> + flb->name, ERR_PTR(err));
>> >> }
>> >>
>> >> if (!err)
>> >> @@ -124,18 +124,18 @@ void liveupdate_test_register(struct liveupdate_file_handler *fh)
>> >> err = liveupdate_register_flb(fh, flb);
>> >> if (err) {
>> >> pr_err("Failed to register %s %pe\n",
>> >> - flb->compatible, ERR_PTR(err));
>> >> + flb->name, ERR_PTR(err));
>> >> }
>> >> }
>> >>
>> >> err = liveupdate_register_flb(fh, &test_flbs[0]);
>> >> if (!err || err != -EEXIST) {
>> >> pr_err("Failed: %s should be already registered, but got err: %pe\n",
>> >> - test_flbs[0].compatible, ERR_PTR(err));
>> >> + test_flbs[0].name, ERR_PTR(err));
>> >> }
>> >>
>> >> pr_info("Registered %d FLBs with file handler: [%s]\n",
>> >> - TEST_NFLBS, fh->compatible);
>> >> + TEST_NFLBS, fh->name);
>> >> }
>> >>
>> >> MODULE_LICENSE("GPL");
>> >> diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
>> >> index 6b68220275eb..64f8b8cb75bd 100644
>> >> --- a/mm/memfd_luo.c
>> >> +++ b/mm/memfd_luo.c
>> >> @@ -602,7 +602,7 @@ static const struct liveupdate_file_ops memfd_luo_file_ops = {
>> >>
>> >> static struct liveupdate_file_handler memfd_luo_handler = {
>> >> .ops = &memfd_luo_file_ops,
>> >> - .compatible = MEMFD_LUO_FH_COMPATIBLE,
>> >> + .name = MEMFD_LUO_FH_NAME,
>> >> };
>> >>
>> >> static int __init memfd_luo_init(void)
>> >> --
>> >> 2.56.0.212.g129ede20d9-goog
>> >>
^ permalink raw reply [flat|nested] 27+ messages in thread
* [RFC PATCH v2 2/4] liveupdate: Select file handler by name in the preserve ioctl
2026-10-21 15:25 [RFC PATCH v2 0/4] liveupdate: Feature header ABI and named handler Tarun Sahu
2026-10-21 15:25 ` [RFC PATCH v2 1/4] liveupdate: Rename handler compatible to name Tarun Sahu
@ 2026-10-21 15:25 ` Tarun Sahu
2026-10-21 17:17 ` Pasha Tatashin
2026-10-21 15:25 ` [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header Tarun Sahu
2026-10-21 15:25 ` [RFC PATCH v2 4/4] mm: memfd_luo: Add the " Tarun Sahu
3 siblings, 1 reply; 27+ messages in thread
From: Tarun Sahu @ 2026-10-21 15:25 UTC (permalink / raw)
To: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
David Matlack, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Pasha Tatashin,
Alexander Graf, Bjorn Helgaas, baolu.lu
Cc: linux-arm-kernel, linux-arch, sagis, linux-mm, kvmarm, iommu,
linux-pci, kexec, skhawaja, kvm, linux-kernel, Tarun Sahu
Currently luo_preserve_file() iterates over luo_file_handler_list and
picks the first registered handler whose .can_preserve() callback
returns true. When a subsystem introduces a new, backwards-incompatible
file handler alongside an existing one for the same file type, userspace
has no way to choose which handler preserves a file descriptor, nor can
the kernel keep the existing rollback-safe handler as the default while
allowing callers to opt into the new handler.
Append char handler[LIVEUPDATE_HNDL_NAME_LENGTH] to struct
liveupdate_session_preserve_fd immediately after token, keeping
IOCTL_OP min_size at offsetofend(..., token) so callers passing the
original 16-byte structure continue to work:
- When handler is empty (all-zero, or zero-filled when userspace
passes the 16-byte structure), luo_preserve_file() selects the first
registered handler whose .can_preserve() callback accepts the fd.
- When handler is non-empty, it must be null-terminated within
LIVEUPDATE_HNDL_NAME_LENGTH bytes (returning -EINVAL otherwise);
luo_preserve_file() looks up the registered handler with that exact
name (returning -ENOENT if unknown) and verifies that its
.can_preserve() callback accepts the fd (returning -EINVAL if not).
The retrieve ioctl is unchanged because the handler is identified by the
name stored in the serialized payload from the source kernel.
Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
include/uapi/linux/liveupdate.h | 12 +++++++++---
kernel/liveupdate/luo_file.c | 21 ++++++++++++++++++---
kernel/liveupdate/luo_internal.h | 3 ++-
kernel/liveupdate/luo_session.c | 7 ++++++-
4 files changed, 35 insertions(+), 8 deletions(-)
diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
index 509c6236720d..2413b19dabc1 100644
--- a/include/uapi/linux/liveupdate.h
+++ b/include/uapi/linux/liveupdate.h
@@ -126,9 +126,14 @@ struct liveupdate_ioctl_retrieve_session {
/**
* struct liveupdate_session_preserve_fd - ioctl(LIVEUPDATE_SESSION_PRESERVE_FD)
- * @size: Input; sizeof(struct liveupdate_session_preserve_fd)
- * @fd: Input; The user-space file descriptor to be preserved.
- * @token: Input; An opaque, unique token for preserved resource.
+ * @size: Input; sizeof(struct liveupdate_session_preserve_fd)
+ * @fd: Input; The user-space file descriptor to be preserved.
+ * @token: Input; An opaque, unique token for preserved resource.
+ * @handler: Input; Optional null-terminated file handler name (max length
+ * %LIVEUPDATE_HNDL_NAME_LENGTH including null terminator) selecting
+ * which registered handler preserves @fd. If empty (all-zero, or
+ * omitted when passing the 16-byte structure), the kernel selects
+ * the first registered handler whose .can_preserve() accepts @fd.
*
* Holds parameters for preserving a file descriptor.
*
@@ -152,6 +157,7 @@ struct liveupdate_session_preserve_fd {
__u32 size;
__s32 fd;
__aligned_u64 token;
+ char handler[LIVEUPDATE_HNDL_NAME_LENGTH];
};
#define LIVEUPDATE_SESSION_PRESERVE_FD \
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index fb81239bedf3..be20e0f1e0aa 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -207,6 +207,8 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
* @file_set: The file_set to which the preserved file will be added.
* @token: A unique, user-provided identifier for the file.
* @fd: The file descriptor to be preserved.
+ * @handler: Optional NUL-terminated handler name. If NULL or empty, the first
+ * registered handler whose .can_preserve() accepts @fd is selected.
*
* This function orchestrates the first phase of preserving a file. Upon entry,
* it takes a reference to the 'struct file' via fget(), effectively making LUO
@@ -238,11 +240,13 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
* -EBUSY if the file descriptor is already preserved by another session.
* -EBADF if the file descriptor is invalid.
* -ENOSPC if the file_set is full.
- * -ENOENT if no compatible handler is found for the file type.
+ * -ENOENT if no matching handler is registered or accepts @fd.
+ * -EINVAL if the named handler cannot preserve @fd.
* -ENOMEM on memory allocation failure.
* Other erros might be returned by .preserve().
*/
-int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
+int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd,
+ const char *handler)
{
struct liveupdate_file_op_args args = {0};
struct liveupdate_file_handler *fh;
@@ -266,6 +270,18 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
err = -ENOENT;
down_read(&luo_register_rwlock);
list_private_for_each_entry(fh, &luo_file_handler_list, list) {
+ if (handler && handler[0]) {
+ if (strcmp(fh->name, handler))
+ continue;
+ if (!fh->ops->can_preserve(fh, file)) {
+ err = -EINVAL;
+ break;
+ }
+ if (try_module_get(fh->ops->owner))
+ err = 0;
+ break;
+ }
+
if (fh->ops->can_preserve(fh, file)) {
if (try_module_get(fh->ops->owner))
err = 0;
@@ -274,7 +290,6 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
}
up_read(&luo_register_rwlock);
- /* err is still -ENOENT if no handler was found */
if (err)
goto err_fput;
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index dac6644bfb18..911ffedec9f3 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -101,7 +101,8 @@ int __init luo_session_setup_incoming(u64 sessions_pa);
int luo_session_serialize(void);
int luo_session_deserialize(void);
-int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd);
+int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd,
+ const char *handler);
void luo_file_unpreserve_files(struct luo_file_set *file_set);
int luo_file_freeze(struct luo_file_set *file_set,
struct luo_file_set_ser *file_set_ser);
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index f48e9a4185f9..194ef45907c1 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -266,8 +266,13 @@ static int luo_session_preserve_fd(struct luo_session *session,
struct liveupdate_session_preserve_fd *argp = ucmd->cmd;
int err;
+ if (strnlen(argp->handler, sizeof(argp->handler)) ==
+ sizeof(argp->handler))
+ return -EINVAL;
+
guard(mutex)(&session->mutex);
- err = luo_preserve_file(&session->file_set, argp->token, argp->fd);
+ err = luo_preserve_file(&session->file_set, argp->token, argp->fd,
+ argp->handler);
if (err)
return err;
--
2.56.0.212.g129ede20d9-goog
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 2/4] liveupdate: Select file handler by name in the preserve ioctl
2026-10-21 15:25 ` [RFC PATCH v2 2/4] liveupdate: Select file handler by name in the preserve ioctl Tarun Sahu
@ 2026-10-21 17:17 ` Pasha Tatashin
2026-10-21 23:02 ` David Matlack
0 siblings, 1 reply; 27+ messages in thread
From: Pasha Tatashin @ 2026-10-21 17:17 UTC (permalink / raw)
To: Tarun Sahu
Cc: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
David Matlack, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
On Wed, Oct 21, 2026 at 11:25 AM Tarun Sahu <tarunsahu@google.com> wrote:
>
> Currently luo_preserve_file() iterates over luo_file_handler_list and
> picks the first registered handler whose .can_preserve() callback
> returns true. When a subsystem introduces a new, backwards-incompatible
> file handler alongside an existing one for the same file type, userspace
> has no way to choose which handler preserves a file descriptor, nor can
> the kernel keep the existing rollback-safe handler as the default while
> allowing callers to opt into the new handler.
That is not how it will work. If we are in the .can_preserve() call,
it means the user has already decided which file handlers to use. We
need to do that before any preservation happens; otherwise, we could
run into conflicts on FLBs i.e. FH of different version require
different FLBs.
Therefore, there should be a luoctl call (i.e via a new ioctl to set
compatability) that provides the kernel with the specs file of the
target kernel we are updating to, allowing the kernel to select the
compatible file handlers. Since .can_preserve() is called when the
user is already preserving files, the specification must have already
been supplied, or the kernel should fall back to using the default
(i.e., latest) file handlers.
>
> Append char handler[LIVEUPDATE_HNDL_NAME_LENGTH] to struct
> liveupdate_session_preserve_fd immediately after token, keeping
> IOCTL_OP min_size at offsetofend(..., token) so callers passing the
> original 16-byte structure continue to work:
> - When handler is empty (all-zero, or zero-filled when userspace
> passes the 16-byte structure), luo_preserve_file() selects the first
> registered handler whose .can_preserve() callback accepts the fd.
> - When handler is non-empty, it must be null-terminated within
> LIVEUPDATE_HNDL_NAME_LENGTH bytes (returning -EINVAL otherwise);
> luo_preserve_file() looks up the registered handler with that exact
> name (returning -ENOENT if unknown) and verifies that its
> .can_preserve() callback accepts the fd (returning -EINVAL if not).
>
> The retrieve ioctl is unchanged because the handler is identified by the
> name stored in the serialized payload from the source kernel.
>
> Signed-off-by: Tarun Sahu <tarunsahu@google.com>
> ---
> include/uapi/linux/liveupdate.h | 12 +++++++++---
> kernel/liveupdate/luo_file.c | 21 ++++++++++++++++++---
> kernel/liveupdate/luo_internal.h | 3 ++-
> kernel/liveupdate/luo_session.c | 7 ++++++-
> 4 files changed, 35 insertions(+), 8 deletions(-)
>
> diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
> index 509c6236720d..2413b19dabc1 100644
> --- a/include/uapi/linux/liveupdate.h
> +++ b/include/uapi/linux/liveupdate.h
> @@ -126,9 +126,14 @@ struct liveupdate_ioctl_retrieve_session {
>
> /**
> * struct liveupdate_session_preserve_fd - ioctl(LIVEUPDATE_SESSION_PRESERVE_FD)
> - * @size: Input; sizeof(struct liveupdate_session_preserve_fd)
> - * @fd: Input; The user-space file descriptor to be preserved.
> - * @token: Input; An opaque, unique token for preserved resource.
> + * @size: Input; sizeof(struct liveupdate_session_preserve_fd)
> + * @fd: Input; The user-space file descriptor to be preserved.
> + * @token: Input; An opaque, unique token for preserved resource.
> + * @handler: Input; Optional null-terminated file handler name (max length
> + * %LIVEUPDATE_HNDL_NAME_LENGTH including null terminator) selecting
> + * which registered handler preserves @fd. If empty (all-zero, or
> + * omitted when passing the 16-byte structure), the kernel selects
> + * the first registered handler whose .can_preserve() accepts @fd.
> *
> * Holds parameters for preserving a file descriptor.
> *
> @@ -152,6 +157,7 @@ struct liveupdate_session_preserve_fd {
> __u32 size;
> __s32 fd;
> __aligned_u64 token;
> + char handler[LIVEUPDATE_HNDL_NAME_LENGTH];
> };
>
> #define LIVEUPDATE_SESSION_PRESERVE_FD \
> diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
> index fb81239bedf3..be20e0f1e0aa 100644
> --- a/kernel/liveupdate/luo_file.c
> +++ b/kernel/liveupdate/luo_file.c
> @@ -207,6 +207,8 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
> * @file_set: The file_set to which the preserved file will be added.
> * @token: A unique, user-provided identifier for the file.
> * @fd: The file descriptor to be preserved.
> + * @handler: Optional NUL-terminated handler name. If NULL or empty, the first
> + * registered handler whose .can_preserve() accepts @fd is selected.
> *
> * This function orchestrates the first phase of preserving a file. Upon entry,
> * it takes a reference to the 'struct file' via fget(), effectively making LUO
> @@ -238,11 +240,13 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
> * -EBUSY if the file descriptor is already preserved by another session.
> * -EBADF if the file descriptor is invalid.
> * -ENOSPC if the file_set is full.
> - * -ENOENT if no compatible handler is found for the file type.
> + * -ENOENT if no matching handler is registered or accepts @fd.
> + * -EINVAL if the named handler cannot preserve @fd.
> * -ENOMEM on memory allocation failure.
> * Other erros might be returned by .preserve().
> */
> -int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
> +int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd,
> + const char *handler)
> {
> struct liveupdate_file_op_args args = {0};
> struct liveupdate_file_handler *fh;
> @@ -266,6 +270,18 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
> err = -ENOENT;
> down_read(&luo_register_rwlock);
> list_private_for_each_entry(fh, &luo_file_handler_list, list) {
> + if (handler && handler[0]) {
> + if (strcmp(fh->name, handler))
> + continue;
> + if (!fh->ops->can_preserve(fh, file)) {
> + err = -EINVAL;
> + break;
> + }
> + if (try_module_get(fh->ops->owner))
> + err = 0;
> + break;
> + }
> +
> if (fh->ops->can_preserve(fh, file)) {
> if (try_module_get(fh->ops->owner))
> err = 0;
> @@ -274,7 +290,6 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
> }
> up_read(&luo_register_rwlock);
>
> - /* err is still -ENOENT if no handler was found */
> if (err)
> goto err_fput;
>
> diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
> index dac6644bfb18..911ffedec9f3 100644
> --- a/kernel/liveupdate/luo_internal.h
> +++ b/kernel/liveupdate/luo_internal.h
> @@ -101,7 +101,8 @@ int __init luo_session_setup_incoming(u64 sessions_pa);
> int luo_session_serialize(void);
> int luo_session_deserialize(void);
>
> -int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd);
> +int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd,
> + const char *handler);
> void luo_file_unpreserve_files(struct luo_file_set *file_set);
> int luo_file_freeze(struct luo_file_set *file_set,
> struct luo_file_set_ser *file_set_ser);
> diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
> index f48e9a4185f9..194ef45907c1 100644
> --- a/kernel/liveupdate/luo_session.c
> +++ b/kernel/liveupdate/luo_session.c
> @@ -266,8 +266,13 @@ static int luo_session_preserve_fd(struct luo_session *session,
> struct liveupdate_session_preserve_fd *argp = ucmd->cmd;
> int err;
>
> + if (strnlen(argp->handler, sizeof(argp->handler)) ==
> + sizeof(argp->handler))
> + return -EINVAL;
> +
> guard(mutex)(&session->mutex);
> - err = luo_preserve_file(&session->file_set, argp->token, argp->fd);
> + err = luo_preserve_file(&session->file_set, argp->token, argp->fd,
> + argp->handler);
> if (err)
> return err;
>
> --
> 2.56.0.212.g129ede20d9-goog
>
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 2/4] liveupdate: Select file handler by name in the preserve ioctl
2026-10-21 17:17 ` Pasha Tatashin
@ 2026-10-21 23:02 ` David Matlack
2026-10-22 1:14 ` Pasha Tatashin
0 siblings, 1 reply; 27+ messages in thread
From: David Matlack @ 2026-10-21 23:02 UTC (permalink / raw)
To: Pasha Tatashin
Cc: Tarun Sahu, Andrew Morton, joro, Mike Rapoport, Pratyush Yadav,
maz, arnd, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
On 2026-10-21 01:17 PM, Pasha Tatashin wrote:
> On Wed, Oct 21, 2026 at 11:25 AM Tarun Sahu <tarunsahu@google.com> wrote:
> >
> > Currently luo_preserve_file() iterates over luo_file_handler_list and
> > picks the first registered handler whose .can_preserve() callback
> > returns true. When a subsystem introduces a new, backwards-incompatible
> > file handler alongside an existing one for the same file type, userspace
> > has no way to choose which handler preserves a file descriptor, nor can
> > the kernel keep the existing rollback-safe handler as the default while
> > allowing callers to opt into the new handler.
>
> That is not how it will work. If we are in the .can_preserve() call,
> it means the user has already decided which file handlers to use. We
> need to do that before any preservation happens; otherwise, we could
> run into conflicts on FLBs i.e. FH of different version require
> different FLBs.
>
> Therefore, there should be a luoctl call (i.e via a new ioctl to set
> compatability) that provides the kernel with the specs file of the
> target kernel we are updating to, allowing the kernel to select the
> compatible file handlers. Since .can_preserve() is called when the
> user is already preserving files, the specification must have already
> been supplied, or the kernel should fall back to using the default
> (i.e., latest) file handlers.
I don't think that is the right direction. IMO the kernel should not be
choosing which file handler to use, userspace should be. If userspace
gets it wrong and chooses a file handler the next kernel does not
support, that's on them.
The problem with the kernel making a decision about file handlers is
then userspace does not know what arguments to pass in. We are adding
custom per-file handler arguments to PRESERVE_FD and RETRIEVE_FD. If the
kernel is picking which file handler to use, say for a memfd, then
userspace doesn't know which arguments to pass in.
Sure you could add some method where userspace could query which file
handlers are active and decide arguments based on that. But it feels
backwards.
I think a much cleaner solution is that userspace chooses file handler
during PRESERVE_FD. I would even require it for RETRIEVE_FD so that we
are guaranteed that the args the user is passing in are being sent to
the right file handler. Passing to RETRIEVE_FD also opens the door to
transforming across Live Update (similar to how AWS did LU from Xen to
KVM as discussed at LPC).
Regarding FLB conflicts, today we don't support multiple FLBs for the
same global state, so there is nothing to conflict. Selecting a file
handler during PRESERVE_FD doesn't change that.
If we ever need to support multiple FLBs for the same global state
(e.g. an incompatible change to the PCI FLB), we can add an ioctl that
lets userspace select which FLB to use before preserving any files.
After that, only file handlers registered with the selected FLB can be
used to preserve files. PRESERVE_FD with any other file handler fails.
That keeps the FLB decision up front and global, like you want, while
still letting userspace pick the file handler for each fd. I'd also
expect this to be rare, since FLB serialized state can use the same
feature flags as everything else in this series.
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [RFC PATCH v2 2/4] liveupdate: Select file handler by name in the preserve ioctl
2026-10-21 23:02 ` David Matlack
@ 2026-10-22 1:14 ` Pasha Tatashin
0 siblings, 0 replies; 27+ messages in thread
From: Pasha Tatashin @ 2026-10-22 1:14 UTC (permalink / raw)
To: David Matlack
Cc: Tarun Sahu, Andrew Morton, joro, Mike Rapoport, Pratyush Yadav,
maz, arnd, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
On Wed, Oct 21, 2026 at 7:02 PM David Matlack <dmatlack@google.com> wrote:
>
> On 2026-10-21 01:17 PM, Pasha Tatashin wrote:
> > On Wed, Oct 21, 2026 at 11:25 AM Tarun Sahu <tarunsahu@google.com> wrote:
> > >
> > > Currently luo_preserve_file() iterates over luo_file_handler_list and
> > > picks the first registered handler whose .can_preserve() callback
> > > returns true. When a subsystem introduces a new, backwards-incompatible
> > > file handler alongside an existing one for the same file type, userspace
> > > has no way to choose which handler preserves a file descriptor, nor can
> > > the kernel keep the existing rollback-safe handler as the default while
> > > allowing callers to opt into the new handler.
> >
> > That is not how it will work. If we are in the .can_preserve() call,
> > it means the user has already decided which file handlers to use. We
> > need to do that before any preservation happens; otherwise, we could
> > run into conflicts on FLBs i.e. FH of different version require
> > different FLBs.
> >
> > Therefore, there should be a luoctl call (i.e via a new ioctl to set
> > compatability) that provides the kernel with the specs file of the
> > target kernel we are updating to, allowing the kernel to select the
> > compatible file handlers. Since .can_preserve() is called when the
> > user is already preserving files, the specification must have already
> > been supplied, or the kernel should fall back to using the default
> > (i.e., latest) file handlers.
>
> I don't think that is the right direction. IMO the kernel should not be
> choosing which file handler to use, userspace should be. If userspace
> gets it wrong and chooses a file handler the next kernel does not
> support, that's on them.
>
> The problem with the kernel making a decision about file handlers is
> then userspace does not know what arguments to pass in. We are adding
> custom per-file handler arguments to PRESERVE_FD and RETRIEVE_FD. If the
> kernel is picking which file handler to use, say for a memfd, then
> userspace doesn't know which arguments to pass in.
It is not the kernel deciding on its own which handler to use; it is
the system admin (or host orchestrator) that configures LUO before the
LU process starts, because the sysadmin knows what kernel we are
rebooting into and the LUO specs of that target kernel. Meanwhile, the
process calling PRESERVE_FD (e.g., a VMM) only knows about its own FDs
and should not have to track target kernel serialization versions.
Also, the PRESERVE_FD and RETRIEVE_FD arguments are uABI tied to the
FD type, not to a specific internal handler version. They are stable,
and we can expand them the same way any ioctl argument is expanded
(via size and flags), without ever shrinking or breaking them. We
should not play the game of providing completely different uABI
argument structs for different versions of a file handler. If a caller
passes a new feature in the uABI args that the currently configured
file handler version does not support, PRESERVE_FD simply returns
-EOPNOTSUPP, just like any other extensible ioctl.
> Sure you could add some method where userspace could query which file
> handlers are active and decide arguments based on that. But it feels
> backwards.
>
> I think a much cleaner solution is that userspace chooses file handler
> during PRESERVE_FD. I would even require it for RETRIEVE_FD so that we
> are guaranteed that the args the user is passing in are being sent to
> the right file handler. Passing to RETRIEVE_FD also opens the door to
> transforming across Live Update (similar to how AWS did LU from Xen to
> KVM as discussed at LPC).
On RETRIEVE_FD, the preserved metadata already identifies what format
was preserved. Validating that the user is passing the right argument
struct only requires checking the compatable string, not selecting a
serialization version on every call. And if we ever want to support
cross-subsystem transformations on retrieve, that is a retrieve-side
conversion capability.
> Regarding FLB conflicts, today we don't support multiple FLBs for the
> same global state, so there is nothing to conflict. Selecting a file
> handler during PRESERVE_FD doesn't change that.
>
> If we ever need to support multiple FLBs for the same global state
> (e.g. an incompatible change to the PCI FLB), we can add an ioctl that
> lets userspace select which FLB to use before preserving any files.
> After that, only file handlers registered with the selected FLB can be
> used to preserve files. PRESERVE_FD with any other file handler fails.
>
> That keeps the FLB decision up front and global, like you want, while
> still letting userspace pick the file handler for each fd. I'd also
> expect this to be rare, since FLB serialized state can use the same
> feature flags as everything else in this series.
Userspace owns FDs, not FLBs. Whether a subsystem uses one FLB, two
FLBs, or no FLB to coordinate state across FDs is internal kernel
plumbing. Exposing an ioctl for userspace to select FLBs leaks kernel
internals into the uABI.
Therefore, I think we should not select FLBs from userspace at all.
FLBs are an internal kernel mechanism for sharing state across FDs;
from userspace, we only preserve FDs, so that is what should be
configurable, and the kernel should resolve the dependencies and pick
the FLB versions accordingly.
Changing one file handler may require changing another file handler
because they share an FLB. We can't have a situation where one VMM
preserves using FH_A and another uses FH_B for the same FD type,
trying to use different FLBs for the same data (since we can't
preserve the same page into KHO twice). More generally, we are
rebooting the host into a single target kernel, so compatibility is a
session-level property, not a per-FD property. If we need an upfront
global ioctl anyway, configuring the target compatibility upfront in
one place is much cleaner than exposing FLBs to userspace and
splitting version selection between a global ioctl and per-FD
PRESERVE_FD calls.
^ permalink raw reply [flat|nested] 27+ messages in thread
* [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header
2026-10-21 15:25 [RFC PATCH v2 0/4] liveupdate: Feature header ABI and named handler Tarun Sahu
2026-10-21 15:25 ` [RFC PATCH v2 1/4] liveupdate: Rename handler compatible to name Tarun Sahu
2026-10-21 15:25 ` [RFC PATCH v2 2/4] liveupdate: Select file handler by name in the preserve ioctl Tarun Sahu
@ 2026-10-21 15:25 ` Tarun Sahu
2026-10-21 17:27 ` Pasha Tatashin
2026-10-21 23:55 ` David Matlack
2026-10-21 15:25 ` [RFC PATCH v2 4/4] mm: memfd_luo: Add the " Tarun Sahu
3 siblings, 2 replies; 27+ messages in thread
From: Tarun Sahu @ 2026-10-21 15:25 UTC (permalink / raw)
To: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
David Matlack, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Pasha Tatashin,
Alexander Graf, Bjorn Helgaas, baolu.lu
Cc: linux-arm-kernel, linux-arch, sagis, linux-mm, kvmarm, iommu,
linux-pci, kexec, skhawaja, kvm, linux-kernel, Tarun Sahu
LUO and its file handlers validate handed-over state with monolithic
compatibility strings ("luo-v5", "memfd-v2"). Any change to a serialized
structure, even an optional additive one, must bump the string, which
breaks live update between adjacent kernels. Replace the string in
struct luo_ser with a feature header, so that the target kernel can
accept or reject the source kernel state per feature.
struct luo_feature_hdr is a fixed 128-byte header: the serialized
structure size from the source kernel followed by 15 chained u64 feature
flags entries. The fixed size keeps payload field offsets identical in
every kernel, regardless of how many flags entries a source kernel uses.
Each feature has a 2-bit pair (req, active) state (enum luo_feat_state:
LUO_FEAT_DISABLED, LUO_FEAT_OPTIONAL, LUO_FEAT_REQUIRED). A flags entry
holds 31 features, and bits [63:62] = 0b11 chain to the next entry (465
features in total). Each subsystem defines its feature states as a
static const u8 array indexed by its feature enum, which
luo_feature_hdr_init() packs into 64-bit flags entries with
LUO_FEAT_EXT_MASK chain bits. The target kernel validates the header from
the source kernel against its own feature array via
luo_feature_hdr_validate() according to the per-feature (req, active)
truth table:
Source Target Action
(1,0) (x,x) Reject -EINVAL: invalid source kernel (req without active)
(x,x) (1,0) Reject -EINVAL + WARN_ON_ONCE: invalid target kernel
(0,0) (0,0) Accept: feature absent
(0,0) (0,1) Accept: feature absent
(0,0) (1,1) Reject -EOPNOTSUPP: target kernel requires the feature
(0,1) (0,0) Accept: ignore payload data
(1,1) (0,0) Reject -EOPNOTSUPP: source kernel requires the feature
(x,1) (x,1) Accept: use payload data
The relative order and offsets of fields in serialized structures must
remain unchanged as features are added or retired. New fields are always
appended at the end of the structure under a new feature flag, and
retired fields are kept in place so that the offsets of subsequent
fields never shift. If the type or format of a field needs to change, the
existing field and feature are kept intact (and retired when no longer
needed) and a new feature flag with a new appended field of the new type
is added.
In the target kernel, fields of a LUO_FEAT_REQUIRED feature are
guaranteed to be present once luo_feature_hdr_validate() succeeds,
whereas fields of a LUO_FEAT_OPTIONAL feature should be accessed only
after verifying that the feature is active in the source kernel header:
if (luo_feature_is_active(&ser->features, FEAT_X))
val = ser->x_field;
All current struct luo_ser fields (liveupdate_num, sessions_pa, flbs_pa)
belong to LUO_CORE_FEAT_CORE (LUO_FEAT_REQUIRED in luo_core_features).
Future changes add new entries to enum luo_core_feature before
LUO_CORE_FEAT_MAX.
Signed-off-by: Logan Odell <loganodell@google.com>
Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
include/linux/kho/abi/luo.h | 345 +++++++++++++++++++++++++++++++----
kernel/liveupdate/luo_core.c | 27 ++-
2 files changed, 332 insertions(+), 40 deletions(-)
diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 5fb2f46425d7..be35cfbfdb2b 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -9,19 +9,53 @@
* DOC: Live Update Orchestrator ABI
*
* Live Update Orchestrator uses the stable Application Binary Interface
- * defined below to pass state from a pre-update kernel to a post-update
- * kernel. The ABI is built upon the Kexec HandOver framework and registers
- * the central `struct luo_ser` via the KHO raw subtree API.
+ * defined below to pass state from a source kernel to a target kernel.
+ * The ABI is built upon the Kexec HandOver framework and registers the
+ * central `struct luo_ser` via the KHO raw subtree API.
*
- * This interface is a contract. Any modification to the structure fields,
- * compatible strings, or the layout of the `__packed` serialization
- * structures defined here constitutes a breaking change. Such changes require
- * incrementing the version number in the relevant `_COMPATIBLE` string to
- * prevent a new kernel from misinterpreting data from an old kernel.
+ * This interface is a contract. Serialized structures start with a fixed
+ * 128-byte feature header (`struct luo_feature_hdr`) holding the source
+ * kernel's structure `size` and 15 chained 64-bit feature flags entries
+ * (`flags[LUO_FEAT_NR_FLAGS]`). Each entry encodes up to 31 features as
+ * 2-bit `0b<req><active>` pairs (`enum luo_feat_state`), with
+ * `LUO_FEAT_EXT_MASK` in bits `[63:62]` chaining to the next entry (up to
+ * 465 features total). Because the header size is fixed at 128 bytes,
+ * payload field offsets do not depend on how many flags entries a source
+ * kernel uses.
*
- * Changes are allowed provided the compatibility version is incremented;
- * however, backward/forward compatibility is only guaranteed for kernels
- * supporting the same ABI version.
+ * Truth table (`(req,active)` pairs per feature)::
+ *
+ * Source Target Action
+ * (1,0) (x,x) Reject -EINVAL: invalid source kernel (req without active)
+ * (x,x) (1,0) Reject -EINVAL: invalid target kernel (req without active)
+ * (0,0) (0,0) Accept: feature absent
+ * (0,0) (0,1) Accept: feature absent
+ * (0,0) (1,1) Reject -EOPNOTSUPP: target kernel requires the feature
+ * (0,1) (0,0) Accept: ignore payload data
+ * (1,1) (0,0) Reject -EOPNOTSUPP: source kernel requires the feature
+ * (x,1) (x,1) Accept: use payload data
+ *
+ * The relative order and offsets of fields in serialized structures must
+ * remain unchanged as features are added or retired. New fields are always
+ * appended at the end of the structure under a new feature flag, and
+ * retired fields are kept in place so that the offsets of subsequent
+ * fields never shift. If a field's type or format needs to change, the
+ * existing field and feature are kept intact (and retired when no longer
+ * needed) and a new feature flag with a new appended field of the new type
+ * is added.
+ *
+ * In the target kernel, fields of a `LUO_FEAT_REQUIRED` feature are
+ * guaranteed to be present once `luo_feature_hdr_validate()` succeeds,
+ * whereas fields of a `LUO_FEAT_OPTIONAL` feature should be accessed only
+ * after verifying that the feature is active in the source kernel's header::
+ *
+ * if (luo_feature_is_active(&ser->features, FEAT_X))
+ * val = ser->x_field;
+ *
+ * All current `struct luo_ser` fields (`liveupdate_num`, `sessions_pa`,
+ * `flbs_pa`) belong to `LUO_CORE_FEAT_CORE` (`LUO_FEAT_REQUIRED` in
+ * `luo_core_features`). Future changes add new entries to
+ * `enum luo_core_feature` before `LUO_CORE_FEAT_MAX`.
*
* KHO Structure Overview:
* The entire LUO state is encapsulated within a single KHO entry named "LUO".
@@ -30,8 +64,9 @@
* Serialization Structures:
* - struct luo_ser:
* The central ABI structure that contains the overall state of the LUO.
- * It includes the compatibility string, the liveupdate-number, and pointers
- * to sessions and FLBs.
+ * It includes the feature header (with struct size and 2-bit feature flag
+ * pairs), and the core feature (`LUO_CORE_FEAT_CORE`) fields: the
+ * liveupdate-number and pointers to sessions and FLBs.
*
* - struct luo_session_ser:
* Metadata for a single session, including its name and a physical pointer
@@ -40,8 +75,9 @@
*
* - struct luo_file_ser:
* Metadata for a single preserved file. Contains the handler `name` string
- * to find the correct handler in the new kernel, a user-provided `token`
- * for identification, and an opaque `data` handle for the handler to use.
+ * to find the correct handler in the target kernel, a user-provided
+ * `token` for identification, and an opaque `data` handle for the handler
+ * to use.
*
* - struct luo_flb_header_ser:
* Header for the FLB array. Contains the total page count of the
@@ -57,20 +93,265 @@
#ifndef _LINUX_KHO_ABI_LUO_H
#define _LINUX_KHO_ABI_LUO_H
-#include <linux/align.h>
+#include <linux/array_size.h>
+#include <linux/bits.h>
+#include <linux/bug.h>
+#include <linux/build_bug.h>
+#include <linux/errno.h>
#include <linux/kho/abi/block.h>
+#include <linux/types.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-v5"
-#define LUO_ABI_COMPAT_LEN ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
+
+#define LUO_FEAT_PER_FLAGS 31
+#define LUO_FEAT_NR_FLAGS 15
+#define LUO_FEAT_MAX (LUO_FEAT_NR_FLAGS * LUO_FEAT_PER_FLAGS)
+#define LUO_FEAT_EXT_MASK GENMASK_ULL(63, 62)
+#define LUO_FEAT_ACTIVE_MASK (0x5555555555555555ULL & ~LUO_FEAT_EXT_MASK)
+#define LUO_FEAT_REQ_MASK (0xaaaaaaaaaaaaaaaaULL & ~LUO_FEAT_EXT_MASK)
+
+#define LUO_FEAT_FLAGS_IDX(f) ((unsigned int)(f) / LUO_FEAT_PER_FLAGS)
+#define LUO_FEAT_SLOT(f) ((unsigned int)(f) % LUO_FEAT_PER_FLAGS)
+
+#define LUO_FEAT_ACTIVE(f) BIT_ULL(2 * LUO_FEAT_SLOT(f))
+#define LUO_FEAT_REQ(f) BIT_ULL(2 * LUO_FEAT_SLOT(f) + 1)
+
+/**
+ * enum luo_feat_state - 2-bit state of a LUO feature (`0b<req><active>`).
+ * @LUO_FEAT_DISABLED: Feature is inactive (`req=0, active=0`).
+ * @LUO_FEAT_OPTIONAL: Feature is active and optional (`req=0, active=1`).
+ * @LUO_FEAT_REQUIRED: Feature is active and required (`req=1, active=1`).
+ */
+enum luo_feat_state {
+ LUO_FEAT_DISABLED = 0b00,
+ LUO_FEAT_OPTIONAL = 0b01,
+ LUO_FEAT_REQUIRED = 0b11,
+};
+
+/**
+ * struct luo_feature_hdr - Fixed 128-byte header for serialized LUO ABI structures.
+ * @size: Size in bytes of the source kernel's enclosing serialized structure.
+ * @flags: Chained 64-bit feature flags entries (`0 .. LUO_FEAT_NR_FLAGS - 1`).
+ * Each entry holds 31 2-bit feature pairs (`[2*slot + 1 : 2*slot]`)
+ * plus %LUO_FEAT_EXT_MASK in bits `[63:62]` when the next entry is
+ * active.
+ */
+struct luo_feature_hdr {
+ u64 size;
+ u64 flags[LUO_FEAT_NR_FLAGS];
+} __packed;
+
+static_assert(sizeof(struct luo_feature_hdr) == 128);
+
+/**
+ * luo_feature_hdr_for_each_flags - Iterate over chained flags entries in @hdr.
+ * @hdr: Pointer to the &struct luo_feature_hdr to iterate.
+ * @idx: Loop cursor of type `unsigned int` (`0 .. LUO_FEAT_NR_FLAGS - 1`).
+ */
+#define luo_feature_hdr_for_each_flags(hdr, idx) \
+ for ((idx) = 0; (idx) < LUO_FEAT_NR_FLAGS; \
+ (idx) = (((hdr)->flags[idx] & LUO_FEAT_EXT_MASK) == \
+ LUO_FEAT_EXT_MASK) ? (idx) + 1 : LUO_FEAT_NR_FLAGS)
+
+/**
+ * luo_feature_hdr_get_flags - Get the flags entry containing feature @feat.
+ * @hdr: Pointer to the source kernel's feature header.
+ * @feat: Feature index (`0 .. LUO_FEAT_MAX - 1`).
+ *
+ * Return: The u64 flags entry containing @feat if the extension chain reaches
+ * it, or 0 otherwise.
+ */
+static inline u64
+luo_feature_hdr_get_flags(const struct luo_feature_hdr *hdr,
+ unsigned int feat)
+{
+ unsigned int target_idx = LUO_FEAT_FLAGS_IDX(feat);
+ unsigned int idx;
+
+ if (target_idx >= LUO_FEAT_NR_FLAGS)
+ return 0;
+
+ luo_feature_hdr_for_each_flags(hdr, idx) {
+ if (idx == target_idx)
+ return hdr->flags[idx];
+ }
+
+ return 0;
+}
+
+/**
+ * luo_feature_is_active - Test whether feature @feat is active in @hdr.
+ * @hdr: Pointer to the source kernel's feature header.
+ * @feat: Feature index (`0 .. LUO_FEAT_MAX - 1`).
+ *
+ * Return: true if the source kernel marked @feat active, false otherwise.
+ */
+static inline bool luo_feature_is_active(const struct luo_feature_hdr *hdr,
+ unsigned int feat)
+{
+ return luo_feature_hdr_get_flags(hdr, feat) & LUO_FEAT_ACTIVE(feat);
+}
+
+/**
+ * luo_feature_is_req - Test whether feature @feat is required in @hdr.
+ * @hdr: Pointer to the source kernel's feature header.
+ * @feat: Feature index (`0 .. LUO_FEAT_MAX - 1`).
+ *
+ * Return: true if the source kernel marked @feat required, false otherwise.
+ */
+static inline bool luo_feature_is_req(const struct luo_feature_hdr *hdr,
+ unsigned int feat)
+{
+ return luo_feature_hdr_get_flags(hdr, feat) & LUO_FEAT_REQ(feat);
+}
+
+/**
+ * luo_feature_pack_entry - Pack up to 31 feature states into a flags entry.
+ * @features: Array of &enum luo_feat_state values indexed by feature ID.
+ * @nr_features: Number of elements in @features.
+ * @idx: Flags entry index (`0 .. LUO_FEAT_NR_FLAGS - 1`).
+ *
+ * Return: The packed 64-bit flags entry for @idx (without %LUO_FEAT_EXT_MASK).
+ */
+static inline u64
+luo_feature_pack_entry(const u8 *features, size_t nr_features,
+ unsigned int idx)
+{
+ unsigned int base = idx * LUO_FEAT_PER_FLAGS;
+ unsigned int slot;
+ u64 flags = 0;
+
+ for (slot = 0; slot < LUO_FEAT_PER_FLAGS && base + slot < nr_features;
+ slot++)
+ flags |= (u64)(features[base + slot] & 0b11) << (2 * slot);
+
+ return flags;
+}
+
+/**
+ * __luo_feature_hdr_init - Initialize a serialized LUO feature header.
+ * @hdr: Pointer to the feature header to initialize.
+ * @size: Byte size of the enclosing serialized structure.
+ * @features: Array of &enum luo_feat_state values for the source kernel.
+ * @nr_features: Number of elements in @features (`<= LUO_FEAT_MAX`).
+ */
+static inline void
+__luo_feature_hdr_init(struct luo_feature_hdr *hdr, u64 size,
+ const u8 *features, size_t nr_features)
+{
+ bool ext = false;
+ int idx;
+
+ hdr->size = size;
+ for (idx = LUO_FEAT_NR_FLAGS - 1; idx >= 0; idx--) {
+ u64 flags = luo_feature_pack_entry(features, nr_features, idx);
+
+ WARN_ON_ONCE(((flags & LUO_FEAT_REQ_MASK) >> 1) &
+ ~(flags & LUO_FEAT_ACTIVE_MASK));
+ if (ext)
+ flags |= LUO_FEAT_EXT_MASK;
+ else if (flags)
+ ext = true;
+ hdr->flags[idx] = flags;
+ }
+}
+
+/**
+ * luo_feature_hdr_init - Initialize a serialized LUO feature header.
+ * @hdr: Pointer to the feature header to initialize.
+ * @size: Byte size of the enclosing serialized structure.
+ * @features: Fixed-size `u8` array of &enum luo_feat_state entries.
+ */
+#define luo_feature_hdr_init(hdr, size, features) \
+({ \
+ BUILD_BUG_ON(ARRAY_SIZE(features) > LUO_FEAT_MAX); \
+ __luo_feature_hdr_init(hdr, size, features, \
+ ARRAY_SIZE(features)); \
+})
+
+/**
+ * __luo_feature_hdr_validate - Validate a source kernel's LUO feature header
+ * against the target kernel's feature array.
+ * @hdr: Pointer to the feature header from the source kernel.
+ * @features: Array of &enum luo_feat_state values for the target kernel.
+ * @nr_features: Number of elements in @features (`<= LUO_FEAT_MAX`).
+ *
+ * Return: 0 if compatible, -EINVAL on malformed header or `(req=1, active=0)`
+ * feature state, or -EOPNOTSUPP on required feature mismatch.
+ */
+static inline int
+__luo_feature_hdr_validate(const struct luo_feature_hdr *hdr,
+ const u8 *features, size_t nr_features)
+{
+ bool src_ext = true, target_ext = (nr_features > 0);
+ unsigned int idx;
+
+ if (hdr->size < sizeof(*hdr))
+ return -EINVAL;
+
+ for (idx = 0; idx < LUO_FEAT_NR_FLAGS && (src_ext || target_ext); idx++) {
+ u64 src = src_ext ? hdr->flags[idx] : 0;
+ u64 target = target_ext ?
+ luo_feature_pack_entry(features, nr_features, idx) : 0;
+ u64 ext = src & LUO_FEAT_EXT_MASK;
+ u64 src_act = src & LUO_FEAT_ACTIVE_MASK;
+ u64 src_req = (src & LUO_FEAT_REQ_MASK) >> 1;
+ u64 target_act = target & LUO_FEAT_ACTIVE_MASK;
+ u64 target_req = (target & LUO_FEAT_REQ_MASK) >> 1;
+
+ if (ext && (ext != LUO_FEAT_EXT_MASK ||
+ idx == LUO_FEAT_NR_FLAGS - 1))
+ return -EINVAL;
+ if (src_req & ~src_act)
+ return -EINVAL;
+ if (WARN_ON_ONCE(target_req & ~target_act))
+ return -EINVAL;
+ if ((src_req & ~target_act) || (target_req & ~src_act))
+ return -EOPNOTSUPP;
+
+ src_ext = (ext == LUO_FEAT_EXT_MASK);
+ target_ext = ((idx + 1) * LUO_FEAT_PER_FLAGS < nr_features);
+ }
+
+ return 0;
+}
+
+/**
+ * luo_feature_hdr_validate - Validate a source kernel's LUO feature header
+ * against the target kernel's feature array.
+ * @hdr: Pointer to the feature header from the source kernel.
+ * @features: Fixed-size `u8` array of &enum luo_feat_state entries.
+ *
+ * Return: 0 if compatible, -EINVAL on malformed header or feature state, or
+ * -EOPNOTSUPP on required feature mismatch.
+ */
+#define luo_feature_hdr_validate(hdr, features) \
+({ \
+ BUILD_BUG_ON(ARRAY_SIZE(features) > LUO_FEAT_MAX); \
+ __luo_feature_hdr_validate(hdr, features, \
+ ARRAY_SIZE(features)); \
+})
+
+/**
+ * enum luo_core_feature - Feature indices for &struct luo_ser.
+ * @LUO_CORE_FEAT_CORE: Core feature covering `liveupdate_num`, `sessions_pa`,
+ * and `flbs_pa`.
+ * @LUO_CORE_FEAT_MAX: Number of defined &enum luo_core_feature features.
+ */
+enum luo_core_feature {
+ LUO_CORE_FEAT_CORE = 0,
+ LUO_CORE_FEAT_MAX,
+};
+
+static_assert((int)LUO_CORE_FEAT_MAX <= LUO_FEAT_MAX);
/**
* struct luo_ser - Centralized LUO ABI header.
- * @compatible: Compatibility string identifying the LUO ABI version.
+ * @features: ABI feature and size header for luo_ser.
* @liveupdate_num: A counter tracking the number of successful live updates.
* @sessions_pa: Physical address of the first session block header.
* @flbs_pa: Physical address of the FLB header.
@@ -78,7 +359,7 @@
* This structure is the root of all preserved LUO state.
*/
struct luo_ser {
- char compatible[LUO_ABI_COMPAT_LEN];
+ struct luo_feature_hdr features;
u64 liveupdate_num;
u64 sessions_pa;
u64 flbs_pa;
@@ -90,7 +371,7 @@ struct luo_ser {
* @data: Private data
* @token: User provided token for this file
*
- * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
+ * Layout changes must be gated by a required feature in &enum luo_core_feature.
*/
struct luo_file_ser {
char name[LIVEUPDATE_HNDL_NAME_LENGTH];
@@ -121,10 +402,10 @@ struct luo_file_set_ser {
*
* This structure is used to package session-specific metadata for transfer
* between kernels via Kexec Handover. An array of these structures (one per
- * session) is created and passed to the new kernel, allowing it to reconstruct
- * the session context.
+ * session) is created and passed to the target kernel, allowing it to
+ * reconstruct the session context.
*
- * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
+ * Layout changes must be gated by a required feature in &enum luo_core_feature.
*/
struct luo_session_ser {
char name[LIVEUPDATE_SESSION_NAME_LENGTH];
@@ -145,7 +426,7 @@ struct luo_session_ser {
* This structure is located at the physical address specified by the
* flbs_pa in luo_ser.
*
- * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
+ * Layout changes must be gated by a required feature in &enum luo_core_feature.
*/
struct luo_flb_header_ser {
u64 pgcnt;
@@ -155,19 +436,19 @@ struct luo_flb_header_ser {
/**
* struct luo_flb_ser - Represents the serialized state of a single FLB object.
* @name: The unique name string of the FLB object, used to find the
- * corresponding &struct liveupdate_flb handler in the new kernel.
+ * corresponding &struct liveupdate_flb in the target kernel.
* @data: The opaque u64 handle returned by the FLB's .preserve() operation
- * in the old kernel. This handle encapsulates the entire state needed
- * for restoration.
+ * in the source kernel. This handle encapsulates the entire state
+ * needed for restoration.
* @count: The reference count at the time of serialization; i.e., the number
* of preserved files that depended on this FLB. This is used by the
- * new kernel to correctly manage the FLB's lifecycle.
+ * target kernel to correctly manage the FLB's lifecycle.
*
* An array of these structures is created in a preserved memory region and
- * passed to the new kernel. Each entry allows the LUO core to restore one
- * global, shared object.
+ * passed to the target kernel. Each entry allows the LUO core to restore
+ * one global, shared object.
*
- * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
+ * Layout changes must be gated by a required feature in &enum luo_core_feature.
*/
struct luo_flb_ser {
char name[LIVEUPDATE_FLB_NAME_LENGTH];
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 1b2bda22902d..3627a206a79c 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -70,6 +70,10 @@ static struct {
u64 liveupdate_num;
} luo_global;
+static const u8 luo_core_features[LUO_CORE_FEAT_MAX] = {
+ [LUO_CORE_FEAT_CORE] = LUO_FEAT_REQUIRED,
+};
+
/*
* luo_register_rwlock - Protects registration of file handlers and FLBs.
*/
@@ -107,15 +111,21 @@ static int __init luo_early_startup(void)
return 0;
}
- if (len < sizeof(*luo_ser)) {
- pr_err("LUO state is too small (%zu < %zu)\n", len, sizeof(*luo_ser));
- return -EINVAL;
+ luo_ser = phys_to_virt(luo_ser_phys);
+ if (len < sizeof(luo_ser->features) || luo_ser->features.size > len) {
+ pr_err("LUO state size invalid (source kernel header size=%llu, KHO len=%zu)\n",
+ len >= sizeof(luo_ser->features) ? luo_ser->features.size : 0,
+ len);
+ err = -EINVAL;
+ goto out_free_ser;
}
- luo_ser = phys_to_virt(luo_ser_phys);
- if (strncmp(luo_ser->compatible, LUO_ABI_COMPATIBLE, LUO_ABI_COMPAT_LEN)) {
- pr_err("LUO state is incompatible with '%s'\n", LUO_ABI_COMPATIBLE);
- return -EINVAL;
+ err = luo_feature_hdr_validate(&luo_ser->features, luo_core_features);
+ if (err) {
+ pr_err("LUO state feature header validation failed (size=%llu, flags[0]=%#llx): %pe\n",
+ luo_ser->features.size, luo_ser->features.flags[0],
+ ERR_PTR(err));
+ goto out_free_ser;
}
luo_global.liveupdate_num = luo_ser->liveupdate_num;
@@ -162,7 +172,8 @@ static int __init luo_state_setup(void)
return PTR_ERR(luo_ser);
}
- strscpy(luo_ser->compatible, LUO_ABI_COMPATIBLE, sizeof(luo_ser->compatible));
+ luo_feature_hdr_init(&luo_ser->features, sizeof(*luo_ser),
+ luo_core_features);
luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
luo_session_setup_outgoing(&luo_ser->sessions_pa);
--
2.56.0.212.g129ede20d9-goog
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header
2026-10-21 15:25 ` [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header Tarun Sahu
@ 2026-10-21 17:27 ` Pasha Tatashin
2026-10-21 17:40 ` Jason Gunthorpe
2026-10-21 18:40 ` tarunsahu
2026-10-21 23:55 ` David Matlack
1 sibling, 2 replies; 27+ messages in thread
From: Pasha Tatashin @ 2026-10-21 17:27 UTC (permalink / raw)
To: Tarun Sahu
Cc: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
David Matlack, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
On Wed, Oct 21, 2026 at 11:25 AM Tarun Sahu <tarunsahu@google.com> wrote:
>
> LUO and its file handlers validate handed-over state with monolithic
> compatibility strings ("luo-v5", "memfd-v2"). Any change to a serialized
> structure, even an optional additive one, must bump the string, which
> breaks live update between adjacent kernels. Replace the string in
> struct luo_ser with a feature header, so that the target kernel can
> accept or reject the source kernel state per feature.
>
> struct luo_feature_hdr is a fixed 128-byte header: the serialized
This patch is all wrong; 128 bytes is far too large. For every _ser
struct, we only need 8 bytes instead.
The first 4 bytes are the size, and the next 4 bytes are flags (i.e.,
30 feature flags + 2 reserved bits for flags extension). The last two
bits of the flags indicate whether an extended flags field is present.
Each extended flags field is 8 bytes, and its last two bits indicate
that yet another extended flags field follows.
> structure size from the source kernel followed by 15 chained u64 feature
> flags entries. The fixed size keeps payload field offsets identical in
> every kernel, regardless of how many flags entries a source kernel uses.
>
> Each feature has a 2-bit pair (req, active) state (enum luo_feat_state:
> LUO_FEAT_DISABLED, LUO_FEAT_OPTIONAL, LUO_FEAT_REQUIRED). A flags entry
> holds 31 features, and bits [63:62] = 0b11 chain to the next entry (465
> features in total). Each subsystem defines its feature states as a
> static const u8 array indexed by its feature enum, which
> luo_feature_hdr_init() packs into 64-bit flags entries with
> LUO_FEAT_EXT_MASK chain bits. The target kernel validates the header from
> the source kernel against its own feature array via
> luo_feature_hdr_validate() according to the per-feature (req, active)
> truth table:
>
> Source Target Action
> (1,0) (x,x) Reject -EINVAL: invalid source kernel (req without active)
> (x,x) (1,0) Reject -EINVAL + WARN_ON_ONCE: invalid target kernel
> (0,0) (0,0) Accept: feature absent
> (0,0) (0,1) Accept: feature absent
> (0,0) (1,1) Reject -EOPNOTSUPP: target kernel requires the feature
> (0,1) (0,0) Accept: ignore payload data
> (1,1) (0,0) Reject -EOPNOTSUPP: source kernel requires the feature
> (x,1) (x,1) Accept: use payload data
>
> The relative order and offsets of fields in serialized structures must
> remain unchanged as features are added or retired. New fields are always
> appended at the end of the structure under a new feature flag, and
> retired fields are kept in place so that the offsets of subsequent
> fields never shift. If the type or format of a field needs to change, the
> existing field and feature are kept intact (and retired when no longer
> needed) and a new feature flag with a new appended field of the new type
> is added.
>
> In the target kernel, fields of a LUO_FEAT_REQUIRED feature are
> guaranteed to be present once luo_feature_hdr_validate() succeeds,
> whereas fields of a LUO_FEAT_OPTIONAL feature should be accessed only
> after verifying that the feature is active in the source kernel header:
>
> if (luo_feature_is_active(&ser->features, FEAT_X))
> val = ser->x_field;
>
> All current struct luo_ser fields (liveupdate_num, sessions_pa, flbs_pa)
> belong to LUO_CORE_FEAT_CORE (LUO_FEAT_REQUIRED in luo_core_features).
> Future changes add new entries to enum luo_core_feature before
> LUO_CORE_FEAT_MAX.
>
> Signed-off-by: Logan Odell <loganodell@google.com>
> Signed-off-by: Tarun Sahu <tarunsahu@google.com>
> ---
> include/linux/kho/abi/luo.h | 345 +++++++++++++++++++++++++++++++----
> kernel/liveupdate/luo_core.c | 27 ++-
> 2 files changed, 332 insertions(+), 40 deletions(-)
>
> diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
> index 5fb2f46425d7..be35cfbfdb2b 100644
> --- a/include/linux/kho/abi/luo.h
> +++ b/include/linux/kho/abi/luo.h
> @@ -9,19 +9,53 @@
> * DOC: Live Update Orchestrator ABI
> *
> * Live Update Orchestrator uses the stable Application Binary Interface
> - * defined below to pass state from a pre-update kernel to a post-update
> - * kernel. The ABI is built upon the Kexec HandOver framework and registers
> - * the central `struct luo_ser` via the KHO raw subtree API.
> + * defined below to pass state from a source kernel to a target kernel.
> + * The ABI is built upon the Kexec HandOver framework and registers the
> + * central `struct luo_ser` via the KHO raw subtree API.
> *
> - * This interface is a contract. Any modification to the structure fields,
> - * compatible strings, or the layout of the `__packed` serialization
> - * structures defined here constitutes a breaking change. Such changes require
> - * incrementing the version number in the relevant `_COMPATIBLE` string to
> - * prevent a new kernel from misinterpreting data from an old kernel.
> + * This interface is a contract. Serialized structures start with a fixed
> + * 128-byte feature header (`struct luo_feature_hdr`) holding the source
> + * kernel's structure `size` and 15 chained 64-bit feature flags entries
> + * (`flags[LUO_FEAT_NR_FLAGS]`). Each entry encodes up to 31 features as
> + * 2-bit `0b<req><active>` pairs (`enum luo_feat_state`), with
> + * `LUO_FEAT_EXT_MASK` in bits `[63:62]` chaining to the next entry (up to
> + * 465 features total). Because the header size is fixed at 128 bytes,
> + * payload field offsets do not depend on how many flags entries a source
> + * kernel uses.
> *
> - * Changes are allowed provided the compatibility version is incremented;
> - * however, backward/forward compatibility is only guaranteed for kernels
> - * supporting the same ABI version.
> + * Truth table (`(req,active)` pairs per feature)::
> + *
> + * Source Target Action
> + * (1,0) (x,x) Reject -EINVAL: invalid source kernel (req without active)
> + * (x,x) (1,0) Reject -EINVAL: invalid target kernel (req without active)
> + * (0,0) (0,0) Accept: feature absent
> + * (0,0) (0,1) Accept: feature absent
> + * (0,0) (1,1) Reject -EOPNOTSUPP: target kernel requires the feature
> + * (0,1) (0,0) Accept: ignore payload data
> + * (1,1) (0,0) Reject -EOPNOTSUPP: source kernel requires the feature
> + * (x,1) (x,1) Accept: use payload data
> + *
> + * The relative order and offsets of fields in serialized structures must
> + * remain unchanged as features are added or retired. New fields are always
> + * appended at the end of the structure under a new feature flag, and
> + * retired fields are kept in place so that the offsets of subsequent
> + * fields never shift. If a field's type or format needs to change, the
> + * existing field and feature are kept intact (and retired when no longer
> + * needed) and a new feature flag with a new appended field of the new type
> + * is added.
> + *
> + * In the target kernel, fields of a `LUO_FEAT_REQUIRED` feature are
> + * guaranteed to be present once `luo_feature_hdr_validate()` succeeds,
> + * whereas fields of a `LUO_FEAT_OPTIONAL` feature should be accessed only
> + * after verifying that the feature is active in the source kernel's header::
> + *
> + * if (luo_feature_is_active(&ser->features, FEAT_X))
> + * val = ser->x_field;
> + *
> + * All current `struct luo_ser` fields (`liveupdate_num`, `sessions_pa`,
> + * `flbs_pa`) belong to `LUO_CORE_FEAT_CORE` (`LUO_FEAT_REQUIRED` in
> + * `luo_core_features`). Future changes add new entries to
> + * `enum luo_core_feature` before `LUO_CORE_FEAT_MAX`.
> *
> * KHO Structure Overview:
> * The entire LUO state is encapsulated within a single KHO entry named "LUO".
> @@ -30,8 +64,9 @@
> * Serialization Structures:
> * - struct luo_ser:
> * The central ABI structure that contains the overall state of the LUO.
> - * It includes the compatibility string, the liveupdate-number, and pointers
> - * to sessions and FLBs.
> + * It includes the feature header (with struct size and 2-bit feature flag
> + * pairs), and the core feature (`LUO_CORE_FEAT_CORE`) fields: the
> + * liveupdate-number and pointers to sessions and FLBs.
> *
> * - struct luo_session_ser:
> * Metadata for a single session, including its name and a physical pointer
> @@ -40,8 +75,9 @@
> *
> * - struct luo_file_ser:
> * Metadata for a single preserved file. Contains the handler `name` string
> - * to find the correct handler in the new kernel, a user-provided `token`
> - * for identification, and an opaque `data` handle for the handler to use.
> + * to find the correct handler in the target kernel, a user-provided
> + * `token` for identification, and an opaque `data` handle for the handler
> + * to use.
> *
> * - struct luo_flb_header_ser:
> * Header for the FLB array. Contains the total page count of the
> @@ -57,20 +93,265 @@
> #ifndef _LINUX_KHO_ABI_LUO_H
> #define _LINUX_KHO_ABI_LUO_H
>
> -#include <linux/align.h>
> +#include <linux/array_size.h>
> +#include <linux/bits.h>
> +#include <linux/bug.h>
> +#include <linux/build_bug.h>
> +#include <linux/errno.h>
> #include <linux/kho/abi/block.h>
> +#include <linux/types.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-v5"
> -#define LUO_ABI_COMPAT_LEN ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
> +
> +#define LUO_FEAT_PER_FLAGS 31
> +#define LUO_FEAT_NR_FLAGS 15
> +#define LUO_FEAT_MAX (LUO_FEAT_NR_FLAGS * LUO_FEAT_PER_FLAGS)
> +#define LUO_FEAT_EXT_MASK GENMASK_ULL(63, 62)
> +#define LUO_FEAT_ACTIVE_MASK (0x5555555555555555ULL & ~LUO_FEAT_EXT_MASK)
> +#define LUO_FEAT_REQ_MASK (0xaaaaaaaaaaaaaaaaULL & ~LUO_FEAT_EXT_MASK)
> +
> +#define LUO_FEAT_FLAGS_IDX(f) ((unsigned int)(f) / LUO_FEAT_PER_FLAGS)
> +#define LUO_FEAT_SLOT(f) ((unsigned int)(f) % LUO_FEAT_PER_FLAGS)
> +
> +#define LUO_FEAT_ACTIVE(f) BIT_ULL(2 * LUO_FEAT_SLOT(f))
> +#define LUO_FEAT_REQ(f) BIT_ULL(2 * LUO_FEAT_SLOT(f) + 1)
> +
> +/**
> + * enum luo_feat_state - 2-bit state of a LUO feature (`0b<req><active>`).
> + * @LUO_FEAT_DISABLED: Feature is inactive (`req=0, active=0`).
> + * @LUO_FEAT_OPTIONAL: Feature is active and optional (`req=0, active=1`).
> + * @LUO_FEAT_REQUIRED: Feature is active and required (`req=1, active=1`).
> + */
> +enum luo_feat_state {
> + LUO_FEAT_DISABLED = 0b00,
> + LUO_FEAT_OPTIONAL = 0b01,
> + LUO_FEAT_REQUIRED = 0b11,
> +};
> +
> +/**
> + * struct luo_feature_hdr - Fixed 128-byte header for serialized LUO ABI structures.
> + * @size: Size in bytes of the source kernel's enclosing serialized structure.
> + * @flags: Chained 64-bit feature flags entries (`0 .. LUO_FEAT_NR_FLAGS - 1`).
> + * Each entry holds 31 2-bit feature pairs (`[2*slot + 1 : 2*slot]`)
> + * plus %LUO_FEAT_EXT_MASK in bits `[63:62]` when the next entry is
> + * active.
> + */
> +struct luo_feature_hdr {
> + u64 size;
> + u64 flags[LUO_FEAT_NR_FLAGS];
> +} __packed;
> +
> +static_assert(sizeof(struct luo_feature_hdr) == 128);
> +
> +/**
> + * luo_feature_hdr_for_each_flags - Iterate over chained flags entries in @hdr.
> + * @hdr: Pointer to the &struct luo_feature_hdr to iterate.
> + * @idx: Loop cursor of type `unsigned int` (`0 .. LUO_FEAT_NR_FLAGS - 1`).
> + */
> +#define luo_feature_hdr_for_each_flags(hdr, idx) \
> + for ((idx) = 0; (idx) < LUO_FEAT_NR_FLAGS; \
> + (idx) = (((hdr)->flags[idx] & LUO_FEAT_EXT_MASK) == \
> + LUO_FEAT_EXT_MASK) ? (idx) + 1 : LUO_FEAT_NR_FLAGS)
> +
> +/**
> + * luo_feature_hdr_get_flags - Get the flags entry containing feature @feat.
> + * @hdr: Pointer to the source kernel's feature header.
> + * @feat: Feature index (`0 .. LUO_FEAT_MAX - 1`).
> + *
> + * Return: The u64 flags entry containing @feat if the extension chain reaches
> + * it, or 0 otherwise.
> + */
> +static inline u64
> +luo_feature_hdr_get_flags(const struct luo_feature_hdr *hdr,
> + unsigned int feat)
> +{
> + unsigned int target_idx = LUO_FEAT_FLAGS_IDX(feat);
> + unsigned int idx;
> +
> + if (target_idx >= LUO_FEAT_NR_FLAGS)
> + return 0;
> +
> + luo_feature_hdr_for_each_flags(hdr, idx) {
> + if (idx == target_idx)
> + return hdr->flags[idx];
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * luo_feature_is_active - Test whether feature @feat is active in @hdr.
> + * @hdr: Pointer to the source kernel's feature header.
> + * @feat: Feature index (`0 .. LUO_FEAT_MAX - 1`).
> + *
> + * Return: true if the source kernel marked @feat active, false otherwise.
> + */
> +static inline bool luo_feature_is_active(const struct luo_feature_hdr *hdr,
> + unsigned int feat)
> +{
> + return luo_feature_hdr_get_flags(hdr, feat) & LUO_FEAT_ACTIVE(feat);
> +}
> +
> +/**
> + * luo_feature_is_req - Test whether feature @feat is required in @hdr.
> + * @hdr: Pointer to the source kernel's feature header.
> + * @feat: Feature index (`0 .. LUO_FEAT_MAX - 1`).
> + *
> + * Return: true if the source kernel marked @feat required, false otherwise.
> + */
> +static inline bool luo_feature_is_req(const struct luo_feature_hdr *hdr,
> + unsigned int feat)
> +{
> + return luo_feature_hdr_get_flags(hdr, feat) & LUO_FEAT_REQ(feat);
> +}
> +
> +/**
> + * luo_feature_pack_entry - Pack up to 31 feature states into a flags entry.
> + * @features: Array of &enum luo_feat_state values indexed by feature ID.
> + * @nr_features: Number of elements in @features.
> + * @idx: Flags entry index (`0 .. LUO_FEAT_NR_FLAGS - 1`).
> + *
> + * Return: The packed 64-bit flags entry for @idx (without %LUO_FEAT_EXT_MASK).
> + */
> +static inline u64
> +luo_feature_pack_entry(const u8 *features, size_t nr_features,
> + unsigned int idx)
> +{
> + unsigned int base = idx * LUO_FEAT_PER_FLAGS;
> + unsigned int slot;
> + u64 flags = 0;
> +
> + for (slot = 0; slot < LUO_FEAT_PER_FLAGS && base + slot < nr_features;
> + slot++)
> + flags |= (u64)(features[base + slot] & 0b11) << (2 * slot);
> +
> + return flags;
> +}
> +
> +/**
> + * __luo_feature_hdr_init - Initialize a serialized LUO feature header.
> + * @hdr: Pointer to the feature header to initialize.
> + * @size: Byte size of the enclosing serialized structure.
> + * @features: Array of &enum luo_feat_state values for the source kernel.
> + * @nr_features: Number of elements in @features (`<= LUO_FEAT_MAX`).
> + */
> +static inline void
> +__luo_feature_hdr_init(struct luo_feature_hdr *hdr, u64 size,
> + const u8 *features, size_t nr_features)
> +{
> + bool ext = false;
> + int idx;
> +
> + hdr->size = size;
> + for (idx = LUO_FEAT_NR_FLAGS - 1; idx >= 0; idx--) {
> + u64 flags = luo_feature_pack_entry(features, nr_features, idx);
> +
> + WARN_ON_ONCE(((flags & LUO_FEAT_REQ_MASK) >> 1) &
> + ~(flags & LUO_FEAT_ACTIVE_MASK));
> + if (ext)
> + flags |= LUO_FEAT_EXT_MASK;
> + else if (flags)
> + ext = true;
> + hdr->flags[idx] = flags;
> + }
> +}
> +
> +/**
> + * luo_feature_hdr_init - Initialize a serialized LUO feature header.
> + * @hdr: Pointer to the feature header to initialize.
> + * @size: Byte size of the enclosing serialized structure.
> + * @features: Fixed-size `u8` array of &enum luo_feat_state entries.
> + */
> +#define luo_feature_hdr_init(hdr, size, features) \
> +({ \
> + BUILD_BUG_ON(ARRAY_SIZE(features) > LUO_FEAT_MAX); \
> + __luo_feature_hdr_init(hdr, size, features, \
> + ARRAY_SIZE(features)); \
> +})
> +
> +/**
> + * __luo_feature_hdr_validate - Validate a source kernel's LUO feature header
> + * against the target kernel's feature array.
> + * @hdr: Pointer to the feature header from the source kernel.
> + * @features: Array of &enum luo_feat_state values for the target kernel.
> + * @nr_features: Number of elements in @features (`<= LUO_FEAT_MAX`).
> + *
> + * Return: 0 if compatible, -EINVAL on malformed header or `(req=1, active=0)`
> + * feature state, or -EOPNOTSUPP on required feature mismatch.
> + */
> +static inline int
> +__luo_feature_hdr_validate(const struct luo_feature_hdr *hdr,
> + const u8 *features, size_t nr_features)
> +{
> + bool src_ext = true, target_ext = (nr_features > 0);
> + unsigned int idx;
> +
> + if (hdr->size < sizeof(*hdr))
> + return -EINVAL;
> +
> + for (idx = 0; idx < LUO_FEAT_NR_FLAGS && (src_ext || target_ext); idx++) {
> + u64 src = src_ext ? hdr->flags[idx] : 0;
> + u64 target = target_ext ?
> + luo_feature_pack_entry(features, nr_features, idx) : 0;
> + u64 ext = src & LUO_FEAT_EXT_MASK;
> + u64 src_act = src & LUO_FEAT_ACTIVE_MASK;
> + u64 src_req = (src & LUO_FEAT_REQ_MASK) >> 1;
> + u64 target_act = target & LUO_FEAT_ACTIVE_MASK;
> + u64 target_req = (target & LUO_FEAT_REQ_MASK) >> 1;
> +
> + if (ext && (ext != LUO_FEAT_EXT_MASK ||
> + idx == LUO_FEAT_NR_FLAGS - 1))
> + return -EINVAL;
> + if (src_req & ~src_act)
> + return -EINVAL;
> + if (WARN_ON_ONCE(target_req & ~target_act))
> + return -EINVAL;
> + if ((src_req & ~target_act) || (target_req & ~src_act))
> + return -EOPNOTSUPP;
> +
> + src_ext = (ext == LUO_FEAT_EXT_MASK);
> + target_ext = ((idx + 1) * LUO_FEAT_PER_FLAGS < nr_features);
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * luo_feature_hdr_validate - Validate a source kernel's LUO feature header
> + * against the target kernel's feature array.
> + * @hdr: Pointer to the feature header from the source kernel.
> + * @features: Fixed-size `u8` array of &enum luo_feat_state entries.
> + *
> + * Return: 0 if compatible, -EINVAL on malformed header or feature state, or
> + * -EOPNOTSUPP on required feature mismatch.
> + */
> +#define luo_feature_hdr_validate(hdr, features) \
> +({ \
> + BUILD_BUG_ON(ARRAY_SIZE(features) > LUO_FEAT_MAX); \
> + __luo_feature_hdr_validate(hdr, features, \
> + ARRAY_SIZE(features)); \
> +})
> +
> +/**
> + * enum luo_core_feature - Feature indices for &struct luo_ser.
> + * @LUO_CORE_FEAT_CORE: Core feature covering `liveupdate_num`, `sessions_pa`,
> + * and `flbs_pa`.
> + * @LUO_CORE_FEAT_MAX: Number of defined &enum luo_core_feature features.
> + */
> +enum luo_core_feature {
> + LUO_CORE_FEAT_CORE = 0,
> + LUO_CORE_FEAT_MAX,
> +};
> +
> +static_assert((int)LUO_CORE_FEAT_MAX <= LUO_FEAT_MAX);
>
> /**
> * struct luo_ser - Centralized LUO ABI header.
> - * @compatible: Compatibility string identifying the LUO ABI version.
> + * @features: ABI feature and size header for luo_ser.
> * @liveupdate_num: A counter tracking the number of successful live updates.
> * @sessions_pa: Physical address of the first session block header.
> * @flbs_pa: Physical address of the FLB header.
> @@ -78,7 +359,7 @@
> * This structure is the root of all preserved LUO state.
> */
> struct luo_ser {
> - char compatible[LUO_ABI_COMPAT_LEN];
> + struct luo_feature_hdr features;
> u64 liveupdate_num;
> u64 sessions_pa;
> u64 flbs_pa;
> @@ -90,7 +371,7 @@ struct luo_ser {
> * @data: Private data
> * @token: User provided token for this file
> *
> - * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
> + * Layout changes must be gated by a required feature in &enum luo_core_feature.
> */
> struct luo_file_ser {
> char name[LIVEUPDATE_HNDL_NAME_LENGTH];
> @@ -121,10 +402,10 @@ struct luo_file_set_ser {
> *
> * This structure is used to package session-specific metadata for transfer
> * between kernels via Kexec Handover. An array of these structures (one per
> - * session) is created and passed to the new kernel, allowing it to reconstruct
> - * the session context.
> + * session) is created and passed to the target kernel, allowing it to
> + * reconstruct the session context.
> *
> - * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
> + * Layout changes must be gated by a required feature in &enum luo_core_feature.
> */
> struct luo_session_ser {
> char name[LIVEUPDATE_SESSION_NAME_LENGTH];
> @@ -145,7 +426,7 @@ struct luo_session_ser {
> * This structure is located at the physical address specified by the
> * flbs_pa in luo_ser.
> *
> - * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
> + * Layout changes must be gated by a required feature in &enum luo_core_feature.
> */
> struct luo_flb_header_ser {
> u64 pgcnt;
> @@ -155,19 +436,19 @@ struct luo_flb_header_ser {
> /**
> * struct luo_flb_ser - Represents the serialized state of a single FLB object.
> * @name: The unique name string of the FLB object, used to find the
> - * corresponding &struct liveupdate_flb handler in the new kernel.
> + * corresponding &struct liveupdate_flb in the target kernel.
> * @data: The opaque u64 handle returned by the FLB's .preserve() operation
> - * in the old kernel. This handle encapsulates the entire state needed
> - * for restoration.
> + * in the source kernel. This handle encapsulates the entire state
> + * needed for restoration.
> * @count: The reference count at the time of serialization; i.e., the number
> * of preserved files that depended on this FLB. This is used by the
> - * new kernel to correctly manage the FLB's lifecycle.
> + * target kernel to correctly manage the FLB's lifecycle.
> *
> * An array of these structures is created in a preserved memory region and
> - * passed to the new kernel. Each entry allows the LUO core to restore one
> - * global, shared object.
> + * passed to the target kernel. Each entry allows the LUO core to restore
> + * one global, shared object.
> *
> - * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
> + * Layout changes must be gated by a required feature in &enum luo_core_feature.
> */
> struct luo_flb_ser {
> char name[LIVEUPDATE_FLB_NAME_LENGTH];
> diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
> index 1b2bda22902d..3627a206a79c 100644
> --- a/kernel/liveupdate/luo_core.c
> +++ b/kernel/liveupdate/luo_core.c
> @@ -70,6 +70,10 @@ static struct {
> u64 liveupdate_num;
> } luo_global;
>
> +static const u8 luo_core_features[LUO_CORE_FEAT_MAX] = {
> + [LUO_CORE_FEAT_CORE] = LUO_FEAT_REQUIRED,
> +};
> +
> /*
> * luo_register_rwlock - Protects registration of file handlers and FLBs.
> */
> @@ -107,15 +111,21 @@ static int __init luo_early_startup(void)
> return 0;
> }
>
> - if (len < sizeof(*luo_ser)) {
> - pr_err("LUO state is too small (%zu < %zu)\n", len, sizeof(*luo_ser));
> - return -EINVAL;
> + luo_ser = phys_to_virt(luo_ser_phys);
> + if (len < sizeof(luo_ser->features) || luo_ser->features.size > len) {
> + pr_err("LUO state size invalid (source kernel header size=%llu, KHO len=%zu)\n",
> + len >= sizeof(luo_ser->features) ? luo_ser->features.size : 0,
> + len);
> + err = -EINVAL;
> + goto out_free_ser;
> }
>
> - luo_ser = phys_to_virt(luo_ser_phys);
> - if (strncmp(luo_ser->compatible, LUO_ABI_COMPATIBLE, LUO_ABI_COMPAT_LEN)) {
> - pr_err("LUO state is incompatible with '%s'\n", LUO_ABI_COMPATIBLE);
> - return -EINVAL;
> + err = luo_feature_hdr_validate(&luo_ser->features, luo_core_features);
> + if (err) {
> + pr_err("LUO state feature header validation failed (size=%llu, flags[0]=%#llx): %pe\n",
> + luo_ser->features.size, luo_ser->features.flags[0],
> + ERR_PTR(err));
> + goto out_free_ser;
> }
>
> luo_global.liveupdate_num = luo_ser->liveupdate_num;
> @@ -162,7 +172,8 @@ static int __init luo_state_setup(void)
> return PTR_ERR(luo_ser);
> }
>
> - strscpy(luo_ser->compatible, LUO_ABI_COMPATIBLE, sizeof(luo_ser->compatible));
> + luo_feature_hdr_init(&luo_ser->features, sizeof(*luo_ser),
> + luo_core_features);
> luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
>
> luo_session_setup_outgoing(&luo_ser->sessions_pa);
> --
> 2.56.0.212.g129ede20d9-goog
>
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header
2026-10-21 17:27 ` Pasha Tatashin
@ 2026-10-21 17:40 ` Jason Gunthorpe
2026-10-21 17:55 ` Pasha Tatashin
2026-10-21 18:40 ` tarunsahu
1 sibling, 1 reply; 27+ messages in thread
From: Jason Gunthorpe @ 2026-10-21 17:40 UTC (permalink / raw)
To: Pasha Tatashin
Cc: Tarun Sahu, Andrew Morton, joro, Mike Rapoport, Pratyush Yadav,
maz, arnd, David Matlack, robin.murphy, loganodell, oupton,
kevin.tian, alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
On Wed, Oct 21, 2026 at 01:27:22PM -0400, Pasha Tatashin wrote:
> On Wed, Oct 21, 2026 at 11:25 AM Tarun Sahu <tarunsahu@google.com> wrote:
> >
> > LUO and its file handlers validate handed-over state with monolithic
> > compatibility strings ("luo-v5", "memfd-v2"). Any change to a serialized
> > structure, even an optional additive one, must bump the string, which
> > breaks live update between adjacent kernels. Replace the string in
> > struct luo_ser with a feature header, so that the target kernel can
> > accept or reject the source kernel state per feature.
> >
> > struct luo_feature_hdr is a fixed 128-byte header: the serialized
>
> This patch is all wrong; 128 bytes is far too large. For every _ser
> struct, we only need 8 bytes instead.
>
> The first 4 bytes are the size, and the next 4 bytes are flags (i.e.,
> 30 feature flags + 2 reserved bits for flags extension). The last two
> bits of the flags indicate whether an extended flags field is present.
> Each extended flags field is 8 bytes, and its last two bits indicate
> that yet another extended flags field follows.
I thought we agreed not to loose the string too, it was a tree
string -> feature flags -> individual values
The string is the emergency if we really badly mess it up.
Jason
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header
2026-10-21 17:40 ` Jason Gunthorpe
@ 2026-10-21 17:55 ` Pasha Tatashin
0 siblings, 0 replies; 27+ messages in thread
From: Pasha Tatashin @ 2026-10-21 17:55 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: Tarun Sahu, Andrew Morton, joro, Mike Rapoport, Pratyush Yadav,
maz, arnd, David Matlack, robin.murphy, loganodell, oupton,
kevin.tian, alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
On Wed, Oct 21, 2026 at 1:40 PM Jason Gunthorpe <jgg@nvidia.com> wrote:
>
> On Wed, Oct 21, 2026 at 01:27:22PM -0400, Pasha Tatashin wrote:
> > On Wed, Oct 21, 2026 at 11:25 AM Tarun Sahu <tarunsahu@google.com> wrote:
> > >
> > > LUO and its file handlers validate handed-over state with monolithic
> > > compatibility strings ("luo-v5", "memfd-v2"). Any change to a serialized
> > > structure, even an optional additive one, must bump the string, which
> > > breaks live update between adjacent kernels. Replace the string in
> > > struct luo_ser with a feature header, so that the target kernel can
> > > accept or reject the source kernel state per feature.
> > >
> > > struct luo_feature_hdr is a fixed 128-byte header: the serialized
> >
> > This patch is all wrong; 128 bytes is far too large. For every _ser
> > struct, we only need 8 bytes instead.
> >
> > The first 4 bytes are the size, and the next 4 bytes are flags (i.e.,
> > 30 feature flags + 2 reserved bits for flags extension). The last two
> > bits of the flags indicate whether an extended flags field is present.
> > Each extended flags field is 8 bytes, and its last two bits indicate
> > that yet another extended flags field follows.
>
> I thought we agreed not to loose the string too, it was a tree
> string -> feature flags -> individual values
>
> The string is the emergency if we really badly mess it up.
Yes, we'll still have the file-handlers/flbs compatibility strings.
>
> Jason
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header
2026-10-21 17:27 ` Pasha Tatashin
2026-10-21 17:40 ` Jason Gunthorpe
@ 2026-10-21 18:40 ` tarunsahu
2026-10-21 18:46 ` David Matlack
1 sibling, 1 reply; 27+ messages in thread
From: tarunsahu @ 2026-10-21 18:40 UTC (permalink / raw)
To: Pasha Tatashin
Cc: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
David Matlack, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
Pasha Tatashin <pasha.tatashin@soleen.com> writes:
> On Wed, Oct 21, 2026 at 11:25 AM Tarun Sahu <tarunsahu@google.com> wrote:
>>
>> LUO and its file handlers validate handed-over state with monolithic
>> compatibility strings ("luo-v5", "memfd-v2"). Any change to a serialized
>> structure, even an optional additive one, must bump the string, which
>> breaks live update between adjacent kernels. Replace the string in
>> struct luo_ser with a feature header, so that the target kernel can
>> accept or reject the source kernel state per feature.
>>
>> struct luo_feature_hdr is a fixed 128-byte header: the serialized
>
> This patch is all wrong; 128 bytes is far too large. For every _ser
> struct, we only need 8 bytes instead.
NOO, It is not. It is intended to avoid complex mess with unnecessary
over-engineering.
>
> The first 4 bytes are the size, and the next 4 bytes are flags (i.e.,
> 30 feature flags + 2 reserved bits for flags extension). The last two
> bits of the flags indicate whether an extended flags field is present.
> Each extended flags field is 8 bytes, and its last two bits indicate
> that yet another extended flags field follows.
If we will have dynamic extension of flags, Think about this.
Source Kernel:
struct ser_ABI {
struct luo_feature_hdr features; // 8 bytes
type1 field1;
type2 field2;
type3 field3[I_AM_ARRAY];
}
Target Kernel:
struct ser_ABI {
struct luo_feature_hdr features; // 12 bytes
type1 field1;
type2 field2; // this feature is deprecated and optional
type3 field3[I_AM_ARRAY];
type4 field4; // this is new feature
}
Deserialization in target kernel:
Direct Casting is impossible!!
- First It will validate the features. Done!
- Get a start_size variable initialized to 0.
- Then it will caclucalte the header size to know where the next field
in ser_ABI structure starts at. start_size += sizeof(features) Ohh!
this is wrong, so start_size += sizeof(payload->features) But there is
no way to directly know the size of payload->features as direct
casting cant be done with target kernel structure. So count the flags
in the payload feature based on EXTENSION Bit set. start_size +=
calculated_payload_features_size
- now cast this new location with type1 field1 = copy of the (type1)start_size;
- now start_size += sizeof(type1)
And So On! And direct casting is not possible so we will be doing
memcpy (performance regression)
this is how every subsytem will implement their retrieval
function. Very messy and I am completely against it. It will become more
complex when rollback will be considered in this situation as well.
Here is the example of rollback,
Source Kernel:
struct ser_ABI {
struct luo_feature_hdr features; // 12 bytes
type1 field1;
type2 field2; // this feature is deprecated and optional
type3 field3[I_AM_ARRAY];
type4 field4; // this is new feature
}
Target Kernel:
struct ser_ABI {
struct luo_feature_hdr features; // 8 bytes
type1 field1;
type2 field2;
type3 field3[I_AM_ARRAY];
}
Deserialization in target kernel.
Calculate the payload features size, then get the address of the field1
cast it with type1 (you can get the type1 from target kernel ser_ABI)
then you move to field2 which is optional. So again add its size and get
address of field3 then cast it and load the data and move to next field
by adding the size of the array* sizeof(type3), As direct casting is not
possible so we will be doing memcpy (performance regression).
~Tarun
>
>> structure size from the source kernel followed by 15 chained u64 feature
>> flags entries. The fixed size keeps payload field offsets identical in
>> every kernel, regardless of how many flags entries a source kernel uses.
>>
>> Each feature has a 2-bit pair (req, active) state (enum luo_feat_state:
>> LUO_FEAT_DISABLED, LUO_FEAT_OPTIONAL, LUO_FEAT_REQUIRED). A flags entry
>> holds 31 features, and bits [63:62] = 0b11 chain to the next entry (465
>> features in total). Each subsystem defines its feature states as a
>> static const u8 array indexed by its feature enum, which
>> luo_feature_hdr_init() packs into 64-bit flags entries with
>> LUO_FEAT_EXT_MASK chain bits. The target kernel validates the header from
>> the source kernel against its own feature array via
>> luo_feature_hdr_validate() according to the per-feature (req, active)
>> truth table:
>>
>> Source Target Action
>> (1,0) (x,x) Reject -EINVAL: invalid source kernel (req without active)
>> (x,x) (1,0) Reject -EINVAL + WARN_ON_ONCE: invalid target kernel
>> (0,0) (0,0) Accept: feature absent
>> (0,0) (0,1) Accept: feature absent
>> (0,0) (1,1) Reject -EOPNOTSUPP: target kernel requires the feature
>> (0,1) (0,0) Accept: ignore payload data
>> (1,1) (0,0) Reject -EOPNOTSUPP: source kernel requires the feature
>> (x,1) (x,1) Accept: use payload data
>>
>> The relative order and offsets of fields in serialized structures must
>> remain unchanged as features are added or retired. New fields are always
>> appended at the end of the structure under a new feature flag, and
>> retired fields are kept in place so that the offsets of subsequent
>> fields never shift. If the type or format of a field needs to change, the
>> existing field and feature are kept intact (and retired when no longer
>> needed) and a new feature flag with a new appended field of the new type
>> is added.
>>
>> In the target kernel, fields of a LUO_FEAT_REQUIRED feature are
>> guaranteed to be present once luo_feature_hdr_validate() succeeds,
>> whereas fields of a LUO_FEAT_OPTIONAL feature should be accessed only
>> after verifying that the feature is active in the source kernel header:
>>
>> if (luo_feature_is_active(&ser->features, FEAT_X))
>> val = ser->x_field;
>>
>> All current struct luo_ser fields (liveupdate_num, sessions_pa, flbs_pa)
>> belong to LUO_CORE_FEAT_CORE (LUO_FEAT_REQUIRED in luo_core_features).
>> Future changes add new entries to enum luo_core_feature before
>> LUO_CORE_FEAT_MAX.
>>
>> Signed-off-by: Logan Odell <loganodell@google.com>
>> Signed-off-by: Tarun Sahu <tarunsahu@google.com>
>> ---
>> include/linux/kho/abi/luo.h | 345 +++++++++++++++++++++++++++++++----
>> kernel/liveupdate/luo_core.c | 27 ++-
>> 2 files changed, 332 insertions(+), 40 deletions(-)
>>
>> diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
>> index 5fb2f46425d7..be35cfbfdb2b 100644
>> --- a/include/linux/kho/abi/luo.h
>> +++ b/include/linux/kho/abi/luo.h
>> @@ -9,19 +9,53 @@
>> * DOC: Live Update Orchestrator ABI
>> *
>> * Live Update Orchestrator uses the stable Application Binary Interface
>> - * defined below to pass state from a pre-update kernel to a post-update
>> - * kernel. The ABI is built upon the Kexec HandOver framework and registers
>> - * the central `struct luo_ser` via the KHO raw subtree API.
>> + * defined below to pass state from a source kernel to a target kernel.
>> + * The ABI is built upon the Kexec HandOver framework and registers the
>> + * central `struct luo_ser` via the KHO raw subtree API.
>> *
>> - * This interface is a contract. Any modification to the structure fields,
>> - * compatible strings, or the layout of the `__packed` serialization
>> - * structures defined here constitutes a breaking change. Such changes require
>> - * incrementing the version number in the relevant `_COMPATIBLE` string to
>> - * prevent a new kernel from misinterpreting data from an old kernel.
>> + * This interface is a contract. Serialized structures start with a fixed
>> + * 128-byte feature header (`struct luo_feature_hdr`) holding the source
>> + * kernel's structure `size` and 15 chained 64-bit feature flags entries
>> + * (`flags[LUO_FEAT_NR_FLAGS]`). Each entry encodes up to 31 features as
>> + * 2-bit `0b<req><active>` pairs (`enum luo_feat_state`), with
>> + * `LUO_FEAT_EXT_MASK` in bits `[63:62]` chaining to the next entry (up to
>> + * 465 features total). Because the header size is fixed at 128 bytes,
>> + * payload field offsets do not depend on how many flags entries a source
>> + * kernel uses.
>> *
>> - * Changes are allowed provided the compatibility version is incremented;
>> - * however, backward/forward compatibility is only guaranteed for kernels
>> - * supporting the same ABI version.
>> + * Truth table (`(req,active)` pairs per feature)::
>> + *
>> + * Source Target Action
>> + * (1,0) (x,x) Reject -EINVAL: invalid source kernel (req without active)
>> + * (x,x) (1,0) Reject -EINVAL: invalid target kernel (req without active)
>> + * (0,0) (0,0) Accept: feature absent
>> + * (0,0) (0,1) Accept: feature absent
>> + * (0,0) (1,1) Reject -EOPNOTSUPP: target kernel requires the feature
>> + * (0,1) (0,0) Accept: ignore payload data
>> + * (1,1) (0,0) Reject -EOPNOTSUPP: source kernel requires the feature
>> + * (x,1) (x,1) Accept: use payload data
>> + *
>> + * The relative order and offsets of fields in serialized structures must
>> + * remain unchanged as features are added or retired. New fields are always
>> + * appended at the end of the structure under a new feature flag, and
>> + * retired fields are kept in place so that the offsets of subsequent
>> + * fields never shift. If a field's type or format needs to change, the
>> + * existing field and feature are kept intact (and retired when no longer
>> + * needed) and a new feature flag with a new appended field of the new type
>> + * is added.
>> + *
>> + * In the target kernel, fields of a `LUO_FEAT_REQUIRED` feature are
>> + * guaranteed to be present once `luo_feature_hdr_validate()` succeeds,
>> + * whereas fields of a `LUO_FEAT_OPTIONAL` feature should be accessed only
>> + * after verifying that the feature is active in the source kernel's header::
>> + *
>> + * if (luo_feature_is_active(&ser->features, FEAT_X))
>> + * val = ser->x_field;
>> + *
>> + * All current `struct luo_ser` fields (`liveupdate_num`, `sessions_pa`,
>> + * `flbs_pa`) belong to `LUO_CORE_FEAT_CORE` (`LUO_FEAT_REQUIRED` in
>> + * `luo_core_features`). Future changes add new entries to
>> + * `enum luo_core_feature` before `LUO_CORE_FEAT_MAX`.
>> *
>> * KHO Structure Overview:
>> * The entire LUO state is encapsulated within a single KHO entry named "LUO".
>> @@ -30,8 +64,9 @@
>> * Serialization Structures:
>> * - struct luo_ser:
>> * The central ABI structure that contains the overall state of the LUO.
>> - * It includes the compatibility string, the liveupdate-number, and pointers
>> - * to sessions and FLBs.
>> + * It includes the feature header (with struct size and 2-bit feature flag
>> + * pairs), and the core feature (`LUO_CORE_FEAT_CORE`) fields: the
>> + * liveupdate-number and pointers to sessions and FLBs.
>> *
>> * - struct luo_session_ser:
>> * Metadata for a single session, including its name and a physical pointer
>> @@ -40,8 +75,9 @@
>> *
>> * - struct luo_file_ser:
>> * Metadata for a single preserved file. Contains the handler `name` string
>> - * to find the correct handler in the new kernel, a user-provided `token`
>> - * for identification, and an opaque `data` handle for the handler to use.
>> + * to find the correct handler in the target kernel, a user-provided
>> + * `token` for identification, and an opaque `data` handle for the handler
>> + * to use.
>> *
>> * - struct luo_flb_header_ser:
>> * Header for the FLB array. Contains the total page count of the
>> @@ -57,20 +93,265 @@
>> #ifndef _LINUX_KHO_ABI_LUO_H
>> #define _LINUX_KHO_ABI_LUO_H
>>
>> -#include <linux/align.h>
>> +#include <linux/array_size.h>
>> +#include <linux/bits.h>
>> +#include <linux/bug.h>
>> +#include <linux/build_bug.h>
>> +#include <linux/errno.h>
>> #include <linux/kho/abi/block.h>
>> +#include <linux/types.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-v5"
>> -#define LUO_ABI_COMPAT_LEN ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
>> +
>> +#define LUO_FEAT_PER_FLAGS 31
>> +#define LUO_FEAT_NR_FLAGS 15
>> +#define LUO_FEAT_MAX (LUO_FEAT_NR_FLAGS * LUO_FEAT_PER_FLAGS)
>> +#define LUO_FEAT_EXT_MASK GENMASK_ULL(63, 62)
>> +#define LUO_FEAT_ACTIVE_MASK (0x5555555555555555ULL & ~LUO_FEAT_EXT_MASK)
>> +#define LUO_FEAT_REQ_MASK (0xaaaaaaaaaaaaaaaaULL & ~LUO_FEAT_EXT_MASK)
>> +
>> +#define LUO_FEAT_FLAGS_IDX(f) ((unsigned int)(f) / LUO_FEAT_PER_FLAGS)
>> +#define LUO_FEAT_SLOT(f) ((unsigned int)(f) % LUO_FEAT_PER_FLAGS)
>> +
>> +#define LUO_FEAT_ACTIVE(f) BIT_ULL(2 * LUO_FEAT_SLOT(f))
>> +#define LUO_FEAT_REQ(f) BIT_ULL(2 * LUO_FEAT_SLOT(f) + 1)
>> +
>> +/**
>> + * enum luo_feat_state - 2-bit state of a LUO feature (`0b<req><active>`).
>> + * @LUO_FEAT_DISABLED: Feature is inactive (`req=0, active=0`).
>> + * @LUO_FEAT_OPTIONAL: Feature is active and optional (`req=0, active=1`).
>> + * @LUO_FEAT_REQUIRED: Feature is active and required (`req=1, active=1`).
>> + */
>> +enum luo_feat_state {
>> + LUO_FEAT_DISABLED = 0b00,
>> + LUO_FEAT_OPTIONAL = 0b01,
>> + LUO_FEAT_REQUIRED = 0b11,
>> +};
>> +
>> +/**
>> + * struct luo_feature_hdr - Fixed 128-byte header for serialized LUO ABI structures.
>> + * @size: Size in bytes of the source kernel's enclosing serialized structure.
>> + * @flags: Chained 64-bit feature flags entries (`0 .. LUO_FEAT_NR_FLAGS - 1`).
>> + * Each entry holds 31 2-bit feature pairs (`[2*slot + 1 : 2*slot]`)
>> + * plus %LUO_FEAT_EXT_MASK in bits `[63:62]` when the next entry is
>> + * active.
>> + */
>> +struct luo_feature_hdr {
>> + u64 size;
>> + u64 flags[LUO_FEAT_NR_FLAGS];
>> +} __packed;
>
>
>> +
>> +static_assert(sizeof(struct luo_feature_hdr) == 128);
>> +
>> +/**
>> + * luo_feature_hdr_for_each_flags - Iterate over chained flags entries in @hdr.
>> + * @hdr: Pointer to the &struct luo_feature_hdr to iterate.
>> + * @idx: Loop cursor of type `unsigned int` (`0 .. LUO_FEAT_NR_FLAGS - 1`).
>> + */
>> +#define luo_feature_hdr_for_each_flags(hdr, idx) \
>> + for ((idx) = 0; (idx) < LUO_FEAT_NR_FLAGS; \
>> + (idx) = (((hdr)->flags[idx] & LUO_FEAT_EXT_MASK) == \
>> + LUO_FEAT_EXT_MASK) ? (idx) + 1 : LUO_FEAT_NR_FLAGS)
>> +
>> +/**
>> + * luo_feature_hdr_get_flags - Get the flags entry containing feature @feat.
>> + * @hdr: Pointer to the source kernel's feature header.
>> + * @feat: Feature index (`0 .. LUO_FEAT_MAX - 1`).
>> + *
>> + * Return: The u64 flags entry containing @feat if the extension chain reaches
>> + * it, or 0 otherwise.
>> + */
>> +static inline u64
>> +luo_feature_hdr_get_flags(const struct luo_feature_hdr *hdr,
>> + unsigned int feat)
>> +{
>> + unsigned int target_idx = LUO_FEAT_FLAGS_IDX(feat);
>> + unsigned int idx;
>> +
>> + if (target_idx >= LUO_FEAT_NR_FLAGS)
>> + return 0;
>> +
>> + luo_feature_hdr_for_each_flags(hdr, idx) {
>> + if (idx == target_idx)
>> + return hdr->flags[idx];
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +/**
>> + * luo_feature_is_active - Test whether feature @feat is active in @hdr.
>> + * @hdr: Pointer to the source kernel's feature header.
>> + * @feat: Feature index (`0 .. LUO_FEAT_MAX - 1`).
>> + *
>> + * Return: true if the source kernel marked @feat active, false otherwise.
>> + */
>> +static inline bool luo_feature_is_active(const struct luo_feature_hdr *hdr,
>> + unsigned int feat)
>> +{
>> + return luo_feature_hdr_get_flags(hdr, feat) & LUO_FEAT_ACTIVE(feat);
>> +}
>> +
>> +/**
>> + * luo_feature_is_req - Test whether feature @feat is required in @hdr.
>> + * @hdr: Pointer to the source kernel's feature header.
>> + * @feat: Feature index (`0 .. LUO_FEAT_MAX - 1`).
>> + *
>> + * Return: true if the source kernel marked @feat required, false otherwise.
>> + */
>> +static inline bool luo_feature_is_req(const struct luo_feature_hdr *hdr,
>> + unsigned int feat)
>> +{
>> + return luo_feature_hdr_get_flags(hdr, feat) & LUO_FEAT_REQ(feat);
>> +}
>> +
>> +/**
>> + * luo_feature_pack_entry - Pack up to 31 feature states into a flags entry.
>> + * @features: Array of &enum luo_feat_state values indexed by feature ID.
>> + * @nr_features: Number of elements in @features.
>> + * @idx: Flags entry index (`0 .. LUO_FEAT_NR_FLAGS - 1`).
>> + *
>> + * Return: The packed 64-bit flags entry for @idx (without %LUO_FEAT_EXT_MASK).
>> + */
>> +static inline u64
>> +luo_feature_pack_entry(const u8 *features, size_t nr_features,
>> + unsigned int idx)
>> +{
>> + unsigned int base = idx * LUO_FEAT_PER_FLAGS;
>> + unsigned int slot;
>> + u64 flags = 0;
>> +
>> + for (slot = 0; slot < LUO_FEAT_PER_FLAGS && base + slot < nr_features;
>> + slot++)
>> + flags |= (u64)(features[base + slot] & 0b11) << (2 * slot);
>> +
>> + return flags;
>> +}
>> +
>> +/**
>> + * __luo_feature_hdr_init - Initialize a serialized LUO feature header.
>> + * @hdr: Pointer to the feature header to initialize.
>> + * @size: Byte size of the enclosing serialized structure.
>> + * @features: Array of &enum luo_feat_state values for the source kernel.
>> + * @nr_features: Number of elements in @features (`<= LUO_FEAT_MAX`).
>> + */
>> +static inline void
>> +__luo_feature_hdr_init(struct luo_feature_hdr *hdr, u64 size,
>> + const u8 *features, size_t nr_features)
>> +{
>> + bool ext = false;
>> + int idx;
>> +
>> + hdr->size = size;
>> + for (idx = LUO_FEAT_NR_FLAGS - 1; idx >= 0; idx--) {
>> + u64 flags = luo_feature_pack_entry(features, nr_features, idx);
>> +
>> + WARN_ON_ONCE(((flags & LUO_FEAT_REQ_MASK) >> 1) &
>> + ~(flags & LUO_FEAT_ACTIVE_MASK));
>> + if (ext)
>> + flags |= LUO_FEAT_EXT_MASK;
>> + else if (flags)
>> + ext = true;
>> + hdr->flags[idx] = flags;
>> + }
>> +}
>> +
>> +/**
>> + * luo_feature_hdr_init - Initialize a serialized LUO feature header.
>> + * @hdr: Pointer to the feature header to initialize.
>> + * @size: Byte size of the enclosing serialized structure.
>> + * @features: Fixed-size `u8` array of &enum luo_feat_state entries.
>> + */
>> +#define luo_feature_hdr_init(hdr, size, features) \
>> +({ \
>> + BUILD_BUG_ON(ARRAY_SIZE(features) > LUO_FEAT_MAX); \
>> + __luo_feature_hdr_init(hdr, size, features, \
>> + ARRAY_SIZE(features)); \
>> +})
>> +
>> +/**
>> + * __luo_feature_hdr_validate - Validate a source kernel's LUO feature header
>> + * against the target kernel's feature array.
>> + * @hdr: Pointer to the feature header from the source kernel.
>> + * @features: Array of &enum luo_feat_state values for the target kernel.
>> + * @nr_features: Number of elements in @features (`<= LUO_FEAT_MAX`).
>> + *
>> + * Return: 0 if compatible, -EINVAL on malformed header or `(req=1, active=0)`
>> + * feature state, or -EOPNOTSUPP on required feature mismatch.
>> + */
>> +static inline int
>> +__luo_feature_hdr_validate(const struct luo_feature_hdr *hdr,
>> + const u8 *features, size_t nr_features)
>> +{
>> + bool src_ext = true, target_ext = (nr_features > 0);
>> + unsigned int idx;
>> +
>> + if (hdr->size < sizeof(*hdr))
>> + return -EINVAL;
>> +
>> + for (idx = 0; idx < LUO_FEAT_NR_FLAGS && (src_ext || target_ext); idx++) {
>> + u64 src = src_ext ? hdr->flags[idx] : 0;
>> + u64 target = target_ext ?
>> + luo_feature_pack_entry(features, nr_features, idx) : 0;
>> + u64 ext = src & LUO_FEAT_EXT_MASK;
>> + u64 src_act = src & LUO_FEAT_ACTIVE_MASK;
>> + u64 src_req = (src & LUO_FEAT_REQ_MASK) >> 1;
>> + u64 target_act = target & LUO_FEAT_ACTIVE_MASK;
>> + u64 target_req = (target & LUO_FEAT_REQ_MASK) >> 1;
>> +
>> + if (ext && (ext != LUO_FEAT_EXT_MASK ||
>> + idx == LUO_FEAT_NR_FLAGS - 1))
>> + return -EINVAL;
>> + if (src_req & ~src_act)
>> + return -EINVAL;
>> + if (WARN_ON_ONCE(target_req & ~target_act))
>> + return -EINVAL;
>> + if ((src_req & ~target_act) || (target_req & ~src_act))
>> + return -EOPNOTSUPP;
>> +
>> + src_ext = (ext == LUO_FEAT_EXT_MASK);
>> + target_ext = ((idx + 1) * LUO_FEAT_PER_FLAGS < nr_features);
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +/**
>> + * luo_feature_hdr_validate - Validate a source kernel's LUO feature header
>> + * against the target kernel's feature array.
>> + * @hdr: Pointer to the feature header from the source kernel.
>> + * @features: Fixed-size `u8` array of &enum luo_feat_state entries.
>> + *
>> + * Return: 0 if compatible, -EINVAL on malformed header or feature state, or
>> + * -EOPNOTSUPP on required feature mismatch.
>> + */
>> +#define luo_feature_hdr_validate(hdr, features) \
>> +({ \
>> + BUILD_BUG_ON(ARRAY_SIZE(features) > LUO_FEAT_MAX); \
>> + __luo_feature_hdr_validate(hdr, features, \
>> + ARRAY_SIZE(features)); \
>> +})
>> +
>> +/**
>> + * enum luo_core_feature - Feature indices for &struct luo_ser.
>> + * @LUO_CORE_FEAT_CORE: Core feature covering `liveupdate_num`, `sessions_pa`,
>> + * and `flbs_pa`.
>> + * @LUO_CORE_FEAT_MAX: Number of defined &enum luo_core_feature features.
>> + */
>> +enum luo_core_feature {
>> + LUO_CORE_FEAT_CORE = 0,
>> + LUO_CORE_FEAT_MAX,
>> +};
>> +
>> +static_assert((int)LUO_CORE_FEAT_MAX <= LUO_FEAT_MAX);
>>
>> /**
>> * struct luo_ser - Centralized LUO ABI header.
>> - * @compatible: Compatibility string identifying the LUO ABI version.
>> + * @features: ABI feature and size header for luo_ser.
>> * @liveupdate_num: A counter tracking the number of successful live updates.
>> * @sessions_pa: Physical address of the first session block header.
>> * @flbs_pa: Physical address of the FLB header.
>> @@ -78,7 +359,7 @@
>> * This structure is the root of all preserved LUO state.
>> */
>> struct luo_ser {
>> - char compatible[LUO_ABI_COMPAT_LEN];
>> + struct luo_feature_hdr features;
>> u64 liveupdate_num;
>> u64 sessions_pa;
>> u64 flbs_pa;
>> @@ -90,7 +371,7 @@ struct luo_ser {
>> * @data: Private data
>> * @token: User provided token for this file
>> *
>> - * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
>> + * Layout changes must be gated by a required feature in &enum luo_core_feature.
>> */
>> struct luo_file_ser {
>> char name[LIVEUPDATE_HNDL_NAME_LENGTH];
>> @@ -121,10 +402,10 @@ struct luo_file_set_ser {
>> *
>> * This structure is used to package session-specific metadata for transfer
>> * between kernels via Kexec Handover. An array of these structures (one per
>> - * session) is created and passed to the new kernel, allowing it to reconstruct
>> - * the session context.
>> + * session) is created and passed to the target kernel, allowing it to
>> + * reconstruct the session context.
>> *
>> - * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
>> + * Layout changes must be gated by a required feature in &enum luo_core_feature.
>> */
>> struct luo_session_ser {
>> char name[LIVEUPDATE_SESSION_NAME_LENGTH];
>> @@ -145,7 +426,7 @@ struct luo_session_ser {
>> * This structure is located at the physical address specified by the
>> * flbs_pa in luo_ser.
>> *
>> - * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
>> + * Layout changes must be gated by a required feature in &enum luo_core_feature.
>> */
>> struct luo_flb_header_ser {
>> u64 pgcnt;
>> @@ -155,19 +436,19 @@ struct luo_flb_header_ser {
>> /**
>> * struct luo_flb_ser - Represents the serialized state of a single FLB object.
>> * @name: The unique name string of the FLB object, used to find the
>> - * corresponding &struct liveupdate_flb handler in the new kernel.
>> + * corresponding &struct liveupdate_flb in the target kernel.
>> * @data: The opaque u64 handle returned by the FLB's .preserve() operation
>> - * in the old kernel. This handle encapsulates the entire state needed
>> - * for restoration.
>> + * in the source kernel. This handle encapsulates the entire state
>> + * needed for restoration.
>> * @count: The reference count at the time of serialization; i.e., the number
>> * of preserved files that depended on this FLB. This is used by the
>> - * new kernel to correctly manage the FLB's lifecycle.
>> + * target kernel to correctly manage the FLB's lifecycle.
>> *
>> * An array of these structures is created in a preserved memory region and
>> - * passed to the new kernel. Each entry allows the LUO core to restore one
>> - * global, shared object.
>> + * passed to the target kernel. Each entry allows the LUO core to restore
>> + * one global, shared object.
>> *
>> - * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
>> + * Layout changes must be gated by a required feature in &enum luo_core_feature.
>> */
>> struct luo_flb_ser {
>> char name[LIVEUPDATE_FLB_NAME_LENGTH];
>> diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
>> index 1b2bda22902d..3627a206a79c 100644
>> --- a/kernel/liveupdate/luo_core.c
>> +++ b/kernel/liveupdate/luo_core.c
>> @@ -70,6 +70,10 @@ static struct {
>> u64 liveupdate_num;
>> } luo_global;
>>
>> +static const u8 luo_core_features[LUO_CORE_FEAT_MAX] = {
>> + [LUO_CORE_FEAT_CORE] = LUO_FEAT_REQUIRED,
>> +};
>> +
>> /*
>> * luo_register_rwlock - Protects registration of file handlers and FLBs.
>> */
>> @@ -107,15 +111,21 @@ static int __init luo_early_startup(void)
>> return 0;
>> }
>>
>> - if (len < sizeof(*luo_ser)) {
>> - pr_err("LUO state is too small (%zu < %zu)\n", len, sizeof(*luo_ser));
>> - return -EINVAL;
>> + luo_ser = phys_to_virt(luo_ser_phys);
>> + if (len < sizeof(luo_ser->features) || luo_ser->features.size > len) {
>> + pr_err("LUO state size invalid (source kernel header size=%llu, KHO len=%zu)\n",
>> + len >= sizeof(luo_ser->features) ? luo_ser->features.size : 0,
>> + len);
>> + err = -EINVAL;
>> + goto out_free_ser;
>> }
>>
>> - luo_ser = phys_to_virt(luo_ser_phys);
>> - if (strncmp(luo_ser->compatible, LUO_ABI_COMPATIBLE, LUO_ABI_COMPAT_LEN)) {
>> - pr_err("LUO state is incompatible with '%s'\n", LUO_ABI_COMPATIBLE);
>> - return -EINVAL;
>> + err = luo_feature_hdr_validate(&luo_ser->features, luo_core_features);
>> + if (err) {
>> + pr_err("LUO state feature header validation failed (size=%llu, flags[0]=%#llx): %pe\n",
>> + luo_ser->features.size, luo_ser->features.flags[0],
>> + ERR_PTR(err));
>> + goto out_free_ser;
>> }
>>
>> luo_global.liveupdate_num = luo_ser->liveupdate_num;
>> @@ -162,7 +172,8 @@ static int __init luo_state_setup(void)
>> return PTR_ERR(luo_ser);
>> }
>>
>> - strscpy(luo_ser->compatible, LUO_ABI_COMPATIBLE, sizeof(luo_ser->compatible));
>> + luo_feature_hdr_init(&luo_ser->features, sizeof(*luo_ser),
>> + luo_core_features);
>> luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
>>
>> luo_session_setup_outgoing(&luo_ser->sessions_pa);
>> --
>> 2.56.0.212.g129ede20d9-goog
>>
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header
2026-10-21 18:40 ` tarunsahu
@ 2026-10-21 18:46 ` David Matlack
2026-10-21 18:50 ` Pasha Tatashin
0 siblings, 1 reply; 27+ messages in thread
From: David Matlack @ 2026-10-21 18:46 UTC (permalink / raw)
To: tarunsahu
Cc: Pasha Tatashin, Andrew Morton, joro, Mike Rapoport,
Pratyush Yadav, maz, arnd, jgg, robin.murphy, loganodell, oupton,
kevin.tian, alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
On Wed, Oct 21, 2026 at 11:40 AM <tarunsahu@google.com> wrote:
>
> Pasha Tatashin <pasha.tatashin@soleen.com> writes:
>
> > On Wed, Oct 21, 2026 at 11:25 AM Tarun Sahu <tarunsahu@google.com> wrote:
> >>
> >> LUO and its file handlers validate handed-over state with monolithic
> >> compatibility strings ("luo-v5", "memfd-v2"). Any change to a serialized
> >> structure, even an optional additive one, must bump the string, which
> >> breaks live update between adjacent kernels. Replace the string in
> >> struct luo_ser with a feature header, so that the target kernel can
> >> accept or reject the source kernel state per feature.
> >>
> >> struct luo_feature_hdr is a fixed 128-byte header: the serialized
> >
> > This patch is all wrong; 128 bytes is far too large. For every _ser
> > struct, we only need 8 bytes instead.
>
> NOO, It is not. It is intended to avoid complex mess with unnecessary
> over-engineering.
>
> >
> > The first 4 bytes are the size, and the next 4 bytes are flags (i.e.,
> > 30 feature flags + 2 reserved bits for flags extension). The last two
> > bits of the flags indicate whether an extended flags field is present.
> > Each extended flags field is 8 bytes, and its last two bits indicate
> > that yet another extended flags field follows.
>
> If we will have dynamic extension of flags, Think about this.
To support dynamic extensions in a backward compatible way you want
something like this:
struct luo_feature_hdr {
size
flags
extensions // offset of first luo_feature_extensions (or 0)
}
struct luo_feature_extentions {
flags
extensions // offset of next luo_feature_extensions (or 0)
}
And then an example serialization struct would be:
struct foo_ser {
struct luo_feature_hdr header;
... fields ...
struct luo_feature_extension extensions1;
... fields ...
struct luo_feature_extension extensions2;
}
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header
2026-10-21 18:46 ` David Matlack
@ 2026-10-21 18:50 ` Pasha Tatashin
2026-10-21 23:28 ` David Matlack
0 siblings, 1 reply; 27+ messages in thread
From: Pasha Tatashin @ 2026-10-21 18:50 UTC (permalink / raw)
To: David Matlack
Cc: tarunsahu, Andrew Morton, joro, Mike Rapoport, Pratyush Yadav,
maz, arnd, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
On Wed, Oct 21, 2026 at 2:47 PM David Matlack <dmatlack@google.com> wrote:
>
> On Wed, Oct 21, 2026 at 11:40 AM <tarunsahu@google.com> wrote:
> >
> > Pasha Tatashin <pasha.tatashin@soleen.com> writes:
> >
> > > On Wed, Oct 21, 2026 at 11:25 AM Tarun Sahu <tarunsahu@google.com> wrote:
> > >>
> > >> LUO and its file handlers validate handed-over state with monolithic
> > >> compatibility strings ("luo-v5", "memfd-v2"). Any change to a serialized
> > >> structure, even an optional additive one, must bump the string, which
> > >> breaks live update between adjacent kernels. Replace the string in
> > >> struct luo_ser with a feature header, so that the target kernel can
> > >> accept or reject the source kernel state per feature.
> > >>
> > >> struct luo_feature_hdr is a fixed 128-byte header: the serialized
> > >
> > > This patch is all wrong; 128 bytes is far too large. For every _ser
> > > struct, we only need 8 bytes instead.
> >
> > NOO, It is not. It is intended to avoid complex mess with unnecessary
> > over-engineering.
128-byte per serialized struct is not starter.
> >
> > >
> > > The first 4 bytes are the size, and the next 4 bytes are flags (i.e.,
> > > 30 feature flags + 2 reserved bits for flags extension). The last two
> > > bits of the flags indicate whether an extended flags field is present.
> > > Each extended flags field is 8 bytes, and its last two bits indicate
> > > that yet another extended flags field follows.
> >
> > If we will have dynamic extension of flags, Think about this.
>
> To support dynamic extensions in a backward compatible way you want
> something like this:
>
> struct luo_feature_hdr {
> size
> flags
__u32 size
__u32 flags
> extensions // offset of first luo_feature_extensions (or 0)
__u64 extensions[0] --> but the size should be specificed in
declaration macro that I showed in the next patch.
> }
>
> struct luo_feature_extentions {
> flags
> extensions // offset of next luo_feature_extensions (or 0)
> }
>
> And then an example serialization struct would be:
>
> struct foo_ser {
> struct luo_feature_hdr header;
> ... fields ...
> struct luo_feature_extension extensions1;
> ... fields ...
> struct luo_feature_extension extensions2;
> }
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header
2026-10-21 18:50 ` Pasha Tatashin
@ 2026-10-21 23:28 ` David Matlack
2026-10-22 1:26 ` Pasha Tatashin
0 siblings, 1 reply; 27+ messages in thread
From: David Matlack @ 2026-10-21 23:28 UTC (permalink / raw)
To: Pasha Tatashin
Cc: tarunsahu, Andrew Morton, joro, Mike Rapoport, Pratyush Yadav,
maz, arnd, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
On 2026-10-21 02:50 PM, Pasha Tatashin wrote:
> On Wed, Oct 21, 2026 at 2:47 PM David Matlack <dmatlack@google.com> wrote:
> >
> > On Wed, Oct 21, 2026 at 11:40 AM <tarunsahu@google.com> wrote:
> > >
> > > Pasha Tatashin <pasha.tatashin@soleen.com> writes:
> > >
> > > > On Wed, Oct 21, 2026 at 11:25 AM Tarun Sahu <tarunsahu@google.com> wrote:
> > > >>
> > > >> LUO and its file handlers validate handed-over state with monolithic
> > > >> compatibility strings ("luo-v5", "memfd-v2"). Any change to a serialized
> > > >> structure, even an optional additive one, must bump the string, which
> > > >> breaks live update between adjacent kernels. Replace the string in
> > > >> struct luo_ser with a feature header, so that the target kernel can
> > > >> accept or reject the source kernel state per feature.
> > > >>
> > > >> struct luo_feature_hdr is a fixed 128-byte header: the serialized
> > > >
> > > > This patch is all wrong; 128 bytes is far too large. For every _ser
> > > > struct, we only need 8 bytes instead.
> > >
> > > NOO, It is not. It is intended to avoid complex mess with unnecessary
> > > over-engineering.
>
> 128-byte per serialized struct is not starter.
>
> > >
> > > >
> > > > The first 4 bytes are the size, and the next 4 bytes are flags (i.e.,
> > > > 30 feature flags + 2 reserved bits for flags extension). The last two
> > > > bits of the flags indicate whether an extended flags field is present.
> > > > Each extended flags field is 8 bytes, and its last two bits indicate
> > > > that yet another extended flags field follows.
> > >
> > > If we will have dynamic extension of flags, Think about this.
> >
> > To support dynamic extensions in a backward compatible way you want
> > something like this:
> >
> > struct luo_feature_hdr {
> > size
> > flags
>
> __u32 size
> __u32 flags
>
> > extensions // offset of first luo_feature_extensions (or 0)
>
> __u64 extensions[0] --> but the size should be specificed in
> declaration macro that I showed in the next patch.
I think you may have misunderstood what I meant. In my proposal,
extensions is an offset to the first extension, not an array of
extensions that immediately follow the header.
If the extensions immediately follow the header, then the number of
extensions is baked into the struct layout. Every field after them sits
at an offset that depends on how many extensions there are. If we ever
need one more extension, all the fields shift and old and new kernels
can no longer read each other's struct. That is the problem Tarun
pointed out. So in practice the number of extensions is fixed forever
when the struct is first declared, and we have to guess up front how
many features a struct will ever need. That's the same as Tarun's
128-byte header, just with a smaller guess.
What I'm proposing is a linked list of extensions, where each link is
an offset from the start of the struct. A new extension is never
inserted into the middle of the struct. It is appended at the end along
with the new fields that need it, like any other new field, so existing
field offsets never change.
Something like this:
struct luo_ser_hdr {
__u32 flags;
__u32 next; /* Offset of first struct luo_ser_ext, or 0 */
};
struct luo_ser_ext {
__u32 flags;
__u32 next; /* Offset of next struct luo_ser_ext, or 0 */
};
That's the same 8 bytes as your proposal. And because "is there an
extension" is next != 0 instead of reserved flag bits, all of the flag
bits are available for features.
For example, a struct starts out like this:
struct foo_ser {
struct luo_ser_hdr hdr; /* hdr.next = 0 */
__u64 a;
__u64 b;
};
Over time we add features and fields until the header flags run out.
Then we add one more feature, FOO_FEAT_C, which needs a new field c:
struct foo_ser {
struct luo_ser_hdr hdr; /* hdr.next = offsetof(struct foo_ser, ext1) */
__u64 a;
__u64 b;
/* ... fields added for features in hdr.flags ... */
struct luo_ser_ext ext1; /* ext1.next = 0 */
__u64 c;
};
- a, b, and every other existing field stay at the same offsets. Both
kernels can keep casting the KHO memory directly to struct foo_ser.
No memcpy() or variable offset math.
- An old kernel that knows nothing about ext1 can still find it by
following hdr.next, because the chain describes itself. So the old
kernel can still reject the struct if ext1 has a required feature it
doesn't support, and ignore ext1 otherwise.
- A new kernel receiving the struct from an old kernel sees
hdr.next == 0. It treats every feature in ext1 as absent and never
touches c, so it never reads past the end of the old, smaller struct.
The only rule for subsystems is that a field can only be accessed after
checking its feature, which a LUO helper can do by walking the chain:
if (luo_ser_has_feature(&ser->hdr, FOO_FEAT_C))
c = ser->c;
If we keep size in the header, this is where it would be useful: LUO
can check that every next offset is within size and strictly increasing
(so there are no loops) before walking the chain.
>
> > }
> >
> > struct luo_feature_extentions {
> > flags
> > extensions // offset of next luo_feature_extensions (or 0)
> > }
> >
> > And then an example serialization struct would be:
> >
> > struct foo_ser {
> > struct luo_feature_hdr header;
> > ... fields ...
> > struct luo_feature_extension extensions1;
> > ... fields ...
> > struct luo_feature_extension extensions2;
> > }
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header
2026-10-21 23:28 ` David Matlack
@ 2026-10-22 1:26 ` Pasha Tatashin
2026-10-22 2:57 ` David Matlack
0 siblings, 1 reply; 27+ messages in thread
From: Pasha Tatashin @ 2026-10-22 1:26 UTC (permalink / raw)
To: David Matlack
Cc: tarunsahu, Andrew Morton, joro, Mike Rapoport, Pratyush Yadav,
maz, arnd, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
On Wed, Oct 21, 2026 at 7:28 PM David Matlack <dmatlack@google.com> wrote:
>
> On 2026-10-21 02:50 PM, Pasha Tatashin wrote:
> > On Wed, Oct 21, 2026 at 2:47 PM David Matlack <dmatlack@google.com> wrote:
> > >
> > > On Wed, Oct 21, 2026 at 11:40 AM <tarunsahu@google.com> wrote:
> > > >
> > > > Pasha Tatashin <pasha.tatashin@soleen.com> writes:
> > > >
> > > > > On Wed, Oct 21, 2026 at 11:25 AM Tarun Sahu <tarunsahu@google.com> wrote:
> > > > >>
> > > > >> LUO and its file handlers validate handed-over state with monolithic
> > > > >> compatibility strings ("luo-v5", "memfd-v2"). Any change to a serialized
> > > > >> structure, even an optional additive one, must bump the string, which
> > > > >> breaks live update between adjacent kernels. Replace the string in
> > > > >> struct luo_ser with a feature header, so that the target kernel can
> > > > >> accept or reject the source kernel state per feature.
> > > > >>
> > > > >> struct luo_feature_hdr is a fixed 128-byte header: the serialized
> > > > >
> > > > > This patch is all wrong; 128 bytes is far too large. For every _ser
> > > > > struct, we only need 8 bytes instead.
> > > >
> > > > NOO, It is not. It is intended to avoid complex mess with unnecessary
> > > > over-engineering.
> >
> > 128-byte per serialized struct is not starter.
> >
> > > >
> > > > >
> > > > > The first 4 bytes are the size, and the next 4 bytes are flags (i.e.,
> > > > > 30 feature flags + 2 reserved bits for flags extension). The last two
> > > > > bits of the flags indicate whether an extended flags field is present.
> > > > > Each extended flags field is 8 bytes, and its last two bits indicate
> > > > > that yet another extended flags field follows.
> > > >
> > > > If we will have dynamic extension of flags, Think about this.
> > >
> > > To support dynamic extensions in a backward compatible way you want
> > > something like this:
> > >
> > > struct luo_feature_hdr {
> > > size
> > > flags
> >
> > __u32 size
> > __u32 flags
> >
> > > extensions // offset of first luo_feature_extensions (or 0)
> >
> > __u64 extensions[0] --> but the size should be specificed in
> > declaration macro that I showed in the next patch.
>
> I think you may have misunderstood what I meant. In my proposal,
> extensions is an offset to the first extension, not an array of
> extensions that immediately follow the header.
>
> If the extensions immediately follow the header, then the number of
> extensions is baked into the struct layout. Every field after them sits
> at an offset that depends on how many extensions there are. If we ever
> need one more extension, all the fields shift and old and new kernels
> can no longer read each other's struct. That is the problem Tarun
We can structure it cleanly by nesting the payload: something like
struct { struct header {...}h; struct serialize_data { ... }p; };
That way, the header can expand dynamically at the front, while all
fields inside `serialize_data` stay at fixed offsets relative to the
start
of the payload.
Since the header size is self-describing via continuation bits (e.g.,
the last two bits indicating whether another flag word follows), LUO
can
easily compute where the payload begins in the incoming blob. This
keeps the implementation simple, avoids linked lists of offsets.
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header
2026-10-22 1:26 ` Pasha Tatashin
@ 2026-10-22 2:57 ` David Matlack
0 siblings, 0 replies; 27+ messages in thread
From: David Matlack @ 2026-10-22 2:57 UTC (permalink / raw)
To: Pasha Tatashin
Cc: tarunsahu, Andrew Morton, joro, Mike Rapoport, Pratyush Yadav,
maz, arnd, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
On 2026-10-21 09:26 PM, Pasha Tatashin wrote:
> On Wed, Oct 21, 2026 at 7:28 PM David Matlack <dmatlack@google.com> wrote:
> >
> > On 2026-10-21 02:50 PM, Pasha Tatashin wrote:
> > > On Wed, Oct 21, 2026 at 2:47 PM David Matlack <dmatlack@google.com> wrote:
> > > >
> > > > On Wed, Oct 21, 2026 at 11:40 AM <tarunsahu@google.com> wrote:
> > > > >
> > > > > Pasha Tatashin <pasha.tatashin@soleen.com> writes:
> > > > >
> > > > > > On Wed, Oct 21, 2026 at 11:25 AM Tarun Sahu <tarunsahu@google.com> wrote:
> > > > > >>
> > > > > >> LUO and its file handlers validate handed-over state with monolithic
> > > > > >> compatibility strings ("luo-v5", "memfd-v2"). Any change to a serialized
> > > > > >> structure, even an optional additive one, must bump the string, which
> > > > > >> breaks live update between adjacent kernels. Replace the string in
> > > > > >> struct luo_ser with a feature header, so that the target kernel can
> > > > > >> accept or reject the source kernel state per feature.
> > > > > >>
> > > > > >> struct luo_feature_hdr is a fixed 128-byte header: the serialized
> > > > > >
> > > > > > This patch is all wrong; 128 bytes is far too large. For every _ser
> > > > > > struct, we only need 8 bytes instead.
> > > > >
> > > > > NOO, It is not. It is intended to avoid complex mess with unnecessary
> > > > > over-engineering.
> > >
> > > 128-byte per serialized struct is not starter.
> > >
> > > > >
> > > > > >
> > > > > > The first 4 bytes are the size, and the next 4 bytes are flags (i.e.,
> > > > > > 30 feature flags + 2 reserved bits for flags extension). The last two
> > > > > > bits of the flags indicate whether an extended flags field is present.
> > > > > > Each extended flags field is 8 bytes, and its last two bits indicate
> > > > > > that yet another extended flags field follows.
> > > > >
> > > > > If we will have dynamic extension of flags, Think about this.
> > > >
> > > > To support dynamic extensions in a backward compatible way you want
> > > > something like this:
> > > >
> > > > struct luo_feature_hdr {
> > > > size
> > > > flags
> > >
> > > __u32 size
> > > __u32 flags
> > >
> > > > extensions // offset of first luo_feature_extensions (or 0)
> > >
> > > __u64 extensions[0] --> but the size should be specificed in
> > > declaration macro that I showed in the next patch.
> >
> > I think you may have misunderstood what I meant. In my proposal,
> > extensions is an offset to the first extension, not an array of
> > extensions that immediately follow the header.
> >
> > If the extensions immediately follow the header, then the number of
> > extensions is baked into the struct layout. Every field after them sits
> > at an offset that depends on how many extensions there are. If we ever
> > need one more extension, all the fields shift and old and new kernels
> > can no longer read each other's struct. That is the problem Tarun
>
> We can structure it cleanly by nesting the payload: something like
> struct { struct header {...}h; struct serialize_data { ... }p; };
> That way, the header can expand dynamically at the front, while all
> fields inside `serialize_data` stay at fixed offsets relative to the
> start
> of the payload.
>
> Since the header size is self-describing via continuation bits (e.g.,
> the last two bits indicating whether another flag word follows), LUO
> can
> easily compute where the payload begins in the incoming blob. This
> keeps the implementation simple, avoids linked lists of offsets.
Oh interesting. I agree a dynamically sized header at the front can work
and is simpler than a linked list.
So the header and the payload need to be separate, and the payload is
located with pointer arithmetic on the header. For example:
struct luo_ser_hdr {
u32 size; /* Size of the payload in bytes */
u32 flags; /* Bits [31:30] indicate an extension word follows */
/* u64 extension words follow while the continuation bits are set */
};
/* Size of the header, including any extension words. */
size_t luo_ser_hdr_size(const struct luo_ser_hdr *hdr);
static inline void *luo_ser_payload(const struct luo_ser_hdr *hdr)
{
return (void *)hdr + luo_ser_hdr_size(hdr);
}
On preserve:
hdr = kho_alloc_preserve(luo_ser_hdr_size_for(nr_ext) + sizeof(*ser));
luo_ser_hdr_init(hdr, sizeof(*ser), ...);
ser = luo_ser_payload(hdr);
...
args->serialized_data = virt_to_phys(hdr);
On retrieve:
hdr = phys_to_virt(args->serialized_data);
err = luo_ser_hdr_validate(hdr, ...);
if (err)
return err;
ser = luo_ser_payload(hdr);
What would size mean in luo_ser_hdr? The size of the payload or the size
of the combined luo_ser_hdr + payload. luo_ser_hdr is self describing so
maybe it's just the size of the payload?
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header
2026-10-21 15:25 ` [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header Tarun Sahu
2026-10-21 17:27 ` Pasha Tatashin
@ 2026-10-21 23:55 ` David Matlack
2026-10-22 0:04 ` David Matlack
1 sibling, 1 reply; 27+ messages in thread
From: David Matlack @ 2026-10-21 23:55 UTC (permalink / raw)
To: Tarun Sahu
Cc: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
jgg, robin.murphy, loganodell, oupton, kevin.tian, alex, dwmw2,
pbonzini, seanjc, will, Pasha Tatashin, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
On 2026-10-21 03:25 PM, Tarun Sahu wrote:
> LUO and its file handlers validate handed-over state with monolithic
> compatibility strings ("luo-v5", "memfd-v2"). Any change to a serialized
> structure, even an optional additive one, must bump the string, which
> breaks live update between adjacent kernels. Replace the string in
> struct luo_ser with a feature header, so that the target kernel can
> accept or reject the source kernel state per feature.
>
> struct luo_feature_hdr is a fixed 128-byte header: the serialized
> structure size from the source kernel followed by 15 chained u64 feature
> flags entries. The fixed size keeps payload field offsets identical in
> every kernel, regardless of how many flags entries a source kernel uses.
>
> Each feature has a 2-bit pair (req, active) state (enum luo_feat_state:
> LUO_FEAT_DISABLED, LUO_FEAT_OPTIONAL, LUO_FEAT_REQUIRED). A flags entry
> holds 31 features, and bits [63:62] = 0b11 chain to the next entry (465
> features in total). Each subsystem defines its feature states as a
> static const u8 array indexed by its feature enum, which
> luo_feature_hdr_init() packs into 64-bit flags entries with
> LUO_FEAT_EXT_MASK chain bits. The target kernel validates the header from
> the source kernel against its own feature array via
> luo_feature_hdr_validate() according to the per-feature (req, active)
> truth table:
>
> Source Target Action
> (1,0) (x,x) Reject -EINVAL: invalid source kernel (req without active)
> (x,x) (1,0) Reject -EINVAL + WARN_ON_ONCE: invalid target kernel
> (0,0) (0,0) Accept: feature absent
> (0,0) (0,1) Accept: feature absent
> (0,0) (1,1) Reject -EOPNOTSUPP: target kernel requires the feature
> (0,1) (0,0) Accept: ignore payload data
> (1,1) (0,0) Reject -EOPNOTSUPP: source kernel requires the feature
> (x,1) (x,1) Accept: use payload data
I think we have different definitions of "required".
In the PCI series [1], required and optional flags describe the _data_
the previous kernel handed over, not the kernel itself. A required bit
means "this preserved state depends on feature X; a kernel that does
not understand X must not take it over". An optional bit means "the
data for X is here, but you can safely ignore it". The source kernel
only sets a bit when the preserved state actually uses the feature.
It's up to the FLB/FH retrieve how to handle the scenario where a
required bit is set that the target kernel does not understand. For the
PCI FLB we panic since failing to adopt the PCI state could lead to
memory corruption as preserved devices can be doing DMA.
The target kernel never requires anything from the source. If a
feature is absent, the target kernel must handle that, e.g. by
falling back to default behavior. If it can't, that is a compatibility
break and should be handled by the compatible string.
In this patch each kernel has a single static feature array that is used
both to generate the header and to validate the incoming header. So if
kernel N marks a new feature X as required (so that older kernels reject
data they cannot understand), then kernel N will also reject every
kernel that does not have X, because of the (0,0) -> (1,1) row in the
truth table. That breaks forward updates from N-1 to N, and rollbacks
from N to N-1 even when **nothing being preserved uses X**. Therefore,
every new required feature becomes a compatible string bump.
I would drop the target-side required state entirely and let the source
kernel decide which flags to set for each Live Update based on what is
_actually_ preserved. Accessing a field added after the original struct
then always requires checking its flag.
At the end of the day it is up to userspace to never start using a
required feature while they can still rollback to a kernel that does not
support it. Cloud Providers know how to do this since Live Migration has
the same problem. This also means we must be very careful when adding
new required features, as they need some way for userspace to explicitly
opt-into them. I do _not_ think we should try to have some generic LUO
opt-in for required features. That is up to the File Handlers and FLBs,
as they have their own uAPI.
[1] https://lore.kernel.org/linux-pci/20261013162529.1928093-1-dmatlack@google.com/
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header
2026-10-21 23:55 ` David Matlack
@ 2026-10-22 0:04 ` David Matlack
0 siblings, 0 replies; 27+ messages in thread
From: David Matlack @ 2026-10-22 0:04 UTC (permalink / raw)
To: Tarun Sahu
Cc: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
jgg, robin.murphy, loganodell, oupton, kevin.tian, alex, dwmw2,
pbonzini, seanjc, will, Pasha Tatashin, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
On Wed, Oct 21, 2026 at 4:55 PM David Matlack <dmatlack@google.com> wrote:
>
> On 2026-10-21 03:25 PM, Tarun Sahu wrote:
> > LUO and its file handlers validate handed-over state with monolithic
> > compatibility strings ("luo-v5", "memfd-v2"). Any change to a serialized
> > structure, even an optional additive one, must bump the string, which
> > breaks live update between adjacent kernels. Replace the string in
> > struct luo_ser with a feature header, so that the target kernel can
> > accept or reject the source kernel state per feature.
> >
> > struct luo_feature_hdr is a fixed 128-byte header: the serialized
> > structure size from the source kernel followed by 15 chained u64 feature
> > flags entries. The fixed size keeps payload field offsets identical in
> > every kernel, regardless of how many flags entries a source kernel uses.
> >
> > Each feature has a 2-bit pair (req, active) state (enum luo_feat_state:
> > LUO_FEAT_DISABLED, LUO_FEAT_OPTIONAL, LUO_FEAT_REQUIRED). A flags entry
> > holds 31 features, and bits [63:62] = 0b11 chain to the next entry (465
> > features in total). Each subsystem defines its feature states as a
> > static const u8 array indexed by its feature enum, which
> > luo_feature_hdr_init() packs into 64-bit flags entries with
> > LUO_FEAT_EXT_MASK chain bits. The target kernel validates the header from
> > the source kernel against its own feature array via
> > luo_feature_hdr_validate() according to the per-feature (req, active)
> > truth table:
> >
> > Source Target Action
> > (1,0) (x,x) Reject -EINVAL: invalid source kernel (req without active)
> > (x,x) (1,0) Reject -EINVAL + WARN_ON_ONCE: invalid target kernel
> > (0,0) (0,0) Accept: feature absent
> > (0,0) (0,1) Accept: feature absent
> > (0,0) (1,1) Reject -EOPNOTSUPP: target kernel requires the feature
> > (0,1) (0,0) Accept: ignore payload data
> > (1,1) (0,0) Reject -EOPNOTSUPP: source kernel requires the feature
> > (x,1) (x,1) Accept: use payload data
>
> I think we have different definitions of "required".
>
> In the PCI series [1], required and optional flags describe the _data_
> the previous kernel handed over, not the kernel itself. A required bit
> means "this preserved state depends on feature X; a kernel that does
> not understand X must not take it over". An optional bit means "the
> data for X is here, but you can safely ignore it". The source kernel
> only sets a bit when the preserved state actually uses the feature.
>
> It's up to the FLB/FH retrieve how to handle the scenario where a
> required bit is set that the target kernel does not understand. For the
> PCI FLB we panic since failing to adopt the PCI state could lead to
> memory corruption as preserved devices can be doing DMA.
>
> The target kernel never requires anything from the source. If a
> feature is absent, the target kernel must handle that, e.g. by
> falling back to default behavior. If it can't, that is a compatibility
> break and should be handled by the compatible string.
>
> In this patch each kernel has a single static feature array that is used
> both to generate the header and to validate the incoming header. So if
> kernel N marks a new feature X as required (so that older kernels reject
> data they cannot understand), then kernel N will also reject every
> kernel that does not have X, because of the (0,0) -> (1,1) row in the
> truth table. That breaks forward updates from N-1 to N, and rollbacks
> from N to N-1 even when **nothing being preserved uses X**. Therefore,
> every new required feature becomes a compatible string bump.
>
> I would drop the target-side required state entirely and let the source
> kernel decide which flags to set for each Live Update based on what is
> _actually_ preserved. Accessing a field added after the original struct
> then always requires checking its flag.
>
> At the end of the day it is up to userspace to never start using a
> required feature while they can still rollback to a kernel that does not
> support it. Cloud Providers know how to do this since Live Migration has
> the same problem. This also means we must be very careful when adding
> new required features, as they need some way for userspace to explicitly
> opt-into them. I do _not_ think we should try to have some generic LUO
> opt-in for required features. That is up to the File Handlers and FLBs,
> as they have their own uAPI.
>
> [1] https://lore.kernel.org/linux-pci/20261013162529.1928093-1-dmatlack@google.com/
Oh and please see the follow-up PCI series to preserve BARs and bridge
windows for some examples of using optional_flags to extend struct
pci_ser with additional data without breaking backwards compatibility.
e.g. https://lore.kernel.org/linux-pci/20261019213054.3157159-5-dmatlack@google.com/
I don't yet have a use-case for a required_flag.
^ permalink raw reply [flat|nested] 27+ messages in thread
* [RFC PATCH v2 4/4] mm: memfd_luo: Add the 2-bit pair feature header
2026-10-21 15:25 [RFC PATCH v2 0/4] liveupdate: Feature header ABI and named handler Tarun Sahu
` (2 preceding siblings ...)
2026-10-21 15:25 ` [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header Tarun Sahu
@ 2026-10-21 15:25 ` Tarun Sahu
2026-10-21 17:38 ` Pasha Tatashin
3 siblings, 1 reply; 27+ messages in thread
From: Tarun Sahu @ 2026-10-21 15:25 UTC (permalink / raw)
To: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
David Matlack, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Pasha Tatashin,
Alexander Graf, Bjorn Helgaas, baolu.lu
Cc: linux-arm-kernel, linux-arch, sagis, linux-mm, kvmarm, iommu,
linux-pci, kexec, skhawaja, kvm, linux-kernel, Tarun Sahu
Embed the fixed-size 128-byte struct luo_feature_hdr at the start of
struct memfd_luo_ser and define enum memfd_luo_feature
(MEMFD_LUO_FEAT_CORE and MEMFD_LUO_FEAT_MAX) for memfd preservation.
All current struct memfd_luo_ser fields (pos, size, seals, flags,
nr_folios, folios) and MEMFD_LUO_ALL_SEALS belong to
MEMFD_LUO_FEAT_CORE (LUO_FEAT_REQUIRED in memfd_luo_features). Future
changes add new entries to enum memfd_luo_feature before
MEMFD_LUO_FEAT_MAX and append any new fields to the end of struct
memfd_luo_ser so existing field offsets never shift.
Signed-off-by: Logan Odell <loganodell@google.com>
Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
include/linux/kho/abi/memfd.h | 35 ++++++++++++++++++++++++++++++-----
mm/memfd_luo.c | 14 ++++++++++++++
2 files changed, 44 insertions(+), 5 deletions(-)
diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h
index 371ffd108a79..f0b5235a408d 100644
--- a/include/linux/kho/abi/memfd.h
+++ b/include/linux/kho/abi/memfd.h
@@ -13,6 +13,7 @@
#include <linux/types.h>
#include <linux/kho/abi/kexec_handover.h>
+#include <linux/kho/abi/luo.h>
/**
* DOC: memfd Live Update ABI
@@ -21,11 +22,20 @@
* reboot using the LUO.
*
* The state is serialized into a packed structure `struct memfd_luo_ser`
- * which is handed over to the next kernel via the KHO mechanism.
+ * which is handed over to the target kernel via the KHO mechanism.
*
- * This interface is a contract. Any modification to the structure layout
- * constitutes a breaking change. Such changes require incrementing the
- * version number in the MEMFD_LUO_FH_COMPATIBLE string.
+ * This interface is a contract. `struct memfd_luo_ser` starts with a fixed
+ * 128-byte `struct luo_feature_hdr`. The relative order and offsets of
+ * fields must remain unchanged as features are added or retired: new fields
+ * are appended at the end of `struct memfd_luo_ser` under a new feature
+ * flag in `enum memfd_luo_feature` (before `MEMFD_LUO_FEAT_MAX`), and
+ * retired fields are kept in place so subsequent offsets never shift. In
+ * the target kernel, fields of a `LUO_FEAT_REQUIRED` feature are guaranteed
+ * present once `luo_feature_hdr_validate()` succeeds, whereas fields of a
+ * `LUO_FEAT_OPTIONAL` feature should be accessed only after checking
+ * `luo_feature_is_active()`. All current fields (`pos`, `size`, `seals`,
+ * `flags`, `nr_folios`, `folios`) and `MEMFD_LUO_ALL_SEALS` belong to
+ * `MEMFD_LUO_FEAT_CORE` (`LUO_FEAT_REQUIRED` in `memfd_luo_features`).
*/
/**
@@ -58,7 +68,7 @@ struct memfd_luo_folio_ser {
/*
* The set of seals this version supports preserving. If support for any new
- * seals is needed, add it here and bump version.
+ * seals is needed, gate them behind a new feature flag.
*/
#define MEMFD_LUO_ALL_SEALS (F_SEAL_SEAL | \
F_SEAL_SHRINK | \
@@ -67,8 +77,22 @@ struct memfd_luo_folio_ser {
F_SEAL_FUTURE_WRITE | \
F_SEAL_EXEC)
+/**
+ * enum memfd_luo_feature - Feature indices for &struct memfd_luo_ser.
+ * @MEMFD_LUO_FEAT_CORE: Core feature covering `pos`, `size`, `seals`, `flags`,
+ * `nr_folios`, `folios`, and %MEMFD_LUO_ALL_SEALS.
+ * @MEMFD_LUO_FEAT_MAX: Number of defined &enum memfd_luo_feature features.
+ */
+enum memfd_luo_feature {
+ MEMFD_LUO_FEAT_CORE = 0,
+ MEMFD_LUO_FEAT_MAX,
+};
+
+static_assert((int)MEMFD_LUO_FEAT_MAX <= LUO_FEAT_MAX);
+
/**
* struct memfd_luo_ser - Main serialization structure for a memfd.
+ * @features: ABI feature and size header for memfd_luo_ser.
* @pos: The file's current position (f_pos).
* @size: The total size of the file in bytes (i_size).
* @seals: The seals present on the memfd. The seals are uABI so it is safe
@@ -79,6 +103,7 @@ struct memfd_luo_folio_ser {
* struct memfd_luo_folio_ser.
*/
struct memfd_luo_ser {
+ struct luo_feature_hdr features;
u64 pos;
u64 size;
u32 seals;
diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
index 64f8b8cb75bd..ea1f4c8cd259 100644
--- a/mm/memfd_luo.c
+++ b/mm/memfd_luo.c
@@ -84,6 +84,10 @@
#include "internal.h"
+static const u8 memfd_luo_features[MEMFD_LUO_FEAT_MAX] = {
+ [MEMFD_LUO_FEAT_CORE] = LUO_FEAT_REQUIRED,
+};
+
static int memfd_luo_preserve_folios(struct file *file,
struct kho_vmalloc *kho_vmalloc,
struct memfd_luo_folio_ser **out_folios_ser,
@@ -284,6 +288,8 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
goto err_free_ser;
}
+ luo_feature_hdr_init(&ser->features, sizeof(*ser),
+ memfd_luo_features);
ser->pos = args->file->f_pos;
inode_size = i_size_read(inode);
@@ -525,6 +531,14 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
if (!ser)
return -EINVAL;
+ err = luo_feature_hdr_validate(&ser->features, memfd_luo_features);
+ if (err) {
+ pr_err("memfd_luo_ser feature header validation failed (size=%llu, flags[0]=%#llx): %pe\n",
+ ser->features.size, ser->features.flags[0],
+ ERR_PTR(err));
+ goto free_ser;
+ }
+
/* Make sure the file only has seals supported by this version. */
if (ser->seals & ~MEMFD_LUO_ALL_SEALS) {
err = -EOPNOTSUPP;
--
2.56.0.212.g129ede20d9-goog
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 4/4] mm: memfd_luo: Add the 2-bit pair feature header
2026-10-21 15:25 ` [RFC PATCH v2 4/4] mm: memfd_luo: Add the " Tarun Sahu
@ 2026-10-21 17:38 ` Pasha Tatashin
2026-10-21 23:18 ` David Matlack
0 siblings, 1 reply; 27+ messages in thread
From: Pasha Tatashin @ 2026-10-21 17:38 UTC (permalink / raw)
To: Tarun Sahu
Cc: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
David Matlack, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
On Wed, Oct 21, 2026 at 11:25 AM Tarun Sahu <tarunsahu@google.com> wrote:
>
> Embed the fixed-size 128-byte struct luo_feature_hdr at the start of
> struct memfd_luo_ser and define enum memfd_luo_feature
> (MEMFD_LUO_FEAT_CORE and MEMFD_LUO_FEAT_MAX) for memfd preservation.
>
> All current struct memfd_luo_ser fields (pos, size, seals, flags,
> nr_folios, folios) and MEMFD_LUO_ALL_SEALS belong to
> MEMFD_LUO_FEAT_CORE (LUO_FEAT_REQUIRED in memfd_luo_features). Future
> changes add new entries to enum memfd_luo_feature before
> MEMFD_LUO_FEAT_MAX and append any new fields to the end of struct
> memfd_luo_ser so existing field offsets never shift.
>
> Signed-off-by: Logan Odell <loganodell@google.com>
> Signed-off-by: Tarun Sahu <tarunsahu@google.com>
> ---
> include/linux/kho/abi/memfd.h | 35 ++++++++++++++++++++++++++++++-----
> mm/memfd_luo.c | 14 ++++++++++++++
> 2 files changed, 44 insertions(+), 5 deletions(-)
>
> diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h
> index 371ffd108a79..f0b5235a408d 100644
> --- a/include/linux/kho/abi/memfd.h
> +++ b/include/linux/kho/abi/memfd.h
> @@ -13,6 +13,7 @@
>
> #include <linux/types.h>
> #include <linux/kho/abi/kexec_handover.h>
> +#include <linux/kho/abi/luo.h>
>
> /**
> * DOC: memfd Live Update ABI
> @@ -21,11 +22,20 @@
> * reboot using the LUO.
> *
> * The state is serialized into a packed structure `struct memfd_luo_ser`
> - * which is handed over to the next kernel via the KHO mechanism.
> + * which is handed over to the target kernel via the KHO mechanism.
> *
> - * This interface is a contract. Any modification to the structure layout
> - * constitutes a breaking change. Such changes require incrementing the
> - * version number in the MEMFD_LUO_FH_COMPATIBLE string.
> + * This interface is a contract. `struct memfd_luo_ser` starts with a fixed
> + * 128-byte `struct luo_feature_hdr`. The relative order and offsets of
> + * fields must remain unchanged as features are added or retired: new fields
> + * are appended at the end of `struct memfd_luo_ser` under a new feature
> + * flag in `enum memfd_luo_feature` (before `MEMFD_LUO_FEAT_MAX`), and
> + * retired fields are kept in place so subsequent offsets never shift. In
> + * the target kernel, fields of a `LUO_FEAT_REQUIRED` feature are guaranteed
> + * present once `luo_feature_hdr_validate()` succeeds, whereas fields of a
> + * `LUO_FEAT_OPTIONAL` feature should be accessed only after checking
> + * `luo_feature_is_active()`. All current fields (`pos`, `size`, `seals`,
> + * `flags`, `nr_folios`, `folios`) and `MEMFD_LUO_ALL_SEALS` belong to
> + * `MEMFD_LUO_FEAT_CORE` (`LUO_FEAT_REQUIRED` in `memfd_luo_features`).
> */
>
> /**
> @@ -58,7 +68,7 @@ struct memfd_luo_folio_ser {
>
> /*
> * The set of seals this version supports preserving. If support for any new
> - * seals is needed, add it here and bump version.
> + * seals is needed, gate them behind a new feature flag.
> */
> #define MEMFD_LUO_ALL_SEALS (F_SEAL_SEAL | \
> F_SEAL_SHRINK | \
> @@ -67,8 +77,22 @@ struct memfd_luo_folio_ser {
> F_SEAL_FUTURE_WRITE | \
> F_SEAL_EXEC)
>
> +/**
> + * enum memfd_luo_feature - Feature indices for &struct memfd_luo_ser.
> + * @MEMFD_LUO_FEAT_CORE: Core feature covering `pos`, `size`, `seals`, `flags`,
> + * `nr_folios`, `folios`, and %MEMFD_LUO_ALL_SEALS.
> + * @MEMFD_LUO_FEAT_MAX: Number of defined &enum memfd_luo_feature features.
> + */
> +enum memfd_luo_feature {
> + MEMFD_LUO_FEAT_CORE = 0,
> + MEMFD_LUO_FEAT_MAX,
> +};
> +
> +static_assert((int)MEMFD_LUO_FEAT_MAX <= LUO_FEAT_MAX);
> +
> /**
> * struct memfd_luo_ser - Main serialization structure for a memfd.
> + * @features: ABI feature and size header for memfd_luo_ser.
> * @pos: The file's current position (f_pos).
> * @size: The total size of the file in bytes (i_size).
> * @seals: The seals present on the memfd. The seals are uABI so it is safe
> @@ -79,6 +103,7 @@ struct memfd_luo_folio_ser {
> * struct memfd_luo_folio_ser.
> */
> struct memfd_luo_ser {
> + struct luo_feature_hdr features;
I have not looked into this yet, but I would prefer to use some kind
of macro constructor for serialized structs. That way, they will
automatically include the header, making it easier to search the code
for what is actually preserved versus what exists only in this kernel.
So, I think something like this:
/* Base serialization header: 4 bytes total size, 4 bytes flags */
struct luo_ser_hdr {
__u32 total_size;
__u32 flags;
};
#define LUO_SER_INIT_HDR(obj, _flags) do { \
(obj)->hdr.total_size = sizeof(*(obj)); \
(obj)->hdr.flags = (_flags); \
} while (0)
#define DECLARE_LUO_SER(name, expected_size, members) \
struct name { \
struct luo_ser_hdr hdr; \
members \
}; \
static_assert(sizeof(struct name) == (expected_size), \
"LUO struct " #name " size mismatch!")
Usage:
/*
* Header: 8 bytes (hdr: 4 bytes total_size + 4 bytes flags)
* Payload: 8 (pos) + 8 (size) + 4 (seals) + 4 (_pad) = 24 bytes
* Total: 32 bytes
*/
DECLARE_LUO_SER(memfd_luo_ser, 32,
__u64 pos;
__u64 size;
__u32 seals;
__u32 _pad;
);
struct memfd_luo_ser *ser;
...
LUO_SER_INIT_HDR(ser, 0);
Thanks,
Pasha
> u64 pos;
> u64 size;
> u32 seals;
> diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
> index 64f8b8cb75bd..ea1f4c8cd259 100644
> --- a/mm/memfd_luo.c
> +++ b/mm/memfd_luo.c
> @@ -84,6 +84,10 @@
>
> #include "internal.h"
>
> +static const u8 memfd_luo_features[MEMFD_LUO_FEAT_MAX] = {
> + [MEMFD_LUO_FEAT_CORE] = LUO_FEAT_REQUIRED,
> +};
> +
> static int memfd_luo_preserve_folios(struct file *file,
> struct kho_vmalloc *kho_vmalloc,
> struct memfd_luo_folio_ser **out_folios_ser,
> @@ -284,6 +288,8 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
> goto err_free_ser;
> }
>
> + luo_feature_hdr_init(&ser->features, sizeof(*ser),
> + memfd_luo_features);
> ser->pos = args->file->f_pos;
> inode_size = i_size_read(inode);
>
> @@ -525,6 +531,14 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
> if (!ser)
> return -EINVAL;
>
> + err = luo_feature_hdr_validate(&ser->features, memfd_luo_features);
> + if (err) {
> + pr_err("memfd_luo_ser feature header validation failed (size=%llu, flags[0]=%#llx): %pe\n",
> + ser->features.size, ser->features.flags[0],
> + ERR_PTR(err));
> + goto free_ser;
> + }
> +
> /* Make sure the file only has seals supported by this version. */
> if (ser->seals & ~MEMFD_LUO_ALL_SEALS) {
> err = -EOPNOTSUPP;
> --
> 2.56.0.212.g129ede20d9-goog
>
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 4/4] mm: memfd_luo: Add the 2-bit pair feature header
2026-10-21 17:38 ` Pasha Tatashin
@ 2026-10-21 23:18 ` David Matlack
2026-10-22 0:41 ` Pasha Tatashin
0 siblings, 1 reply; 27+ messages in thread
From: David Matlack @ 2026-10-21 23:18 UTC (permalink / raw)
To: Pasha Tatashin
Cc: Tarun Sahu, Andrew Morton, joro, Mike Rapoport, Pratyush Yadav,
maz, arnd, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
On 2026-10-21 01:38 PM, Pasha Tatashin wrote:
> On Wed, Oct 21, 2026 at 11:25 AM Tarun Sahu <tarunsahu@google.com> wrote:
> >
> > Embed the fixed-size 128-byte struct luo_feature_hdr at the start of
> > struct memfd_luo_ser and define enum memfd_luo_feature
> > (MEMFD_LUO_FEAT_CORE and MEMFD_LUO_FEAT_MAX) for memfd preservation.
> >
> > All current struct memfd_luo_ser fields (pos, size, seals, flags,
> > nr_folios, folios) and MEMFD_LUO_ALL_SEALS belong to
> > MEMFD_LUO_FEAT_CORE (LUO_FEAT_REQUIRED in memfd_luo_features). Future
> > changes add new entries to enum memfd_luo_feature before
> > MEMFD_LUO_FEAT_MAX and append any new fields to the end of struct
> > memfd_luo_ser so existing field offsets never shift.
> >
> > Signed-off-by: Logan Odell <loganodell@google.com>
> > Signed-off-by: Tarun Sahu <tarunsahu@google.com>
> > ---
> > include/linux/kho/abi/memfd.h | 35 ++++++++++++++++++++++++++++++-----
> > mm/memfd_luo.c | 14 ++++++++++++++
> > 2 files changed, 44 insertions(+), 5 deletions(-)
> >
> > diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h
> > index 371ffd108a79..f0b5235a408d 100644
> > --- a/include/linux/kho/abi/memfd.h
> > +++ b/include/linux/kho/abi/memfd.h
> > @@ -13,6 +13,7 @@
> >
> > #include <linux/types.h>
> > #include <linux/kho/abi/kexec_handover.h>
> > +#include <linux/kho/abi/luo.h>
> >
> > /**
> > * DOC: memfd Live Update ABI
> > @@ -21,11 +22,20 @@
> > * reboot using the LUO.
> > *
> > * The state is serialized into a packed structure `struct memfd_luo_ser`
> > - * which is handed over to the next kernel via the KHO mechanism.
> > + * which is handed over to the target kernel via the KHO mechanism.
> > *
> > - * This interface is a contract. Any modification to the structure layout
> > - * constitutes a breaking change. Such changes require incrementing the
> > - * version number in the MEMFD_LUO_FH_COMPATIBLE string.
> > + * This interface is a contract. `struct memfd_luo_ser` starts with a fixed
> > + * 128-byte `struct luo_feature_hdr`. The relative order and offsets of
> > + * fields must remain unchanged as features are added or retired: new fields
> > + * are appended at the end of `struct memfd_luo_ser` under a new feature
> > + * flag in `enum memfd_luo_feature` (before `MEMFD_LUO_FEAT_MAX`), and
> > + * retired fields are kept in place so subsequent offsets never shift. In
> > + * the target kernel, fields of a `LUO_FEAT_REQUIRED` feature are guaranteed
> > + * present once `luo_feature_hdr_validate()` succeeds, whereas fields of a
> > + * `LUO_FEAT_OPTIONAL` feature should be accessed only after checking
> > + * `luo_feature_is_active()`. All current fields (`pos`, `size`, `seals`,
> > + * `flags`, `nr_folios`, `folios`) and `MEMFD_LUO_ALL_SEALS` belong to
> > + * `MEMFD_LUO_FEAT_CORE` (`LUO_FEAT_REQUIRED` in `memfd_luo_features`).
> > */
> >
> > /**
> > @@ -58,7 +68,7 @@ struct memfd_luo_folio_ser {
> >
> > /*
> > * The set of seals this version supports preserving. If support for any new
> > - * seals is needed, add it here and bump version.
> > + * seals is needed, gate them behind a new feature flag.
> > */
> > #define MEMFD_LUO_ALL_SEALS (F_SEAL_SEAL | \
> > F_SEAL_SHRINK | \
> > @@ -67,8 +77,22 @@ struct memfd_luo_folio_ser {
> > F_SEAL_FUTURE_WRITE | \
> > F_SEAL_EXEC)
> >
> > +/**
> > + * enum memfd_luo_feature - Feature indices for &struct memfd_luo_ser.
> > + * @MEMFD_LUO_FEAT_CORE: Core feature covering `pos`, `size`, `seals`, `flags`,
> > + * `nr_folios`, `folios`, and %MEMFD_LUO_ALL_SEALS.
> > + * @MEMFD_LUO_FEAT_MAX: Number of defined &enum memfd_luo_feature features.
> > + */
> > +enum memfd_luo_feature {
> > + MEMFD_LUO_FEAT_CORE = 0,
> > + MEMFD_LUO_FEAT_MAX,
> > +};
> > +
> > +static_assert((int)MEMFD_LUO_FEAT_MAX <= LUO_FEAT_MAX);
> > +
> > /**
> > * struct memfd_luo_ser - Main serialization structure for a memfd.
> > + * @features: ABI feature and size header for memfd_luo_ser.
> > * @pos: The file's current position (f_pos).
> > * @size: The total size of the file in bytes (i_size).
> > * @seals: The seals present on the memfd. The seals are uABI so it is safe
> > @@ -79,6 +103,7 @@ struct memfd_luo_folio_ser {
> > * struct memfd_luo_folio_ser.
> > */
> > struct memfd_luo_ser {
> > + struct luo_feature_hdr features;
>
> I have not looked into this yet, but I would prefer to use some kind
> of macro constructor for serialized structs. That way, they will
> automatically include the header, making it easier to search the code
> for what is actually preserved versus what exists only in this kernel.
If you want to know what is preserved don't you just look at
include/linux/kho/abi?
>
> So, I think something like this:
> /* Base serialization header: 4 bytes total size, 4 bytes flags */
> struct luo_ser_hdr {
> __u32 total_size;
Why do we need the size? I'm not really opposed to it I just don't
understand what problem it solves.
The luo_ser_hdr is needed for top-level file handler and FLB ser
structs. Those structs are always dynamically allocated and preserved
with KHO.
On retrieve, KHO gives you back the blob of memory. You look at the
header to determine what features are used in the struct, and then
access the fields associated with those features. At no point do you
need to check the size.
The only time you need the size is for a KHO block array. You want to
store the size of the KHO block array elements so that the retrieving
kernel can correctly initialize the iterator.
e.g. from the PCI base series:
/**
* struct pci_ser - PCI Subsystem Live Update State
*
* This struct tracks state about all devices that are being preserved across
* a Live Update for the next kernel. It contains only state owned by the PCI
* core; the state a driver needs to resume its device is preserved separately
* by that driver.
*
* @required_flags: Features that the next kernel must support to take over
* the preserved devices. No flags are currently defined.
* @optional_flags: Features that the next kernel may ignore if it does not
* support them. No flags are currently defined.
* @dev_ser_size: The size in bytes of each struct pci_dev_ser in @devices.
* @nr_devices: The number of devices that were preserved.
* @devices: Physical address of the first KHO block containing pci_dev_ser.
*/
struct pci_ser {
u64 required_flags;
u64 optional_flags;
u32 dev_ser_size; <==========
u32 nr_devices;
u64 devices;
};
> __u32 flags;
> };
>
> #define LUO_SER_INIT_HDR(obj, _flags) do { \
> (obj)->hdr.total_size = sizeof(*(obj)); \
> (obj)->hdr.flags = (_flags); \
> } while (0)
>
> #define DECLARE_LUO_SER(name, expected_size, members) \
> struct name { \
> struct luo_ser_hdr hdr; \
> members \
> }; \
> static_assert(sizeof(struct name) == (expected_size), \
> "LUO struct " #name " size mismatch!")
Why?
Practically every change to a serialized struct is going to involve
appending one or more fields to the end of the struct. Asserting the
size means we have to update the hard-coded size as well every time we
do that.
Is this to protect against embedded structs accidentally changing?
>
> Usage:
> /*
> * Header: 8 bytes (hdr: 4 bytes total_size + 4 bytes flags)
> * Payload: 8 (pos) + 8 (size) + 4 (seals) + 4 (_pad) = 24 bytes
> * Total: 32 bytes
> */
> DECLARE_LUO_SER(memfd_luo_ser, 32,
> __u64 pos;
> __u64 size;
> __u32 seals;
> __u32 _pad;
> );
IMO this makes the code harder to read. I think open coding keeps things
simpler.
struct pci_ser {
struct luo_ser_hdr header;
__u32 dev_ser_size;
__u32 nr_devices;
__u64 devices;
};
LUO_SER_ASSSERT_SIZE(pci_ser, 32);
>
> struct memfd_luo_ser *ser;
> ...
> LUO_SER_INIT_HDR(ser, 0);
>
> Thanks,
> Pasha
>
> > u64 pos;
> > u64 size;
> > u32 seals;
> > diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
> > index 64f8b8cb75bd..ea1f4c8cd259 100644
> > --- a/mm/memfd_luo.c
> > +++ b/mm/memfd_luo.c
> > @@ -84,6 +84,10 @@
> >
> > #include "internal.h"
> >
> > +static const u8 memfd_luo_features[MEMFD_LUO_FEAT_MAX] = {
> > + [MEMFD_LUO_FEAT_CORE] = LUO_FEAT_REQUIRED,
> > +};
> > +
> > static int memfd_luo_preserve_folios(struct file *file,
> > struct kho_vmalloc *kho_vmalloc,
> > struct memfd_luo_folio_ser **out_folios_ser,
> > @@ -284,6 +288,8 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
> > goto err_free_ser;
> > }
> >
> > + luo_feature_hdr_init(&ser->features, sizeof(*ser),
> > + memfd_luo_features);
> > ser->pos = args->file->f_pos;
> > inode_size = i_size_read(inode);
> >
> > @@ -525,6 +531,14 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
> > if (!ser)
> > return -EINVAL;
> >
> > + err = luo_feature_hdr_validate(&ser->features, memfd_luo_features);
> > + if (err) {
> > + pr_err("memfd_luo_ser feature header validation failed (size=%llu, flags[0]=%#llx): %pe\n",
> > + ser->features.size, ser->features.flags[0],
> > + ERR_PTR(err));
> > + goto free_ser;
> > + }
> > +
> > /* Make sure the file only has seals supported by this version. */
> > if (ser->seals & ~MEMFD_LUO_ALL_SEALS) {
> > err = -EOPNOTSUPP;
> > --
> > 2.56.0.212.g129ede20d9-goog
> >
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 4/4] mm: memfd_luo: Add the 2-bit pair feature header
2026-10-21 23:18 ` David Matlack
@ 2026-10-22 0:41 ` Pasha Tatashin
2026-10-22 2:51 ` David Matlack
0 siblings, 1 reply; 27+ messages in thread
From: Pasha Tatashin @ 2026-10-22 0:41 UTC (permalink / raw)
To: David Matlack
Cc: Tarun Sahu, Andrew Morton, joro, Mike Rapoport, Pratyush Yadav,
maz, arnd, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
> > > /**
> > > * struct memfd_luo_ser - Main serialization structure for a memfd.
> > > + * @features: ABI feature and size header for memfd_luo_ser.
> > > * @pos: The file's current position (f_pos).
> > > * @size: The total size of the file in bytes (i_size).
> > > * @seals: The seals present on the memfd. The seals are uABI so it is safe
> > > @@ -79,6 +103,7 @@ struct memfd_luo_folio_ser {
> > > * struct memfd_luo_folio_ser.
> > > */
> > > struct memfd_luo_ser {
> > > + struct luo_feature_hdr features;
> >
> > I have not looked into this yet, but I would prefer to use some kind
> > of macro constructor for serialized structs. That way, they will
> > automatically include the header, making it easier to search the code
> > for what is actually preserved versus what exists only in this kernel.
>
> If you want to know what is preserved don't you just look at
> include/linux/kho/abi?
Yes, and having it initialized and declared in the code correctly with
proper macros and verified just gives the right handling and avoids
making silly mistakes. The second reason is so we never forget to
include the header, and to make it expandable and easy for everyone to
use. I think this kind of constructor is just useful overall. Are
there any reasons why we wouldn't want to use it?
> > So, I think something like this:
> > /* Base serialization header: 4 bytes total size, 4 bytes flags */
> > struct luo_ser_hdr {
> > __u32 total_size;
>
> Why do we need the size? I'm not really opposed to it I just don't
> understand what problem it solves.
[...]
> The luo_ser_hdr is needed for top-level file handler and FLB ser
> structs. Those structs are always dynamically allocated and preserved
> with KHO.
The top level is not enough. I think it is needed for every single
serialized struct in kho/abi - the blocks, the FLBs, etc. They all
must
be expandable in case we need to allow that. Let's have future
protection for everything that gets preserved.
For example, take a look at vCPU preservation:
https://lore.kernel.org/all/20260920193650.3373435-9-pasha.tatashin@soleen.com
https://lore.kernel.org/all/20260920193650.3373435-11-pasha.tatashin@soleen.com
+struct kvm_vcpu_ser {
+ u32 vcpu_id;
+ u32 flags;
+ u64 vm_token;
+ DECLARE_KHOSER_PTR(arch_state, struct kvm_vcpu_arch_ser *);
+};
We might want to add fields to the arch-independent part, and we also
absolutely must keep the arch-dependent parts expandable. Therefore,
the header needs to stay small and flexible enough to grow as needed.
> On retrieve, KHO gives you back the blob of memory. You look at the
> header to determine what features are used in the struct, and then
> access the fields associated with those features. At no point do you
> need to check the size.
>
> The only time you need the size is for a KHO block array. You want to
> store the size of the KHO block array elements so that the retrieving
> kernel can correctly initialize the iterator.
>
> e.g. from the PCI base series:
>
> /**
> * struct pci_ser - PCI Subsystem Live Update State
> *
> * This struct tracks state about all devices that are being preserved across
> * a Live Update for the next kernel. It contains only state owned by the PCI
> * core; the state a driver needs to resume its device is preserved separately
> * by that driver.
> *
> * @required_flags: Features that the next kernel must support to take over
> * the preserved devices. No flags are currently defined.
> * @optional_flags: Features that the next kernel may ignore if it does not
> * support them. No flags are currently defined.
> * @dev_ser_size: The size in bytes of each struct pci_dev_ser in @devices.
> * @nr_devices: The number of devices that were preserved.
> * @devices: Physical address of the first KHO block containing pci_dev_ser.
> */
> struct pci_ser {
> u64 required_flags;
> u64 optional_flags;
> u32 dev_ser_size; <==========
> u32 nr_devices;
> u64 devices;
> };
Having `total_size` embedded in the standard header of every
serialized struct solves this uniformly: any struct can be placed in a
block array without needing an ad-hoc `*_ser_size` field in the parent
struct, and on retrieve we can always sanity-check that the preserved
blob is at least as large as the struct fields implied by the flags.
>
> > __u32 flags;
> > };
> >
> > #define LUO_SER_INIT_HDR(obj, _flags) do { \
> > (obj)->hdr.total_size = sizeof(*(obj)); \
> > (obj)->hdr.flags = (_flags); \
> > } while (0)
> >
> > #define DECLARE_LUO_SER(name, expected_size, members) \
> > struct name { \
> > struct luo_ser_hdr hdr; \
> > members \
> > }; \
> > static_assert(sizeof(struct name) == (expected_size), \
> > "LUO struct " #name " size mismatch!")
>
> Why?
>
> Practically every change to a serialized struct is going to involve
> appending one or more fields to the end of the struct. Asserting the
> size means we have to update the hard-coded size as well every time we
> do that.
>
> Is this to protect against embedded structs accidentally changing?
>
Yes, exactly. See how kvm_vcpu_arch_ser is proposed in that same series:
+struct kvm_vcpu_arch_ser {
+ struct kvm_regs regs;
+ struct kvm_sregs sregs;
+ struct kvm_mp_state mp_state;
+ u32 pad;
+ struct kvm_xcrs xcrs;
+ struct kvm_lapic_state lapic;
+ u8 pad_xsave[40];
+ struct kvm_xsave xsave;
+ struct kvm_vcpu_events events;
+ struct kvm_debugregs debugregs;
+ u32 num_msrs;
+ u32 cpuid_nent;
+ struct kvm_msr_entry msrs[];
+};
All the above structs are uAPI, so they are stable compared to random
kernel structs, but they still have a size and can be expanded. So,
having the size and a static assert guarantees that when we expand
these uAPI structs, we automatically catch that in this struct and add
the appropriate flag to notify the next kernel of the changed world.
Also, it catches implicit compiler padding changes across
architectures.
> >
> > Usage:
> > /*
> > * Header: 8 bytes (hdr: 4 bytes total_size + 4 bytes flags)
> > * Payload: 8 (pos) + 8 (size) + 4 (seals) + 4 (_pad) = 24 bytes
> > * Total: 32 bytes
> > */
> > DECLARE_LUO_SER(memfd_luo_ser, 32,
> > __u64 pos;
> > __u64 size;
> > __u32 seals;
> > __u32 _pad;
> > );
>
> IMO this makes the code harder to read. I think open coding keeps things
> simpler.
I am ok switching to a different construct or way of declaring these
structs if what I proposed is hard to read. However, we still need
standardization so that every serialized struct has a common,
expandable header (taking only 8 bytes by default), has its size
verified at both compile time and runtime, and is initialized in a
uniform way.
>
> struct pci_ser {
> struct luo_ser_hdr header;
> __u32 dev_ser_size;
> __u32 nr_devices;
> __u64 devices;
> };
> LUO_SER_ASSSERT_SIZE(pci_ser, 32);
>
> >
> > struct memfd_luo_ser *ser;
> > ...
> > LUO_SER_INIT_HDR(ser, 0);
> >
> > Thanks,
> > Pasha
> >
> > > u64 pos;
> > > u64 size;
> > > u32 seals;
> > > diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
> > > index 64f8b8cb75bd..ea1f4c8cd259 100644
> > > --- a/mm/memfd_luo.c
> > > +++ b/mm/memfd_luo.c
> > > @@ -84,6 +84,10 @@
> > >
> > > #include "internal.h"
> > >
> > > +static const u8 memfd_luo_features[MEMFD_LUO_FEAT_MAX] = {
> > > + [MEMFD_LUO_FEAT_CORE] = LUO_FEAT_REQUIRED,
> > > +};
> > > +
> > > static int memfd_luo_preserve_folios(struct file *file,
> > > struct kho_vmalloc *kho_vmalloc,
> > > struct memfd_luo_folio_ser **out_folios_ser,
> > > @@ -284,6 +288,8 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
> > > goto err_free_ser;
> > > }
> > >
> > > + luo_feature_hdr_init(&ser->features, sizeof(*ser),
> > > + memfd_luo_features);
> > > ser->pos = args->file->f_pos;
> > > inode_size = i_size_read(inode);
> > >
> > > @@ -525,6 +531,14 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
> > > if (!ser)
> > > return -EINVAL;
> > >
> > > + err = luo_feature_hdr_validate(&ser->features, memfd_luo_features);
> > > + if (err) {
> > > + pr_err("memfd_luo_ser feature header validation failed (size=%llu, flags[0]=%#llx): %pe\n",
> > > + ser->features.size, ser->features.flags[0],
> > > + ERR_PTR(err));
> > > + goto free_ser;
> > > + }
> > > +
> > > /* Make sure the file only has seals supported by this version. */
> > > if (ser->seals & ~MEMFD_LUO_ALL_SEALS) {
> > > err = -EOPNOTSUPP;
> > > --
> > > 2.56.0.212.g129ede20d9-goog
> > >
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [RFC PATCH v2 4/4] mm: memfd_luo: Add the 2-bit pair feature header
2026-10-22 0:41 ` Pasha Tatashin
@ 2026-10-22 2:51 ` David Matlack
0 siblings, 0 replies; 27+ messages in thread
From: David Matlack @ 2026-10-22 2:51 UTC (permalink / raw)
To: Pasha Tatashin
Cc: Tarun Sahu, Andrew Morton, joro, Mike Rapoport, Pratyush Yadav,
maz, arnd, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Alexander Graf,
Bjorn Helgaas, baolu.lu, linux-arm-kernel, linux-arch, sagis,
linux-mm, kvmarm, iommu, linux-pci, kexec, skhawaja, kvm,
linux-kernel
On 2026-10-21 08:41 PM, Pasha Tatashin wrote:
> > > > /**
> > > > * struct memfd_luo_ser - Main serialization structure for a memfd.
> > > > + * @features: ABI feature and size header for memfd_luo_ser.
> > > > * @pos: The file's current position (f_pos).
> > > > * @size: The total size of the file in bytes (i_size).
> > > > * @seals: The seals present on the memfd. The seals are uABI so it is safe
> > > > @@ -79,6 +103,7 @@ struct memfd_luo_folio_ser {
> > > > * struct memfd_luo_folio_ser.
> > > > */
> > > > struct memfd_luo_ser {
> > > > + struct luo_feature_hdr features;
> > >
> > > I have not looked into this yet, but I would prefer to use some kind
> > > of macro constructor for serialized structs. That way, they will
> > > automatically include the header, making it easier to search the code
> > > for what is actually preserved versus what exists only in this kernel.
> >
> > If you want to know what is preserved don't you just look at
> > include/linux/kho/abi?
>
> Yes, and having it initialized and declared in the code correctly with
> proper macros and verified just gives the right handling and avoids
> making silly mistakes. The second reason is so we never forget to
> include the header, and to make it expandable and easy for everyone to
> use. I think this kind of constructor is just useful overall. Are
> there any reasons why we wouldn't want to use it?
>
> > > So, I think something like this:
> > > /* Base serialization header: 4 bytes total size, 4 bytes flags */
> > > struct luo_ser_hdr {
> > > __u32 total_size;
> >
> > Why do we need the size? I'm not really opposed to it I just don't
> > understand what problem it solves.
> [...]
> > The luo_ser_hdr is needed for top-level file handler and FLB ser
> > structs. Those structs are always dynamically allocated and preserved
> > with KHO.
>
> The top level is not enough. I think it is needed for every single
> serialized struct in kho/abi - the blocks, the FLBs, etc. They all
> must
> be expandable in case we need to allow that. Let's have future
> protection for everything that gets preserved.
>
> For example, take a look at vCPU preservation:
> https://lore.kernel.org/all/20260920193650.3373435-9-pasha.tatashin@soleen.com
> https://lore.kernel.org/all/20260920193650.3373435-11-pasha.tatashin@soleen.com
>
> +struct kvm_vcpu_ser {
> + u32 vcpu_id;
> + u32 flags;
> + u64 vm_token;
> + DECLARE_KHOSER_PTR(arch_state, struct kvm_vcpu_arch_ser *);
> +};
>
> We might want to add fields to the arch-independent part, and we also
> absolutely must keep the arch-dependent parts expandable. Therefore,
> the header needs to stay small and flexible enough to grow as needed.
Hm, anything that is dynamically allocated can have it, but anything
that it is embedded directly or in an array cannot, because those
structs cannot grow anyway. So I agree it's not exclusive to the
top-level FLB and FH structs. But I would push back on any a mandate
that _every_ struct in include/linux/kho/abi needs it.
>
> > On retrieve, KHO gives you back the blob of memory. You look at the
> > header to determine what features are used in the struct, and then
> > access the fields associated with those features. At no point do you
> > need to check the size.
> >
> > The only time you need the size is for a KHO block array. You want to
> > store the size of the KHO block array elements so that the retrieving
> > kernel can correctly initialize the iterator.
> >
> > e.g. from the PCI base series:
> >
> > /**
> > * struct pci_ser - PCI Subsystem Live Update State
> > *
> > * This struct tracks state about all devices that are being preserved across
> > * a Live Update for the next kernel. It contains only state owned by the PCI
> > * core; the state a driver needs to resume its device is preserved separately
> > * by that driver.
> > *
> > * @required_flags: Features that the next kernel must support to take over
> > * the preserved devices. No flags are currently defined.
> > * @optional_flags: Features that the next kernel may ignore if it does not
> > * support them. No flags are currently defined.
> > * @dev_ser_size: The size in bytes of each struct pci_dev_ser in @devices.
> > * @nr_devices: The number of devices that were preserved.
> > * @devices: Physical address of the first KHO block containing pci_dev_ser.
> > */
> > struct pci_ser {
> > u64 required_flags;
> > u64 optional_flags;
> > u32 dev_ser_size; <==========
> > u32 nr_devices;
> > u64 devices;
> > };
>
> Having `total_size` embedded in the standard header of every
> serialized struct solves this uniformly: any struct can be placed in a
> block array without needing an ad-hoc `*_ser_size` field in the parent
> struct, and on retrieve we can always sanity-check that the preserved
> blob is at least as large as the struct fields implied by the flags.
I agree we shouldn't need an ad-hoc *_ser_size field in the parent. But
I don't think the entries are the right place to store the size.
A KHO block set needs the entry size before it can do anything with the
incoming blocks. kho_block_set_restore() uses it to validate each
block's count, and the iterator uses it to locate every entry (index *
entry_size). The entry size is a property of the block set, not of each
entry.
Putting the size in a header on every entry means:
- The block set has to peek into the first entry to learn its own
layout, so KHO blocks would depend on the LUO header format of
whatever is stored in them.
- Every entry repeats the same value, since all entries in a block set
are written by the same kernel using the same struct. For small
entries that is a lot of overhead, e.g. struct pci_dev_ser is 8 bytes,
so an 8-byte header doubles its size.
- Nothing enforces that all the entries actually agree.
Instead, I think KHO blocks should record the entry size themselves.
For example:
struct kho_block_header_ser {
u64 next;
u32 count;
u32 entry_size;
};
kho_block_set_restore() would then take the entry size from the
previous kernel and verify that every block agrees. Users of the block
set can read the entry size back and decide how to handle entries
written by an older or newer kernel, e.g. ignoring fields past the end
of a smaller entry. That removes dev_ser_size from struct pci_ser, and
any struct can be stored in a block array without a header or a size
field in the parent.
I also don't think block set entries need their own feature flags. The
struct that holds the block set (e.g. struct pci_ser) already has
feature flags, and those flags can describe extensions to the entries.
For example, an optional flag in struct pci_ser could indicate that
struct pci_dev_ser has a new field appended. All entries in a block set
are written by the same kernel, so the flags would be identical for
every entry anyway. The flags only need to be checked once, before
iterating, rather than once per entry.
If a feature only applies to some entries (e.g. some devices), the
parent flag indicates the field is present and the value of the field
in each entry indicates whether that entry uses it.
The entry size in the block header and the feature flags in the parent
are complementary: the entry size lets the iterator step over fields
the kernel doesn't know about, and the flags say which fields are there
and whether the next kernel must understand them.
Please take a look at this series for how I am extending KHO block set
elements using top-level feature flags:
https://lore.kernel.org/linux-pci/20261019213054.3157159-1-dmatlack@google.com/
>
> >
> > > __u32 flags;
> > > };
> > >
> > > #define LUO_SER_INIT_HDR(obj, _flags) do { \
> > > (obj)->hdr.total_size = sizeof(*(obj)); \
> > > (obj)->hdr.flags = (_flags); \
> > > } while (0)
> > >
> > > #define DECLARE_LUO_SER(name, expected_size, members) \
> > > struct name { \
> > > struct luo_ser_hdr hdr; \
> > > members \
> > > }; \
> > > static_assert(sizeof(struct name) == (expected_size), \
> > > "LUO struct " #name " size mismatch!")
> >
> > Why?
> >
> > Practically every change to a serialized struct is going to involve
> > appending one or more fields to the end of the struct. Asserting the
> > size means we have to update the hard-coded size as well every time we
> > do that.
> >
> > Is this to protect against embedded structs accidentally changing?
> >
>
> Yes, exactly. See how kvm_vcpu_arch_ser is proposed in that same series:
>
> +struct kvm_vcpu_arch_ser {
> + struct kvm_regs regs;
> + struct kvm_sregs sregs;
> + struct kvm_mp_state mp_state;
> + u32 pad;
> + struct kvm_xcrs xcrs;
> + struct kvm_lapic_state lapic;
> + u8 pad_xsave[40];
> + struct kvm_xsave xsave;
> + struct kvm_vcpu_events events;
> + struct kvm_debugregs debugregs;
> + u32 num_msrs;
> + u32 cpuid_nent;
> + struct kvm_msr_entry msrs[];
> +};
>
> All the above structs are uAPI, so they are stable compared to random
> kernel structs, but they still have a size and can be expanded. So,
> having the size and a static assert guarantees that when we expand
> these uAPI structs, we automatically catch that in this struct and add
> the appropriate flag to notify the next kernel of the changed world.
> Also, it catches implicit compiler padding changes across
> architectures.
Ok makes sense. Having protection from accidentally changing the offset
of fields in a serialized struct is a good thing.
>
> > >
> > > Usage:
> > > /*
> > > * Header: 8 bytes (hdr: 4 bytes total_size + 4 bytes flags)
> > > * Payload: 8 (pos) + 8 (size) + 4 (seals) + 4 (_pad) = 24 bytes
> > > * Total: 32 bytes
> > > */
> > > DECLARE_LUO_SER(memfd_luo_ser, 32,
> > > __u64 pos;
> > > __u64 size;
> > > __u32 seals;
> > > __u32 _pad;
> > > );
> >
> > IMO this makes the code harder to read. I think open coding keeps things
> > simpler.
>
> I am ok switching to a different construct or way of declaring these
> structs if what I proposed is hard to read. However, we still need
> standardization so that every serialized struct has a common,
> expandable header (taking only 8 bytes by default), has its size
> verified at both compile time and runtime, and is initialized in a
> uniform way.
I think your idea to have a growable header from the thread basically
means that the header and the ser struct will be 2 completely separate
structs and you will need to user pointer arithmetic to jump past the
header and find your serialized struct. So I think this is probably
moot.
>
> >
> > struct pci_ser {
> > struct luo_ser_hdr header;
> > __u32 dev_ser_size;
> > __u32 nr_devices;
> > __u64 devices;
> > };
> > LUO_SER_ASSSERT_SIZE(pci_ser, 32);
> >
> > >
> > > struct memfd_luo_ser *ser;
> > > ...
> > > LUO_SER_INIT_HDR(ser, 0);
> > >
> > > Thanks,
> > > Pasha
> > >
> > > > u64 pos;
> > > > u64 size;
> > > > u32 seals;
> > > > diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
> > > > index 64f8b8cb75bd..ea1f4c8cd259 100644
> > > > --- a/mm/memfd_luo.c
> > > > +++ b/mm/memfd_luo.c
> > > > @@ -84,6 +84,10 @@
> > > >
> > > > #include "internal.h"
> > > >
> > > > +static const u8 memfd_luo_features[MEMFD_LUO_FEAT_MAX] = {
> > > > + [MEMFD_LUO_FEAT_CORE] = LUO_FEAT_REQUIRED,
> > > > +};
> > > > +
> > > > static int memfd_luo_preserve_folios(struct file *file,
> > > > struct kho_vmalloc *kho_vmalloc,
> > > > struct memfd_luo_folio_ser **out_folios_ser,
> > > > @@ -284,6 +288,8 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
> > > > goto err_free_ser;
> > > > }
> > > >
> > > > + luo_feature_hdr_init(&ser->features, sizeof(*ser),
> > > > + memfd_luo_features);
> > > > ser->pos = args->file->f_pos;
> > > > inode_size = i_size_read(inode);
> > > >
> > > > @@ -525,6 +531,14 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
> > > > if (!ser)
> > > > return -EINVAL;
> > > >
> > > > + err = luo_feature_hdr_validate(&ser->features, memfd_luo_features);
> > > > + if (err) {
> > > > + pr_err("memfd_luo_ser feature header validation failed (size=%llu, flags[0]=%#llx): %pe\n",
> > > > + ser->features.size, ser->features.flags[0],
> > > > + ERR_PTR(err));
> > > > + goto free_ser;
> > > > + }
> > > > +
> > > > /* Make sure the file only has seals supported by this version. */
> > > > if (ser->seals & ~MEMFD_LUO_ALL_SEALS) {
> > > > err = -EOPNOTSUPP;
> > > > --
> > > > 2.56.0.212.g129ede20d9-goog
> > > >
^ permalink raw reply [flat|nested] 27+ messages in thread