[PATCH v2 9/9] selftests: kho: add read-only KHO test to vmtest.sh
From: Pasha Tatashin
Date: Sun Oct 25 2026 - 13:44:33 EST
vmtest.sh currently only tests standard KHO restoration and does not
exercise read-only KHO mode ('kho_ro=<fdt_phys>') in QEMU.
Extend tools/testing/selftests/kho/vmtest.sh and init.c to enable both
CONFIG_KEXEC_HANDOVER=y and CONFIG_KEXEC_HANDOVER_READONLY=y in the
kernel configuration and run a second QEMU test verifying read-only KHO
after the standard KHO test on both x86_64 and aarch64:
- Enable CONFIG_KEXEC_HANDOVER_READONLY=y alongside
CONFIG_KEXEC_HANDOVER=y in build_kernel().
- Pass KHO_RO_CMDLINE ('nokaslr kho_bootmem=64M,64M,64M' on x86_64) to
the first kernel in the read-only test run and configure 4G of RAM on
aarch64 so GFP_KERNEL KHO allocations are placed in ZONE_NORMAL above
4G, outside the read-only kernel's System RAM.
- When booted with 'kho_ro_test', read /debugfs/kho/out/fdt_phys in
init.c and kexec into the target kernel with 'kho_ro=<fdt_phys>' and
architecture-specific memory parameters
('memmap=792M$16M memmap=24M$1000M' on x86_64, 'mem=128M' on aarch64)
so the preserved pages are excluded from the boot memory map and
mapped via kho_ro_map_folio().
- Verify 'KHO restore succeeded' from test_kho in read-only mode.
Signed-off-by: Pasha Tatashin <pasha.tatashin@xxxxxxxxxx>
---
tools/testing/selftests/kho/arm64.conf | 1 +
tools/testing/selftests/kho/init.c | 38 ++++++++++++++-
tools/testing/selftests/kho/vmtest.sh | 65 ++++++++++++++++----------
tools/testing/selftests/kho/x86.conf | 1 +
4 files changed, 79 insertions(+), 26 deletions(-)
diff --git a/tools/testing/selftests/kho/arm64.conf b/tools/testing/selftests/kho/arm64.conf
index ee696807cd35..202f92121695 100644
--- a/tools/testing/selftests/kho/arm64.conf
+++ b/tools/testing/selftests/kho/arm64.conf
@@ -1,4 +1,5 @@
QEMU_CMD="qemu-system-aarch64 -M virt -cpu max"
+QEMU_MEM="4G"
QEMU_KCONFIG="
CONFIG_SERIAL_AMBA_PL010=y
CONFIG_SERIAL_AMBA_PL010_CONSOLE=y
diff --git a/tools/testing/selftests/kho/init.c b/tools/testing/selftests/kho/init.c
index 88a41b6eba95..e2ebcf7ab307 100644
--- a/tools/testing/selftests/kho/init.c
+++ b/tools/testing/selftests/kho/init.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
+#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/syscall.h>
@@ -12,6 +13,15 @@
#define COMMAND_LINE_SIZE 2048
#define KERNEL_IMAGE "/kernel"
+#define FDT_PHYS_PATH "/debugfs/kho/out/fdt_phys"
+
+#if defined(__x86_64__)
+#define KHO_RO_MEM_PARAM "memmap=792M$16M memmap=24M$1000M"
+#elif defined(__aarch64__)
+#define KHO_RO_MEM_PARAM "mem=128M"
+#else
+#define KHO_RO_MEM_PARAM ""
+#endif
static int mount_filesystems(void)
{
@@ -32,7 +42,8 @@ static long kexec_file_load(int kernel_fd, int initrd_fd,
static int kexec_load(void)
{
char cmdline[COMMAND_LINE_SIZE];
- ssize_t len;
+ ssize_t len, fdt_len;
+ char fdt_phys[64];
int fd, err;
fd = open("/proc/cmdline", O_RDONLY);
@@ -41,11 +52,34 @@ static int kexec_load(void)
len = read(fd, cmdline, sizeof(cmdline));
close(fd);
- if (len < 0)
+ if (len <= 0)
return -1;
/* replace \n with \0 */
cmdline[len - 1] = 0;
+
+ if (strstr(cmdline, "kho_ro_test")) {
+ fd = open(FDT_PHYS_PATH, O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ fdt_len = read(fd, fdt_phys, sizeof(fdt_phys) - 1);
+ close(fd);
+ if (fdt_len <= 0)
+ return -1;
+
+ if (fdt_phys[fdt_len - 1] == '\n')
+ fdt_len--;
+ fdt_phys[fdt_len] = 0;
+
+ len = (len - 1) + snprintf(cmdline + len - 1,
+ sizeof(cmdline) - (len - 1),
+ " kho_ro=%s " KHO_RO_MEM_PARAM,
+ fdt_phys) + 1;
+ if (len <= 0 || (size_t)len > sizeof(cmdline))
+ return -1;
+ }
+
fd = open(KERNEL_IMAGE, O_RDONLY);
if (fd < 0)
return -1;
diff --git a/tools/testing/selftests/kho/vmtest.sh b/tools/testing/selftests/kho/vmtest.sh
index 0014bd76e88d..ca59a58ae2fb 100755
--- a/tools/testing/selftests/kho/vmtest.sh
+++ b/tools/testing/selftests/kho/vmtest.sh
@@ -35,17 +35,12 @@ trap cleanup EXIT
function skip() {
local msg=${1:-""}
- ktap_test_skip "$msg"
+ while [[ "$KTAP_TESTNO" -le "$KSFT_NUM_TESTS" ]]; do
+ ktap_test_skip "$msg"
+ done
exit "$KSFT_SKIP"
}
-function fail() {
- local msg=${1:-""}
-
- ktap_test_fail "$msg"
- exit "$KSFT_FAIL"
-}
-
function build_kernel() {
local build_dir=$1
local make_cmd=$2
@@ -55,10 +50,11 @@ function build_kernel() {
local kho_config="$tmp_dir/kho.config"
local kconfig="$build_dir/.config"
- # enable initrd, KHO and KHO test in kernel configuration
- tee "$kconfig" > "$kho_config" <<EOF
+ # enable initrd, KHO, read-only KHO, and KHO test in kernel configuration
+ cat > "$kho_config" <<EOF
CONFIG_BLK_DEV_INITRD=y
CONFIG_KEXEC_HANDOVER=y
+CONFIG_KEXEC_HANDOVER_READONLY=y
CONFIG_KEXEC_HANDOVER_DEBUG=y
CONFIG_KEXEC_HANDOVER_DEBUGFS=y
CONFIG_TEST_KEXEC_HANDOVER=y
@@ -71,11 +67,25 @@ $arch_kconfig
EOF
make_cmd="$make_cmd -C $kernel_dir O=$build_dir"
- $make_cmd olddefconfig
+ local need_config=0
+ if [[ ! -f "$kconfig" ]]; then
+ need_config=1
+ else
+ while read -r opt ; do
+ [[ -z "$opt" ]] && continue
+ grep -q "^$opt$" "$kconfig" || need_config=1
+ done < "$kho_config"
+ fi
+
+ if [[ "$need_config" -eq 1 ]]; then
+ cp "$kho_config" "$kconfig"
+ $make_cmd olddefconfig
+ fi
- # verify that kernel confiration has all necessary options
+ # verify that kernel configuration has all necessary options
while read -r opt ; do
- grep "$opt" "$kconfig" &>/dev/null || skip "$opt is missing"
+ [[ -z "$opt" ]] && continue
+ grep -q "^$opt$" "$kconfig" || skip "$opt is missing"
done < "$kho_config"
$make_cmd "$kimage"
@@ -107,18 +117,23 @@ function run_qemu() {
local qemu_cmd=$1
local cmdline=$2
local kernel=$3
+ local test_name=$4
local serial="$tmp_dir/qemu.serial"
cmdline="$cmdline kho=on panic=-1"
- $qemu_cmd -m 1G -smp 2 -no-reboot -nographic -nodefaults \
- -accel kvm -accel hvf -accel tcg \
- -serial file:"$serial" \
- -append "$cmdline" \
- -kernel "$kernel" \
- -initrd "$initrd"
-
- grep "KHO restore succeeded" "$serial" &> /dev/null || fail "KHO failed"
+ rm -f "$serial"
+ if $qemu_cmd -m "${QEMU_MEM:-1G}" -smp 2 -no-reboot -nographic -nodefaults \
+ -accel kvm -accel hvf -accel tcg \
+ -serial file:"$serial" \
+ -append "$cmdline" \
+ -kernel "$kernel" \
+ -initrd "$initrd" && \
+ grep "KHO restore succeeded" "$serial" &> /dev/null; then
+ ktap_test_pass "$test_name succeeded"
+ else
+ ktap_test_fail "$test_name failed"
+ fi
}
function target_to_arch() {
@@ -164,7 +179,7 @@ function main() {
done
ktap_print_header
- ktap_set_plan 1
+ ktap_set_plan 2
if [[ "$target" != "$(uname -m)" ]] && [[ -z "$CROSS_COMPILE" ]]; then
skip "Cross-platform testing needs to specify CROSS_COMPILE"
@@ -182,9 +197,11 @@ function main() {
local kernel="$build_dir/arch/$arch/boot/$KERNEL_IMAGE"
mkinitrd "$kernel"
- run_qemu "$QEMU_CMD" "$KERNEL_CMDLINE" "$kernel"
+ trap - ERR
- ktap_test_pass "KHO succeeded"
+ run_qemu "$QEMU_CMD" "$KERNEL_CMDLINE" "$kernel" "KHO"
+ run_qemu "$QEMU_CMD" "$KERNEL_CMDLINE ${KHO_RO_CMDLINE:-} kho_ro_test" \
+ "$kernel" "KHO read-only"
}
main "$@"
diff --git a/tools/testing/selftests/kho/x86.conf b/tools/testing/selftests/kho/x86.conf
index b419e610ca22..2c097c5bd6c6 100644
--- a/tools/testing/selftests/kho/x86.conf
+++ b/tools/testing/selftests/kho/x86.conf
@@ -5,3 +5,4 @@ CONFIG_SERIAL_8250_CONSOLE=y
"
KERNEL_IMAGE="bzImage"
KERNEL_CMDLINE="console=ttyS0"
+KHO_RO_CMDLINE="nokaslr kho_bootmem=64M,64M,64M"
--
2.56.0.307.g5fe7bd680c-goog