* [PATCH 0/9] kho, liveupdate: KHO/LUO observability
@ 2026-11-11 7:52 Chris Down
2026-11-11 7:52 ` [PATCH 1/9] kho: Reclaim the metadata page left reserved after kexec Chris Down
` (8 more replies)
0 siblings, 9 replies; 12+ messages in thread
From: Chris Down @ 2026-11-11 7:52 UTC (permalink / raw)
To: Pasha Tatashin, Mike Rapoport, Pratyush Yadav
Cc: Alexander Graf, Baoquan He, Jonathan Corbet, Shuah Khan, kexec,
linux-mm, linux-kernel, linux-doc, linux-kselftest, kernel-team
At Meta, we are moving towards using live update on hypervisors whose
VMs can't be migrated away first. We also want to preserve service fd
stores across systemctl kexec.
Our kernel rollout is driven by health checks from each host. For
example, an increase in rejected handovers or retrieval failures may be
a reason to stop a rollout, and the bootmem size and sequence number
help us understand what is happening on which hosts. The series exposes:
- how much memory KHO set aside
- whether the new kernel accepted the incoming handover
- whether LUO could give back the state that userspace handed over
We expose these in sysfs so the collector can read them without opening
/dev/liveupdate, which starts deserialisation, or depending on debugfs
or the kernel log.
I also noticed that the incoming kexec metadata page stays reserved
after boot, so free it once its contents have been copied.
Chris Down (9):
kho: Reclaim the metadata page left reserved after kexec
kho: Make bootmem reservations visible to fleet monitoring
liveupdate: Make failed retrieves distinguishable from dropped files
selftests/liveupdate: Catch incorrect file accounting across kexec
selftests/kho: Catch missing or zero bootmem reports before kexec
kho: Expose rejected handovers that look like cold boots
kho: Report how much handover memory is preserved and restored
liveupdate: Expose the sequence number for consecutive live updates
selftests/liveupdate: Check handover revival, sequence and memory
stats
.../ABI/testing/sysfs-class-misc-liveupdate | 56 ++++
.../ABI/testing/sysfs-kernel-kexec-kdump | 30 ++
Documentation/admin-guide/mm/kho.rst | 19 +-
Documentation/userspace-api/liveupdate.rst | 8 +
MAINTAINERS | 1 +
include/linux/kexec_handover.h | 3 +
kernel/kexec_core.c | 26 +-
kernel/liveupdate/kexec_handover.c | 82 +++++-
kernel/liveupdate/luo_core.c | 58 ++++
kernel/liveupdate/luo_file.c | 16 +-
kernel/liveupdate/luo_internal.h | 34 +++
kernel/liveupdate/luo_session.c | 6 +
tools/testing/selftests/kho/init.c | 24 ++
tools/testing/selftests/kho/vmtest.sh | 1 +
tools/testing/selftests/liveupdate/Makefile | 1 +
tools/testing/selftests/liveupdate/init.c | 5 +
.../selftests/liveupdate/luo_handover_stats.c | 258 ++++++++++++++++++
.../selftests/liveupdate/run-vmtests.sh | 1 +
tools/testing/selftests/liveupdate/vmtest.sh | 2 +
19 files changed, 624 insertions(+), 7 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-class-misc-liveupdate
create mode 100644 tools/testing/selftests/liveupdate/luo_handover_stats.c
base-commit: a0dc8eddbfdb6dd9c5c2b70eb66ab49cb1978425
--
2.54.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 1/9] kho: Reclaim the metadata page left reserved after kexec
2026-11-11 7:52 [PATCH 0/9] kho, liveupdate: KHO/LUO observability Chris Down
@ 2026-11-11 7:52 ` Chris Down
2026-11-11 11:08 ` Breno Leitao
2026-11-11 22:29 ` Pasha Tatashin
2026-11-11 7:53 ` [PATCH 2/9] kho: Make bootmem reservations visible to fleet monitoring Chris Down
` (7 subsequent siblings)
8 siblings, 2 replies; 12+ messages in thread
From: Chris Down @ 2026-11-11 7:52 UTC (permalink / raw)
To: Pasha Tatashin, Mike Rapoport, Pratyush Yadav
Cc: Breno Leitao, Alexander Graf, Baoquan He, Jonathan Corbet,
Shuah Khan, kexec, linux-mm, linux-kernel, linux-doc,
linux-kselftest, kernel-team
After a KHO kexec, the new kernel keeps the page holding the previous
kernel's kexec metadata reserved for as long as it runs. It only needs
the page once, since kho_in_kexec_metadata() copies the previous release
and the kexec count out of it at boot, and nothing reads it again.
Free it with kho_restore_free() once copying is complete.
Fixes: 76aa46b9e404 ("kho: kexec-metadata: track previous kernel chain")
Signed-off-by: Chris Down <chris@chrisdown.name>
---
Documentation/admin-guide/mm/kho.rst | 4 +++-
kernel/liveupdate/kexec_handover.c | 10 +++++++---
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/Documentation/admin-guide/mm/kho.rst b/Documentation/admin-guide/mm/kho.rst
index 902d171a780a..f9d2cc5999ee 100644
--- a/Documentation/admin-guide/mm/kho.rst
+++ b/Documentation/admin-guide/mm/kho.rst
@@ -120,4 +120,6 @@ stabilized.
``/sys/kernel/debug/kho/in/sub_fdts/``
Similar to ``kho/out/sub_fdts/``, but contains sub blobs
- of KHO producers passed from the old kernel.
+ of KHO producers passed from the old kernel. A blob disappears
+ once the kernel has read and freed it, as ``kexec-metadata`` does
+ during boot.
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index eeec298aa9d6..3046b89e5db4 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -1808,6 +1808,9 @@ static void __init kho_in_kexec_metadata(void)
pr_info("exec from: %s (count %u)\n",
kho_in.previous_release, kho_in.kexec_count);
+
+ kho_debugfs_blob_remove(&kho_in.dbg, metadata);
+ kho_restore_free(metadata);
}
/*
@@ -1885,14 +1888,15 @@ static __init int kho_init(void)
if (err)
goto err_free_fdt;
+ if (fdt)
+ kho_in_debugfs_init(&kho_in.dbg, fdt);
+
err = kho_kexec_metadata_init(fdt);
if (err)
goto err_free_fdt;
- if (fdt) {
- kho_in_debugfs_init(&kho_in.dbg, fdt);
+ if (fdt)
return 0;
- }
for (int i = 0; i < kho_bootmem_cnt; i++) {
unsigned long base_pfn = PHYS_PFN(kho_bootmem[i].addr);
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 2/9] kho: Make bootmem reservations visible to fleet monitoring
2026-11-11 7:52 [PATCH 0/9] kho, liveupdate: KHO/LUO observability Chris Down
2026-11-11 7:52 ` [PATCH 1/9] kho: Reclaim the metadata page left reserved after kexec Chris Down
@ 2026-11-11 7:53 ` Chris Down
2026-11-11 7:53 ` [PATCH 3/9] liveupdate: Make failed retrieves distinguishable from dropped files Chris Down
` (6 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Chris Down @ 2026-11-11 7:53 UTC (permalink / raw)
To: Pasha Tatashin, Mike Rapoport, Pratyush Yadav
Cc: Alexander Graf, Baoquan He, Jonathan Corbet, Shuah Khan, kexec,
linux-mm, linux-kernel, linux-doc, linux-kselftest, kernel-team
We want to use live update at Meta to upgrade hypervisors whose VMs
can't be migrated away first. But this poses a problem: whether we can
enable KHO on a class of host depends on how much memory it leaves
available for unmovable allocations. In an early test, a 64 GiB host
with hugetlb_cma=6G set aside about 30 GiB of bootmem, which the running
kernel can only use for movable allocations.
The reservation varies with the kernel, memory size, NUMA layout and
command line. Our fleet collector needs the actual size from each host
so we can decide where to enable KHO and catch a kernel release that
takes those hosts over their bootmem budget.
Today the collector would have to sum kho/out/bootmem_len in debugfs,
which isn't a userspace ABI. In /proc/meminfo, free bootmem contributes
to CmaFree, but the reservation doesn't contribute to CmaTotal. The cma
count in /proc/zoneinfo includes bootmem after a cold boot but not after
a KHO kexec. On an 8 GiB QEMU guest booted with
kho_bootmem=32M,1536M,512M:
CmaTotal CmaFree zoneinfo cma (all zones)
cold boot 0 kB 2127872 kB 2129920 kB
KHO kexec 0 kB 881336 kB 0 kB
Even if bootmem becomes a CMA area, CmaTotal will still include other
areas such as hugetlb CMA, so inferring the bootmem size is still
error-prone.
Let's report the total bootmem reservation in bytes at
/sys/kernel/kexec/kho_bootmem_size.
Signed-off-by: Chris Down <chris@chrisdown.name>
---
.../ABI/testing/sysfs-kernel-kexec-kdump | 8 ++++++++
Documentation/admin-guide/mm/kho.rst | 9 +++++++++
include/linux/kexec_handover.h | 1 +
kernel/kexec_core.c | 13 +++++++++++++
kernel/liveupdate/kexec_handover.c | 15 +++++++++++++++
5 files changed, 46 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-kernel-kexec-kdump b/Documentation/ABI/testing/sysfs-kernel-kexec-kdump
index f59051b5d96d..80941a7d8a7c 100644
--- a/Documentation/ABI/testing/sysfs-kernel-kexec-kdump
+++ b/Documentation/ABI/testing/sysfs-kernel-kexec-kdump
@@ -59,3 +59,11 @@ Description: read only
to the crash (kdump) kernel. It lists the start and end physical
addresses of CMA regions assigned for crashkernel use.
User: kdump service
+
+What: /sys/kernel/kexec/kho_bootmem_size
+Date: Nov 2026
+Contact: kexec@lists.infradead.org
+Description: read only
+ Total size of the KHO bootmem regions in bytes. Returns 0
+ when KHO is disabled.
+User: system monitoring
diff --git a/Documentation/admin-guide/mm/kho.rst b/Documentation/admin-guide/mm/kho.rst
index f9d2cc5999ee..c329d720bf25 100644
--- a/Documentation/admin-guide/mm/kho.rst
+++ b/Documentation/admin-guide/mm/kho.rst
@@ -81,6 +81,15 @@ At scale, correlating crashes to the previous kernel version enables
faster root cause analysis when issues only occur in specific kernel
transition scenarios.
+sysfs Interfaces
+================
+
+``/sys/kernel/kexec/kho_bootmem_size``
+ Total size of the KHO bootmem regions in bytes, or 0 when KHO is
+ disabled.
+
+See Documentation/ABI/testing/sysfs-kernel-kexec-kdump for the sysfs ABI.
+
debugfs Interfaces
==================
diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
index ef2b188c8ed0..736d51e79fd2 100644
--- a/include/linux/kexec_handover.h
+++ b/include/linux/kexec_handover.h
@@ -40,6 +40,7 @@ void kho_populate(phys_addr_t fdt_phys, u64 fdt_len, phys_addr_t bootmem_phys,
u64 bootmem_len);
bool kho_bootmem_overlap(phys_addr_t phys, size_t size);
+phys_addr_t kho_bootmem_size(void);
static inline enum migratetype kho_bootmem_migratetype(unsigned long pfn,
enum migratetype mt)
diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
index f4a31565c447..ed105e6351eb 100644
--- a/kernel/kexec_core.c
+++ b/kernel/kexec_core.c
@@ -13,6 +13,7 @@
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/kexec.h>
+#include <linux/kexec_handover.h>
#include <linux/mutex.h>
#include <linux/list.h>
#include <linux/liveupdate.h>
@@ -1324,6 +1325,15 @@ static struct kobj_attribute crash_elfcorehdr_size_attr = __ATTR_RO(crash_elfcor
#endif /* CONFIG_CRASH_HOTPLUG */
#endif /* CONFIG_CRASH_DUMP */
+#ifdef CONFIG_KEXEC_HANDOVER
+static ssize_t kho_bootmem_size_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "%llu\n", (unsigned long long)kho_bootmem_size());
+}
+static struct kobj_attribute kho_bootmem_size_attr = __ATTR_RO(kho_bootmem_size);
+#endif
+
static struct attribute *kexec_attrs[] = {
&loaded_attr.attr,
#ifdef CONFIG_CRASH_DUMP
@@ -1335,6 +1345,9 @@ static struct attribute *kexec_attrs[] = {
#ifdef CONFIG_CRASH_HOTPLUG
&crash_elfcorehdr_size_attr.attr,
#endif
+#endif
+#ifdef CONFIG_KEXEC_HANDOVER
+ &kho_bootmem_size_attr.attr,
#endif
NULL
};
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 3046b89e5db4..21d77e29d2bb 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -942,6 +942,21 @@ static void __init kho_reserve_bootmem(void)
kho_enable = false;
}
+phys_addr_t kho_bootmem_size(void)
+{
+ phys_addr_t size = 0;
+ unsigned int i;
+
+ /* kho_init() can disable KHO without clearing kho_bootmem_cnt. */
+ if (!kho_enable)
+ return 0;
+
+ for (i = 0; i < kho_bootmem_cnt; i++)
+ size += kho_bootmem[i].size;
+
+ return size;
+}
+
/*
* Look for free blocks of 1G. This is a heuristic chosen to work efficiently
* with large systems with hundreds of gigabytes of memory. It will work poorly
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 3/9] liveupdate: Make failed retrieves distinguishable from dropped files
2026-11-11 7:52 [PATCH 0/9] kho, liveupdate: KHO/LUO observability Chris Down
2026-11-11 7:52 ` [PATCH 1/9] kho: Reclaim the metadata page left reserved after kexec Chris Down
2026-11-11 7:53 ` [PATCH 2/9] kho: Make bootmem reservations visible to fleet monitoring Chris Down
@ 2026-11-11 7:53 ` Chris Down
2026-11-11 7:53 ` [PATCH 4/9] selftests/liveupdate: Catch incorrect file accounting across kexec Chris Down
` (5 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Chris Down @ 2026-11-11 7:53 UTC (permalink / raw)
To: Pasha Tatashin, Mike Rapoport, Pratyush Yadav
Cc: Alexander Graf, Baoquan He, Jonathan Corbet, Shuah Khan, kexec,
linux-mm, linux-kernel, linux-doc, linux-kselftest, kernel-team
At Meta, we want to use live update to upgrade hypervisors in place and
preserve service fd stores across systemctl kexec. Our kernel rollout
stops automatically when host health checks fail.
We need it to stop if the new kernel can't give back state that a VMM or
service handed over. We also want to track files that userspace never
asks for.
Today the process that tries a retrieve sees its error, but a fleet
collector can't see that error or tell whether a session went unclaimed.
Opening /dev/liveupdate to check would itself trigger deserialisation on
the first open and can get in the way of the real restorer because the
device is exclusive-open. The collector polls procfs and sysfs, so it
needs a result it can read after the event.
Let's expose the incoming deserialisation state and aggregate session
and file counts under /sys/class/misc/liveupdate/incoming/. A collector
can stop the rollout on state=failed or files_failed > 0, and track
files that were dropped or are still held. The session owner already
knows which VM or service it belongs to, so there is no need to expose
session names.
Signed-off-by: Chris Down <chris@chrisdown.name>
---
.../ABI/testing/sysfs-class-misc-liveupdate | 44 ++++++++++++++++
Documentation/userspace-api/liveupdate.rst | 7 +++
MAINTAINERS | 1 +
kernel/liveupdate/luo_core.c | 50 +++++++++++++++++++
kernel/liveupdate/luo_file.c | 16 +++++-
kernel/liveupdate/luo_internal.h | 34 +++++++++++++
kernel/liveupdate/luo_session.c | 6 +++
7 files changed, 156 insertions(+), 2 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-class-misc-liveupdate
diff --git a/Documentation/ABI/testing/sysfs-class-misc-liveupdate b/Documentation/ABI/testing/sysfs-class-misc-liveupdate
new file mode 100644
index 000000000000..25e3b49d7aeb
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-misc-liveupdate
@@ -0,0 +1,44 @@
+What: /sys/class/misc/liveupdate/incoming/
+Date: Nov 2026
+Contact: kexec@lists.infradead.org
+Description:
+ Incoming Live Update Orchestrator (LUO) state and counters.
+ Present when live update is enabled. Each attribute is read
+ independently.
+Users: system monitoring
+
+What: /sys/class/misc/liveupdate/incoming/state
+Date: Nov 2026
+Contact: kexec@lists.infradead.org
+Description: read only
+ Status of incoming LUO sessions:
+
+ "none" No sessions were handed over.
+ "pending" Deserialization has not finished. It
+ starts on the first open of /dev/liveupdate.
+ "deserialized" The sessions can be retrieved.
+ "failed" Deserialization failed. Opening
+ /dev/liveupdate returns -EIO.
+Users: system monitoring
+
+What: /sys/class/misc/liveupdate/incoming/sessions
+What: /sys/class/misc/liveupdate/incoming/files
+Date: Nov 2026
+Contact: kexec@lists.infradead.org
+Description: read only
+ Number of sessions deserialized and number of files in
+ those sessions, respectively. Counts can be partial while
+ state is "pending" or after it changes to "failed".
+Users: system monitoring
+
+What: /sys/class/misc/liveupdate/incoming/files_retrieved
+What: /sys/class/misc/liveupdate/incoming/files_failed
+What: /sys/class/misc/liveupdate/incoming/files_dropped
+Date: Nov 2026
+Contact: kexec@lists.infradead.org
+Description: read only
+ Number of successful retrieves, permanent retrieve failures,
+ and files discarded at session finish without a recorded
+ retrieve outcome, respectively. Retrieves and failures are
+ counted when they occur.
+Users: system monitoring
diff --git a/Documentation/userspace-api/liveupdate.rst b/Documentation/userspace-api/liveupdate.rst
index cdd74627ab75..39bbb91d0dd4 100644
--- a/Documentation/userspace-api/liveupdate.rst
+++ b/Documentation/userspace-api/liveupdate.rst
@@ -45,6 +45,13 @@ For details, see:
- `sd_pid_notify_with_fds(3)
<https://www.freedesktop.org/software/systemd/man/latest/sd_pid_notify_with_fds.html>`_
+Monitoring
+==========
+
+Read ``/sys/class/misc/liveupdate/incoming/`` to monitor incoming
+sessions and file retrievals without opening ``/dev/liveupdate``.
+See Documentation/ABI/testing/sysfs-class-misc-liveupdate for the sysfs ABI.
+
See Also
========
diff --git a/MAINTAINERS b/MAINTAINERS
index 1572f6c905aa..b98b95cd6d12 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15523,6 +15523,7 @@ L: kexec@lists.infradead.org
L: linux-kernel@vger.kernel.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
+F: Documentation/ABI/testing/sysfs-class-misc-liveupdate
F: Documentation/core-api/liveupdate.rst
F: Documentation/mm/memfd_preservation.rst
F: Documentation/userspace-api/liveupdate.rst
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 1b2bda22902d..ba174e9aae03 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -75,6 +75,8 @@ static struct {
*/
DECLARE_RWSEM(luo_register_rwlock);
+struct luo_incoming_stats luo_incoming_stats;
+
static int __init early_liveupdate_param(char *buf)
{
return kstrtobool(buf, &luo_global.enabled);
@@ -423,11 +425,59 @@ static const struct file_operations luo_fops = {
.unlocked_ioctl = luo_ioctl,
};
+static const char * const luo_incoming_state_names[] = {
+ [LUO_INCOMING_NONE] = "none",
+ [LUO_INCOMING_PENDING] = "pending",
+ [LUO_INCOMING_DESERIALIZED] = "deserialized",
+ [LUO_INCOMING_FAILED] = "failed",
+};
+
+static ssize_t state_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ enum luo_incoming_state state = READ_ONCE(luo_incoming_stats.state);
+
+ return sysfs_emit(buf, "%s\n", luo_incoming_state_names[state]);
+}
+static DEVICE_ATTR_RO(state);
+
+#define LUO_INCOMING_ATTR_RO(_name) \
+static ssize_t _name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ return sysfs_emit(buf, "%ld\n", \
+ atomic_long_read(&luo_incoming_stats._name)); \
+} \
+static DEVICE_ATTR_RO(_name)
+
+LUO_INCOMING_ATTR_RO(sessions);
+LUO_INCOMING_ATTR_RO(files);
+LUO_INCOMING_ATTR_RO(files_retrieved);
+LUO_INCOMING_ATTR_RO(files_failed);
+LUO_INCOMING_ATTR_RO(files_dropped);
+
+static struct attribute *luo_incoming_attrs[] = {
+ &dev_attr_state.attr,
+ &dev_attr_sessions.attr,
+ &dev_attr_files.attr,
+ &dev_attr_files_retrieved.attr,
+ &dev_attr_files_failed.attr,
+ &dev_attr_files_dropped.attr,
+ NULL
+};
+
+static const struct attribute_group luo_incoming_group = {
+ .name = "incoming",
+ .attrs = luo_incoming_attrs,
+};
+__ATTRIBUTE_GROUPS(luo_incoming);
+
static struct luo_device_state luo_dev = {
.miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "liveupdate",
.fops = &luo_fops,
+ .groups = luo_incoming_groups,
},
.in_use = ATOMIC_INIT(0),
};
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index a39992da504f..3aac1afa05c0 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -543,6 +543,16 @@ void luo_file_unfreeze(struct luo_file_set *file_set,
memset(file_set_ser, 0, sizeof(*file_set_ser));
}
+/* Record the first retrieve result. A negative status is permanent. */
+static void luo_file_set_retrieve_status(struct luo_file *luo_file, int status)
+{
+ luo_file->retrieve_status = status;
+ if (status > 0)
+ atomic_long_inc(&luo_incoming_stats.files_retrieved);
+ else
+ atomic_long_inc(&luo_incoming_stats.files_failed);
+}
+
/**
* luo_retrieve_file - Restores a preserved file from a file_set by its token.
* @file_set: The file_set from which to retrieve the file.
@@ -611,7 +621,7 @@ int luo_retrieve_file(struct luo_file_set *file_set, u64 token,
err = luo_file->fh->ops->retrieve(&args);
if (err) {
/* Keep the error code for later use. */
- luo_file->retrieve_status = err;
+ luo_file_set_retrieve_status(luo_file, err);
return err;
}
@@ -624,7 +634,7 @@ int luo_retrieve_file(struct luo_file_set *file_set, u64 token,
luo_file->file, GFP_KERNEL));
*filep = luo_file->file;
- luo_file->retrieve_status = 1;
+ luo_file_set_retrieve_status(luo_file, 1);
return 0;
}
@@ -719,6 +729,8 @@ int luo_file_finish(struct luo_file_set *file_set)
struct luo_file, list);
luo_file_finish_one(file_set, luo_file);
+ if (!luo_file->retrieve_status)
+ atomic_long_inc(&luo_incoming_stats.files_dropped);
if (luo_file->file) {
xa_erase(&luo_preserved_files,
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index dac6644bfb18..504963fc9898 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -77,6 +77,40 @@ struct luo_session {
extern struct rw_semaphore luo_register_rwlock;
+/**
+ * enum luo_incoming_state - Deserialization state of incoming sessions
+ * @LUO_INCOMING_NONE: No incoming sessions.
+ * @LUO_INCOMING_PENDING: Deserialization has not finished.
+ * @LUO_INCOMING_DESERIALIZED: Sessions are ready to retrieve.
+ * @LUO_INCOMING_FAILED: Deserialization failed.
+ */
+enum luo_incoming_state {
+ LUO_INCOMING_NONE,
+ LUO_INCOMING_PENDING,
+ LUO_INCOMING_DESERIALIZED,
+ LUO_INCOMING_FAILED,
+};
+
+/**
+ * struct luo_incoming_stats - Incoming session and file counters
+ * @state: Deserialization state.
+ * @sessions: Sessions deserialized.
+ * @files: Files in deserialized sessions.
+ * @files_retrieved: Files retrieved.
+ * @files_failed: Permanent retrieve failures.
+ * @files_dropped: Files discarded without a retrieve outcome.
+ */
+struct luo_incoming_stats {
+ enum luo_incoming_state state;
+ atomic_long_t sessions;
+ atomic_long_t files;
+ atomic_long_t files_retrieved;
+ atomic_long_t files_failed;
+ atomic_long_t files_dropped;
+};
+
+extern struct luo_incoming_stats luo_incoming_stats;
+
static inline struct liveupdate_session *luo_session_from_file_set(struct luo_file_set *file_set)
{
struct luo_session *session;
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 74bc66929f4b..1a40607bfc45 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -549,6 +549,7 @@ int __init luo_session_setup_incoming(u64 sessions_pa)
return err;
sh->active = true;
+ luo_incoming_stats.state = LUO_INCOMING_PENDING;
return 0;
}
@@ -583,6 +584,9 @@ static int luo_session_deserialize_one(struct luo_session_header *sh,
return err;
}
+ atomic_long_inc(&luo_incoming_stats.sessions);
+ atomic_long_add(session->file_set.count, &luo_incoming_stats.files);
+
return 0;
}
@@ -626,12 +630,14 @@ int luo_session_deserialize(void)
}
kho_block_set_destroy(&sh->block_set);
+ WRITE_ONCE(luo_incoming_stats.state, LUO_INCOMING_DESERIALIZED);
return 0;
save_err:
kho_block_set_destroy(&sh->block_set);
saved_err = err;
+ WRITE_ONCE(luo_incoming_stats.state, LUO_INCOMING_FAILED);
return err;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 4/9] selftests/liveupdate: Catch incorrect file accounting across kexec
2026-11-11 7:52 [PATCH 0/9] kho, liveupdate: KHO/LUO observability Chris Down
` (2 preceding siblings ...)
2026-11-11 7:53 ` [PATCH 3/9] liveupdate: Make failed retrieves distinguishable from dropped files Chris Down
@ 2026-11-11 7:53 ` Chris Down
2026-11-11 7:53 ` [PATCH 5/9] selftests/kho: Catch missing or zero bootmem reports before kexec Chris Down
` (4 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Chris Down @ 2026-11-11 7:53 UTC (permalink / raw)
To: Pasha Tatashin, Mike Rapoport, Pratyush Yadav
Cc: Alexander Graf, Baoquan He, Jonathan Corbet, Shuah Khan, kexec,
linux-mm, linux-kernel, linux-doc, linux-kselftest, kernel-team
Check that each retrieve is counted while the session is still open.
Finishing the session should count its unretrieved file as dropped,
while a file in an unclaimed session should still be held.
Signed-off-by: Chris Down <chris@chrisdown.name>
---
tools/testing/selftests/liveupdate/Makefile | 1 +
tools/testing/selftests/liveupdate/init.c | 5 +
.../selftests/liveupdate/luo_handover_stats.c | 198 ++++++++++++++++++
.../selftests/liveupdate/run-vmtests.sh | 1 +
tools/testing/selftests/liveupdate/vmtest.sh | 2 +
5 files changed, 207 insertions(+)
create mode 100644 tools/testing/selftests/liveupdate/luo_handover_stats.c
diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile
index 634211c66652..78a4419ff610 100644
--- a/tools/testing/selftests/liveupdate/Makefile
+++ b/tools/testing/selftests/liveupdate/Makefile
@@ -2,6 +2,7 @@
TEST_GEN_PROGS += liveupdate
+TEST_GEN_PROGS_EXTENDED += luo_handover_stats
TEST_GEN_PROGS_EXTENDED += luo_kexec_simple
TEST_GEN_PROGS_EXTENDED += luo_multi_session
TEST_GEN_PROGS_EXTENDED += luo_stress_sessions
diff --git a/tools/testing/selftests/liveupdate/init.c b/tools/testing/selftests/liveupdate/init.c
index fb08bd58b9b9..31bd8f0ec3a9 100644
--- a/tools/testing/selftests/liveupdate/init.c
+++ b/tools/testing/selftests/liveupdate/init.c
@@ -37,6 +37,11 @@ static int mount_filesystems(void)
return -1;
}
+ if (mount("sysfs", "/sys", "sysfs", 0, NULL) < 0) {
+ fprintf(stderr, "INIT: Failed to mount sysfs\n");
+ return -1;
+ }
+
return 0;
}
diff --git a/tools/testing/selftests/liveupdate/luo_handover_stats.c b/tools/testing/selftests/liveupdate/luo_handover_stats.c
new file mode 100644
index 000000000000..f18df79dda55
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/luo_handover_stats.c
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <libliveupdate.h>
+
+#define INCOMING_DIR "/sys/class/misc/liveupdate/incoming/"
+
+#define SESSION_CLAIMED "stats-claimed"
+#define SESSION_UNCLAIMED "stats-unclaimed"
+
+#define MFD_RETRIEVED_TOKEN 0x51
+#define MFD_DROPPED_TOKEN 0x52
+#define MFD_UNCLAIMED_TOKEN 0x53
+
+#define MFD_RETRIEVED_DATA "retrieved before finish"
+#define MFD_DROPPED_DATA "never retrieved"
+#define MFD_UNCLAIMED_DATA "session never retrieved"
+
+#define STATE_SESSION_NAME "handover_stats_state"
+#define STATE_MEMFD_TOKEN 997
+
+/* The first open starts deserialization. */
+static char state_before_open[16];
+
+static void read_incoming(const char *name, char *buf, size_t len)
+{
+ char path[128];
+ ssize_t n;
+ int fd;
+
+ snprintf(path, sizeof(path), INCOMING_DIR "%s", name);
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ fail_exit("open %s", path);
+
+ n = read(fd, buf, len - 1);
+ if (n < 0)
+ fail_exit("read %s", path);
+ close(fd);
+
+ /* A full buffer could hide the rest of a malformed value */
+ if ((size_t)n == len - 1)
+ ksft_exit_fail_msg("%s is longer than %zu bytes\n", path,
+ len - 2);
+
+ buf[n] = '\0';
+ if (n > 0 && buf[n - 1] == '\n')
+ buf[n - 1] = '\0';
+}
+
+static void expect_state(const char *state, const char *expected)
+{
+ if (strcmp(state, expected))
+ ksft_exit_fail_msg("state is '%s', expected '%s'\n", state,
+ expected);
+}
+
+static unsigned long long read_number(const char *name)
+{
+ unsigned long long val = 0;
+ char buf[32];
+ char *p;
+
+ read_incoming(name, buf, sizeof(buf));
+ for (p = buf; *p >= '0' && *p <= '9'; p++) {
+ if (val > (ULLONG_MAX - (*p - '0')) / 10)
+ break;
+ val = val * 10 + (*p - '0');
+ }
+ if (p == buf || *p)
+ ksft_exit_fail_msg("%s%s reads '%s', not a 64-bit unsigned number\n",
+ INCOMING_DIR, name, buf);
+ return val;
+}
+
+static void expect_count(const char *name, unsigned long long expected)
+{
+ unsigned long long val = read_number(name);
+
+ if (val != expected)
+ ksft_exit_fail_msg("%s is %llu, expected %llu\n", name, val,
+ expected);
+}
+
+static void expect_counts(long retrieved, long dropped)
+{
+ char state[16];
+
+ read_incoming("state", state, sizeof(state));
+ expect_state(state, "deserialized");
+
+ /* Three sessions, including the one carrying the test stage. */
+ expect_count("sessions", 3);
+ expect_count("files", 4);
+ expect_count("files_retrieved", retrieved);
+ expect_count("files_failed", 0);
+ expect_count("files_dropped", dropped);
+}
+
+static void run_stage_1(int luo_fd)
+{
+ int claimed_fd, unclaimed_fd;
+
+ ksft_print_msg("[STAGE 1] Checking state on a cold boot...\n");
+ expect_state(state_before_open, "none");
+ expect_count("sessions", 0);
+ expect_count("files", 0);
+
+ create_state_file(luo_fd, STATE_SESSION_NAME, STATE_MEMFD_TOKEN, 2);
+
+ ksft_print_msg("[STAGE 1] Preserving two memfds in '%s'...\n",
+ SESSION_CLAIMED);
+ claimed_fd = luo_create_session(luo_fd, SESSION_CLAIMED);
+ if (claimed_fd < 0)
+ fail_exit("luo_create_session for '%s'", SESSION_CLAIMED);
+ if (create_and_preserve_memfd(claimed_fd, MFD_RETRIEVED_TOKEN,
+ MFD_RETRIEVED_DATA) < 0)
+ fail_exit("create_and_preserve_memfd for token %#x",
+ MFD_RETRIEVED_TOKEN);
+ if (create_and_preserve_memfd(claimed_fd, MFD_DROPPED_TOKEN,
+ MFD_DROPPED_DATA) < 0)
+ fail_exit("create_and_preserve_memfd for token %#x",
+ MFD_DROPPED_TOKEN);
+
+ ksft_print_msg("[STAGE 1] Preserving one memfd in '%s'...\n",
+ SESSION_UNCLAIMED);
+ unclaimed_fd = luo_create_session(luo_fd, SESSION_UNCLAIMED);
+ if (unclaimed_fd < 0)
+ fail_exit("luo_create_session for '%s'", SESSION_UNCLAIMED);
+ if (create_and_preserve_memfd(unclaimed_fd, MFD_UNCLAIMED_TOKEN,
+ MFD_UNCLAIMED_DATA) < 0)
+ fail_exit("create_and_preserve_memfd for token %#x",
+ MFD_UNCLAIMED_TOKEN);
+
+ close(luo_fd);
+ daemonize_and_wait();
+}
+
+static void run_stage_2(int luo_fd, int state_session_fd)
+{
+ int claimed_fd, mfd, stage;
+
+ ksft_print_msg("[STAGE 2] Checking state before and after the first open...\n");
+ expect_state(state_before_open, "pending");
+ expect_counts(0, 0);
+
+ restore_and_read_stage(state_session_fd, STATE_MEMFD_TOKEN, &stage);
+ if (stage != 2)
+ fail_exit("Expected stage 2, but state file contains %d", stage);
+
+ /* The retrieve must count before the session finishes. */
+ expect_counts(1, 0);
+
+ ksft_print_msg("[STAGE 2] Retrieving one of the two memfds in '%s'...\n",
+ SESSION_CLAIMED);
+ claimed_fd = luo_retrieve_session(luo_fd, SESSION_CLAIMED);
+ if (claimed_fd < 0)
+ fail_exit("luo_retrieve_session for '%s'", SESSION_CLAIMED);
+
+ mfd = restore_and_verify_memfd(claimed_fd, MFD_RETRIEVED_TOKEN,
+ MFD_RETRIEVED_DATA);
+ if (mfd < 0)
+ fail_exit("restore_and_verify_memfd for token %#x",
+ MFD_RETRIEVED_TOKEN);
+ close(mfd);
+ expect_counts(2, 0);
+
+ ksft_print_msg("[STAGE 2] Finishing '%s'...\n", SESSION_CLAIMED);
+ if (luo_session_finish(claimed_fd) < 0)
+ fail_exit("luo_session_finish for '%s'", SESSION_CLAIMED);
+ close(claimed_fd);
+
+ expect_counts(2, 1);
+
+ ksft_print_msg("[STAGE 2] Finishing the state session...\n");
+ if (luo_session_finish(state_session_fd) < 0)
+ fail_exit("luo_session_finish for state session");
+ close(state_session_fd);
+
+ /* Finishing the state session must not drop anything. */
+ expect_counts(2, 1);
+
+ ksft_print_msg("\n--- HANDOVER STATS TEST PASSED ---\n");
+}
+
+int main(int argc, char *argv[])
+{
+ read_incoming("state", state_before_open, sizeof(state_before_open));
+
+ return luo_test(argc, argv, STATE_SESSION_NAME,
+ run_stage_1, run_stage_2);
+}
diff --git a/tools/testing/selftests/liveupdate/run-vmtests.sh b/tools/testing/selftests/liveupdate/run-vmtests.sh
index 6f592aec0f85..bce7679279d4 100755
--- a/tools/testing/selftests/liveupdate/run-vmtests.sh
+++ b/tools/testing/selftests/liveupdate/run-vmtests.sh
@@ -16,6 +16,7 @@ failed=0
skipped=0
TEST_NAMES=(
+ "luo_handover_stats"
"luo_kexec_simple"
"luo_multi_session"
"luo_stress_files"
diff --git a/tools/testing/selftests/liveupdate/vmtest.sh b/tools/testing/selftests/liveupdate/vmtest.sh
index dea5a6fb2abc..76ffea7d4eb8 100755
--- a/tools/testing/selftests/liveupdate/vmtest.sh
+++ b/tools/testing/selftests/liveupdate/vmtest.sh
@@ -145,6 +145,7 @@ function mkinitrd() {
cat > "$workspace_dir/cpio_list_inner" <<EOF
dir /dev 0755 0 0
dir /proc 0755 0 0
+dir /sys 0755 0 0
dir /debugfs 0755 0 0
nod /dev/console 0600 0 0 c 5 1
file /init $workspace_dir/init 0755 0 0
@@ -157,6 +158,7 @@ EOF
cat > "$workspace_dir/cpio_list" <<EOF
dir /dev 0755 0 0
dir /proc 0755 0 0
+dir /sys 0755 0 0
dir /debugfs 0755 0 0
nod /dev/console 0600 0 0 c 5 1
file /init $workspace_dir/init 0755 0 0
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 5/9] selftests/kho: Catch missing or zero bootmem reports before kexec
2026-11-11 7:52 [PATCH 0/9] kho, liveupdate: KHO/LUO observability Chris Down
` (3 preceding siblings ...)
2026-11-11 7:53 ` [PATCH 4/9] selftests/liveupdate: Catch incorrect file accounting across kexec Chris Down
@ 2026-11-11 7:53 ` Chris Down
2026-11-11 7:53 ` [PATCH 6/9] kho: Expose rejected handovers that look like cold boots Chris Down
` (3 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Chris Down @ 2026-11-11 7:53 UTC (permalink / raw)
To: Pasha Tatashin, Mike Rapoport, Pratyush Yadav
Cc: Alexander Graf, Baoquan He, Jonathan Corbet, Shuah Khan, kexec,
linux-mm, linux-kernel, linux-doc, linux-kselftest, kernel-team
The KHO vmtest can pass even if the bootmem size is no longer visible to
userspace. Let's fail if /sys/kernel/kexec/kho_bootmem_size can't be
read or reports zero on the KHO-enabled boot.
Signed-off-by: Chris Down <chris@chrisdown.name>
---
tools/testing/selftests/kho/init.c | 24 ++++++++++++++++++++++++
tools/testing/selftests/kho/vmtest.sh | 1 +
2 files changed, 25 insertions(+)
diff --git a/tools/testing/selftests/kho/init.c b/tools/testing/selftests/kho/init.c
index 88a41b6eba95..ca9297d896d3 100644
--- a/tools/testing/selftests/kho/init.c
+++ b/tools/testing/selftests/kho/init.c
@@ -18,9 +18,30 @@ static int mount_filesystems(void)
if (mount("debugfs", "/debugfs", "debugfs", 0, NULL) < 0)
return -1;
+ if (mount("sysfs", "/sys", "sysfs", 0, NULL) < 0)
+ return -1;
+
return mount("proc", "/proc", "proc", 0, NULL);
}
+static int check_bootmem_size(void)
+{
+ char buf[32];
+ ssize_t len;
+ int fd;
+
+ fd = open("/sys/kernel/kexec/kho_bootmem_size", O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ len = read(fd, buf, sizeof(buf));
+ close(fd);
+ if (len <= 0)
+ return -1;
+
+ return buf[0] == '0' ? -1 : 0;
+}
+
static long kexec_file_load(int kernel_fd, int initrd_fd,
unsigned long cmdline_len, const char *cmdline,
unsigned long flags)
@@ -61,6 +82,9 @@ int main(int argc, char *argv[])
if (mount_filesystems())
goto err_reboot;
+ if (check_bootmem_size())
+ goto err_reboot;
+
if (kexec_load())
goto err_reboot;
diff --git a/tools/testing/selftests/kho/vmtest.sh b/tools/testing/selftests/kho/vmtest.sh
index 1e86ffcc3a79..e3e99cfe3ac1 100755
--- a/tools/testing/selftests/kho/vmtest.sh
+++ b/tools/testing/selftests/kho/vmtest.sh
@@ -95,6 +95,7 @@ function mkinitrd() {
dir /dev 0755 0 0
dir /proc 0755 0 0
dir /debugfs 0755 0 0
+dir /sys 0755 0 0
nod /dev/console 0600 0 0 c 5 1
file /init $tmp_dir/init 0755 0 0
file /kernel $kernel 0644 0 0
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 6/9] kho: Expose rejected handovers that look like cold boots
2026-11-11 7:52 [PATCH 0/9] kho, liveupdate: KHO/LUO observability Chris Down
` (4 preceding siblings ...)
2026-11-11 7:53 ` [PATCH 5/9] selftests/kho: Catch missing or zero bootmem reports before kexec Chris Down
@ 2026-11-11 7:53 ` Chris Down
2026-11-11 7:53 ` [PATCH 7/9] kho: Report how much handover memory is preserved and restored Chris Down
` (2 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Chris Down @ 2026-11-11 7:53 UTC (permalink / raw)
To: Pasha Tatashin, Mike Rapoport, Pratyush Yadav
Cc: Alexander Graf, Baoquan He, Jonathan Corbet, Shuah Khan, kexec,
linux-mm, linux-kernel, linux-doc, linux-kselftest, kernel-team
At Meta, our rollout needs to stop if the new kernel rejects the KHO
handover. For example, if it doesn't recognise the handover FDT's
compatible string, it can boot normally with all the preserved state
lost. LUO's incoming/state reads "none", as on a cold boot. We log
"disabling KHO revival", but our fleet collector reads procfs and sysfs
and can't rely on kernel logs from every host.
Let's add /sys/kernel/kexec/kho_incoming/state so the collector can tell
a rejected handover from an ordinary boot. "revived" means the kernel
accepted the handover and completed the preserved memory map walk. LUO
reports the outcome of restoring sessions separately.
Signed-off-by: Chris Down <chris@chrisdown.name>
---
.../ABI/testing/sysfs-class-misc-liveupdate | 3 ++
.../ABI/testing/sysfs-kernel-kexec-kdump | 12 +++++++
Documentation/admin-guide/mm/kho.rst | 3 ++
include/linux/kexec_handover.h | 2 ++
kernel/kexec_core.c | 13 ++++++-
kernel/liveupdate/kexec_handover.c | 34 +++++++++++++++++++
6 files changed, 66 insertions(+), 1 deletion(-)
diff --git a/Documentation/ABI/testing/sysfs-class-misc-liveupdate b/Documentation/ABI/testing/sysfs-class-misc-liveupdate
index 25e3b49d7aeb..608f788ef0fa 100644
--- a/Documentation/ABI/testing/sysfs-class-misc-liveupdate
+++ b/Documentation/ABI/testing/sysfs-class-misc-liveupdate
@@ -19,6 +19,9 @@ Description: read only
"deserialized" The sessions can be retrieved.
"failed" Deserialization failed. Opening
/dev/liveupdate returns -EIO.
+
+ See /sys/kernel/kexec/kho_incoming/state for the status of
+ the underlying KHO handover.
Users: system monitoring
What: /sys/class/misc/liveupdate/incoming/sessions
diff --git a/Documentation/ABI/testing/sysfs-kernel-kexec-kdump b/Documentation/ABI/testing/sysfs-kernel-kexec-kdump
index 80941a7d8a7c..5636cd060cb2 100644
--- a/Documentation/ABI/testing/sysfs-kernel-kexec-kdump
+++ b/Documentation/ABI/testing/sysfs-kernel-kexec-kdump
@@ -67,3 +67,15 @@ Description: read only
Total size of the KHO bootmem regions in bytes. Returns 0
when KHO is disabled.
User: system monitoring
+
+What: /sys/kernel/kexec/kho_incoming/state
+Date: Nov 2026
+Contact: kexec@lists.infradead.org
+Description: read only
+ Status of the incoming KHO handover:
+
+ "none" No handover was passed to this kernel.
+ "revived" The kernel accepted the handover and completed
+ the walk of its preserved memory map.
+ "failed" The kernel rejected the handover.
+User: system monitoring
diff --git a/Documentation/admin-guide/mm/kho.rst b/Documentation/admin-guide/mm/kho.rst
index c329d720bf25..9d74d4ce18b5 100644
--- a/Documentation/admin-guide/mm/kho.rst
+++ b/Documentation/admin-guide/mm/kho.rst
@@ -88,6 +88,9 @@ sysfs Interfaces
Total size of the KHO bootmem regions in bytes, or 0 when KHO is
disabled.
+``/sys/kernel/kexec/kho_incoming/state``
+ Status of the incoming KHO handover.
+
See Documentation/ABI/testing/sysfs-kernel-kexec-kdump for the sysfs ABI.
debugfs Interfaces
diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
index 736d51e79fd2..1f37d2300e26 100644
--- a/include/linux/kexec_handover.h
+++ b/include/linux/kexec_handover.h
@@ -42,6 +42,8 @@ void kho_populate(phys_addr_t fdt_phys, u64 fdt_len, phys_addr_t bootmem_phys,
bool kho_bootmem_overlap(phys_addr_t phys, size_t size);
phys_addr_t kho_bootmem_size(void);
+extern const struct attribute_group kho_incoming_attr_group;
+
static inline enum migratetype kho_bootmem_migratetype(unsigned long pfn,
enum migratetype mt)
{
diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
index ed105e6351eb..c1085a53b7f4 100644
--- a/kernel/kexec_core.c
+++ b/kernel/kexec_core.c
@@ -1372,7 +1372,18 @@ static struct kexec_link_entry kexec_links[] = {
};
static struct kobject *kexec_kobj;
-ATTRIBUTE_GROUPS(kexec);
+
+static const struct attribute_group kexec_group = {
+ .attrs = kexec_attrs,
+};
+
+static const struct attribute_group *kexec_groups[] = {
+ &kexec_group,
+#ifdef CONFIG_KEXEC_HANDOVER
+ &kho_incoming_attr_group,
+#endif
+ NULL
+};
static int __init init_kexec_sysctl(void)
{
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 21d77e29d2bb..dfd6fa7e4a71 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -19,6 +19,7 @@
#include <linux/kexec.h>
#include <linux/kexec_handover.h>
#include <linux/kho_radix_tree.h>
+#include <linux/kobject.h>
#include <linux/utsname.h>
#include <linux/kho/abi/kexec_handover.h>
#include <linux/kho/abi/kexec_metadata.h>
@@ -97,11 +98,18 @@ static struct kho_out kho_out = {
},
};
+enum kho_incoming_state {
+ KHO_INCOMING_NONE,
+ KHO_INCOMING_REVIVED,
+ KHO_INCOMING_FAILED,
+};
+
struct kho_in {
phys_addr_t fdt_phys;
phys_addr_t bootmem_phys;
char previous_release[__NEW_UTS_LEN + 1];
u32 kexec_count;
+ enum kho_incoming_state state;
struct kho_debugfs dbg;
struct kho_radix_tree radix_tree;
};
@@ -957,6 +965,29 @@ phys_addr_t kho_bootmem_size(void)
return size;
}
+static const char * const kho_incoming_state_names[] = {
+ [KHO_INCOMING_NONE] = "none",
+ [KHO_INCOMING_REVIVED] = "revived",
+ [KHO_INCOMING_FAILED] = "failed",
+};
+
+static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n", kho_incoming_state_names[kho_in.state]);
+}
+static struct kobj_attribute state_attr = __ATTR_RO(state);
+
+static struct attribute *kho_incoming_attrs[] = {
+ &state_attr.attr,
+ NULL
+};
+
+const struct attribute_group kho_incoming_attr_group = {
+ .name = "kho_incoming",
+ .attrs = kho_incoming_attrs,
+};
+
/*
* Look for free blocks of 1G. This is a heuristic chosen to work efficiently
* with large systems with hundreds of gigabytes of memory. It will work poorly
@@ -1753,6 +1784,7 @@ static void __init kho_mem_retrieve(void)
*/
kho_in.fdt_phys = 0;
kho_in.radix_tree.root = NULL;
+ kho_in.state = KHO_INCOMING_FAILED;
}
static __init int kho_out_fdt_setup(void)
@@ -1992,6 +2024,7 @@ void __init kho_memory_init_early(void)
*/
kho_in.fdt_phys = 0;
kho_in.bootmem_phys = 0;
+ kho_in.state = KHO_INCOMING_FAILED;
}
void __init kho_memory_init(void)
@@ -2090,6 +2123,7 @@ void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len,
unmap_fdt:
early_memunmap(fdt, fdt_len);
report:
+ kho_in.state = populated ? KHO_INCOMING_REVIVED : KHO_INCOMING_FAILED;
if (!populated)
pr_warn("disabling KHO revival\n");
}
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 7/9] kho: Report how much handover memory is preserved and restored
2026-11-11 7:52 [PATCH 0/9] kho, liveupdate: KHO/LUO observability Chris Down
` (5 preceding siblings ...)
2026-11-11 7:53 ` [PATCH 6/9] kho: Expose rejected handovers that look like cold boots Chris Down
@ 2026-11-11 7:53 ` Chris Down
2026-11-11 7:53 ` [PATCH 8/9] liveupdate: Expose the sequence number for consecutive live updates Chris Down
2026-11-11 7:53 ` [PATCH 9/9] selftests/liveupdate: Check handover revival, sequence and memory stats Chris Down
8 siblings, 0 replies; 12+ messages in thread
From: Chris Down @ 2026-11-11 7:53 UTC (permalink / raw)
To: Pasha Tatashin, Mike Rapoport, Pratyush Yadav
Cc: Alexander Graf, Baoquan He, Jonathan Corbet, Shuah Khan, kexec,
linux-mm, linux-kernel, linux-doc, linux-kselftest, kernel-team
Most memory the previous kernel preserved stays reserved until its user
restores it through KHO, either to use it again or to free it. If
nothing does, it stays reserved for as long as the kernel runs. On a
hypervisor host that can be gigabytes, but amusingly nothing really
shows it reliably. The host simply has less memory than it should, which
is not exactly obvious.
All preserved memory is reserved in kho_preserved_memory_reserve(), and
everything restored through KHO goes through kho_restore_page(), so KHO
can count both easily. Let's add preserved_size and restored_size, in
bytes, to /sys/kernel/kexec/kho_incoming/.
For example, after a live update preserving a 64 MiB memfd:
preserved_size restored_size
before retrieval 67403776 8192
after retrieval 67403776 67395584
The 8 KiB left are the incoming KHO FDT and LUO's FLB header.
Signed-off-by: Chris Down <chris@chrisdown.name>
---
.../ABI/testing/sysfs-kernel-kexec-kdump | 10 ++++++++
Documentation/admin-guide/mm/kho.rst | 3 +++
kernel/liveupdate/kexec_handover.c | 23 +++++++++++++++++++
3 files changed, 36 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-kernel-kexec-kdump b/Documentation/ABI/testing/sysfs-kernel-kexec-kdump
index 5636cd060cb2..2b9f9917d2d4 100644
--- a/Documentation/ABI/testing/sysfs-kernel-kexec-kdump
+++ b/Documentation/ABI/testing/sysfs-kernel-kexec-kdump
@@ -79,3 +79,13 @@ Description: read only
the walk of its preserved memory map.
"failed" The kernel rejected the handover.
User: system monitoring
+
+What: /sys/kernel/kexec/kho_incoming/preserved_size
+What: /sys/kernel/kexec/kho_incoming/restored_size
+Date: Nov 2026
+Contact: kexec@lists.infradead.org
+Description: read only
+ Bytes preserved by the previous kernel and bytes restored
+ through KHO, respectively. Both are 0 unless the incoming
+ state is "revived".
+User: system monitoring
diff --git a/Documentation/admin-guide/mm/kho.rst b/Documentation/admin-guide/mm/kho.rst
index 9d74d4ce18b5..37a62daa7622 100644
--- a/Documentation/admin-guide/mm/kho.rst
+++ b/Documentation/admin-guide/mm/kho.rst
@@ -91,6 +91,9 @@ sysfs Interfaces
``/sys/kernel/kexec/kho_incoming/state``
Status of the incoming KHO handover.
+``/sys/kernel/kexec/kho_incoming/preserved_size`` and ``restored_size``
+ Bytes preserved by the previous kernel and bytes restored through KHO.
+
See Documentation/ABI/testing/sysfs-kernel-kexec-kdump for the sysfs ABI.
debugfs Interfaces
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index dfd6fa7e4a71..fe6b66270124 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -110,6 +110,8 @@ struct kho_in {
char previous_release[__NEW_UTS_LEN + 1];
u32 kexec_count;
enum kho_incoming_state state;
+ unsigned long preserved_pages;
+ atomic_long_t restored_pages;
struct kho_debugfs dbg;
struct kho_radix_tree radix_tree;
};
@@ -552,6 +554,7 @@ static struct page *kho_restore_page(phys_addr_t phys, bool is_folio,
kho_init_pages(page, nr_pages);
adjust_managed_page_count(page, nr_pages);
+ atomic_long_add(nr_pages, &kho_in.restored_pages);
return page;
}
@@ -643,6 +646,7 @@ static int __init kho_preserved_memory_reserve(unsigned long key, void *data)
info.magic = KHO_PAGE_MAGIC;
info.order = order;
page->private = info.page_private;
+ kho_in.preserved_pages += 1UL << order;
return 0;
}
@@ -978,8 +982,26 @@ static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr,
}
static struct kobj_attribute state_attr = __ATTR_RO(state);
+static ssize_t preserved_size_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "%llu\n", (u64)kho_in.preserved_pages << PAGE_SHIFT);
+}
+static struct kobj_attribute preserved_size_attr = __ATTR_RO(preserved_size);
+
+static ssize_t restored_size_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ u64 pages = atomic_long_read(&kho_in.restored_pages);
+
+ return sysfs_emit(buf, "%llu\n", pages << PAGE_SHIFT);
+}
+static struct kobj_attribute restored_size_attr = __ATTR_RO(restored_size);
+
static struct attribute *kho_incoming_attrs[] = {
&state_attr.attr,
+ &preserved_size_attr.attr,
+ &restored_size_attr.attr,
NULL
};
@@ -1785,6 +1807,7 @@ static void __init kho_mem_retrieve(void)
kho_in.fdt_phys = 0;
kho_in.radix_tree.root = NULL;
kho_in.state = KHO_INCOMING_FAILED;
+ kho_in.preserved_pages = 0;
}
static __init int kho_out_fdt_setup(void)
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 8/9] liveupdate: Expose the sequence number for consecutive live updates
2026-11-11 7:52 [PATCH 0/9] kho, liveupdate: KHO/LUO observability Chris Down
` (6 preceding siblings ...)
2026-11-11 7:53 ` [PATCH 7/9] kho: Report how much handover memory is preserved and restored Chris Down
@ 2026-11-11 7:53 ` Chris Down
2026-11-11 7:53 ` [PATCH 9/9] selftests/liveupdate: Check handover revival, sequence and memory stats Chris Down
8 siblings, 0 replies; 12+ messages in thread
From: Chris Down @ 2026-11-11 7:53 UTC (permalink / raw)
To: Pasha Tatashin, Mike Rapoport, Pratyush Yadav
Cc: Alexander Graf, Baoquan He, Jonathan Corbet, Shuah Khan, kexec,
linux-mm, linux-kernel, linux-doc, linux-kselftest, kernel-team
Export LUO's live update sequence number, so that fleet monitoring can
compare hosts on their first live update with hosts that have been
through several.
We want this because some bugs, like memory corruption, may only show up
after several live updates in a row.
Signed-off-by: Chris Down <chris@chrisdown.name>
---
Documentation/ABI/testing/sysfs-class-misc-liveupdate | 9 +++++++++
Documentation/userspace-api/liveupdate.rst | 1 +
kernel/liveupdate/luo_core.c | 8 ++++++++
3 files changed, 18 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-class-misc-liveupdate b/Documentation/ABI/testing/sysfs-class-misc-liveupdate
index 608f788ef0fa..b0ba0f626793 100644
--- a/Documentation/ABI/testing/sysfs-class-misc-liveupdate
+++ b/Documentation/ABI/testing/sysfs-class-misc-liveupdate
@@ -24,6 +24,15 @@ Description: read only
the underlying KHO handover.
Users: system monitoring
+What: /sys/class/misc/liveupdate/incoming/liveupdate_num
+Date: Nov 2026
+Contact: kexec@lists.infradead.org
+Description: read only
+ LUO sequence number passed by the previous kernel. Reads 0
+ if no LUO state was handed over, 1 after the first live
+ update in a sequence, and increments with each update.
+Users: system monitoring
+
What: /sys/class/misc/liveupdate/incoming/sessions
What: /sys/class/misc/liveupdate/incoming/files
Date: Nov 2026
diff --git a/Documentation/userspace-api/liveupdate.rst b/Documentation/userspace-api/liveupdate.rst
index 39bbb91d0dd4..61c246371200 100644
--- a/Documentation/userspace-api/liveupdate.rst
+++ b/Documentation/userspace-api/liveupdate.rst
@@ -50,6 +50,7 @@ Monitoring
Read ``/sys/class/misc/liveupdate/incoming/`` to monitor incoming
sessions and file retrievals without opening ``/dev/liveupdate``.
+``liveupdate_num`` gives the LUO sequence number for the current boot.
See Documentation/ABI/testing/sysfs-class-misc-liveupdate for the sysfs ABI.
See Also
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index ba174e9aae03..3ca7b3875247 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -441,6 +441,13 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr,
}
static DEVICE_ATTR_RO(state);
+static ssize_t liveupdate_num_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "%llu\n", luo_global.liveupdate_num);
+}
+static DEVICE_ATTR_RO(liveupdate_num);
+
#define LUO_INCOMING_ATTR_RO(_name) \
static ssize_t _name##_show(struct device *dev, \
struct device_attribute *attr, char *buf) \
@@ -458,6 +465,7 @@ LUO_INCOMING_ATTR_RO(files_dropped);
static struct attribute *luo_incoming_attrs[] = {
&dev_attr_state.attr,
+ &dev_attr_liveupdate_num.attr,
&dev_attr_sessions.attr,
&dev_attr_files.attr,
&dev_attr_files_retrieved.attr,
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 9/9] selftests/liveupdate: Check handover revival, sequence and memory stats
2026-11-11 7:52 [PATCH 0/9] kho, liveupdate: KHO/LUO observability Chris Down
` (7 preceding siblings ...)
2026-11-11 7:53 ` [PATCH 8/9] liveupdate: Expose the sequence number for consecutive live updates Chris Down
@ 2026-11-11 7:53 ` Chris Down
8 siblings, 0 replies; 12+ messages in thread
From: Chris Down @ 2026-11-11 7:53 UTC (permalink / raw)
To: Pasha Tatashin, Mike Rapoport, Pratyush Yadav
Cc: Alexander Graf, Baoquan He, Jonathan Corbet, Shuah Khan, kexec,
linux-mm, linux-kernel, linux-doc, linux-kselftest, kernel-team
Extend the handover stats test to cover the new files. On the first
boot, kho_incoming/state should be none, liveupdate_num 0, and both KHO
sizes 0. After the kexec, the handover should be revived and
liveupdate_num should be 1.
restored_size should grow by at least a page each time a memfd is
retrieved or dropped. The memfd in the unclaimed session is retrieved
last, once everything else is done.
Signed-off-by: Chris Down <chris@chrisdown.name>
---
.../selftests/liveupdate/luo_handover_stats.c | 74 +++++++++++++++++--
1 file changed, 67 insertions(+), 7 deletions(-)
diff --git a/tools/testing/selftests/liveupdate/luo_handover_stats.c b/tools/testing/selftests/liveupdate/luo_handover_stats.c
index f18df79dda55..2863630b823e 100644
--- a/tools/testing/selftests/liveupdate/luo_handover_stats.c
+++ b/tools/testing/selftests/liveupdate/luo_handover_stats.c
@@ -10,6 +10,7 @@
#include <libliveupdate.h>
#define INCOMING_DIR "/sys/class/misc/liveupdate/incoming/"
+#define KHO_INCOMING_DIR "/sys/kernel/kexec/kho_incoming/"
#define SESSION_CLAIMED "stats-claimed"
#define SESSION_UNCLAIMED "stats-unclaimed"
@@ -28,13 +29,13 @@
/* The first open starts deserialization. */
static char state_before_open[16];
-static void read_incoming(const char *name, char *buf, size_t len)
+static void read_sysfs(const char *dir, const char *name, char *buf, size_t len)
{
char path[128];
ssize_t n;
int fd;
- snprintf(path, sizeof(path), INCOMING_DIR "%s", name);
+ snprintf(path, sizeof(path), "%s%s", dir, name);
fd = open(path, O_RDONLY);
if (fd < 0)
fail_exit("open %s", path);
@@ -54,6 +55,11 @@ static void read_incoming(const char *name, char *buf, size_t len)
buf[n - 1] = '\0';
}
+static void read_incoming(const char *name, char *buf, size_t len)
+{
+ read_sysfs(INCOMING_DIR, name, buf, len);
+}
+
static void expect_state(const char *state, const char *expected)
{
if (strcmp(state, expected))
@@ -61,13 +67,13 @@ static void expect_state(const char *state, const char *expected)
expected);
}
-static unsigned long long read_number(const char *name)
+static unsigned long long read_number(const char *dir, const char *name)
{
unsigned long long val = 0;
char buf[32];
char *p;
- read_incoming(name, buf, sizeof(buf));
+ read_sysfs(dir, name, buf, sizeof(buf));
for (p = buf; *p >= '0' && *p <= '9'; p++) {
if (val > (ULLONG_MAX - (*p - '0')) / 10)
break;
@@ -75,19 +81,45 @@ static unsigned long long read_number(const char *name)
}
if (p == buf || *p)
ksft_exit_fail_msg("%s%s reads '%s', not a 64-bit unsigned number\n",
- INCOMING_DIR, name, buf);
+ dir, name, buf);
return val;
}
static void expect_count(const char *name, unsigned long long expected)
{
- unsigned long long val = read_number(name);
+ unsigned long long val = read_number(INCOMING_DIR, name);
if (val != expected)
ksft_exit_fail_msg("%s is %llu, expected %llu\n", name, val,
expected);
}
+static void expect_kho_state(const char *expected)
+{
+ char state[16];
+
+ read_sysfs(KHO_INCOMING_DIR, "state", state, sizeof(state));
+ if (strcmp(state, expected))
+ ksft_exit_fail_msg("kho_incoming/state is '%s', expected '%s'\n",
+ state, expected);
+}
+
+static unsigned long long kho_size(const char *name)
+{
+ return read_number(KHO_INCOMING_DIR, name);
+}
+
+static unsigned long long expect_more_restored(unsigned long long prev)
+{
+ unsigned long long restored = kho_size("restored_size");
+
+ if (restored < prev + getpagesize() ||
+ restored > kho_size("preserved_size"))
+ ksft_exit_fail_msg("restored_size went from %llu to %llu\n",
+ prev, restored);
+ return restored;
+}
+
static void expect_counts(long retrieved, long dropped)
{
char state[16];
@@ -111,6 +143,10 @@ static void run_stage_1(int luo_fd)
expect_state(state_before_open, "none");
expect_count("sessions", 0);
expect_count("files", 0);
+ expect_count("liveupdate_num", 0);
+ expect_kho_state("none");
+ if (kho_size("preserved_size") || kho_size("restored_size"))
+ ksft_exit_fail_msg("KHO sizes aren't 0 on a cold boot\n");
create_state_file(luo_fd, STATE_SESSION_NAME, STATE_MEMFD_TOKEN, 2);
@@ -144,11 +180,16 @@ static void run_stage_1(int luo_fd)
static void run_stage_2(int luo_fd, int state_session_fd)
{
- int claimed_fd, mfd, stage;
+ int claimed_fd, unclaimed_fd, mfd, stage;
+ unsigned long long restored;
ksft_print_msg("[STAGE 2] Checking state before and after the first open...\n");
expect_state(state_before_open, "pending");
expect_counts(0, 0);
+ expect_count("liveupdate_num", 1);
+ expect_kho_state("revived");
+ if (!kho_size("preserved_size"))
+ ksft_exit_fail_msg("kho_incoming/preserved_size is 0\n");
restore_and_read_stage(state_session_fd, STATE_MEMFD_TOKEN, &stage);
if (stage != 2)
@@ -163,6 +204,7 @@ static void run_stage_2(int luo_fd, int state_session_fd)
if (claimed_fd < 0)
fail_exit("luo_retrieve_session for '%s'", SESSION_CLAIMED);
+ restored = kho_size("restored_size");
mfd = restore_and_verify_memfd(claimed_fd, MFD_RETRIEVED_TOKEN,
MFD_RETRIEVED_DATA);
if (mfd < 0)
@@ -170,6 +212,7 @@ static void run_stage_2(int luo_fd, int state_session_fd)
MFD_RETRIEVED_TOKEN);
close(mfd);
expect_counts(2, 0);
+ restored = expect_more_restored(restored);
ksft_print_msg("[STAGE 2] Finishing '%s'...\n", SESSION_CLAIMED);
if (luo_session_finish(claimed_fd) < 0)
@@ -177,6 +220,7 @@ static void run_stage_2(int luo_fd, int state_session_fd)
close(claimed_fd);
expect_counts(2, 1);
+ expect_more_restored(restored);
ksft_print_msg("[STAGE 2] Finishing the state session...\n");
if (luo_session_finish(state_session_fd) < 0)
@@ -185,6 +229,22 @@ static void run_stage_2(int luo_fd, int state_session_fd)
/* Finishing the state session must not drop anything. */
expect_counts(2, 1);
+ restored = kho_size("restored_size");
+
+ ksft_print_msg("[STAGE 2] Retrieving the memfd in '%s'...\n",
+ SESSION_UNCLAIMED);
+ unclaimed_fd = luo_retrieve_session(luo_fd, SESSION_UNCLAIMED);
+ if (unclaimed_fd < 0)
+ fail_exit("luo_retrieve_session for '%s'", SESSION_UNCLAIMED);
+ mfd = restore_and_verify_memfd(unclaimed_fd, MFD_UNCLAIMED_TOKEN,
+ MFD_UNCLAIMED_DATA);
+ if (mfd < 0)
+ fail_exit("restore_and_verify_memfd for token %#x",
+ MFD_UNCLAIMED_TOKEN);
+ close(mfd);
+ close(unclaimed_fd);
+ expect_counts(3, 1);
+ expect_more_restored(restored);
ksft_print_msg("\n--- HANDOVER STATS TEST PASSED ---\n");
}
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH 1/9] kho: Reclaim the metadata page left reserved after kexec
2026-11-11 7:52 ` [PATCH 1/9] kho: Reclaim the metadata page left reserved after kexec Chris Down
@ 2026-11-11 11:08 ` Breno Leitao
2026-11-11 22:29 ` Pasha Tatashin
1 sibling, 0 replies; 12+ messages in thread
From: Breno Leitao @ 2026-11-11 11:08 UTC (permalink / raw)
To: Chris Down
Cc: Pasha Tatashin, Mike Rapoport, Pratyush Yadav, Alexander Graf,
Baoquan He, Jonathan Corbet, Shuah Khan, kexec, linux-mm,
linux-kernel, linux-doc, linux-kselftest, kernel-team
On Tue, Nov 10, 2026 at 11:52:55PM -0800, Chris Down wrote:
> After a KHO kexec, the new kernel keeps the page holding the previous
> kernel's kexec metadata reserved for as long as it runs. It only needs
> the page once, since kho_in_kexec_metadata() copies the previous release
> and the kexec count out of it at boot, and nothing reads it again.
Good catch.
>
> Free it with kho_restore_free() once copying is complete.
>
> Fixes: 76aa46b9e404 ("kho: kexec-metadata: track previous kernel chain")
> Signed-off-by: Chris Down <chris@chrisdown.name>
Tested-by: Breno Leitao <leitao@debian.org>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 1/9] kho: Reclaim the metadata page left reserved after kexec
2026-11-11 7:52 ` [PATCH 1/9] kho: Reclaim the metadata page left reserved after kexec Chris Down
2026-11-11 11:08 ` Breno Leitao
@ 2026-11-11 22:29 ` Pasha Tatashin
1 sibling, 0 replies; 12+ messages in thread
From: Pasha Tatashin @ 2026-11-11 22:29 UTC (permalink / raw)
To: Chris Down
Cc: Pasha Tatashin, Mike Rapoport, Pratyush Yadav, Breno Leitao,
Alexander Graf, Baoquan He, Jonathan Corbet, Shuah Khan, kexec,
linux-mm, linux-kernel, linux-doc, linux-kselftest, kernel-team
> After a KHO kexec, the new kernel keeps the page holding the previous
> kernel's kexec metadata reserved for as long as it runs. It only needs
> the page once, since kho_in_kexec_metadata() copies the previous release
> and the kexec count out of it at boot, and nothing reads it again.
>
> Free it with kho_restore_free() once copying is complete.
>
> Fixes: 76aa46b9e404 ("kho: kexec-metadata: track previous kernel chain")
Do we really need to apply this to stable? It behaves correctly, and
your patch performs a minor optimization by releasing a single page back
to the system.
> Signed-off-by: Chris Down <chris@chrisdown.name>
>
> diff --git a/Documentation/admin-guide/mm/kho.rst b/Documentation/admin-guide/mm/kho.rst
> index 902d171a780a..f9d2cc5999ee 100644
> --- a/Documentation/admin-guide/mm/kho.rst
> +++ b/Documentation/admin-guide/mm/kho.rst
> @@ -120,4 +120,6 @@ stabilized.
>
> ``/sys/kernel/debug/kho/in/sub_fdts/``
> Similar to ``kho/out/sub_fdts/``, but contains sub blobs
> - of KHO producers passed from the old kernel.
> + of KHO producers passed from the old kernel. A blob disappears
> + once the kernel has read and freed it, as ``kexec-metadata`` does
> + during boot.
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index 6f1f55fe3181..8a07783326a4 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -1782,6 +1782,9 @@ static void __init kho_in_kexec_metadata(void)
>
> pr_info("exec from: %s (count %u)\n",
> kho_in.previous_release, kho_in.kexec_count);
> +
> + kho_debugfs_blob_remove(&kho_in.dbg, metadata);
> + kho_restore_free(metadata);
> }
>
> /*
> @@ -1859,14 +1862,15 @@ static __init int kho_init(void)
> if (err)
> goto err_free_fdt;
>
> + if (fdt)
> + kho_in_debugfs_init(&kho_in.dbg, fdt);
> +
so add debugfs node here.
> err = kho_kexec_metadata_init(fdt);
Remove the kexec metadata node here. While this is somewhat unintuitive,
I do not see a better alternative.
> if (err)
> goto err_free_fdt;
>
> - if (fdt) {
> - kho_in_debugfs_init(&kho_in.dbg, fdt);
> + if (fdt)
> return 0;
> - }
>
> for (int i = 0; i < kho_bootmem_cnt; i++) {
> unsigned long base_pfn = PHYS_PFN(kho_bootmem[i].addr);
Reviewed-by: Pasha Tatashin <pasha.tatashin@soleen.com>
--
Pasha
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-11-11 22:29 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-11 7:52 [PATCH 0/9] kho, liveupdate: KHO/LUO observability Chris Down
2026-11-11 7:52 ` [PATCH 1/9] kho: Reclaim the metadata page left reserved after kexec Chris Down
2026-11-11 11:08 ` Breno Leitao
2026-11-11 22:29 ` Pasha Tatashin
2026-11-11 7:53 ` [PATCH 2/9] kho: Make bootmem reservations visible to fleet monitoring Chris Down
2026-11-11 7:53 ` [PATCH 3/9] liveupdate: Make failed retrieves distinguishable from dropped files Chris Down
2026-11-11 7:53 ` [PATCH 4/9] selftests/liveupdate: Catch incorrect file accounting across kexec Chris Down
2026-11-11 7:53 ` [PATCH 5/9] selftests/kho: Catch missing or zero bootmem reports before kexec Chris Down
2026-11-11 7:53 ` [PATCH 6/9] kho: Expose rejected handovers that look like cold boots Chris Down
2026-11-11 7:53 ` [PATCH 7/9] kho: Report how much handover memory is preserved and restored Chris Down
2026-11-11 7:53 ` [PATCH 8/9] liveupdate: Expose the sequence number for consecutive live updates Chris Down
2026-11-11 7:53 ` [PATCH 9/9] selftests/liveupdate: Check handover revival, sequence and memory stats Chris Down
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox