Re: [PATCH v6 04/20] liveupdate: luo_session: add sessions support
From: Mike Rapoport
Date: Sun Nov 16 2025 - 12:06:39 EST
On Sat, Nov 15, 2025 at 06:33:50PM -0500, Pasha Tatashin wrote:
> Introduce concept of "Live Update Sessions" within the LUO framework.
> LUO sessions provide a mechanism to group and manage `struct file *`
> instances (representing file descriptors) that need to be preserved
> across a kexec-based live update.
>
> Each session is identified by a unique name and acts as a container
> for file objects whose state is critical to a userspace workload, such
> as a virtual machine or a high-performance database, aiming to maintain
> their functionality across a kernel transition.
>
> This groundwork establishes the framework for preserving file-backed
> state across kernel updates, with the actual file data preservation
> mechanisms to be implemented in subsequent patches.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin@xxxxxxxxxx>
> ---
> include/linux/liveupdate/abi/luo.h | 83 +++++-
> include/uapi/linux/liveupdate.h | 3 +
> kernel/liveupdate/Makefile | 3 +-
> kernel/liveupdate/luo_core.c | 10 +
> kernel/liveupdate/luo_internal.h | 52 ++++
> kernel/liveupdate/luo_session.c | 421 +++++++++++++++++++++++++++++
> 6 files changed, 570 insertions(+), 2 deletions(-)
> create mode 100644 kernel/liveupdate/luo_internal.h
> create mode 100644 kernel/liveupdate/luo_session.c
...
> +/**
> + * struct luo_session_ser - Represents the serialized metadata for a LUO session.
> + * @name: The unique name of the session, copied from the `luo_session`
> + * structure.
I'd phase it as
The unique name of the session provided by the userspace at
the time of session creation.
> + * @files: The physical address of a contiguous memory block that holds
> + * the serialized state of files.
Maybe add ^ in this session?
> + * @pgcnt: The number of pages occupied by the `files` memory block.
> + * @count: The total number of files that were part of this session during
> + * serialization. Used for iteration and validation during
> + * restoration.
> + *
> + * 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.
> + *
> + * If this structure is modified, LUO_SESSION_COMPATIBLE must be updated.
This comment applies to the luo_session_header_ser description as well.
> + */
> +struct luo_session_ser {
> + char name[LIVEUPDATE_SESSION_NAME_LENGTH];
> + u64 files;
> + u64 pgcnt;
> + u64 count;
> +} __packed;
> +
> #endif /* _LINUX_LIVEUPDATE_ABI_LUO_H */
> diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
> index df34c1642c4d..d2ef2f7e0dbd 100644
> --- a/include/uapi/linux/liveupdate.h
> +++ b/include/uapi/linux/liveupdate.h
> @@ -43,4 +43,7 @@
> /* The ioctl type, documented in ioctl-number.rst */
> #define LIVEUPDATE_IOCTL_TYPE 0xBA
>
> +/* The maximum length of session name including null termination */
> +#define LIVEUPDATE_SESSION_NAME_LENGTH 56
You decided not to bump it to 64 in the end? ;-)
> +
> #endif /* _UAPI_LIVEUPDATE_H */
> diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
> index 413722002b7a..83285e7ad726 100644
> --- a/kernel/liveupdate/Makefile
> +++ b/kernel/liveupdate/Makefile
> @@ -2,7 +2,8 @@
>
> luo-y := \
> luo_core.o \
> - luo_ioctl.o
> + luo_ioctl.o \
> + luo_session.o
>
> obj-$(CONFIG_KEXEC_HANDOVER) += kexec_handover.o
> obj-$(CONFIG_KEXEC_HANDOVER_DEBUG) += kexec_handover_debug.o
...
> +int luo_session_retrieve(const char *name, struct file **filep)
> +{
> + struct luo_session_header *sh = &luo_session_global.incoming;
> + struct luo_session *session = NULL;
> + struct luo_session *it;
> + int err;
> +
> + scoped_guard(rwsem_read, &sh->rwsem) {
> + list_for_each_entry(it, &sh->list, list) {
> + if (!strncmp(it->name, name, sizeof(it->name))) {
> + session = it;
> + break;
> + }
> + }
> + }
> +
> + if (!session)
> + return -ENOENT;
> +
> + scoped_guard(mutex, &session->mutex) {
> + if (session->retrieved)
> + return -EINVAL;
> + }
> +
> + err = luo_session_getfile(session, filep);
> + if (!err) {
> + scoped_guard(mutex, &session->mutex)
> + session->retrieved = true;
Retaking the mutex here seems a bit odd.
Do we really have to lock session->mutex in luo_session_getfile()?
> + }
> +
> + return err;
> +}
...
> +int __init luo_session_setup_incoming(void *fdt_in)
> +{
> + struct luo_session_header_ser *header_ser;
> + int err, header_size, offset;
> + u64 header_ser_pa;
> + const void *ptr;
> +
> + offset = fdt_subnode_offset(fdt_in, 0, LUO_FDT_SESSION_NODE_NAME);
> + if (offset < 0) {
> + pr_err("Unable to get session node: [%s]\n",
> + LUO_FDT_SESSION_NODE_NAME);
> + return -EINVAL;
> + }
> +
> + err = fdt_node_check_compatible(fdt_in, offset,
> + LUO_FDT_SESSION_COMPATIBLE);
> + if (err) {
> + pr_err("Session node incompatible [%s]\n",
> + LUO_FDT_SESSION_COMPATIBLE);
> + return -EINVAL;
> + }
> +
> + header_size = 0;
> + ptr = fdt_getprop(fdt_in, offset, LUO_FDT_SESSION_HEADER, &header_size);
> + if (!ptr || header_size != sizeof(u64)) {
> + pr_err("Unable to get session header '%s' [%d]\n",
> + LUO_FDT_SESSION_HEADER, header_size);
> + return -EINVAL;
> + }
> +
> + header_ser_pa = get_unaligned((u64 *)ptr);
> + header_ser = phys_to_virt(header_ser_pa);
> +
> + luo_session_global.incoming.header_ser = header_ser;
> + luo_session_global.incoming.ser = (void *)(header_ser + 1);
> + INIT_LIST_HEAD(&luo_session_global.incoming.list);
> + init_rwsem(&luo_session_global.incoming.rwsem);
> + luo_session_global.incoming.active = true;
> +
> + return 0;
> +}
> +
> +bool luo_session_is_deserialized(void)
> +{
> + return luo_session_global.deserialized;
> +}
> +
> +int luo_session_deserialize(void)
> +{
> + struct luo_session_header *sh = &luo_session_global.incoming;
> + int err;
> +
> + if (luo_session_is_deserialized())
> + return 0;
> +
> + luo_session_global.deserialized = true;
> + if (!sh->active) {
> + INIT_LIST_HEAD(&sh->list);
> + init_rwsem(&sh->rwsem);
> + return 0;
How this can happen? luo_session_deserialize() is supposed to be called
from ioctl and luo_session_global.incoming should be set up way earlier.
And, why don't we initialize ->list and ->rwsem statically?
> + }
> +
> + for (int i = 0; i < sh->header_ser->count; i++) {
> + struct luo_session *session;
> +
> + session = luo_session_alloc(sh->ser[i].name);
> + if (IS_ERR(session)) {
> + pr_warn("Failed to allocate session [%s] during deserialization %pe\n",
> + sh->ser[i].name, session);
> + return PTR_ERR(session);
> + }
The allocated sessions still need to be freed if an insert fails ;-)
> +
> + err = luo_session_insert(sh, session);
> + if (err) {
> + luo_session_free(session);
> + pr_warn("Failed to insert session [%s] %pe\n",
> + session->name, ERR_PTR(err));
> + return err;
> + }
> +
> + session->count = sh->ser[i].count;
> + session->files = sh->ser[i].files ? phys_to_virt(sh->ser[i].files) : 0;
> + session->pgcnt = sh->ser[i].pgcnt;
> + }
> +
> + kho_restore_free(sh->header_ser);
> + sh->header_ser = NULL;
> + sh->ser = NULL;
> +
> + return 0;
> +}
--
Sincerely yours,
Mike.