This series introduces the LUO, a kernel subsystem designed to
facilitate live kernel updates with minimal downtime,
particularly in cloud delplyoments aiming to update without fully
disrupting running virtual machines.
This series builds upon KHO framework by adding programmatic
control over KHO's lifecycle and leveraging KHO for persisting LUO's
own metadata across the kexec boundary. The git branch for this series
can be found at:
https://github.com/googleprodkernel/linux-liveupdate/tree/luo/v3
Changelog from v2:
- Addressed comments from Mike Rapoport and Jason Gunthorpe
- Only one user agent (LiveupdateD) can open /dev/liveupdate
- Release all preserved resources if /dev/liveupdate closes
before reboot.
- With the above changes, sessions are not needed, and should be
maintained by the user-agent itself, so removed support for
sessions.
- Added support for changing per-FD state (i.e. some FDs can be
prepared or finished before the global transition.
- All IOCTLs now follow iommufd/fwctl extendable design.
- Replaced locks with guards
- Added a callback for registered subsystems to be notified
during boot: ops->boot().
- Removed args from callbacks, instead use container_of() to
carry context specific data (see luo_selftests.c for example).
- removed patches for luolib, they are going to be introduced in
a separate repository.
What is Live Update?
Live Update is a kexec based reboot process where selected kernel
resources (memory, file descriptors, and eventually devices) are kept
operational or their state preserved across a kernel transition. For
certain resources, DMA and interrupt activity might continue with
minimal interruption during the kernel reboot.
LUO provides a framework for coordinating live updates. It features:
State Machine: Manages the live update process through states:
NORMAL, PREPARED, FROZEN, UPDATED.
KHO Integration:
LUO programmatically drives KHO's finalization and abort sequences.
KHO's debugfs interface is now optional configured via
CONFIG_KEXEC_HANDOVER_DEBUG.
LUO preserves its own metadata via KHO's kho_add_subtree and
kho_preserve_phys() mechanisms.
Subsystem Participation: A callback API liveupdate_register_subsystem()
allows kernel subsystems (e.g., KVM, IOMMU, VFIO, PCI) to register
handlers for LUO events (PREPARE, FREEZE, FINISH, CANCEL) and persist a
u64 payload via the LUO FDT.
File Descriptor Preservation: Infrastructure
liveupdate_register_filesystem, luo_register_file, luo_retrieve_file to
allow specific types of file descriptors (e.g., memfd, vfio) to be
preserved and restored.
Handlers for specific file types can be registered to manage their
preservation and restoration, storing a u64 payload in the LUO FDT.
User-space Interface:
ioctl (/dev/liveupdate): The primary control interface for
triggering LUO state transitions (prepare, freeze, finish, cancel)
and managing the preservation/restoration of file descriptors.
Access requires CAP_SYS_ADMIN.
sysfs (/sys/kernel/liveupdate/state): A read-only interface for
monitoring the current LUO state. This allows userspace services to
track progress and coordinate actions.
Selftests: Includes kernel-side hooks and userspace selftests to
verify core LUO functionality, particularly subsystem registration and
basic state transitions.
LUO State Machine and Events:
NORMAL: Default operational state.
PREPARED: Initial preparation complete after LIVEUPDATE_PREPARE
event. Subsystems have saved initial state.
FROZEN: Final "blackout window" state after LIVEUPDATE_FREEZE
event, just before kexec. Workloads must be suspended.
UPDATED: Next kernel has booted via live update. Awaiting restoration
and LIVEUPDATE_FINISH.
Events:
LIVEUPDATE_PREPARE: Prepare for reboot, serialize state.
LIVEUPDATE_FREEZE: Final opportunity to save state before kexec.
LIVEUPDATE_FINISH: Post-reboot cleanup in the next kernel.
LIVEUPDATE_CANCEL: Abort prepare or freeze, revert changes.
v2: https://lore.kernel.org/all/20250723144649.1696299-1-pasha.tatashin@soleen.com
v1: https://lore.kernel.org/all/20250625231838.1897085-1-pasha.tatashin@soleen.com
RFC v2: https://lore.kernel.org/all/20250515182322.117840-1-pasha.tatashin@soleen.com
RFC v1: https://lore.kernel.org/all/20250320024011.2995837-1-pasha.tatashin@soleen.com
Changyuan Lyu (1):
kho: add interfaces to unpreserve folios and physical memory ranges
Mike Rapoport (Microsoft) (1):
kho: drop notifiers
Pasha Tatashin (23):
kho: init new_physxa->phys_bits to fix lockdep
kho: mm: Don't allow deferred struct page with KHO
kho: warn if KHO is disabled due to an error
kho: allow to drive kho from within kernel
kho: make debugfs interface optional
kho: don't unpreserve memory during abort
liveupdate: kho: move to kernel/liveupdate
liveupdate: luo_core: luo_ioctl: Live Update Orchestrator
liveupdate: luo_core: integrate with KHO
liveupdate: luo_subsystems: add subsystem registration
liveupdate: luo_subsystems: implement subsystem callbacks
liveupdate: luo_files: add infrastructure for FDs
liveupdate: luo_files: implement file systems callbacks
liveupdate: luo_ioctl: add userpsace interface
liveupdate: luo_files: luo_ioctl: Unregister all FDs on device close
liveupdate: luo_files: luo_ioctl: Add ioctls for per-file state
management
liveupdate: luo_sysfs: add sysfs state monitoring
reboot: call liveupdate_reboot() before kexec
kho: move kho debugfs directory to liveupdate
liveupdate: add selftests for subsystems un/registration
selftests/liveupdate: add subsystem/state tests
docs: add luo documentation
MAINTAINERS: add liveupdate entry
Pratyush Yadav (5):
mm: shmem: use SHMEM_F_* flags instead of VM_* flags
mm: shmem: allow freezing inode mapping
mm: shmem: export some functions to internal.h
luo: allow preserving memfd
docs: add documentation for memfd preservation via LUO
.../ABI/testing/sysfs-kernel-liveupdate | 51 +
Documentation/admin-guide/index.rst | 1 +
Documentation/admin-guide/liveupdate.rst | 16 +
Documentation/core-api/index.rst | 1 +
Documentation/core-api/kho/concepts.rst | 2 +-
Documentation/core-api/liveupdate.rst | 57 +
Documentation/mm/index.rst | 1 +
Documentation/mm/memfd_preservation.rst | 138 +++
Documentation/userspace-api/index.rst | 1 +
.../userspace-api/ioctl/ioctl-number.rst | 2 +
Documentation/userspace-api/liveupdate.rst | 25 +
MAINTAINERS | 19 +-
include/linux/kexec_handover.h | 53 +-
include/linux/liveupdate.h | 203 ++++
include/linux/shmem_fs.h | 23 +
include/uapi/linux/liveupdate.h | 399 +++++++
init/Kconfig | 2 +
kernel/Kconfig.kexec | 14 -
kernel/Makefile | 2 +-
kernel/liveupdate/Kconfig | 90 ++
kernel/liveupdate/Makefile | 17 +
kernel/{ => liveupdate}/kexec_handover.c | 554 ++++-----
kernel/liveupdate/kexec_handover_debug.c | 222 ++++
kernel/liveupdate/kexec_handover_internal.h | 45 +
kernel/liveupdate/luo_core.c | 517 +++++++++
kernel/liveupdate/luo_files.c | 1033 +++++++++++++++++
kernel/liveupdate/luo_internal.h | 60 +
kernel/liveupdate/luo_ioctl.c | 297 +++++
kernel/liveupdate/luo_selftests.c | 345 ++++++
kernel/liveupdate/luo_selftests.h | 84 ++
kernel/liveupdate/luo_subsystems.c | 452 ++++++++
kernel/liveupdate/luo_sysfs.c | 92 ++
kernel/reboot.c | 4 +
mm/Makefile | 1 +
mm/internal.h | 6 +
mm/memblock.c | 56 +-
mm/memfd_luo.c | 507 ++++++++
mm/shmem.c | 52 +-
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/liveupdate/.gitignore | 1 +
tools/testing/selftests/liveupdate/Makefile | 7 +
tools/testing/selftests/liveupdate/config | 6 +
.../testing/selftests/liveupdate/liveupdate.c | 406 +++++++
43 files changed, 5448 insertions(+), 417 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-kernel-liveupdate
create mode 100644 Documentation/admin-guide/liveupdate.rst
create mode 100644 Documentation/core-api/liveupdate.rst
create mode 100644 Documentation/mm/memfd_preservation.rst
create mode 100644 Documentation/userspace-api/liveupdate.rst
create mode 100644 include/linux/liveupdate.h
create mode 100644 include/uapi/linux/liveupdate.h
create mode 100644 kernel/liveupdate/Kconfig
create mode 100644 kernel/liveupdate/Makefile
rename kernel/{ => liveupdate}/kexec_handover.c (74%)
create mode 100644 kernel/liveupdate/kexec_handover_debug.c
create mode 100644 kernel/liveupdate/kexec_handover_internal.h
create mode 100644 kernel/liveupdate/luo_core.c
create mode 100644 kernel/liveupdate/luo_files.c
create mode 100644 kernel/liveupdate/luo_internal.h
create mode 100644 kernel/liveupdate/luo_ioctl.c
create mode 100644 kernel/liveupdate/luo_selftests.c
create mode 100644 kernel/liveupdate/luo_selftests.h
create mode 100644 kernel/liveupdate/luo_subsystems.c
create mode 100644 kernel/liveupdate/luo_sysfs.c
create mode 100644 mm/memfd_luo.c
create mode 100644 tools/testing/selftests/liveupdate/.gitignore
create mode 100644 tools/testing/selftests/liveupdate/Makefile
create mode 100644 tools/testing/selftests/liveupdate/config
create mode 100644 tools/testing/selftests/liveupdate/liveupdate.c
--
2.50.1.565.gc32cd1483b-goog
Lockdep shows the following warning:
INFO: trying to register non-static key.
The code is fine but needs lockdep annotation, or maybe
you didn't initialize this object before use?
turning off the locking correctness validator.
[<ffffffff810133a6>] dump_stack_lvl+0x66/0xa0
[<ffffffff8136012c>] assign_lock_key+0x10c/0x120
[<ffffffff81358bb4>] register_lock_class+0xf4/0x2f0
[<ffffffff813597ff>] __lock_acquire+0x7f/0x2c40
[<ffffffff81360cb0>] ? __pfx_hlock_conflict+0x10/0x10
[<ffffffff811707be>] ? native_flush_tlb_global+0x8e/0xa0
[<ffffffff8117096e>] ? __flush_tlb_all+0x4e/0xa0
[<ffffffff81172fc2>] ? __kernel_map_pages+0x112/0x140
[<ffffffff813ec327>] ? xa_load_or_alloc+0x67/0xe0
[<ffffffff81359556>] lock_acquire+0xe6/0x280
[<ffffffff813ec327>] ? xa_load_or_alloc+0x67/0xe0
[<ffffffff8100b9e0>] _raw_spin_lock+0x30/0x40
[<ffffffff813ec327>] ? xa_load_or_alloc+0x67/0xe0
[<ffffffff813ec327>] xa_load_or_alloc+0x67/0xe0
[<ffffffff813eb4c0>] kho_preserve_folio+0x90/0x100
[<ffffffff813ebb7f>] __kho_finalize+0xcf/0x400
[<ffffffff813ebef4>] kho_finalize+0x34/0x70
This is becase xa has its own lock, that is not initialized in
xa_load_or_alloc.
Modifiy __kho_preserve_order(), to properly call
xa_init(&new_physxa->phys_bits);
Fixes: fc33e4b44b27 ("kexec: enable KHO support for memory preservation")
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
kernel/kexec_handover.c | 29 +++++++++++++++++++++++++----
1 file changed, 25 insertions(+), 4 deletions(-)
KHO uses struct pages for the preserved memory early in boot, however,
with deferred struct page initialization, only a small portion of
memory has properly initialized struct pages.
This problem was detected where vmemmap is poisoned, and illegal flag
combinations are detected.
Don't allow them to be enabled together, and later we will have to
teach KHO to work properly with deferred struct page init kernel
feature.
Fixes: 990a950fe8fd ("kexec: add config option for KHO")
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
kernel/Kconfig.kexec | 1 +
1 file changed, 1 insertion(+)
During boot scratch area is allocated based on command line
parameters or auto calculated. However, scratch area may fail
to allocate, and in that case KHO is disabled. Currently,
no warning is printed that KHO is disabled, which makes it
confusing for the end user to figure out why KHO is not
available. Add the missing warning message.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
kernel/kexec_handover.c | 1 +
1 file changed, 1 insertion(+)
Allow to do finalize and abort from kernel modules, so LUO could
drive the KHO sequence via its own state machine.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
include/linux/kexec_handover.h | 15 +++++++++
kernel/kexec_handover.c | 56 ++++++++++++++++++++++++++++++++--
2 files changed, 69 insertions(+), 2 deletions(-)
Currently, KHO is controlled via debugfs interface, but once LUO is
introduced, it can control KHO, and the debug interface becomes
optional.
Add a separate config CONFIG_KEXEC_HANDOVER_DEBUG that enables
the debugfs interface, and allows to inspect the tree.
Move all debugfs related code to a new file to keep the .c files
clear of ifdefs.
Co-developed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
MAINTAINERS | 3 +-
kernel/Kconfig.kexec | 10 ++
kernel/Makefile | 1 +
kernel/kexec_handover.c | 278 ++++---------------------------
kernel/kexec_handover_debug.c | 218 ++++++++++++++++++++++++
kernel/kexec_handover_internal.h | 44 +++++
6 files changed, 311 insertions(+), 243 deletions(-)
create mode 100644 kernel/kexec_handover_debug.c
create mode 100644 kernel/kexec_handover_internal.h
@@ -734,29 +698,6 @@ int kho_preserve_phys(phys_addr_t phys, size_t size)}EXPORT_SYMBOL_GPL(kho_preserve_phys);-/* Handling for debug/kho/out */--staticstructdentry*debugfs_root;--staticintkho_out_update_debugfs_fdt(void)-{-interr=0;-structfdt_debugfs*ff,*tmp;--if(kho_out.finalized){-err=kho_debugfs_fdt_add(&kho_out.ser.fdt_list,kho_out.dir,-"fdt",page_to_virt(kho_out.ser.fdt));-}else{-list_for_each_entry_safe(ff,tmp,&kho_out.ser.fdt_list,list){-debugfs_remove(ff->file);-list_del(&ff->list);-kfree(ff);-}-}--returnerr;-}-staticint__kho_abort(void){interr;
@@ -809,7 +750,8 @@ int kho_abort(void)gotounlock;kho_out.finalized=false;-ret=kho_out_update_debugfs_fdt();++kho_debugfs_cleanup(&kho_out.dbg);unlock:mutex_unlock(&kho_out.lock);
@@ -859,7 +801,7 @@ static int __kho_finalize(void)abort:if(err){pr_err("Failed to convert KHO state tree: %d\n",err);-kho_abort();+__kho_abort();}returnerr;
@@ -884,119 +826,32 @@ int kho_finalize(void)gotounlock;kho_out.finalized=true;-ret=kho_out_update_debugfs_fdt();+ret=kho_debugfs_fdt_add(&kho_out.dbg,"fdt",+page_to_virt(kho_out.ser.fdt),true);unlock:mutex_unlock(&kho_out.lock);returnret;}-staticintkho_out_finalize_get(void*data,u64*val)+boolkho_finalized(void){-mutex_lock(&kho_out.lock);-*val=kho_out.finalized;-mutex_unlock(&kho_out.lock);--return0;-}--staticintkho_out_finalize_set(void*data,u64_val)-{-intret=0;-boolval=!!_val;+boolret;mutex_lock(&kho_out.lock);--if(val==kho_out.finalized){-if(kho_out.finalized)-ret=-EEXIST;-else-ret=-ENOENT;-gotounlock;-}--if(val)-ret=kho_finalize();-else-ret=kho_abort();--if(ret)-gotounlock;--kho_out.finalized=val;-ret=kho_out_update_debugfs_fdt();--unlock:+ret=kho_out.finalized;mutex_unlock(&kho_out.lock);-returnret;-}--DEFINE_DEBUGFS_ATTRIBUTE(fops_kho_out_finalize,kho_out_finalize_get,-kho_out_finalize_set,"%llu\n");--staticintscratch_phys_show(structseq_file*m,void*v)-{-for(inti=0;i<kho_scratch_cnt;i++)-seq_printf(m,"0x%llx\n",kho_scratch[i].addr);--return0;-}-DEFINE_SHOW_ATTRIBUTE(scratch_phys);--staticintscratch_len_show(structseq_file*m,void*v)-{-for(inti=0;i<kho_scratch_cnt;i++)-seq_printf(m,"0x%llx\n",kho_scratch[i].size);--return0;-}-DEFINE_SHOW_ATTRIBUTE(scratch_len);--static__initintkho_out_debugfs_init(void)-{-structdentry*dir,*f,*sub_fdt_dir;--dir=debugfs_create_dir("out",debugfs_root);-if(IS_ERR(dir))-return-ENOMEM;--sub_fdt_dir=debugfs_create_dir("sub_fdts",dir);-if(IS_ERR(sub_fdt_dir))-gotoerr_rmdir;-f=debugfs_create_file("scratch_phys",0400,dir,NULL,-&scratch_phys_fops);-if(IS_ERR(f))-gotoerr_rmdir;--f=debugfs_create_file("scratch_len",0400,dir,NULL,-&scratch_len_fops);-if(IS_ERR(f))-gotoerr_rmdir;--f=debugfs_create_file("finalize",0600,dir,NULL,-&fops_kho_out_finalize);-if(IS_ERR(f))-gotoerr_rmdir;--kho_out.dir=dir;-kho_out.ser.sub_fdt_dir=sub_fdt_dir;-return0;--err_rmdir:-debugfs_remove_recursive(dir);-return-ENOENT;+returnret;}structkho_in{-structdentry*dir;phys_addr_tfdt_phys;phys_addr_tscratch_phys;-structlist_headfdt_list;+structkho_debugfsdbg;};staticstructkho_inkho_in={-.fdt_list=LIST_HEAD_INIT(kho_in.fdt_list),};staticconstvoid*kho_get_fdt(void)
@@ -1040,56 +895,6 @@ int kho_retrieve_subtree(const char *name, phys_addr_t *phys)}EXPORT_SYMBOL_GPL(kho_retrieve_subtree);-/* Handling for debugfs/kho/in */--static__initintkho_in_debugfs_init(constvoid*fdt)-{-structdentry*sub_fdt_dir;-interr,child;--kho_in.dir=debugfs_create_dir("in",debugfs_root);-if(IS_ERR(kho_in.dir))-returnPTR_ERR(kho_in.dir);--sub_fdt_dir=debugfs_create_dir("sub_fdts",kho_in.dir);-if(IS_ERR(sub_fdt_dir)){-err=PTR_ERR(sub_fdt_dir);-gotoerr_rmdir;-}--err=kho_debugfs_fdt_add(&kho_in.fdt_list,kho_in.dir,"fdt",fdt);-if(err)-gotoerr_rmdir;--fdt_for_each_subnode(child,fdt,0){-intlen=0;-constchar*name=fdt_get_name(fdt,child,NULL);-constu64*fdt_phys;--fdt_phys=fdt_getprop(fdt,child,"fdt",&len);-if(!fdt_phys)-continue;-if(len!=sizeof(*fdt_phys)){-pr_warn("node `%s`'s prop `fdt` has invalid length: %d\n",-name,len);-continue;-}-err=kho_debugfs_fdt_add(&kho_in.fdt_list,sub_fdt_dir,name,-phys_to_virt(*fdt_phys));-if(err){-pr_warn("failed to add fdt `%s` to debugfs: %d\n",name,-err);-continue;-}-}--return0;--err_rmdir:-debugfs_remove_recursive(kho_in.dir);-returnerr;-}-static__initintkho_init(void){interr=0;
@@ -1104,27 +909,16 @@ static __init int kho_init(void)gotoerr_free_scratch;}-debugfs_root=debugfs_create_dir("kho",NULL);-if(IS_ERR(debugfs_root)){-err=-ENOENT;+err=kho_debugfs_init();+if(err)gotoerr_free_fdt;-}-err=kho_out_debugfs_init();+err=kho_out_debugfs_init(&kho_out.dbg);if(err)gotoerr_free_fdt;if(fdt){-err=kho_in_debugfs_init(fdt);-/*-*Failuretocreate/sys/kernel/debug/kho/indoesnotprevent-*revivingstatefromKHOandsettingupKHOforthenext-*kexec.-*/-if(err)-pr_err("failed exposing handover FDT in debugfs: %d\n",-err);-+kho_in_debugfs_init(&kho_in.dbg,fdt);return0;}
From: "Mike Rapoport (Microsoft)" <rppt@kernel.org>
The KHO framework uses a notifier chain as the mechanism for clients to
participate in the finalization process. While this works for a single,
central state machine, it is too restrictive for kernel-internal
components like pstore/reserve_mem or IMA. These components need a
simpler, direct way to register their state for preservation (e.g.,
during their initcall) without being part of a complex,
shutdown-time notifier sequence. The notifier model forces all
participants into a single finalization flow and makes direct
preservation from an arbitrary context difficult.
This patch refactors the client participation model by removing the
notifier chain and introducing a direct API for managing FDT subtrees.
The core kho_finalize() and kho_abort() state machine remains, but
clients now register their data with KHO beforehand.
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
include/linux/kexec_handover.h | 28 +----
kernel/kexec_handover.c | 177 +++++++++++++++++--------------
kernel/kexec_handover_debug.c | 17 +--
kernel/kexec_handover_internal.h | 5 +-
mm/memblock.c | 56 ++--------
5 files changed, 124 insertions(+), 159 deletions(-)
@@ -2555,6 +2522,7 @@ static int __init prepare_kho_fdt(void)for(i=0;i<reserved_mem_count;i++){structreserve_mem_table*map=&reserved_mem_table[i];+err|=kho_preserve_phys(map->start,map->size);err|=fdt_begin_node(fdt,map->name);err|=fdt_property_string(fdt,"compatible",RESERVE_MEM_KHO_NODE_COMPATIBLE);err|=fdt_property(fdt,"start",&map->start,sizeof(map->start));
@@ -2562,13 +2530,14 @@ static int __init prepare_kho_fdt(void)err|=fdt_end_node(fdt);}err|=fdt_end_node(fdt);-err|=fdt_finish(fdt);+err|=kho_preserve_folio(page_folio(fdt_page));+err|=kho_add_subtree(MEMBLOCK_KHO_FDT,fdt);+if(err){pr_err("failed to prepare memblock FDT for KHO: %d\n",err);-put_page(kho_fdt);-kho_fdt=NULL;+put_page(fdt_page);}returnerr;
@@ -2584,13 +2553,6 @@ static int __init reserve_mem_init(void)err=prepare_kho_fdt();if(err)returnerr;--err=register_kho_notifier(&reserve_mem_kho_nb);-if(err){-put_page(kho_fdt);-kho_fdt=NULL;-}-returnerr;}late_initcall(reserve_mem_init);
KHO allows clients to preserve memory regions at any point before the
KHO state is finalized. The finalization process itself involves KHO
performing its own actions, such as serializing the overall
preserved memory map.
If this finalization process is aborted, the current implementation
destroys KHO's internal memory tracking structures
(`kho_out.ser.track.orders`). This behavior effectively unpreserves
all memory from KHO's perspective, regardless of whether those
preservations were made by clients before the finalization attempt
or by KHO itself during finalization.
This premature unpreservation is incorrect. An abort of the
finalization process should only undo actions taken by KHO as part of
that specific finalization attempt. Individual memory regions
preserved by clients prior to finalization should remain preserved,
as their lifecycle is managed by the clients themselves. These
clients might still need to call kho_unpreserve_folio() or
kho_unpreserve_phys() based on their own logic, even after a KHO
finalization attempt is aborted.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/kexec_handover.c | 21 +--------------------
1 file changed, 1 insertion(+), 20 deletions(-)
@@ -70,5 +70,5 @@ in the FDT. That state is called the KHO finalization phase. Public API ==========-..kernel-doc:: kernel/kexec_handover.c+..kernel-doc:: kernel/liveupdate/kexec_handover.c:export:
@@ -0,0 +1,7 @@+# SPDX-License-Identifier: GPL-2.0+#+# Makefile for the linux kernel.+#++obj-$(CONFIG_KEXEC_HANDOVER)+=kexec_handover.o+obj-$(CONFIG_KEXEC_HANDOVER_DEBUG)+=kexec_handover_debug.o
diff --git a/kernel/kexec_handover.c b/kernel/liveupdate/kexec_handover.csimilarity index 99%rename from kernel/kexec_handover.crename to kernel/liveupdate/kexec_handover.cindex 07755184f44b..05f5694ea057 100644--- a/kernel/kexec_handover.c+++ b/kernel/liveupdate/kexec_handover.c
@@ -824,7 +824,7 @@ static int __kho_finalize(void)err|=fdt_finish_reservemap(root);err|=fdt_begin_node(root,"");err|=fdt_property_string(root,"compatible",KHO_FDT_COMPATIBLE);-/**+/**Reservethepreserved-memory-mappropertyintherootFDT,so*thatallpropertydefinitionswillprecedesubnodescreatedby*KHOcallers.
diff --git a/kernel/kexec_handover_debug.c b/kernel/liveupdate/kexec_handover_debug.csimilarity index 100%rename from kernel/kexec_handover_debug.crename to kernel/liveupdate/kexec_handover_debug.cdiff --git a/kernel/kexec_handover_internal.h b/kernel/liveupdate/kexec_handover_internal.hsimilarity index 100%rename from kernel/kexec_handover_internal.hrename to kernel/liveupdate/kexec_handover_internal.h
--
2.50.1.565.gc32cd1483b-goog
Introduce LUO, a mechanism intended to facilitate kernel updates while
keeping designated devices operational across the transition (e.g., via
kexec). The primary use case is updating hypervisors with minimal
disruption to running virtual machines. For userspace side of hypervisor
update we have copyless migration. LUO is for updating the kernel.
This initial patch lays the groundwork for the LUO subsystem.
Further functionality, including the implementation of state transition
logic, integration with KHO, and hooks for subsystems and file
descriptors, will be added in subsequent patches.
Create a character device at /dev/liveupdate.
A new uAPI header, <uapi/linux/liveupdate.h>, will define the necessary
structures. The magic number for IOCTL is registered in
Documentation/userspace-api/ioctl/ioctl-number.rst.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
.../userspace-api/ioctl/ioctl-number.rst | 2 +
include/linux/liveupdate.h | 64 ++++
include/uapi/linux/liveupdate.h | 94 ++++++
kernel/liveupdate/Kconfig | 27 ++
kernel/liveupdate/Makefile | 6 +
kernel/liveupdate/luo_core.c | 297 ++++++++++++++++++
kernel/liveupdate/luo_internal.h | 21 ++
kernel/liveupdate/luo_ioctl.c | 48 +++
8 files changed, 559 insertions(+)
create mode 100644 include/linux/liveupdate.h
create mode 100644 include/uapi/linux/liveupdate.h
create mode 100644 kernel/liveupdate/luo_core.c
create mode 100644 kernel/liveupdate/luo_internal.h
create mode 100644 kernel/liveupdate/luo_ioctl.c
@@ -3,5 +3,11 @@# Makefile for the linux kernel.#+luo-y:=\+luo_core.o\+luo_ioctl.o+obj-$(CONFIG_KEXEC_HANDOVER)+=kexec_handover.oobj-$(CONFIG_KEXEC_HANDOVER_DEBUG)+=kexec_handover_debug.o++obj-$(CONFIG_LIVEUPDATE)+=luo.o
@@ -0,0 +1,297 @@+// SPDX-License-Identifier: GPL-2.0++/*+*Copyright(c)2025,GoogleLLC.+*PashaTatashin<pasha.tatashin@soleen.com>+*/++/**+*DOC:LiveUpdateOrchestrator(LUO)+*+*LiveUpdateisaspecialized,kexec-basedrebootprocessthatallowsa+*runningkerneltobeupdatedfromoneversiontoanotherwhilepreserving+*thestateofselectedresourcesandkeepingdesignatedhardwaredevices+*operational.Forthesedevices,DMAactivitymaycontinuethroughoutthe+*kerneltransition.+*+*Whiletheprimaryusecasedrivingthisworkissupportingliveupdatesof+*theLinuxkernelwhenitisusedasahypervisorincloudenvironments,the+*LUOframeworkitselfisdesignedtobeworkload-agnostic.MuchlikeKernel+*LivePatching,whichappliessecurityfixesregardlessoftheworkload,+*LiveUpdatefacilitatesafullkernelversionupgradeforanytypeofsystem.+*+*Forexample,anon-hypervisorsystemrunninganin-memorycachelike+*memcachedwithmanygigabytesofdatacanuseLUO.Theuserspaceservice+*canplaceitscacheintoamemfd,haveitsstatepreservedbyLUO,and+*restoreitimmediatelyafterthekernelkexec.+*+*Whetherthesystemisrunningvirtualmachines,containers,a+*high-performancedatabase,ornetworkingservices,LUO'sprimarygoalisto+*enableafullkernelupdatebypreservingcriticaluserspacestateand+*keepingessentialdevicesoperational.+*+*ThecoreofLUOisastatemachinethattrackstheprogressofaliveupdate,+*alongwithacallbackAPIthatallowsotherkernelsubsystemstoparticipate+*intheprocess.ExamplesubsystemsthatcanhookintoLUOinclude:kvm,+*iommu,interrupts,vfio,participatingfilesystems,andmemorymanagement.+*+*LUOusesKexecHandovertotransfermemorystatefromthecurrentkernelto+*thenextkernel.Formoredetailssee+*Documentation/core-api/kho/concepts.rst.+*+*TheLUOstatemachineensuresthatoperationsareperformedinthecorrect+*sequenceandprovidesamechanismtotrackandrecoverfrompotential+*failures.+*/++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt++#include<linux/err.h>+#include<linux/kobject.h>+#include<linux/liveupdate.h>+#include<linux/rwsem.h>+#include<linux/string.h>+#include"luo_internal.h"++staticDECLARE_RWSEM(luo_state_rwsem);++staticenumliveupdate_stateluo_state=LIVEUPDATE_STATE_UNDEFINED;++staticconstchar*constluo_state_str[]={+[LIVEUPDATE_STATE_UNDEFINED]="undefined",+[LIVEUPDATE_STATE_NORMAL]="normal",+[LIVEUPDATE_STATE_PREPARED]="prepared",+[LIVEUPDATE_STATE_FROZEN]="frozen",+[LIVEUPDATE_STATE_UPDATED]="updated",+};++staticboolluo_enabled;++staticint__initearly_liveupdate_param(char*buf)+{+returnkstrtobool(buf,&luo_enabled);+}+early_param("liveupdate",early_liveupdate_param);++/* Return true if the current state is equal to the provided state */+staticinlineboolis_current_luo_state(enumliveupdate_stateexpected_state)+{+returnliveupdate_get_state()==expected_state;+}++staticvoid__luo_set_state(enumliveupdate_statestate)+{+WRITE_ONCE(luo_state,state);+}++staticinlinevoidluo_set_state(enumliveupdate_statestate)+{+pr_info("Switched from [%s] to [%s] state\n",+luo_current_state_str(),luo_state_str[state]);+__luo_set_state(state);+}++staticintluo_do_freeze_calls(void)+{+return0;+}++staticvoidluo_do_finish_calls(void)+{+}++/* Get the current state as a string */+constchar*luo_current_state_str(void)+{+returnluo_state_str[liveupdate_get_state()];+}++enumliveupdate_stateliveupdate_get_state(void)+{+returnREAD_ONCE(luo_state);+}++intluo_prepare(void)+{+return0;+}++/**+*luo_freeze()-Initiatethefinalfreezenotificationphaseforliveupdate.+*+*Attemptstotransitiontheliveupdateorchestratorstatefrom+*%LIVEUPDATE_STATE_PREPAREDto%LIVEUPDATE_STATE_FROZEN.Thisfunctionis+*typicallycalledjustbeforetheactualrebootsystemcall(e.g.,kexec)+*isinvoked,eitherdirectlybytheorchestrationtoolorpotentiallyfrom+*withintherebootsyscallpathitself.+*+*@return0:Success.Negativeerrorotherwise.Stateisrevertedto+*%LIVEUPDATE_STATE_NORMALincaseofanerrorduringcallbacks,andeverything+*iscanceledviacancelnotifcation.+*/+intluo_freeze(void)+{+intret;++if(down_write_killable(&luo_state_rwsem)){+pr_warn("[freeze] event canceled by user\n");+return-EAGAIN;+}++if(!is_current_luo_state(LIVEUPDATE_STATE_PREPARED)){+pr_warn("Can't switch to [%s] from [%s] state\n",+luo_state_str[LIVEUPDATE_STATE_FROZEN],+luo_current_state_str());+up_write(&luo_state_rwsem);++return-EINVAL;+}++ret=luo_do_freeze_calls();+if(!ret)+luo_set_state(LIVEUPDATE_STATE_FROZEN);+else+luo_set_state(LIVEUPDATE_STATE_NORMAL);++up_write(&luo_state_rwsem);++returnret;+}++/**+*luo_finish-Finalizetheliveupdateprocessinthenewkernel.+*+*Thisfunctioniscalledafterasuccessfulliveupdaterebootintoanew+*kernel,oncethenewkernelisreadytotransitiontothenormaloperational+*state.Itsignalsthecompletionoftheliveupdatesequencetosubsystems.+*+*@return0onsuccess,``-EAGAIN``ifthestatechangewascancelledbythe+*userwhilewaitingforthelock,or``-EINVAL``iftheorchestratorisnotin+*theupdatedstate.+*/+intluo_finish(void)+{+if(down_write_killable(&luo_state_rwsem)){+pr_warn("[finish] event canceled by user\n");+return-EAGAIN;+}++if(!is_current_luo_state(LIVEUPDATE_STATE_UPDATED)){+pr_warn("Can't switch to [%s] from [%s] state\n",+luo_state_str[LIVEUPDATE_STATE_NORMAL],+luo_current_state_str());+up_write(&luo_state_rwsem);++return-EINVAL;+}++luo_do_finish_calls();+luo_set_state(LIVEUPDATE_STATE_NORMAL);++up_write(&luo_state_rwsem);++return0;+}++intluo_cancel(void)+{+return0;+}++voidluo_state_read_enter(void)+{+down_read(&luo_state_rwsem);+}++voidluo_state_read_exit(void)+{+up_read(&luo_state_rwsem);+}++staticint__initluo_startup(void)+{+__luo_set_state(LIVEUPDATE_STATE_NORMAL);++return0;+}+early_initcall(luo_startup);++/* Public Functions */++/**+*liveupdate_reboot()-Kernelrebootnotifierforliveupdatefinal+*serialization.+*+*Thisfunctionisinvokeddirectlyfromthereboot()syscallpathwayifa+*rebootisinitiatedwhiletheliveupdatestateis%LIVEUPDATE_STATE_PREPARED+*(i.e.,iftheuserdidnotexplicitlytriggerthefrozenstate).Ithandles+*theimplicittransitionintothefinalfrozenstate.+*+*Ittriggersthe%LIVEUPDATE_REBOOTeventcallbacksforparticipating+*subsystems.Thesecallbacksmustperformfinalstatesavingveryquicklyas+*theyexecuteduringtheblackoutperiodjustbeforekexec.+*+*Ifany%LIVEUPDATE_FREEZEcallbackfails,thisfunctiontriggersthe+*%LIVEUPDATE_CANCELeventforallparticipantstoreverttheirstate,aborts+*theliveupdate,andreturnsanerror.+*/+intliveupdate_reboot(void)+{+if(!is_current_luo_state(LIVEUPDATE_STATE_PREPARED))+return0;++returnluo_freeze();+}++/**+*liveupdate_state_updated-Checkifthesystemisintheliveupdate+*'updated'state.+*+*Thisfunctionchecksiftheliveupdateorchestratorisinthe+*``LIVEUPDATE_STATE_UPDATED``state.Thisstateindicatesthatthesystemhas+*successfullyrebootedintoanewkernelaspartofaliveupdate,andthe+*preserveddevicesareexpectedtobeintheprocessofbeingreclaimed.+*+*Thisistypicallyusedbysubsystemsduringearlybootofthenewkernel+*todetermineiftheyneedtoattempttorestorestatefromaprevious+*liveupdate.+*+*@returntrueifthesystemisinthe``LIVEUPDATE_STATE_UPDATED``state,+*falseotherwise.+*/+boolliveupdate_state_updated(void)+{+returnis_current_luo_state(LIVEUPDATE_STATE_UPDATED);+}++/**+*liveupdate_state_normal-Checkifthesystemisintheliveupdate'normal'+*state.+*+*Thisfunctionchecksiftheliveupdateorchestratorisinthe+*``LIVEUPDATE_STATE_NORMAL``state.Thisstateindicatesthatnoliveupdate+*isinprogress.Itrepresentsthedefaultoperationalstateofthesystem.+*+*Thiscanbeusedtogateactionsthatshouldonlybeperformedwhenno+*liveupdateactivityisoccurring.+*+*@returntrueifthesystemisinthe``LIVEUPDATE_STATE_NORMAL``state,+*falseotherwise.+*/+boolliveupdate_state_normal(void)+{+returnis_current_luo_state(LIVEUPDATE_STATE_NORMAL);+}++/**+*liveupdate_enabled-Checkiftheliveupdatefeatureisenabled.+*+*Thisfunctionreturnsthestateoftheliveupdatefeatureflag,which+*canbecontrolledviathe``liveupdate``kernelcommand-lineparameter.+*+*@returntrueifliveupdateisenabled,falseotherwise.+*/+boolliveupdate_enabled(void)+{+returnluo_enabled;+}
Integrate the LUO with the KHO framework to enable passing LUO state
across a kexec reboot.
When LUO is transitioned to a "prepared" state, it tells KHO to
finalize, so all memory segments that were added to KHO preservation
list are getting preserved. After "Prepared" state no new segments
can be preserved. If LUO is canceled, it also tells KHO to cancel the
serialization, and therefore, later LUO can go back into the prepared
state.
This patch introduces the following changes:
- During the KHO finalization phase allocate FDT blob.
- Populate this FDT with a LUO compatibility string ("luo-v1").
LUO now depends on `CONFIG_KEXEC_HANDOVER`. The core state transition
logic (`luo_do_*_calls`) remains unimplemented in this patch.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/liveupdate/luo_core.c | 210 ++++++++++++++++++++++++++++++-
kernel/liveupdate/luo_internal.h | 9 ++
2 files changed, 216 insertions(+), 3 deletions(-)
@@ -91,6 +109,60 @@ static inline void luo_set_state(enum liveupdate_state state)__luo_set_state(state);}+/* Called during the prepare phase, to create LUO fdt tree */+staticintluo_fdt_setup(void)+{+void*fdt_out;+intret;++fdt_out=(void*)__get_free_pages(GFP_KERNEL|__GFP_ZERO,+get_order(LUO_FDT_SIZE));+if(!fdt_out){+pr_err("failed to allocate FDT memory\n");+return-ENOMEM;+}++ret=fdt_create_empty_tree(fdt_out,LUO_FDT_SIZE);+if(ret)+gotoexit_free;++ret=fdt_setprop_string(fdt_out,0,"compatible",LUO_COMPATIBLE);+if(ret)+gotoexit_free;++ret=kho_preserve_phys(__pa(fdt_out),LUO_FDT_SIZE);+if(ret)+gotoexit_free;++ret=kho_add_subtree(LUO_KHO_ENTRY_NAME,fdt_out);+if(ret)+gotoexit_unpreserve;+luo_fdt_out=fdt_out;++return0;++exit_unpreserve:+WARN_ON_ONCE(kho_unpreserve_phys(__pa(fdt_out),LUO_FDT_SIZE));+exit_free:+free_pages((unsignedlong)fdt_out,get_order(LUO_FDT_SIZE));+pr_err("failed to prepare LUO FDT: %d\n",ret);++returnret;+}++staticvoidluo_fdt_destroy(void)+{+WARN_ON_ONCE(kho_unpreserve_phys(__pa(luo_fdt_out),LUO_FDT_SIZE));+kho_remove_subtree(luo_fdt_out);+free_pages((unsignedlong)luo_fdt_out,get_order(LUO_FDT_SIZE));+luo_fdt_out=NULL;+}++staticintluo_do_prepare_calls(void)+{+return0;+}+staticintluo_do_freeze_calls(void){return0;
@@ -100,6 +172,71 @@ static void luo_do_finish_calls(void){}+staticvoidluo_do_cancel_calls(void)+{+}++staticint__luo_prepare(void)+{+intret;++if(down_write_killable(&luo_state_rwsem)){+pr_warn("[prepare] event canceled by user\n");+return-EAGAIN;+}++if(!is_current_luo_state(LIVEUPDATE_STATE_NORMAL)){+pr_warn("Can't switch to [%s] from [%s] state\n",+luo_state_str[LIVEUPDATE_STATE_PREPARED],+luo_current_state_str());+ret=-EINVAL;+gotoexit_unlock;+}++ret=luo_fdt_setup();+if(ret)+gotoexit_unlock;++ret=luo_do_prepare_calls();+if(ret){+luo_fdt_destroy();+gotoexit_unlock;+}++luo_set_state(LIVEUPDATE_STATE_PREPARED);++exit_unlock:+up_write(&luo_state_rwsem);++returnret;+}++staticint__luo_cancel(void)+{+if(down_write_killable(&luo_state_rwsem)){+pr_warn("[cancel] event canceled by user\n");+return-EAGAIN;+}++if(!is_current_luo_state(LIVEUPDATE_STATE_PREPARED)&&+!is_current_luo_state(LIVEUPDATE_STATE_FROZEN)){+pr_warn("Can't switch to [%s] from [%s] state\n",+luo_state_str[LIVEUPDATE_STATE_NORMAL],+luo_current_state_str());+up_write(&luo_state_rwsem);++return-EINVAL;+}++luo_do_cancel_calls();+luo_fdt_destroy();+luo_set_state(LIVEUPDATE_STATE_NORMAL);++up_write(&luo_state_rwsem);++return0;+}+/* Get the current state as a string */constchar*luo_current_state_str(void){
@@ -193,9 +349,28 @@ int luo_finish(void)return0;}+/**+*luo_cancel-Canceltheongoingliveupdatefrompreparedorfrozenstates.+*+*Thisfunctioniscalledtoabortaliveupdatethatiscurrentlyinthe+*``LIVEUPDATE_STATE_PREPARED``state.+*+*Ifthestateiscorrect,ittriggersthe``LIVEUPDATE_CANCEL``notifierchain+*toallowsubsystemstoundoanyactionsperformedduringtheprepareor+*freezeevents.Finally,theorchestratorstateistransitionedbackto+*``LIVEUPDATE_STATE_NORMAL``.+*+*@return0onsuccess,or``-EAGAIN``ifthestatechangewascancelledbythe+*userwhilewaitingforthelock.+*/intluo_cancel(void){-return0;+interr=kho_abort();++if(err)+returnerr;++return__luo_cancel();}voidluo_state_read_enter(void)
@@ -210,7 +385,36 @@ void luo_state_read_exit(void)staticint__initluo_startup(void){-__luo_set_state(LIVEUPDATE_STATE_NORMAL);+phys_addr_tfdt_phys;+intret;++if(!kho_is_enabled()){+if(luo_enabled)+pr_warn("Disabling liveupdate because KHO is disabled\n");+luo_enabled=false;+return0;+}++/* Retrieve LUO subtree, and verify its format. */+ret=kho_retrieve_subtree(LUO_KHO_ENTRY_NAME,&fdt_phys);+if(ret){+if(ret!=-ENOENT){+luo_restore_fail("failed to retrieve FDT '%s' from KHO: %d\n",+LUO_KHO_ENTRY_NAME,ret);+}+__luo_set_state(LIVEUPDATE_STATE_NORMAL);++return0;+}++luo_fdt_in=__va(fdt_phys);+ret=fdt_node_check_compatible(luo_fdt_in,0,LUO_COMPATIBLE);+if(ret){+luo_restore_fail("FDT '%s' is incompatible with '%s' [%d]\n",+LUO_KHO_ENTRY_NAME,LUO_COMPATIBLE,ret);+}++__luo_set_state(LIVEUPDATE_STATE_UPDATED);return0;}
Introduce the framework for kernel subsystems (e.g., KVM, IOMMU, device
drivers) to register with LUO and participate in the live update process
via callbacks.
Subsystem Registration:
- Defines struct liveupdate_subsystem in linux/liveupdate.h,
which subsystems use to provide their name and optional callbacks
(prepare, freeze, cancel, finish). The callbacks accept
a u64 *data intended for passing state/handles.
- Exports liveupdate_register_subsystem() and
liveupdate_unregister_subsystem() API functions.
- Adds drivers/misc/liveupdate/luo_subsystems.c to manage a list
of registered subsystems.
Registration/unregistration is restricted to
specific LUO states (NORMAL/UPDATED).
Callback Framework:
- The main luo_core.c state transition functions
now delegate to new luo_do_subsystems_*_calls() functions
defined in luo_subsystems.c.
- These new functions are intended to iterate through the registered
subsystems and invoke their corresponding callbacks.
FDT Integration:
- Adds a /subsystems subnode within the main LUO FDT created in
luo_core.c. This node has its own compatibility string
(subsystems-v1).
- luo_subsystems_fdt_setup() populates this node by adding a
property for each registered subsystem, using the subsystem's
name.
Currently, these properties are initialized with a placeholder
u64 value (0).
- luo_subsystems_startup() is called from luo_core.c on boot to
find and validate the /subsystems node in the FDT received via
KHO.
- Adds a stub API function liveupdate_get_subsystem_data() intended
for subsystems to retrieve their persisted u64 data from the FDT
in the new kernel.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
include/linux/liveupdate.h | 66 +++++++
kernel/liveupdate/Makefile | 3 +-
kernel/liveupdate/luo_core.c | 19 +-
kernel/liveupdate/luo_internal.h | 7 +
kernel/liveupdate/luo_subsystems.c | 291 +++++++++++++++++++++++++++++
5 files changed, 383 insertions(+), 3 deletions(-)
create mode 100644 kernel/liveupdate/luo_subsystems.c
@@ -12,6 +12,52 @@#include<linux/list.h>#include<uapi/linux/liveupdate.h>+structliveupdate_subsystem;++/**+*structliveupdate_subsystem_ops-LUOeventscallbackfunctions+*@prepare:Optional.CalledduringLUOpreparephase.Shouldperform+*preparatoryactionsandcanstoreau64handle/state+*viathe'data'pointerforuseinlatercallbacks.+*Return0onsuccess,negativeerrorcodeonfailure.+*@freeze:Optional.CalledduringLUOfreezeevent(beforeactualjump+*tonewkernel).Shouldperformfinalstatesavingactionsand+*canupdatetheu64handle/stateviathe'data'pointer.Retur:+*0onsuccess,negativeerrorcodeonfailure.+*@cancel:Optional.Callediftheliveupdateprocessiscanceledafter+*prepare(orfreeze)wascalled.Receivestheu64data+*setbyprepare/freeze.Usedforcleanup.+*@boot:Optional.Calldurngbootpostliveupdate.Thiscallbackis+*donewhensubsystemregisterduringliveupdate.+*@finish:Optional.Calledaftertheliveupdateisfinishedinthenew+*kernel.+*Receivestheu64datasetbyprepare/freeze.Usedforcleanup.+*@owner:Modulereference+*/+structliveupdate_subsystem_ops{+int(*prepare)(structliveupdate_subsystem*handle,u64*data);+int(*freeze)(structliveupdate_subsystem*handle,u64*data);+void(*cancel)(structliveupdate_subsystem*handle,u64data);+void(*boot)(structliveupdate_subsystem*handle,u64data);+void(*finish)(structliveupdate_subsystem*handle,u64data);+structmodule*owner;+};++/**+*structliveupdate_subsystem-RepresentsasubsystemparticipatinginLUO+*@ops:Callbackfunctions+*@name:Uniquenameidentifyingthesubsystem.+*@list:ListheadusedinternallybyLUO.Shouldnotbemodifiedby+*callerafterregistration.+*@private_data:ForLUOinternaluse,cachedvalueofdatafield.+*/+structliveupdate_subsystem{+conststructliveupdate_subsystem_ops*ops;+constchar*name;+structlist_headlist;+u64private_data;+};+#ifdef CONFIG_LIVEUPDATE/* Return true if live update orchestrator is enabled */
@@ -0,0 +1,291 @@+// SPDX-License-Identifier: GPL-2.0++/*+*Copyright(c)2025,GoogleLLC.+*PashaTatashin<pasha.tatashin@soleen.com>+*/++/**+*DOC:LUOSubsystemssupport+*+*VariouskernelsubsystemsregisterwiththeLiveUpdateOrchestratorto+*participateintheliveupdateprocess.Thesesubsystemsarenotifiedat+*differentstagesoftheliveupdatesequence,allowingthemtoserialize+*devicestatebeforetherebootandrestoreitafterwards.Examplesinclude+*thedevicelayer,interruptcontrollers,KVM,IOMMU,andspecificdevice+*drivers.+*/++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt++#include<linux/err.h>+#include<linux/libfdt.h>+#include<linux/liveupdate.h>+#include<linux/module.h>+#include<linux/mutex.h>+#include<linux/string.h>+#include"luo_internal.h"++#define LUO_SUBSYSTEMS_NODE_NAME "subsystems"+#define LUO_SUBSYSTEMS_COMPATIBLE "subsystems-v1"++staticDEFINE_MUTEX(luo_subsystem_list_mutex);+staticLIST_HEAD(luo_subsystems_list);+staticvoid*luo_fdt_out;+staticvoid*luo_fdt_in;++/**+*luo_subsystems_fdt_setup-Addsandpopulatesthe'subsystems'nodeinthe+*FDT.+*@fdt:PointertotheLUOFDTblob.+*+*AddsubsystemsnodeandeachsubsystemtotheLUOFDTblob.+*+*Returns:0onsuccess,negativeerrnoonfailure.+*/+intluo_subsystems_fdt_setup(void*fdt)+{+structliveupdate_subsystem*subsystem;+constu64zero_data=0;+intret,node_offset;++guard(mutex)(&luo_subsystem_list_mutex);+ret=fdt_add_subnode(fdt,0,LUO_SUBSYSTEMS_NODE_NAME);+if(ret<0)+gotoexit_error;++node_offset=ret;+ret=fdt_setprop_string(fdt,node_offset,"compatible",+LUO_SUBSYSTEMS_COMPATIBLE);+if(ret<0)+gotoexit_error;++list_for_each_entry(subsystem,&luo_subsystems_list,list){+ret=fdt_setprop(fdt,node_offset,subsystem->name,+&zero_data,sizeof(zero_data));+if(ret<0)+gotoexit_error;+}++luo_fdt_out=fdt;+return0;+exit_error:+pr_err("Failed to setup 'subsystems' node to FDT: %s\n",+fdt_strerror(ret));+return-ENOSPC;+}++/**+*luo_subsystems_startup-ValidatestheLUOsubsystemsFDTnodeatstartup.+*@fdt:PointertotheLUOFDTblobpassedfromthepreviouskernel.+*+*This__initfunctioncheckstheexistenceandvalidityofthe'/subsystems'+*nodeintheFDT.Thisnodeisconsideredmandatory.+*/+void__initluo_subsystems_startup(void*fdt)+{+intret,node_offset;++guard(mutex)(&luo_subsystem_list_mutex);+node_offset=fdt_subnode_offset(fdt,0,LUO_SUBSYSTEMS_NODE_NAME);+if(node_offset<0)+luo_restore_fail("Failed to find /subsystems node\n");++ret=fdt_node_check_compatible(fdt,node_offset,+LUO_SUBSYSTEMS_COMPATIBLE);+if(ret){+luo_restore_fail("FDT '%s' is incompatible with '%s' [%d]\n",+LUO_SUBSYSTEMS_NODE_NAME,+LUO_SUBSYSTEMS_COMPATIBLE,ret);+}+luo_fdt_in=fdt;+}++staticintluo_get_subsystem_data(structliveupdate_subsystem*h,u64*data)+{+return0;+}++/**+*luo_do_subsystems_prepare_calls-CallspreparecallbacksandupdatesFDT+*ifallpreparessucceed.Handlescancellationonfailure.+*+*Phase1:Calls'prepare'forallsubsystemsandstoresresultstemporarily.+*Ifany'prepare'fails,calls'cancel'onpreviouslypreparedsubsystems+*andreturnstheerror.+*Phase2:Ifall'prepare'callssucceeded,writesthestoreddatatotheFDT.+*IfanyFDTwritefails,calls'cancel'on*all*preparedsubsystemsand+*returnstheFDTerror.+*+*Returns:0onsuccess.Negativeerrnoonfailure.+*/+intluo_do_subsystems_prepare_calls(void)+{+return0;+}++/**+*luo_do_subsystems_freeze_calls-CallsfreezecallbacksandupdatesFDT+*ifallfreezessucceed.Handlescancellationonfailure.+*+*Phase1:Calls'freeze'forallsubsystemsandstoresresultstemporarily.+*Ifany'freeze'fails,calls'cancel'onpreviouslycalledsubsystems+*andreturnstheerror.+*Phase2:Ifall'freeze'callssucceeded,writesthestoreddatatotheFDT.+*IfanyFDTwritefails,calls'cancel'on*all*subsystemsand+*returnstheFDTerror.+*+*Returns:0onsuccess.Negativeerrnoonfailure.+*/+intluo_do_subsystems_freeze_calls(void)+{+return0;+}++/**+*luo_do_subsystems_finish_calls-Callsfinishcallbacksforallsubsystems.+*+*Thisfunctioniscalledattheendofliveupdatecycletodothefinal+*clean-uporhousekeepingofthepost-liveupdatestates.+*/+voidluo_do_subsystems_finish_calls(void)+{+}++/**+*luo_do_subsystems_cancel_calls-Callscancelcallbacksforallsubsystems.+*+*Thisfunctionistypicallycalledwhentheliveupdateprocessneedstobe+*abortedexternally,forexample,afterthepreparephasemayhaverunbut+*beforeactualreboot.Ititeratesthroughallregisteredsubsystemsandcalls+*the'cancel'callbackforthosethatimplementitandlikelycompleted+*prepare.+*/+voidluo_do_subsystems_cancel_calls(void)+{+}++/**+*liveupdate_register_subsystem-RegisterakernelsubsystemhandlerwithLUO+*@h:Pointertotheliveupdate_subsystemstructureallocatedandpopulated+*bythecallingsubsystem.+*+*Registersasubsystemhandlerthatprovidescallbacksfordifferentevents+*oftheliveupdatecycle.Registrationistypicallydoneduringthe+*subsystem'smoduleinitorcoreinitialization.+*+*CanonlybecalledwhenLUOisintheNORMALorUPDATEDstates.+*Theprovidedname(@h->name)mustbeuniqueamongregisteredsubsystems.+*+*Return:0onsuccess,negativeerrorcodeotherwise.+*/+intliveupdate_register_subsystem(structliveupdate_subsystem*h)+{+structliveupdate_subsystem*iter;+intret=0;++luo_state_read_enter();+if(!liveupdate_state_normal()&&!liveupdate_state_updated()){+luo_state_read_exit();+return-EBUSY;+}++guard(mutex)(&luo_subsystem_list_mutex);+list_for_each_entry(iter,&luo_subsystems_list,list){+if(iter==h){+pr_warn("Subsystem '%s' (%p) already registered.\n",+h->name,h);+ret=-EEXIST;+gotoout_unlock;+}++if(!strcmp(iter->name,h->name)){+pr_err("Subsystem with name '%s' already registered.\n",+h->name);+ret=-EEXIST;+gotoout_unlock;+}+}++if(!try_module_get(h->ops->owner)){+pr_warn("Subsystem '%s' unable to get reference.\n",h->name);+ret=-EAGAIN;+gotoout_unlock;+}++INIT_LIST_HEAD(&h->list);+list_add_tail(&h->list,&luo_subsystems_list);++out_unlock:+/*+*Ifwearebootingduringliveupdate,andsubsystemprovidedaboot+*callback,doitnow,sinceweknowthatsubsystemhasalready+*initialized.+*/+if(!ret&&liveupdate_state_updated()&&h->ops->boot){+u64data;++ret=luo_get_subsystem_data(h,&data);+if(!WARN_ON_ONCE(ret))+h->ops->boot(h,data);+}++luo_state_read_exit();++returnret;+}++/**+*liveupdate_unregister_subsystem-Unregisterakernelsubsystemhandlerfrom+*LUO+*@h:Pointertothesameliveupdate_subsystemstructurethatwasusedduring+*registration.+*+*Unregistersapreviouslyregisteredsubsystemhandler.Typicallycalled+*duringmoduleexitorsubsystemteardown.LUOremovesthestructurefromits+*internallist;thecallerisresponsibleforanynecessarymemorycleanup+*ofthestructureitself.+*+*Return:0onsuccess,negativeerrorcodeotherwise.+*-EINVALifhisNULL.+*-ENOENTifthespecifiedhandler@hisnotfoundintheregistrationlist.+*-EBUSYifLUOisnotintheNORMALstate.+*/+intliveupdate_unregister_subsystem(structliveupdate_subsystem*h)+{+structliveupdate_subsystem*iter;+boolfound=false;+intret=0;++luo_state_read_enter();+if(!liveupdate_state_normal()&&!liveupdate_state_updated()){+luo_state_read_exit();+return-EBUSY;+}++guard(mutex)(&luo_subsystem_list_mutex);+list_for_each_entry(iter,&luo_subsystems_list,list){+if(iter==h){+found=true;+break;+}+}++if(found){+list_del_init(&h->list);+}else{+pr_warn("Subsystem handler '%s' not found for unregistration.\n",+h->name);+ret=-ENOENT;+}++module_put(h->ops->owner);+luo_state_read_exit();++returnret;+}++intliveupdate_get_subsystem_data(structliveupdate_subsystem*h,u64*data)+{+return0;+}
Implement the core logic within luo_subsystems.c to handle the
invocation of registered subsystem callbacks and manage the persistence
of their state via the LUO FDT. This replaces the stub implementations
from the previous patch.
This completes the core mechanism enabling subsystems to actively
participate in the LUO state machine, execute phase-specific logic, and
persist/restore a u64 state across the live update transition
using the FDT.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/liveupdate/luo_subsystems.c | 167 ++++++++++++++++++++++++++++-
1 file changed, 164 insertions(+), 3 deletions(-)
Introduce the framework within LUO to support preserving specific types
of file descriptors across a live update transition. This allows
stateful FDs (like memfds or vfio FDs used by VMs) to be recreated in
the new kernel.
Note: The core logic for iterating through the luo_files_list and
invoking the handler callbacks (prepare, freeze, cancel, finish)
within luo_do_files_*_calls, as well as managing the u64 data
persistence via the FDT for individual files, is currently implemented
as stubs in this patch. This patch sets up the registration, FDT layout,
and retrieval framework.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
include/linux/liveupdate.h | 73 ++++
kernel/liveupdate/Makefile | 1 +
kernel/liveupdate/luo_files.c | 677 +++++++++++++++++++++++++++++++
kernel/liveupdate/luo_internal.h | 4 +
4 files changed, 755 insertions(+)
create mode 100644 kernel/liveupdate/luo_files.c
@@ -0,0 +1,677 @@+// SPDX-License-Identifier: GPL-2.0++/*+*Copyright(c)2025,GoogleLLC.+*PashaTatashin<pasha.tatashin@soleen.com>+*/++/**+*DOC:LUOfiledescriptors+*+*LUOprovidestheinfrastructurenecessarytopreserve+*specifictypesofstatefulfiledescriptorsacrossakernellive+*updatetransition.Theprimarygoalistoallowworkloads,suchasvirtual+*machinesusingvfio,memfd,oriommufdtoretainaccesstotheiressential+*resourceswithoutinterruptionaftertheunderlyingkernelisupdated.+*+*Theframeworkoperatesbasedonhandlerregistrationandinstancetracking:+*+*1.HandlerRegistration:Kernelmodulesresponsibleforspecificfile+*types(e.g.,memfd,vfio)registera&structliveupdate_file_handler+*handler.Thishandlercontainscallbacks+*(&liveupdate_file_handler.ops->prepare,+*&liveupdate_file_handler.ops->freeze,+*&liveupdate_file_handler.ops->finish,etc.)andaunique'compatible'string+*identifyingthefiletype.Registrationoccursvia+*liveupdate_register_file_handler().+*+*2.FileInstanceTracking:Whenapotentiallypreservablefileneedstobe+*managedforliveupdate,thecoreLUOlogic(luo_register_file())findsa+*compatibleregisteredhandlerusingits+*&liveupdate_file_handler.ops->can_preservecallback.Iffound,aninternal+*&structluo_fileinstanceiscreated,assignedauniqueu64'token',and+*addedtoalist.+*+*3.StatePersistence(FDT):DuringtheLUOprepare/freezephases,the+*registeredhandlercallbacksareinvokedforeachtrackedfileinstance.+*Thesecallbackscangenerateau64datapayloadrepresentingtheminimal+*stateneededforrestoration.Thispayload,alongwiththehandler's+*compatiblestringandtheuniquetoken,isstoredinadedicated+*'/file-descriptors'nodewithinthemainLUOFDTblobpassedvia+*KexecHandover(KHO).+*+*4.Restoration:Inthenewkernel,theLUOframeworkparsestheincoming+*FDTtoreconstructthelistof&structluo_fileinstances.Whenthe+*originalownerrequeststhefile,luo_retrieve_file()usesthecorresponding+*handler's&liveupdate_file_handler.ops->retrievecallback,passingthe+*persistedu64data,torecreateorfindtheappropriate&structfileobject.+*/++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt++#include<linux/atomic.h>+#include<linux/err.h>+#include<linux/file.h>+#include<linux/kexec_handover.h>+#include<linux/libfdt.h>+#include<linux/liveupdate.h>+#include<linux/module.h>+#include<linux/mutex.h>+#include<linux/rwsem.h>+#include<linux/sizes.h>+#include<linux/slab.h>+#include<linux/string.h>+#include<linux/xarray.h>+#include"luo_internal.h"++#define LUO_FILES_NODE_NAME "file-descriptors"+#define LUO_FILES_COMPATIBLE "file-descriptors-v1"++staticDEFINE_XARRAY(luo_files_xa_in);+staticDEFINE_XARRAY(luo_files_xa_out);+staticboolluo_files_xa_in_recreated;++/* Registered files. */+staticDECLARE_RWSEM(luo_register_file_list_rwsem);+staticLIST_HEAD(luo_register_file_list);++staticDECLARE_RWSEM(luo_file_fdt_rwsem);+staticvoid*luo_file_fdt_out;+staticvoid*luo_file_fdt_in;++staticsize_tluo_file_fdt_out_size;++staticatomic64_tluo_files_count;++/**+*structluo_file-Representsafiledescriptorinstancepreserved+*acrossliveupdate.+*@fh:Pointertothe&structliveupdate_file_handlercontaining+*theimplementationofprepare,freeze,cancel,andfinish+*operationsspecifictothisfile'stype.+*@file:Apointertothekernel's&structfileobjectrepresenting+*theopenfiledescriptorthatisbeingpreserved.+*@private_data:Internalstorageusedbytheliveupdatecoreframework+*betweenphases.+*@reclaimed:Flagindicatingwhetherthispreservedfiledescriptorhas+*beensuccessfully'reclaimed'(e.g.,requestedviaanioctl)+*byuser-spaceortheowningkernelsubsysteminthenew+*kernelaftertheliveupdate.+*@state:Thecurrentstateoffiledescriptor,itisallowedto+*prepare,freeze,andfinishFDsbeforetheglobalstate+*switch.+*@mutex:LocktoprotectFDstate,andallowindependentlytochange+*theFDstatecomparedtoglobalstate.+*+*Thisstructureholdsthenecessarycallbacksandcontextformanaginga+*specificopenfiledescriptorthroughoutthedifferentphasesofalive+*updateprocess.Instancesofthisstructurearetypicallyallocated,+*populatedwithfile-specificdetails(&file,&arg,callbacks,compatibility+*string,token),andlinkedintoacentrallistmanagedbytheLUO.The+*private_datafieldisusedinternallybythecorelogictostorestate+*betweenphases.+*/+structluo_file{+structliveupdate_file_handler*fh;+structfile*file;+u64private_data;+boolreclaimed;+enumliveupdate_statestate;+structmutexmutex;+};++staticvoidluo_files_recreate_luo_files_xa_in(void)+{+constchar*node_name,*fdt_compat_str;+structliveupdate_file_handler*fh;+structluo_file*luo_file;+constvoid*data_ptr;+intfile_node_offset;+intret=0;++guard(rwsem_read)(&luo_file_fdt_rwsem);+if(luo_files_xa_in_recreated||!luo_file_fdt_in)+return;++/* Take write in order to guarantee that we re-create list once */+guard(rwsem_write)(&luo_register_file_list_rwsem);+if(luo_files_xa_in_recreated)+return;++fdt_for_each_subnode(file_node_offset,luo_file_fdt_in,0){+boolhandler_found=false;+u64token;++node_name=fdt_get_name(luo_file_fdt_in,file_node_offset,+NULL);+if(!node_name){+luo_restore_fail("FDT subnode at offset %d: Cannot get name\n",+file_node_offset);+}++ret=kstrtou64(node_name,0,&token);+if(ret<0){+luo_restore_fail("FDT node '%s': Failed to parse token\n",+node_name);+}++if(xa_load(&luo_files_xa_in,token)){+luo_restore_fail("Duplicate token %llu found in incoming FDT for file descriptors.\n",+token);+}++fdt_compat_str=fdt_getprop(luo_file_fdt_in,file_node_offset,+"compatible",NULL);+if(!fdt_compat_str){+luo_restore_fail("FDT node '%s': Missing 'compatible' property\n",+node_name);+}++data_ptr=fdt_getprop(luo_file_fdt_in,file_node_offset,"data",+NULL);+if(!data_ptr){+luo_restore_fail("Can't recover property 'data' for FDT node '%s'\n",+node_name);+}++list_for_each_entry(fh,&luo_register_file_list,list){+if(!strcmp(fh->compatible,fdt_compat_str)){+handler_found=true;+break;+}+}++if(!handler_found){+luo_restore_fail("FDT node '%s': No registered handler for compatible '%s'\n",+node_name,fdt_compat_str);+}++luo_file=kmalloc(sizeof(*luo_file),+GFP_KERNEL|__GFP_NOFAIL);+luo_file->fh=fh;+luo_file->file=NULL;+memcpy(&luo_file->private_data,data_ptr,sizeof(u64));+luo_file->reclaimed=false;+mutex_init(&luo_file->mutex);+luo_file->state=LIVEUPDATE_STATE_UPDATED;+ret=xa_err(xa_store(&luo_files_xa_in,token,luo_file,+GFP_KERNEL|__GFP_NOFAIL));+if(ret<0){+luo_restore_fail("Failed to store luo_file for token %llu in XArray: %d\n",+token,ret);+}+}+luo_files_xa_in_recreated=true;+}++staticsize_tluo_files_fdt_size(void)+{+u64num_files=atomic64_read(&luo_files_count);++/* Estimate a 1K overhead, + 128 bytes per file entry */+returnPAGE_SIZE<<get_order(SZ_1K+(num_files*128));+}++staticvoidluo_files_fdt_cleanup(void)+{+WARN_ON_ONCE(kho_unpreserve_phys(__pa(luo_file_fdt_out),+luo_file_fdt_out_size));++free_pages((unsignedlong)luo_file_fdt_out,+get_order(luo_file_fdt_out_size));++luo_file_fdt_out_size=0;+luo_file_fdt_out=NULL;+}++staticintluo_files_to_fdt(structxarray*files_xa_out)+{+constu64zero_data=0;+unsignedlongtoken;+structluo_file*h;+chartoken_str[19];+intret=0;++xa_for_each(files_xa_out,token,h){+snprintf(token_str,sizeof(token_str),"%#0llx",(u64)token);++ret=fdt_begin_node(luo_file_fdt_out,token_str);+if(ret<0)+break;++ret=fdt_property_string(luo_file_fdt_out,"compatible",+h->fh->compatible);+if(ret<0){+fdt_end_node(luo_file_fdt_out);+break;+}++ret=fdt_property_u64(luo_file_fdt_out,"data",zero_data);+if(ret<0){+fdt_end_node(luo_file_fdt_out);+break;+}++ret=fdt_end_node(luo_file_fdt_out);+if(ret<0)+break;+}++returnret;+}++staticintluo_files_fdt_setup(void)+{+intret;++guard(rwsem_write)(&luo_file_fdt_rwsem);+luo_file_fdt_out_size=luo_files_fdt_size();+luo_file_fdt_out=(void*)__get_free_pages(GFP_KERNEL|__GFP_ZERO,+get_order(luo_file_fdt_out_size));+if(!luo_file_fdt_out){+pr_err("Failed to allocate FDT memory (%zu bytes)\n",+luo_file_fdt_out_size);+luo_file_fdt_out_size=0;+return-ENOMEM;+}++ret=kho_preserve_phys(__pa(luo_file_fdt_out),luo_file_fdt_out_size);+if(ret){+pr_err("Failed to kho preserve FDT memory (%zu bytes)\n",+luo_file_fdt_out_size);+luo_file_fdt_out_size=0;+luo_file_fdt_out=NULL;+returnret;+}++ret=fdt_create(luo_file_fdt_out,luo_file_fdt_out_size);+if(ret<0)+gotoexit_cleanup;++ret=fdt_finish_reservemap(luo_file_fdt_out);+if(ret<0)+gotoexit_finish;++ret=fdt_begin_node(luo_file_fdt_out,LUO_FILES_NODE_NAME);+if(ret<0)+gotoexit_finish;++ret=fdt_property_string(luo_file_fdt_out,"compatible",+LUO_FILES_COMPATIBLE);+if(ret<0)+gotoexit_end_node;++ret=luo_files_to_fdt(&luo_files_xa_out);+if(ret<0)+gotoexit_end_node;++ret=fdt_end_node(luo_file_fdt_out);+if(ret<0)+gotoexit_finish;++ret=fdt_finish(luo_file_fdt_out);+if(ret<0)+gotoexit_cleanup;++return0;++exit_end_node:+fdt_end_node(luo_file_fdt_out);+exit_finish:+fdt_finish(luo_file_fdt_out);+exit_cleanup:+pr_err("Failed to setup FDT: %s (ret %d)\n",fdt_strerror(ret),ret);+luo_files_fdt_cleanup();++returnret;+}++staticintluo_files_prepare(structliveupdate_subsystem*h,u64*data)+{+intret;++ret=luo_files_fdt_setup();+if(ret)+returnret;++scoped_guard(rwsem_read,&luo_file_fdt_rwsem)+*data=__pa(luo_file_fdt_out);++returnret;+}++staticintluo_files_freeze(structliveupdate_subsystem*h,u64*data)+{+return0;+}++staticvoidluo_files_finish(structliveupdate_subsystem*h,u64data)+{+luo_files_recreate_luo_files_xa_in();+}++staticvoidluo_files_cancel(structliveupdate_subsystem*h,u64data)+{+}++staticvoidluo_files_boot(structliveupdate_subsystem*h,u64fdt_pa)+{+intret;++ret=fdt_node_check_compatible(__va(fdt_pa),0,+LUO_FILES_COMPATIBLE);+if(ret){+luo_restore_fail("FDT '%s' is incompatible with '%s' [%d]\n",+LUO_FILES_NODE_NAME,LUO_FILES_COMPATIBLE,+ret);+}+scoped_guard(rwsem_write,&luo_file_fdt_rwsem)+luo_file_fdt_in=__va(fdt_pa);+}++staticconststructliveupdate_subsystem_opsluo_file_subsys_ops={+.prepare=luo_files_prepare,+.freeze=luo_files_freeze,+.cancel=luo_files_cancel,+.boot=luo_files_boot,+.finish=luo_files_finish,+.owner=THIS_MODULE,+};++staticstructliveupdate_subsystemluo_file_subsys={+.ops=&luo_file_subsys_ops,+.name=LUO_FILES_NODE_NAME,+};++staticint__initluo_files_startup(void)+{+intret;++if(!liveupdate_enabled())+return0;++ret=liveupdate_register_subsystem(&luo_file_subsys);+if(ret){+pr_warn("Failed to register luo_file subsystem [%d]\n",ret);+returnret;+}++returnret;+}+late_initcall(luo_files_startup);++/**+*luo_register_file-Registerafiledescriptorforliveupdatemanagement.+*@token:Tokenvalueforthisfiledescriptor.+*@fd:filedescriptortobepreserved.+*+*Context:MustbecalledwhenLUOisin'normal'state.+*+*Return:0onsuccess.Negativeerrnoonfailure.+*/+intluo_register_file(u64token,intfd)+{+structliveupdate_file_handler*fh;+structluo_file*luo_file;+boolfound=false;+intret=-ENOENT;+structfile*file;++file=fget(fd);+if(!file){+pr_err("Bad file descriptor\n");+return-EBADF;+}++luo_state_read_enter();+if(!liveupdate_state_normal()&&!liveupdate_state_updated()){+pr_warn("File can be registered only in normal or updated state\n");+luo_state_read_exit();+fput(file);+return-EBUSY;+}++guard(rwsem_read)(&luo_register_file_list_rwsem);+list_for_each_entry(fh,&luo_register_file_list,list){+if(fh->ops->can_preserve(fh,file)){+found=true;+break;+}+}++if(!found)+gotoexit_unlock;++luo_file=kmalloc(sizeof(*luo_file),GFP_KERNEL);+if(!luo_file){+ret=-ENOMEM;+gotoexit_unlock;+}++luo_file->private_data=0;+luo_file->reclaimed=false;++luo_file->file=file;+luo_file->fh=fh;+mutex_init(&luo_file->mutex);+luo_file->state=LIVEUPDATE_STATE_NORMAL;++if(xa_load(&luo_files_xa_out,token)){+ret=-EEXIST;+pr_warn("Token %llu is already taken\n",token);+mutex_destroy(&luo_file->mutex);+kfree(luo_file);+gotoexit_unlock;+}++ret=xa_err(xa_store(&luo_files_xa_out,token,luo_file,+GFP_KERNEL));+if(ret<0){+pr_warn("Failed to store file for token %llu in XArray: %d\n",+token,ret);+mutex_destroy(&luo_file->mutex);+kfree(luo_file);+gotoexit_unlock;+}+atomic64_inc(&luo_files_count);++exit_unlock:+luo_state_read_exit();++if(ret)+fput(file);++returnret;+}++staticint__luo_unregister_file(u64token)+{+structluo_file*luo_file;++luo_file=xa_erase(&luo_files_xa_out,token);+if(!luo_file)+return-ENOENT;++fput(luo_file->file);+mutex_destroy(&luo_file->mutex);+kfree(luo_file);+atomic64_dec(&luo_files_count);++return0;+}++/**+*luo_unregister_file-Unregisterafileinstanceusingitstoken.+*@token:Theuniquetokenofthefileinstancetounregister.+*+*Findsthe&structluo_fileassociatedwiththe@tokeninthe+*globallistandremovesit.Thisfunction*only*removestheentryfromthe+*list;itdoes*not*freethememoryallocatedforthe&structluo_file+*itself.Thecallerisresponsibleforfreeingthestructureafterthis+*functionreturnssuccessfully.+*+*Context:Canbecalledwhenapreservedfiledescriptorisclosedor+*nolongerneedsliveupdatemanagement.+*+*Return:0onsuccess.Negativeerrnoonfailure.+*/+intluo_unregister_file(u64token)+{+intret=0;++luo_state_read_enter();+if(!liveupdate_state_normal()&&!liveupdate_state_updated()){+pr_warn("File can be unregistered only in normal or updates state\n");+luo_state_read_exit();+return-EBUSY;+}++ret=__luo_unregister_file(token);+if(ret){+pr_warn("Failed to unregister: token %llu not found.\n",+token);+}+luo_state_read_exit();++returnret;+}++/**+*luo_retrieve_file-Findaregisteredfileinstancebyitstoken.+*@token:Theuniquetokenofthefileinstancetoretrieve.+*@filep:Outputparameter.Onsuccess(returnvalue0),thiswillpoint+*totheretrieved"struct file".+*+*Searchesthegloballistfora&structluo_filematchingthe@token.Usesa+*readlock,allowingconcurrentretrievals.+*+*Return:0onsuccess.Negativeerrnoonfailure.+*/+intluo_retrieve_file(u64token,structfile**filep)+{+structluo_file*luo_file;+intret=0;++luo_files_recreate_luo_files_xa_in();+luo_state_read_enter();+if(!liveupdate_state_updated()){+pr_warn("File can be retrieved only in updated state\n");+luo_state_read_exit();+return-EBUSY;+}++luo_file=xa_load(&luo_files_xa_in,token);+if(luo_file&&!luo_file->reclaimed){+scoped_guard(mutex,&luo_file->mutex){+if(!luo_file->reclaimed){+luo_file->reclaimed=true;+ret=luo_file->fh->ops->retrieve(luo_file->fh,+luo_file->private_data,+filep);+if(!ret)+luo_file->file=*filep;+}+}+}elseif(luo_file&&luo_file->reclaimed){+pr_err("The file descriptor for token %lld has already been retrieved\n",+token);+ret=-EINVAL;+}else{+ret=-ENOENT;+}++luo_state_read_exit();++returnret;+}++/**+*liveupdate_register_file_handler-RegisterafilehandlerwithLUO.+*@fh:Pointertoacaller-allocated&structliveupdate_file_handler.+*Thecallermustinitializethisstructure,includingaunique+*'compatible'stringandavalid'fh'callbacks.Thisfunctionaddsthe+*handlertothegloballistofsupportedfilehandlers.+*+*Context:Typicallycalledduringmoduleinitializationforfiletypesthat+*supportliveupdatepreservation.+*+*Return:0onsuccess.Negativeerrnoonfailure.+*/+intliveupdate_register_file_handler(structliveupdate_file_handler*fh)+{+structliveupdate_file_handler*fh_iter;+intret=0;++luo_state_read_enter();+if(!liveupdate_state_normal()&&!liveupdate_state_updated()){+luo_state_read_exit();+return-EBUSY;+}++guard(rwsem_write)(&luo_register_file_list_rwsem);+list_for_each_entry(fh_iter,&luo_register_file_list,list){+if(!strcmp(fh_iter->compatible,fh->compatible)){+pr_err("File handler registration failed: Compatible string '%s' already registered.\n",+fh->compatible);+ret=-EEXIST;+gotoexit_unlock;+}+}++if(!try_module_get(fh->ops->owner)){+pr_warn("File handler '%s' unable to get reference.\n",+fh->compatible);+ret=-EAGAIN;+gotoexit_unlock;+}++INIT_LIST_HEAD(&fh->list);+list_add_tail(&fh->list,&luo_register_file_list);++exit_unlock:+luo_state_read_exit();++returnret;+}++/**+*liveupdate_unregister_file-Unregisterafilehandler.+*@fh:Pointertothespecific&structliveupdate_file_handlerinstance+*thatwaspreviouslyreturnedbyorpassedto+*liveupdate_register_file_handler.+*+*Removesthespecifiedhandlerinstance@fhfromthegloballistof+*registeredfilehandlers.Thisfunctiononlyremovestheentryfromthe+*list;itdoesnotfreethememoryassociatedwith@fhitself.Thecaller+*isresponsibleforfreeingthestructurememoryafterthisfunctionreturns+*successfully.+*+*Return:0onsuccess.Negativeerrnoonfailure.+*/+intliveupdate_unregister_file_handler(structliveupdate_file_handler*fh)+{+unsignedlongtoken;+structluo_file*h;+intret=0;++luo_state_read_enter();+if(!liveupdate_state_normal()&&!liveupdate_state_updated()){+luo_state_read_exit();+return-EBUSY;+}++guard(rwsem_write)(&luo_register_file_list_rwsem);++xa_for_each(&luo_files_xa_out,token,h){+if(h->fh==fh){+luo_state_read_exit();+return-EBUSY;+}+}++list_del_init(&fh->list);+luo_state_read_exit();+module_put(fh->ops->owner);++returnret;+}
Implements the core logic within luo_files.c to invoke the prepare,
reboot, finish, and cancel callbacks for preserved file instances,
replacing the previous stub implementations. It also handles
the persistence and retrieval of the u64 data payload associated with
each file via the LUO FDT.
This completes the core mechanism enabling registered files handlers to actively
manage file state across the live update transition using the LUO framework.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/liveupdate/luo_files.c | 191 +++++++++++++++++++++++++++++++++-
1 file changed, 188 insertions(+), 3 deletions(-)
@@ -326,32 +326,190 @@ static int luo_files_fdt_setup(void)returnret;}+staticintluo_files_prepare_one(structluo_file*h)+{+intret=0;++guard(mutex)(&h->mutex);+if(h->state==LIVEUPDATE_STATE_NORMAL){+if(h->fh->ops->prepare){+ret=h->fh->ops->prepare(h->fh,h->file,+&h->private_data);+}+if(!ret)+h->state=LIVEUPDATE_STATE_PREPARED;+}else{+WARN_ON_ONCE(h->state!=LIVEUPDATE_STATE_PREPARED&&+h->state!=LIVEUPDATE_STATE_FROZEN);+}++returnret;+}++staticintluo_files_freeze_one(structluo_file*h)+{+intret=0;++guard(mutex)(&h->mutex);+if(h->state==LIVEUPDATE_STATE_PREPARED){+if(h->fh->ops->freeze){+ret=h->fh->ops->freeze(h->fh,h->file,+&h->private_data);+}+if(!ret)+h->state=LIVEUPDATE_STATE_FROZEN;+}else{+WARN_ON_ONCE(h->state!=LIVEUPDATE_STATE_FROZEN);+}++returnret;+}++staticvoidluo_files_finish_one(structluo_file*h)+{+guard(mutex)(&h->mutex);+if(h->state==LIVEUPDATE_STATE_UPDATED){+if(h->fh->ops->finish){+h->fh->ops->finish(h->fh,h->file,h->private_data,+h->reclaimed);+}+h->state=LIVEUPDATE_STATE_NORMAL;+}else{+WARN_ON_ONCE(h->state!=LIVEUPDATE_STATE_NORMAL);+}+}++staticvoidluo_files_cancel_one(structluo_file*h)+{+intret;++guard(mutex)(&h->mutex);+if(h->state==LIVEUPDATE_STATE_NORMAL)+return;++ret=WARN_ON_ONCE(h->state!=LIVEUPDATE_STATE_PREPARED&&+h->state!=LIVEUPDATE_STATE_FROZEN);+if(ret)+return;++if(h->fh->ops->cancel)+h->fh->ops->cancel(h->fh,h->file,h->private_data);+h->private_data=0;+h->state=LIVEUPDATE_STATE_NORMAL;+}++staticvoid__luo_files_cancel(structluo_file*boundary_file)+{+unsignedlongtoken;+structluo_file*h;++xa_for_each(&luo_files_xa_out,token,h){+if(h==boundary_file)+break;++luo_files_cancel_one(h);+}+luo_files_fdt_cleanup();+}++staticintluo_files_commit_data_to_fdt(void)+{+intnode_offset,ret;+unsignedlongtoken;+chartoken_str[19];+structluo_file*h;++guard(rwsem_read)(&luo_file_fdt_rwsem);+xa_for_each(&luo_files_xa_out,token,h){+snprintf(token_str,sizeof(token_str),"%#0llx",(u64)token);+node_offset=fdt_subnode_offset(luo_file_fdt_out,+0,+token_str);+ret=fdt_setprop(luo_file_fdt_out,node_offset,"data",+&h->private_data,sizeof(h->private_data));+if(ret<0){+pr_err("Failed to set data property for token %s: %s\n",+token_str,fdt_strerror(ret));+return-ENOSPC;+}+}++return0;+}+staticintluo_files_prepare(structliveupdate_subsystem*h,u64*data){+unsignedlongtoken;+structluo_file*luo_file;intret;ret=luo_files_fdt_setup();if(ret)returnret;-scoped_guard(rwsem_read,&luo_file_fdt_rwsem)-*data=__pa(luo_file_fdt_out);+xa_for_each(&luo_files_xa_out,token,luo_file){+ret=luo_files_prepare_one(luo_file);+if(ret<0){+pr_err("Prepare failed for file token %#0llx handler '%s' [%d]\n",+(u64)token,luo_file->fh->compatible,ret);+__luo_files_cancel(luo_file);++returnret;+}+}++ret=luo_files_commit_data_to_fdt();+if(ret){+__luo_files_cancel(NULL);+}else{+scoped_guard(rwsem_read,&luo_file_fdt_rwsem)+*data=__pa(luo_file_fdt_out);+}returnret;}staticintluo_files_freeze(structliveupdate_subsystem*h,u64*data){-return0;+unsignedlongtoken;+structluo_file*luo_file;+intret;++xa_for_each(&luo_files_xa_out,token,luo_file){+ret=luo_files_freeze_one(luo_file);+if(ret<0){+pr_err("Freeze callback failed for file token %#0llx handler '%s' [%d]\n",+(u64)token,luo_file->fh->compatible,ret);+__luo_files_cancel(luo_file);++returnret;+}+}++ret=luo_files_commit_data_to_fdt();+if(ret)+__luo_files_cancel(NULL);++returnret;}staticvoidluo_files_finish(structliveupdate_subsystem*h,u64data){+unsignedlongtoken;+structluo_file*luo_file;+luo_files_recreate_luo_files_xa_in();+xa_for_each(&luo_files_xa_in,token,luo_file){+luo_files_finish_one(luo_file);+mutex_destroy(&luo_file->mutex);+kfree(luo_file);+}+xa_destroy(&luo_files_xa_in);}staticvoidluo_files_cancel(structliveupdate_subsystem*h,u64data){+__luo_files_cancel(NULL);}staticvoidluo_files_boot(structliveupdate_subsystem*h,u64fdt_pa)
@@ -484,6 +642,27 @@ int luo_register_file(u64 token, int fd)returnret;}+staticvoidluo_files_fdt_remove_node(u64token)+{+chartoken_str[19];+intoffset,ret;++guard(rwsem_write)(&luo_file_fdt_rwsem);+if(!luo_file_fdt_out)+return;++snprintf(token_str,sizeof(token_str),"%#0llx",token);+offset=fdt_subnode_offset(luo_file_fdt_out,0,token_str);+if(offset<0)+return;++ret=fdt_del_node(luo_file_fdt_out,offset);+if(ret<0){+pr_warn("LUO Files: Failed to delete FDT node for token %s: %s\n",+token_str,fdt_strerror(ret));+}+}+staticint__luo_unregister_file(u64token){structluo_file*luo_file;
@@ -492,6 +671,12 @@ static int __luo_unregister_file(u64 token)if(!luo_file)return-ENOENT;+if(luo_file->state==LIVEUPDATE_STATE_FROZEN||+luo_file->state==LIVEUPDATE_STATE_PREPARED){+luo_files_cancel_one(luo_file);+luo_files_fdt_remove_node(token);+}+fput(luo_file->file);mutex_destroy(&luo_file->mutex);kfree(luo_file);
Introduce the user-space interface for the Live Update Orchestrator
via ioctl commands, enabling external control over the live update
process and management of preserved resources.
The idea is that there is going to be a single userspace agent driving
the live update, therefore, only a single process can ever hold this
device opened at a time.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
include/uapi/linux/liveupdate.h | 243 ++++++++++++++++++++++++++++++++
kernel/liveupdate/luo_ioctl.c | 200 ++++++++++++++++++++++++++
2 files changed, 443 insertions(+)
Currently, a file descriptor registered for preservation via the remains
globally registered with LUO until it is explicitly unregistered. This
creates a potential for resource leaks into the next kernel if the
userspace agent crashes or exits without proper cleanup before a live
update is fully initiated.
This patch ties the lifetime of FD preservation requests to the lifetime
of the open file descriptor for /dev/liveupdate, creating an implicit
"session".
When the /dev/liveupdate file descriptor is closed (either explicitly
via close() or implicitly on process exit/crash), the .release
handler, luo_release(), is now called. This handler invokes the new
function luo_unregister_all_files(), which iterates through all FDs
that were preserved through that session and unregisters them.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/liveupdate/luo_files.c | 19 +++++++++++++++++++
kernel/liveupdate/luo_internal.h | 1 +
kernel/liveupdate/luo_ioctl.c | 1 +
3 files changed, 21 insertions(+)
Introduce a set of new ioctls to allow a userspace agent to query and
control the live update state of individual file descriptors that have
been registered for preservation.
Previously, state transitions (prepare, freeze, finish) were handled
globally for all registered resources by the main LUO state machine.
This patch provides a more granular interface, enabling a controlling
agent to manage the lifecycle of specific FDs independently, which is
useful for performance reasons.
- Adds LIVEUPDATE_IOCTL_GET_FD_STATE to query the current state
(e.g., NORMAL, PREPARED, FROZEN) of a file identified by its token.
- Adds LIVEUPDATE_IOCTL_SET_FD_EVENT to trigger state transitions
(PREPARE, FREEZE, CANCEL, FINISH) for a single file.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
include/uapi/linux/liveupdate.h | 62 +++++++++++++
kernel/liveupdate/luo_files.c | 152 +++++++++++++++++++++++++++++++
kernel/liveupdate/luo_internal.h | 8 ++
kernel/liveupdate/luo_ioctl.c | 48 ++++++++++
4 files changed, 270 insertions(+)
@@ -740,6 +740,158 @@ void luo_unregister_all_files(void)WARN_ON_ONCE(atomic64_read(&luo_files_count)!=0);}+/**+*luo_file_get_state-Getthepreservationstateofaspecificfile.+*@token:Thetokenofthefiletoquery.+*@statep:Outputpointertostorethefile'scurrentliveupdatestate.+*@incoming:Iftrue,querythestateofarestoredfilefromtheincoming+*(previouskernel's)set.Iffalse,queryafilebeingprepared+*forpreservationinthecurrentset.+*+*Findsthefileassociatedwiththegiven@tokenineithertheincoming+*oroutgoingtrackingarraysandreturnsitscurrentLUOstate+*(NORMAL,PREPARED,FROZEN,UPDATED).+*+*Return:0onsuccess,-ENOENTifthetokenisnotfound.+*/+intluo_file_get_state(u64token,enumliveupdate_state*statep,boolincoming)+{+structluo_file*luo_file;+structxarray*target_xa;+intret=0;++luo_state_read_enter();++target_xa=incoming?&luo_files_xa_in:&luo_files_xa_out;+luo_file=xa_load(target_xa,token);++if(!luo_file){+ret=-ENOENT;+gotoout_unlock;+}++scoped_guard(mutex,&luo_file->mutex)+*statep=luo_file->state;++out_unlock:+luo_state_read_exit();+returnret;+}++/**+*luo_file_prepare-Prepareasingleregisteredfileforliveupdate.+*@token:Thetokenofthefiletoprepare.+*+*Findsthefileassociatedwith@tokenandtransitionsittothePREPARED+*statebyinvokingitshandler's->prepare()callback.Thisallowsfor+*granular,per-filepreparationbeforetheglobalLUOPREPAREevent.+*+*Return:0onsuccess,negativeerrorcodeonfailure.+*/+intluo_file_prepare(u64token)+{+structluo_file*luo_file;+intret;++luo_state_read_enter();+luo_file=xa_load(&luo_files_xa_out,token);+if(!luo_file){+ret=-ENOENT;+gotoout_unlock;+}++ret=luo_files_prepare_one(luo_file);+out_unlock:+luo_state_read_exit();+returnret;+}++/**+*luo_file_freeze-Freezeasinglepreparedfileforliveupdate.+*@token:Thetokenofthefiletofreeze.+*+*Findsthefileassociatedwith@tokenandtransitionsitfromthePREPARED+*totheFROZENstatebyinvokingitshandler's->freeze()callback.Thisis+*typicallyusedforfinal,"blackout window"statesavingforaspecific+*file.+*+*Return:0onsuccess,negativeerrorcodeonfailure.+*/+intluo_file_freeze(u64token)+{+structluo_file*luo_file;+intret;++luo_state_read_enter();+luo_file=xa_load(&luo_files_xa_out,token);+if(!luo_file){+ret=-ENOENT;+gotoout_unlock;+}++ret=luo_files_freeze_one(luo_file);+out_unlock:+luo_state_read_exit();+returnret;+}++intluo_file_cancel(u64token)+{+structluo_file*luo_file;+intret=0;++luo_state_read_enter();+luo_file=xa_load(&luo_files_xa_out,token);+if(!luo_file){+ret=-ENOENT;+gotoout_unlock;+}++luo_files_cancel_one(luo_file);+out_unlock:+luo_state_read_exit();+returnret;+}++/**+*luo_file_finish-Clean-upasinglerestoredfileafterliveupdate.+*@token:Thetokenofthefiletofinalize.+*+*Thisfunctioniscalledinthenewkernelafteraliveupdate,typically+*afterafilehasbeenrestoredvialuo_retrieve_file()andisnolonger+*neededbytheuserspaceagentinitspreservedstate.Itinvokesthe+*handler's->finish()callback,allowingforanyfinalcleanupofthe+*preservedstateassociatedwiththisspecificfile.+*+*ThismustbecalledwhenLUOisintheUPDATEDstate.+*+*Return:0onsuccess,-ENOENTifthetokenisnotfound,-EBUSYifnot+*intheUPDATEDstate.+*/+intluo_file_finish(u64token)+{+structluo_file*luo_file;+intret=0;++luo_state_read_enter();+if(!liveupdate_state_updated()){+pr_warn("finish can only be done in UPDATED state\n");+ret=-EBUSY;+gotoout_unlock;+}++luo_file=xa_load(&luo_files_xa_in,token);+if(!luo_file){+ret=-ENOENT;+gotoout_unlock;+}++luo_files_finish_one(luo_file);+out_unlock:+luo_state_read_exit();+returnret;+}+/***luo_retrieve_file-Findaregisteredfileinstancebyitstoken.*@token:Theuniquetokenofthefileinstancetoretrieve.
Introduce a sysfs interface for the Live Update Orchestrator
under /sys/kernel/liveupdate/. This interface provides a way for
userspace tools and scripts to monitor the current state of the LUO
state machine.
The main feature is a read-only file, state, which displays the
current LUO state as a string ("normal", "prepared", "frozen",
"updated"). The interface uses sysfs_notify to allow userspace
listeners (e.g., via poll) to be efficiently notified of state changes.
ABI documentation for this new sysfs interface is added in
Documentation/ABI/testing/sysfs-kernel-liveupdate.
This read-only sysfs interface complements the main ioctl interface
provided by /dev/liveupdate, which handles LUO control operations and
resource management.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
.../ABI/testing/sysfs-kernel-liveupdate | 51 ++++++++++
kernel/liveupdate/Kconfig | 18 ++++
kernel/liveupdate/Makefile | 1 +
kernel/liveupdate/luo_core.c | 1 +
kernel/liveupdate/luo_internal.h | 6 ++
kernel/liveupdate/luo_sysfs.c | 92 +++++++++++++++++++
6 files changed, 169 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-kernel-liveupdate
create mode 100644 kernel/liveupdate/luo_sysfs.c
@@ -0,0 +1,51 @@+What: /sys/kernel/liveupdate/+Date: May 2025+KernelVersion: 6.16.0+Contact: pasha.tatashin@soleen.com+Description: Directory containing interfaces to query the live+ update orchestrator. Live update is the ability to reboot the+ host kernel (e.g., via kexec, without a full power cycle) while+ keeping specifically designated devices operational ("alive")+ across the transition. After the new kernel boots, these devices+ can be re-attached to their original workloads (e.g., virtual+ machines) with their state preserved. This is particularly+ useful, for example, for quick hypervisor updates without+ terminating running virtual machines.+++What: /sys/kernel/liveupdate/state+Date: May 2025+KernelVersion: 6.16.0+Contact: pasha.tatashin@soleen.com+Description: Read-only file that displays the current state of the live+ update orchestrator as a string. Possible values are:++ "normal" No live update operation is in progress. This is+ the default operational state.++ "prepared" The live update preparation phase has completed+ successfully (e.g., triggered via the+ /dev/liveupdate event). Kernel subsystems have+ been notified via the %LIVEUPDATE_PREPARE+ event/callback and should have initiated state+ saving. User workloads (e.g., VMs) are generally+ still running, but some operations (like device+ unbinding or new DMA mappings) might be+ restricted. The system is ready for the reboot+ trigger.++ "frozen" The final reboot notification has been sent+ (e.g., triggered via the 'reboot()' syscall),+ corresponding to the %LIVEUPDATE_REBOOT kernel+ event. Subsystems have had their final chance to+ save state. User workloads must be suspended.+ The system is about to execute the reboot into+ the new kernel (imminent kexec). This state+ corresponds to the "blackout window".++ "updated" The system has successfully rebooted into the+ new kernel via live update. Restoration of+ preserved resources can now occur (typically via+ ioctl commands). The system is awaiting the+ final 'finish' signal after user space completes+ restoration tasks.
@@ -0,0 +1,92 @@+// SPDX-License-Identifier: GPL-2.0++/*+*Copyright(c)2025,GoogleLLC.+*PashaTatashin<pasha.tatashin@soleen.com>+*/++/**+*DOC:LUOsysfsinterface+*+*Providesasysfsinterfaceat``/sys/kernel/liveupdate/``formonitoringLUO+*state.Liveupdateallowsrebootingthekernel(viakexec)whilepreserving+*designateddevicestateforattachedworkloads(e.g.,VMs),usefulfor+*minimizingdowntimeduringhypervisorupdates.+*+*/sys/kernel/liveupdate/state+*----------------------------+*-Permissions:Read-only+*-Description:DisplaysthecurrentLUOstatestring.+*-ValidStates:+*@normal+*Idlestate.+*@prepared+*Preparationphasecomplete(triggeredvia'/dev/liveupdate').Resources+*checked,statesavinginitiatedvia%LIVEUPDATE_PREPAREevent.+*Workloadsmostlyrunningbutmayberestricted.Readyforreboot+*trigger.+*@frozen+*Finalrebootnotificationsent(triggeredvia'reboot').Correspondsto+*%LIVEUPDATE_REBOOTevent.Finalstatesaving.Workloadsmustbe+*suspended.Systemabouttokexec("blackout window").+*@updated+*Newkernelbootedvialiveupdate.Awaiting'finish'signal.+*+*UserspaceInteraction&BlackoutWindowReduction+*-------------------------------------------------+*Userspacemonitorsthe``state``filetocoordinateactions:+*-Suspendworkloadsbefore@frozenstateisentered.+*-Initiateresourcerestorationuponentering@updatedstate.+*-Resumeworkloadsafterrestoration,minimizingdowntime.+*/++#include<linux/kobject.h>+#include<linux/liveupdate.h>+#include<linux/sysfs.h>+#include"luo_internal.h"++staticboolluo_sysfs_initialized;++#define LUO_DIR_NAME "liveupdate"++voidluo_sysfs_notify(void)+{+if(luo_sysfs_initialized)+sysfs_notify(kernel_kobj,LUO_DIR_NAME,"state");+}++/* Show the current live update state */+staticssize_tstate_show(structkobject*kobj,structkobj_attribute*attr,+char*buf)+{+returnsysfs_emit(buf,"%s\n",luo_current_state_str());+}++staticstructkobj_attributestate_attribute=__ATTR_RO(state);++staticstructattribute*luo_attrs[]={+&state_attribute.attr,+NULL+};++staticstructattribute_groupluo_attr_group={+.attrs=luo_attrs,+.name=LUO_DIR_NAME,+};++staticint__initluo_init(void)+{+intret;++ret=sysfs_create_group(kernel_kobj,&luo_attr_group);+if(ret){+pr_err("Failed to create group\n");+returnret;+}++luo_sysfs_initialized=true;+pr_info("Initialized\n");++return0;+}+subsys_initcall(luo_init);
Modify the reboot() syscall handler in kernel/reboot.c to call
liveupdate_reboot() when processing the LINUX_REBOOT_CMD_KEXEC
command.
This ensures that the Live Update Orchestrator is notified just
before the kernel executes the kexec jump. The liveupdate_reboot()
function triggers the final LIVEUPDATE_FREEZE event, allowing
participating subsystems to perform last-minute state saving within
the blackout window, and transitions the LUO state machine to FROZEN.
The call is placed immediately before kernel_kexec() to ensure LUO
finalization happens at the latest possible moment before the kernel
transition.
If liveupdate_reboot() returns an error (indicating a failure during
LUO finalization), the kexec operation is aborted to prevent proceeding
with an inconsistent state.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/reboot.c | 4 ++++
1 file changed, 4 insertions(+)
Now, that LUO and KHO both live under kernel/liveupdate, it makes
sense to also move the kho debugfs files to liveupdate/
The old names:
/sys/kernel/debug/kho/out/
/sys/kernel/debug/kho/in/
The new names:
/sys/kernel/debug/liveupdate/kho_out/
/sys/kernel/debug/liveupdate/kho_in/
Also, export the liveupdate_debufs_root, so LUO selftests could use
it as well.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/liveupdate/kexec_handover_debug.c | 11 ++++++-----
kernel/liveupdate/luo_internal.h | 4 ++++
2 files changed, 10 insertions(+), 5 deletions(-)
Introduce a self-test mechanism for the LUO to allow verification of
core subsystem management functionality. This is primarily intended
for developers and system integrators validating the live update
feature.
The tests are enabled via the new Kconfig option
CONFIG_LIVEUPDATE_SELFTESTS (default 'n') and are triggered through
a new ioctl command, LIVEUPDATE_IOCTL_SELFTESTS, added to the
/dev/liveupdate device node.
This ioctl accepts commands defined in luo_selftests.h to:
- LUO_CMD_SUBSYSTEM_REGISTER: Creates and registers a dummy LUO
subsystem using the liveupdate_register_subsystem() function. It
allocates a data page and copies initial data from userspace.
- LUO_CMD_SUBSYSTEM_UNREGISTER: Unregisters the specified dummy
subsystem using the liveupdate_unregister_subsystem() function and
cleans up associated test resources.
- LUO_CMD_SUBSYSTEM_GETDATA: Copies the data page associated with a
registered test subsystem back to userspace, allowing verification of
data potentially modified or preserved by test callbacks.
This provides a way to test the fundamental registration and
unregistration flows within the LUO framework from userspace without
requiring a full live update sequence.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/liveupdate/Kconfig | 15 ++
kernel/liveupdate/Makefile | 1 +
kernel/liveupdate/luo_selftests.c | 345 ++++++++++++++++++++++++++++++
kernel/liveupdate/luo_selftests.h | 84 ++++++++
4 files changed, 445 insertions(+)
create mode 100644 kernel/liveupdate/luo_selftests.c
create mode 100644 kernel/liveupdate/luo_selftests.h
@@ -0,0 +1,345 @@+// SPDX-License-Identifier: GPL-2.0++/*+*Copyright(c)2025,GoogleLLC.+*PashaTatashin<pasha.tatashin@soleen.com>+*/++/**+*DOC:LUOSelftests+*+*Weprovideioctl-basedselftestinterfacefortheLUO.Itprovidesa+*mechanismtotestcoreLUOfunctionality,particularlytheregistration,+*unregistration,anddatahandlingaspectsofLUOsubsystems,without+*requiringafullliveupdateeventsequence.+*+*ThetestsareintendedprimarilyfordevelopersworkingontheLUOframework+*orforvalidationpurposesduringsystemintegration.Thisfunctionalityis+*conditionallycompiledbasedonthe`CONFIG_LIVEUPDATE_SELFTESTS`Kconfig+*optionandshouldtypicallybedisabledinproductionkernels.+*+*Interface:+*Theselftestsareaccessedviathe`/dev/liveupdate`characterdeviceusing+*the`LIVEUPDATE_IOCTL_SELFTESTS`ioctlcommand.Theargumenttotheioctl+*isapointertoa`structliveupdate_selftest`structure(definedin+*`uapi/linux/liveupdate.h`),whichcontains:+*-`cmd`:Thespecificselftestcommandtoexecute(e.g.,+*`LUO_CMD_SUBSYSTEM_REGISTER`).+*-`arg`:Apointertoacommand-specificargumentstructure.Forsubsystem+*tests,thispointstoa`structluo_arg_subsystem`(definedin+*`luo_selftests.h`).+*+*Commands:+*-`LUO_CMD_SUBSYSTEM_REGISTER`:+*RegistersanewdummyLUOsubsystem.Itallocateskernelmemoryfortest+*data,copiesinitialdatafromtheuser-provided`data_page`,setsup+*simpleloggingcallbacks,andcallsthecore+*`liveupdate_register_subsystem()`+*function.Requires`arg`pointingto`structluo_arg_subsystem`.+*-`LUO_CMD_SUBSYSTEM_UNREGISTER`:+*Unregistersapreviouslyregistereddummysubsystemidentifiedby`name`.+*Itcallsthecore`liveupdate_unregister_subsystem()`functionandthen+*freestheassociatedkernelmemoryandinternaltrackingstructures.+*Requires`arg`pointingto`structluo_arg_subsystem`(only`name`used).+*-`LUO_CMD_SUBSYSTEM_GETDATA`:+*Copiesthecontentofthekerneldatapageassociatedwiththespecified+*dummysubsystem(`name`)backtotheuser-provided`data_page`.Thisallows+*userspacetoverifythestateofthedataafterpotentialtestoperations.+*Requires`arg`pointingto`structluo_arg_subsystem`.+*/++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt++#include<linux/debugfs.h>+#include<linux/errno.h>+#include<linux/gfp.h>+#include<linux/kexec_handover.h>+#include<linux/liveupdate.h>+#include<linux/mutex.h>+#include<linux/uaccess.h>+#include<uapi/linux/liveupdate.h>+#include"luo_internal.h"+#include"luo_selftests.h"++staticstructluo_subsystems{+structliveupdate_subsystemhandle;+charname[LUO_NAME_LENGTH];+void*data;+boolin_use;+boolpreserved;+}luo_subsystems[LUO_MAX_SUBSYSTEMS];++/* Only allow one selftest ioctl operation at a time */+staticDEFINE_MUTEX(luo_ioctl_mutex);++staticintluo_subsystem_prepare(structliveupdate_subsystem*h,u64*data)+{+structluo_subsystems*s=container_of(h,structluo_subsystems,+handle);+unsignedlongphys_addr=__pa(s->data);+intret;++ret=kho_preserve_phys(phys_addr,PAGE_SIZE);+if(ret)+returnret;++s->preserved=true;+*data=phys_addr;+pr_info("Subsystem '%s' prepare data[%lx]\n",+s->name,phys_addr);++if(strstr(s->name,NAME_PREPARE_FAIL))+return-EAGAIN;++return0;+}++staticintluo_subsystem_freeze(structliveupdate_subsystem*h,u64*data)+{+structluo_subsystems*s=container_of(h,structluo_subsystems,+handle);++pr_info("Subsystem '%s' freeze data[%llx]\n",s->name,*data);++return0;+}++staticvoidluo_subsystem_cancel(structliveupdate_subsystem*h,u64data)+{+structluo_subsystems*s=container_of(h,structluo_subsystems,+handle);++pr_info("Subsystem '%s' canel data[%llx]\n",s->name,data);+s->preserved=false;+WARN_ON(kho_unpreserve_phys(data,PAGE_SIZE));+}++staticvoidluo_subsystem_finish(structliveupdate_subsystem*h,u64data)+{+structluo_subsystems*s=container_of(h,structluo_subsystems,+handle);++pr_info("Subsystem '%s' finish data[%llx]\n",s->name,data);+}++staticconststructliveupdate_subsystem_opsluo_selftest_subsys_ops={+.prepare=luo_subsystem_prepare,+.freeze=luo_subsystem_freeze,+.cancel=luo_subsystem_cancel,+.finish=luo_subsystem_finish,+.owner=THIS_MODULE,+};++staticintluo_subsystem_idx(char*name)+{+inti;++for(i=0;i<LUO_MAX_SUBSYSTEMS;i++){+if(luo_subsystems[i].in_use&&+!strcmp(luo_subsystems[i].name,name))+break;+}++if(i==LUO_MAX_SUBSYSTEMS){+pr_warn("Subsystem with name '%s' is not registred\n",name);++return-EINVAL;+}++returni;+}++staticvoidluo_put_and_free_subsystem(char*name)+{+inti=luo_subsystem_idx(name);++if(i<0)+return;++if(luo_subsystems[i].preserved)+kho_unpreserve_phys(__pa(luo_subsystems[i].data),PAGE_SIZE);+free_page((unsignedlong)luo_subsystems[i].data);+luo_subsystems[i].in_use=false;+luo_subsystems[i].preserved=false;+}++staticintluo_get_and_alloc_subsystem(char*name,void__user*data,+structliveupdate_subsystem**hp)+{+unsignedlongpage_addr,i;++page_addr=get_zeroed_page(GFP_KERNEL);+if(!page_addr){+pr_warn("Failed to allocate memory for subsystem data\n");+return-ENOMEM;+}++if(copy_from_user((void*)page_addr,data,PAGE_SIZE)){+free_page(page_addr);+return-EFAULT;+}++for(i=0;i<LUO_MAX_SUBSYSTEMS;i++){+if(!luo_subsystems[i].in_use)+break;+}++if(i==LUO_MAX_SUBSYSTEMS){+pr_warn("Maximum number of subsystems registered\n");+free_page(page_addr);+return-ENOMEM;+}++luo_subsystems[i].in_use=true;+luo_subsystems[i].handle.ops=&luo_selftest_subsys_ops;+luo_subsystems[i].handle.name=luo_subsystems[i].name;+strscpy(luo_subsystems[i].name,name,LUO_NAME_LENGTH);+luo_subsystems[i].data=(void*)page_addr;++*hp=&luo_subsystems[i].handle;++return0;+}++staticintluo_cmd_subsystem_unregister(void__user*argp)+{+structluo_arg_subsystemarg;+intret,i;++if(copy_from_user(&arg,argp,sizeof(arg)))+return-EFAULT;++i=luo_subsystem_idx(arg.name);+if(i<0)+returni;++ret=liveupdate_unregister_subsystem(&luo_subsystems[i].handle);+if(ret)+returnret;++luo_put_and_free_subsystem(arg.name);++return0;+}++staticintluo_cmd_subsystem_register(void__user*argp)+{+structliveupdate_subsystem*h;+structluo_arg_subsystemarg;+intret;++if(copy_from_user(&arg,argp,sizeof(arg)))+return-EFAULT;++ret=luo_get_and_alloc_subsystem(arg.name,+(void__user*)arg.data_page,&h);+if(ret)+returnret;++ret=liveupdate_register_subsystem(h);+if(ret)+luo_put_and_free_subsystem(arg.name);++returnret;+}++staticintluo_cmd_subsystem_getdata(void__user*argp)+{+structluo_arg_subsystemarg;+inti;++if(copy_from_user(&arg,argp,sizeof(arg)))+return-EFAULT;++i=luo_subsystem_idx(arg.name);+if(i<0)+returni;++if(copy_to_user(arg.data_page,luo_subsystems[i].data,+PAGE_SIZE)){+return-EFAULT;+}++return0;+}++staticintluo_ioctl_selftests(void__user*argp)+{+structliveupdate_selftestluo_st;+void__user*cmd_argp;+intret=0;++if(copy_from_user(&luo_st,argp,sizeof(luo_st)))+return-EFAULT;++cmd_argp=(void__user*)luo_st.arg;++mutex_lock(&luo_ioctl_mutex);+switch(luo_st.cmd){+caseLUO_CMD_SUBSYSTEM_REGISTER:+ret=luo_cmd_subsystem_register(cmd_argp);+break;++caseLUO_CMD_SUBSYSTEM_UNREGISTER:+ret=luo_cmd_subsystem_unregister(cmd_argp);+break;++caseLUO_CMD_SUBSYSTEM_GETDATA:+ret=luo_cmd_subsystem_getdata(cmd_argp);+break;++default:+pr_warn("ioctl: unknown self-test command nr: 0x%llx\n",+luo_st.cmd);+ret=-ENOTTY;+break;+}+mutex_unlock(&luo_ioctl_mutex);++returnret;+}++staticlongluo_selftest_ioctl(structfile*filep,unsignedintcmd,+unsignedlongarg)+{+intret=0;++if(_IOC_TYPE(cmd)!=LIVEUPDATE_IOCTL_TYPE)+return-ENOTTY;++switch(cmd){+caseLIVEUPDATE_IOCTL_FREEZE:+ret=luo_freeze();+break;++caseLIVEUPDATE_IOCTL_SELFTESTS:+ret=luo_ioctl_selftests((void__user*)arg);+break;++default:+pr_warn("ioctl: unknown command nr: 0x%x\n",_IOC_NR(cmd));+ret=-ENOTTY;+break;+}++returnret;+}++staticconststructfile_operationsluo_selftest_fops={+.open=nonseekable_open,+.unlocked_ioctl=luo_selftest_ioctl,+};++staticint__initluo_seltesttest_init(void)+{+if(!liveupdate_debugfs_root){+pr_err("liveupdate root is not set\n");+return0;+}+debugfs_create_file_unsafe("luo_selftest",0600,+liveupdate_debugfs_root,NULL,+&luo_selftest_fops);+return0;+}++late_initcall(luo_seltesttest_init);
Introduces a new set of userspace selftests for the LUO. These tests
verify the functionality LUO by using the kernel-side selftest ioctls
provided by the LUO module, primarily focusing on subsystem management
and basic LUO state transitions.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/liveupdate/.gitignore | 1 +
tools/testing/selftests/liveupdate/Makefile | 7 +
tools/testing/selftests/liveupdate/config | 6 +
.../testing/selftests/liveupdate/liveupdate.c | 406 ++++++++++++++++++
5 files changed, 421 insertions(+)
create mode 100644 tools/testing/selftests/liveupdate/.gitignore
create mode 100644 tools/testing/selftests/liveupdate/Makefile
create mode 100644 tools/testing/selftests/liveupdate/config
create mode 100644 tools/testing/selftests/liveupdate/liveupdate.c
@@ -0,0 +1,406 @@+// SPDX-License-Identifier: GPL-2.0-only++/*+*Copyright(c)2025,GoogleLLC.+*PashaTatashin<pasha.tatashin@soleen.com>+*/++#include<errno.h>+#include<fcntl.h>+#include<stdbool.h>+#include<stdio.h>+#include<stdlib.h>+#include<string.h>+#include<unistd.h>++#include<sys/ioctl.h>+#include<sys/mman.h>++#include<linux/liveupdate.h>++#include"../kselftest.h"+#include"../kselftest_harness.h"+#include"../../../../kernel/liveupdate/luo_selftests.h"++structsubsystem_info{+void*data_page;+void*verify_page;+chartest_name[LUO_NAME_LENGTH];+boolregistered;+};++FIXTURE(subsystem){+intfd;+intfd_dbg;+structsubsystem_infosi[LUO_MAX_SUBSYSTEMS];+};++FIXTURE(state){+intfd;+intfd_dbg;+};++#define LUO_DEVICE "/dev/liveupdate"+#define LUO_DBG_DEVICE "/sys/kernel/debug/liveupdate/luo_selftest"+#define LUO_SYSFS_STATE "/sys/kernel/liveupdate/state"+staticsize_tpage_size;++constchar*constluo_state_str[]={+[LIVEUPDATE_STATE_UNDEFINED]="undefined",+[LIVEUPDATE_STATE_NORMAL]="normal",+[LIVEUPDATE_STATE_PREPARED]="prepared",+[LIVEUPDATE_STATE_FROZEN]="frozen",+[LIVEUPDATE_STATE_UPDATED]="updated",+};++staticintrun_luo_selftest_cmd(intfd_dbg,__u64cmd_code,+structluo_arg_subsystem*subsys_arg)+{+structliveupdate_selftestk_arg;++k_arg.cmd=cmd_code;+k_arg.arg=(__u64)(unsignedlong)subsys_arg;++returnioctl(fd_dbg,LIVEUPDATE_IOCTL_SELFTESTS,&k_arg);+}++staticintregister_subsystem(intfd_dbg,structsubsystem_info*si)+{+structluo_arg_subsystemsubsys_arg;+intret;++memset(&subsys_arg,0,sizeof(subsys_arg));+snprintf(subsys_arg.name,LUO_NAME_LENGTH,"%s",si->test_name);+subsys_arg.data_page=si->data_page;++ret=run_luo_selftest_cmd(fd_dbg,LUO_CMD_SUBSYSTEM_REGISTER,+&subsys_arg);+if(!ret)+si->registered=true;++returnret;+}++staticintunregister_subsystem(intfd_dbg,structsubsystem_info*si)+{+structluo_arg_subsystemsubsys_arg;+intret;++memset(&subsys_arg,0,sizeof(subsys_arg));+snprintf(subsys_arg.name,LUO_NAME_LENGTH,"%s",si->test_name);++ret=run_luo_selftest_cmd(fd_dbg,LUO_CMD_SUBSYSTEM_UNREGISTER,+&subsys_arg);+if(!ret)+si->registered=false;++returnret;+}++staticintget_sysfs_state(void)+{+charbuf[64];+ssize_tlen;+intfd,i;++fd=open(LUO_SYSFS_STATE,O_RDONLY);+if(fd<0){+ksft_print_msg("Failed to open sysfs state file '%s': %s\n",+LUO_SYSFS_STATE,strerror(errno));+return-errno;+}++len=read(fd,buf,sizeof(buf)-1);+close(fd);++if(len<=0){+ksft_print_msg("Failed to read sysfs state file '%s': %s\n",+LUO_SYSFS_STATE,strerror(errno));+return-errno;+}+if(buf[len-1]=='\n')+buf[len-1]='\0';+else+buf[len]='\0';++for(i=0;i<ARRAY_SIZE(luo_state_str);i++){+if(!strcmp(buf,luo_state_str[i]))+returni;+}++return-EIO;+}++FIXTURE_SETUP(state)+{+intstate;++page_size=sysconf(_SC_PAGE_SIZE);+self->fd=open(LUO_DEVICE,O_RDWR);+if(self->fd<0)+SKIP(return,"open(%s) failed [%d]",LUO_DEVICE,errno);++self->fd_dbg=open(LUO_DBG_DEVICE,O_RDWR);+ASSERT_GE(self->fd_dbg,0);++state=get_sysfs_state();+if(state<0){+if(state==-ENOENT||state==-EACCES)+SKIP(return,"sysfs state not accessible (%d)",state);+}+}++FIXTURE_TEARDOWN(state)+{+structliveupdate_ioctl_set_eventcancel={+.size=sizeof(cancel),+.event=LIVEUPDATE_CANCEL,+};+structliveupdate_ioctl_get_stateligs={.size=sizeof(ligs)};++ioctl(self->fd,LIVEUPDATE_IOCTL_GET_STATE,&ligs);+if(ligs.state!=LIVEUPDATE_STATE_NORMAL)+ioctl(self->fd,LIVEUPDATE_IOCTL_SET_EVENT,&cancel);+close(self->fd);+}++FIXTURE_SETUP(subsystem)+{+inti;++page_size=sysconf(_SC_PAGE_SIZE);+memset(&self->si,0,sizeof(self->si));+self->fd=open(LUO_DEVICE,O_RDWR);+if(self->fd<0)+SKIP(return,"open(%s) failed [%d]",LUO_DEVICE,errno);++self->fd_dbg=open(LUO_DBG_DEVICE,O_RDWR);+ASSERT_GE(self->fd_dbg,0);++for(i=0;i<LUO_MAX_SUBSYSTEMS;i++){+snprintf(self->si[i].test_name,LUO_NAME_LENGTH,+NAME_NORMAL".%d",i);++self->si[i].data_page=mmap(NULL,page_size,+PROT_READ|PROT_WRITE,+MAP_PRIVATE|MAP_ANONYMOUS,+-1,0);+ASSERT_NE(MAP_FAILED,self->si[i].data_page);+memset(self->si[i].data_page,'A'+i,page_size);++self->si[i].verify_page=mmap(NULL,page_size,+PROT_READ|PROT_WRITE,+MAP_PRIVATE|MAP_ANONYMOUS,+-1,0);+ASSERT_NE(MAP_FAILED,self->si[i].verify_page);+memset(self->si[i].verify_page,0,page_size);+}+}++FIXTURE_TEARDOWN(subsystem)+{+structliveupdate_ioctl_set_eventcancel={+.size=sizeof(cancel),+.event=LIVEUPDATE_CANCEL,+};+enumliveupdate_statestate=LIVEUPDATE_STATE_NORMAL;+inti;++ioctl(self->fd,LIVEUPDATE_IOCTL_GET_STATE,&state);+if(state!=LIVEUPDATE_STATE_NORMAL)+ioctl(self->fd,LIVEUPDATE_IOCTL_SET_EVENT,&cancel);++for(i=0;i<LUO_MAX_SUBSYSTEMS;i++){+if(self->si[i].registered)+unregister_subsystem(self->fd_dbg,&self->si[i]);+munmap(self->si[i].data_page,page_size);+munmap(self->si[i].verify_page,page_size);+}++close(self->fd);+}++TEST_F(state,normal)+{+structliveupdate_ioctl_get_stateligs={.size=sizeof(ligs)};++ASSERT_EQ(0,ioctl(self->fd,LIVEUPDATE_IOCTL_GET_STATE,&ligs));+ASSERT_EQ(ligs.state,LIVEUPDATE_STATE_NORMAL);+}++TEST_F(state,prepared)+{+structliveupdate_ioctl_get_stateligs={.size=sizeof(ligs)};+structliveupdate_ioctl_set_eventprepare={+.size=sizeof(prepare),+.event=LIVEUPDATE_PREPARE,+};+structliveupdate_ioctl_set_eventcancel={+.size=sizeof(cancel),+.event=LIVEUPDATE_CANCEL,+};++ASSERT_EQ(0,ioctl(self->fd,LIVEUPDATE_IOCTL_SET_EVENT,&prepare));++ASSERT_EQ(0,ioctl(self->fd,LIVEUPDATE_IOCTL_GET_STATE,&ligs));+ASSERT_EQ(ligs.state,LIVEUPDATE_STATE_PREPARED);++ASSERT_EQ(0,ioctl(self->fd,LIVEUPDATE_IOCTL_SET_EVENT,&cancel));++ASSERT_EQ(0,ioctl(self->fd,LIVEUPDATE_IOCTL_GET_STATE,&ligs));+ASSERT_EQ(ligs.state,LIVEUPDATE_STATE_NORMAL);+}++TEST_F(state,sysfs_normal)+{+ASSERT_EQ(LIVEUPDATE_STATE_NORMAL,get_sysfs_state());+}++TEST_F(state,sysfs_prepared)+{+structliveupdate_ioctl_set_eventprepare={+.size=sizeof(prepare),+.event=LIVEUPDATE_PREPARE,+};+structliveupdate_ioctl_set_eventcancel={+.size=sizeof(cancel),+.event=LIVEUPDATE_CANCEL,+};++ASSERT_EQ(0,ioctl(self->fd,LIVEUPDATE_IOCTL_SET_EVENT,&prepare));+ASSERT_EQ(LIVEUPDATE_STATE_PREPARED,get_sysfs_state());++ASSERT_EQ(0,ioctl(self->fd,LIVEUPDATE_IOCTL_SET_EVENT,&cancel));+ASSERT_EQ(LIVEUPDATE_STATE_NORMAL,get_sysfs_state());+}++TEST_F(state,sysfs_frozen)+{+structliveupdate_ioctl_set_eventprepare={+.size=sizeof(prepare),+.event=LIVEUPDATE_PREPARE,+};+structliveupdate_ioctl_set_eventcancel={+.size=sizeof(cancel),+.event=LIVEUPDATE_CANCEL,+};++ASSERT_EQ(0,ioctl(self->fd,LIVEUPDATE_IOCTL_SET_EVENT,&prepare));++ASSERT_EQ(LIVEUPDATE_STATE_PREPARED,get_sysfs_state());++ASSERT_EQ(0,ioctl(self->fd_dbg,LIVEUPDATE_IOCTL_FREEZE,NULL));+ASSERT_EQ(LIVEUPDATE_STATE_FROZEN,get_sysfs_state());++ASSERT_EQ(0,ioctl(self->fd,LIVEUPDATE_IOCTL_SET_EVENT,&cancel));+ASSERT_EQ(LIVEUPDATE_STATE_NORMAL,get_sysfs_state());+}++TEST_F(subsystem,register_unregister)+{+ASSERT_EQ(0,register_subsystem(self->fd_dbg,&self->si[0]));+ASSERT_EQ(0,unregister_subsystem(self->fd_dbg,&self->si[0]));+}++TEST_F(subsystem,double_unregister)+{+ASSERT_EQ(0,register_subsystem(self->fd_dbg,&self->si[0]));+ASSERT_EQ(0,unregister_subsystem(self->fd_dbg,&self->si[0]));+EXPECT_NE(0,unregister_subsystem(self->fd_dbg,&self->si[0]));+EXPECT_TRUE(errno==EINVAL||errno==ENOENT);+}++TEST_F(subsystem,register_unregister_many)+{+inti;++for(i=0;i<LUO_MAX_SUBSYSTEMS;i++)+ASSERT_EQ(0,register_subsystem(self->fd_dbg,&self->si[i]));++for(i=0;i<LUO_MAX_SUBSYSTEMS;i++)+ASSERT_EQ(0,unregister_subsystem(self->fd_dbg,&self->si[i]));+}++TEST_F(subsystem,getdata_verify)+{+structliveupdate_ioctl_get_stateligs={.size=sizeof(ligs),.state=0};+structliveupdate_ioctl_set_eventprepare={+.size=sizeof(prepare),+.event=LIVEUPDATE_PREPARE,+};+structliveupdate_ioctl_set_eventcancel={+.size=sizeof(cancel),+.event=LIVEUPDATE_CANCEL,+};+inti;++for(i=0;i<LUO_MAX_SUBSYSTEMS;i++)+ASSERT_EQ(0,register_subsystem(self->fd_dbg,&self->si[i]));++ASSERT_EQ(0,ioctl(self->fd,LIVEUPDATE_IOCTL_SET_EVENT,&prepare));+ASSERT_EQ(0,ioctl(self->fd,LIVEUPDATE_IOCTL_GET_STATE,&ligs));+ASSERT_EQ(ligs.state,LIVEUPDATE_STATE_PREPARED);++for(i=0;i<LUO_MAX_SUBSYSTEMS;i++){+structluo_arg_subsystemsubsys_arg;++memset(&subsys_arg,0,sizeof(subsys_arg));+snprintf(subsys_arg.name,LUO_NAME_LENGTH,"%s",+self->si[i].test_name);+subsys_arg.data_page=self->si[i].verify_page;++ASSERT_EQ(0,run_luo_selftest_cmd(self->fd_dbg,+LUO_CMD_SUBSYSTEM_GETDATA,+&subsys_arg));+ASSERT_EQ(0,memcmp(self->si[i].data_page,+self->si[i].verify_page,+page_size));+}++ASSERT_EQ(0,ioctl(self->fd,LIVEUPDATE_IOCTL_SET_EVENT,&cancel));+ASSERT_EQ(0,ioctl(self->fd,LIVEUPDATE_IOCTL_GET_STATE,&ligs));+ASSERT_EQ(ligs.state,LIVEUPDATE_STATE_NORMAL);++for(i=0;i<LUO_MAX_SUBSYSTEMS;i++)+ASSERT_EQ(0,unregister_subsystem(self->fd_dbg,&self->si[i]));+}++TEST_F(subsystem,prepare_fail)+{+structliveupdate_ioctl_set_eventprepare={+.size=sizeof(prepare),+.event=LIVEUPDATE_PREPARE,+};+structliveupdate_ioctl_set_eventcancel={+.size=sizeof(cancel),+.event=LIVEUPDATE_CANCEL,+};+inti;++snprintf(self->si[LUO_MAX_SUBSYSTEMS-1].test_name,LUO_NAME_LENGTH,+NAME_PREPARE_FAIL".%d",LUO_MAX_SUBSYSTEMS-1);++for(i=0;i<LUO_MAX_SUBSYSTEMS;i++)+ASSERT_EQ(0,register_subsystem(self->fd_dbg,&self->si[i]));++ASSERT_EQ(-1,ioctl(self->fd,LIVEUPDATE_IOCTL_SET_EVENT,&prepare));++for(i=0;i<LUO_MAX_SUBSYSTEMS;i++)+ASSERT_EQ(0,unregister_subsystem(self->fd_dbg,&self->si[i]));++snprintf(self->si[LUO_MAX_SUBSYSTEMS-1].test_name,LUO_NAME_LENGTH,+NAME_NORMAL".%d",LUO_MAX_SUBSYSTEMS-1);++for(i=0;i<LUO_MAX_SUBSYSTEMS;i++)+ASSERT_EQ(0,register_subsystem(self->fd_dbg,&self->si[i]));++ASSERT_EQ(0,ioctl(self->fd,LIVEUPDATE_IOCTL_SET_EVENT,&prepare));+ASSERT_EQ(0,ioctl(self->fd_dbg,LIVEUPDATE_IOCTL_FREEZE,NULL));+ASSERT_EQ(0,ioctl(self->fd,LIVEUPDATE_IOCTL_SET_EVENT,&cancel));+ASSERT_EQ(LIVEUPDATE_STATE_NORMAL,get_sysfs_state());++for(i=0;i<LUO_MAX_SUBSYSTEMS;i++)+ASSERT_EQ(0,unregister_subsystem(self->fd_dbg,&self->si[i]));+}++TEST_HARNESS_MAIN
@@ -95,6 +95,7 @@ likely to be of interest on almost any system. cgroup-v2 cgroup-v1/index cpu-load+ liveupdate mm/index module-signing namespaces/index
@@ -137,6 +137,7 @@ Documents that don't fit elsewhere or which have yet to be categorized.:maxdepth: 1 librs+ liveupdate netlink..only:: subproject and html
Add a MAINTAINERS file entry for the new Live Update Orchestrator
introduced in previous patches.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
MAINTAINERS | 13 +++++++++++++
1 file changed, 13 insertions(+)
From: Pratyush Yadav <redacted>
shmem_inode_info::flags can have the VM flags VM_NORESERVE and
VM_LOCKED. These are used to suppress pre-accounting or to lock the
pages in the inode respectively. Using the VM flags directly makes it
difficult to add shmem-specific flags that are unrelated to VM behavior
since one would need to find a VM flag not used by shmem and re-purpose
it.
Introduce SHMEM_F_NORESERVE and SHMEM_F_LOCKED which represent the same
information, but their bits are independent of the VM flags. Callers can
still pass VM_NORESERVE to shmem_get_inode(), but it gets transformed to
the shmem-specific flag internally.
No functional changes intended.
Signed-off-by: Pratyush Yadav <redacted>
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
include/linux/shmem_fs.h | 6 ++++++
mm/shmem.c | 30 +++++++++++++++++-------------
2 files changed, 23 insertions(+), 13 deletions(-)
@@ -206,7 +206,7 @@ static inline int shmem_reacct_size(unsigned long flags,*/staticinlineintshmem_acct_blocks(unsignedlongflags,longpages){-if(!(flags&VM_NORESERVE))+if(!(flags&SHMEM_F_NORESERVE))return0;returnsecurity_vm_enough_memory_mm(current->mm,
@@ -215,7 +215,7 @@ static inline int shmem_acct_blocks(unsigned long flags, long pages)staticinlinevoidshmem_unacct_blocks(unsignedlongflags,longpages){-if(flags&VM_NORESERVE)+if(flags&SHMEM_F_NORESERVE)vm_unacct_memory(pages*VM_ACCT(PAGE_SIZE));}
From: Pratyush Yadav <redacted>
To prepare a shmem inode for live update via the Live Update
Orchestrator (LUO), its index -> folio mappings must be serialized. Once
the mappings are serialized, they cannot change since it would cause the
serialized data to become inconsistent. This can be done by pinning the
folios to avoid migration, and by making sure no folios can be added to
or removed from the inode.
While mechanisms to pin folios already exist, the only way to stop
folios being added or removed are the grow and shrink file seals. But
file seals come with their own semantics, one of which is that they
can't be removed. This doesn't work with liveupdate since it can be
cancelled or error out, which would need the seals to be removed and the
file's normal functionality to be restored.
Introduce SHMEM_F_MAPPING_FROZEN to indicate this instead. It is
internal to shmem and is not directly exposed to userspace. It functions
similar to F_SEAL_GROW | F_SEAL_SHRINK, but additionally disallows hole
punching, and can be removed.
Signed-off-by: Pratyush Yadav <redacted>
Signed-off-by: Pasha Tatashin <redacted>
---
include/linux/shmem_fs.h | 17 +++++++++++++++++
mm/shmem.c | 12 +++++++++++-
2 files changed, 28 insertions(+), 1 deletion(-)
@@ -186,6 +194,15 @@ static inline bool shmem_file(struct file *file)returnshmem_mapping(file->f_mapping);}+/* Must be called with inode lock taken exclusive. */+staticinlinevoidshmem_i_mapping_freeze(structinode*inode,boolfreeze)+{+if(freeze)+SHMEM_I(inode)->flags|=SHMEM_F_MAPPING_FROZEN;+else+SHMEM_I(inode)->flags&=~SHMEM_F_MAPPING_FROZEN;+}+/**Iffallocate(FALLOC_FL_KEEP_SIZE)hasbeenused,theremaybepages*beyondi_size'snotionofEOF,whichfallocatehascommittedtoreserving:
From: Pratyush Yadav <redacted>
shmem_inode_acct_blocks(), shmem_recalc_inode(), and
shmem_add_to_page_cache() are used by shmem_alloc_and_add_folio(). This
functionality will also be used in the future by Live Update
Orchestrator (LUO) to recreate memfd files after a live update.
Signed-off-by: Pratyush Yadav <redacted>
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
mm/internal.h | 6 ++++++
mm/shmem.c | 10 +++++-----
2 files changed, 11 insertions(+), 5 deletions(-)
From: Pratyush Yadav <redacted>
The ability to preserve a memfd allows userspace to use KHO and LUO to
transfer its memory contents to the next kernel. This is useful in many
ways. For one, it can be used with IOMMUFD as the backing store for
IOMMU page tables. Preserving IOMMUFD is essential for performing a
hypervisor live update with passthrough devices. memfd support provides
the first building block for making that possible.
For another, applications with a large amount of memory that takes time
to reconstruct, reboots to consume kernel upgrades can be very
expensive. memfd with LUO gives those applications reboot-persistent
memory that they can use to quickly save and reconstruct that state.
While memfd is backed by either hugetlbfs or shmem, currently only
support on shmem is added. To be more precise, support for anonymous
shmem files is added.
The handover to the next kernel is not transparent. All the properties
of the file are not preserved; only its memory contents, position, and
size. The recreated file gets the UID and GID of the task doing the
restore, and the task's cgroup gets charged with the memory.
After LUO is in prepared state, the file cannot grow or shrink, and all
its pages are pinned to avoid migrations and swapping. The file can
still be read from or written to.
Co-developed-by: Changyuan Lyu <redacted>
Signed-off-by: Changyuan Lyu <redacted>
Co-developed-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Signed-off-by: Pratyush Yadav <redacted>
---
MAINTAINERS | 2 +
mm/Makefile | 1 +
mm/memfd_luo.c | 507 +++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 510 insertions(+)
create mode 100644 mm/memfd_luo.c
@@ -0,0 +1,507 @@+// SPDX-License-Identifier: GPL-2.0++/*+*Copyright(c)2025,GoogleLLC.+*PashaTatashin<pasha.tatashin@soleen.com>+*ChangyuanLyu<changyuanl@google.com>+*+*Copyright(C)2025Amazon.comInc.oritsaffiliates.+*PratyushYadav<ptyadav@amazon.de>+*/++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt++#include<linux/file.h>+#include<linux/io.h>+#include<linux/libfdt.h>+#include<linux/liveupdate.h>+#include<linux/kexec_handover.h>+#include<linux/shmem_fs.h>+#include<linux/bits.h>+#include"internal.h"++staticconstcharmemfd_luo_compatible[]="memfd-v1";++#define PRESERVED_PFN_MASK GENMASK(63, 12)+#define PRESERVED_PFN_SHIFT 12+#define PRESERVED_FLAG_DIRTY BIT(0)+#define PRESERVED_FLAG_UPTODATE BIT(1)++#define PRESERVED_FOLIO_PFN(desc) (((desc) & PRESERVED_PFN_MASK) >> PRESERVED_PFN_SHIFT)+#define PRESERVED_FOLIO_FLAGS(desc) ((desc) & ~PRESERVED_PFN_MASK)+#define PRESERVED_FOLIO_MKDESC(pfn, flags) (((pfn) << PRESERVED_PFN_SHIFT) | (flags))++structmemfd_luo_preserved_folio{+/*+*Thefoliodescriptorismadeof2parts.Thebottom12bitsareused+*forstoringflags,theothersforstoringthePFN.+*/+u64foliodesc;+u64index;+};++staticintmemfd_luo_preserve_folios(structmemfd_luo_preserved_folio*pfolios,+structfolio**folios,+unsignedintnr_folios)+{+unsignedinti;+interr;++for(i=0;i<nr_folios;i++){+structmemfd_luo_preserved_folio*pfolio=&pfolios[i];+structfolio*folio=folios[i];+unsignedintflags=0;+unsignedlongpfn;++err=kho_preserve_folio(folio);+if(err)+gotoerr_unpreserve;++pfn=folio_pfn(folio);+if(folio_test_dirty(folio))+flags|=PRESERVED_FLAG_DIRTY;+if(folio_test_uptodate(folio))+flags|=PRESERVED_FLAG_UPTODATE;++pfolio->foliodesc=PRESERVED_FOLIO_MKDESC(pfn,flags);+pfolio->index=folio->index;+}++return0;++err_unpreserve:+i--;+for(;i>=0;i--)+WARN_ON_ONCE(kho_unpreserve_folio(folios[i]));+returnerr;+}++staticvoidmemfd_luo_unpreserve_folios(conststructmemfd_luo_preserved_folio*pfolios,+unsignedintnr_folios)+{+unsignedinti;++for(i=0;i<nr_folios;i++){+conststructmemfd_luo_preserved_folio*pfolio=&pfolios[i];+structfolio*folio;++if(!pfolio->foliodesc)+continue;++folio=pfn_folio(PRESERVED_FOLIO_PFN(pfolio->foliodesc));++kho_unpreserve_folio(folio);+unpin_folio(folio);+}+}++staticvoid*memfd_luo_create_fdt(unsignedlongsize)+{+unsignedintorder=get_order(size);+structfolio*fdt_folio;+interr=0;+void*fdt;++if(order>MAX_PAGE_ORDER)+returnNULL;++fdt_folio=folio_alloc(GFP_KERNEL,order);+if(!fdt_folio)+returnNULL;++fdt=folio_address(fdt_folio);++err|=fdt_create(fdt,(1<<(order+PAGE_SHIFT)));+err|=fdt_finish_reservemap(fdt);+err|=fdt_begin_node(fdt,"");+if(err)+gotofree;++returnfdt;++free:+folio_put(fdt_folio);+returnNULL;+}++staticintmemfd_luo_finish_fdt(void*fdt)+{+interr;++err=fdt_end_node(fdt);+if(err)+returnerr;++returnfdt_finish(fdt);+}++staticintmemfd_luo_prepare(structliveupdate_file_handler*handler,+structfile*file,u64*data)+{+structmemfd_luo_preserved_folio*preserved_folios;+structinode*inode=file_inode(file);+unsignedintmax_folios,nr_folios=0;+interr=0,preserved_size;+structfolio**folios;+longsize,nr_pinned;+pgoff_toffset;+void*fdt;+u64pos;++if(WARN_ON_ONCE(!shmem_file(file)))+return-EINVAL;++inode_lock(inode);+shmem_i_mapping_freeze(inode,true);++size=i_size_read(inode);+if((PAGE_ALIGN(size)/PAGE_SIZE)>UINT_MAX){+err=-E2BIG;+gotoerr_unlock;+}++/*+*Guessthenumberoffoliosbasedoninodesize.Realnumbermightend+*upbeingsmalleriftherearehigherorderfolios.+*/+max_folios=PAGE_ALIGN(size)/PAGE_SIZE;+folios=kvmalloc_array(max_folios,sizeof(*folios),GFP_KERNEL);+if(!folios){+err=-ENOMEM;+gotoerr_unfreeze;+}++/*+*Pinthefoliossotheydon'tmovearoundbehindourback.Thisalso+*ensuresnoneofthefoliosareinCMA--whichensurestheydon't+*fallinKHOscratchmemory.Italsomovesswappedoutfoliosbackto+*memory.+*+*Asideeffectofdoingthisisthatitallocatesafolioforall+*indicesinthefile.Thismightwastememoryonsparsememfds.If+*thatisreallyaprobleminthefuture,wecanhavea+*memfd_pin_folios()variantthatdoesnotallocateapageonempty+*slots.+*/+nr_pinned=memfd_pin_folios(file,0,size-1,folios,max_folios,+&offset);+if(nr_pinned<0){+err=nr_pinned;+pr_err("failed to pin folios: %d\n",err);+gotoerr_free_folios;+}+/* nr_pinned won't be more than max_folios which is also unsigned int. */+nr_folios=(unsignedint)nr_pinned;++preserved_size=sizeof(structmemfd_luo_preserved_folio)*nr_folios;+if(check_mul_overflow(sizeof(structmemfd_luo_preserved_folio),+nr_folios,&preserved_size)){+err=-E2BIG;+gotoerr_unpin;+}++/*+*Mostofthespaceshouldbetakenbypreservedfolios.Sotakeits+*size,plusapageforotherproperties.+*/+fdt=memfd_luo_create_fdt(PAGE_ALIGN(preserved_size)+PAGE_SIZE);+if(!fdt){+err=-ENOMEM;+gotoerr_unpin;+}++pos=file->f_pos;+err=fdt_property(fdt,"pos",&pos,sizeof(pos));+if(err)+gotoerr_free_fdt;++err=fdt_property(fdt,"size",&size,sizeof(size));+if(err)+gotoerr_free_fdt;++err=fdt_property_placeholder(fdt,"folios",preserved_size,+(void**)&preserved_folios);+if(err){+pr_err("Failed to reserve folios property in FDT: %s\n",+fdt_strerror(err));+err=-ENOMEM;+gotoerr_free_fdt;+}++err=memfd_luo_preserve_folios(preserved_folios,folios,nr_folios);+if(err)+gotoerr_free_fdt;++err=memfd_luo_finish_fdt(fdt);+if(err)+gotoerr_unpreserve;++err=kho_preserve_folio(virt_to_folio(fdt));+if(err)+gotoerr_unpreserve;++kvfree(folios);+inode_unlock(inode);++*data=virt_to_phys(fdt);+return0;++err_unpreserve:+memfd_luo_unpreserve_folios(preserved_folios,nr_folios);+err_free_fdt:+folio_put(virt_to_folio(fdt));+err_unpin:+unpin_folios(folios,nr_pinned);+err_free_folios:+kvfree(folios);+err_unfreeze:+shmem_i_mapping_freeze(inode,false);+err_unlock:+inode_unlock(inode);+returnerr;+}++staticintmemfd_luo_freeze(structliveupdate_file_handler*handler,+structfile*file,u64*data)+{+u64pos=file->f_pos;+void*fdt;+interr;++if(WARN_ON_ONCE(!*data))+return-EINVAL;++fdt=phys_to_virt(*data);++/*+*Theposorsizemighthavechangedsinceprepare.Everythingelse+*staysthesame.+*/+err=fdt_setprop(fdt,0,"pos",&pos,sizeof(pos));+if(err)+returnerr;++return0;+}++staticvoidmemfd_luo_cancel(structliveupdate_file_handler*handler,+structfile*file,u64data)+{+conststructmemfd_luo_preserved_folio*pfolios;+structinode*inode=file_inode(file);+structfolio*fdt_folio;+void*fdt;+intlen;++if(WARN_ON_ONCE(!data))+return;++inode_lock(inode);+shmem_i_mapping_freeze(inode,false);++fdt=phys_to_virt(data);+fdt_folio=virt_to_folio(fdt);+pfolios=fdt_getprop(fdt,0,"folios",&len);+if(pfolios)+memfd_luo_unpreserve_folios(pfolios,len/sizeof(*pfolios));++kho_unpreserve_folio(fdt_folio);+folio_put(fdt_folio);+inode_unlock(inode);+}++staticstructfolio*memfd_luo_get_fdt(u64data)+{+returnkho_restore_folio((phys_addr_t)data);+}++staticvoidmemfd_luo_finish(structliveupdate_file_handler*handler,+structfile*file,u64data,boolreclaimed)+{+conststructmemfd_luo_preserved_folio*pfolios;+structfolio*fdt_folio;+intlen;++if(reclaimed)+return;++fdt_folio=memfd_luo_get_fdt(data);++pfolios=fdt_getprop(folio_address(fdt_folio),0,"folios",&len);+if(pfolios)+memfd_luo_unpreserve_folios(pfolios,len/sizeof(*pfolios));++folio_put(fdt_folio);+}++staticintmemfd_luo_retrieve(structliveupdate_file_handler*handler,u64data,+structfile**file_p)+{+conststructmemfd_luo_preserved_folio*pfolios;+intnr_pfolios,len,ret=0,i=0;+structaddress_space*mapping;+structfolio*folio,*fdt_folio;+constu64*pos,*size;+structinode*inode;+structfile*file;+constvoid*fdt;++fdt_folio=memfd_luo_get_fdt(data);+if(!fdt_folio)+return-ENOENT;++fdt=page_to_virt(folio_page(fdt_folio,0));++pfolios=fdt_getprop(fdt,0,"folios",&len);+if(!pfolios||len%sizeof(*pfolios)){+pr_err("invalid 'folios' property\n");+ret=-EINVAL;+gotoput_fdt;+}+nr_pfolios=len/sizeof(*pfolios);++size=fdt_getprop(fdt,0,"size",&len);+if(!size||len!=sizeof(u64)){+pr_err("invalid 'size' property\n");+ret=-EINVAL;+gotoput_folios;+}++pos=fdt_getprop(fdt,0,"pos",&len);+if(!pos||len!=sizeof(u64)){+pr_err("invalid 'pos' property\n");+ret=-EINVAL;+gotoput_folios;+}++file=shmem_file_setup("",0,VM_NORESERVE);++if(IS_ERR(file)){+ret=PTR_ERR(file);+pr_err("failed to setup file: %d\n",ret);+gotoput_folios;+}++inode=file->f_inode;+mapping=inode->i_mapping;+vfs_setpos(file,*pos,MAX_LFS_FILESIZE);++for(;i<nr_pfolios;i++){+conststructmemfd_luo_preserved_folio*pfolio=&pfolios[i];+phys_addr_tphys;+u64index;+intflags;++if(!pfolio->foliodesc)+continue;++phys=PFN_PHYS(PRESERVED_FOLIO_PFN(pfolio->foliodesc));+folio=kho_restore_folio(phys);+if(!folio){+pr_err("Unable to restore folio at physical address: %llx\n",+phys);+gotoput_file;+}+index=pfolio->index;+flags=PRESERVED_FOLIO_FLAGS(pfolio->foliodesc);++/* Set up the folio for insertion. */+/*+*TODO:Shouldfindawaytounifythisand+*shmem_alloc_and_add_folio().+*/+__folio_set_locked(folio);+__folio_set_swapbacked(folio);++ret=mem_cgroup_charge(folio,NULL,mapping_gfp_mask(mapping));+if(ret){+pr_err("shmem: failed to charge folio index %d: %d\n",+i,ret);+gotounlock_folio;+}++ret=shmem_add_to_page_cache(folio,mapping,index,NULL,+mapping_gfp_mask(mapping));+if(ret){+pr_err("shmem: failed to add to page cache folio index %d: %d\n",+i,ret);+gotounlock_folio;+}++if(flags&PRESERVED_FLAG_UPTODATE)+folio_mark_uptodate(folio);+if(flags&PRESERVED_FLAG_DIRTY)+folio_mark_dirty(folio);++ret=shmem_inode_acct_blocks(inode,1);+if(ret){+pr_err("shmem: failed to account folio index %d: %d\n",+i,ret);+gotounlock_folio;+}++shmem_recalc_inode(inode,1,0);+folio_add_lru(folio);+folio_unlock(folio);+folio_put(folio);+}++inode->i_size=*size;+*file_p=file;+folio_put(fdt_folio);+return0;++unlock_folio:+folio_unlock(folio);+folio_put(folio);+put_file:+fput(file);+i++;+put_folios:+for(;i<nr_pfolios;i++){+conststructmemfd_luo_preserved_folio*pfolio=&pfolios[i];++folio=kho_restore_folio(PRESERVED_FOLIO_PFN(pfolio->foliodesc));+if(folio)+folio_put(folio);+}++put_fdt:+folio_put(fdt_folio);+returnret;+}++staticboolmemfd_luo_can_preserve(structliveupdate_file_handler*handler,+structfile*file)+{+structinode*inode=file_inode(file);++returnshmem_file(file)&&!inode->i_nlink;+}++staticconststructliveupdate_file_opsmemfd_luo_file_ops={+.prepare=memfd_luo_prepare,+.freeze=memfd_luo_freeze,+.cancel=memfd_luo_cancel,+.finish=memfd_luo_finish,+.retrieve=memfd_luo_retrieve,+.can_preserve=memfd_luo_can_preserve,+.owner=THIS_MODULE,+};++staticstructliveupdate_file_handlermemfd_luo_handler={+.ops=&memfd_luo_file_ops,+.compatible=memfd_luo_compatible,+};++staticint__initmemfd_luo_init(void)+{+interr;++err=liveupdate_register_file_handler(&memfd_luo_handler);+if(err)+pr_err("Could not register luo filesystem handler: %d\n",err);++returnerr;+}+late_initcall(memfd_luo_init);
From: Pratyush Yadav <redacted>
Add the documentation under the "Preserving file descriptors" section of
LUO's documentation. The doc describes the properties preserved,
behaviour of the file under different LUO states, serialization format,
and current limitations.
Signed-off-by: Pratyush Yadav <redacted>
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
Documentation/core-api/liveupdate.rst | 7 ++
Documentation/mm/index.rst | 1 +
Documentation/mm/memfd_preservation.rst | 138 ++++++++++++++++++++++++
MAINTAINERS | 1 +
4 files changed, 147 insertions(+)
create mode 100644 Documentation/mm/memfd_preservation.rst
@@ -18,6 +18,13 @@ LUO Preserving File Descriptors..kernel-doc:: kernel/liveupdate/luo_files.c:doc: LUO file descriptors+The following types of file descriptors can be preserved++..toctree::+:maxdepth: 1++ ../mm/memfd_preservation+ Public API ==========..kernel-doc:: include/linux/liveupdate.h
@@ -47,6 +47,7 @@ documentation, or deleted if it has served its purpose. hugetlbfs_reserv ksm memory-model+ memfd_preservation mmu_notifier multigen_lru numa
@@ -0,0 +1,138 @@+.. SPDX-License-Identifier: GPL-2.0-or-later++==========================+Memfd Preservation via LUO+==========================++Overview+========++Memory file descriptors (memfd) can be preserved over a kexec using the Live+Update Orchestrator (LUO) file preservation. This allows userspace to transfer+its memory contents to the next kernel after a kexec.++The preservation is not intended to be transparent. Only select properties of+the file are preserved. All others are reset to default. The preserved+properties are described below.++..note::+ The LUO API is not stabilized yet, so the preserved properties of a memfd are+ also not stable and are subject to backwards incompatible changes.++..note::+ Currently a memfd backed by Hugetlb is not supported. Memfds created+ with ``MFD_HUGETLB`` will be rejected.++Preserved Properties+====================++The following properties of the memfd are preserved across kexec:++File Contents+ All data stored in the file is preserved.++File Size+ The size of the file is preserved. Holes in the file are filled by allocating+ pages for them during preservation.++File Position+ The current file position is preserved, allowing applications to continue+ reading/writing from their last position.++File Status Flags+ memfds are always opened with ``O_RDWR`` and ``O_LARGEFILE``. This property is+ maintained.++Non-Preserved Properties+========================++All properties which are not preserved must be assumed to be reset to default.+This section describes some of those properties which may be more of note.++``FD_CLOEXEC`` flag+ A memfd can be created with the ``MFD_CLOEXEC`` flag that sets the+``FD_CLOEXEC`` on the file. This flag is not preserved and must be set again+ after restore via ``fcntl()``.++Seals+ File seals are not preserved. The file is unsealed on restore and if needed,+ must be sealed again via ``fcntl()``.++Behavior with LUO states+========================++This section described the behavior of the memfd in the different LUO states.++Normal Phase+ During the normal phase, the memfd can be marked for preservation using the+``LIVEUPDATE_IOCTL_FD_PRESERVE`` ioctl. The memfd acts as a regular memfd+ during this phase with no additional restrictions.++Prepared Phase+ After LUO enters ``LIVEUPDATE_STATE_PREPARED``, the memfd is serialized and+ prepared for the next kernel. During this phase, the below things happen:++- All the folios are pinned. If some folios reside in ``ZONE_MIGRATE``, they+ are migrated out. This ensures none of the preserved folios land in KHO+ scratch area.+- Pages in swap are swapped in. Currently, there is no way to pass pages in+ swap over KHO, so all swapped out pages are swapped back in and pinned.+- The memfd goes into "frozen mapping" mode. The file can no longer grow or+ shrink, or punch holes. This ensures the serialized mappings stay in sync.+ The file can still be read from or written to or mmap-ed.++Freeze Phase+ Updates the current file position in the serialized data to capture any+ changes that occurred between prepare and freeze phases. After this, the FD is+ not allowed to be accessed.++Restoration Phase+ After being restored, the memfd is functional as normal with the properties+ listed above restored.++Cancellation+ If the liveupdate is canceled after going into prepared phase, the memfd+ functions like in normal phase.++Serialization format+====================++The state is serialized in an FDT with the following structure::++ /dts-v1/;++ / {+ compatible = "memfd-v1";+ pos = <current_file_position>;+ size = <file_size_in_bytes>;+ folios = <array_of_preserved_folio_descriptors>;+ };++Each folio descriptor contains:++- PFN + flags (8 bytes)++- Physical frame number (PFN) of the preserved folio (bits 63:12).+- Folio flags (bits 11:0):++-``PRESERVED_FLAG_DIRTY`` (bit 0)+-``PRESERVED_FLAG_UPTODATE`` (bit 1)++- Folio index within the file (8 bytes).++Limitations+===========++The current implementation has the following limitations:++Size+ Currently the size of the file is limited by the size of the FDT. The FDT can+ be at of most ``MAX_PAGE_ORDER`` order. By default this is 4 MiB with 4K+ pages. Each page in the file is tracked using 16 bytes. This limits the+ maximum size of the file to 1 GiB.++See Also+========++-:doc:`Live Update Orchestrator </admin-guide/liveupdate>`+-:doc:`/core-api/kho/concepts`
Hi Pasha,
On Thu, Aug 07 2025, Pasha Tatashin wrote:
quoted hunk
Lockdep shows the following warning:
INFO: trying to register non-static key.
The code is fine but needs lockdep annotation, or maybe
you didn't initialize this object before use?
turning off the locking correctness validator.
[<ffffffff810133a6>] dump_stack_lvl+0x66/0xa0
[<ffffffff8136012c>] assign_lock_key+0x10c/0x120
[<ffffffff81358bb4>] register_lock_class+0xf4/0x2f0
[<ffffffff813597ff>] __lock_acquire+0x7f/0x2c40
[<ffffffff81360cb0>] ? __pfx_hlock_conflict+0x10/0x10
[<ffffffff811707be>] ? native_flush_tlb_global+0x8e/0xa0
[<ffffffff8117096e>] ? __flush_tlb_all+0x4e/0xa0
[<ffffffff81172fc2>] ? __kernel_map_pages+0x112/0x140
[<ffffffff813ec327>] ? xa_load_or_alloc+0x67/0xe0
[<ffffffff81359556>] lock_acquire+0xe6/0x280
[<ffffffff813ec327>] ? xa_load_or_alloc+0x67/0xe0
[<ffffffff8100b9e0>] _raw_spin_lock+0x30/0x40
[<ffffffff813ec327>] ? xa_load_or_alloc+0x67/0xe0
[<ffffffff813ec327>] xa_load_or_alloc+0x67/0xe0
[<ffffffff813eb4c0>] kho_preserve_folio+0x90/0x100
[<ffffffff813ebb7f>] __kho_finalize+0xcf/0x400
[<ffffffff813ebef4>] kho_finalize+0x34/0x70
This is becase xa has its own lock, that is not initialized in
xa_load_or_alloc.
Modifiy __kho_preserve_order(), to properly call
xa_init(&new_physxa->phys_bits);
Fixes: fc33e4b44b27 ("kexec: enable KHO support for memory preservation")
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
kernel/kexec_handover.c | 29 +++++++++++++++++++++++++----
1 file changed, 25 insertions(+), 4 deletions(-)
@@ -144,14 +144,35 @@ static int __kho_preserve_order(struct kho_mem_track *track, unsigned long pfn,unsignedintorder){structkho_mem_phys_bits*bits;-structkho_mem_phys*physxa;+structkho_mem_phys*physxa,*new_physxa;constunsignedlongpfn_high=pfn>>order;might_sleep();-physxa=xa_load_or_alloc(&track->orders,order,sizeof(*physxa));-if(IS_ERR(physxa))-returnPTR_ERR(physxa);+physxa=xa_load(&track->orders,order);+if(!physxa){+new_physxa=kzalloc(sizeof(*physxa),GFP_KERNEL);+if(!new_physxa)+return-ENOMEM;++xa_init(&new_physxa->phys_bits);+physxa=xa_cmpxchg(&track->orders,order,NULL,new_physxa,+GFP_KERNEL);+if(xa_is_err(physxa)){+interr=xa_err(physxa);++xa_destroy(&new_physxa->phys_bits);+kfree(new_physxa);++returnerr;+}+if(physxa){+xa_destroy(&new_physxa->phys_bits);+kfree(new_physxa);+}else{+physxa=new_physxa;+}
I suppose this could be simplified a bit to:
err = xa_err(physxa);
if (err || physxa) {
xa_destroy(&new_physxa->phys_bits);
kfree(new_physxa);
if (err)
return err;
} else {
physxa = new_physxa;
}
No strong preference though, so fine either way. Up to you.
Reviewed-by: Pratyush Yadav <pratyush@kernel.org>
KHO uses struct pages for the preserved memory early in boot, however,
with deferred struct page initialization, only a small portion of
memory has properly initialized struct pages.
This problem was detected where vmemmap is poisoned, and illegal flag
combinations are detected.
Don't allow them to be enabled together, and later we will have to
teach KHO to work properly with deferred struct page init kernel
feature.
Fixes: 990a950fe8fd ("kexec: add config option for KHO")
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Nit: Drop the blank line before fixes. git interpret-trailers doesn't
seem to recognize the fixes otherwise, so this may break some tooling.
Try it yourself:
$ git interpret-trailers --parse commit_message.txt
Other than this,
Acked-by: Pratyush Yadav <pratyush@kernel.org>
During boot scratch area is allocated based on command line
parameters or auto calculated. However, scratch area may fail
to allocate, and in that case KHO is disabled. Currently,
no warning is printed that KHO is disabled, which makes it
confusing for the end user to figure out why KHO is not
available. Add the missing warning message.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
@@ -144,14 +144,35 @@ static int __kho_preserve_order(struct kho_mem_track *track, unsigned long pfn, unsigned int order) { struct kho_mem_phys_bits *bits;- struct kho_mem_phys *physxa;+ struct kho_mem_phys *physxa, *new_physxa; const unsigned long pfn_high = pfn >> order; might_sleep();- physxa = xa_load_or_alloc(&track->orders, order, sizeof(*physxa));- if (IS_ERR(physxa))- return PTR_ERR(physxa);+ physxa = xa_load(&track->orders, order);+ if (!physxa) {+ new_physxa = kzalloc(sizeof(*physxa), GFP_KERNEL);+ if (!new_physxa)+ return -ENOMEM;++ xa_init(&new_physxa->phys_bits);+ physxa = xa_cmpxchg(&track->orders, order, NULL, new_physxa,+ GFP_KERNEL);+ if (xa_is_err(physxa)) {+ int err = xa_err(physxa);++ xa_destroy(&new_physxa->phys_bits);+ kfree(new_physxa);++ return err;+ }+ if (physxa) {+ xa_destroy(&new_physxa->phys_bits);+ kfree(new_physxa);+ } else {+ physxa = new_physxa;+ }
I suppose this could be simplified a bit to:
err = xa_err(physxa);
if (err || physxa) {
xa_destroy(&new_physxa->phys_bits);
kfree(new_physxa);
if (err)
return err;
} else {
physxa = new_physxa;
}
My email client completely messed the whitespace up so this is a bit
unreadable. Here is what I meant:
err = xa_err(physxa);
if (err || physxa) {
xa_destroy(&new_physxa->phys_bits);
kfree(new_physxa);
if (err)
return err;
} else {
physxa = new_physxa;
}
[...]
--
Regards,
Pratyush Yadav
From: David Hildenbrand <hidden> Date: 2025-08-08 12:07:14
On 07.08.25 03:44, Pasha Tatashin wrote:
This series introduces the LUO, a kernel subsystem designed to
facilitate live kernel updates with minimal downtime,
particularly in cloud delplyoments aiming to update without fully
disrupting running virtual machines.
This series builds upon KHO framework by adding programmatic
control over KHO's lifecycle and leveraging KHO for persisting LUO's
own metadata across the kexec boundary. The git branch for this series
can be found at:
https://github.com/googleprodkernel/linux-liveupdate/tree/luo/v3
Changelog from v2:
- Addressed comments from Mike Rapoport and Jason Gunthorpe
- Only one user agent (LiveupdateD) can open /dev/liveupdate
- Release all preserved resources if /dev/liveupdate closes
before reboot.
- With the above changes, sessions are not needed, and should be
maintained by the user-agent itself, so removed support for
sessions.
- Added support for changing per-FD state (i.e. some FDs can be
prepared or finished before the global transition.
- All IOCTLs now follow iommufd/fwctl extendable design.
- Replaced locks with guards
- Added a callback for registered subsystems to be notified
during boot: ops->boot().
- Removed args from callbacks, instead use container_of() to
carry context specific data (see luo_selftests.c for example).
- removed patches for luolib, they are going to be introduced in
a separate repository.
What is Live Update?
Live Update is a kexec based reboot process where selected kernel
resources (memory, file descriptors, and eventually devices) are kept
operational or their state preserved across a kernel transition. For
certain resources, DMA and interrupt activity might continue with
minimal interruption during the kernel reboot.
LUO provides a framework for coordinating live updates. It features:
State Machine: Manages the live update process through states:
NORMAL, PREPARED, FROZEN, UPDATED.
KHO Integration:
LUO programmatically drives KHO's finalization and abort sequences.
KHO's debugfs interface is now optional configured via
CONFIG_KEXEC_HANDOVER_DEBUG.
LUO preserves its own metadata via KHO's kho_add_subtree and
kho_preserve_phys() mechanisms.
Subsystem Participation: A callback API liveupdate_register_subsystem()
allows kernel subsystems (e.g., KVM, IOMMU, VFIO, PCI) to register
handlers for LUO events (PREPARE, FREEZE, FINISH, CANCEL) and persist a
u64 payload via the LUO FDT.
File Descriptor Preservation: Infrastructure
liveupdate_register_filesystem, luo_register_file, luo_retrieve_file to
allow specific types of file descriptors (e.g., memfd, vfio) to be
preserved and restored.
Handlers for specific file types can be registered to manage their
preservation and restoration, storing a u64 payload in the LUO FDT.
User-space Interface:
ioctl (/dev/liveupdate): The primary control interface for
triggering LUO state transitions (prepare, freeze, finish, cancel)
and managing the preservation/restoration of file descriptors.
Access requires CAP_SYS_ADMIN.
sysfs (/sys/kernel/liveupdate/state): A read-only interface for
monitoring the current LUO state. This allows userspace services to
track progress and coordinate actions.
Selftests: Includes kernel-side hooks and userspace selftests to
verify core LUO functionality, particularly subsystem registration and
basic state transitions.
LUO State Machine and Events:
NORMAL: Default operational state.
PREPARED: Initial preparation complete after LIVEUPDATE_PREPARE
event. Subsystems have saved initial state.
FROZEN: Final "blackout window" state after LIVEUPDATE_FREEZE
event, just before kexec. Workloads must be suspended.
UPDATED: Next kernel has booted via live update. Awaiting restoration
and LIVEUPDATE_FINISH.
Events:
LIVEUPDATE_PREPARE: Prepare for reboot, serialize state.
LIVEUPDATE_FREEZE: Final opportunity to save state before kexec.
LIVEUPDATE_FINISH: Post-reboot cleanup in the next kernel.
LIVEUPDATE_CANCEL: Abort prepare or freeze, revert changes.
v2: https://lore.kernel.org/all/20250723144649.1696299-1-pasha.tatashin@soleen.com
v1: https://lore.kernel.org/all/20250625231838.1897085-1-pasha.tatashin@soleen.com
RFC v2: https://lore.kernel.org/all/20250515182322.117840-1-pasha.tatashin@soleen.com
RFC v1: https://lore.kernel.org/all/20250320024011.2995837-1-pasha.tatashin@soleen.com
Changyuan Lyu (1):
kho: add interfaces to unpreserve folios and physical memory ranges
Mike Rapoport (Microsoft) (1):
kho: drop notifiers
Pasha Tatashin (23):
kho: init new_physxa->phys_bits to fix lockdep
kho: mm: Don't allow deferred struct page with KHO
kho: warn if KHO is disabled due to an error
kho: allow to drive kho from within kernel
kho: make debugfs interface optional
kho: don't unpreserve memory during abort
liveupdate: kho: move to kernel/liveupdate
liveupdate: luo_core: luo_ioctl: Live Update Orchestrator
liveupdate: luo_core: integrate with KHO
liveupdate: luo_subsystems: add subsystem registration
liveupdate: luo_subsystems: implement subsystem callbacks
liveupdate: luo_files: add infrastructure for FDs
liveupdate: luo_files: implement file systems callbacks
liveupdate: luo_ioctl: add userpsace interface
liveupdate: luo_files: luo_ioctl: Unregister all FDs on device close
liveupdate: luo_files: luo_ioctl: Add ioctls for per-file state
management
liveupdate: luo_sysfs: add sysfs state monitoring
reboot: call liveupdate_reboot() before kexec
kho: move kho debugfs directory to liveupdate
liveupdate: add selftests for subsystems un/registration
selftests/liveupdate: add subsystem/state tests
docs: add luo documentation
MAINTAINERS: add liveupdate entry
Pratyush Yadav (5):
mm: shmem: use SHMEM_F_* flags instead of VM_* flags
mm: shmem: allow freezing inode mapping
mm: shmem: export some functions to internal.h
luo: allow preserving memfd
docs: add documentation for memfd preservation via LUO
It's not clear from the description why these mm shmem changes are
buried in this patch set. It's not even described above in the patch
description.
I suggest sending that part out separately, so Hugh actually spots this.
(is he even CC'ed?)
--
Cheers,
David / dhildenb
This series introduces the LUO, a kernel subsystem designed to
facilitate live kernel updates with minimal downtime,
particularly in cloud delplyoments aiming to update without fully
disrupting running virtual machines.
This series builds upon KHO framework by adding programmatic
control over KHO's lifecycle and leveraging KHO for persisting LUO's
own metadata across the kexec boundary. The git branch for this series
can be found at:
https://github.com/googleprodkernel/linux-liveupdate/tree/luo/v3
Changelog from v2:
- Addressed comments from Mike Rapoport and Jason Gunthorpe
- Only one user agent (LiveupdateD) can open /dev/liveupdate
- Release all preserved resources if /dev/liveupdate closes
before reboot.
- With the above changes, sessions are not needed, and should be
maintained by the user-agent itself, so removed support for
sessions.
- Added support for changing per-FD state (i.e. some FDs can be
prepared or finished before the global transition.
- All IOCTLs now follow iommufd/fwctl extendable design.
- Replaced locks with guards
- Added a callback for registered subsystems to be notified
during boot: ops->boot().
- Removed args from callbacks, instead use container_of() to
carry context specific data (see luo_selftests.c for example).
- removed patches for luolib, they are going to be introduced in
a separate repository.
What is Live Update?
Live Update is a kexec based reboot process where selected kernel
resources (memory, file descriptors, and eventually devices) are kept
operational or their state preserved across a kernel transition. For
certain resources, DMA and interrupt activity might continue with
minimal interruption during the kernel reboot.
LUO provides a framework for coordinating live updates. It features:
State Machine: Manages the live update process through states:
NORMAL, PREPARED, FROZEN, UPDATED.
KHO Integration:
LUO programmatically drives KHO's finalization and abort sequences.
KHO's debugfs interface is now optional configured via
CONFIG_KEXEC_HANDOVER_DEBUG.
LUO preserves its own metadata via KHO's kho_add_subtree and
kho_preserve_phys() mechanisms.
Subsystem Participation: A callback API liveupdate_register_subsystem()
allows kernel subsystems (e.g., KVM, IOMMU, VFIO, PCI) to register
handlers for LUO events (PREPARE, FREEZE, FINISH, CANCEL) and persist a
u64 payload via the LUO FDT.
File Descriptor Preservation: Infrastructure
liveupdate_register_filesystem, luo_register_file, luo_retrieve_file to
allow specific types of file descriptors (e.g., memfd, vfio) to be
preserved and restored.
Handlers for specific file types can be registered to manage their
preservation and restoration, storing a u64 payload in the LUO FDT.
User-space Interface:
ioctl (/dev/liveupdate): The primary control interface for
triggering LUO state transitions (prepare, freeze, finish, cancel)
and managing the preservation/restoration of file descriptors.
Access requires CAP_SYS_ADMIN.
sysfs (/sys/kernel/liveupdate/state): A read-only interface for
monitoring the current LUO state. This allows userspace services to
track progress and coordinate actions.
Selftests: Includes kernel-side hooks and userspace selftests to
verify core LUO functionality, particularly subsystem registration and
basic state transitions.
LUO State Machine and Events:
NORMAL: Default operational state.
PREPARED: Initial preparation complete after LIVEUPDATE_PREPARE
event. Subsystems have saved initial state.
FROZEN: Final "blackout window" state after LIVEUPDATE_FREEZE
event, just before kexec. Workloads must be suspended.
UPDATED: Next kernel has booted via live update. Awaiting restoration
and LIVEUPDATE_FINISH.
Events:
LIVEUPDATE_PREPARE: Prepare for reboot, serialize state.
LIVEUPDATE_FREEZE: Final opportunity to save state before kexec.
LIVEUPDATE_FINISH: Post-reboot cleanup in the next kernel.
LIVEUPDATE_CANCEL: Abort prepare or freeze, revert changes.
v2:
https://lore.kernel.org/all/20250723144649.1696299-1-pasha.tatashin@soleen.com
v1: https://lore.kernel.org/all/20250625231838.1897085-1-pasha.tatashin@soleen.com
RFC v2: https://lore.kernel.org/all/20250515182322.117840-1-pasha.tatashin@soleen.com
RFC v1: https://lore.kernel.org/all/20250320024011.2995837-1-pasha.tatashin@soleen.com
Changyuan Lyu (1):
kho: add interfaces to unpreserve folios and physical memory ranges
Mike Rapoport (Microsoft) (1):
kho: drop notifiers
Pasha Tatashin (23):
kho: init new_physxa->phys_bits to fix lockdep
kho: mm: Don't allow deferred struct page with KHO
kho: warn if KHO is disabled due to an error
kho: allow to drive kho from within kernel
kho: make debugfs interface optional
kho: don't unpreserve memory during abort
liveupdate: kho: move to kernel/liveupdate
liveupdate: luo_core: luo_ioctl: Live Update Orchestrator
liveupdate: luo_core: integrate with KHO
liveupdate: luo_subsystems: add subsystem registration
liveupdate: luo_subsystems: implement subsystem callbacks
liveupdate: luo_files: add infrastructure for FDs
liveupdate: luo_files: implement file systems callbacks
liveupdate: luo_ioctl: add userpsace interface
liveupdate: luo_files: luo_ioctl: Unregister all FDs on device close
liveupdate: luo_files: luo_ioctl: Add ioctls for per-file state
management
liveupdate: luo_sysfs: add sysfs state monitoring
reboot: call liveupdate_reboot() before kexec
kho: move kho debugfs directory to liveupdate
liveupdate: add selftests for subsystems un/registration
selftests/liveupdate: add subsystem/state tests
docs: add luo documentation
MAINTAINERS: add liveupdate entry
Pratyush Yadav (5):
mm: shmem: use SHMEM_F_* flags instead of VM_* flags
mm: shmem: allow freezing inode mapping
mm: shmem: export some functions to internal.h
luo: allow preserving memfd
docs: add documentation for memfd preservation via LUO
It's not clear from the description why these mm shmem changes are buried in
this patch set. It's not even described above in the patch description.
Patches 26-30 describe the shmem changes in more detail, but you're
right, it should be mentioned in the cover as well.
The idea is, LUO is used to preserve kernel resources across kexec. One
of the most fundamental resources the kernel has is memory. Since LUO
does preservation based on file descriptors, memfd is the way to attach
a FD to memory. So we went with memfd as the first user of LUO. memfd
can be backed by shmem or hugetlb, but currently only shmem is
supported. We do plan to support hugetlb as well in the future.
The idea is to keep the serialization/live update logic out of the way
of the main subsystem. So we decided to keep the logic out in a separate
file.
I suggest sending that part out separately, so Hugh actually spots this.
(is he even CC'ed?)
On Fri, Aug 8, 2025 at 12:07 PM David Hildenbrand [off-list ref] wrote:
On 07.08.25 03:44, Pasha Tatashin wrote:
quoted
This series introduces the LUO, a kernel subsystem designed to
facilitate live kernel updates with minimal downtime,
particularly in cloud delplyoments aiming to update without fully
disrupting running virtual machines.
This series builds upon KHO framework by adding programmatic
control over KHO's lifecycle and leveraging KHO for persisting LUO's
own metadata across the kexec boundary. The git branch for this series
can be found at:
https://github.com/googleprodkernel/linux-liveupdate/tree/luo/v3
Changelog from v2:
- Addressed comments from Mike Rapoport and Jason Gunthorpe
- Only one user agent (LiveupdateD) can open /dev/liveupdate
- Release all preserved resources if /dev/liveupdate closes
before reboot.
- With the above changes, sessions are not needed, and should be
maintained by the user-agent itself, so removed support for
sessions.
- Added support for changing per-FD state (i.e. some FDs can be
prepared or finished before the global transition.
- All IOCTLs now follow iommufd/fwctl extendable design.
- Replaced locks with guards
- Added a callback for registered subsystems to be notified
during boot: ops->boot().
- Removed args from callbacks, instead use container_of() to
carry context specific data (see luo_selftests.c for example).
- removed patches for luolib, they are going to be introduced in
a separate repository.
What is Live Update?
Live Update is a kexec based reboot process where selected kernel
resources (memory, file descriptors, and eventually devices) are kept
operational or their state preserved across a kernel transition. For
certain resources, DMA and interrupt activity might continue with
minimal interruption during the kernel reboot.
LUO provides a framework for coordinating live updates. It features:
State Machine: Manages the live update process through states:
NORMAL, PREPARED, FROZEN, UPDATED.
KHO Integration:
LUO programmatically drives KHO's finalization and abort sequences.
KHO's debugfs interface is now optional configured via
CONFIG_KEXEC_HANDOVER_DEBUG.
LUO preserves its own metadata via KHO's kho_add_subtree and
kho_preserve_phys() mechanisms.
Subsystem Participation: A callback API liveupdate_register_subsystem()
allows kernel subsystems (e.g., KVM, IOMMU, VFIO, PCI) to register
handlers for LUO events (PREPARE, FREEZE, FINISH, CANCEL) and persist a
u64 payload via the LUO FDT.
File Descriptor Preservation: Infrastructure
liveupdate_register_filesystem, luo_register_file, luo_retrieve_file to
allow specific types of file descriptors (e.g., memfd, vfio) to be
preserved and restored.
Handlers for specific file types can be registered to manage their
preservation and restoration, storing a u64 payload in the LUO FDT.
User-space Interface:
ioctl (/dev/liveupdate): The primary control interface for
triggering LUO state transitions (prepare, freeze, finish, cancel)
and managing the preservation/restoration of file descriptors.
Access requires CAP_SYS_ADMIN.
sysfs (/sys/kernel/liveupdate/state): A read-only interface for
monitoring the current LUO state. This allows userspace services to
track progress and coordinate actions.
Selftests: Includes kernel-side hooks and userspace selftests to
verify core LUO functionality, particularly subsystem registration and
basic state transitions.
LUO State Machine and Events:
NORMAL: Default operational state.
PREPARED: Initial preparation complete after LIVEUPDATE_PREPARE
event. Subsystems have saved initial state.
FROZEN: Final "blackout window" state after LIVEUPDATE_FREEZE
event, just before kexec. Workloads must be suspended.
UPDATED: Next kernel has booted via live update. Awaiting restoration
and LIVEUPDATE_FINISH.
Events:
LIVEUPDATE_PREPARE: Prepare for reboot, serialize state.
LIVEUPDATE_FREEZE: Final opportunity to save state before kexec.
LIVEUPDATE_FINISH: Post-reboot cleanup in the next kernel.
LIVEUPDATE_CANCEL: Abort prepare or freeze, revert changes.
v2: https://lore.kernel.org/all/20250723144649.1696299-1-pasha.tatashin@soleen.com
v1: https://lore.kernel.org/all/20250625231838.1897085-1-pasha.tatashin@soleen.com
RFC v2: https://lore.kernel.org/all/20250515182322.117840-1-pasha.tatashin@soleen.com
RFC v1: https://lore.kernel.org/all/20250320024011.2995837-1-pasha.tatashin@soleen.com
Changyuan Lyu (1):
kho: add interfaces to unpreserve folios and physical memory ranges
Mike Rapoport (Microsoft) (1):
kho: drop notifiers
Pasha Tatashin (23):
kho: init new_physxa->phys_bits to fix lockdep
kho: mm: Don't allow deferred struct page with KHO
kho: warn if KHO is disabled due to an error
kho: allow to drive kho from within kernel
kho: make debugfs interface optional
kho: don't unpreserve memory during abort
liveupdate: kho: move to kernel/liveupdate
liveupdate: luo_core: luo_ioctl: Live Update Orchestrator
liveupdate: luo_core: integrate with KHO
liveupdate: luo_subsystems: add subsystem registration
liveupdate: luo_subsystems: implement subsystem callbacks
liveupdate: luo_files: add infrastructure for FDs
liveupdate: luo_files: implement file systems callbacks
liveupdate: luo_ioctl: add userpsace interface
liveupdate: luo_files: luo_ioctl: Unregister all FDs on device close
liveupdate: luo_files: luo_ioctl: Add ioctls for per-file state
management
liveupdate: luo_sysfs: add sysfs state monitoring
reboot: call liveupdate_reboot() before kexec
kho: move kho debugfs directory to liveupdate
liveupdate: add selftests for subsystems un/registration
selftests/liveupdate: add subsystem/state tests
docs: add luo documentation
MAINTAINERS: add liveupdate entry
Pratyush Yadav (5):
mm: shmem: use SHMEM_F_* flags instead of VM_* flags
mm: shmem: allow freezing inode mapping
mm: shmem: export some functions to internal.h
luo: allow preserving memfd
docs: add documentation for memfd preservation via LUO
It's not clear from the description why these mm shmem changes are
buried in this patch set. It's not even described above in the patch
description.
Hi David,
Yes, I should update the cover letter to include memfd preservation work.
I suggest sending that part out separately, so Hugh actually spots this.
(is he even CC'ed?)
+cc hughd@google.com
While MM list is CCed, you are right, I have not specifically CCed
shmem maintainers. This will be fixed in the next revision.
Thank you,
Pasha
On Fri, Aug 8, 2025 at 11:52 AM Pratyush Yadav [off-list ref] wrote:
On Fri, Aug 08 2025, Pratyush Yadav wrote:
[...]
quoted
quoted
@@ -144,14 +144,35 @@ static int __kho_preserve_order(struct kho_mem_track *track, unsigned long pfn, unsigned int order) { struct kho_mem_phys_bits *bits;- struct kho_mem_phys *physxa;+ struct kho_mem_phys *physxa, *new_physxa; const unsigned long pfn_high = pfn >> order; might_sleep();- physxa = xa_load_or_alloc(&track->orders, order, sizeof(*physxa));- if (IS_ERR(physxa))- return PTR_ERR(physxa);+ physxa = xa_load(&track->orders, order);+ if (!physxa) {+ new_physxa = kzalloc(sizeof(*physxa), GFP_KERNEL);+ if (!new_physxa)+ return -ENOMEM;++ xa_init(&new_physxa->phys_bits);+ physxa = xa_cmpxchg(&track->orders, order, NULL, new_physxa,+ GFP_KERNEL);+ if (xa_is_err(physxa)) {+ int err = xa_err(physxa);++ xa_destroy(&new_physxa->phys_bits);+ kfree(new_physxa);++ return err;+ }+ if (physxa) {+ xa_destroy(&new_physxa->phys_bits);+ kfree(new_physxa);+ } else {+ physxa = new_physxa;+ }
I suppose this could be simplified a bit to:
err = xa_err(physxa);
if (err || physxa) {
xa_destroy(&new_physxa->phys_bits);
kfree(new_physxa);
if (err)
return err;
} else {
physxa = new_physxa;
}
My email client completely messed the whitespace up so this is a bit
unreadable. Here is what I meant:
err = xa_err(physxa);
if (err || physxa) {
xa_destroy(&new_physxa->phys_bits);
kfree(new_physxa);
if (err)
return err;
} else {
physxa = new_physxa;
}
[...]
Thanks Pratyush, I will make this simplification change if Andrew does
not take this patch in before the next revision.
Pasha
On Fri, Aug 8, 2025 at 11:47 AM Pratyush Yadav [off-list ref] wrote:
On Thu, Aug 07 2025, Pasha Tatashin wrote:
quoted
KHO uses struct pages for the preserved memory early in boot, however,
with deferred struct page initialization, only a small portion of
memory has properly initialized struct pages.
This problem was detected where vmemmap is poisoned, and illegal flag
combinations are detected.
Don't allow them to be enabled together, and later we will have to
teach KHO to work properly with deferred struct page init kernel
feature.
Fixes: 990a950fe8fd ("kexec: add config option for KHO")
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Nit: Drop the blank line before fixes. git interpret-trailers doesn't
Makes sense.
seem to recognize the fixes otherwise, so this may break some tooling.
Try it yourself:
$ git interpret-trailers --parse commit_message.txt
Other than this,
Acked-by: Pratyush Yadav <pratyush@kernel.org>
From: Andrew Morton <akpm@linux-foundation.org> Date: 2025-08-08 19:06:19
On Fri, 8 Aug 2025 14:00:08 +0000 Pasha Tatashin [off-list ref] wrote:
quoted
quoted
I suppose this could be simplified a bit to:
err = xa_err(physxa);
if (err || physxa) {
xa_destroy(&new_physxa->phys_bits);
kfree(new_physxa);
if (err)
return err;
} else {
physxa = new_physxa;
}
My email client completely messed the whitespace up so this is a bit
unreadable. Here is what I meant:
err = xa_err(physxa);
if (err || physxa) {
xa_destroy(&new_physxa->phys_bits);
kfree(new_physxa);
if (err)
return err;
} else {
physxa = new_physxa;
}
[...]
Thanks Pratyush, I will make this simplification change if Andrew does
not take this patch in before the next revision.
Yes please on the simplification - the original has an irritating
amount of kinda duplication of things from other places. Perhaps a bit
of a redo of these functions would clean things up. But later.
Can we please have this as a standalone hotfix patch with a cc:stable?
As Pratyush helpfully suggested in
https://lkml.kernel.org/r/mafs0sei2aw80.fsf@kernel.org.
Thanks.
Thanks Pratyush, I will make this simplification change if Andrew does
not take this patch in before the next revision.
Yes please on the simplification - the original has an irritating
amount of kinda duplication of things from other places. Perhaps a bit
of a redo of these functions would clean things up. But later.
Can we please have this as a standalone hotfix patch with a cc:stable?
As Pratyush helpfully suggested in
https://lkml.kernel.org/r/mafs0sei2aw80.fsf@kernel.org.
I think we should take the first three patches as hotfixes.
Let me send them as a separate series in the next 15 minutes.
Pasha
On Fri, Aug 8, 2025 at 7:51 PM Pasha Tatashin [off-list ref] wrote:
quoted
quoted
Thanks Pratyush, I will make this simplification change if Andrew does
not take this patch in before the next revision.
Yes please on the simplification - the original has an irritating
amount of kinda duplication of things from other places. Perhaps a bit
of a redo of these functions would clean things up. But later.
Can we please have this as a standalone hotfix patch with a cc:stable?
This one is only check for shmem_file, whereas in
memfd_luo_can_preserve() there is check for inode->i_nlink also. Is that
not needed here?
+
+ inode_lock(inode);
+ shmem_i_mapping_freeze(inode, true);
+
+ size = i_size_read(inode);
+ if ((PAGE_ALIGN(size) / PAGE_SIZE) > UINT_MAX) {
+ err = -E2BIG;
+ goto err_unlock;
+ }
+
+ /*
+ * Guess the number of folios based on inode size. Real number might end
+ * up being smaller if there are higher order folios.
+ */
+ max_folios = PAGE_ALIGN(size) / PAGE_SIZE;
+ folios = kvmalloc_array(max_folios, sizeof(*folios), GFP_KERNEL);
__GFP_ZERO?
+static int memfd_luo_freeze(struct liveupdate_file_handler *handler,
+ struct file *file, u64 *data)
+{
+ u64 pos = file->f_pos;
+ void *fdt;
+ int err;
+
+ if (WARN_ON_ONCE(!*data))
+ return -EINVAL;
+
+ fdt = phys_to_virt(*data);
+
+ /*
+ * The pos or size might have changed since prepare. Everything else
+ * stays the same.
+ */
+ err = fdt_setprop(fdt, 0, "pos", &pos, sizeof(pos));
+ if (err)
+ return err;
Comment is talking about pos and size but code is only updating pos.
On Tue, Aug 12, 2025 at 11:34:37PM -0700, Vipin Sharma wrote:
On 2025-08-07 01:44:35, Pasha Tatashin wrote:
quoted
From: Pratyush Yadav <redacted>
+static void memfd_luo_unpreserve_folios(const struct memfd_luo_preserved_folio *pfolios,
+ unsigned int nr_folios)
+{
+ unsigned int i;
+
+ for (i = 0; i < nr_folios; i++) {
+ const struct memfd_luo_preserved_folio *pfolio = &pfolios[i];
+ struct folio *folio;
+
+ if (!pfolio->foliodesc)
+ continue;
+
+ folio = pfn_folio(PRESERVED_FOLIO_PFN(pfolio->foliodesc));
+
+ kho_unpreserve_folio(folio);
This one is missing WARN_ON_ONCE() similar to the one in
memfd_luo_preserve_folios().
So you really want to cause a machine to reboot and get a CVE issued for
this, if it could be triggered? That's bold :)
Please don't. If that can happen, handle the issue and move on, don't
crash boxes.
thanks,
greg k-h
On Tue, Aug 12, 2025 at 11:34:37PM -0700, Vipin Sharma wrote:
quoted
On 2025-08-07 01:44:35, Pasha Tatashin wrote:
quoted
From: Pratyush Yadav <redacted>
+static void memfd_luo_unpreserve_folios(const struct memfd_luo_preserved_folio *pfolios,
+ unsigned int nr_folios)
+{
+ unsigned int i;
+
+ for (i = 0; i < nr_folios; i++) {
+ const struct memfd_luo_preserved_folio *pfolio = &pfolios[i];
+ struct folio *folio;
+
+ if (!pfolio->foliodesc)
+ continue;
+
+ folio = pfn_folio(PRESERVED_FOLIO_PFN(pfolio->foliodesc));
+
+ kho_unpreserve_folio(folio);
This one is missing WARN_ON_ONCE() similar to the one in
memfd_luo_preserve_folios().
So you really want to cause a machine to reboot and get a CVE issued for
this, if it could be triggered? That's bold :)
Please don't. If that can happen, handle the issue and move on, don't
crash boxes.
Why would a WARN() crash the machine? That is what BUG() does, not
WARN().
--
Regards,
Pratyush Yadav
On Wed, Aug 13, 2025 at 02:02:07PM +0200, Pratyush Yadav wrote:
On Wed, Aug 13 2025, Greg KH wrote:
quoted
On Tue, Aug 12, 2025 at 11:34:37PM -0700, Vipin Sharma wrote:
quoted
On 2025-08-07 01:44:35, Pasha Tatashin wrote:
quoted
From: Pratyush Yadav <redacted>
+static void memfd_luo_unpreserve_folios(const struct memfd_luo_preserved_folio *pfolios,
+ unsigned int nr_folios)
+{
+ unsigned int i;
+
+ for (i = 0; i < nr_folios; i++) {
+ const struct memfd_luo_preserved_folio *pfolio = &pfolios[i];
+ struct folio *folio;
+
+ if (!pfolio->foliodesc)
+ continue;
+
+ folio = pfn_folio(PRESERVED_FOLIO_PFN(pfolio->foliodesc));
+
+ kho_unpreserve_folio(folio);
This one is missing WARN_ON_ONCE() similar to the one in
memfd_luo_preserve_folios().
So you really want to cause a machine to reboot and get a CVE issued for
this, if it could be triggered? That's bold :)
Please don't. If that can happen, handle the issue and move on, don't
crash boxes.
Why would a WARN() crash the machine? That is what BUG() does, not
WARN().
See 'panic_on_warn' which is enabled in a few billion Linux systems
these days :(
Hi Vipin,
Thanks for the review.
On Tue, Aug 12 2025, Vipin Sharma wrote:
On 2025-08-07 01:44:35, Pasha Tatashin wrote:
quoted
From: Pratyush Yadav <redacted>
+static void memfd_luo_unpreserve_folios(const struct memfd_luo_preserved_folio *pfolios,
+ unsigned int nr_folios)
+{
+ unsigned int i;
+
+ for (i = 0; i < nr_folios; i++) {
+ const struct memfd_luo_preserved_folio *pfolio = &pfolios[i];
+ struct folio *folio;
+
+ if (!pfolio->foliodesc)
+ continue;
+
+ folio = pfn_folio(PRESERVED_FOLIO_PFN(pfolio->foliodesc));
+
+ kho_unpreserve_folio(folio);
This one is missing WARN_ON_ONCE() similar to the one in
memfd_luo_preserve_folios().
Right, will add.
quoted
+ unpin_folio(folio);
Looking at this code caught my eye. This can also be called from LUO's
finish callback if no one claimed the memfd after live update. In that
case, unpin_folio() is going to underflow the pincount or refcount on
the folio since after the kexec, the folio is no longer pinned. We
should only be doing folio_put().
I think this function should take a argument to specify which of these
cases it is dealing with.
quoted
+ }
+}
+
+static void *memfd_luo_create_fdt(unsigned long size)
+{
+ unsigned int order = get_order(size);
+ struct folio *fdt_folio;
+ int err = 0;
+ void *fdt;
+
+ if (order > MAX_PAGE_ORDER)
+ return NULL;
+
+ fdt_folio = folio_alloc(GFP_KERNEL, order);
__GFP_ZERO should also be used here. Otherwise this can lead to
unintentional passing of old kernel memory.
fdt_create() zeroes out the buffer so this should not be a problem.
This one is only check for shmem_file, whereas in
memfd_luo_can_preserve() there is check for inode->i_nlink also. Is that
not needed here?
Actually, this should never happen since the LUO can_preserve() callback
should make sure of this. I think it would be perfectly fine to just
drop this check. I only added it because I was being extra careful.
quoted
+
+ inode_lock(inode);
+ shmem_i_mapping_freeze(inode, true);
+
+ size = i_size_read(inode);
+ if ((PAGE_ALIGN(size) / PAGE_SIZE) > UINT_MAX) {
+ err = -E2BIG;
+ goto err_unlock;
+ }
+
+ /*
+ * Guess the number of folios based on inode size. Real number might end
+ * up being smaller if there are higher order folios.
+ */
+ max_folios = PAGE_ALIGN(size) / PAGE_SIZE;
+ folios = kvmalloc_array(max_folios, sizeof(*folios), GFP_KERNEL);
__GFP_ZERO?
Why? This is only used in this function and gets freed on return. And
the function only looks at the elements that get initialized by
memfd_pin_folios().
quoted
+static int memfd_luo_freeze(struct liveupdate_file_handler *handler,
+ struct file *file, u64 *data)
+{
+ u64 pos = file->f_pos;
+ void *fdt;
+ int err;
+
+ if (WARN_ON_ONCE(!*data))
+ return -EINVAL;
+
+ fdt = phys_to_virt(*data);
+
+ /*
+ * The pos or size might have changed since prepare. Everything else
+ * stays the same.
+ */
+ err = fdt_setprop(fdt, 0, "pos", &pos, sizeof(pos));
+ if (err)
+ return err;
Comment is talking about pos and size but code is only updating pos.
Right. Comment is out of date. size can no longer change since prepare.
So will update the comment.
Print should clearly state that error is because fields is not found or
len is not multiple of sizeof(*pfolios).
Eh, there is already too much boilerplate one has to write (and read)
for parsing the FDT. Is there really a need for an extra 3-4 lines of
code for _each_ property that is parsed?
Long term, I think we shouldn't be doing this manually anyway. I think
the maintainable path forward is to define a schema for the serialized
data and have a parser that takes in the schema and gives out a parsed
struct, doing all sorts of checks in the process.
--
Regards,
Pratyush Yadav
From: Jason Gunthorpe <jgg@nvidia.com> Date: 2025-08-13 12:41:48
On Wed, Aug 13, 2025 at 02:14:23PM +0200, Greg KH wrote:
On Wed, Aug 13, 2025 at 02:02:07PM +0200, Pratyush Yadav wrote:
quoted
On Wed, Aug 13 2025, Greg KH wrote:
quoted
On Tue, Aug 12, 2025 at 11:34:37PM -0700, Vipin Sharma wrote:
quoted
On 2025-08-07 01:44:35, Pasha Tatashin wrote:
quoted
From: Pratyush Yadav <redacted>
+static void memfd_luo_unpreserve_folios(const struct memfd_luo_preserved_folio *pfolios,
+ unsigned int nr_folios)
+{
+ unsigned int i;
+
+ for (i = 0; i < nr_folios; i++) {
+ const struct memfd_luo_preserved_folio *pfolio = &pfolios[i];
+ struct folio *folio;
+
+ if (!pfolio->foliodesc)
+ continue;
+
+ folio = pfn_folio(PRESERVED_FOLIO_PFN(pfolio->foliodesc));
+
+ kho_unpreserve_folio(folio);
This one is missing WARN_ON_ONCE() similar to the one in
memfd_luo_preserve_folios().
So you really want to cause a machine to reboot and get a CVE issued for
this, if it could be triggered? That's bold :)
Please don't. If that can happen, handle the issue and move on, don't
crash boxes.
Why would a WARN() crash the machine? That is what BUG() does, not
WARN().
See 'panic_on_warn' which is enabled in a few billion Linux systems
these days :(
On Wed, Aug 13, 2025 at 09:41:40AM -0300, Jason Gunthorpe wrote:
On Wed, Aug 13, 2025 at 02:14:23PM +0200, Greg KH wrote:
quoted
On Wed, Aug 13, 2025 at 02:02:07PM +0200, Pratyush Yadav wrote:
quoted
On Wed, Aug 13 2025, Greg KH wrote:
quoted
On Tue, Aug 12, 2025 at 11:34:37PM -0700, Vipin Sharma wrote:
quoted
On 2025-08-07 01:44:35, Pasha Tatashin wrote:
quoted
From: Pratyush Yadav <redacted>
+static void memfd_luo_unpreserve_folios(const struct memfd_luo_preserved_folio *pfolios,
+ unsigned int nr_folios)
+{
+ unsigned int i;
+
+ for (i = 0; i < nr_folios; i++) {
+ const struct memfd_luo_preserved_folio *pfolio = &pfolios[i];
+ struct folio *folio;
+
+ if (!pfolio->foliodesc)
+ continue;
+
+ folio = pfn_folio(PRESERVED_FOLIO_PFN(pfolio->foliodesc));
+
+ kho_unpreserve_folio(folio);
This one is missing WARN_ON_ONCE() similar to the one in
memfd_luo_preserve_folios().
So you really want to cause a machine to reboot and get a CVE issued for
this, if it could be triggered? That's bold :)
Please don't. If that can happen, handle the issue and move on, don't
crash boxes.
Why would a WARN() crash the machine? That is what BUG() does, not
WARN().
See 'panic_on_warn' which is enabled in a few billion Linux systems
these days :(
"should be" is the key here. And it's not obvious from this patch if
that's true or not, which is why I mentioned it.
I will keep bringing this up, given the HUGE number of CVEs I keep
assigning each week for when userspace hits WARN_ON() calls until that
flow starts to die out either because we don't keep adding new calls, OR
we finally fix them all. Both would be good...
thanks,
greg k-h
On Wed, Aug 13, 2025 at 02:14:23PM +0200, Greg KH wrote:
quoted
On Wed, Aug 13, 2025 at 02:02:07PM +0200, Pratyush Yadav wrote:
quoted
On Wed, Aug 13 2025, Greg KH wrote:
quoted
On Tue, Aug 12, 2025 at 11:34:37PM -0700, Vipin Sharma wrote:
quoted
On 2025-08-07 01:44:35, Pasha Tatashin wrote:
quoted
From: Pratyush Yadav <redacted>
+static void memfd_luo_unpreserve_folios(const struct memfd_luo_preserved_folio *pfolios,
+ unsigned int nr_folios)
+{
+ unsigned int i;
+
+ for (i = 0; i < nr_folios; i++) {
+ const struct memfd_luo_preserved_folio *pfolio = &pfolios[i];
+ struct folio *folio;
+
+ if (!pfolio->foliodesc)
+ continue;
+
+ folio = pfn_folio(PRESERVED_FOLIO_PFN(pfolio->foliodesc));
+
+ kho_unpreserve_folio(folio);
This one is missing WARN_ON_ONCE() similar to the one in
memfd_luo_preserve_folios().
So you really want to cause a machine to reboot and get a CVE issued for
this, if it could be triggered? That's bold :)
Please don't. If that can happen, handle the issue and move on, don't
crash boxes.
Why would a WARN() crash the machine? That is what BUG() does, not
WARN().
See 'panic_on_warn' which is enabled in a few billion Linux systems
these days :(
Yep. And if we are saying WARN() should never be used then doesn't that
make panic_on_warn a no-op? What is even the point of that option then?
Here, we are unable to unpreserve a folio that we have preserved. This
isn't a normal error that we expect to happen. This should _not_ happen
unless something has gone horribly wrong.
For example, the calls to kho_preserve_folio() don't WARN(), since that
can fail for various reasons. They just return the error up the call
chain. As an analogy, allocating a page can fail, and it is quite
reasonable to expect the code to not throw out WARN()s for that. But if
for some reason you can't free a page that you allocated, this is very
unexpected and should WARN(). Of course, in Linux the page free APIs
don't even return a status, but I hope you get my point.
If I were a system administrator who sets panic_on_warn, I would _want_
the system to crash so no further damage happens and I can collect
logs/crash dumps to investigate later. Without the WARN(), I never get a
chance to debug and my system breaks silently. For all others, the
kernel goes on with some possibly corrupted/broken state.
--
Regards,
Pratyush Yadav
On Wed, Aug 13, 2025 at 09:41:40AM -0300, Jason Gunthorpe wrote:
[...]
quoted
Use the warn ons. Make sure they can't be triggered by userspace. Use
them to detect corruption/malfunction in the kernel.
In this case if kho_unpreserve_folio() fails in this call chain it
means some error unwind is wrongly happening out of sequence, and we
are now forced to leak memory. Unwind is not something that userspace
should be controlling, so of course we want a WARN_ON here.
"should be" is the key here. And it's not obvious from this patch if
that's true or not, which is why I mentioned it.
I will keep bringing this up, given the HUGE number of CVEs I keep
assigning each week for when userspace hits WARN_ON() calls until that
flow starts to die out either because we don't keep adding new calls, OR
we finally fix them all. Both would be good...
Out of curiosity, why is hitting a WARN_ON() considered a vulnerability?
I'd guess one reason is overwhelming system console which can cause a
denial of service, but what about WARN_ON_ONCE() or WARN_RATELIMIT()?
--
Regards,
Pratyush Yadav
On Wed, Aug 13, 2025 at 1:37 PM Pratyush Yadav [off-list ref] wrote:
On Wed, Aug 13 2025, Greg KH wrote:
quoted
On Wed, Aug 13, 2025 at 09:41:40AM -0300, Jason Gunthorpe wrote:
[...]
quoted
quoted
Use the warn ons. Make sure they can't be triggered by userspace. Use
them to detect corruption/malfunction in the kernel.
In this case if kho_unpreserve_folio() fails in this call chain it
means some error unwind is wrongly happening out of sequence, and we
are now forced to leak memory. Unwind is not something that userspace
should be controlling, so of course we want a WARN_ON here.
"should be" is the key here. And it's not obvious from this patch if
that's true or not, which is why I mentioned it.
I will keep bringing this up, given the HUGE number of CVEs I keep
assigning each week for when userspace hits WARN_ON() calls until that
flow starts to die out either because we don't keep adding new calls, OR
we finally fix them all. Both would be good...
Out of curiosity, why is hitting a WARN_ON() considered a vulnerability?
I'd guess one reason is overwhelming system console which can cause a
denial of service, but what about WARN_ON_ONCE() or WARN_RATELIMIT()?
My understanding that it is vulnerability only if it can be triggered
from userspace, otherwise it is a preferred method to give a notice
that something is very wrong.
Given the large number of machines that have panic_on_warn, a reliable
kernel crash that is triggered from userspace is a vulnerability(?).
Pasha
On Wed, Aug 13, 2025 at 12:29 PM Pratyush Yadav [off-list ref] wrote:
Hi Vipin,
Thanks for the review.
On Tue, Aug 12 2025, Vipin Sharma wrote:
quoted
On 2025-08-07 01:44:35, Pasha Tatashin wrote:
quoted
From: Pratyush Yadav <redacted>
+static void memfd_luo_unpreserve_folios(const struct memfd_luo_preserved_folio *pfolios,
+ unsigned int nr_folios)
+{
+ unsigned int i;
+
+ for (i = 0; i < nr_folios; i++) {
+ const struct memfd_luo_preserved_folio *pfolio = &pfolios[i];
+ struct folio *folio;
+
+ if (!pfolio->foliodesc)
+ continue;
+
+ folio = pfn_folio(PRESERVED_FOLIO_PFN(pfolio->foliodesc));
+
+ kho_unpreserve_folio(folio);
This one is missing WARN_ON_ONCE() similar to the one in
memfd_luo_preserve_folios().
Right, will add.
quoted
quoted
+ unpin_folio(folio);
Looking at this code caught my eye. This can also be called from LUO's
finish callback if no one claimed the memfd after live update. In that
case, unpin_folio() is going to underflow the pincount or refcount on
the folio since after the kexec, the folio is no longer pinned. We
should only be doing folio_put().
I think this function should take a argument to specify which of these
cases it is dealing with.
quoted
quoted
+ }
+}
+
+static void *memfd_luo_create_fdt(unsigned long size)
+{
+ unsigned int order = get_order(size);
+ struct folio *fdt_folio;
+ int err = 0;
+ void *fdt;
+
+ if (order > MAX_PAGE_ORDER)
+ return NULL;
+
+ fdt_folio = folio_alloc(GFP_KERNEL, order);
__GFP_ZERO should also be used here. Otherwise this can lead to
unintentional passing of old kernel memory.
fdt_create() zeroes out the buffer so this should not be a problem.
You are right, fdt_create() zeroes the whole buffer, however, I wonder
if it could be `optimized` to only clear only the header part of FDT,
not the rest and this could potentially lead us to send an FDT buffer
that contains both a valid FDT and the trailing bits contain data from
old kernel.
Pasha
On Wed, Aug 13, 2025 at 03:37:03PM +0200, Pratyush Yadav wrote:
On Wed, Aug 13 2025, Greg KH wrote:
quoted
On Wed, Aug 13, 2025 at 09:41:40AM -0300, Jason Gunthorpe wrote:
[...]
quoted
quoted
Use the warn ons. Make sure they can't be triggered by userspace. Use
them to detect corruption/malfunction in the kernel.
In this case if kho_unpreserve_folio() fails in this call chain it
means some error unwind is wrongly happening out of sequence, and we
are now forced to leak memory. Unwind is not something that userspace
should be controlling, so of course we want a WARN_ON here.
"should be" is the key here. And it's not obvious from this patch if
that's true or not, which is why I mentioned it.
I will keep bringing this up, given the HUGE number of CVEs I keep
assigning each week for when userspace hits WARN_ON() calls until that
flow starts to die out either because we don't keep adding new calls, OR
we finally fix them all. Both would be good...
Out of curiosity, why is hitting a WARN_ON() considered a vulnerability?
I'd guess one reason is overwhelming system console which can cause a
denial of service, but what about WARN_ON_ONCE() or WARN_RATELIMIT()?
If panic_on_warn is set, this will cause the machine to crash/reboot,
which is considered a "vulnerability" by the CVE.org definition. If a
user can trigger this, it gets a CVE assigned to it.
hope this helps,
greg k-h
On Wed, Aug 13, 2025 at 01:41:51PM +0000, Pasha Tatashin wrote:
On Wed, Aug 13, 2025 at 1:37 PM Pratyush Yadav [off-list ref] wrote:
quoted
On Wed, Aug 13 2025, Greg KH wrote:
quoted
On Wed, Aug 13, 2025 at 09:41:40AM -0300, Jason Gunthorpe wrote:
[...]
quoted
quoted
Use the warn ons. Make sure they can't be triggered by userspace. Use
them to detect corruption/malfunction in the kernel.
In this case if kho_unpreserve_folio() fails in this call chain it
means some error unwind is wrongly happening out of sequence, and we
are now forced to leak memory. Unwind is not something that userspace
should be controlling, so of course we want a WARN_ON here.
"should be" is the key here. And it's not obvious from this patch if
that's true or not, which is why I mentioned it.
I will keep bringing this up, given the HUGE number of CVEs I keep
assigning each week for when userspace hits WARN_ON() calls until that
flow starts to die out either because we don't keep adding new calls, OR
we finally fix them all. Both would be good...
Out of curiosity, why is hitting a WARN_ON() considered a vulnerability?
I'd guess one reason is overwhelming system console which can cause a
denial of service, but what about WARN_ON_ONCE() or WARN_RATELIMIT()?
My understanding that it is vulnerability only if it can be triggered
from userspace, otherwise it is a preferred method to give a notice
that something is very wrong.
Given the large number of machines that have panic_on_warn, a reliable
kernel crash that is triggered from userspace is a vulnerability(?).
On Wed, Aug 13, 2025 at 12:29 PM Pratyush Yadav [off-list ref] wrote:
quoted
Hi Vipin,
Thanks for the review.
On Tue, Aug 12 2025, Vipin Sharma wrote:
quoted
On 2025-08-07 01:44:35, Pasha Tatashin wrote:
quoted
From: Pratyush Yadav <redacted>
+static void memfd_luo_unpreserve_folios(const struct memfd_luo_preserved_folio *pfolios,
+ unsigned int nr_folios)
+{
+ unsigned int i;
+
+ for (i = 0; i < nr_folios; i++) {
+ const struct memfd_luo_preserved_folio *pfolio = &pfolios[i];
+ struct folio *folio;
+
+ if (!pfolio->foliodesc)
+ continue;
+
+ folio = pfn_folio(PRESERVED_FOLIO_PFN(pfolio->foliodesc));
+
+ kho_unpreserve_folio(folio);
This one is missing WARN_ON_ONCE() similar to the one in
memfd_luo_preserve_folios().
Right, will add.
quoted
quoted
+ unpin_folio(folio);
Looking at this code caught my eye. This can also be called from LUO's
finish callback if no one claimed the memfd after live update. In that
case, unpin_folio() is going to underflow the pincount or refcount on
the folio since after the kexec, the folio is no longer pinned. We
should only be doing folio_put().
I think this function should take a argument to specify which of these
cases it is dealing with.
quoted
quoted
+ }
+}
+
+static void *memfd_luo_create_fdt(unsigned long size)
+{
+ unsigned int order = get_order(size);
+ struct folio *fdt_folio;
+ int err = 0;
+ void *fdt;
+
+ if (order > MAX_PAGE_ORDER)
+ return NULL;
+
+ fdt_folio = folio_alloc(GFP_KERNEL, order);
__GFP_ZERO should also be used here. Otherwise this can lead to
unintentional passing of old kernel memory.
fdt_create() zeroes out the buffer so this should not be a problem.
You are right, fdt_create() zeroes the whole buffer, however, I wonder
if it could be `optimized` to only clear only the header part of FDT,
not the rest and this could potentially lead us to send an FDT buffer
that contains both a valid FDT and the trailing bits contain data from
old kernel.
Fair enough. At least the API documentation does not say anything about
the state of the buffer. My main concern was around performance since
the FDT can be multiple megabytes long for big memfds. Anyway, this
isn't in the blackout window so perhaps we can live with it. Will add
the GFP_ZERO.
--
Regards,
Pratyush Yadav
From: Jason Gunthorpe <jgg@nvidia.com> Date: 2025-08-13 20:03:52
On Wed, Aug 13, 2025 at 03:00:08PM +0200, Greg KH wrote:
quoted
In this case if kho_unpreserve_folio() fails in this call chain it
means some error unwind is wrongly happening out of sequence, and we
are now forced to leak memory. Unwind is not something that userspace
should be controlling, so of course we want a WARN_ON here.
"should be" is the key here. And it's not obvious from this patch if
that's true or not, which is why I mentioned it.
I will keep bringing this up, given the HUGE number of CVEs I keep
assigning each week for when userspace hits WARN_ON() calls until that
flow starts to die out either because we don't keep adding new calls, OR
we finally fix them all. Both would be good...
WARN or not, userspace triggering permanently leaking kernel memory is
a CVE worthy bug in of itself.
So even if userspace triggers this I'd rather have the warn than the
difficult to find leak.
I don't know what your CVEs are, but I get a decent number of
userspace hits a WARN bug from with syzkaller, and they are all bugs
in the kernel. Bugs that should probably get CVEs even without the
crash on WARN issue anyhow. The WARN made them discoverable cheaply.
The most recent was a userspace triggerable arthimetic overflow
corrupted a datastructure and a WARN caught it, syzkaller found it,
and we fixed it before it became a splashy exploit with a web
site.
Removing bug catching to reduce CVEs because we don't find the bugs
anymore seems like the wrong direction to me.
Jason
It is probably better to introduce a function pointer argument to this
xa_load_or_alloc() to do the alloc and init operation than to open
code the thing.
Jason
Why are we adding phys apis? Didn't we talk about this before and
agree not to expose these?
The places using it are goofy:
+static int luo_fdt_setup(void)
+{
+ fdt_out = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO,
+ get_order(LUO_FDT_SIZE));
+ ret = kho_preserve_phys(__pa(fdt_out), LUO_FDT_SIZE);
+ WARN_ON_ONCE(kho_unpreserve_phys(__pa(fdt_out), LUO_FDT_SIZE));
It literally allocated a page and then for some reason switches to
phys with an open coded __pa??
This is ugly, if you want a helper to match __get_free_pages() then
make one that works on void * directly. You can get the order of the
void * directly from the struct page IIRC when using GFP_COMP.
Which is perhaps another comment, if this __get_free_pages() is going
to be a common pattern (and I guess it will be) then the API should be
streamlined alot more:
void *kho_alloc_preserved_memory(gfp, size);
void kho_free_preserved_memory(void *);
Which can wrapper the get_free_pages and the preserve logic and gives
a nice path to possibly someday supporting non-PAGE_SIZE allocations.
Jason
Now nothing ever cleans this up :\
Are you sure the issue isn't in the caller that it shouldn't be
calling kho abort until all the other stuff is cleaned up first?
I feel like this is another case of absuing globals gives an unclear
lifecycle model.
Jason
From: Jason Gunthorpe <jgg@nvidia.com> Date: 2025-08-14 13:49:21
On Thu, Aug 07, 2025 at 01:44:22AM +0000, Pasha Tatashin wrote:
+/**
+ * DOC: General ioctl format
+ *
+ * The ioctl interface follows a general format to allow for extensibility. Each
+ * ioctl is passed in a structure pointer as the argument providing the size of
+ * the structure in the first u32. The kernel checks that any structure space
+ * beyond what it understands is 0. This allows userspace to use the backward
+ * compatible portion while consistently using the newer, larger, structures.
+ *
+ * ioctls use a standard meaning for common errnos:
+ *
+ * - ENOTTY: The IOCTL number itself is not supported at all
+ * - E2BIG: The IOCTL number is supported, but the provided structure has
+ * non-zero in a part the kernel does not understand.
+ * - EOPNOTSUPP: The IOCTL number is supported, and the structure is
+ * understood, however a known field has a value the kernel does not
+ * understand or support.
+ * - EINVAL: Everything about the IOCTL was understood, but a field is not
+ * correct.
+ * - ENOENT: An ID or IOVA provided does not exist.
^^^^^^^^^
Maybe this should be 'token' ?
+ * - ENOMEM: Out of memory.
+ * - EOVERFLOW: Mathematics overflowed.
+ *
+ * As well as additional errnos, within specific ioctls.
+ */
Ah if you copy the comment make sure to faithfully follow it in the
implementation :)
It is best to explicitly pad, so add a __u32 reserved between size and
token
Then you need to also check that the reserved is 0 when parsing it,
return -EOPNOTSUPP otherwise.
I suggest you bundle this together into one struct with the misc_dev
and the other globals and largely pretend it is not global, eg refer
to it through container_of, etc
Following practices like this make it harder to abuse the globals.
This will overflow memory, ucmd->user_size may be > sizeof(*argp)
The respond function is an important part of this scheme:
static inline int iommufd_ucmd_respond(struct iommufd_ucmd *ucmd,
size_t cmd_len)
{
if (copy_to_user(ucmd->ubuffer, ucmd->cmd,
min_t(size_t, ucmd->user_size, cmd_len)))
return -EFAULT;
The min (sizeof(*argp) in this case) can't be skipped!
+static int luo_ioctl_fd_restore(struct luo_ucmd *ucmd)
+{
+ struct liveupdate_ioctl_fd_restore *argp = ucmd->cmd;
+ struct file *file;
+ int ret;
+
+ argp->fd = get_unused_fd_flags(O_CLOEXEC);
+ if (argp->fd < 0) {
+ pr_err("Failed to allocate new fd: %d\n", argp->fd);
Same remark about explicit padding and checking padding for 0
+ * luo_file_get_state - Get the preservation state of a specific file.
+ * @token: The token of the file to query.
+ * @statep: Output pointer to store the file's current live update state.
+ * @incoming: If true, query the state of a restored file from the incoming
+ * (previous kernel's) set. If false, query a file being prepared
+ * for preservation in the current set.
+ *
+ * Finds the file associated with the given @token in either the incoming
+ * or outgoing tracking arrays and returns its current LUO state
+ * (NORMAL, PREPARED, FROZEN, UPDATED).
+ *
+ * Return: 0 on success, -ENOENT if the token is not found.
+ */
+int luo_file_get_state(u64 token, enum liveupdate_state *statep, bool incoming)
+{
+ struct luo_file *luo_file;
+ struct xarray *target_xa;
+ int ret = 0;
+
+ luo_state_read_enter();
Less globals, at this point everything should be within memory
attached to the file descriptor and not in globals. Doing this will
promote good maintainable structure and not a spaghetti
Also I think a BKL design is not a good idea for new code. We've had
so many bad experiences with this pattern promoting uncontrolled
incomprehensible locking.
The xarray already has a lock, why not have reasonable locking inside
the luo_file? Probably just a refcount?
If we are using cleanup.h then use it for this too..
But it seems kind of weird, why not just
xa_lock()
xa_load()
*statep = READ_ONCE(luo_file->state);
xa_unlock()
?
+static int luo_ioctl_set_fd_event(struct luo_ucmd *ucmd)
+{
+ struct liveupdate_ioctl_set_fd_event *argp = ucmd->cmd;
+ int ret;
+
+ switch (argp->event) {
+ case LIVEUPDATE_PREPARE:
+ ret = luo_file_prepare(argp->token);
+ break;
+ case LIVEUPDATE_FREEZE:
+ ret = luo_file_freeze(argp->token);
+ break;
+ case LIVEUPDATE_FINISH:
+ ret = luo_file_finish(argp->token);
+ break;
+ case LIVEUPDATE_CANCEL:
+ ret = luo_file_cancel(argp->token);
+ break;
The token should be converted to a file here instead of duplicated in
each function
quoted hunk
static int luo_open(struct inode *inodep, struct file *filep)
{
if (atomic_cmpxchg(&luo_device_in_use, 0, 1))
It is probably better to introduce a function pointer argument to this
xa_load_or_alloc() to do the alloc and init operation than to open
code the thing.
Agreed, but this should be a separate clean-up, this particular patch
is a hotfix that should land soon (it was separated from this this
series). Once it lands, we are going to do this clean-up.
Pasha
Why are we adding phys apis? Didn't we talk about this before and
agree not to expose these?
It is already there, this patch simply completes a lacking unpreserve part.
We can talk about removing it in the future, but the phys interface
provides a benefit of not having to preserve power of two in length
objects.
The places using it are goofy:
+static int luo_fdt_setup(void)
+{
+ fdt_out = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO,
+ get_order(LUO_FDT_SIZE));
+ ret = kho_preserve_phys(__pa(fdt_out), LUO_FDT_SIZE);
+ WARN_ON_ONCE(kho_unpreserve_phys(__pa(fdt_out), LUO_FDT_SIZE));
It literally allocated a page and then for some reason switches to
phys with an open coded __pa??
This is ugly, if you want a helper to match __get_free_pages() then
make one that works on void * directly. You can get the order of the
void * directly from the struct page IIRC when using GFP_COMP.
I will make this changes.
Which is perhaps another comment, if this __get_free_pages() is going
to be a common pattern (and I guess it will be) then the API should be
streamlined alot more:
void *kho_alloc_preserved_memory(gfp, size);
void kho_free_preserved_memory(void *);
Hm, not all GFP flags are compatible with KHO preserve, but we could
add this or similar API, but first let's make KHO completely
stateless: remove, finalize and abort parts from it.
Which can wrapper the get_free_pages and the preserve logic and gives
a nice path to possibly someday supporting non-PAGE_SIZE allocations.
Jason
Why are we adding phys apis? Didn't we talk about this before and
agree not to expose these?
It is already there, this patch simply completes a lacking unpreserve part.
This patch yes, but that is because the later patches intend to use
it, which I argue those patches should not.
There should not be any users of these phys interfaces because they
make no sense. The API preserves folios and brings allocated folios
back on the other side. None of that is phys.
quoted
Which is perhaps another comment, if this __get_free_pages() is going
to be a common pattern (and I guess it will be) then the API should be
streamlined alot more:
void *kho_alloc_preserved_memory(gfp, size);
void kho_free_preserved_memory(void *);
Hm, not all GFP flags are compatible with KHO preserve, but we could
add this or similar API, but first let's make KHO completely
stateless: remove, finalize and abort parts from it.
Right, in those cases we often warn on and mask invalid flag
Jason
Why are we adding phys apis? Didn't we talk about this before and
agree not to expose these?
The places using it are goofy:
+static int luo_fdt_setup(void)
+{
+ fdt_out = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO,
+ get_order(LUO_FDT_SIZE));
+ ret = kho_preserve_phys(__pa(fdt_out), LUO_FDT_SIZE);
+ WARN_ON_ONCE(kho_unpreserve_phys(__pa(fdt_out), LUO_FDT_SIZE));
It literally allocated a page and then for some reason switches to
phys with an open coded __pa??
This is ugly, if you want a helper to match __get_free_pages() then
make one that works on void * directly. You can get the order of the
void * directly from the struct page IIRC when using GFP_COMP.
Which is perhaps another comment, if this __get_free_pages() is going
to be a common pattern (and I guess it will be) then the API should be
streamlined alot more:
void *kho_alloc_preserved_memory(gfp, size);
void kho_free_preserved_memory(void *);
This looks backwards to me. KHO should not deal with memory allocation,
it's responsibility to preserve/restore memory objects it supports.
For __get_free_pages() the natural KHO API is kho_(un)preserve_pages().
With struct page/mesdesc we always have page_to_<specialized object> from
one side and page_to_pfn from the other side.
Then folio and phys/virt APIS just become a thin wrappers around the _page
APIs. And down the road we can add slab and maybe vmalloc.
Once folio won't overlap struct page, we'll have a hard time with only
kho_preserve_folio() for memory that's not actually folio (i.e. anon and
page cache)
Which can wrapper the get_free_pages and the preserve logic and gives
a nice path to possibly someday supporting non-PAGE_SIZE allocations.
Jason
From: Jason Gunthorpe <jgg@nvidia.com> Date: 2025-08-18 13:55:14
On Fri, Aug 15, 2025 at 12:12:10PM +0300, Mike Rapoport wrote:
quoted
Which is perhaps another comment, if this __get_free_pages() is going
to be a common pattern (and I guess it will be) then the API should be
streamlined alot more:
void *kho_alloc_preserved_memory(gfp, size);
void kho_free_preserved_memory(void *);
This looks backwards to me. KHO should not deal with memory allocation,
it's responsibility to preserve/restore memory objects it supports.
Then maybe those are luo_ helpers
But having users open code __get_free_pages() and convert to/from
struct page, phys, etc is not a great idea.
The use case is simply to get some memory to preserve, it should work
in terms of void *. We don't support slab today so this has to be
emulated with full pages, but this detail should not leak out of the
API.
Jason
Hi Pasha,
On Thu, Aug 07 2025, Pasha Tatashin wrote:
This series introduces the LUO, a kernel subsystem designed to
facilitate live kernel updates with minimal downtime,
particularly in cloud delplyoments aiming to update without fully
disrupting running virtual machines.
This series builds upon KHO framework by adding programmatic
control over KHO's lifecycle and leveraging KHO for persisting LUO's
own metadata across the kexec boundary. The git branch for this series
can be found at:
https://github.com/googleprodkernel/linux-liveupdate/tree/luo/v3
Changelog from v2:
- Addressed comments from Mike Rapoport and Jason Gunthorpe
- Only one user agent (LiveupdateD) can open /dev/liveupdate
- With the above changes, sessions are not needed, and should be
maintained by the user-agent itself, so removed support for
sessions.
If all the FDs are restored in the agent's context, this assigns all the
resources to the agent. For example, if the agent restores a memfd, all
the memory gets charged to the agent's cgroup, and the client gets none
of it. This makes it impossible to do any kind of resource limits.
This was one of the advantages of being able to pass around sessions
instead of FDs. The agent can pass on the right session to the right
client, and then the client does the restore, getting all the resources
charged to it.
If we don't allow this, I think we will make LUO/LiveupdateD unsuitable
for many kinds of workloads. Do you have any ideas on how to do proper
resource attribution with the current patches? If not, then perhaps we
should reconsider this change?
[...]
--
Regards,
Pratyush Yadav