Add integration tests performing kexec live updates with preserved CPUs: - Preserve physical cores across kexec reboot. - Verify cores remain preserved and isolated in incoming kernel. - Verify unpreserve and online restoration.
Signed-off-by: Pasha Tatashin <[email protected]> --- tools/testing/selftests/liveupdate/Makefile | 1 + tools/testing/selftests/liveupdate/init.c | 27 +++- .../selftests/liveupdate/luo_cpu_preserve.c | 145 ++++++++++++++++++ .../selftests/liveupdate/run-vmtests.sh | 1 + tools/testing/selftests/liveupdate/vmtest.sh | 4 +- 5 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 tools/testing/selftests/liveupdate/luo_cpu_preserve.c diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile index 634211c66652..0fa80f26a191 100644 --- a/tools/testing/selftests/liveupdate/Makefile +++ b/tools/testing/selftests/liveupdate/Makefile @@ -3,6 +3,7 @@ TEST_GEN_PROGS += liveupdate TEST_GEN_PROGS_EXTENDED += luo_kexec_simple +TEST_GEN_PROGS_EXTENDED += luo_cpu_preserve TEST_GEN_PROGS_EXTENDED += luo_multi_session TEST_GEN_PROGS_EXTENDED += luo_stress_sessions TEST_GEN_PROGS_EXTENDED += luo_stress_files diff --git a/tools/testing/selftests/liveupdate/init.c b/tools/testing/selftests/liveupdate/init.c index fb08bd58b9b9..a3850ff95c00 100644 --- a/tools/testing/selftests/liveupdate/init.c +++ b/tools/testing/selftests/liveupdate/init.c @@ -11,6 +11,7 @@ #include <string.h> #include <sys/mount.h> #include <sys/reboot.h> +#include <sys/stat.h> #include <sys/syscall.h> #include <sys/wait.h> #include <unistd.h> @@ -27,11 +28,15 @@ static int mount_filesystems(void) return -1; } - if (mount("debugfs", "/debugfs", "debugfs", 0, NULL) < 0) { - fprintf(stderr, "INIT: Failed to mount debugfs\n"); + if (mount("sysfs", "/sys", "sysfs", 0, NULL) < 0) { + fprintf(stderr, "INIT: Failed to mount sysfs\n"); return -1; } + mkdir("/sys/kernel/debug", 0755); + mount("debugfs", "/sys/kernel/debug", "debugfs", 0, NULL); + mount("debugfs", "/debugfs", "debugfs", 0, NULL); + if (mount("proc", "/proc", "proc", 0, NULL) < 0) { fprintf(stderr, "INIT: Failed to mount proc\n"); return -1; @@ -116,7 +121,23 @@ static int run_test(int stage) } waitpid(pid, &status, 0); + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) + return 0; + + /* If test binary does not accept -s (e.g. kselftest harness), run without -s */ + pid = fork(); + if (pid < 0) + return -1; + + if (!pid) { + char *const argv[] = {TEST_BINARY, NULL}; + execve(TEST_BINARY, argv, NULL); + fprintf(stderr, "INIT: execve failed\n"); + _exit(1); + } + + waitpid(pid, &status, 0); return (WIFEXITED(status) && WEXITSTATUS(status) == 0) ? 0 : -1; } @@ -168,6 +189,8 @@ int main(int argc, char *argv[]) } printf("INIT: Stage %d completed successfully.\n", current_stage); + if (current_stage == 2) + printf("\n--- TEST PASSED ---\n"); reboot(current_stage == 1 ? RB_KEXEC : RB_AUTOBOOT); return 0; diff --git a/tools/testing/selftests/liveupdate/luo_cpu_preserve.c b/tools/testing/selftests/liveupdate/luo_cpu_preserve.c new file mode 100644 index 000000000000..85d17ccf3d90 --- /dev/null +++ b/tools/testing/selftests/liveupdate/luo_cpu_preserve.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * Copyright (c) 2026, Google LLC. + * Pasha Tatashin <[email protected]> + * + * A selftest to validate the lifecycle of preserved CPUs across Live Update + * and within a local session. + */ + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#include <libliveupdate.h> + +#define TEST_SESSION_NAME "cpu-preserve-session" +#define STATE_SESSION_NAME "cpu_preserve_state" +#define STATE_MEMFD_TOKEN 999 +#define TEST_CPU_TOKEN 0x100 + +static int find_target_cpu(void) +{ + char path[128]; + int cpu; + + for (cpu = 1; cpu < 256; cpu++) { + snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/preserve", cpu); + if (access(path, R_OK) == 0) { + snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/online", cpu); + if (access(path, R_OK) == 0) + return cpu; + } + } + return -1; +} + +static int read_cpu_online(int cpu) +{ + char path[128], buf[16]; + int fd, val = -1; + + snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/online", cpu); + fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + if (read(fd, buf, sizeof(buf) - 1) > 0) + val = atoi(buf); + close(fd); + return val; +} + +static int preserve_cpu_fd(int session_fd, int cpu, int token) +{ + struct liveupdate_session_preserve_fd pfd = {}; + char path[128]; + int cpu_fd, ret; + + snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/preserve", cpu); + cpu_fd = open(path, O_RDONLY); + if (cpu_fd < 0) + return -1; + + pfd.size = sizeof(pfd); + pfd.fd = cpu_fd; + pfd.token = token; + + ret = ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &pfd); + close(cpu_fd); + return ret; +} + +/* Stage 1: Executed before kexec */ +static void run_stage_1(int luo_fd) +{ + int target_cpu, session_fd; + + target_cpu = find_target_cpu(); + if (target_cpu < 0) + fail_exit("No hotpluggable CPU with preserve attribute found"); + + ksft_print_msg("[STAGE 1] Target CPU for preservation: %d\n", target_cpu); + + create_state_file(luo_fd, STATE_SESSION_NAME, STATE_MEMFD_TOKEN, target_cpu); + + session_fd = luo_create_session(luo_fd, TEST_SESSION_NAME); + if (session_fd < 0) + fail_exit("luo_create_session for '%s'", TEST_SESSION_NAME); + + if (preserve_cpu_fd(session_fd, target_cpu, TEST_CPU_TOKEN) < 0) + fail_exit("preserve_cpu_fd for cpu %d", target_cpu); + + if (read_cpu_online(target_cpu) != 0) + fail_exit("CPU %d was expected to be offline after preservation", target_cpu); + + ksft_print_msg("[STAGE 1] CPU %d successfully preserved and offlined\n", target_cpu); + + close(luo_fd); + daemonize_and_wait(); +} + +/* Stage 2: Executed after kexec */ +static void run_stage_2(int luo_fd, int state_session_fd) +{ + struct liveupdate_session_retrieve_fd rfd = {}; + int target_cpu, session_fd; + + restore_and_read_stage(state_session_fd, STATE_MEMFD_TOKEN, &target_cpu); + ksft_print_msg("[STAGE 2] Restored target CPU: %d\n", target_cpu); + + session_fd = luo_retrieve_session(luo_fd, TEST_SESSION_NAME); + if (session_fd < 0) + fail_exit("luo_retrieve_session for '%s'", TEST_SESSION_NAME); + + rfd.size = sizeof(rfd); + rfd.token = TEST_CPU_TOKEN; + if (ioctl(session_fd, LIVEUPDATE_SESSION_RETRIEVE_FD, &rfd) < 0) + fail_exit("LIVEUPDATE_SESSION_RETRIEVE_FD for cpu token %#x", TEST_CPU_TOKEN); + + close(rfd.fd); + + if (read_cpu_online(target_cpu) != 1) + fail_exit("CPU %d was expected to be online after retrieval", target_cpu); + + ksft_print_msg("[STAGE 2] CPU %d successfully retrieved and online\n", target_cpu); + + if (luo_session_finish(session_fd) < 0) + fail_exit("luo_session_finish for test session"); + close(session_fd); + + if (luo_session_finish(state_session_fd) < 0) + fail_exit("luo_session_finish for state session"); + close(state_session_fd); + + ksft_print_msg("\n--- CPU PRESERVATION KEXEC TEST PASSED ---\n"); +} + +int main(int argc, char *argv[]) +{ + 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 d656ce58c5a7..1b90d56ec4a2 100755 --- a/tools/testing/selftests/liveupdate/run-vmtests.sh +++ b/tools/testing/selftests/liveupdate/run-vmtests.sh @@ -20,6 +20,7 @@ TEST_NAMES=( "luo_multi_session" "luo_stress_files" "luo_stress_sessions" + "luo_cpu_preserve" ) function usage() { diff --git a/tools/testing/selftests/liveupdate/vmtest.sh b/tools/testing/selftests/liveupdate/vmtest.sh index 64fd2ab55b76..c934c76c00a0 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 @@ -179,7 +181,7 @@ function run_qemu() { echo "# Serial Log: $serial" timeout 30s \ - $qemu_cmd -m 1G -smp 2 -no-reboot -nographic -nodefaults \ + $qemu_cmd -m 1G -smp 4 -no-reboot -nographic -nodefaults \ -accel tcg -accel hvf -accel kvm \ -serial file:"$serial" \ -append "$cmdline" \ -- 2.55.0.1082.g2b9226bbc0-goog

