[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <c8b46600-d40f-41b4-a5a3-99300ef1a2eb@linux.dev>
Date: Sun, 16 Nov 2025 10:53:34 -0800
From: Zhu Yanjun <yanjun.zhu@...ux.dev>
To: Pasha Tatashin <pasha.tatashin@...een.com>, pratyush@...nel.org,
jasonmiu@...gle.com, graf@...zon.com, rppt@...nel.org, dmatlack@...gle.com,
rientjes@...gle.com, corbet@....net, rdunlap@...radead.org,
ilpo.jarvinen@...ux.intel.com, kanie@...ux.alibaba.com, ojeda@...nel.org,
aliceryhl@...gle.com, masahiroy@...nel.org, akpm@...ux-foundation.org,
tj@...nel.org, yoann.congal@...le.fr, mmaurer@...gle.com,
roman.gushchin@...ux.dev, chenridong@...wei.com, axboe@...nel.dk,
mark.rutland@....com, jannh@...gle.com, vincent.guittot@...aro.org,
hannes@...xchg.org, dan.j.williams@...el.com, david@...hat.com,
joel.granados@...nel.org, rostedt@...dmis.org, anna.schumaker@...cle.com,
song@...nel.org, linux@...ssschuh.net, linux-kernel@...r.kernel.org,
linux-doc@...r.kernel.org, linux-mm@...ck.org, gregkh@...uxfoundation.org,
tglx@...utronix.de, mingo@...hat.com, bp@...en8.de,
dave.hansen@...ux.intel.com, x86@...nel.org, hpa@...or.com,
rafael@...nel.org, dakr@...nel.org, bartosz.golaszewski@...aro.org,
cw00.choi@...sung.com, myungjoo.ham@...sung.com, yesanishhere@...il.com,
Jonathan.Cameron@...wei.com, quic_zijuhu@...cinc.com,
aleksander.lobakin@...el.com, ira.weiny@...el.com,
andriy.shevchenko@...ux.intel.com, leon@...nel.org, lukas@...ner.de,
bhelgaas@...gle.com, wagi@...nel.org, djeffery@...hat.com,
stuart.w.hayes@...il.com, ptyadav@...zon.de, lennart@...ttering.net,
brauner@...nel.org, linux-api@...r.kernel.org,
linux-fsdevel@...r.kernel.org, saeedm@...dia.com, ajayachandra@...dia.com,
jgg@...dia.com, parav@...dia.com, leonro@...dia.com, witu@...dia.com,
hughd@...gle.com, skhawaja@...gle.com, chrisl@...nel.org
Subject: Re: [PATCH v6 18/20] selftests/liveupdate: Add kexec-based selftest
for session lifecycle
在 2025/11/15 15:34, Pasha Tatashin 写道:
> Introduce a kexec-based selftest, luo_kexec_simple, to validate the
> end-to-end lifecycle of a Live Update Orchestrator (LUO) session across
> a reboot.
>
> While existing tests verify the uAPI in a pre-reboot context, this test
> ensures that the core functionality—preserving state via Kexec Handover
> and restoring it in a new kernel—works as expected.
>
> The test operates in two stages, managing its state across the reboot by
> preserving a dedicated "state session" containing a memfd. This
> mechanism dogfoods the LUO feature itself for state tracking, making the
> test self-contained.
>
> The test validates the following sequence:
>
> Stage 1 (Pre-kexec):
> - Creates a test session (test-session).
> - Creates and preserves a memfd with a known data pattern into the test
> session.
> - Creates the state-tracking session to signal progression to Stage 2.
> - Executes a kexec reboot via a helper script.
>
> Stage 2 (Post-kexec):
> - Retrieves the state-tracking session to confirm it is in the
> post-reboot stage.
> - Retrieves the preserved test session.
> - Restores the memfd from the test session and verifies its contents
> match the original data pattern written in Stage 1.
> - Finalizes both the test and state sessions to ensure a clean
> teardown.
>
> The test relies on a helper script (do_kexec.sh) to perform the reboot
> and a shared utility library (luo_test_utils.c) for common LUO
> operations, keeping the main test logic clean and focused.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin@...een.com>
> ---
> tools/testing/selftests/liveupdate/.gitignore | 1 +
> tools/testing/selftests/liveupdate/Makefile | 32 ++++
> .../testing/selftests/liveupdate/do_kexec.sh | 16 ++
> .../selftests/liveupdate/luo_kexec_simple.c | 114 ++++++++++++
> .../selftests/liveupdate/luo_test_utils.c | 168 ++++++++++++++++++
> .../selftests/liveupdate/luo_test_utils.h | 39 ++++
> 6 files changed, 370 insertions(+)
> create mode 100755 tools/testing/selftests/liveupdate/do_kexec.sh
> create mode 100644 tools/testing/selftests/liveupdate/luo_kexec_simple.c
> create mode 100644 tools/testing/selftests/liveupdate/luo_test_utils.c
> create mode 100644 tools/testing/selftests/liveupdate/luo_test_utils.h
>
> diff --git a/tools/testing/selftests/liveupdate/.gitignore b/tools/testing/selftests/liveupdate/.gitignore
> index af6e773cf98f..daeef116174d 100644
> --- a/tools/testing/selftests/liveupdate/.gitignore
> +++ b/tools/testing/selftests/liveupdate/.gitignore
> @@ -1 +1,2 @@
> /liveupdate
> +/luo_kexec_simple
> diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile
> index 2a573c36016e..1563ac84006a 100644
> --- a/tools/testing/selftests/liveupdate/Makefile
> +++ b/tools/testing/selftests/liveupdate/Makefile
> @@ -1,7 +1,39 @@
> # SPDX-License-Identifier: GPL-2.0-only
> +
> +KHDR_INCLUDES ?= -I../../../../usr/include
> CFLAGS += -Wall -O2 -Wno-unused-function
> CFLAGS += $(KHDR_INCLUDES)
> +LDFLAGS += -static
> +OUTPUT ?= .
> +
> +# --- Test Configuration (Edit this section when adding new tests) ---
> +LUO_SHARED_SRCS := luo_test_utils.c
> +LUO_SHARED_HDRS += luo_test_utils.h
> +
> +LUO_MANUAL_TESTS += luo_kexec_simple
> +
> +TEST_FILES += do_kexec.sh
>
> TEST_GEN_PROGS += liveupdate
>
> +# --- Automatic Rule Generation (Do not edit below) ---
> +
> +TEST_GEN_PROGS_EXTENDED += $(LUO_MANUAL_TESTS)
> +
> +# Define the full list of sources for each manual test.
> +$(foreach test,$(LUO_MANUAL_TESTS), \
> + $(eval $(test)_SOURCES := $(test).c $(LUO_SHARED_SRCS)))
> +
> +# This loop automatically generates an explicit build rule for each manual test.
> +# It includes dependencies on the shared headers and makes the output
> +# executable.
> +# Note the use of '$$' to escape automatic variables for the 'eval' command.
> +$(foreach test,$(LUO_MANUAL_TESTS), \
> + $(eval $(OUTPUT)/$(test): $($(test)_SOURCES) $(LUO_SHARED_HDRS) \
> + $(call msg,LINK,,$$@) ; \
> + $(Q)$(LINK.c) $$^ $(LDLIBS) -o $$@ ; \
> + $(Q)chmod +x $$@ \
> + ) \
> +)
> +
> include ../lib.mk
> diff --git a/tools/testing/selftests/liveupdate/do_kexec.sh b/tools/testing/selftests/liveupdate/do_kexec.sh
> new file mode 100755
> index 000000000000..3c7c6cafbef8
> --- /dev/null
> +++ b/tools/testing/selftests/liveupdate/do_kexec.sh
> @@ -0,0 +1,16 @@
> +#!/bin/sh
> +# SPDX-License-Identifier: GPL-2.0
> +set -e
> +
> +# Use $KERNEL and $INITRAMFS to pass custom Kernel and optional initramfs
> +
> +KERNEL="${KERNEL:-/boot/bzImage}"
> +set -- -l -s --reuse-cmdline "$KERNEL"
> +
> +INITRAMFS="${INITRAMFS:-/boot/initramfs}"
> +if [ -f "$INITRAMFS" ]; then
> + set -- "$@" --initrd="$INITRAMFS"
> +fi
> +
> +kexec "$@"
> +kexec -e
Thanks a lot. Just with kernel image, it is not enough to boot the host.
Adding initramfs will avoid the crash when the host boots.
I have made tests to verify this.
Reviewed-by: Zhu Yanjun <yanjun.zhu@...ux.dev>
Zhu Yanjun
> diff --git a/tools/testing/selftests/liveupdate/luo_kexec_simple.c b/tools/testing/selftests/liveupdate/luo_kexec_simple.c
> new file mode 100644
> index 000000000000..67ab6ebf9eec
> --- /dev/null
> +++ b/tools/testing/selftests/liveupdate/luo_kexec_simple.c
> @@ -0,0 +1,114 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +/*
> + * Copyright (c) 2025, Google LLC.
> + * Pasha Tatashin <pasha.tatashin@...een.com>
> + *
> + * A simple selftest to validate the end-to-end lifecycle of a LUO session
> + * across a single kexec reboot.
> + */
> +
> +#include "luo_test_utils.h"
> +
> +/* Test-specific constants are now defined locally */
> +#define KEXEC_SCRIPT "./do_kexec.sh"
> +#define TEST_SESSION_NAME "test-session"
> +#define TEST_MEMFD_TOKEN 0x1A
> +#define TEST_MEMFD_DATA "hello kexec world"
> +
> +/* Constants for the state-tracking mechanism, specific to this test file. */
> +#define STATE_SESSION_NAME "kexec_simple_state"
> +#define STATE_MEMFD_TOKEN 999
> +
> +/* Stage 1: Executed before the kexec reboot. */
> +static void run_stage_1(int luo_fd)
> +{
> + int session_fd;
> +
> + ksft_print_msg("[STAGE 1] Starting pre-kexec setup...\n");
> +
> + ksft_print_msg("[STAGE 1] Creating state file for next stage (2)...\n");
> + create_state_file(luo_fd, STATE_SESSION_NAME, STATE_MEMFD_TOKEN, 2);
> +
> + ksft_print_msg("[STAGE 1] Creating session '%s' and preserving memfd...\n",
> + TEST_SESSION_NAME);
> + 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 (create_and_preserve_memfd(session_fd, TEST_MEMFD_TOKEN,
> + TEST_MEMFD_DATA) < 0) {
> + fail_exit("create_and_preserve_memfd for token %#x",
> + TEST_MEMFD_TOKEN);
> + }
> +
> + ksft_print_msg("[STAGE 1] Executing kexec...\n");
> + if (system(KEXEC_SCRIPT) != 0)
> + fail_exit("kexec script failed");
> + exit(EXIT_FAILURE);
> +}
> +
> +/* Stage 2: Executed after the kexec reboot. */
> +static void run_stage_2(int luo_fd, int state_session_fd)
> +{
> + int session_fd, mfd, stage;
> +
> + ksft_print_msg("[STAGE 2] Starting post-kexec verification...\n");
> +
> + restore_and_read_stage(state_session_fd, STATE_MEMFD_TOKEN, &stage);
> + if (stage != 2)
> + fail_exit("Expected stage 2, but state file contains %d", stage);
> +
> + ksft_print_msg("[STAGE 2] Retrieving session '%s'...\n", TEST_SESSION_NAME);
> + session_fd = luo_retrieve_session(luo_fd, TEST_SESSION_NAME);
> + if (session_fd < 0)
> + fail_exit("luo_retrieve_session for '%s'", TEST_SESSION_NAME);
> +
> + ksft_print_msg("[STAGE 2] Restoring and verifying memfd (token %#x)...\n",
> + TEST_MEMFD_TOKEN);
> + mfd = restore_and_verify_memfd(session_fd, TEST_MEMFD_TOKEN,
> + TEST_MEMFD_DATA);
> + if (mfd < 0)
> + fail_exit("restore_and_verify_memfd for token %#x", TEST_MEMFD_TOKEN);
> + close(mfd);
> +
> + ksft_print_msg("[STAGE 2] Test data verified successfully.\n");
> + ksft_print_msg("[STAGE 2] Finalizing test session...\n");
> + if (luo_session_finish(session_fd) < 0)
> + fail_exit("luo_session_finish for test session");
> + close(session_fd);
> +
> + ksft_print_msg("[STAGE 2] Finalizing state session...\n");
> + if (luo_session_finish(state_session_fd) < 0)
> + fail_exit("luo_session_finish for state session");
> + close(state_session_fd);
> +
> + ksft_print_msg("\n--- SIMPLE KEXEC TEST PASSED ---\n");
> +}
> +
> +int main(int argc, char *argv[])
> +{
> + int luo_fd;
> + int state_session_fd;
> +
> + luo_fd = luo_open_device();
> + if (luo_fd < 0)
> + ksft_exit_skip("Failed to open %s. Is the luo module loaded?\n",
> + LUO_DEVICE);
> +
> + /*
> + * Determine the stage by attempting to retrieve the state session.
> + * If it doesn't exist (ENOENT), we are in Stage 1 (pre-kexec).
> + */
> + state_session_fd = luo_retrieve_session(luo_fd, STATE_SESSION_NAME);
> + if (state_session_fd == -ENOENT) {
> + run_stage_1(luo_fd);
> + } else if (state_session_fd >= 0) {
> + /* We got a valid handle, pass it directly to stage 2 */
> + run_stage_2(luo_fd, state_session_fd);
> + } else {
> + fail_exit("Failed to check for state session");
> + }
> +
> + close(luo_fd);
> +}
> diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.c b/tools/testing/selftests/liveupdate/luo_test_utils.c
> new file mode 100644
> index 000000000000..0a24105cbc54
> --- /dev/null
> +++ b/tools/testing/selftests/liveupdate/luo_test_utils.c
> @@ -0,0 +1,168 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +/*
> + * Copyright (c) 2025, Google LLC.
> + * Pasha Tatashin <pasha.tatashin@...een.com>
> + */
> +
> +#define _GNU_SOURCE
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +#include <sys/ioctl.h>
> +#include <sys/syscall.h>
> +#include <sys/mman.h>
> +#include <errno.h>
> +#include <stdarg.h>
> +
> +#include "luo_test_utils.h"
> +
> +int luo_open_device(void)
> +{
> + return open(LUO_DEVICE, O_RDWR);
> +}
> +
> +int luo_create_session(int luo_fd, const char *name)
> +{
> + struct liveupdate_ioctl_create_session arg = { .size = sizeof(arg) };
> +
> + snprintf((char *)arg.name, LIVEUPDATE_SESSION_NAME_LENGTH, "%.*s",
> + LIVEUPDATE_SESSION_NAME_LENGTH - 1, name);
> +
> + if (ioctl(luo_fd, LIVEUPDATE_IOCTL_CREATE_SESSION, &arg) < 0)
> + return -errno;
> +
> + return arg.fd;
> +}
> +
> +int luo_retrieve_session(int luo_fd, const char *name)
> +{
> + struct liveupdate_ioctl_retrieve_session arg = { .size = sizeof(arg) };
> +
> + snprintf((char *)arg.name, LIVEUPDATE_SESSION_NAME_LENGTH, "%.*s",
> + LIVEUPDATE_SESSION_NAME_LENGTH - 1, name);
> +
> + if (ioctl(luo_fd, LIVEUPDATE_IOCTL_RETRIEVE_SESSION, &arg) < 0)
> + return -errno;
> +
> + return arg.fd;
> +}
> +
> +int create_and_preserve_memfd(int session_fd, int token, const char *data)
> +{
> + struct liveupdate_session_preserve_fd arg = { .size = sizeof(arg) };
> + long page_size = sysconf(_SC_PAGE_SIZE);
> + void *map = MAP_FAILED;
> + int mfd = -1, ret = -1;
> +
> + mfd = memfd_create("test_mfd", 0);
> + if (mfd < 0)
> + return -errno;
> +
> + if (ftruncate(mfd, page_size) != 0)
> + goto out;
> +
> + map = mmap(NULL, page_size, PROT_WRITE, MAP_SHARED, mfd, 0);
> + if (map == MAP_FAILED)
> + goto out;
> +
> + snprintf(map, page_size, "%s", data);
> + munmap(map, page_size);
> +
> + arg.fd = mfd;
> + arg.token = token;
> + if (ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &arg) < 0)
> + goto out;
> +
> + ret = 0;
> +out:
> + if (ret != 0 && errno != 0)
> + ret = -errno;
> + if (mfd >= 0)
> + close(mfd);
> + return ret;
> +}
> +
> +int restore_and_verify_memfd(int session_fd, int token,
> + const char *expected_data)
> +{
> + struct liveupdate_session_retrieve_fd arg = { .size = sizeof(arg) };
> + long page_size = sysconf(_SC_PAGE_SIZE);
> + void *map = MAP_FAILED;
> + int mfd = -1, ret = -1;
> +
> + arg.token = token;
> + if (ioctl(session_fd, LIVEUPDATE_SESSION_RETRIEVE_FD, &arg) < 0)
> + return -errno;
> + mfd = arg.fd;
> +
> + map = mmap(NULL, page_size, PROT_READ, MAP_SHARED, mfd, 0);
> + if (map == MAP_FAILED)
> + goto out;
> +
> + if (expected_data && strcmp(expected_data, map) != 0) {
> + ksft_print_msg("Data mismatch! Expected '%s', Got '%s'\n",
> + expected_data, (char *)map);
> + ret = -EINVAL;
> + goto out_munmap;
> + }
> +
> + ret = mfd;
> +out_munmap:
> + munmap(map, page_size);
> +out:
> + if (ret < 0 && errno != 0)
> + ret = -errno;
> + if (ret < 0 && mfd >= 0)
> + close(mfd);
> + return ret;
> +}
> +
> +int luo_session_finish(int session_fd)
> +{
> + struct liveupdate_session_finish arg = { .size = sizeof(arg) };
> +
> + if (ioctl(session_fd, LIVEUPDATE_SESSION_FINISH, &arg) < 0)
> + return -errno;
> +
> + return 0;
> +}
> +
> +void create_state_file(int luo_fd, const char *session_name, int token,
> + int next_stage)
> +{
> + char buf[32];
> + int state_session_fd;
> +
> + state_session_fd = luo_create_session(luo_fd, session_name);
> + if (state_session_fd < 0)
> + fail_exit("luo_create_session for state tracking");
> +
> + snprintf(buf, sizeof(buf), "%d", next_stage);
> + if (create_and_preserve_memfd(state_session_fd, token, buf) < 0)
> + fail_exit("create_and_preserve_memfd for state tracking");
> +
> + /*
> + * DO NOT close session FD, otherwise it is going to be unpreserved
> + */
> +}
> +
> +void restore_and_read_stage(int state_session_fd, int token, int *stage)
> +{
> + char buf[32] = {0};
> + int mfd;
> +
> + mfd = restore_and_verify_memfd(state_session_fd, token, NULL);
> + if (mfd < 0)
> + fail_exit("failed to restore state memfd");
> +
> + if (read(mfd, buf, sizeof(buf) - 1) < 0)
> + fail_exit("failed to read state mfd");
> +
> + *stage = atoi(buf);
> +
> + close(mfd);
> +}
> diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.h b/tools/testing/selftests/liveupdate/luo_test_utils.h
> new file mode 100644
> index 000000000000..093e787b9f4b
> --- /dev/null
> +++ b/tools/testing/selftests/liveupdate/luo_test_utils.h
> @@ -0,0 +1,39 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +/*
> + * Copyright (c) 2025, Google LLC.
> + * Pasha Tatashin <pasha.tatashin@...een.com>
> + *
> + * Utility functions for LUO kselftests.
> + */
> +
> +#ifndef LUO_TEST_UTILS_H
> +#define LUO_TEST_UTILS_H
> +
> +#include <errno.h>
> +#include <string.h>
> +#include <linux/liveupdate.h>
> +#include "../kselftest.h"
> +
> +#define LUO_DEVICE "/dev/liveupdate"
> +
> +#define fail_exit(fmt, ...) \
> + ksft_exit_fail_msg("[%s:%d] " fmt " (errno: %s)\n", \
> + __func__, __LINE__, ##__VA_ARGS__, strerror(errno))
> +
> +/* Generic LUO and session management helpers */
> +int luo_open_device(void);
> +int luo_create_session(int luo_fd, const char *name);
> +int luo_retrieve_session(int luo_fd, const char *name);
> +int luo_session_finish(int session_fd);
> +
> +/* Generic file preservation and restoration helpers */
> +int create_and_preserve_memfd(int session_fd, int token, const char *data);
> +int restore_and_verify_memfd(int session_fd, int token, const char *expected_data);
> +
> +/* Kexec state-tracking helpers */
> +void create_state_file(int luo_fd, const char *session_name, int token,
> + int next_stage);
> +void restore_and_read_stage(int state_session_fd, int token, int *stage);
> +
> +#endif /* LUO_TEST_UTILS_H */
Powered by blists - more mailing lists