[PATCH v1 8/8] selftests: kho: add read-only KHO test to vmtest.sh

Pasha Tatashin pasha.tatashin at soleen.com
Sun Oct 18 19:11:11 PDT 2026


Extend tools/testing/selftests/kho/vmtest.sh and init.c to run a second
QEMU test verifying read-only KHO ('kho_ro=<fdt_phys>') after the
standard KHO test on both x86_64 and aarch64:

- Build a second kernel configured with CONFIG_KEXEC_HANDOVER_READONLY=y
  (and CONFIG_KEXEC_HANDOVER=n).
- Pass KHO_RO_CMDLINE ('nokaslr kho_bootmem=64M,64M,64M' on x86_64,
  'cma=116M at 0x40c00000' on aarch64) to the first kernel in the read-only
  test run so KHO allocations are placed in predictable physical ranges.
- When booted with 'kho_ro_test', read /debugfs/kho/out/fdt_phys in
  init.c and kexec into the read-only kernel with 'kho_ro=<fdt_phys>'
  and architecture-specific memory parameters
  ('memmap=792M$16M memmap=24M$1000M' on x86_64,
  'mem=128M cma=12M at 0x40000000' 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 at soleen.com>
---
 tools/testing/selftests/kho/arm64.conf |   3 +
 tools/testing/selftests/kho/init.c     |  38 ++++++++-
 tools/testing/selftests/kho/vmtest.sh  | 103 +++++++++++++++++--------
 tools/testing/selftests/kho/x86.conf   |   1 +
 4 files changed, 112 insertions(+), 33 deletions(-)

diff --git a/tools/testing/selftests/kho/arm64.conf b/tools/testing/selftests/kho/arm64.conf
index ee696807cd35..36dd8c46060c 100644
--- a/tools/testing/selftests/kho/arm64.conf
+++ b/tools/testing/selftests/kho/arm64.conf
@@ -4,6 +4,9 @@ CONFIG_SERIAL_AMBA_PL010=y
 CONFIG_SERIAL_AMBA_PL010_CONSOLE=y
 CONFIG_SERIAL_AMBA_PL011=y
 CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
+CONFIG_CMA=y
+CONFIG_DMA_CMA=y
 "
 KERNEL_IMAGE="Image"
 KERNEL_CMDLINE="console=ttyAMA0"
+KHO_RO_CMDLINE="cma=116M at 0x40c00000"
diff --git a/tools/testing/selftests/kho/init.c b/tools/testing/selftests/kho/init.c
index 88a41b6eba95..272338f186be 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 cma=12M at 0x40000000"
+#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..b3659e4fd1c9 100755
--- a/tools/testing/selftests/kho/vmtest.sh
+++ b/tools/testing/selftests/kho/vmtest.sh
@@ -35,32 +35,25 @@ 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
 	local arch_kconfig=$3
 	local kimage=$4
+	local kho_mode=${5:-"rw"}
 
 	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
+	cat > "$kho_config" <<EOF
 CONFIG_BLK_DEV_INITRD=y
-CONFIG_KEXEC_HANDOVER=y
-CONFIG_KEXEC_HANDOVER_DEBUG=y
-CONFIG_KEXEC_HANDOVER_DEBUGFS=y
 CONFIG_TEST_KEXEC_HANDOVER=y
 CONFIG_DEBUG_KERNEL=y
 CONFIG_DEBUG_VM=y
@@ -70,20 +63,50 @@ CONFIG_DEFERRED_STRUCT_PAGE_INIT=y
 $arch_kconfig
 EOF
 
+	if [[ "$kho_mode" == "ro" ]]; then
+		cat >> "$kho_config" <<EOF
+# CONFIG_KEXEC_HANDOVER is not set
+CONFIG_KEXEC_HANDOVER_READONLY=y
+EOF
+	else
+		cat >> "$kho_config" <<EOF
+CONFIG_KEXEC_HANDOVER=y
+CONFIG_KEXEC_HANDOVER_DEBUG=y
+CONFIG_KEXEC_HANDOVER_DEBUGFS=y
+EOF
+	fi
+
 	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
 	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"
-	$make_cmd headers_install INSTALL_HDR_PATH="$headers_dir"
+	if [[ "$kho_mode" != "ro" ]]; then
+		$make_cmd headers_install INSTALL_HDR_PATH="$headers_dir"
+	fi
 }
 
 function mkinitrd() {
 	local kernel=$1
+	local out_initrd=$2
 
 	"$CROSS_COMPILE"gcc -s -static -Os -nostdinc -nostdlib \
 			-fno-asynchronous-unwind-tables -fno-ident \
@@ -100,25 +123,31 @@ file /init $tmp_dir/init 0755 0 0
 file /kernel $kernel 0644 0 0
 EOF
 
-	"$build_dir/usr/gen_init_cpio" "$tmp_dir/cpio_list" > "$initrd"
+	"$build_dir/usr/gen_init_cpio" "$tmp_dir/cpio_list" > "$out_initrd"
 }
 
 function run_qemu() {
 	local qemu_cmd=$1
 	local cmdline=$2
 	local kernel=$3
+	local initrd_path=$4
+	local test_name=$5
 	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 1G -smp 2 -no-reboot -nographic -nodefaults \
+		     -accel kvm -accel hvf -accel tcg  \
+		     -serial file:"$serial" \
+		     -append "$cmdline" \
+		     -kernel "$kernel" \
+		     -initrd "$initrd_path" && \
+	   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 +193,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"
@@ -174,17 +203,29 @@ function main() {
 	local arch=$(target_to_arch "$target")
 	source "$test_dir/$arch.conf"
 
-	# build the kernel and create initrd
+	# build the kernels and create initrds
 	# initrd includes the kernel image that will be kexec'ed
 	local make_cmd="make ARCH=$arch CROSS_COMPILE=$CROSS_COMPILE -j$jobs"
-	build_kernel "$build_dir" "$make_cmd" "$QEMU_KCONFIG" "$KERNEL_IMAGE"
+	build_kernel "$build_dir" "$make_cmd" "$QEMU_KCONFIG" "$KERNEL_IMAGE" "rw"
 
-	local kernel="$build_dir/arch/$arch/boot/$KERNEL_IMAGE"
-	mkinitrd "$kernel"
+	local kernel="$tmp_dir/$KERNEL_IMAGE"
+	cp "$build_dir/arch/$arch/boot/$KERNEL_IMAGE" "$kernel"
+	mkinitrd "$kernel" "$initrd"
+
+	local build_dir_ro="${build_dir}-ro"
+	if [[ ! -d "$build_dir_ro" ]]; then
+		cp -a "$build_dir" "$build_dir_ro"
+	fi
+	build_kernel "$build_dir_ro" "$make_cmd" "$QEMU_KCONFIG" "$KERNEL_IMAGE" "ro"
+	local kernel_ro="$build_dir_ro/arch/$arch/boot/$KERNEL_IMAGE"
+	local initrd_ro="$tmp_dir/initrd-ro.cpio"
+	mkinitrd "$kernel_ro" "$initrd_ro"
 
-	run_qemu "$QEMU_CMD" "$KERNEL_CMDLINE" "$kernel"
+	trap - ERR
 
-	ktap_test_pass "KHO succeeded"
+	run_qemu "$QEMU_CMD" "$KERNEL_CMDLINE" "$kernel" "$initrd" "KHO"
+	run_qemu "$QEMU_CMD" "$KERNEL_CMDLINE $KHO_RO_CMDLINE kho_ro_test" \
+		 "$kernel" "$initrd_ro" "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.176.g3575e65682-goog




More information about the kexec mailing list