[PATCH v3 5/7] kstate, test: add test module for testing kstate subsystem.

Andrey Ryabinin
2025-09-09T20:15:46Z

This is simple test and playground useful kstate subsystem development.
It contains some structure with different kind of data which migrated
across kexec to the new kernel using kstate.

Signed-off-by: Andrey Ryabinin <arbn@yandex-team.com>
---
MAINTAINERS | 1 +
include/linux/kstate.h | 2 +
kernel/liveupdate/Kconfig | 8 +++
lib/Makefile | 2 +
lib/test_kstate.c | 116 ++++++++++++++++++++++++++++++++++++++
5 files changed, 129 insertions(+)
create mode 100644 lib/test_kstate.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 2cd9e49abee5..e96da6d97e75 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13723,6 +13723,7 @@ M: Andrey Ryabinin <ryabinin.a.a@gmail.com>
S: Maintained
F: include/linux/kstate.h
F: kernel/livupdate/kstate.c
+F: lib/test_kstate.c

KTD253 BACKLIGHT DRIVER
M: Linus Walleij <linus.walleij@linaro.org>
diff --git a/include/linux/kstate.h b/include/linux/kstate.h
index 5a95960e5b03..0ced0da37c8f 100644
--- a/include/linux/kstate.h
+++ b/include/linux/kstate.h
@@ -95,6 +95,8 @@ struct kstate_field {
enum kstate_ids {
KSTATE_FOLIO_ID = 1,
KSTATE_KHO_FDT_ID,
+ KSTATE_TEST_ID,
+ KSTATE_TEST_ID_V2,
KSTATE_LAST_ID = -1,
};

diff --git a/kernel/liveupdate/Kconfig b/kernel/liveupdate/Kconfig
index b6ea861006bf..af9a25bdcd6e 100644
--- a/kernel/liveupdate/Kconfig
+++ b/kernel/liveupdate/Kconfig
@@ -69,6 +69,14 @@ config KSTATE
state, save it into the memory and restore the state after kexec
in new kernel.

+config KSTATE_TEST
+ bool "KSTATE test code"
+ help
+ Build a simple test/playground code that is useful for kstate
+ subsystem development. It contains some structure with different
+ kind of data which migrated across kexec to the new kernel
+ using KSTATE.
+
config KEXEC_HANDOVER
bool "kexec handover"
depends on ARCH_SUPPORTS_KEXEC_HANDOVER && ARCH_SUPPORTS_KEXEC_FILE
diff --git a/lib/Makefile b/lib/Makefile
index 392ff808c9b9..46616577caf3 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -316,6 +316,8 @@ obj-$(CONFIG_PARMAN) += parman.o

obj-y += group_cpus.o

+obj-$(CONFIG_KSTATE_TEST) += test_kstate.o
+
# GCC library routines
obj-$(CONFIG_GENERIC_LIB_ASHLDI3) += ashldi3.o
obj-$(CONFIG_GENERIC_LIB_ASHRDI3) += ashrdi3.o
diff --git a/lib/test_kstate.c b/lib/test_kstate.c
new file mode 100644
index 000000000000..70534e8c718f
--- /dev/null
+++ b/lib/test_kstate.c
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0
+#define pr_fmt(fmt) "kstate test: " fmt
+#include <linux/io.h>
+#include <linux/kexec_handover.h>
+#include <linux/kstate.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+
+static unsigned long ulong_val;
+struct kstate_test_data {
+ int i;
+ unsigned long *p_ulong;
+ char s[10];
+ struct folio *folio;
+};
+
+#define KSTATE_TEST_DATA_ID 123
+
+struct kstate_description test_state_v2 = {
+ .name = "test_v2",
+ .version_id = 1,
+ .id = KSTATE_TEST_ID_V2,
+ .fields = (const struct kstate_field[]) {
+ KSTATE_BASE_TYPE(i, struct kstate_test_data, int),
+ KSTATE_END_OF_LIST()
+ },
+};
+
+struct kstate_description test_state = {
+ .name = "test",
+ .version_id = 2,
+ .id = KSTATE_TEST_ID,
+ .fields = (const struct kstate_field[]) {
+ KSTATE_BASE_TYPE(s, struct kstate_test_data, char [10]),
+ KSTATE_POINTER(p_ulong, struct kstate_test_data),
+ KSTATE_FOLIO(folio, struct kstate_test_data),
+ KSTATE_BASE_TYPE_DEPRECATED(k, u16, 1),
+ KSTATE_END_OF_LIST()
+ },
+ .subsections = (const struct kstate_description *[]){
+ &test_state_v2,
+ NULL
+ },
+};
+
+static struct kstate_test_data test_data;
+
+static int init_test_data(void)
+{
+ struct folio *folio;
+ int i;
+
+ test_data.i = 10;
+ ulong_val = 20;
+ memcpy(test_data.s, "abcdefghk", sizeof(test_data.s));
+ folio = folio_alloc(GFP_KERNEL, 0);
+ if (!folio)
+ return -ENOMEM;
+
+ for (i = 0; i < folio_size(folio)/sizeof(u32); i += 4)
+ *((u32 *)folio_address(folio) + i) = 0xdeadbeef;
+ test_data.folio = folio;
+ return 0;
+}
+
+static void validate_test_data(void)
+{
+ int i;
+
+ if (WARN_ON(test_data.i != 10))
+ return;
+ if (WARN_ON(*test_data.p_ulong != 20))
+ return;
+ if (WARN_ON(strcmp(test_data.s, "abcdefghk") != 0))
+ return;
+
+ for (i = 0; i < folio_size(test_data.folio)/4; i += 4) {
+ u32 val = *((u32 *)folio_address(test_data.folio) + i);
+
+ if (WARN_ON_ONCE(val != 0xdeadbeef))
+ return;
+ }
+}
+
+static int __init test_kstate_init(void)
+{
+ int ret = 0;
+
+ test_data.p_ulong = &ulong_val;
+
+ ret = kstate_register(&test_state, &test_data, KSTATE_TEST_DATA_ID);
+ if (ret) {
+ pr_err("register failed %d\n", ret);
+ goto out;
+ }
+
+ if (!is_kho_boot()) {
+ ret = init_test_data();
+ if (ret)
+ goto out;
+ } else {
+ pr_info("restoring data\n");
+ ret = kstate_restore(&test_state, &test_data, KSTATE_TEST_DATA_ID);
+ if (ret) {
+ pr_err("restore failed %d\n", ret);
+ goto out;
+ }
+
+ }
+
+ validate_test_data();
+
+out:
+ return ret;
+}
+late_initcall(test_kstate_init);
--
2.49.1