[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20251125225006.3722394-3-pasha.tatashin@soleen.com>
Date: Tue, 25 Nov 2025 17:50:06 -0500
From: Pasha Tatashin <pasha.tatashin@...een.com>
To: pratyush@...nel.org,
pasha.tatashin@...een.com,
rppt@...nel.org,
dmatlack@...gle.com,
skhawaja@...gle.com,
rientjes@...gle.com,
corbet@....net,
akpm@...ux-foundation.org,
kees@...nel.org,
davidgow@...gle.com,
pmladek@...e.com,
linux-kernel@...r.kernel.org,
linux-mm@...ck.org
Subject: [PATCH v8 2/2] tests/liveupdate: Add in-kernel liveupdate test
Introduce an in-kernel test module to validate the core logic of the
Live Update Orchestrator's File-Lifecycle-Bound feature. This
provides a low-level, controlled environment to test FLB registration
and callback invocation without requiring userspace interaction or
actual kexec reboots.
The test is enabled by the CONFIG_LIVEUPDATE_TEST Kconfig option.
Signed-off-by: Pasha Tatashin <pasha.tatashin@...een.com>
---
MAINTAINERS | 1 +
include/linux/kho/abi/luo.h | 5 +
kernel/liveupdate/luo_file.c | 8 +-
kernel/liveupdate/luo_internal.h | 8 ++
lib/Kconfig.debug | 23 +++++
lib/tests/Makefile | 1 +
lib/tests/liveupdate.c | 158 +++++++++++++++++++++++++++++++
7 files changed, 203 insertions(+), 1 deletion(-)
create mode 100644 lib/tests/liveupdate.c
diff --git a/MAINTAINERS b/MAINTAINERS
index ce2da2d39786..2722f98d0ed7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14479,6 +14479,7 @@ F: include/linux/liveupdate.h
F: include/linux/liveupdate/
F: include/uapi/linux/liveupdate.h
F: kernel/liveupdate/
+F: lib/tests/liveupdate.c
F: mm/memfd_luo.c
F: tools/testing/selftests/liveupdate/
diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 2734d1020dda..4a1cc6a5f3f8 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -239,4 +239,9 @@ struct luo_flb_ser {
u64 count;
} __packed;
+/* Kernel Live Update Test ABI */
+#ifdef CONFIG_LIVEUPDATE_TEST
+#define LIVEUPDATE_TEST_FLB_COMPATIBLE(i) "liveupdate-test-flb-v" #i
+#endif
+
#endif /* _LINUX_KHO_ABI_LUO_H */
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index 6a29d20e9ed9..fca3806dae28 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -860,6 +860,8 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
list_add_tail(&ACCESS_PRIVATE(fh, list), &luo_file_handler_list);
luo_session_resume();
+ liveupdate_test_register(fh);
+
return 0;
err_resume:
@@ -891,8 +893,10 @@ int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
if (!liveupdate_enabled())
return -EOPNOTSUPP;
+ liveupdate_test_unregister(fh);
+
if (!luo_session_quiesce())
- return -EBUSY;
+ goto err_register;
if (!list_empty(&ACCESS_PRIVATE(fh, flb_list)))
goto err_resume;
@@ -905,5 +909,7 @@ int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
err_resume:
luo_session_resume();
+err_register:
+ liveupdate_test_register(fh);
return err;
}
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 9732a7ca3d5f..6115d6a4054d 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -114,4 +114,12 @@ int __init luo_flb_setup_outgoing(void *fdt);
int __init luo_flb_setup_incoming(void *fdt);
void luo_flb_serialize(void);
+#ifdef CONFIG_LIVEUPDATE_TEST
+void liveupdate_test_register(struct liveupdate_file_handler *fh);
+void liveupdate_test_unregister(struct liveupdate_file_handler *fh);
+#else
+static inline void liveupdate_test_register(struct liveupdate_file_handler *fh) { }
+static inline void liveupdate_test_unregister(struct liveupdate_file_handler *fh) { }
+#endif
+
#endif /* _LINUX_LUO_INTERNAL_H */
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index bd3bb7a0c801..59b5ce8f89cf 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2803,6 +2803,29 @@ config LINEAR_RANGES_TEST
If unsure, say N.
+config LIVEUPDATE_TEST
+ bool "Live Update Kernel Test"
+ default n
+ depends on LIVEUPDATE
+ help
+ Enable a built-in kernel test module for the Live Update
+ Orchestrator.
+
+ This module validates the File-Lifecycle-Bound subsystem by
+ registering a set of mock FLB objects with any real file handlers
+ that support live update (such as the memfd handler).
+
+ When live update operations are performed, this test module will
+ output messages to the kernel log (dmesg), confirming that its
+ registration and various callback functions (preserve, retrieve,
+ finish, etc.) are being invoked correctly.
+
+ This is a debugging and regression testing tool for developers
+ working on the Live Update subsystem. It should not be enabled in
+ production kernels.
+
+ If unsure, say N
+
config CMDLINE_KUNIT_TEST
tristate "KUnit test for cmdline API" if !KUNIT_ALL_TESTS
depends on KUNIT
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
index 601dba4b7d96..ab0173e6e8b0 100644
--- a/lib/tests/Makefile
+++ b/lib/tests/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_LIST_KUNIT_TEST) += list-test.o
obj-$(CONFIG_KFIFO_KUNIT_TEST) += kfifo_kunit.o
obj-$(CONFIG_TEST_LIST_SORT) += test_list_sort.o
obj-$(CONFIG_LINEAR_RANGES_TEST) += test_linear_ranges.o
+obj-$(CONFIG_LIVEUPDATE_TEST) += liveupdate.o
CFLAGS_longest_symbol_kunit.o += $(call cc-disable-warning, missing-prototypes)
obj-$(CONFIG_LONGEST_SYM_KUNIT_TEST) += longest_symbol_kunit.o
diff --git a/lib/tests/liveupdate.c b/lib/tests/liveupdate.c
new file mode 100644
index 000000000000..496d6ef91a30
--- /dev/null
+++ b/lib/tests/liveupdate.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2025, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@...een.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME " test: " fmt
+
+#include <linux/cleanup.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/liveupdate.h>
+#include <linux/module.h>
+#include "../../kernel/liveupdate/luo_internal.h"
+
+static const struct liveupdate_flb_ops test_flb_ops;
+#define DEFINE_TEST_FLB(i) { \
+ .ops = &test_flb_ops, \
+ .compatible = LIVEUPDATE_TEST_FLB_COMPATIBLE(i), \
+}
+
+/* Number of Test FLBs to register with every file handler */
+#define TEST_NFLBS 3
+static struct liveupdate_flb test_flbs[TEST_NFLBS] = {
+ DEFINE_TEST_FLB(0),
+ DEFINE_TEST_FLB(1),
+ DEFINE_TEST_FLB(2),
+};
+
+#define TEST_FLB_MAGIC_BASE 0xFEEDF00DCAFEBEE0ULL
+
+static int test_flb_preserve(struct liveupdate_flb_op_args *argp)
+{
+ ptrdiff_t index = argp->flb - test_flbs;
+
+ pr_info("%s: preserve was triggered\n", argp->flb->compatible);
+ argp->data = TEST_FLB_MAGIC_BASE + index;
+
+ return 0;
+}
+
+static void test_flb_unpreserve(struct liveupdate_flb_op_args *argp)
+{
+ pr_info("%s: unpreserve was triggered\n", argp->flb->compatible);
+}
+
+static int test_flb_retrieve(struct liveupdate_flb_op_args *argp)
+{
+ ptrdiff_t index = argp->flb - test_flbs;
+ u64 expected_data = TEST_FLB_MAGIC_BASE + index;
+
+ if (argp->data == expected_data) {
+ pr_info("%s: found flb data from the previous boot\n",
+ argp->flb->compatible);
+ argp->obj = (void *)argp->data;
+ } else {
+ pr_err("%s: ERROR - incorrect data handle: %llx, expected %llx\n",
+ argp->flb->compatible, argp->data, expected_data);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void test_flb_finish(struct liveupdate_flb_op_args *argp)
+{
+ ptrdiff_t index = argp->flb - test_flbs;
+ void *expected_obj = (void *)(TEST_FLB_MAGIC_BASE + index);
+
+ if (argp->obj == expected_obj) {
+ pr_info("%s: finish was triggered\n", argp->flb->compatible);
+ } else {
+ pr_err("%s: ERROR - finish called with invalid object\n",
+ argp->flb->compatible);
+ }
+}
+
+static const struct liveupdate_flb_ops test_flb_ops = {
+ .preserve = test_flb_preserve,
+ .unpreserve = test_flb_unpreserve,
+ .retrieve = test_flb_retrieve,
+ .finish = test_flb_finish,
+ .owner = THIS_MODULE,
+};
+
+static void liveupdate_test_init(void)
+{
+ static DEFINE_MUTEX(init_lock);
+ static bool initialized;
+ int i;
+
+ guard(mutex)(&init_lock);
+
+ if (initialized)
+ return;
+
+ for (i = 0; i < TEST_NFLBS; i++) {
+ struct liveupdate_flb *flb = &test_flbs[i];
+ void *obj;
+ int err;
+
+ err = liveupdate_flb_get_incoming(flb, &obj);
+ if (err && err != -ENODATA && err != -ENOENT) {
+ pr_err("liveupdate_flb_get_incoming for %s failed: %pe\n",
+ flb->compatible, ERR_PTR(err));
+ }
+ }
+ initialized = true;
+}
+
+void liveupdate_test_register(struct liveupdate_file_handler *fh)
+{
+ int err, i;
+
+ liveupdate_test_init();
+
+ for (i = 0; i < TEST_NFLBS; i++) {
+ struct liveupdate_flb *flb = &test_flbs[i];
+
+ err = liveupdate_register_flb(fh, flb);
+ if (err) {
+ pr_err("Failed to register %s %pe\n",
+ flb->compatible, ERR_PTR(err));
+ }
+ }
+
+ err = liveupdate_register_flb(fh, &test_flbs[0]);
+ if (!err || err != -EEXIST) {
+ pr_err("Failed: %s should be already registered, but got err: %pe\n",
+ test_flbs[0].compatible, ERR_PTR(err));
+ }
+
+ pr_info("Registered %d FLBs with file handler: [%s]\n",
+ TEST_NFLBS, fh->compatible);
+}
+
+void liveupdate_test_unregister(struct liveupdate_file_handler *fh)
+{
+ int err, i;
+
+ for (i = 0; i < TEST_NFLBS; i++) {
+ struct liveupdate_flb *flb = &test_flbs[i];
+
+ err = liveupdate_unregister_flb(fh, flb);
+ if (err) {
+ pr_err("Failed to unregister %s %pe\n",
+ flb->compatible, ERR_PTR(err));
+ }
+ }
+
+ pr_info("Unregistered %d FLBs from file handler: [%s]\n",
+ TEST_NFLBS, fh->compatible);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Pasha Tatashin <pasha.tatashin@...een.com>");
+MODULE_DESCRIPTION("In-kernel test for LUO mechanism");
--
2.52.0.487.g5c8c507ade-goog
Powered by blists - more mailing lists