* [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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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
0 siblings, 0 replies; 18+ 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] 18+ 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 15:25 ` [RFC PATCH v2 4/4] mm: memfd_luo: Add the " Tarun Sahu
3 siblings, 1 reply; 18+ 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] 18+ 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
0 siblings, 2 replies; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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
0 siblings, 0 replies; 18+ 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] 18+ 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; 18+ 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] 18+ 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
0 siblings, 0 replies; 18+ 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] 18+ messages in thread