Documentation/core-api/index.rst | 1 + Documentation/core-api/liveupdate.rst | 71 ++ Documentation/mm/index.rst | 1 + Documentation/mm/memfd_preservation.rst | 23 + Documentation/userspace-api/index.rst | 1 + .../userspace-api/ioctl/ioctl-number.rst | 2 + Documentation/userspace-api/liveupdate.rst | 20 + MAINTAINERS | 16 + include/linux/kho/abi/luo.h | 243 +++++ include/linux/kho/abi/memfd.h | 77 ++ include/linux/liveupdate.h | 311 ++++++ include/linux/shmem_fs.h | 23 + include/uapi/linux/liveupdate.h | 216 ++++ kernel/kexec_core.c | 5 + kernel/liveupdate/Kconfig | 27 + kernel/liveupdate/Makefile | 8 + kernel/liveupdate/luo_core.c | 454 ++++++++ kernel/liveupdate/luo_file.c | 987 ++++++++++++++++++ kernel/liveupdate/luo_flb.c | 701 +++++++++++++ kernel/liveupdate/luo_internal.h | 141 +++ kernel/liveupdate/luo_session.c | 645 ++++++++++++ lib/Kconfig.debug | 23 + lib/tests/Makefile | 1 + lib/tests/liveupdate.c | 160 +++ mm/Makefile | 1 + mm/internal.h | 6 + mm/memfd_luo.c | 517 +++++++++ mm/shmem.c | 57 +- tools/testing/selftests/Makefile | 1 + tools/testing/selftests/liveupdate/.gitignore | 9 + tools/testing/selftests/liveupdate/Makefile | 34 + tools/testing/selftests/liveupdate/config | 11 + .../testing/selftests/liveupdate/do_kexec.sh | 16 + tools/testing/selftests/liveupdate/init.c | 174 +++ .../testing/selftests/liveupdate/liveupdate.c | 348 ++++++ .../selftests/liveupdate/luo_kexec_simple.c | 89 ++ .../selftests/liveupdate/luo_multi_session.c | 162 +++ .../testing/selftests/liveupdate/luo_test.sh | 296 ++++++ .../selftests/liveupdate/luo_test_utils.c | 266 +++++ .../selftests/liveupdate/luo_test_utils.h | 44 + tools/testing/selftests/liveupdate/run.sh | 68 ++ 41 files changed, 6235 insertions(+), 21 deletions(-) create mode 100644 Documentation/core-api/liveupdate.rst create mode 100644 Documentation/mm/memfd_preservation.rst create mode 100644 Documentation/userspace-api/liveupdate.rst create mode 100644 include/linux/kho/abi/luo.h create mode 100644 include/linux/kho/abi/memfd.h create mode 100644 include/linux/liveupdate.h create mode 100644 include/uapi/linux/liveupdate.h create mode 100644 kernel/liveupdate/luo_core.c create mode 100644 kernel/liveupdate/luo_file.c create mode 100644 kernel/liveupdate/luo_flb.c create mode 100644 kernel/liveupdate/luo_internal.h create mode 100644 kernel/liveupdate/luo_session.c create mode 100644 lib/tests/liveupdate.c create mode 100644 mm/memfd_luo.c create mode 100644 tools/testing/selftests/liveupdate/.gitignore create mode 100644 tools/testing/selftests/liveupdate/Makefile create mode 100644 tools/testing/selftests/liveupdate/config create mode 100755 tools/testing/selftests/liveupdate/do_kexec.sh create mode 100644 tools/testing/selftests/liveupdate/init.c create mode 100644 tools/testing/selftests/liveupdate/liveupdate.c create mode 100644 tools/testing/selftests/liveupdate/luo_kexec_simple.c create mode 100644 tools/testing/selftests/liveupdate/luo_multi_session.c create mode 100755 tools/testing/selftests/liveupdate/luo_test.sh create mode 100644 tools/testing/selftests/liveupdate/luo_test_utils.c create mode 100644 tools/testing/selftests/liveupdate/luo_test_utils.h create mode 100755 tools/testing/selftests/liveupdate/run.sh
This series introduces the Live Update Orchestrator, a kernel subsystem designed to facilitate live kernel updates using a kexec-based reboot. This capability is critical for cloud environments, allowing hypervisors to be updated with minimal downtime for running virtual machines. LUO achieves this by preserving the state of selected resources, such as memory, devices and their dependencies, across the kernel transition. As a key feature, this series includes support for preserving memfd file descriptors, which allows critical in-memory data, such as guest RAM or any other large memory region, to be maintained in RAM across the kexec reboot. The other series that use LUO, are VFIO [1], IOMMU [2], and PCI [3] preservations. Github repo of this series [4]. The core of LUO is a framework for managing the lifecycle of preserved resources through a userspace-driven interface. Key features include: - Session Management Userspace agent (i.e. luod [5]) creates named sessions, each represented by a file descriptor (via centralized agent that controls /dev/liveupdate). The lifecycle of all preserved resources within a session is tied to this FD, ensuring automatic kernel cleanup if the controlling userspace agent crashes or exits unexpectedly. - File Preservation A handler-based framework allows specific file types (demonstrated here with memfd) to be preserved. Handlers manage the serialization, restoration, and lifecycle of their specific file types. - File-Lifecycle-Bound State A new mechanism for managing shared global state whose lifecycle is tied to the preservation of one or more files. This is crucial for subsystems like IOMMU or HugeTLB, where multiple file descriptors may depend on a single, shared underlying resource that must be preserved only once. - KHO Integration LUO drives the Kexec Handover framework programmatically to pass its serialized metadata to the next kernel. The LUO state is finalized and added to the kexec image just before the reboot is triggered. In the future this step will also be removed once stateless KHO is merged [6]. - Userspace Interface Control is provided via ioctl commands on /dev/liveupdate for creating and retrieving sessions, as well as on session file descriptors for managing individual files. - Testing The series includes a set of selftests, including userspace API validation, kexec-based lifecycle tests for various session and file scenarios, and a new in-kernel test module to validate the FLB logic. Changelog since v6 [7] - Collected Reviewed-by tags from Mike Rapoport, Pratyush Yadav, and Zhu Yanjun. Addressed all outstanding comments. - Moved ABI headers from include/linux/liveupdate/abi/ to include/linux/kho/abi/ to align with other future users of KHO and KHO itself. - Separated internal APIs to allow kernel subsystems to preserve file objects programmatically. - Introduced struct luo_file_set to manage groups of preserved files, decoupling this logic from the luo_session structure. This simplifies internal management and serialization. - Implemented luo_session_quiesce() and luo_session_resume() mechanisms. These ensure that file handlers and FLBs can be safely unregistered (liveupdate_unregister_file_handler, liveupdate_unregister_flb) by preventing new operations while unregistration is in progress. - Added a comprehensive test orchestration framework. This includes a custom init process (init.c) and scripts (luo_test.sh, run.sh) to automate kexec testing within QEMU environments across x86_64 and arm64. [1] https://lore.kernel.org/all/20251018000713.677779-1-vipinsh@google.com/ [2] https://lore.kernel.org/linux-iommu/20250928190624.3735830-1-skhawaja@google.com [3] https://lore.kernel.org/linux-pci/20250916-luo-pci-v2-0-c494053c3c08@kernel.org [4] https://github.com/googleprodkernel/linux-liveupdate/tree/luo/v7 [5] https://tinyurl.com/luoddesign [6] https://lore.kernel.org/all/20251020100306.2709352-1-jasonmiu@google.com [7] https://lore.kernel.org/all/20251115233409.768044-1-pasha.tatashin@soleen.com Pasha Tatashin (16): liveupdate: luo_core: Live Update Orchestrator liveupdate: luo_core: integrate with KHO kexec: call liveupdate_reboot() before kexec liveupdate: luo_session: add sessions support liveupdate: luo_core: add user interface liveupdate: luo_file: implement file systems callbacks liveupdate: luo_session: Add ioctls for file preservation docs: add luo documentation MAINTAINERS: add liveupdate entry selftests/liveupdate: Add userspace API selftests selftests/liveupdate: Add kexec-based selftest for selftests/liveupdate: Add kexec test for multiple and empty sessions selftests/liveupdate: add test infrastructure and scripts liveupdate: luo_file: Add internal APIs for file preservation liveupdate: luo_flb: Introduce File-Lifecycle-Bound global state tests/liveupdate: Add in-kernel liveupdate test Pratyush Yadav (6): mm: shmem: use SHMEM_F_* flags instead of VM_* flags mm: shmem: allow freezing inode mapping mm: shmem: export some functions to internal.h liveupdate: luo_file: add private argument to store runtime state mm: memfd_luo: allow preserving memfd docs: add documentation for memfd preservation via LUO Documentation/core-api/index.rst | 1 + Documentation/core-api/liveupdate.rst | 71 ++ Documentation/mm/index.rst | 1 + Documentation/mm/memfd_preservation.rst | 23 + Documentation/userspace-api/index.rst | 1 + .../userspace-api/ioctl/ioctl-number.rst | 2 + Documentation/userspace-api/liveupdate.rst | 20 + MAINTAINERS | 16 + include/linux/kho/abi/luo.h | 243 +++++ include/linux/kho/abi/memfd.h | 77 ++ include/linux/liveupdate.h | 311 ++++++ include/linux/shmem_fs.h | 23 + include/uapi/linux/liveupdate.h | 216 ++++ kernel/kexec_core.c | 5 + kernel/liveupdate/Kconfig | 27 + kernel/liveupdate/Makefile | 8 + kernel/liveupdate/luo_core.c | 454 ++++++++ kernel/liveupdate/luo_file.c | 987 ++++++++++++++++++ kernel/liveupdate/luo_flb.c | 701 +++++++++++++ kernel/liveupdate/luo_internal.h | 141 +++ kernel/liveupdate/luo_session.c | 645 ++++++++++++ lib/Kconfig.debug | 23 + lib/tests/Makefile | 1 + lib/tests/liveupdate.c | 160 +++ mm/Makefile | 1 + mm/internal.h | 6 + mm/memfd_luo.c | 517 +++++++++ mm/shmem.c | 57 +- tools/testing/selftests/Makefile | 1 + tools/testing/selftests/liveupdate/.gitignore | 9 + tools/testing/selftests/liveupdate/Makefile | 34 + tools/testing/selftests/liveupdate/config | 11 + .../testing/selftests/liveupdate/do_kexec.sh | 16 + tools/testing/selftests/liveupdate/init.c | 174 +++ .../testing/selftests/liveupdate/liveupdate.c | 348 ++++++ .../selftests/liveupdate/luo_kexec_simple.c | 89 ++ .../selftests/liveupdate/luo_multi_session.c | 162 +++ .../testing/selftests/liveupdate/luo_test.sh | 296 ++++++ .../selftests/liveupdate/luo_test_utils.c | 266 +++++ .../selftests/liveupdate/luo_test_utils.h | 44 + tools/testing/selftests/liveupdate/run.sh | 68 ++ 41 files changed, 6235 insertions(+), 21 deletions(-) create mode 100644 Documentation/core-api/liveupdate.rst create mode 100644 Documentation/mm/memfd_preservation.rst create mode 100644 Documentation/userspace-api/liveupdate.rst create mode 100644 include/linux/kho/abi/luo.h create mode 100644 include/linux/kho/abi/memfd.h create mode 100644 include/linux/liveupdate.h create mode 100644 include/uapi/linux/liveupdate.h create mode 100644 kernel/liveupdate/luo_core.c create mode 100644 kernel/liveupdate/luo_file.c create mode 100644 kernel/liveupdate/luo_flb.c create mode 100644 kernel/liveupdate/luo_internal.h create mode 100644 kernel/liveupdate/luo_session.c create mode 100644 lib/tests/liveupdate.c create mode 100644 mm/memfd_luo.c create mode 100644 tools/testing/selftests/liveupdate/.gitignore create mode 100644 tools/testing/selftests/liveupdate/Makefile create mode 100644 tools/testing/selftests/liveupdate/config create mode 100755 tools/testing/selftests/liveupdate/do_kexec.sh create mode 100644 tools/testing/selftests/liveupdate/init.c create mode 100644 tools/testing/selftests/liveupdate/liveupdate.c create mode 100644 tools/testing/selftests/liveupdate/luo_kexec_simple.c create mode 100644 tools/testing/selftests/liveupdate/luo_multi_session.c create mode 100755 tools/testing/selftests/liveupdate/luo_test.sh create mode 100644 tools/testing/selftests/liveupdate/luo_test_utils.c create mode 100644 tools/testing/selftests/liveupdate/luo_test_utils.h create mode 100755 tools/testing/selftests/liveupdate/run.sh base-commit: 2cb7e27ffe3e3e1d8a837026462ebca22cba3b4f -- 2.52.0.rc2.455.g230fcf2819-goog
On Sat, 22 Nov 2025 17:23:27 -0500 Pasha Tatashin <pasha.tatashin@soleen.com> wrote:
> This series introduces the Live Update Orchestrator, a kernel subsystem
> designed to facilitate live kernel updates using a kexec-based reboot.
>
> ...
>
I udpated mm.git's mm-unstable branch to this version.
> Changelog since v6 [7]
> - Collected Reviewed-by tags from Mike Rapoport,
> Pratyush Yadav, and Zhu Yanjun. Addressed all outstanding comments.
> - Moved ABI headers from include/linux/liveupdate/abi/ to
> include/linux/kho/abi/ to align with other future users of KHO and KHO
> itself.
> - Separated internal APIs to allow kernel subsystems to preserve file
> objects programmatically.
> - Introduced struct luo_file_set to manage groups of preserved files,
> decoupling this logic from the luo_session structure. This simplifies
> internal management and serialization.
> - Implemented luo_session_quiesce() and luo_session_resume() mechanisms.
> These ensure that file handlers and FLBs can be safely unregistered
> (liveupdate_unregister_file_handler, liveupdate_unregister_flb) by
> preventing new operations while unregistration is in progress.
> - Added a comprehensive test orchestration framework. This includes a
> custom init process (init.c) and scripts (luo_test.sh, run.sh) to
> automate kexec testing within QEMU environments across x86_64 and
> arm64.
>
Below is how this v7 series altered mm.git.
Documentation/core-api/liveupdate.rst | 4
Documentation/mm/memfd_preservation.rst | 4
Documentation/userspace-api/liveupdate.rst | 2
MAINTAINERS | 1
include/linux/kho/abi/luo.h | 243 +++++
include/linux/kho/abi/memfd.h | 77 +
include/linux/liveupdate.h | 92 +-
include/linux/liveupdate/abi/luo.h | 238 -----
include/linux/liveupdate/abi/memfd.h | 88 -
include/linux/shmem_fs.h | 2
include/uapi/linux/liveupdate.h | 4
kernel/liveupdate/Makefile | 1
kernel/liveupdate/luo_core.c | 216 ++++
kernel/liveupdate/luo_file.c | 447 +++++-----
kernel/liveupdate/luo_flb.c | 425 +++++----
kernel/liveupdate/luo_internal.h | 98 +-
kernel/liveupdate/luo_ioctl.c | 223 ----
kernel/liveupdate/luo_session.c | 187 ++--
lib/tests/liveupdate.c | 31
mm/memfd_luo.c | 402 ++------
mm/shmem.c | 11
tools/testing/selftests/liveupdate/.gitignore | 12
tools/testing/selftests/liveupdate/Makefile | 52 -
tools/testing/selftests/liveupdate/config | 6
tools/testing/selftests/liveupdate/init.c | 174 +++
tools/testing/selftests/liveupdate/luo_kexec_simple.c | 33
tools/testing/selftests/liveupdate/luo_multi_session.c | 36
tools/testing/selftests/liveupdate/luo_test.sh | 296 ++++++
tools/testing/selftests/liveupdate/luo_test_utils.c | 98 ++
tools/testing/selftests/liveupdate/luo_test_utils.h | 11
tools/testing/selftests/liveupdate/run.sh | 68 +
31 files changed, 2138 insertions(+), 1444 deletions(-)
--- a/Documentation/core-api/liveupdate.rst~b
+++ a/Documentation/core-api/liveupdate.rst
@@ -25,7 +25,7 @@ LUO File Lifecycle Bound Global Data
Live Update Orchestrator ABI
============================
-.. kernel-doc:: include/linux/liveupdate/abi/luo.h
+.. kernel-doc:: include/linux/kho/abi/luo.h
:doc: Live Update Orchestrator ABI
The following types of file descriptors can be preserved
@@ -39,7 +39,7 @@ Public API
==========
.. kernel-doc:: include/linux/liveupdate.h
-.. kernel-doc:: include/linux/liveupdate/abi/luo.h
+.. kernel-doc:: include/linux/kho/abi/luo.h
.. kernel-doc:: kernel/liveupdate/luo_core.c
:export:
--- a/Documentation/mm/memfd_preservation.rst~b
+++ a/Documentation/mm/memfd_preservation.rst
@@ -10,10 +10,10 @@ Memfd Preservation via LUO
Memfd Preservation ABI
======================
-.. kernel-doc:: include/linux/liveupdate/abi/memfd.h
+.. kernel-doc:: include/linux/kho/abi/memfd.h
:doc: DOC: memfd Live Update ABI
-.. kernel-doc:: include/linux/liveupdate/abi/memfd.h
+.. kernel-doc:: include/linux/kho/abi/memfd.h
:internal:
See Also
--- a/Documentation/userspace-api/liveupdate.rst~b
+++ a/Documentation/userspace-api/liveupdate.rst
@@ -7,7 +7,7 @@ Live Update uAPI
ioctl interface
===============
-.. kernel-doc:: kernel/liveupdate/luo_ioctl.c
+.. kernel-doc:: kernel/liveupdate/luo_core.c
:doc: LUO ioctl Interface
ioctl uAPI
diff --git a/include/linux/kho/abi/luo.h a/include/linux/kho/abi/luo.h
new file mode 100644
--- /dev/null
+++ a/include/linux/kho/abi/luo.h
@@ -0,0 +1,243 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (c) 2025, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ */
+
+/**
+ * DOC: Live Update Orchestrator ABI
+ *
+ * This header defines the stable Application Binary Interface used by the
+ * Live Update Orchestrator to pass state from a pre-update kernel to a
+ * post-update kernel. The ABI is built upon the Kexec HandOver framework
+ * and uses a Flattened Device Tree to describe the preserved data.
+ *
+ * This interface is a contract. Any modification to the FDT structure, node
+ * properties, compatible strings, or the layout of the `__packed` serialization
+ * structures defined here constitutes a breaking change. Such changes require
+ * incrementing the version number in the relevant `_COMPATIBLE` string to
+ * prevent a new kernel from misinterpreting data from an old kernel.
+ *
+ * FDT Structure Overview:
+ * The entire LUO state is encapsulated within a single KHO entry named "LUO".
+ * This entry contains an FDT with the following layout:
+ *
+ * .. code-block:: none
+ *
+ * / {
+ * compatible = "luo-v1";
+ * liveupdate-number = <...>;
+ *
+ * luo-session {
+ * compatible = "luo-session-v1";
+ * luo-session-header = <phys_addr_of_session_header_ser>;
+ * };
+ *
+ * luo-flb {
+ * compatible = "luo-flb-v1";
+ * luo-flb-header = <phys_addr_of_flb_header_ser>;
+ * };
+ * };
+ *
+ * Main LUO Node (/):
+ *
+ * - compatible: "luo-v1"
+ * Identifies the overall LUO ABI version.
+ * - liveupdate-number: u64
+ * A counter tracking the number of successful live updates performed.
+ *
+ * Session Node (luo-session):
+ * This node describes all preserved user-space sessions.
+ *
+ * - compatible: "luo-session-v1"
+ * Identifies the session ABI version.
+ * - luo-session-header: u64
+ * The physical address of a `struct luo_session_header_ser`. This structure
+ * is the header for a contiguous block of memory containing an array of
+ * `struct luo_session_ser`, one for each preserved session.
+ *
+ * File-Lifecycle-Bound Node (luo-flb):
+ * This node describes all preserved global objects whose lifecycle is bound
+ * to that of the preserved files (e.g., shared IOMMU state).
+ *
+ * - compatible: "luo-flb-v1"
+ * Identifies the FLB ABI version.
+ * - luo-flb-header: u64
+ * The physical address of a `struct luo_flb_header_ser`. This structure is
+ * the header for a contiguous block of memory containing an array of
+ * `struct luo_flb_ser`, one for each preserved global object.
+ *
+ * Serialization Structures:
+ * The FDT properties point to memory regions containing arrays of simple,
+ * `__packed` structures. These structures contain the actual preserved state.
+ *
+ * - struct luo_session_header_ser:
+ * Header for the session array. Contains the total page count of the
+ * preserved memory block and the number of `struct luo_session_ser`
+ * entries that follow.
+ *
+ * - struct luo_session_ser:
+ * Metadata for a single session, including its name and a physical pointer
+ * to another preserved memory block containing an array of
+ * `struct luo_file_ser` for all files in that session.
+ *
+ * - struct luo_file_ser:
+ * Metadata for a single preserved file. Contains the `compatible` string to
+ * find the correct handler in the new kernel, a user-provided `token` for
+ * identification, and an opaque `data` handle for the handler to use.
+ *
+ * - struct luo_flb_header_ser:
+ * Header for the FLB array. Contains the total page count of the
+ * preserved memory block and the number of `struct luo_flb_ser` entries
+ * that follow.
+ *
+ * - struct luo_flb_ser:
+ * Metadata for a single preserved global object. Contains its `name`
+ * (compatible string), an opaque `data` handle, and the `count`
+ * number of files depending on it.
+ */
+
+#ifndef _LINUX_KHO_ABI_LUO_H
+#define _LINUX_KHO_ABI_LUO_H
+
+#include <uapi/linux/liveupdate.h>
+
+/*
+ * The LUO FDT hooks all LUO state for sessions, fds, etc.
+ * In the root it also carries "liveupdate-number" 64-bit property that
+ * corresponds to the number of live-updates performed on this machine.
+ */
+#define LUO_FDT_SIZE PAGE_SIZE
+#define LUO_FDT_KHO_ENTRY_NAME "LUO"
+#define LUO_FDT_COMPATIBLE "luo-v1"
+#define LUO_FDT_LIVEUPDATE_NUM "liveupdate-number"
+
+#define LIVEUPDATE_HNDL_COMPAT_LENGTH 48
+
+/**
+ * struct luo_file_ser - Represents the serialized preserves files.
+ * @compatible: File handler compatible string.
+ * @data: Private data
+ * @token: User provided token for this file
+ *
+ * If this structure is modified, LUO_SESSION_COMPATIBLE must be updated.
+ */
+struct luo_file_ser {
+ char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
+ u64 data;
+ u64 token;
+} __packed;
+
+/**
+ * struct luo_file_set_ser - Represents the serialized metadata for file set
+ * @files: The physical address of a contiguous memory block that holds
+ * the serialized state of files (array of luo_file_ser) in this file
+ * set.
+ * @count: The total number of files that were part of this session during
+ * serialization. Used for iteration and validation during
+ * restoration.
+ */
+struct luo_file_set_ser {
+ u64 files;
+ u64 count;
+} __packed;
+
+/*
+ * LUO FDT session node
+ * LUO_FDT_SESSION_HEADER: is a u64 physical address of struct
+ * luo_session_header_ser
+ */
+#define LUO_FDT_SESSION_NODE_NAME "luo-session"
+#define LUO_FDT_SESSION_COMPATIBLE "luo-session-v2"
+#define LUO_FDT_SESSION_HEADER "luo-session-header"
+
+/**
+ * struct luo_session_header_ser - Header for the serialized session data block.
+ * @count: The number of `struct luo_session_ser` entries that immediately
+ * follow this header in the memory block.
+ *
+ * This structure is located at the beginning of a contiguous block of
+ * physical memory preserved across the kexec. It provides the necessary
+ * metadata to interpret the array of session entries that follow.
+ *
+ * If this structure is modified, `LUO_FDT_SESSION_COMPATIBLE` must be updated.
+ */
+struct luo_session_header_ser {
+ u64 count;
+} __packed;
+
+/**
+ * struct luo_session_ser - Represents the serialized metadata for a LUO session.
+ * @name: The unique name of the session, provided by the userspace at
+ * the time of session creation.
+ * @file_set_ser: Serialized files belonging to this session,
+ *
+ * This structure is used to package session-specific metadata for transfer
+ * between kernels via Kexec Handover. An array of these structures (one per
+ * session) is created and passed to the new kernel, allowing it to reconstruct
+ * the session context.
+ *
+ * If this structure is modified, `LUO_FDT_SESSION_COMPATIBLE` must be updated.
+ */
+struct luo_session_ser {
+ char name[LIVEUPDATE_SESSION_NAME_LENGTH];
+ struct luo_file_set_ser file_set_ser;
+} __packed;
+
+/* The max size is set so it can be reliably used during in serialization */
+#define LIVEUPDATE_FLB_COMPAT_LENGTH 48
+
+#define LUO_FDT_FLB_NODE_NAME "luo-flb"
+#define LUO_FDT_FLB_COMPATIBLE "luo-flb-v1"
+#define LUO_FDT_FLB_HEADER "luo-flb-header"
+
+/**
+ * struct luo_flb_header_ser - Header for the serialized FLB data block.
+ * @pgcnt: The total number of pages occupied by the entire preserved memory
+ * region, including this header and the subsequent array of
+ * &struct luo_flb_ser entries.
+ * @count: The number of &struct luo_flb_ser entries that follow this header
+ * in the memory block.
+ *
+ * This structure is located at the physical address specified by the
+ * `LUO_FDT_FLB_HEADER` FDT property. It provides the new kernel with the
+ * necessary information to find and iterate over the array of preserved
+ * File-Lifecycle-Bound objects and to manage the underlying memory.
+ *
+ * If this structure is modified, LUO_FDT_FLB_COMPATIBLE must be updated.
+ */
+struct luo_flb_header_ser {
+ u64 pgcnt;
+ u64 count;
+} __packed;
+
+/**
+ * struct luo_flb_ser - Represents the serialized state of a single FLB object.
+ * @name: The unique compatibility string of the FLB object, used to find the
+ * corresponding &struct liveupdate_flb handler in the new kernel.
+ * @data: The opaque u64 handle returned by the FLB's .preserve() operation
+ * in the old kernel. This handle encapsulates the entire state needed
+ * for restoration.
+ * @count: The reference count at the time of serialization; i.e., the number
+ * of preserved files that depended on this FLB. This is used by the
+ * new kernel to correctly manage the FLB's lifecycle.
+ *
+ * An array of these structures is created in a preserved memory region and
+ * passed to the new kernel. Each entry allows the LUO core to restore one
+ * global, shared object.
+ *
+ * If this structure is modified, LUO_FDT_FLB_COMPATIBLE must be updated.
+ */
+struct luo_flb_ser {
+ char name[LIVEUPDATE_FLB_COMPAT_LENGTH];
+ u64 data;
+ u64 count;
+} __packed;
+
+/* Kernel Live Update Test ABI */
+#ifdef CONFIG_LIVEUPDATE_TEST
+#define LIVEUPDATE_TEST_FLB_COMPATIBLE(i) "liveupdate-test-flb-v" #i
+#endif
+
+#endif /* _LINUX_KHO_ABI_LUO_H */
diff --git a/include/linux/kho/abi/memfd.h a/include/linux/kho/abi/memfd.h
new file mode 100644
--- /dev/null
+++ a/include/linux/kho/abi/memfd.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (c) 2025, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ *
+ * Copyright (C) 2025 Amazon.com Inc. or its affiliates.
+ * Pratyush Yadav <ptyadav@amazon.de>
+ */
+
+#ifndef _LINUX_KHO_ABI_MEMFD_H
+#define _LINUX_KHO_ABI_MEMFD_H
+
+#include <linux/types.h>
+#include <linux/kexec_handover.h>
+
+/**
+ * DOC: memfd Live Update ABI
+ *
+ * This header defines the ABI for preserving the state of a memfd across a
+ * kexec reboot using the LUO.
+ *
+ * The state is serialized into a packed structure `struct memfd_luo_ser`
+ * which is handed over to the next kernel via the KHO mechanism.
+ *
+ * This interface is a contract. Any modification to the structure layout
+ * constitutes a breaking change. Such changes require incrementing the
+ * version number in the MEMFD_LUO_FH_COMPATIBLE string.
+ */
+
+/**
+ * MEMFD_LUO_FOLIO_DIRTY - The folio is dirty.
+ *
+ * This flag indicates the folio contains data from user. A non-dirty folio is
+ * one that was allocated (say using fallocate(2)) but not written to.
+ */
+#define MEMFD_LUO_FOLIO_DIRTY BIT(0)
+
+/**
+ * MEMFD_LUO_FOLIO_UPTODATE - The folio is up-to-date.
+ *
+ * An up-to-date folio has been zeroed out. shmem zeroes out folios on first
+ * use. This flag tracks which folios need zeroing.
+ */
+#define MEMFD_LUO_FOLIO_UPTODATE BIT(1)
+
+/**
+ * struct memfd_luo_folio_ser - Serialized state of a single folio.
+ * @pfn: The page frame number of the folio.
+ * @flags: Flags to describe the state of the folio.
+ * @index: The page offset (pgoff_t) of the folio within the original file.
+ */
+struct memfd_luo_folio_ser {
+ u64 pfn:52;
+ u64 flags:12;
+ u64 index;
+} __packed;
+
+/**
+ * struct memfd_luo_ser - Main serialization structure for a memfd.
+ * @pos: The file's current position (f_pos).
+ * @size: The total size of the file in bytes (i_size).
+ * @nr_folios: Number of folios in the folios array.
+ * @folios: KHO vmalloc descriptor pointing to the array of
+ * struct memfd_luo_folio_ser.
+ */
+struct memfd_luo_ser {
+ u64 pos;
+ u64 size;
+ u64 nr_folios;
+ struct kho_vmalloc folios;
+} __packed;
+
+/* The compatibility string for memfd file handler */
+#define MEMFD_LUO_FH_COMPATIBLE "memfd-v1"
+
+#endif /* _LINUX_KHO_ABI_MEMFD_H */
diff --git a/include/linux/liveupdate/abi/luo.h a/include/linux/liveupdate/abi/luo.h
deleted file mode 100644
--- a/include/linux/liveupdate/abi/luo.h
+++ /dev/null
@@ -1,238 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-
-/*
- * Copyright (c) 2025, Google LLC.
- * Pasha Tatashin <pasha.tatashin@soleen.com>
- */
-
-/**
- * DOC: Live Update Orchestrator ABI
- *
- * This header defines the stable Application Binary Interface used by the
- * Live Update Orchestrator to pass state from a pre-update kernel to a
- * post-update kernel. The ABI is built upon the Kexec HandOver framework
- * and uses a Flattened Device Tree to describe the preserved data.
- *
- * This interface is a contract. Any modification to the FDT structure, node
- * properties, compatible strings, or the layout of the `__packed` serialization
- * structures defined here constitutes a breaking change. Such changes require
- * incrementing the version number in the relevant `_COMPATIBLE` string to
- * prevent a new kernel from misinterpreting data from an old kernel.
- *
- * FDT Structure Overview:
- * The entire LUO state is encapsulated within a single KHO entry named "LUO".
- * This entry contains an FDT with the following layout:
- *
- * .. code-block:: none
- *
- * / {
- * compatible = "luo-v1";
- * liveupdate-number = <...>;
- *
- * luo-session {
- * compatible = "luo-session-v1";
- * luo-session-header = <phys_addr_of_session_header_ser>;
- * };
- *
- * luo-flb {
- * compatible = "luo-flb-v1";
- * luo-flb-header = <phys_addr_of_flb_header_ser>;
- * };
- * };
- *
- * Main LUO Node (/):
- *
- * - compatible: "luo-v1"
- * Identifies the overall LUO ABI version.
- * - liveupdate-number: u64
- * A counter tracking the number of successful live updates performed.
- *
- * Session Node (luo-session):
- * This node describes all preserved user-space sessions.
- *
- * - compatible: "luo-session-v1"
- * Identifies the session ABI version.
- * - luo-session-header: u64
- * The physical address of a `struct luo_session_header_ser`. This structure
- * is the header for a contiguous block of memory containing an array of
- * `struct luo_session_ser`, one for each preserved session.
- *
- * File-Lifecycle-Bound Node (luo-flb):
- * This node describes all preserved global objects whose lifecycle is bound
- * to that of the preserved files (e.g., shared IOMMU state).
- *
- * - compatible: "luo-flb-v1"
- * Identifies the FLB ABI version.
- * - luo-flb-header: u64
- * The physical address of a `struct luo_flb_header_ser`. This structure is
- * the header for a contiguous block of memory containing an array of
- * `struct luo_flb_ser`, one for each preserved global object.
- *
- * Serialization Structures:
- * The FDT properties point to memory regions containing arrays of simple,
- * `__packed` structures. These structures contain the actual preserved state.
- *
- * - struct luo_session_header_ser:
- * Header for the session array. Contains the total page count of the
- * preserved memory block and the number of `struct luo_session_ser`
- * entries that follow.
- *
- * - struct luo_session_ser:
- * Metadata for a single session, including its name and a physical pointer
- * to another preserved memory block containing an array of
- * `struct luo_file_ser` for all files in that session.
- *
- * - struct luo_file_ser:
- * Metadata for a single preserved file. Contains the `compatible` string to
- * find the correct handler in the new kernel, a user-provided `token` for
- * identification, and an opaque `data` handle for the handler to use.
- *
- * - struct luo_flb_header_ser:
- * Header for the FLB array. Contains the total page count of the
- * preserved memory block and the number of `struct luo_flb_ser` entries
- * that follow.
- *
- * - struct luo_flb_ser:
- * Metadata for a single preserved global object. Contains its `name`
- * (compatible string), an opaque `data` handle, and the `count`
- * number of files depending on it.
- */
-
-#ifndef _LINUX_LIVEUPDATE_ABI_LUO_H
-#define _LINUX_LIVEUPDATE_ABI_LUO_H
-
-#include <uapi/linux/liveupdate.h>
-
-/*
- * The LUO FDT hooks all LUO state for sessions, fds, etc.
- * In the root it also carries "liveupdate-number" 64-bit property that
- * corresponds to the number of live-updates performed on this machine.
- */
-#define LUO_FDT_SIZE PAGE_SIZE
-#define LUO_FDT_KHO_ENTRY_NAME "LUO"
-#define LUO_FDT_COMPATIBLE "luo-v1"
-#define LUO_FDT_LIVEUPDATE_NUM "liveupdate-number"
-
-/*
- * LUO FDT session node
- * LUO_FDT_SESSION_HEADER: is a u64 physical address of struct
- * luo_session_header_ser
- */
-#define LUO_FDT_SESSION_NODE_NAME "luo-session"
-#define LUO_FDT_SESSION_COMPATIBLE "luo-session-v1"
-#define LUO_FDT_SESSION_HEADER "luo-session-header"
-
-/**
- * struct luo_session_header_ser - Header for the serialized session data block.
- * @pgcnt: The total size, in pages, of the entire preserved memory block
- * that this header describes.
- * @count: The number of 'struct luo_session_ser' entries that immediately
- * follow this header in the memory block.
- *
- * This structure is located at the beginning of a contiguous block of
- * physical memory preserved across the kexec. It provides the necessary
- * metadata to interpret the array of session entries that follow.
- */
-struct luo_session_header_ser {
- u64 pgcnt;
- u64 count;
-} __packed;
-
-/**
- * struct luo_session_ser - Represents the serialized metadata for a LUO session.
- * @name: The unique name of the session, copied from the `luo_session`
- * structure.
- * @files: The physical address of a contiguous memory block that holds
- * the serialized state of files.
- * @pgcnt: The number of pages occupied by the `files` memory block.
- * @count: The total number of files that were part of this session during
- * serialization. Used for iteration and validation during
- * restoration.
- *
- * This structure is used to package session-specific metadata for transfer
- * between kernels via Kexec Handover. An array of these structures (one per
- * session) is created and passed to the new kernel, allowing it to reconstruct
- * the session context.
- *
- * If this structure is modified, LUO_SESSION_COMPATIBLE must be updated.
- */
-struct luo_session_ser {
- char name[LIVEUPDATE_SESSION_NAME_LENGTH];
- u64 files;
- u64 pgcnt;
- u64 count;
-} __packed;
-
-/* The max size is set so it can be reliably used during in serialization */
-#define LIVEUPDATE_HNDL_COMPAT_LENGTH 48
-
-/**
- * struct luo_file_ser - Represents the serialized preserves files.
- * @compatible: File handler compatible string.
- * @data: Private data
- * @token: User provided token for this file
- *
- * If this structure is modified, LUO_SESSION_COMPATIBLE must be updated.
- */
-struct luo_file_ser {
- char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
- u64 data;
- u64 token;
-} __packed;
-
-/* The max size is set so it can be reliably used during in serialization */
-#define LIVEUPDATE_FLB_COMPAT_LENGTH 48
-
-#define LUO_FDT_FLB_NODE_NAME "luo-flb"
-#define LUO_FDT_FLB_COMPATIBLE "luo-flb-v1"
-#define LUO_FDT_FLB_HEADER "luo-flb-header"
-
-/**
- * struct luo_flb_header_ser - Header for the serialized FLB data block.
- * @pgcnt: The total number of pages occupied by the entire preserved memory
- * region, including this header and the subsequent array of
- * &struct luo_flb_ser entries.
- * @count: The number of &struct luo_flb_ser entries that follow this header
- * in the memory block.
- *
- * This structure is located at the physical address specified by the
- * `LUO_FDT_FLB_HEADER` FDT property. It provides the new kernel with the
- * necessary information to find and iterate over the array of preserved
- * File-Lifecycle-Bound objects and to manage the underlying memory.
- *
- * If this structure is modified, LUO_FDT_FLB_COMPATIBLE must be updated.
- */
-struct luo_flb_header_ser {
- u64 pgcnt;
- u64 count;
-} __packed;
-
-/**
- * struct luo_flb_ser - Represents the serialized state of a single FLB object.
- * @name: The unique compatibility string of the FLB object, used to find the
- * corresponding &struct liveupdate_flb handler in the new kernel.
- * @data: The opaque u64 handle returned by the FLB's .preserve() operation
- * in the old kernel. This handle encapsulates the entire state needed
- * for restoration.
- * @count: The reference count at the time of serialization; i.e., the number
- * of preserved files that depended on this FLB. This is used by the
- * new kernel to correctly manage the FLB's lifecycle.
- *
- * An array of these structures is created in a preserved memory region and
- * passed to the new kernel. Each entry allows the LUO core to restore one
- * global, shared object.
- *
- * If this structure is modified, LUO_FDT_FLB_COMPATIBLE must be updated.
- */
-struct luo_flb_ser {
- char name[LIVEUPDATE_FLB_COMPAT_LENGTH];
- u64 data;
- u64 count;
-} __packed;
-
-/* Kernel Live Update Test ABI */
-#ifdef CONFIG_LIVEUPDATE_TEST
-#define LIVEUPDATE_TEST_FLB_COMPATIBLE(i) "liveupdate-test-flb-v" #i
-#endif
-
-#endif /* _LINUX_LIVEUPDATE_ABI_LUO_H */
diff --git a/include/linux/liveupdate/abi/memfd.h a/include/linux/liveupdate/abi/memfd.h
deleted file mode 100644
--- a/include/linux/liveupdate/abi/memfd.h
+++ /dev/null
@@ -1,88 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-
-/*
- * Copyright (c) 2025, Google LLC.
- * Pasha Tatashin <pasha.tatashin@soleen.com>
- *
- * Copyright (C) 2025 Amazon.com Inc. or its affiliates.
- * Pratyush Yadav <ptyadav@amazon.de>
- */
-
-#ifndef _LINUX_LIVEUPDATE_ABI_MEMFD_H
-#define _LINUX_LIVEUPDATE_ABI_MEMFD_H
-
-/**
- * DOC: memfd Live Update ABI
- *
- * This header defines the ABI for preserving the state of a memfd across a
- * kexec reboot using the LUO.
- *
- * The state is serialized into a Flattened Device Tree which is then handed
- * over to the next kernel via the KHO mechanism. The FDT is passed as the
- * opaque `data` handle in the file handler callbacks.
- *
- * This interface is a contract. Any modification to the FDT structure,
- * node properties, compatible string, or the layout of the serialization
- * structures defined here constitutes a breaking change. Such changes require
- * incrementing the version number in the MEMFD_LUO_FH_COMPATIBLE string.
- *
- * FDT Structure Overview:
- * The memfd state is contained within a single FDT with the following layout:
- *
- * .. code-block:: none
- *
- * / {
- * pos = <...>;
- * size = <...>;
- * nr_folios = <...>;
- * folios = < ... binary data ... >;
- * };
- *
- * Node Properties:
- * - pos: u64
- * The file's current position (f_pos).
- * - size: u64
- * The total size of the file in bytes (i_size).
- * - nr_folios: u64
- * Number of folios in folios array. Only present when size > 0.
- * - folios: struct kho_vmalloc
- * KHO vmalloc preservation for an array of &struct memfd_luo_folio_ser,
- * one for each preserved folio from the original file's mapping. Only
- * present when size > 0.
- */
-
-/**
- * struct memfd_luo_folio_ser - Serialized state of a single folio.
- * @foliodesc: A packed 64-bit value containing both the PFN and status flags of
- * the preserved folio. The upper 52 bits store the PFN, and the
- * lower 12 bits are reserved for flags (e.g., dirty, uptodate).
- * @index: The page offset (pgoff_t) of the folio within the original file's
- * address space. This is used to correctly position the folio
- * during restoration.
- *
- * This structure represents the minimal information required to restore a
- * single folio in the new kernel. An array of these structs forms the binary
- * data for the "folios" property in the handover FDT.
- */
-struct memfd_luo_folio_ser {
- u64 foliodesc;
- u64 index;
-};
-
-/* The strings used for memfd KHO FDT sub-tree. */
-
-/* 64-bit pos value for the preserved memfd */
-#define MEMFD_FDT_POS "pos"
-
-/* 64-bit size value of the preserved memfd */
-#define MEMFD_FDT_SIZE "size"
-
-#define MEMFD_FDT_FOLIOS "folios"
-
-/* Number of folios in the folios array. */
-#define MEMFD_FDT_NR_FOLIOS "nr_folios"
-
-/* The compatibility string for memfd file handler */
-#define MEMFD_LUO_FH_COMPATIBLE "memfd-v1"
-
-#endif /* _LINUX_LIVEUPDATE_ABI_MEMFD_H */
--- a/include/linux/liveupdate.h~b
+++ a/include/linux/liveupdate.h
@@ -8,9 +8,11 @@
#define _LINUX_LIVEUPDATE_H
#include <linux/bug.h>
-#include <linux/types.h>
+#include <linux/compiler.h>
+#include <linux/kho/abi/luo.h>
#include <linux/list.h>
-#include <linux/liveupdate/abi/luo.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
#include <uapi/linux/liveupdate.h>
struct liveupdate_file_handler;
@@ -85,9 +87,6 @@ struct liveupdate_file_ops {
* that uniquely identifies the file type this handler
* supports. This is matched against the compatible string
* associated with individual &struct file instances.
- * @list: Used for linking this handler instance into a global
- * list of registered file handlers.
- * @flb_list: A list of FLB dependencies.
*
* Modules that want to support live update for specific file types should
* register an instance of this structure. LUO uses this registration to
@@ -97,8 +96,16 @@ struct liveupdate_file_ops {
struct liveupdate_file_handler {
const struct liveupdate_file_ops *ops;
const char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
- struct list_head list;
- struct list_head flb_list;
+
+ /* private: */
+
+ /*
+ * Used for linking this handler instance into a global list of
+ * registered file handlers.
+ */
+ struct list_head __private list;
+ /* A list of FLB dependencies. */
+ struct list_head __private flb_list;
};
/**
@@ -150,6 +157,42 @@ struct liveupdate_flb_ops {
struct module *owner;
};
+/*
+ * struct luo_flb_private_state - Private FLB state structures.
+ * @count: The number of preserved files currently depending on this FLB.
+ * This is used to trigger the preserve/unpreserve/finish ops on the
+ * first/last file.
+ * @data: The opaque u64 handle returned by .preserve() or passed to
+ * .retrieve().
+ * @obj: The live kernel object returned by .preserve() or .retrieve().
+ * @lock: A mutex that protects all fields within this structure, providing
+ * the synchronization service for the FLB's ops.
+ */
+struct luo_flb_private_state {
+ long count;
+ u64 data;
+ void *obj;
+ struct mutex lock;
+};
+
+/*
+ * struct luo_flb_private - Keep separate incoming and outgoing states.
+ * @list: A global list of registered FLBs.
+ * @outgoing: The runtime state for the pre-reboot
+ * (preserve/unpreserve) lifecycle.
+ * @incoming: The runtime state for the post-reboot (retrieve/finish)
+ * lifecycle.
+ * @users: With how many File-Handlers this FLB is registered.
+ * @initialized: true when private fields have been initialized.
+ */
+struct luo_flb_private {
+ struct list_head list;
+ struct luo_flb_private_state outgoing;
+ struct luo_flb_private_state incoming;
+ int users;
+ bool initialized;
+};
+
/**
* struct liveupdate_flb - A global definition for a shared data object.
* @ops: Callback functions
@@ -158,20 +201,17 @@ struct liveupdate_flb_ops {
* supports. This is matched against the compatible string
* associated with individual &struct liveupdate_flb
* instances.
- * @list: A global list of registered FLBs.
- * @internal: Internal state, set in liveupdate_init_flb().
*
* This struct is the "template" that a driver registers to define a shared,
* file-lifecycle-bound object. The actual runtime state (the live object,
- * refcount, etc.) is managed internally by the LUO core.
- * Use liveupdate_init_flb() to initialize this struct before using it in
- * other functions.
+ * refcount, etc.) is managed privately by the LUO core.
*/
struct liveupdate_flb {
const struct liveupdate_flb_ops *ops;
const char compatible[LIVEUPDATE_FLB_COMPAT_LENGTH];
- struct list_head list;
- void *internal;
+
+ /* private: */
+ struct luo_flb_private __private private;
};
#ifdef CONFIG_LIVEUPDATE
@@ -182,7 +222,8 @@ bool liveupdate_enabled(void);
/* Called during kexec to tell LUO that entered into reboot */
int liveupdate_reboot(void);
-int liveupdate_register_file_handler(struct liveupdate_file_handler *h);
+int liveupdate_register_file_handler(struct liveupdate_file_handler *fh);
+int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh);
/* kernel can internally retrieve files */
int liveupdate_get_file_incoming(struct liveupdate_session *s, u64 token,
@@ -192,11 +233,10 @@ int liveupdate_get_file_incoming(struct
int liveupdate_get_token_outgoing(struct liveupdate_session *s,
struct file *file, u64 *tokenp);
-/* Before using FLB for the first time it should be initialized */
-int liveupdate_init_flb(struct liveupdate_flb *flb);
-
-int liveupdate_register_flb(struct liveupdate_file_handler *h,
+int liveupdate_register_flb(struct liveupdate_file_handler *fh,
struct liveupdate_flb *flb);
+int liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
+ struct liveupdate_flb *flb);
int liveupdate_flb_incoming_locked(struct liveupdate_flb *flb, void **objp);
void liveupdate_flb_incoming_unlock(struct liveupdate_flb *flb, void *obj);
@@ -215,7 +255,12 @@ static inline int liveupdate_reboot(void
return 0;
}
-static inline int liveupdate_register_file_handler(struct liveupdate_file_handler *h)
+static inline int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
{
return -EOPNOTSUPP;
}
@@ -232,13 +277,14 @@ static inline int liveupdate_get_token_o
return -EOPNOTSUPP;
}
-static inline int liveupdate_init_flb(struct liveupdate_flb *flb)
+static inline int liveupdate_register_flb(struct liveupdate_file_handler *fh,
+ struct liveupdate_flb *flb)
{
return -EOPNOTSUPP;
}
-static inline int liveupdate_register_flb(struct liveupdate_file_handler *h,
- struct liveupdate_flb *flb)
+static inline int liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
+ struct liveupdate_flb *flb)
{
return -EOPNOTSUPP;
}
--- a/include/linux/shmem_fs.h~b
+++ a/include/linux/shmem_fs.h
@@ -201,7 +201,7 @@ static inline bool shmem_file(struct fil
}
/* Must be called with inode lock taken exclusive. */
-static inline void shmem_i_mapping_freeze(struct inode *inode, bool freeze)
+static inline void shmem_freeze(struct inode *inode, bool freeze)
{
if (freeze)
SHMEM_I(inode)->flags |= SHMEM_F_MAPPING_FROZEN;
--- a/include/uapi/linux/liveupdate.h~b
+++ a/include/uapi/linux/liveupdate.h
@@ -67,7 +67,7 @@ enum {
* @fd: Output; The new file descriptor for the created session.
* @name: Input; A null-terminated string for the session name, max
* length %LIVEUPDATE_SESSION_NAME_LENGTH including termination
- * char.
+ * character.
*
* Creates a new live update session for managing preserved resources.
* This ioctl can only be called on the main /dev/liveupdate device.
@@ -155,7 +155,7 @@ struct liveupdate_session_preserve_fd {
/**
* struct liveupdate_session_retrieve_fd - ioctl(LIVEUPDATE_SESSION_RETRIEVE_FD)
- * @size: Input; sizeof(struct liveupdate_session_RETRIEVE_fd)
+ * @size: Input; sizeof(struct liveupdate_session_retrieve_fd)
* @fd: Output; The new file descriptor representing the fully restored
* kernel resource.
* @token: Input; An opaque, token that was used to preserve the resource.
--- a/kernel/liveupdate/luo_core.c~b
+++ a/kernel/liveupdate/luo_core.c
@@ -16,9 +16,8 @@
*
* While the primary use case driving this work is supporting live updates of
* the Linux kernel when it is used as a hypervisor in cloud environments, the
- * LUO framework itself is designed to be workload-agnostic. Much like Kernel
- * Live Patching, which applies security fixes regardless of the workload,
- * Live Update facilitates a full kernel version upgrade for any type of system.
+ * LUO framework itself is designed to be workload-agnostic. Live Update
+ * facilitates a full kernel version upgrade for any type of system.
*
* For example, a non-hypervisor system running an in-memory cache like
* memcached with many gigabytes of data can use LUO. The userspace service
@@ -42,17 +41,23 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/atomic.h>
+#include <linux/errno.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/init.h>
#include <linux/io.h>
+#include <linux/kernel.h>
#include <linux/kexec_handover.h>
+#include <linux/kho/abi/luo.h>
#include <linux/kobject.h>
#include <linux/libfdt.h>
#include <linux/liveupdate.h>
-#include <linux/liveupdate/abi/luo.h>
+#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/sizes.h>
#include <linux/string.h>
#include <linux/unaligned.h>
-
#include "kexec_handover_internal.h"
#include "luo_internal.h"
@@ -133,9 +138,9 @@ static int __init liveupdate_early_init(
err = luo_early_startup();
if (err) {
- pr_err("The incoming tree failed to initialize properly [%pe], disabling live update\n",
- ERR_PTR(err));
luo_global.enabled = false;
+ luo_restore_fail("The incoming tree failed to initialize properly [%pe], disabling live update\n",
+ ERR_PTR(err));
}
return err;
@@ -250,3 +255,200 @@ bool liveupdate_enabled(void)
{
return luo_global.enabled;
}
+
+/**
+ * DOC: LUO ioctl Interface
+ *
+ * The IOCTL user-space control interface for the LUO subsystem.
+ * It registers a character device, typically found at ``/dev/liveupdate``,
+ * which allows a userspace agent to manage the LUO state machine and its
+ * associated resources, such as preservable file descriptors.
+ *
+ * To ensure that the state machine is controlled by a single entity, access
+ * to this device is exclusive: only one process is permitted to have
+ * ``/dev/liveupdate`` open at any given time. Subsequent open attempts will
+ * fail with -EBUSY until the first process closes its file descriptor.
+ * This singleton model simplifies state management by preventing conflicting
+ * commands from multiple userspace agents.
+ */
+
+struct luo_device_state {
+ struct miscdevice miscdev;
+ atomic_t in_use;
+};
+
+static int luo_ioctl_create_session(struct luo_ucmd *ucmd)
+{
+ struct liveupdate_ioctl_create_session *argp = ucmd->cmd;
+ struct file *file;
+ int err;
+
+ argp->fd = get_unused_fd_flags(O_CLOEXEC);
+ if (argp->fd < 0)
+ return argp->fd;
+
+ err = luo_session_create(argp->name, &file);
+ if (err)
+ goto err_put_fd;
+
+ err = luo_ucmd_respond(ucmd, sizeof(*argp));
+ if (err)
+ goto err_put_file;
+
+ fd_install(argp->fd, file);
+
+ return 0;
+
+err_put_file:
+ fput(file);
+err_put_fd:
+ put_unused_fd(argp->fd);
+
+ return err;
+}
+
+static int luo_ioctl_retrieve_session(struct luo_ucmd *ucmd)
+{
+ struct liveupdate_ioctl_retrieve_session *argp = ucmd->cmd;
+ struct file *file;
+ int err;
+
+ argp->fd = get_unused_fd_flags(O_CLOEXEC);
+ if (argp->fd < 0)
+ return argp->fd;
+
+ err = luo_session_retrieve(argp->name, &file);
+ if (err < 0)
+ goto err_put_fd;
+
+ err = luo_ucmd_respond(ucmd, sizeof(*argp));
+ if (err)
+ goto err_put_file;
+
+ fd_install(argp->fd, file);
+
+ return 0;
+
+err_put_file:
+ fput(file);
+err_put_fd:
+ put_unused_fd(argp->fd);
+
+ return err;
+}
+
+static int luo_open(struct inode *inodep, struct file *filep)
+{
+ struct luo_device_state *ldev = container_of(filep->private_data,
+ struct luo_device_state,
+ miscdev);
+
+ if (atomic_cmpxchg(&ldev->in_use, 0, 1))
+ return -EBUSY;
+
+ /* Always return -EIO to user if deserialization fail */
+ if (luo_session_deserialize()) {
+ atomic_set(&ldev->in_use, 0);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int luo_release(struct inode *inodep, struct file *filep)
+{
+ struct luo_device_state *ldev = container_of(filep->private_data,
+ struct luo_device_state,
+ miscdev);
+ atomic_set(&ldev->in_use, 0);
+
+ return 0;
+}
+
+union ucmd_buffer {
+ struct liveupdate_ioctl_create_session create;
+ struct liveupdate_ioctl_retrieve_session retrieve;
+};
+
+struct luo_ioctl_op {
+ unsigned int size;
+ unsigned int min_size;
+ unsigned int ioctl_num;
+ int (*execute)(struct luo_ucmd *ucmd);
+};
+
+#define IOCTL_OP(_ioctl, _fn, _struct, _last) \
+ [_IOC_NR(_ioctl) - LIVEUPDATE_CMD_BASE] = { \
+ .size = sizeof(_struct) + \
+ BUILD_BUG_ON_ZERO(sizeof(union ucmd_buffer) < \
+ sizeof(_struct)), \
+ .min_size = offsetofend(_struct, _last), \
+ .ioctl_num = _ioctl, \
+ .execute = _fn, \
+ }
+
+static const struct luo_ioctl_op luo_ioctl_ops[] = {
+ IOCTL_OP(LIVEUPDATE_IOCTL_CREATE_SESSION, luo_ioctl_create_session,
+ struct liveupdate_ioctl_create_session, name),
+ IOCTL_OP(LIVEUPDATE_IOCTL_RETRIEVE_SESSION, luo_ioctl_retrieve_session,
+ struct liveupdate_ioctl_retrieve_session, name),
+};
+
+static long luo_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
+{
+ const struct luo_ioctl_op *op;
+ struct luo_ucmd ucmd = {};
+ union ucmd_buffer buf;
+ unsigned int nr;
+ int err;
+
+ nr = _IOC_NR(cmd);
+ if (nr < LIVEUPDATE_CMD_BASE ||
+ (nr - LIVEUPDATE_CMD_BASE) >= ARRAY_SIZE(luo_ioctl_ops)) {
+ return -EINVAL;
+ }
+
+ ucmd.ubuffer = (void __user *)arg;
+ err = get_user(ucmd.user_size, (u32 __user *)ucmd.ubuffer);
+ if (err)
+ return err;
+
+ op = &luo_ioctl_ops[nr - LIVEUPDATE_CMD_BASE];
+ if (op->ioctl_num != cmd)
+ return -ENOIOCTLCMD;
+ if (ucmd.user_size < op->min_size)
+ return -EINVAL;
+
+ ucmd.cmd = &buf;
+ err = copy_struct_from_user(ucmd.cmd, op->size, ucmd.ubuffer,
+ ucmd.user_size);
+ if (err)
+ return err;
+
+ return op->execute(&ucmd);
+}
+
+static const struct file_operations luo_fops = {
+ .owner = THIS_MODULE,
+ .open = luo_open,
+ .release = luo_release,
+ .unlocked_ioctl = luo_ioctl,
+};
+
+static struct luo_device_state luo_dev = {
+ .miscdev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "liveupdate",
+ .fops = &luo_fops,
+ },
+ .in_use = ATOMIC_INIT(0),
+};
+
+static int __init liveupdate_ioctl_init(void)
+{
+ if (!liveupdate_enabled())
+ return 0;
+
+ return misc_register(&luo_dev.miscdev);
+}
+late_initcall(liveupdate_ioctl_init);
--- a/kernel/liveupdate/luo_file.c~b
+++ a/kernel/liveupdate/luo_file.c
@@ -25,9 +25,9 @@
* - can_preserve(): A lightweight check to determine if the handler is
* compatible with a given 'struct file'.
* - preserve(): The heavyweight operation that saves the file's state and
- * returns an opaque u64 handle, happens while vcpus are still running.
- * LUO becomes the owner of this file until session is closed or file is
- * finished.
+ * returns an opaque u64 handle. This is typically performed while the
+ * workload is still active to minimize the downtime during the
+ * actual reboot transition.
* - unpreserve(): Cleans up any resources allocated by .preserve(), called
* if the preservation process is aborted before the reboot (i.e. session is
* closed).
@@ -45,18 +45,19 @@
*
* 1. Preserve (Normal Operation): A userspace agent preserves files one by one
* via an ioctl. For each file, luo_preserve_file() finds a compatible
- * handler, calls its .preserve() op, and creates an internal &struct
+ * handler, calls its .preserve() operation, and creates an internal &struct
* luo_file to track the live state.
*
* 2. Freeze (Pre-Reboot): Just before the kexec, luo_file_freeze() is called.
* It iterates through all preserved files, calls their respective .freeze()
- * ops, and serializes their final metadata (compatible string, token, and
- * data handle) into a contiguous memory block for KHO.
+ * operation, and serializes their final metadata (compatible string, token,
+ * and data handle) into a contiguous memory block for KHO.
*
- * 3. Deserialize (New Kernel - Early Boot): After kexec, luo_file_deserialize()
- * runs. It reads the serialized data from the KHO memory region and
- * reconstructs the in-memory list of &struct luo_file instances for the new
- * kernel, linking them to their corresponding handlers.
+ * 3. Deserialize: After kexec, luo_file_deserialize() runs when session gets
+ * deserialized (which is when /dev/liveupdate is first opened). It reads the
+ * serialized data from the KHO memory region and reconstructs the in-memory
+ * list of &struct luo_file instances for the new kernel, linking them to
+ * their corresponding handlers.
*
* 4. Retrieve (New Kernel - Userspace Ready): The userspace agent can now
* restore file descriptors by providing a token. luo_retrieve_file()
@@ -65,9 +66,9 @@
* retrieved in ANY order.
*
* 5. Finish (New Kernel - Cleanup): Once a session retrival is complete,
- * luo_file_finish() is called. It iterates through all files,
- * invokes their .finish() ops for final cleanup, and releases all
- * associated kernel resources.
+ * luo_file_finish() is called. It iterates through all files, invokes their
+ * .finish() operations for final cleanup, and releases all associated kernel
+ * resources.
*
* File Preservation Lifecycle unhappy paths:
*
@@ -95,13 +96,15 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/cleanup.h>
+#include <linux/compiler.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/file.h>
#include <linux/fs.h>
+#include <linux/io.h>
#include <linux/kexec_handover.h>
+#include <linux/kho/abi/luo.h>
#include <linux/liveupdate.h>
-#include <linux/liveupdate/abi/luo.h>
#include <linux/module.h>
#include <linux/sizes.h>
#include <linux/slab.h>
@@ -110,7 +113,7 @@
static LIST_HEAD(luo_file_handler_list);
-/* 2 4K pages, give space for 128 files per session */
+/* 2 4K pages, give space for 128 files per file_set */
#define LUO_FILE_PGCNT 2ul
#define LUO_FILE_MAX \
((LUO_FILE_PGCNT << PAGE_SHIFT) / sizeof(struct luo_file_ser))
@@ -137,7 +140,7 @@ static LIST_HEAD(luo_file_handler_list);
* (e.g., @retrieved, @file), ensuring that operations like
* retrieving or finishing a file are atomic.
* @list: The list_head linking this instance into its parent
- * session's list of preserved files.
+ * file_set's list of preserved files.
* @token: The user-provided unique token used to identify this file.
*
* This structure is the core in-kernel representation of a single file being
@@ -146,7 +149,7 @@ static LIST_HEAD(luo_file_handler_list);
* and the serialized state handle returned by the handler's .preserve()
* operation.
*
- * These instances are tracked in a per-session list. The @serialized_data
+ * These instances are tracked in a per-file_set list. The @serialized_data
* field, which holds a handle to the file's serialized state, may be updated
* during the .freeze() callback before being serialized for the next kernel.
* After reboot, these structures are recreated by luo_file_deserialize() and
@@ -163,46 +166,44 @@ struct luo_file {
u64 token;
};
-static int luo_session_alloc_files_mem(struct luo_session *session)
+static int luo_alloc_files_mem(struct luo_file_set *file_set)
{
size_t size;
void *mem;
- if (session->files)
+ if (file_set->files)
return 0;
- WARN_ON_ONCE(session->count);
+ WARN_ON_ONCE(file_set->count);
size = LUO_FILE_PGCNT << PAGE_SHIFT;
mem = kho_alloc_preserve(size);
if (IS_ERR(mem))
return PTR_ERR(mem);
- session->files = mem;
- session->pgcnt = LUO_FILE_PGCNT;
+ file_set->files = mem;
return 0;
}
-static void luo_session_free_files_mem(struct luo_session *session)
+static void luo_free_files_mem(struct luo_file_set *file_set)
{
- /* If session has files, no need to free preservation memory */
- if (session->count)
+ /* If file_set has files, no need to free preservation memory */
+ if (file_set->count)
return;
- if (!session->files)
+ if (!file_set->files)
return;
- kho_unpreserve_free(session->files);
- session->files = NULL;
- session->pgcnt = 0;
+ kho_unpreserve_free(file_set->files);
+ file_set->files = NULL;
}
-static bool luo_token_is_used(struct luo_session *session, u64 token)
+static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
{
struct luo_file *iter;
- list_for_each_entry(iter, &session->files_list, list) {
+ list_for_each_entry(iter, &file_set->files_list, list) {
if (iter->token == token)
return true;
}
@@ -212,9 +213,9 @@ static bool luo_token_is_used(struct luo
/**
* luo_preserve_file - Initiate the preservation of a file descriptor.
- * @session: The session to which the preserved file will be added.
- * @token: A unique, user-provided identifier for the file.
- * @fd: The file descriptor to be preserved.
+ * @file_set: The file_set to which the preserved file will be added.
+ * @token: A unique, user-provided identifier for the file.
+ * @fd: The file descriptor to be preserved.
*
* This function orchestrates the first phase of preserving a file. Upon entry,
* it takes a reference to the 'struct file' via fget(), effectively making LUO
@@ -225,14 +226,14 @@ static bool luo_token_is_used(struct luo
* This function orchestrates the first phase of preserving a file. It performs
* the following steps:
*
- * 1. Validates that the @token is not already in use within the session.
- * 2. Ensures the session's memory for files serialization is allocated
+ * 1. Validates that the @token is not already in use within the file_set.
+ * 2. Ensures the file_set's memory for files serialization is allocated
* (allocates if needed).
* 3. Iterates through registered handlers, calling can_preserve() to find one
* compatible with the given @fd.
* 4. Calls the handler's .preserve() operation, which saves the file's state
* and returns an opaque private data handle.
- * 5. Adds the new instance to the session's internal list.
+ * 5. Adds the new instance to the file_set's internal list.
*
* On success, LUO takes a reference to the 'struct file' and considers it
* under its management until it is unpreserved or finished.
@@ -244,12 +245,12 @@ static bool luo_token_is_used(struct luo
* Return: 0 on success. Returns a negative errno on failure:
* -EEXIST if the token is already used.
* -EBADF if the file descriptor is invalid.
- * -ENOSPC if the session is full.
+ * -ENOSPC if the file_set is full.
* -ENOENT if no compatible handler is found.
* -ENOMEM on memory allocation failure.
* Other erros might be returned by .preserve().
*/
-int luo_preserve_file(struct luo_session *session, u64 token, int fd)
+int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
{
struct liveupdate_file_op_args args = {0};
struct liveupdate_file_handler *fh;
@@ -257,26 +258,24 @@ int luo_preserve_file(struct luo_session
struct file *file;
int err;
- lockdep_assert_held(&session->mutex);
-
- if (luo_token_is_used(session, token))
+ if (luo_token_is_used(file_set, token))
return -EEXIST;
file = fget(fd);
if (!file)
return -EBADF;
- err = luo_session_alloc_files_mem(session);
+ err = luo_alloc_files_mem(file_set);
if (err)
- goto exit_err;
+ goto err_files_mem;
- if (session->count == LUO_FILE_MAX) {
+ if (file_set->count == LUO_FILE_MAX) {
err = -ENOSPC;
- goto exit_err;
+ goto err_files_mem;
}
err = -ENOENT;
- list_for_each_entry(fh, &luo_file_handler_list, list) {
+ luo_list_for_each_private(fh, &luo_file_handler_list, list) {
if (fh->ops->can_preserve(fh, file)) {
err = 0;
break;
@@ -285,16 +284,16 @@ int luo_preserve_file(struct luo_session
/* err is still -ENOENT if no handler was found */
if (err)
- goto exit_err;
+ goto err_files_mem;
err = luo_flb_file_preserve(fh);
if (err)
- goto exit_err;
+ goto err_files_mem;
luo_file = kzalloc(sizeof(*luo_file), GFP_KERNEL);
if (!luo_file) {
err = -ENOMEM;
- goto exit_err;
+ goto err_files_mem;
}
luo_file->file = file;
@@ -304,41 +303,41 @@ int luo_preserve_file(struct luo_session
mutex_init(&luo_file->mutex);
args.handler = fh;
- args.session = (struct liveupdate_session *)session;
+ args.session = luo_session_from_file_set(file_set);
args.file = file;
err = fh->ops->preserve(&args);
- if (err) {
- mutex_destroy(&luo_file->mutex);
- kfree(luo_file);
- luo_flb_file_unpreserve(fh);
- goto exit_err;
- } else {
- luo_file->serialized_data = args.serialized_data;
- luo_file->private_data = args.private_data;
- list_add_tail(&luo_file->list, &session->files_list);
- session->count++;
- }
+ if (err)
+ goto err_kfree;
+
+ luo_file->serialized_data = args.serialized_data;
+ luo_file->private_data = args.private_data;
+ list_add_tail(&luo_file->list, &file_set->files_list);
+ file_set->count++;
return 0;
-exit_err:
+err_kfree:
+ mutex_destroy(&luo_file->mutex);
+ kfree(luo_file);
+ luo_flb_file_unpreserve(fh);
+err_files_mem:
fput(file);
- luo_session_free_files_mem(session);
+ luo_free_files_mem(file_set);
return err;
}
/**
- * luo_file_unpreserve_files - Unpreserves all files from a session.
- * @session: The session to be cleaned up.
+ * luo_file_unpreserve_files - Unpreserves all files from a file_set.
+ * @file_set: The files to be cleaned up.
*
- * This function serves as the primary cleanup path for a session. It is
- * invoked when the userspace agent closes the session's file descriptor.
+ * This function serves as the primary cleanup path for a file_set. It is
+ * invoked when the userspace agent closes the file_set's file descriptor.
*
* For each file, it performs the following cleanup actions:
* 1. Calls the handler's .unpreserve() callback to allow the handler to
* release any resources it allocated.
- * 2. Removes the file from the session's internal tracking list.
+ * 2. Removes the file from the file_set's internal tracking list.
* 3. Releases the reference to the 'struct file' that was taken by
* luo_preserve_file() via fput(), returning ownership.
* 4. Frees the memory associated with the internal 'struct luo_file'.
@@ -346,20 +345,18 @@ exit_err:
* After all individual files are unpreserved, it frees the contiguous memory
* block that was allocated to hold their serialization data.
*/
-void luo_file_unpreserve_files(struct luo_session *session)
+void luo_file_unpreserve_files(struct luo_file_set *file_set)
{
struct luo_file *luo_file;
- lockdep_assert_held(&session->mutex);
-
- while (!list_empty(&session->files_list)) {
+ while (!list_empty(&file_set->files_list)) {
struct liveupdate_file_op_args args = {0};
- luo_file = list_last_entry(&session->files_list,
+ luo_file = list_last_entry(&file_set->files_list,
struct luo_file, list);
args.handler = luo_file->fh;
- args.session = (struct liveupdate_session *)session;
+ args.session = luo_session_from_file_set(file_set);
args.file = luo_file->file;
args.serialized_data = luo_file->serialized_data;
args.private_data = luo_file->private_data;
@@ -367,17 +364,17 @@ void luo_file_unpreserve_files(struct lu
luo_flb_file_unpreserve(luo_file->fh);
list_del(&luo_file->list);
- session->count--;
+ file_set->count--;
fput(luo_file->file);
mutex_destroy(&luo_file->mutex);
kfree(luo_file);
}
- luo_session_free_files_mem(session);
+ luo_free_files_mem(file_set);
}
-static int luo_file_freeze_one(struct luo_session *session,
+static int luo_file_freeze_one(struct luo_file_set *file_set,
struct luo_file *luo_file)
{
int err = 0;
@@ -388,7 +385,7 @@ static int luo_file_freeze_one(struct lu
struct liveupdate_file_op_args args = {0};
args.handler = luo_file->fh;
- args.session = (struct liveupdate_session *)session;
+ args.session = luo_session_from_file_set(file_set);
args.file = luo_file->file;
args.serialized_data = luo_file->serialized_data;
args.private_data = luo_file->private_data;
@@ -401,7 +398,7 @@ static int luo_file_freeze_one(struct lu
return err;
}
-static void luo_file_unfreeze_one(struct luo_session *session,
+static void luo_file_unfreeze_one(struct luo_file_set *file_set,
struct luo_file *luo_file)
{
guard(mutex)(&luo_file->mutex);
@@ -410,7 +407,7 @@ static void luo_file_unfreeze_one(struct
struct liveupdate_file_op_args args = {0};
args.handler = luo_file->fh;
- args.session = (struct liveupdate_session *)session;
+ args.session = luo_session_from_file_set(file_set);
args.file = luo_file->file;
args.serialized_data = luo_file->serialized_data;
args.private_data = luo_file->private_data;
@@ -421,29 +418,30 @@ static void luo_file_unfreeze_one(struct
luo_file->serialized_data = 0;
}
-static void __luo_file_unfreeze(struct luo_session *session,
+static void __luo_file_unfreeze(struct luo_file_set *file_set,
struct luo_file *failed_entry)
{
- struct list_head *files_list = &session->files_list;
+ struct list_head *files_list = &file_set->files_list;
struct luo_file *luo_file;
list_for_each_entry(luo_file, files_list, list) {
if (luo_file == failed_entry)
break;
- luo_file_unfreeze_one(session, luo_file);
+ luo_file_unfreeze_one(file_set, luo_file);
}
- memset(session->files, 0, session->pgcnt << PAGE_SHIFT);
+ memset(file_set->files, 0, LUO_FILE_PGCNT << PAGE_SHIFT);
}
/**
* luo_file_freeze - Freezes all preserved files and serializes their metadata.
- * @session: The session whose files are to be frozen.
+ * @file_set: The file_set whose files are to be frozen.
+ * @file_set_ser: Where to put the serialized file_set.
*
* This function is called from the reboot() syscall path, just before the
* kernel transitions to the new image via kexec. Its purpose is to perform the
- * final preparation and serialization of all preserved files in the session.
+ * final preparation and serialization of all preserved files in the file_set.
*
* It iterates through each preserved file in FIFO order (the order of
* preservation) and performs two main actions:
@@ -456,7 +454,7 @@ static void __luo_file_unfreeze(struct l
* 2. Serializes Metadata: After a successful freeze, it copies the final file
* metadata—the handler's compatible string, the user token, and the final
* private data handle—into the pre-allocated contiguous memory buffer
- * (session->files) that will be handed over to the next kernel via KHO.
+ * (file_set->files) that will be handed over to the next kernel via KHO.
*
* Error Handling (Rollback):
* This function is atomic. If any handler's .freeze() operation fails, the
@@ -469,29 +467,28 @@ static void __luo_file_unfreeze(struct l
* Context: Called only from the liveupdate_reboot() path.
* Return: 0 on success, or a negative errno on failure.
*/
-int luo_file_freeze(struct luo_session *session)
+int luo_file_freeze(struct luo_file_set *file_set,
+ struct luo_file_set_ser *file_set_ser)
{
- struct luo_file_ser *file_ser = session->files;
+ struct luo_file_ser *file_ser = file_set->files;
struct luo_file *luo_file;
int err;
int i;
- lockdep_assert_held(&session->mutex);
-
- if (!session->count)
+ if (!file_set->count)
return 0;
if (WARN_ON(!file_ser))
return -EINVAL;
i = 0;
- list_for_each_entry(luo_file, &session->files_list, list) {
- err = luo_file_freeze_one(session, luo_file);
+ list_for_each_entry(luo_file, &file_set->files_list, list) {
+ err = luo_file_freeze_one(file_set, luo_file);
if (err < 0) {
- pr_warn("Freeze failed for session[%s] token[%#0llx] handler[%s] err[%pe]\n",
- session->name, luo_file->token,
- luo_file->fh->compatible, ERR_PTR(err));
- goto exit_err;
+ pr_warn("Freeze failed for token[%#0llx] handler[%s] err[%pe]\n",
+ luo_file->token, luo_file->fh->compatible,
+ ERR_PTR(err));
+ goto err_unfreeze;
}
strscpy(file_ser[i].compatible, luo_file->fh->compatible,
@@ -501,48 +498,53 @@ int luo_file_freeze(struct luo_session *
i++;
}
+ file_set_ser->count = file_set->count;
+ if (file_set->files)
+ file_set_ser->files = virt_to_phys(file_set->files);
+
return 0;
-exit_err:
- __luo_file_unfreeze(session, luo_file);
+err_unfreeze:
+ __luo_file_unfreeze(file_set, luo_file);
return err;
}
/**
- * luo_file_unfreeze - Unfreezes all files in a session.
- * @session: The session whose files are to be unfrozen.
+ * luo_file_unfreeze - Unfreezes all files in a file_set and clear serialization
+ * @file_set: The file_set whose files are to be unfrozen.
+ * @file_set_ser: Serialized file_set.
*
- * This function rolls back the state of all files in a session after the freeze
- * phase has begun but must be aborted. It is the counterpart to
+ * This function rolls back the state of all files in a file_set after the
+ * freeze phase has begun but must be aborted. It is the counterpart to
* luo_file_freeze().
*
* It invokes the __luo_file_unfreeze() helper with a NULL argument, which
- * signals the helper to iterate through all files in the session and call
+ * signals the helper to iterate through all files in the file_set and call
* their respective .unfreeze() handler callbacks.
*
* Context: This is called when the live update is aborted during
* the reboot() syscall, after luo_file_freeze() has been called.
*/
-void luo_file_unfreeze(struct luo_session *session)
+void luo_file_unfreeze(struct luo_file_set *file_set,
+ struct luo_file_set_ser *file_set_ser)
{
- lockdep_assert_held(&session->mutex);
-
- if (!session->count)
+ if (!file_set->count)
return;
- __luo_file_unfreeze(session, NULL);
+ __luo_file_unfreeze(file_set, NULL);
+ memset(file_set_ser, 0, sizeof(*file_set_ser));
}
/**
- * luo_retrieve_file - Restores a preserved file from a session by its token.
- * @session: The session from which to retrieve the file.
- * @token: The unique token identifying the file to be restored.
- * @filep: Output parameter; on success, this is populated with a pointer
- * to the newly retrieved 'struct file'.
+ * luo_retrieve_file - Restores a preserved file from a file_set by its token.
+ * @file_set: The file_set from which to retrieve the file.
+ * @token: The unique token identifying the file to be restored.
+ * @filep: Output parameter; on success, this is populated with a pointer
+ * to the newly retrieved 'struct file'.
*
* This function is the primary mechanism for recreating a file in the new
- * kernel after a live update. It searches the session's list of deserialized
+ * kernel after a live update. It searches the file_set's list of deserialized
* files for an entry matching the provided @token.
*
* The operation is idempotent: if a file has already been successfully
@@ -559,19 +561,17 @@ void luo_file_unfreeze(struct luo_sessio
* -ENOENT if no file with the matching token is found.
* Any error code returned by the handler's .retrieve() op.
*/
-int luo_retrieve_file(struct luo_session *session, u64 token,
+int luo_retrieve_file(struct luo_file_set *file_set, u64 token,
struct file **filep)
{
struct liveupdate_file_op_args args = {0};
struct luo_file *luo_file;
int err;
- lockdep_assert_held(&session->mutex);
-
- if (list_empty(&session->files_list))
+ if (list_empty(&file_set->files_list))
return -ENOENT;
- list_for_each_entry(luo_file, &session->files_list, list) {
+ list_for_each_entry(luo_file, &file_set->files_list, list) {
if (luo_file->token == token)
break;
}
@@ -591,7 +591,7 @@ int luo_retrieve_file(struct luo_session
}
args.handler = luo_file->fh;
- args.session = (struct liveupdate_session *)session;
+ args.session = luo_session_from_file_set(file_set);
args.serialized_data = luo_file->serialized_data;
err = luo_file->fh->ops->retrieve(&args);
if (!err) {
@@ -606,7 +606,7 @@ int luo_retrieve_file(struct luo_session
return err;
}
-static int luo_file_can_finish_one(struct luo_session *session,
+static int luo_file_can_finish_one(struct luo_file_set *file_set,
struct luo_file *luo_file)
{
bool can_finish = true;
@@ -617,7 +617,7 @@ static int luo_file_can_finish_one(struc
struct liveupdate_file_op_args args = {0};
args.handler = luo_file->fh;
- args.session = (struct liveupdate_session *)session;
+ args.session = luo_session_from_file_set(file_set);
args.file = luo_file->file;
args.serialized_data = luo_file->serialized_data;
args.retrieved = luo_file->retrieved;
@@ -627,7 +627,7 @@ static int luo_file_can_finish_one(struc
return can_finish ? 0 : -EBUSY;
}
-static void luo_file_finish_one(struct luo_session *session,
+static void luo_file_finish_one(struct luo_file_set *file_set,
struct luo_file *luo_file)
{
struct liveupdate_file_op_args args = {0};
@@ -635,7 +635,7 @@ static void luo_file_finish_one(struct l
guard(mutex)(&luo_file->mutex);
args.handler = luo_file->fh;
- args.session = (struct liveupdate_session *)session;
+ args.session = luo_session_from_file_set(file_set);
args.file = luo_file->file;
args.serialized_data = luo_file->serialized_data;
args.retrieved = luo_file->retrieved;
@@ -645,24 +645,24 @@ static void luo_file_finish_one(struct l
}
/**
- * luo_file_finish - Completes the lifecycle for all files in a session.
- * @session: The session to be finalized.
+ * luo_file_finish - Completes the lifecycle for all files in a file_set.
+ * @file_set: The file_set to be finalized.
*
- * This function orchestrates the final teardown of a live update session in the
- * new kernel. It should be called after all necessary files have been
+ * This function orchestrates the final teardown of a live update file_set in
+ * the new kernel. It should be called after all necessary files have been
* retrieved and the userspace agent is ready to release the preserved state.
*
* The function iterates through all tracked files. For each file, it performs
* the following sequence of cleanup actions:
*
* 1. If file is not yet retrieved, retrieves it, and calls can_finish() on
- * every file in the session. If all can_finish return true, continue to
+ * every file in the file_set. If all can_finish return true, continue to
* finish.
* 2. Calls the handler's .finish() callback (via luo_file_finish_one) to
* allow for final resource cleanup within the handler.
* 3. Releases LUO's ownership reference on the 'struct file' via fput(). This
* is the counterpart to the get_file() call in luo_retrieve_file().
- * 4. Removes the 'struct luo_file' from the session's internal list.
+ * 4. Removes the 'struct luo_file' from the file_set's internal list.
* 5. Frees the memory for the 'struct luo_file' instance itself.
*
* After successfully finishing all individual files, it frees the
@@ -676,41 +676,38 @@ static void luo_file_finish_one(struct l
* Context: Can be called from an ioctl handler in the new kernel.
* Return: 0 on success, or a negative errno on failure.
*/
-int luo_file_finish(struct luo_session *session)
+int luo_file_finish(struct luo_file_set *file_set)
{
- struct list_head *files_list = &session->files_list;
+ struct list_head *files_list = &file_set->files_list;
struct luo_file *luo_file;
int err;
- if (!session->count)
+ if (!file_set->count)
return 0;
- lockdep_assert_held(&session->mutex);
-
list_for_each_entry(luo_file, files_list, list) {
- err = luo_file_can_finish_one(session, luo_file);
+ err = luo_file_can_finish_one(file_set, luo_file);
if (err)
return err;
}
- while (!list_empty(&session->files_list)) {
- luo_file = list_last_entry(&session->files_list,
+ while (!list_empty(&file_set->files_list)) {
+ luo_file = list_last_entry(&file_set->files_list,
struct luo_file, list);
- luo_file_finish_one(session, luo_file);
+ luo_file_finish_one(file_set, luo_file);
if (luo_file->file)
fput(luo_file->file);
list_del(&luo_file->list);
- session->count--;
+ file_set->count--;
mutex_destroy(&luo_file->mutex);
kfree(luo_file);
}
- if (session->files) {
- kho_restore_free(session->files);
- session->files = NULL;
- session->pgcnt = 0;
+ if (file_set->files) {
+ kho_restore_free(file_set->files);
+ file_set->files = NULL;
}
return 0;
@@ -718,7 +715,8 @@ int luo_file_finish(struct luo_session *
/**
* luo_file_deserialize - Reconstructs the list of preserved files in the new kernel.
- * @session: The incoming session containing the serialized file data from KHO.
+ * @file_set: The incoming file_set to fill with deserialized data.
+ * @file_set_ser: Serialized KHO file_set data from the previous kernel.
*
* This function is called during the early boot process of the new kernel. It
* takes the raw, contiguous memory block of 'struct luo_file_ser' entries,
@@ -733,30 +731,49 @@ int luo_file_finish(struct luo_session *
* 4. Populates the new structure with the deserialized data (token, private
* data handle) and links it to the found handler. The 'file' pointer is
* initialized to NULL, as the file has not been retrieved yet.
- * 5. Adds the new 'struct luo_file' to the session's files_list.
+ * 5. Adds the new 'struct luo_file' to the file_set's files_list.
*
- * This prepares the session for userspace, which can later call
+ * This prepares the file_set for userspace, which can later call
* luo_retrieve_file() to restore the actual file descriptors.
*
* Context: Called from session deserialization.
*/
-int luo_file_deserialize(struct luo_session *session)
+int luo_file_deserialize(struct luo_file_set *file_set,
+ struct luo_file_set_ser *file_set_ser)
{
struct luo_file_ser *file_ser;
u64 i;
- lockdep_assert_held(&session->mutex);
-
- if (!session->files)
+ if (!file_set_ser->files) {
+ WARN_ON(file_set_ser->count);
return 0;
+ }
+
+ file_set->count = file_set_ser->count;
+ file_set->files = phys_to_virt(file_set_ser->files);
- file_ser = session->files;
- for (i = 0; i < session->count; i++) {
+ /*
+ * Note on error handling:
+ *
+ * If deserialization fails (e.g., allocation failure or corrupt data),
+ * we intentionally skip cleanup of files that were already restored.
+ *
+ * A partial failure leaves the preserved state inconsistent.
+ * Implementing a safe "undo" to unwind complex dependencies (sessions,
+ * files, hardware state) is error-prone and provides little value, as
+ * the system is effectively in a broken state.
+ *
+ * We treat these resources as leaked. The expected recovery path is for
+ * userspace to detect the failure and trigger a reboot, which will
+ * reliably reset devices and reclaim memory.
+ */
+ file_ser = file_set->files;
+ for (i = 0; i < file_set->count; i++) {
struct liveupdate_file_handler *fh;
bool handler_found = false;
struct luo_file *luo_file;
- list_for_each_entry(fh, &luo_file_handler_list, list) {
+ luo_list_for_each_private(fh, &luo_file_handler_list, list) {
if (!strcmp(fh->compatible, file_ser[i].compatible)) {
handler_found = true;
break;
@@ -779,12 +796,23 @@ int luo_file_deserialize(struct luo_sess
luo_file->token = file_ser[i].token;
luo_file->retrieved = false;
mutex_init(&luo_file->mutex);
- list_add_tail(&luo_file->list, &session->files_list);
+ list_add_tail(&luo_file->list, &file_set->files_list);
}
return 0;
}
+void luo_file_set_init(struct luo_file_set *file_set)
+{
+ INIT_LIST_HEAD(&file_set->files_list);
+}
+
+void luo_file_set_destroy(struct luo_file_set *file_set)
+{
+ WARN_ON(file_set->count);
+ WARN_ON(!list_empty(&file_set->files_list));
+}
+
/**
* liveupdate_register_file_handler - Register a file handler with LUO.
* @fh: Pointer to a caller-allocated &struct liveupdate_file_handler.
@@ -799,44 +827,99 @@ int luo_file_deserialize(struct luo_sess
*/
int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
{
- static DEFINE_MUTEX(register_file_handler_lock);
struct liveupdate_file_handler *fh_iter;
+ int err;
if (!liveupdate_enabled())
return -EOPNOTSUPP;
- /*
- * Once sessions have been deserialized, file handlers cannot be
- * registered, it is too late.
- */
- if (WARN_ON(luo_session_is_deserialized()))
- return -EBUSY;
-
/* Sanity check that all required callbacks are set */
if (!fh->ops->preserve || !fh->ops->unpreserve ||
!fh->ops->retrieve || !fh->ops->finish) {
return -EINVAL;
}
- guard(mutex)(®ister_file_handler_lock);
- list_for_each_entry(fh_iter, &luo_file_handler_list, list) {
+ /*
+ * Ensure the system is quiescent (no active sessions).
+ * This prevents registering new handlers while sessions are active or
+ * while deserialization is in progress.
+ */
+ if (!luo_session_quiesce())
+ return -EBUSY;
+
+ /* Check for duplicate compatible strings */
+ luo_list_for_each_private(fh_iter, &luo_file_handler_list, list) {
if (!strcmp(fh_iter->compatible, fh->compatible)) {
pr_err("File handler registration failed: Compatible string '%s' already registered.\n",
fh->compatible);
- return -EEXIST;
+ err = -EEXIST;
+ goto err_resume;
}
}
- if (!try_module_get(fh->ops->owner))
- return -EAGAIN;
+ /* Pin the module implementing the handler */
+ if (!try_module_get(fh->ops->owner)) {
+ err = -EAGAIN;
+ goto err_resume;
+ }
- INIT_LIST_HEAD(&fh->list);
- INIT_LIST_HEAD(&fh->flb_list);
- list_add_tail(&fh->list, &luo_file_handler_list);
+ INIT_LIST_HEAD(&ACCESS_PRIVATE(fh, flb_list));
+ INIT_LIST_HEAD(&ACCESS_PRIVATE(fh, list));
+ list_add_tail(&ACCESS_PRIVATE(fh, list), &luo_file_handler_list);
+ luo_session_resume();
liveupdate_test_register(fh);
return 0;
+
+err_resume:
+ luo_session_resume();
+ return err;
+}
+
+/**
+ * liveupdate_unregister_file_handler - Unregister a liveupdate file handler
+ * @fh: The file handler to unregister
+ *
+ * Unregisters the file handler from the liveupdate core. This function
+ * reverses the operations of liveupdate_register_file_handler().
+ *
+ * It ensures safe removal by checking that:
+ * No live update session is currently in progress.
+ * No FLB registered with this file handler.
+ *
+ * If the unregistration fails, the internal test state is reverted.
+ *
+ * Return: 0 Success. -EOPNOTSUPP when live update is not enabled. -EBUSY A live
+ * update is in progress, can't quiesce live update or FLB is registred with
+ * this file handler.
+ */
+int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
+{
+ int err = -EBUSY;
+
+ if (!liveupdate_enabled())
+ return -EOPNOTSUPP;
+
+ liveupdate_test_unregister(fh);
+
+ if (!luo_session_quiesce())
+ goto err_register;
+
+ if (!list_empty(&ACCESS_PRIVATE(fh, flb_list)))
+ goto err_resume;
+
+ list_del(&ACCESS_PRIVATE(fh, list));
+ module_put(fh->ops->owner);
+ luo_session_resume();
+
+ return 0;
+
+err_resume:
+ luo_session_resume();
+err_register:
+ liveupdate_test_register(fh);
+ return err;
}
/**
@@ -857,11 +940,11 @@ int liveupdate_register_file_handler(str
int liveupdate_get_token_outgoing(struct liveupdate_session *s,
struct file *file, u64 *tokenp)
{
- struct luo_session *session = (struct luo_session *)s;
+ struct luo_file_set *file_set = luo_file_set_from_session(s);
struct luo_file *luo_file;
int err = -ENOENT;
- list_for_each_entry(luo_file, &session->files_list, list) {
+ list_for_each_entry(luo_file, &file_set->files_list, list) {
if (luo_file->file == file) {
if (tokenp)
*tokenp = luo_file->token;
@@ -887,10 +970,10 @@ int liveupdate_get_token_outgoing(struct
* The operation is idempotent; subsequent calls for the same token will return
* a pointer to the same 'struct file' object.
*
- * The caller receives a pointer to the file with a reference incremented. The
- * file's lifetime is managed by LUO and any userspace file
- * descriptors. If the caller needs to hold a reference to the file beyond the
- * immediate scope, it must call get_file() itself.
+ * The caller receives a new reference to the file and must call fput() when it
+ * is no longer needed. The file's lifetime is managed by LUO and any userspace
+ * file descriptors. If the caller needs to hold a reference to the file beyond
+ * the immediate scope, it must call get_file() itself.
*
* Context: Can be called from any context in the new kernel that has a handle
* to a restored session.
@@ -900,7 +983,5 @@ int liveupdate_get_token_outgoing(struct
int liveupdate_get_file_incoming(struct liveupdate_session *s, u64 token,
struct file **filep)
{
- struct luo_session *session = (struct luo_session *)s;
-
- return luo_retrieve_file(session, token, filep);
+ return luo_retrieve_file(luo_file_set_from_session(s), token, filep);
}
--- a/kernel/liveupdate/luo_flb.c~b
+++ a/kernel/liveupdate/luo_flb.c
@@ -43,10 +43,10 @@
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/kexec_handover.h>
+#include <linux/kho/abi/luo.h>
#include <linux/libfdt.h>
#include <linux/list.h>
#include <linux/liveupdate.h>
-#include <linux/liveupdate/abi/luo.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
@@ -85,42 +85,28 @@ struct luo_flb_link {
struct list_head list;
};
-/*
- * struct luo_flb_state - Holds the runtime state for one FLB lifecycle path.
- * @count: The number of preserved files currently depending on this FLB.
- * This is used to trigger the preserve/unpreserve/finish ops on the
- * first/last file.
- * @data: The opaque u64 handle returned by .preserve() or passed to
- * .retrieve().
- * @obj: The live kernel object returned by .preserve() or .retrieve().
- * @lock: A mutex that protects all fields within this structure, providing
- * the synchronization service for the FLB's ops.
- */
-struct luo_flb_state {
- long count;
- u64 data;
- void *obj;
- struct mutex lock;
-};
+/* luo_flb_get_private - Access private field, and if needed initialize it. */
+static struct luo_flb_private *luo_flb_get_private(struct liveupdate_flb *flb)
+{
+ struct luo_flb_private *private = &ACCESS_PRIVATE(flb, private);
-/*
- * struct luo_flb_internal - Keep separate incoming and outgoing states.
- * @outgoing: The runtime state for the pre-reboot (preserve/unpreserve)
- * lifecycle.
- * @incoming: The runtime state for the post-reboot (retrieve/finish)
- * lifecycle.
- */
-struct luo_flb_internal {
- struct luo_flb_state outgoing;
- struct luo_flb_state incoming;
-};
+ if (!private->initialized) {
+ mutex_init(&private->incoming.lock);
+ mutex_init(&private->outgoing.lock);
+ INIT_LIST_HEAD(&private->list);
+ private->users = 0;
+ private->initialized = true;
+ }
+
+ return private;
+}
static int luo_flb_file_preserve_one(struct liveupdate_flb *flb)
{
- struct luo_flb_internal *internal = flb->internal;
+ struct luo_flb_private *private = luo_flb_get_private(flb);
- scoped_guard(mutex, &internal->outgoing.lock) {
- if (!internal->outgoing.count) {
+ scoped_guard(mutex, &private->outgoing.lock) {
+ if (!private->outgoing.count) {
struct liveupdate_flb_op_args args = {0};
int err;
@@ -128,10 +114,10 @@ static int luo_flb_file_preserve_one(str
err = flb->ops->preserve(&args);
if (err)
return err;
- internal->outgoing.data = args.data;
- internal->outgoing.obj = args.obj;
+ private->outgoing.data = args.data;
+ private->outgoing.obj = args.obj;
}
- internal->outgoing.count++;
+ private->outgoing.count++;
}
return 0;
@@ -139,37 +125,37 @@ static int luo_flb_file_preserve_one(str
static void luo_flb_file_unpreserve_one(struct liveupdate_flb *flb)
{
- struct luo_flb_internal *internal = flb->internal;
+ struct luo_flb_private *private = luo_flb_get_private(flb);
- scoped_guard(mutex, &internal->outgoing.lock) {
- internal->outgoing.count--;
- if (!internal->outgoing.count) {
+ scoped_guard(mutex, &private->outgoing.lock) {
+ private->outgoing.count--;
+ if (!private->outgoing.count) {
struct liveupdate_flb_op_args args = {0};
args.flb = flb;
- args.data = internal->outgoing.data;
- args.obj = internal->outgoing.obj;
+ args.data = private->outgoing.data;
+ args.obj = private->outgoing.obj;
if (flb->ops->unpreserve)
flb->ops->unpreserve(&args);
- internal->outgoing.data = 0;
- internal->outgoing.obj = NULL;
+ private->outgoing.data = 0;
+ private->outgoing.obj = NULL;
}
}
}
static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
{
+ struct luo_flb_private *private = luo_flb_get_private(flb);
struct luo_flb_header *fh = &luo_flb_global.incoming;
- struct luo_flb_internal *internal = flb->internal;
struct liveupdate_flb_op_args args = {0};
bool found = false;
int err;
- guard(mutex)(&internal->incoming.lock);
+ guard(mutex)(&private->incoming.lock);
- if (internal->incoming.obj)
+ if (private->incoming.obj)
return 0;
if (!fh->active)
@@ -177,8 +163,8 @@ static int luo_flb_retrieve_one(struct l
for (int i = 0; i < fh->header_ser->count; i++) {
if (!strcmp(fh->ser[i].name, flb->compatible)) {
- internal->incoming.data = fh->ser[i].data;
- internal->incoming.count = fh->ser[i].count;
+ private->incoming.data = fh->ser[i].data;
+ private->incoming.count = fh->ser[i].count;
found = true;
break;
}
@@ -188,15 +174,15 @@ static int luo_flb_retrieve_one(struct l
return -ENOENT;
args.flb = flb;
- args.data = internal->incoming.data;
+ args.data = private->incoming.data;
err = flb->ops->retrieve(&args);
if (err)
return err;
- internal->incoming.obj = args.obj;
+ private->incoming.obj = args.obj;
- if (WARN_ON_ONCE(!internal->incoming.obj))
+ if (WARN_ON_ONCE(!private->incoming.obj))
return -EIO;
return 0;
@@ -204,36 +190,36 @@ static int luo_flb_retrieve_one(struct l
static void luo_flb_file_finish_one(struct liveupdate_flb *flb)
{
- struct luo_flb_internal *internal = flb->internal;
+ struct luo_flb_private *private = luo_flb_get_private(flb);
u64 count;
- scoped_guard(mutex, &internal->incoming.lock)
- count = --internal->incoming.count;
+ scoped_guard(mutex, &private->incoming.lock)
+ count = --private->incoming.count;
if (!count) {
struct liveupdate_flb_op_args args = {0};
- if (!internal->incoming.obj) {
+ if (!private->incoming.obj) {
int err = luo_flb_retrieve_one(flb);
if (WARN_ON(err))
return;
}
- scoped_guard(mutex, &internal->incoming.lock) {
+ scoped_guard(mutex, &private->incoming.lock) {
args.flb = flb;
- args.obj = internal->incoming.obj;
+ args.obj = private->incoming.obj;
flb->ops->finish(&args);
- internal->incoming.data = 0;
- internal->incoming.obj = NULL;
+ private->incoming.data = 0;
+ private->incoming.obj = NULL;
}
}
}
/**
* luo_flb_file_preserve - Notifies FLBs that a file is about to be preserved.
- * @h: The file handler for the preserved file.
+ * @fh: The file handler for the preserved file.
*
* This function iterates through all FLBs associated with the given file
* handler. It increments the reference count for each FLB. If the count becomes
@@ -246,12 +232,13 @@ static void luo_flb_file_finish_one(stru
* Context: Called from luo_preserve_file()
* Return: 0 on success, or a negative errno on failure.
*/
-int luo_flb_file_preserve(struct liveupdate_file_handler *h)
+int luo_flb_file_preserve(struct liveupdate_file_handler *fh)
{
+ struct list_head *flb_list = &ACCESS_PRIVATE(fh, flb_list);
struct luo_flb_link *iter;
int err = 0;
- list_for_each_entry(iter, &h->flb_list, list) {
+ list_for_each_entry(iter, flb_list, list) {
err = luo_flb_file_preserve_one(iter->flb);
if (err)
goto exit_err;
@@ -260,7 +247,7 @@ int luo_flb_file_preserve(struct liveupd
return 0;
exit_err:
- list_for_each_entry_continue_reverse(iter, &h->flb_list, list)
+ list_for_each_entry_continue_reverse(iter, flb_list, list)
luo_flb_file_unpreserve_one(iter->flb);
return err;
@@ -268,7 +255,7 @@ exit_err:
/**
* luo_flb_file_unpreserve - Notifies FLBs that a dependent file was unpreserved.
- * @h: The file handler for the unpreserved file.
+ * @fh: The file handler for the unpreserved file.
*
* This function iterates through all FLBs associated with the given file
* handler, in reverse order of registration. It decrements the reference count
@@ -278,17 +265,18 @@ exit_err:
* Context: Called when a preserved file is being cleaned up before reboot
* (e.g., from luo_file_unpreserve_files()).
*/
-void luo_flb_file_unpreserve(struct liveupdate_file_handler *h)
+void luo_flb_file_unpreserve(struct liveupdate_file_handler *fh)
{
+ struct list_head *flb_list = &ACCESS_PRIVATE(fh, flb_list);
struct luo_flb_link *iter;
- list_for_each_entry_reverse(iter, &h->flb_list, list)
+ list_for_each_entry_reverse(iter, flb_list, list)
luo_flb_file_unpreserve_one(iter->flb);
}
/**
* luo_flb_file_finish - Notifies FLBs that a dependent file has been finished.
- * @h: The file handler for the finished file.
+ * @fh: The file handler for the finished file.
*
* This function iterates through all FLBs associated with the given file
* handler, in reverse order of registration. It decrements the incoming
@@ -297,54 +285,22 @@ void luo_flb_file_unpreserve(struct live
*
* Context: Called from luo_file_finish() for each file being finished.
*/
-void luo_flb_file_finish(struct liveupdate_file_handler *h)
+void luo_flb_file_finish(struct liveupdate_file_handler *fh)
{
+ struct list_head *flb_list = &ACCESS_PRIVATE(fh, flb_list);
struct luo_flb_link *iter;
- list_for_each_entry_reverse(iter, &h->flb_list, list)
+ list_for_each_entry_reverse(iter, flb_list, list)
luo_flb_file_finish_one(iter->flb);
}
/**
- * liveupdate_init_flb - Initializes a liveupdate FLB structure.
- * @flb: The &struct liveupdate_flb to initialize.
- *
- * This function must be called to prepare an FLB structure before it can be
- * used with liveupdate_register_flb() or any other LUO functions.
- *
- * Context: Typically called once from a subsystem's module init function for
- * each global FLB object that the module defines.
- *
- * Return: 0 on success, or -ENOMEM if memory allocation fails, and -EOPNOTSUPP
- * when live update is disabled or not configured.
- */
-int liveupdate_init_flb(struct liveupdate_flb *flb)
-{
- struct luo_flb_internal *internal;
-
- if (!liveupdate_enabled())
- return -EOPNOTSUPP;
-
- internal = kzalloc(sizeof(*internal), GFP_KERNEL | __GFP_ZERO);
- if (!internal)
- return -ENOMEM;
-
- mutex_init(&internal->incoming.lock);
- mutex_init(&internal->outgoing.lock);
-
- flb->internal = internal;
- INIT_LIST_HEAD(&flb->list);
-
- return 0;
-}
-
-/**
* liveupdate_register_flb - Associate an FLB with a file handler and register it globally.
- * @h: The file handler that will now depend on the FLB.
- * @flb: The File-Lifecycle-Bound object to associate.
+ * @fh: The file handler that will now depend on the FLB.
+ * @flb: The File-Lifecycle-Bound object to associate.
*
* Establishes a dependency, informing the LUO core that whenever a file of
- * type @h is preserved, the state of @flb must also be managed.
+ * type @fh is preserved, the state of @flb must also be managed.
*
* On the first registration of a given @flb object, it is added to a global
* registry. This function checks for duplicate registrations, both for a
@@ -360,76 +316,207 @@ int liveupdate_init_flb(struct liveupdat
* -ENOSPC if the maximum number of global FLBs has been reached.
* -EOPNOTSUPP if live update is disabled or not configured.
*/
-int liveupdate_register_flb(struct liveupdate_file_handler *h,
+int liveupdate_register_flb(struct liveupdate_file_handler *fh,
struct liveupdate_flb *flb)
{
- struct luo_flb_internal *internal = flb->internal;
+ struct luo_flb_private *private = luo_flb_get_private(flb);
+ struct list_head *flb_list = &ACCESS_PRIVATE(fh, flb_list);
struct luo_flb_link *link __free(kfree) = NULL;
- static DEFINE_MUTEX(register_flb_lock);
struct liveupdate_flb *gflb;
struct luo_flb_link *iter;
+ int err;
if (!liveupdate_enabled())
return -EOPNOTSUPP;
- if (WARN_ON(!h || !flb || !internal))
- return -EINVAL;
-
if (WARN_ON(!flb->ops->preserve || !flb->ops->unpreserve ||
!flb->ops->retrieve || !flb->ops->finish)) {
return -EINVAL;
}
/*
- * Once session/files have been deserialized, FLBs cannot be registered,
- * it is too late. Deserialization uses file handlers, and FLB registers
- * to file handlers.
- */
- if (WARN_ON(luo_session_is_deserialized()))
- return -EBUSY;
-
- /*
- * File handler must already be registered, as it is initializes the
+ * File handler must already be registered, as it initializes the
* flb_list
*/
- if (WARN_ON(list_empty(&h->list)))
+ if (WARN_ON(list_empty(&ACCESS_PRIVATE(fh, list))))
return -EINVAL;
link = kzalloc(sizeof(*link), GFP_KERNEL);
if (!link)
return -ENOMEM;
- guard(mutex)(®ister_flb_lock);
+ /*
+ * Ensure the system is quiescent (no active sessions).
+ * This acts as a global lock for registration: no other thread can
+ * be in this section, and no sessions can be creating/using FDs.
+ */
+ if (!luo_session_quiesce())
+ return -EBUSY;
/* Check that this FLB is not already linked to this file handler */
- list_for_each_entry(iter, &h->flb_list, list) {
+ err = -EEXIST;
+ list_for_each_entry(iter, flb_list, list) {
if (iter->flb == flb)
- return -EEXIST;
+ goto err_resume;
}
- /* Is this FLB linked to global list ? */
- if (list_empty(&flb->list)) {
- if (luo_flb_global.count == LUO_FLB_MAX)
- return -ENOSPC;
+ /*
+ * If this FLB is not linked to global list it's the first time the FLB
+ * is registered
+ */
+ if (!private->users) {
+ if (WARN_ON(!list_empty(&private->list))) {
+ err = -EINVAL;
+ goto err_resume;
+ }
+
+ if (luo_flb_global.count == LUO_FLB_MAX) {
+ err = -ENOSPC;
+ goto err_resume;
+ }
/* Check that compatible string is unique in global list */
- list_for_each_entry(gflb, &luo_flb_global.list, list) {
+ luo_list_for_each_private(gflb, &luo_flb_global.list, private.list) {
if (!strcmp(gflb->compatible, flb->compatible))
- return -EEXIST;
+ goto err_resume;
}
- if (!try_module_get(flb->ops->owner))
- return -EAGAIN;
+ if (!try_module_get(flb->ops->owner)) {
+ err = -EAGAIN;
+ goto err_resume;
+ }
- list_add_tail(&flb->list, &luo_flb_global.list);
+ list_add_tail(&private->list, &luo_flb_global.list);
luo_flb_global.count++;
}
/* Finally, link the FLB to the file handler */
+ private->users++;
link->flb = flb;
- list_add_tail(&no_free_ptr(link)->list, &h->flb_list);
+ list_add_tail(&no_free_ptr(link)->list, flb_list);
+ luo_session_resume();
return 0;
+
+err_resume:
+ luo_session_resume();
+ return err;
+}
+
+/**
+ * liveupdate_unregister_flb - Remove an FLB dependency from a file handler.
+ * @fh: The file handler that is currently depending on the FLB.
+ * @flb: The File-Lifecycle-Bound object to remove.
+ *
+ * Removes the association between the specified file handler and the FLB
+ * previously established by liveupdate_register_flb().
+ *
+ * This function manages the global lifecycle of the FLB. It decrements the
+ * FLB's usage count. If this was the last file handler referencing this FLB,
+ * the FLB is removed from the global registry and the reference to its
+ * owner module (acquired during registration) is released.
+ *
+ * Context: This function ensures the session is quiesced (no active FDs
+ * being created) during the update. It is typically called from a
+ * subsystem's module exit function.
+ * Return: 0 on success.
+ * -EOPNOTSUPP if live update is disabled.
+ * -EBUSY if the live update session is active and cannot be quiesced.
+ * -ENOENT if the FLB was not found in the file handler's list.
+ */
+int liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
+ struct liveupdate_flb *flb)
+{
+ struct luo_flb_private *private = luo_flb_get_private(flb);
+ struct list_head *flb_list = &ACCESS_PRIVATE(fh, flb_list);
+ struct luo_flb_link *iter;
+ int err = -ENOENT;
+
+ if (!liveupdate_enabled())
+ return -EOPNOTSUPP;
+
+ /*
+ * Ensure the system is quiescent (no active sessions).
+ * This acts as a global lock for unregistration.
+ */
+ if (!luo_session_quiesce())
+ return -EBUSY;
+
+ /* Find and remove the link from the file handler's list */
+ list_for_each_entry(iter, flb_list, list) {
+ if (iter->flb == flb) {
+ list_del(&iter->list);
+ kfree(iter);
+ err = 0;
+ break;
+ }
+ }
+
+ if (err)
+ goto err_resume;
+
+ private->users--;
+ /*
+ * If this is the last file-handler with which we are registred, remove
+ * from the global list, and relese module reference.
+ */
+ if (!private->users) {
+ list_del_init(&private->list);
+ luo_flb_global.count--;
+ module_put(flb->ops->owner);
+ }
+
+ luo_session_resume();
+
+ return 0;
+
+err_resume:
+ luo_session_resume();
+ return err;
+}
+
+static int luo_flb_locked(struct liveupdate_flb *flb, bool incoming,
+ void **objp)
+{
+ struct luo_flb_private *private = luo_flb_get_private(flb);
+ struct luo_flb_private_state *state;
+
+ if (!liveupdate_enabled())
+ return -EOPNOTSUPP;
+
+ if (incoming) {
+ state = &private->incoming;
+ if (!state->obj) {
+ int err = luo_flb_retrieve_one(flb);
+
+ if (err)
+ return err;
+ }
+ } else {
+ state = &private->outgoing;
+
+ /* Sanity check that object exists */
+ if (WARN_ON_ONCE(!state->obj))
+ return -ENOENT;
+ }
+
+ mutex_lock(&state->lock);
+ *objp = state->obj;
+
+ return 0;
+}
+
+static void luo_flb_unlock(struct liveupdate_flb *flb, bool incoming,
+ void *obj)
+{
+ struct luo_flb_private *private = luo_flb_get_private(flb);
+ struct luo_flb_private_state *state;
+
+ state = incoming ? &private->incoming : &private->outgoing;
+
+ lockdep_assert_held(&state->lock);
+ state->obj = obj;
+ mutex_unlock(&state->lock);
}
/**
@@ -453,41 +540,19 @@ int liveupdate_register_flb(struct liveu
*/
int liveupdate_flb_incoming_locked(struct liveupdate_flb *flb, void **objp)
{
- struct luo_flb_internal *internal = flb->internal;
-
- if (!liveupdate_enabled())
- return -EOPNOTSUPP;
-
- if (WARN_ON(!internal))
- return -EINVAL;
-
- if (!internal->incoming.obj) {
- int err = luo_flb_retrieve_one(flb);
-
- if (err)
- return err;
- }
-
- mutex_lock(&internal->incoming.lock);
- *objp = internal->incoming.obj;
-
- return 0;
+ return luo_flb_locked(flb, true, objp);
}
/**
* liveupdate_flb_incoming_unlock - Unlock an incoming FLB object.
* @flb: The FLB definition.
- * @obj: The object that was returned by the _locked call (used for validation).
+ * @obj: The object that was returned by the _locked call
*
* Releases the internal lock acquired by liveupdate_flb_incoming_locked().
*/
void liveupdate_flb_incoming_unlock(struct liveupdate_flb *flb, void *obj)
{
- struct luo_flb_internal *internal = flb->internal;
-
- lockdep_assert_held(&internal->incoming.lock);
- internal->incoming.obj = obj;
- mutex_unlock(&internal->incoming.lock);
+ luo_flb_unlock(flb, true, obj);
}
/**
@@ -508,41 +573,19 @@ void liveupdate_flb_incoming_unlock(stru
*/
int liveupdate_flb_outgoing_locked(struct liveupdate_flb *flb, void **objp)
{
- struct luo_flb_internal *internal = flb->internal;
-
- if (!liveupdate_enabled())
- return -EOPNOTSUPP;
-
- if (WARN_ON(!internal))
- return -EINVAL;
-
- mutex_lock(&internal->outgoing.lock);
-
- /* The object must exist if any file is being preserved */
- if (WARN_ON_ONCE(!internal->outgoing.obj)) {
- mutex_unlock(&internal->outgoing.lock);
- return -ENOENT;
- }
-
- *objp = internal->outgoing.obj;
-
- return 0;
+ return luo_flb_locked(flb, false, objp);
}
/**
* liveupdate_flb_outgoing_unlock - Unlock an outgoing FLB object.
* @flb: The FLB definition.
- * @obj: The object that was returned by the _locked call (used for validation).
+ * @obj: The object that was returned by the _locked call
*
* Releases the internal lock acquired by liveupdate_flb_outgoing_locked().
*/
void liveupdate_flb_outgoing_unlock(struct liveupdate_flb *flb, void *obj)
{
- struct luo_flb_internal *internal = flb->internal;
-
- lockdep_assert_held(&internal->outgoing.lock);
- internal->outgoing.obj = obj;
- mutex_unlock(&internal->outgoing.lock);
+ luo_flb_unlock(flb, false, obj);
}
int __init luo_flb_setup_outgoing(void *fdt_out)
@@ -639,17 +682,17 @@ int __init luo_flb_setup_incoming(void *
void luo_flb_serialize(void)
{
struct luo_flb_header *fh = &luo_flb_global.outgoing;
- struct liveupdate_flb *flb;
+ struct liveupdate_flb *gflb;
int i = 0;
- list_for_each_entry(flb, &luo_flb_global.list, list) {
- struct luo_flb_internal *internal = flb->internal;
+ luo_list_for_each_private(gflb, &luo_flb_global.list, private.list) {
+ struct luo_flb_private *private = luo_flb_get_private(gflb);
- if (internal->outgoing.count > 0) {
- strscpy(fh->ser[i].name, flb->compatible,
+ if (private->outgoing.count > 0) {
+ strscpy(fh->ser[i].name, gflb->compatible,
sizeof(fh->ser[i].name));
- fh->ser[i].data = internal->outgoing.data;
- fh->ser[i].count = internal->outgoing.count;
+ fh->ser[i].data = private->outgoing.data;
+ fh->ser[i].count = private->outgoing.count;
i++;
}
}
--- a/kernel/liveupdate/luo_internal.h~b
+++ a/kernel/liveupdate/luo_internal.h
@@ -31,65 +31,111 @@ static inline int luo_ucmd_respond(struc
return 0;
}
+/*
+ * Handles a deserialization failure: devices and memory is in unpredictable
+ * state.
+ *
+ * Continuing the boot process after a failure is dangerous because it could
+ * lead to leaks of private data.
+ */
+#define luo_restore_fail(__fmt, ...) panic(__fmt, ##__VA_ARGS__)
+
+/* Mimics list_for_each_entry() but for private list head entries */
+#define luo_list_for_each_private(pos, head, member) \
+ for (struct list_head *__iter = (head)->next; \
+ __iter != (head) && \
+ ({ pos = container_of(__iter, typeof(*(pos)), member); 1; }); \
+ __iter = __iter->next)
+
/**
- * struct luo_session - Represents an active or incoming Live Update session.
- * @name: A unique name for this session, used for identification and
- * retrieval.
+ * struct luo_file_set - A set of files that belong to the same sessions.
* @files_list: An ordered list of files associated with this session, it is
* ordered by preservation time.
- * @ser: Pointer to the serialized data for this session.
+ * @files: The physically contiguous memory block that holds the serialized
+ * state of files.
* @count: A counter tracking the number of files currently stored in the
* @files_list for this session.
+ */
+struct luo_file_set {
+ struct list_head files_list;
+ struct luo_file_ser *files;
+ long count;
+};
+
+/**
+ * struct luo_session - Represents an active or incoming Live Update session.
+ * @name: A unique name for this session, used for identification and
+ * retrieval.
+ * @ser: Pointer to the serialized data for this session.
* @list: A list_head member used to link this session into a global list
* of either outgoing (to be preserved) or incoming (restored from
* previous kernel) sessions.
* @retrieved: A boolean flag indicating whether this session has been
* retrieved by a consumer in the new kernel.
- * @mutex: Session lock, protects files_list, and count.
- * @files: The physically contiguous memory block that holds the serialized
- * state of files.
- * @pgcnt: The number of pages @files occupy.
+ * @file_set: A set of files that belong to this session.
+ * @mutex: protects fields in the luo_session.
*/
struct luo_session {
char name[LIVEUPDATE_SESSION_NAME_LENGTH];
- struct list_head files_list;
struct luo_session_ser *ser;
- long count;
struct list_head list;
bool retrieved;
+ struct luo_file_set file_set;
struct mutex mutex;
- struct luo_file_ser *files;
- u64 pgcnt;
};
+static inline struct liveupdate_session *luo_session_from_file_set(struct luo_file_set *file_set)
+{
+ struct luo_session *session;
+
+ session = container_of(file_set, struct luo_session, file_set);
+
+ return (struct liveupdate_session *)session;
+}
+
+static inline struct luo_file_set *luo_file_set_from_session(struct liveupdate_session *s)
+{
+ struct luo_session *session = (struct luo_session *)s;
+
+ return &session->file_set;
+}
+
int luo_session_create(const char *name, struct file **filep);
int luo_session_retrieve(const char *name, struct file **filep);
int __init luo_session_setup_outgoing(void *fdt);
int __init luo_session_setup_incoming(void *fdt);
int luo_session_serialize(void);
int luo_session_deserialize(void);
-bool luo_session_is_deserialized(void);
+bool luo_session_quiesce(void);
+void luo_session_resume(void);
-int luo_preserve_file(struct luo_session *session, u64 token, int fd);
-void luo_file_unpreserve_files(struct luo_session *session);
-int luo_file_freeze(struct luo_session *session);
-void luo_file_unfreeze(struct luo_session *session);
-int luo_retrieve_file(struct luo_session *session, u64 token,
+int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd);
+void luo_file_unpreserve_files(struct luo_file_set *file_set);
+int luo_file_freeze(struct luo_file_set *file_set,
+ struct luo_file_set_ser *file_set_ser);
+void luo_file_unfreeze(struct luo_file_set *file_set,
+ struct luo_file_set_ser *file_set_ser);
+int luo_retrieve_file(struct luo_file_set *file_set, u64 token,
struct file **filep);
-int luo_file_finish(struct luo_session *session);
-int luo_file_deserialize(struct luo_session *session);
-
-int luo_flb_file_preserve(struct liveupdate_file_handler *h);
-void luo_flb_file_unpreserve(struct liveupdate_file_handler *h);
-void luo_flb_file_finish(struct liveupdate_file_handler *h);
+int luo_file_finish(struct luo_file_set *file_set);
+int luo_file_deserialize(struct luo_file_set *file_set,
+ struct luo_file_set_ser *file_set_ser);
+void luo_file_set_init(struct luo_file_set *file_set);
+void luo_file_set_destroy(struct luo_file_set *file_set);
+
+int luo_flb_file_preserve(struct liveupdate_file_handler *fh);
+void luo_flb_file_unpreserve(struct liveupdate_file_handler *fh);
+void luo_flb_file_finish(struct liveupdate_file_handler *fh);
int __init luo_flb_setup_outgoing(void *fdt);
int __init luo_flb_setup_incoming(void *fdt);
void luo_flb_serialize(void);
#ifdef CONFIG_LIVEUPDATE_TEST
-void liveupdate_test_register(struct liveupdate_file_handler *h);
+void liveupdate_test_register(struct liveupdate_file_handler *fh);
+void liveupdate_test_unregister(struct liveupdate_file_handler *fh);
#else
-static inline void liveupdate_test_register(struct liveupdate_file_handler *h) { }
+static inline void liveupdate_test_register(struct liveupdate_file_handler *fh) { }
+static inline void liveupdate_test_unregister(struct liveupdate_file_handler *fh) { }
#endif
#endif /* _LINUX_LUO_INTERNAL_H */
diff --git a/kernel/liveupdate/luo_ioctl.c a/kernel/liveupdate/luo_ioctl.c
deleted file mode 100644
--- a/kernel/liveupdate/luo_ioctl.c
+++ /dev/null
@@ -1,223 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-
-/*
- * Copyright (c) 2025, Google LLC.
- * Pasha Tatashin <pasha.tatashin@soleen.com>
- */
-
-/**
- * DOC: LUO ioctl Interface
- *
- * The IOCTL user-space control interface for the LUO subsystem.
- * It registers a character device, typically found at ``/dev/liveupdate``,
- * which allows a userspace agent to manage the LUO state machine and its
- * associated resources, such as preservable file descriptors.
- *
- * To ensure that the state machine is controlled by a single entity, access
- * to this device is exclusive: only one process is permitted to have
- * ``/dev/liveupdate`` open at any given time. Subsequent open attempts will
- * fail with -EBUSY until the first process closes its file descriptor.
- * This singleton model simplifies state management by preventing conflicting
- * commands from multiple userspace agents.
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <linux/atomic.h>
-#include <linux/errno.h>
-#include <linux/file.h>
-#include <linux/fs.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/liveupdate.h>
-#include <linux/miscdevice.h>
-#include <uapi/linux/liveupdate.h>
-#include "luo_internal.h"
-
-struct luo_device_state {
- struct miscdevice miscdev;
- atomic_t in_use;
-};
-
-static int luo_ioctl_create_session(struct luo_ucmd *ucmd)
-{
- struct liveupdate_ioctl_create_session *argp = ucmd->cmd;
- struct file *file;
- int err;
-
- argp->fd = get_unused_fd_flags(O_CLOEXEC);
- if (argp->fd < 0)
- return argp->fd;
-
- err = luo_session_create(argp->name, &file);
- if (err)
- goto err_put_fd;
-
- err = luo_ucmd_respond(ucmd, sizeof(*argp));
- if (err)
- goto err_put_file;
-
- fd_install(argp->fd, file);
-
- return 0;
-
-err_put_file:
- fput(file);
-err_put_fd:
- put_unused_fd(argp->fd);
-
- return err;
-}
-
-static int luo_ioctl_retrieve_session(struct luo_ucmd *ucmd)
-{
- struct liveupdate_ioctl_retrieve_session *argp = ucmd->cmd;
- struct file *file;
- int err;
-
- argp->fd = get_unused_fd_flags(O_CLOEXEC);
- if (argp->fd < 0)
- return argp->fd;
-
- err = luo_session_retrieve(argp->name, &file);
- if (err < 0)
- goto err_put_fd;
-
- err = luo_ucmd_respond(ucmd, sizeof(*argp));
- if (err)
- goto err_put_file;
-
- fd_install(argp->fd, file);
-
- return 0;
-
-err_put_file:
- fput(file);
-err_put_fd:
- put_unused_fd(argp->fd);
-
- return err;
-}
-
-static int luo_open(struct inode *inodep, struct file *filep)
-{
- struct luo_device_state *ldev = container_of(filep->private_data,
- struct luo_device_state,
- miscdev);
-
- if (atomic_cmpxchg(&ldev->in_use, 0, 1))
- return -EBUSY;
-
- luo_session_deserialize();
-
- return 0;
-}
-
-static int luo_release(struct inode *inodep, struct file *filep)
-{
- struct luo_device_state *ldev = container_of(filep->private_data,
- struct luo_device_state,
- miscdev);
- atomic_set(&ldev->in_use, 0);
-
- return 0;
-}
-
-union ucmd_buffer {
- struct liveupdate_ioctl_create_session create;
- struct liveupdate_ioctl_retrieve_session retrieve;
-};
-
-struct luo_ioctl_op {
- unsigned int size;
- unsigned int min_size;
- unsigned int ioctl_num;
- int (*execute)(struct luo_ucmd *ucmd);
-};
-
-#define IOCTL_OP(_ioctl, _fn, _struct, _last) \
- [_IOC_NR(_ioctl) - LIVEUPDATE_CMD_BASE] = { \
- .size = sizeof(_struct) + \
- BUILD_BUG_ON_ZERO(sizeof(union ucmd_buffer) < \
- sizeof(_struct)), \
- .min_size = offsetofend(_struct, _last), \
- .ioctl_num = _ioctl, \
- .execute = _fn, \
- }
-
-static const struct luo_ioctl_op luo_ioctl_ops[] = {
- IOCTL_OP(LIVEUPDATE_IOCTL_CREATE_SESSION, luo_ioctl_create_session,
- struct liveupdate_ioctl_create_session, name),
- IOCTL_OP(LIVEUPDATE_IOCTL_RETRIEVE_SESSION, luo_ioctl_retrieve_session,
- struct liveupdate_ioctl_retrieve_session, name),
-};
-
-static long luo_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
-{
- const struct luo_ioctl_op *op;
- struct luo_ucmd ucmd = {};
- union ucmd_buffer buf;
- unsigned int nr;
- int err;
-
- nr = _IOC_NR(cmd);
- if (nr < LIVEUPDATE_CMD_BASE ||
- (nr - LIVEUPDATE_CMD_BASE) >= ARRAY_SIZE(luo_ioctl_ops)) {
- return -EINVAL;
- }
-
- ucmd.ubuffer = (void __user *)arg;
- err = get_user(ucmd.user_size, (u32 __user *)ucmd.ubuffer);
- if (err)
- return err;
-
- op = &luo_ioctl_ops[nr - LIVEUPDATE_CMD_BASE];
- if (op->ioctl_num != cmd)
- return -ENOIOCTLCMD;
- if (ucmd.user_size < op->min_size)
- return -EINVAL;
-
- ucmd.cmd = &buf;
- err = copy_struct_from_user(ucmd.cmd, op->size, ucmd.ubuffer,
- ucmd.user_size);
- if (err)
- return err;
-
- return op->execute(&ucmd);
-}
-
-static const struct file_operations luo_fops = {
- .owner = THIS_MODULE,
- .open = luo_open,
- .release = luo_release,
- .unlocked_ioctl = luo_ioctl,
-};
-
-static struct luo_device_state luo_dev = {
- .miscdev = {
- .minor = MISC_DYNAMIC_MINOR,
- .name = "liveupdate",
- .fops = &luo_fops,
- },
- .in_use = ATOMIC_INIT(0),
-};
-
-static int __init liveupdate_ioctl_init(void)
-{
- if (!liveupdate_enabled())
- return 0;
-
- return misc_register(&luo_dev.miscdev);
-}
-module_init(liveupdate_ioctl_init);
-
-static void __exit liveupdate_exit(void)
-{
- misc_deregister(&luo_dev.miscdev);
-}
-module_exit(liveupdate_exit);
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Pasha Tatashin");
-MODULE_DESCRIPTION("Live Update Orchestrator");
-MODULE_VERSION("0.1");
--- a/kernel/liveupdate/luo_session.c~b
+++ a/kernel/liveupdate/luo_session.c
@@ -58,17 +58,18 @@
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/kexec_handover.h>
+#include <linux/kho/abi/luo.h>
#include <linux/libfdt.h>
#include <linux/list.h>
#include <linux/liveupdate.h>
-#include <linux/liveupdate/abi/luo.h>
#include <linux/mutex.h>
+#include <linux/rwsem.h>
#include <linux/slab.h>
#include <linux/unaligned.h>
#include <uapi/linux/liveupdate.h>
#include "luo_internal.h"
-/* 16 4K pages, give space for 819 sessions */
+/* 16 4K pages, give space for 744 sessions */
#define LUO_SESSION_PGCNT 16ul
#define LUO_SESSION_MAX (((LUO_SESSION_PGCNT << PAGE_SHIFT) - \
sizeof(struct luo_session_header_ser)) / \
@@ -99,16 +100,22 @@ struct luo_session_header {
* struct luo_session_global - Global container for managing LUO sessions.
* @incoming: The sessions passed from the previous kernel.
* @outgoing: The sessions that are going to be passed to the next kernel.
- * @deserialized: The sessions have been deserialized once /dev/liveupdate
- * has been opened.
*/
struct luo_session_global {
struct luo_session_header incoming;
struct luo_session_header outgoing;
- bool deserialized;
};
-static struct luo_session_global luo_session_global;
+static struct luo_session_global luo_session_global = {
+ .incoming = {
+ .list = LIST_HEAD_INIT(luo_session_global.incoming.list),
+ .rwsem = __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem),
+ },
+ .outgoing = {
+ .list = LIST_HEAD_INIT(luo_session_global.outgoing.list),
+ .rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem),
+ },
+};
static struct luo_session *luo_session_alloc(const char *name)
{
@@ -118,18 +125,17 @@ static struct luo_session *luo_session_a
return ERR_PTR(-ENOMEM);
strscpy(session->name, name, sizeof(session->name));
- INIT_LIST_HEAD(&session->files_list);
+ INIT_LIST_HEAD(&session->file_set.files_list);
+ luo_file_set_init(&session->file_set);
INIT_LIST_HEAD(&session->list);
mutex_init(&session->mutex);
- session->count = 0;
return session;
}
static void luo_session_free(struct luo_session *session)
{
- WARN_ON(session->count);
- WARN_ON(!list_empty(&session->files_list));
+ luo_file_set_destroy(&session->file_set);
mutex_destroy(&session->mutex);
kfree(session);
}
@@ -177,50 +183,48 @@ static void luo_session_remove(struct lu
static int luo_session_finish_one(struct luo_session *session)
{
guard(mutex)(&session->mutex);
- return luo_file_finish(session);
+ return luo_file_finish(&session->file_set);
}
-static void luo_session_unfreeze_one(struct luo_session *session)
+static void luo_session_unfreeze_one(struct luo_session *session,
+ struct luo_session_ser *ser)
{
guard(mutex)(&session->mutex);
- luo_file_unfreeze(session);
+ luo_file_unfreeze(&session->file_set, &ser->file_set_ser);
}
-static int luo_session_freeze_one(struct luo_session *session)
+static int luo_session_freeze_one(struct luo_session *session,
+ struct luo_session_ser *ser)
{
guard(mutex)(&session->mutex);
- return luo_file_freeze(session);
+ return luo_file_freeze(&session->file_set, &ser->file_set_ser);
}
static int luo_session_release(struct inode *inodep, struct file *filep)
{
struct luo_session *session = filep->private_data;
struct luo_session_header *sh;
- int err = 0;
/* If retrieved is set, it means this session is from incoming list */
if (session->retrieved) {
- sh = &luo_session_global.incoming;
+ int err = luo_session_finish_one(session);
- err = luo_session_finish_one(session);
if (err) {
pr_warn("Unable to finish session [%s] on release\n",
session->name);
- } else {
- luo_session_remove(sh, session);
- luo_session_free(session);
+ return err;
}
-
+ sh = &luo_session_global.incoming;
} else {
- sh = &luo_session_global.outgoing;
-
scoped_guard(mutex, &session->mutex)
- luo_file_unpreserve_files(session);
- luo_session_remove(sh, session);
- luo_session_free(session);
+ luo_file_unpreserve_files(&session->file_set);
+ sh = &luo_session_global.outgoing;
}
- return err;
+ luo_session_remove(sh, session);
+ luo_session_free(session);
+
+ return 0;
}
static int luo_session_preserve_fd(struct luo_session *session,
@@ -230,7 +234,7 @@ static int luo_session_preserve_fd(struc
int err;
guard(mutex)(&session->mutex);
- err = luo_preserve_file(session, argp->token, argp->fd);
+ err = luo_preserve_file(&session->file_set, argp->token, argp->fd);
if (err)
return err;
@@ -253,7 +257,7 @@ static int luo_session_retrieve_fd(struc
return argp->fd;
guard(mutex)(&session->mutex);
- err = luo_retrieve_file(session, argp->token, &file);
+ err = luo_retrieve_file(&session->file_set, argp->token, &file);
if (err < 0)
goto err_put_fd;
@@ -365,7 +369,7 @@ static int luo_session_getfile(struct lu
char name_buf[128];
struct file *file;
- guard(mutex)(&session->mutex);
+ lockdep_assert_held(&session->mutex);
snprintf(name_buf, sizeof(name_buf), "[luo_session] %s", session->name);
file = anon_inode_getfile(name_buf, &luo_session_fops, session, O_RDWR);
if (IS_ERR(file))
@@ -389,7 +393,8 @@ int luo_session_create(const char *name,
if (err)
goto err_free;
- err = luo_session_getfile(session, filep);
+ scoped_guard(mutex, &session->mutex)
+ err = luo_session_getfile(session, filep);
if (err)
goto err_remove;
@@ -422,16 +427,13 @@ int luo_session_retrieve(const char *nam
if (!session)
return -ENOENT;
- scoped_guard(mutex, &session->mutex) {
- if (session->retrieved)
- return -EINVAL;
- }
+ guard(mutex)(&session->mutex);
+ if (session->retrieved)
+ return -EINVAL;
err = luo_session_getfile(session, filep);
- if (!err) {
- scoped_guard(mutex, &session->mutex)
- session->retrieved = true;
- }
+ if (!err)
+ session->retrieved = true;
return err;
}
@@ -439,12 +441,14 @@ int luo_session_retrieve(const char *nam
int __init luo_session_setup_outgoing(void *fdt_out)
{
struct luo_session_header_ser *header_ser;
+ void *outgoing_buffer;
u64 header_ser_pa;
int err;
- header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
- if (IS_ERR(header_ser))
+ outgoing_buffer = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
+ if (IS_ERR(outgoing_buffer))
return PTR_ERR(header_ser);
+ header_ser = outgoing_buffer;
header_ser_pa = virt_to_phys(header_ser);
err = fdt_begin_node(fdt_out, LUO_FDT_SESSION_NODE_NAME);
@@ -457,9 +461,6 @@ int __init luo_session_setup_outgoing(vo
if (err)
goto err_unpreserve;
- header_ser->pgcnt = LUO_SESSION_PGCNT;
- INIT_LIST_HEAD(&luo_session_global.outgoing.list);
- init_rwsem(&luo_session_global.outgoing.rwsem);
luo_session_global.outgoing.header_ser = header_ser;
luo_session_global.outgoing.ser = (void *)(header_ser + 1);
luo_session_global.outgoing.active = true;
@@ -506,33 +507,40 @@ int __init luo_session_setup_incoming(vo
luo_session_global.incoming.header_ser = header_ser;
luo_session_global.incoming.ser = (void *)(header_ser + 1);
- INIT_LIST_HEAD(&luo_session_global.incoming.list);
- init_rwsem(&luo_session_global.incoming.rwsem);
luo_session_global.incoming.active = true;
return 0;
}
-bool luo_session_is_deserialized(void)
-{
- return luo_session_global.deserialized;
-}
-
int luo_session_deserialize(void)
{
struct luo_session_header *sh = &luo_session_global.incoming;
- int err;
+ static bool is_deserialized;
+ static int err;
- if (luo_session_is_deserialized())
- return 0;
+ /* If has been deserialized, always return the same error code */
+ if (is_deserialized)
+ return err;
- luo_session_global.deserialized = true;
- if (!sh->active) {
- INIT_LIST_HEAD(&sh->list);
- init_rwsem(&sh->rwsem);
+ is_deserialized = true;
+ if (!sh->active)
return 0;
- }
+ /*
+ * Note on error handling:
+ *
+ * If deserialization fails (e.g., allocation failure or corrupt data),
+ * we intentionally skip cleanup of sessions that were already restored.
+ *
+ * A partial failure leaves the preserved state inconsistent.
+ * Implementing a safe "undo" to unwind complex dependencies (sessions,
+ * files, hardware state) is error-prone and provides little value, as
+ * the system is effectively in a broken state.
+ *
+ * We treat these resources as leaked. The expected recovery path is for
+ * userspace to detect the failure and trigger a reboot, which will
+ * reliably reset devices and reclaim memory.
+ */
for (int i = 0; i < sh->header_ser->count; i++) {
struct luo_session *session;
@@ -551,11 +559,10 @@ int luo_session_deserialize(void)
return err;
}
- session->count = sh->ser[i].count;
- session->files = sh->ser[i].files ? phys_to_virt(sh->ser[i].files) : 0;
- session->pgcnt = sh->ser[i].pgcnt;
- scoped_guard(mutex, &session->mutex)
- luo_file_deserialize(session);
+ scoped_guard(mutex, &session->mutex) {
+ luo_file_deserialize(&session->file_set,
+ &sh->ser[i].file_set_ser);
+ }
}
kho_restore_free(sh->header_ser);
@@ -574,15 +581,12 @@ int luo_session_serialize(void)
guard(rwsem_write)(&sh->rwsem);
list_for_each_entry(session, &sh->list, list) {
- err = luo_session_freeze_one(session);
+ err = luo_session_freeze_one(session, &sh->ser[i]);
if (err)
goto err_undo;
strscpy(sh->ser[i].name, session->name,
sizeof(sh->ser[i].name));
- sh->ser[i].count = session->count;
- sh->ser[i].files = session->files ? virt_to_phys(session->files) : 0;
- sh->ser[i].pgcnt = session->pgcnt;
i++;
}
sh->header_ser->count = sh->count;
@@ -591,10 +595,51 @@ int luo_session_serialize(void)
err_undo:
list_for_each_entry_continue_reverse(session, &sh->list, list) {
- luo_session_unfreeze_one(session);
i--;
- memset(&sh->ser[i], 0, sizeof(sh->ser[i]));
+ luo_session_unfreeze_one(session, &sh->ser[i]);
+ memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name));
}
return err;
}
+
+/**
+ * luo_session_quiesce - Ensure no active sessions exist and lock session lists.
+ *
+ * Acquires exclusive write locks on both incoming and outgoing session lists.
+ * It then validates no sessions exist in either list.
+ *
+ * This mechanism is used during file handler un/registration to ensure that no
+ * sessions are currently using the handler, and no new sessions can be created
+ * while un/registration is in progress.
+ *
+ * Return:
+ * true - System is quiescent (0 sessions) and locked.
+ * false - Active sessions exist. The locks are released internally.
+ */
+bool luo_session_quiesce(void)
+{
+ down_write(&luo_session_global.incoming.rwsem);
+ down_write(&luo_session_global.outgoing.rwsem);
+
+ if (luo_session_global.incoming.count ||
+ luo_session_global.outgoing.count) {
+ up_write(&luo_session_global.outgoing.rwsem);
+ up_write(&luo_session_global.incoming.rwsem);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * luo_session_resume - Unlock session lists and resume normal activity.
+ *
+ * Releases the exclusive locks acquired by a successful call to
+ * luo_session_quiesce().
+ */
+void luo_session_resume(void)
+{
+ up_write(&luo_session_global.outgoing.rwsem);
+ up_write(&luo_session_global.incoming.rwsem);
+}
--- a/kernel/liveupdate/Makefile~b
+++ a/kernel/liveupdate/Makefile
@@ -4,7 +4,6 @@ luo-y := \
luo_core.o \
luo_file.o \
luo_flb.o \
- luo_ioctl.o \
luo_session.o
obj-$(CONFIG_KEXEC_HANDOVER) += kexec_handover.o
--- a/lib/tests/liveupdate.c~b
+++ a/lib/tests/liveupdate.c
@@ -100,8 +100,6 @@ static void liveupdate_test_init(void)
void *obj;
int err;
- liveupdate_init_flb(flb);
-
err = liveupdate_flb_incoming_locked(flb, &obj);
if (!err) {
liveupdate_flb_incoming_unlock(flb, obj);
@@ -113,7 +111,7 @@ static void liveupdate_test_init(void)
initialized = true;
}
-void liveupdate_test_register(struct liveupdate_file_handler *h)
+void liveupdate_test_register(struct liveupdate_file_handler *fh)
{
int err, i;
@@ -122,20 +120,39 @@ void liveupdate_test_register(struct liv
for (i = 0; i < TEST_NFLBS; i++) {
struct liveupdate_flb *flb = &test_flbs[i];
- err = liveupdate_register_flb(h, flb);
- if (err)
+ err = liveupdate_register_flb(fh, flb);
+ if (err) {
pr_err("Failed to register %s %pe\n",
flb->compatible, ERR_PTR(err));
+ }
}
- err = liveupdate_register_flb(h, &test_flbs[0]);
+ err = liveupdate_register_flb(fh, &test_flbs[0]);
if (!err || err != -EEXIST) {
pr_err("Failed: %s should be already registered, but got err: %pe\n",
test_flbs[0].compatible, ERR_PTR(err));
}
pr_info("Registered %d FLBs with file handler: [%s]\n",
- TEST_NFLBS, h->compatible);
+ TEST_NFLBS, fh->compatible);
+}
+
+void liveupdate_test_unregister(struct liveupdate_file_handler *fh)
+{
+ int err, i;
+
+ for (i = 0; i < TEST_NFLBS; i++) {
+ struct liveupdate_flb *flb = &test_flbs[i];
+
+ err = liveupdate_unregister_flb(fh, flb);
+ if (err) {
+ pr_err("Failed to unregister %s %pe\n",
+ flb->compatible, ERR_PTR(err));
+ }
+ }
+
+ pr_info("Unregistered %d FLBs from file handler: [%s]\n",
+ TEST_NFLBS, fh->compatible);
}
MODULE_LICENSE("GPL");
--- a/MAINTAINERS~b
+++ a/MAINTAINERS
@@ -14472,6 +14472,7 @@ F: tools/testing/selftests/livepatch/
LIVE UPDATE
M: Pasha Tatashin <pasha.tatashin@soleen.com>
+M: Mike Rapoport <rppt@kernel.org>
R: Pratyush Yadav <pratyush@kernel.org>
L: linux-kernel@vger.kernel.org
S: Maintained
--- a/mm/memfd_luo.c~b
+++ a/mm/memfd_luo.c
@@ -74,33 +74,19 @@
#include <linux/file.h>
#include <linux/io.h>
#include <linux/kexec_handover.h>
-#include <linux/libfdt.h>
+#include <linux/kho/abi/memfd.h>
#include <linux/liveupdate.h>
-#include <linux/liveupdate/abi/memfd.h>
#include <linux/shmem_fs.h>
#include <linux/vmalloc.h>
#include "internal.h"
-#define PRESERVED_PFN_MASK GENMASK(63, 12)
-#define PRESERVED_PFN_SHIFT 12
-#define PRESERVED_FLAG_DIRTY BIT(0)
-#define PRESERVED_FLAG_UPTODATE BIT(1)
-
-#define PRESERVED_FOLIO_PFN(desc) (((desc) & PRESERVED_PFN_MASK) >> PRESERVED_PFN_SHIFT)
-#define PRESERVED_FOLIO_FLAGS(desc) ((desc) & ~PRESERVED_PFN_MASK)
-#define PRESERVED_FOLIO_MKDESC(pfn, flags) (((pfn) << PRESERVED_PFN_SHIFT) | (flags))
-
-struct memfd_luo_private {
- struct memfd_luo_folio_ser *pfolios;
- u64 nr_folios;
-};
-
-static struct memfd_luo_folio_ser *memfd_luo_preserve_folios(struct file *file, void *fdt,
- u64 *nr_foliosp)
+static int memfd_luo_preserve_folios(struct file *file,
+ struct kho_vmalloc *kho_vmalloc,
+ struct memfd_luo_folio_ser **out_folios_ser,
+ u64 *nr_foliosp)
{
struct inode *inode = file_inode(file);
- struct memfd_luo_folio_ser *pfolios;
- struct kho_vmalloc *kho_vmalloc;
+ struct memfd_luo_folio_ser *folios_ser;
unsigned int max_folios;
long i, size, nr_pinned;
struct folio **folios;
@@ -115,7 +101,9 @@ static struct memfd_luo_folio_ser *memfd
*/
if (!size) {
*nr_foliosp = 0;
- return NULL;
+ *out_folios_ser = NULL;
+ memset(kho_vmalloc, 0, sizeof(*kho_vmalloc));
+ return 0;
}
/*
@@ -125,7 +113,7 @@ static struct memfd_luo_folio_ser *memfd
max_folios = PAGE_ALIGN(size) / PAGE_SIZE;
folios = kvmalloc_array(max_folios, sizeof(*folios), GFP_KERNEL);
if (!folios)
- return ERR_PTR(-ENOMEM);
+ return -ENOMEM;
/*
* Pin the folios so they don't move around behind our back. This also
@@ -148,294 +136,179 @@ static struct memfd_luo_folio_ser *memfd
}
nr_folios = nr_pinned;
- err = fdt_property(fdt, MEMFD_FDT_NR_FOLIOS, &nr_folios, sizeof(nr_folios));
- if (err)
- goto err_unpin;
-
- err = fdt_property_placeholder(fdt, MEMFD_FDT_FOLIOS, sizeof(*kho_vmalloc),
- (void **)&kho_vmalloc);
- if (err) {
- pr_err("Failed to reserve '%s' property in FDT: %s\n",
- MEMFD_FDT_FOLIOS, fdt_strerror(err));
- err = -ENOMEM;
- goto err_unpin;
- }
-
- pfolios = vcalloc(nr_folios, sizeof(*pfolios));
- if (!pfolios) {
+ folios_ser = vcalloc(nr_folios, sizeof(*folios_ser));
+ if (!folios_ser) {
err = -ENOMEM;
goto err_unpin;
}
for (i = 0; i < nr_folios; i++) {
- struct memfd_luo_folio_ser *pfolio = &pfolios[i];
+ struct memfd_luo_folio_ser *pfolio = &folios_ser[i];
struct folio *folio = folios[i];
unsigned int flags = 0;
- unsigned long pfn;
err = kho_preserve_folio(folio);
if (err)
goto err_unpreserve;
- pfn = folio_pfn(folio);
if (folio_test_dirty(folio))
- flags |= PRESERVED_FLAG_DIRTY;
+ flags |= MEMFD_LUO_FOLIO_DIRTY;
if (folio_test_uptodate(folio))
- flags |= PRESERVED_FLAG_UPTODATE;
+ flags |= MEMFD_LUO_FOLIO_UPTODATE;
- pfolio->foliodesc = PRESERVED_FOLIO_MKDESC(pfn, flags);
+ pfolio->pfn = folio_pfn(folio);
+ pfolio->flags = flags;
pfolio->index = folio->index;
}
- err = kho_preserve_vmalloc(pfolios, kho_vmalloc);
+ err = kho_preserve_vmalloc(folios_ser, kho_vmalloc);
if (err)
goto err_unpreserve;
kvfree(folios);
*nr_foliosp = nr_folios;
- return pfolios;
+ *out_folios_ser = folios_ser;
+
+ /*
+ * Note: folios_ser is purposely not freed here. It is preserved
+ * memory (via KHO). In the 'unpreserve' path, we use the vmap pointer
+ * that is passed via private_data.
+ */
+ return 0;
err_unpreserve:
- i--;
- for (; i >= 0; i--)
+ for (i = i - 1; i >= 0; i--)
kho_unpreserve_folio(folios[i]);
- vfree(pfolios);
+ vfree(folios_ser);
err_unpin:
unpin_folios(folios, nr_folios);
err_free_folios:
kvfree(folios);
- return ERR_PTR(err);
+
+ return err;
}
-static void memfd_luo_unpreserve_folios(void *fdt, struct memfd_luo_folio_ser *pfolios,
+static void memfd_luo_unpreserve_folios(struct kho_vmalloc *kho_vmalloc,
+ struct memfd_luo_folio_ser *folios_ser,
u64 nr_folios)
{
- struct kho_vmalloc *kho_vmalloc;
long i;
if (!nr_folios)
return;
- kho_vmalloc = (struct kho_vmalloc *)fdt_getprop(fdt, 0, MEMFD_FDT_FOLIOS, NULL);
- /* The FDT was created by this kernel so expect it to be sane. */
- WARN_ON_ONCE(!kho_vmalloc);
kho_unpreserve_vmalloc(kho_vmalloc);
for (i = 0; i < nr_folios; i++) {
- const struct memfd_luo_folio_ser *pfolio = &pfolios[i];
+ const struct memfd_luo_folio_ser *pfolio = &folios_ser[i];
struct folio *folio;
- if (!pfolio->foliodesc)
+ if (!pfolio->pfn)
continue;
- folio = pfn_folio(PRESERVED_FOLIO_PFN(pfolio->foliodesc));
+ folio = pfn_folio(pfolio->pfn);
kho_unpreserve_folio(folio);
unpin_folio(folio);
}
- vfree(pfolios);
-}
-
-static struct memfd_luo_folio_ser *memfd_luo_fdt_folios(const void *fdt, u64 *nr_folios)
-{
- const struct kho_vmalloc *kho_vmalloc;
- struct memfd_luo_folio_ser *pfolios;
- const u64 *nr;
- int len;
-
- nr = fdt_getprop(fdt, 0, MEMFD_FDT_NR_FOLIOS, &len);
- if (!nr || len != sizeof(*nr)) {
- pr_err("invalid '%s' property\n", MEMFD_FDT_NR_FOLIOS);
- return NULL;
- }
-
- kho_vmalloc = fdt_getprop(fdt, 0, MEMFD_FDT_FOLIOS, &len);
- if (!kho_vmalloc || len != sizeof(*kho_vmalloc)) {
- pr_err("invalid '%s' property\n", MEMFD_FDT_FOLIOS);
- return NULL;
- }
-
- pfolios = kho_restore_vmalloc(kho_vmalloc);
- if (!pfolios)
- return NULL;
-
- *nr_folios = *nr;
- return pfolios;
-}
-
-static void *memfd_luo_create_fdt(void)
-{
- struct folio *fdt_folio;
- int err = 0;
- void *fdt;
-
- /*
- * The FDT only contains a couple of properties and a kho_vmalloc
- * object. One page should be enough for that.
- */
- fdt_folio = folio_alloc(GFP_KERNEL | __GFP_ZERO, 0);
- if (!fdt_folio)
- return NULL;
-
- fdt = folio_address(fdt_folio);
-
- err |= fdt_create(fdt, folio_size(fdt_folio));
- err |= fdt_finish_reservemap(fdt);
- err |= fdt_begin_node(fdt, "");
- if (err)
- goto free;
-
- return fdt;
-
-free:
- folio_put(fdt_folio);
- return NULL;
-}
-
-static int memfd_luo_finish_fdt(void *fdt)
-{
- int err;
-
- err = fdt_end_node(fdt);
- if (err)
- return err;
-
- return fdt_finish(fdt);
+ vfree(folios_ser);
}
static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
{
struct inode *inode = file_inode(args->file);
- struct memfd_luo_folio_ser *pfolios;
- struct memfd_luo_private *private;
- u64 pos, nr_folios;
+ struct memfd_luo_folio_ser *folios_ser;
+ struct memfd_luo_ser *ser;
+ u64 nr_folios;
int err = 0;
- void *fdt;
- long size;
-
- private = kmalloc(sizeof(*private), GFP_KERNEL);
- if (!private)
- return -ENOMEM;
inode_lock(inode);
- shmem_i_mapping_freeze(inode, true);
+ shmem_freeze(inode, true);
- size = i_size_read(inode);
-
- fdt = memfd_luo_create_fdt();
- if (!fdt) {
- err = -ENOMEM;
+ /* Allocate the main serialization structure in preserved memory */
+ ser = kho_alloc_preserve(sizeof(*ser));
+ if (IS_ERR(ser)) {
+ err = PTR_ERR(ser);
goto err_unlock;
}
- pos = args->file->f_pos;
- err = fdt_property(fdt, MEMFD_FDT_POS, &pos, sizeof(pos));
- if (err)
- goto err_free_fdt;
-
- err = fdt_property(fdt, MEMFD_FDT_SIZE, &size, sizeof(size));
- if (err)
- goto err_free_fdt;
-
- pfolios = memfd_luo_preserve_folios(args->file, fdt, &nr_folios);
- if (IS_ERR(pfolios)) {
- err = PTR_ERR(pfolios);
- goto err_free_fdt;
- }
-
- err = memfd_luo_finish_fdt(fdt);
- if (err)
- goto err_unpreserve_folios;
+ ser->pos = args->file->f_pos;
+ ser->size = i_size_read(inode);
- err = kho_preserve_folio(virt_to_folio(fdt));
+ err = memfd_luo_preserve_folios(args->file, &ser->folios,
+ &folios_ser, &nr_folios);
if (err)
- goto err_unpreserve_folios;
+ goto err_free_ser;
+ ser->nr_folios = nr_folios;
inode_unlock(inode);
- private->pfolios = pfolios;
- private->nr_folios = nr_folios;
- args->private_data = private;
- args->serialized_data = virt_to_phys(fdt);
+ args->private_data = folios_ser;
+ args->serialized_data = virt_to_phys(ser);
+
return 0;
-err_unpreserve_folios:
- memfd_luo_unpreserve_folios(fdt, pfolios, nr_folios);
-err_free_fdt:
- folio_put(virt_to_folio(fdt));
+err_free_ser:
+ kho_unpreserve_free(ser);
err_unlock:
- shmem_i_mapping_freeze(inode, false);
+ shmem_freeze(inode, false);
inode_unlock(inode);
- kfree(private);
return err;
}
static int memfd_luo_freeze(struct liveupdate_file_op_args *args)
{
- u64 pos = args->file->f_pos;
- void *fdt;
- int err;
+ struct memfd_luo_ser *ser;
if (WARN_ON_ONCE(!args->serialized_data))
return -EINVAL;
- fdt = phys_to_virt(args->serialized_data);
+ ser = phys_to_virt(args->serialized_data);
/*
* The pos might have changed since prepare. Everything else stays the
* same.
*/
- err = fdt_setprop(fdt, 0, "pos", &pos, sizeof(pos));
- if (err)
- return err;
+ ser->pos = args->file->f_pos;
return 0;
}
static void memfd_luo_unpreserve(struct liveupdate_file_op_args *args)
{
- struct memfd_luo_private *private = args->private_data;
struct inode *inode = file_inode(args->file);
- struct folio *fdt_folio;
- void *fdt;
+ struct memfd_luo_ser *ser;
- if (WARN_ON_ONCE(!args->serialized_data || !args->private_data))
+ if (WARN_ON_ONCE(!args->serialized_data))
return;
inode_lock(inode);
- shmem_i_mapping_freeze(inode, false);
+ shmem_freeze(inode, false);
- fdt = phys_to_virt(args->serialized_data);
- fdt_folio = virt_to_folio(fdt);
+ ser = phys_to_virt(args->serialized_data);
- memfd_luo_unpreserve_folios(fdt, private->pfolios, private->nr_folios);
+ memfd_luo_unpreserve_folios(&ser->folios, args->private_data,
+ ser->nr_folios);
- kho_unpreserve_folio(fdt_folio);
- folio_put(fdt_folio);
+ kho_unpreserve_free(ser);
inode_unlock(inode);
- kfree(private);
-}
-
-static struct folio *memfd_luo_get_fdt(u64 data)
-{
- return kho_restore_folio((phys_addr_t)data);
}
-static void memfd_luo_discard_folios(const struct memfd_luo_folio_ser *pfolios,
- long nr_folios)
+static void memfd_luo_discard_folios(const struct memfd_luo_folio_ser *folios_ser,
+ u64 nr_folios)
{
- unsigned int i;
+ u64 i;
for (i = 0; i < nr_folios; i++) {
- const struct memfd_luo_folio_ser *pfolio = &pfolios[i];
+ const struct memfd_luo_folio_ser *pfolio = &folios_ser[i];
struct folio *folio;
phys_addr_t phys;
- if (!pfolio->foliodesc)
+ if (!pfolio->pfn)
continue;
- phys = PFN_PHYS(PRESERVED_FOLIO_PFN(pfolio->foliodesc));
+ phys = PFN_PHYS(pfolio->pfn);
folio = kho_restore_folio(phys);
if (!folio) {
pr_warn_ratelimited("Unable to restore folio at physical address: %llx\n",
@@ -449,66 +322,49 @@ static void memfd_luo_discard_folios(con
static void memfd_luo_finish(struct liveupdate_file_op_args *args)
{
- const struct memfd_luo_folio_ser *pfolios;
- struct folio *fdt_folio;
- const void *fdt;
- u64 nr_folios;
+ struct memfd_luo_folio_ser *folios_ser;
+ struct memfd_luo_ser *ser;
if (args->retrieved)
return;
- fdt_folio = memfd_luo_get_fdt(args->serialized_data);
- if (!fdt_folio) {
- pr_err("failed to restore memfd FDT\n");
+ ser = phys_to_virt(args->serialized_data);
+ if (!ser)
return;
- }
- fdt = folio_address(fdt_folio);
+ if (ser->nr_folios) {
+ folios_ser = kho_restore_vmalloc(&ser->folios);
+ if (!folios_ser)
+ goto out;
- pfolios = memfd_luo_fdt_folios(fdt, &nr_folios);
- if (!pfolios)
- goto out;
-
- memfd_luo_discard_folios(pfolios, nr_folios);
- vfree(pfolios);
+ memfd_luo_discard_folios(folios_ser, ser->nr_folios);
+ vfree(folios_ser);
+ }
out:
- folio_put(fdt_folio);
+ kho_restore_free(ser);
}
-static int memfd_luo_retrieve_folios(struct file *file, const void *fdt)
+static int memfd_luo_retrieve_folios(struct file *file,
+ struct memfd_luo_folio_ser *folios_ser,
+ u64 nr_folios)
{
- const struct memfd_luo_folio_ser *pfolios;
struct inode *inode = file_inode(file);
- struct address_space *mapping;
+ struct address_space *mapping = inode->i_mapping;
struct folio *folio;
- u64 nr_folios;
long i = 0;
int err;
- /* Careful: folios don't exist in FDT on zero-size files. */
- if (!inode->i_size)
- return 0;
-
- pfolios = memfd_luo_fdt_folios(fdt, &nr_folios);
- if (!pfolios) {
- pr_err("failed to fetch preserved folio list\n");
- return -EINVAL;
- }
-
- inode = file->f_inode;
- mapping = inode->i_mapping;
-
for (; i < nr_folios; i++) {
- const struct memfd_luo_folio_ser *pfolio = &pfolios[i];
+ const struct memfd_luo_folio_ser *pfolio = &folios_ser[i];
phys_addr_t phys;
u64 index;
int flags;
- if (!pfolio->foliodesc)
+ if (!pfolio->pfn)
continue;
- phys = PFN_PHYS(PRESERVED_FOLIO_PFN(pfolio->foliodesc));
+ phys = PFN_PHYS(pfolio->pfn);
folio = kho_restore_folio(phys);
if (!folio) {
pr_err("Unable to restore folio at physical address: %llx\n",
@@ -516,7 +372,7 @@ static int memfd_luo_retrieve_folios(str
goto put_folios;
}
index = pfolio->index;
- flags = PRESERVED_FOLIO_FLAGS(pfolio->foliodesc);
+ flags = pfolio->flags;
/* Set up the folio for insertion. */
__folio_set_locked(folio);
@@ -537,9 +393,9 @@ static int memfd_luo_retrieve_folios(str
goto unlock_folio;
}
- if (flags & PRESERVED_FLAG_UPTODATE)
+ if (flags & MEMFD_LUO_FOLIO_UPTODATE)
folio_mark_uptodate(folio);
- if (flags & PRESERVED_FLAG_DIRTY)
+ if (flags & MEMFD_LUO_FOLIO_DIRTY)
folio_mark_dirty(folio);
err = shmem_inode_acct_blocks(inode, 1);
@@ -555,7 +411,6 @@ static int memfd_luo_retrieve_folios(str
folio_put(folio);
}
- vfree(pfolios);
return 0;
unlock_folio:
@@ -568,69 +423,59 @@ put_folios:
* freed when the file is freed. Free the ones not added yet here.
*/
for (; i < nr_folios; i++) {
- const struct memfd_luo_folio_ser *pfolio = &pfolios[i];
+ const struct memfd_luo_folio_ser *pfolio = &folios_ser[i];
- folio = kho_restore_folio(PRESERVED_FOLIO_PFN(pfolio->foliodesc));
+ folio = kho_restore_folio(pfolio->pfn);
if (folio)
folio_put(folio);
}
- vfree(pfolios);
return err;
}
static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
{
- struct folio *fdt_folio;
- const u64 *pos, *size;
+ struct memfd_luo_folio_ser *folios_ser;
+ struct memfd_luo_ser *ser;
struct file *file;
- int len, ret = 0;
- const void *fdt;
+ int err;
- fdt_folio = memfd_luo_get_fdt(args->serialized_data);
- if (!fdt_folio)
- return -ENOENT;
-
- fdt = page_to_virt(folio_page(fdt_folio, 0));
-
- size = fdt_getprop(fdt, 0, "size", &len);
- if (!size || len != sizeof(u64)) {
- pr_err("invalid 'size' property\n");
- ret = -EINVAL;
- goto put_fdt;
- }
-
- pos = fdt_getprop(fdt, 0, "pos", &len);
- if (!pos || len != sizeof(u64)) {
- pr_err("invalid 'pos' property\n");
- ret = -EINVAL;
- goto put_fdt;
- }
+ ser = phys_to_virt(args->serialized_data);
+ if (!ser)
+ return -EINVAL;
file = shmem_file_setup("", 0, VM_NORESERVE);
if (IS_ERR(file)) {
- ret = PTR_ERR(file);
- pr_err("failed to setup file: %d\n", ret);
- goto put_fdt;
+ pr_err("failed to setup file: %pe\n", file);
+ return PTR_ERR(file);
}
- vfs_setpos(file, *pos, MAX_LFS_FILESIZE);
- file->f_inode->i_size = *size;
+ vfs_setpos(file, ser->pos, MAX_LFS_FILESIZE);
+ file->f_inode->i_size = ser->size;
- ret = memfd_luo_retrieve_folios(file, fdt);
- if (ret)
- goto put_file;
+ if (ser->nr_folios) {
+ folios_ser = kho_restore_vmalloc(&ser->folios);
+ if (!folios_ser) {
+ err = -EINVAL;
+ goto put_file;
+ }
+
+ err = memfd_luo_retrieve_folios(file, folios_ser, ser->nr_folios);
+ vfree(folios_ser);
+ if (err)
+ goto put_file;
+ }
args->file = file;
- folio_put(fdt_folio);
+ kho_restore_free(ser);
+
return 0;
put_file:
fput(file);
-put_fdt:
- folio_put(fdt_folio);
- return ret;
+
+ return err;
}
static bool memfd_luo_can_preserve(struct liveupdate_file_handler *handler,
@@ -661,7 +506,8 @@ static int __init memfd_luo_init(void)
int err = liveupdate_register_file_handler(&memfd_luo_handler);
if (err && err != -EOPNOTSUPP) {
- pr_err("Could not register luo filesystem handler: %pe\n", ERR_PTR(err));
+ pr_err("Could not register luo filesystem handler: %pe\n",
+ ERR_PTR(err));
return err;
}
--- a/mm/shmem.c~b
+++ a/mm/shmem.c
@@ -1310,10 +1310,13 @@ static int shmem_setattr(struct mnt_idma
loff_t newsize = attr->ia_size;
/* protected by i_rwsem */
- if ((info->flags & SHMEM_F_MAPPING_FROZEN) ||
- (newsize < oldsize && (info->seals & F_SEAL_SHRINK)) ||
- (newsize > oldsize && (info->seals & F_SEAL_GROW)))
- return -EPERM;
+ if (newsize != oldsize) {
+ if (info->flags & SHMEM_F_MAPPING_FROZEN)
+ return -EPERM;
+ if ((newsize < oldsize && (info->seals & F_SEAL_SHRINK)) ||
+ (newsize > oldsize && (info->seals & F_SEAL_GROW)))
+ return -EPERM;
+ }
if (newsize != oldsize) {
error = shmem_reacct_size(SHMEM_I(inode)->flags,
--- a/tools/testing/selftests/liveupdate/config~b
+++ a/tools/testing/selftests/liveupdate/config
@@ -1,5 +1,11 @@
+CONFIG_BLK_DEV_INITRD=y
CONFIG_KEXEC_FILE=y
CONFIG_KEXEC_HANDOVER=y
+CONFIG_KEXEC_HANDOVER_ENABLE_DEFAULT=y
CONFIG_KEXEC_HANDOVER_DEBUGFS=y
CONFIG_KEXEC_HANDOVER_DEBUG=y
CONFIG_LIVEUPDATE=y
+CONFIG_LIVEUPDATE_TEST=y
+CONFIG_MEMFD_CREATE=y
+CONFIG_TMPFS=y
+CONFIG_SHMEM=y
--- a/tools/testing/selftests/liveupdate/.gitignore~b
+++ a/tools/testing/selftests/liveupdate/.gitignore
@@ -1,3 +1,9 @@
-/liveupdate
-/luo_kexec_simple
-/luo_multi_session
+# SPDX-License-Identifier: GPL-2.0-only
+*
+!/**/
+!*.c
+!*.h
+!*.sh
+!.gitignore
+!config
+!Makefile
diff --git a/tools/testing/selftests/liveupdate/init.c a/tools/testing/selftests/liveupdate/init.c
new file mode 100644
--- /dev/null
+++ a/tools/testing/selftests/liveupdate/init.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2025, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ */
+#include <fcntl.h>
+#include <linux/kexec.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/reboot.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define COMMAND_LINE_SIZE 2048
+#define KERNEL_IMAGE "/kernel"
+#define INITRD_IMAGE "/initrd.img"
+#define TEST_BINARY "/test_binary"
+
+static int mount_filesystems(void)
+{
+ if (mount("devtmpfs", "/dev", "devtmpfs", 0, NULL) < 0) {
+ fprintf(stderr, "INIT: Warning: Failed to mount devtmpfs\n");
+ return -1;
+ }
+
+ if (mount("debugfs", "/debugfs", "debugfs", 0, NULL) < 0) {
+ fprintf(stderr, "INIT: Failed to mount debugfs\n");
+ return -1;
+ }
+
+ if (mount("proc", "/proc", "proc", 0, NULL) < 0) {
+ fprintf(stderr, "INIT: Failed to mount proc\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static long kexec_file_load(int kernel_fd, int initrd_fd,
+ unsigned long cmdline_len, const char *cmdline,
+ unsigned long flags)
+{
+ return syscall(__NR_kexec_file_load, kernel_fd, initrd_fd, cmdline_len,
+ cmdline, flags);
+}
+
+static int kexec_load(void)
+{
+ char cmdline[COMMAND_LINE_SIZE];
+ int kernel_fd, initrd_fd, err;
+ ssize_t len;
+ int fd;
+
+ fd = open("/proc/cmdline", O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "INIT: Failed to read /proc/cmdline\n");
+
+ return -1;
+ }
+
+ len = read(fd, cmdline, sizeof(cmdline) - 1);
+ close(fd);
+ if (len < 0)
+ return -1;
+
+ cmdline[len] = 0;
+ if (len > 0 && cmdline[len - 1] == '\n')
+ cmdline[len - 1] = 0;
+
+ strncat(cmdline, " luo_stage=2", sizeof(cmdline) - strlen(cmdline) - 1);
+
+ kernel_fd = open(KERNEL_IMAGE, O_RDONLY);
+ if (kernel_fd < 0) {
+ fprintf(stderr, "INIT: Failed to open kernel image\n");
+ return -1;
+ }
+
+ initrd_fd = open(INITRD_IMAGE, O_RDONLY);
+ if (initrd_fd < 0) {
+ fprintf(stderr, "INIT: Failed to open initrd image\n");
+ close(kernel_fd);
+ return -1;
+ }
+
+ err = kexec_file_load(kernel_fd, initrd_fd, strlen(cmdline) + 1,
+ cmdline, 0);
+
+ close(initrd_fd);
+ close(kernel_fd);
+
+ return err ? : 0;
+}
+
+static int run_test(int stage)
+{
+ char stage_arg[32];
+ int status;
+ pid_t pid;
+
+ snprintf(stage_arg, sizeof(stage_arg), "--stage=%d", stage);
+
+ pid = fork();
+ if (pid < 0)
+ return -1;
+
+ if (!pid) {
+ static const char *const argv[] = {TEST_BINARY, stage_arg, 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;
+}
+
+static int is_stage_2(void)
+{
+ char cmdline[COMMAND_LINE_SIZE];
+ ssize_t len;
+ int fd;
+
+ fd = open("/proc/cmdline", O_RDONLY);
+ if (fd < 0)
+ return 0;
+
+ len = read(fd, cmdline, sizeof(cmdline) - 1);
+ close(fd);
+
+ if (len < 0)
+ return 0;
+
+ cmdline[len] = 0;
+
+ return !!strstr(cmdline, "luo_stage=2");
+}
+
+int main(int argc, char *argv[])
+{
+ int current_stage;
+
+ if (mount_filesystems())
+ goto err_reboot;
+
+ current_stage = is_stage_2() ? 2 : 1;
+
+ printf("INIT: Starting Stage %d\n", current_stage);
+
+ if (current_stage == 1 && kexec_load()) {
+ fprintf(stderr, "INIT: Failed to load kexec kernel\n");
+ goto err_reboot;
+ }
+
+ if (run_test(current_stage)) {
+ fprintf(stderr, "INIT: Test binary returned failure\n");
+ goto err_reboot;
+ }
+
+ printf("INIT: Stage %d completed successfully.\n", current_stage);
+ reboot(current_stage == 1 ? RB_KEXEC : RB_AUTOBOOT);
+
+ return 0;
+
+err_reboot:
+ reboot(RB_AUTOBOOT);
+
+ return -1;
+}
--- a/tools/testing/selftests/liveupdate/luo_kexec_simple.c~b
+++ a/tools/testing/selftests/liveupdate/luo_kexec_simple.c
@@ -10,8 +10,6 @@
#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"
@@ -42,10 +40,8 @@ static void run_stage_1(int luo_fd)
TEST_MEMFD_TOKEN);
}
- ksft_print_msg("[STAGE 1] Executing kexec...\n");
- if (system(KEXEC_SCRIPT) != 0)
- fail_exit("kexec script failed");
- exit(EXIT_FAILURE);
+ close(luo_fd);
+ daemonize_and_wait();
}
/* Stage 2: Executed after the kexec reboot. */
@@ -88,27 +84,6 @@ static void run_stage_2(int luo_fd, int
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);
+ return luo_test(argc, argv, STATE_SESSION_NAME,
+ run_stage_1, run_stage_2);
}
--- a/tools/testing/selftests/liveupdate/luo_multi_session.c~b
+++ a/tools/testing/selftests/liveupdate/luo_multi_session.c
@@ -11,8 +11,6 @@
#include "luo_test_utils.h"
-#define KEXEC_SCRIPT "./do_kexec.sh"
-
#define SESSION_EMPTY_1 "multi-test-empty-1"
#define SESSION_EMPTY_2 "multi-test-empty-2"
#define SESSION_FILES_1 "multi-test-files-1"
@@ -75,12 +73,8 @@ static void run_stage_1(int luo_fd)
MFD3_TOKEN);
}
- ksft_print_msg("[STAGE 1] Executing kexec...\n");
-
- if (system(KEXEC_SCRIPT) != 0)
- fail_exit("kexec script failed");
-
- exit(EXIT_FAILURE);
+ close(luo_fd);
+ daemonize_and_wait();
}
/* Stage 2: Executed after the kexec reboot. */
@@ -163,28 +157,6 @@ static void run_stage_2(int luo_fd, int
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);
- return 0;
+ return luo_test(argc, argv, STATE_SESSION_NAME,
+ run_stage_1, run_stage_2);
}
diff --git a/tools/testing/selftests/liveupdate/luo_test.sh a/tools/testing/selftests/liveupdate/luo_test.sh
new file mode 100755
--- /dev/null
+++ a/tools/testing/selftests/liveupdate/luo_test.sh
@@ -0,0 +1,296 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+set -ue
+
+CROSS_COMPILE="${CROSS_COMPILE:-""}"
+
+test_dir=$(realpath "$(dirname "$0")")
+kernel_dir=$(realpath "$test_dir/../../../..")
+
+workspace_dir=""
+headers_dir=""
+initrd=""
+KEEP_WORKSPACE=0
+
+source "$test_dir/../kselftest/ktap_helpers.sh"
+
+function get_arch_conf() {
+ local arch=$1
+ if [[ "$arch" == "arm64" ]]; then
+ QEMU_CMD="qemu-system-aarch64 -M virt -cpu max"
+ KERNEL_IMAGE="Image"
+ KERNEL_CMDLINE="console=ttyAMA0"
+ elif [[ "$arch" == "x86" ]]; then
+ QEMU_CMD="qemu-system-x86_64"
+ KERNEL_IMAGE="bzImage"
+ KERNEL_CMDLINE="console=ttyS0"
+ else
+ echo "Unsupported architecture: $arch"
+ exit 1
+ fi
+}
+
+function usage() {
+ cat <<EOF
+$0 [-d build_dir] [-j jobs] [-t target_arch] [-T test_name] [-w workspace_dir] [-k] [-h]
+Options:
+ -d) path to the kernel build directory (default: .luo_test_build.<arch>)
+ -j) number of jobs for compilation
+ -t) run test for target_arch (aarch64, x86_64)
+ -T) test name to run (default: luo_kexec_simple)
+ -w) custom workspace directory (default: creates temp dir)
+ -k) keep workspace directory after successful test
+ -h) display this help
+EOF
+}
+
+function cleanup() {
+ local exit_code=$?
+
+ if [ -z "$workspace_dir" ]; then
+ ktap_finished
+ return
+ fi
+
+ if [ $exit_code -ne 0 ]; then
+ echo "# Test failed (exit code $exit_code)."
+ echo "# Workspace preserved at: $workspace_dir"
+ elif [ "$KEEP_WORKSPACE" -eq 1 ]; then
+ echo "# Workspace preserved (user request) at: $workspace_dir"
+ else
+ rm -fr "$workspace_dir"
+ fi
+ ktap_finished
+}
+trap cleanup EXIT
+
+function skip() {
+ local msg=${1:-""}
+ ktap_test_skip "$msg"
+ exit "$KSFT_SKIP"
+}
+
+function fail() {
+ local msg=${1:-""}
+ ktap_test_fail "$msg"
+ exit "$KSFT_FAIL"
+}
+
+function detect_cross_compile() {
+ local target=$1
+ local host=$(uname -m)
+
+ if [ -n "$CROSS_COMPILE" ]; then
+ return
+ fi
+
+ [[ "$host" == "arm64" ]] && host="aarch64"
+ [[ "$target" == "arm64" ]] && target="aarch64"
+
+ if [[ "$host" == "$target" ]]; then
+ CROSS_COMPILE=""
+ return
+ fi
+
+ local candidate=""
+ case "$target" in
+ aarch64) candidate="aarch64-linux-gnu-" ;;
+ x86_64) candidate="x86_64-linux-gnu-" ;;
+ *) skip "Auto-detection for target '$target' not supported. Please set CROSS_COMPILE manually." ;;
+ esac
+
+ if command -v "${candidate}gcc" &> /dev/null; then
+ CROSS_COMPILE="$candidate"
+ else
+ skip "Compiler '${candidate}gcc' not found. Please install it (e.g., 'apt install gcc-aarch64-linux-gnu') or set CROSS_COMPILE."
+ fi
+}
+
+function build_kernel() {
+ local build_dir=$1
+ local make_cmd=$2
+ local kimage=$3
+ local target_arch=$4
+
+ local kconfig="$build_dir/.config"
+ local common_conf="$test_dir/config"
+ local arch_conf="$test_dir/config.$target_arch"
+
+ echo "# Building kernel in: $build_dir"
+ $make_cmd defconfig
+
+ local fragments=""
+ if [[ -f "$common_conf" ]]; then
+ fragments="$fragments $common_conf"
+ fi
+
+ if [[ -f "$arch_conf" ]]; then
+ fragments="$fragments $arch_conf"
+ fi
+
+ if [[ -n "$fragments" ]]; then
+ "$kernel_dir/scripts/kconfig/merge_config.sh" \
+ -Q -m -O "$build_dir" "$kconfig" $fragments >> /dev/null
+ fi
+
+ $make_cmd olddefconfig
+ $make_cmd "$kimage"
+ $make_cmd headers_install INSTALL_HDR_PATH="$headers_dir"
+}
+
+function mkinitrd() {
+ local build_dir=$1
+ local kernel_path=$2
+ local test_name=$3
+
+ # 1. Compile the test binary and the init process
+ "$CROSS_COMPILE"gcc -static -O2 \
+ -I "$headers_dir/include" \
+ -I "$test_dir" \
+ -o "$workspace_dir/test_binary" \
+ "$test_dir/$test_name.c" "$test_dir/luo_test_utils.c"
+
+ "$CROSS_COMPILE"gcc -s -static -Os -nostdinc -nostdlib \
+ -fno-asynchronous-unwind-tables -fno-ident \
+ -fno-stack-protector \
+ -I "$headers_dir/include" \
+ -I "$kernel_dir/tools/include/nolibc" \
+ -o "$workspace_dir/init" "$test_dir/init.c"
+
+ cat > "$workspace_dir/cpio_list_inner" <<EOF
+dir /dev 0755 0 0
+dir /proc 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
+file /test_binary $workspace_dir/test_binary 0755 0 0
+EOF
+
+ # Generate inner_initrd.cpio
+ "$build_dir/usr/gen_init_cpio" "$workspace_dir/cpio_list_inner" > "$workspace_dir/inner_initrd.cpio"
+
+ cat > "$workspace_dir/cpio_list" <<EOF
+dir /dev 0755 0 0
+dir /proc 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
+file /kernel $kernel_path 0644 0 0
+file /test_binary $workspace_dir/test_binary 0755 0 0
+file /initrd.img $workspace_dir/inner_initrd.cpio 0644 0 0
+EOF
+
+ # Generate the final initrd
+ "$build_dir/usr/gen_init_cpio" "$workspace_dir/cpio_list" > "$initrd"
+ local size=$(du -h "$initrd" | cut -f1)
+}
+
+function run_qemu() {
+ local qemu_cmd=$1
+ local cmdline=$2
+ local kernel_path=$3
+ local serial="$workspace_dir/qemu.serial"
+
+ local accel="-accel tcg"
+ local host_machine=$(uname -m)
+
+ [[ "$host_machine" == "arm64" ]] && host_machine="aarch64"
+ [[ "$host_machine" == "x86_64" ]] && host_machine="x86_64"
+
+ if [[ "$qemu_cmd" == *"$host_machine"* ]]; then
+ if [ -w /dev/kvm ]; then
+ accel="-accel kvm"
+ fi
+ fi
+
+ cmdline="$cmdline liveupdate=on panic=-1"
+
+ echo "# Serial Log: $serial"
+ timeout 30s $qemu_cmd -m 1G -smp 2 -no-reboot -nographic -nodefaults \
+ $accel \
+ -serial file:"$serial" \
+ -append "$cmdline" \
+ -kernel "$kernel_path" \
+ -initrd "$initrd"
+
+ local ret=$?
+
+ if [ $ret -eq 124 ]; then
+ fail "QEMU timed out"
+ fi
+
+ grep "TEST PASSED" "$serial" &> /dev/null || fail "Liveupdate failed. Check $serial for details."
+}
+
+function target_to_arch() {
+ local target=$1
+ case $target in
+ aarch64) echo "arm64" ;;
+ x86_64) echo "x86" ;;
+ *) skip "architecture $target is not supported"
+ esac
+}
+
+function main() {
+ local build_dir=""
+ local jobs=$(nproc)
+ local target="$(uname -m)"
+ local test_name="luo_kexec_simple"
+ local workspace_arg=""
+
+ set -o errtrace
+ trap skip ERR
+
+ while getopts 'hd:j:t:T:w:k' opt; do
+ case $opt in
+ d) build_dir="$OPTARG" ;;
+ j) jobs="$OPTARG" ;;
+ t) target="$OPTARG" ;;
+ T) test_name="$OPTARG" ;;
+ w) workspace_arg="$OPTARG" ;;
+ k) KEEP_WORKSPACE=1 ;;
+ h) usage; exit 0 ;;
+ *) echo "Unknown argument $opt"; usage; exit 1 ;;
+ esac
+ done
+
+ ktap_print_header
+ ktap_set_plan 1
+
+ if [ -n "$workspace_arg" ]; then
+ workspace_dir="$(realpath -m "$workspace_arg")"
+ mkdir -p "$workspace_dir"
+ else
+ workspace_dir=$(mktemp -d /tmp/luo-test.XXXXXXXX)
+ fi
+
+ echo "# Workspace created at: $workspace_dir"
+ headers_dir="$workspace_dir/usr"
+ initrd="$workspace_dir/initrd.cpio"
+
+ detect_cross_compile "$target"
+
+ local arch=$(target_to_arch "$target")
+
+ if [ -z "$build_dir" ]; then
+ build_dir="$kernel_dir/.luo_test_build.$arch"
+ fi
+
+ mkdir -p "$build_dir"
+ build_dir=$(realpath "$build_dir")
+ get_arch_conf "$arch"
+
+ local make_cmd="make -s ARCH=$arch CROSS_COMPILE=$CROSS_COMPILE -j$jobs"
+ local make_cmd_build="$make_cmd -C $kernel_dir O=$build_dir"
+
+ build_kernel "$build_dir" "$make_cmd_build" "$KERNEL_IMAGE" "$target"
+
+ local final_kernel="$build_dir/arch/$arch/boot/$KERNEL_IMAGE"
+ mkinitrd "$build_dir" "$final_kernel" "$test_name"
+
+ run_qemu "$QEMU_CMD" "$KERNEL_CMDLINE" "$final_kernel"
+ ktap_test_pass "$test_name succeeded"
+}
+
+main "$@"
--- a/tools/testing/selftests/liveupdate/luo_test_utils.c~b
+++ a/tools/testing/selftests/liveupdate/luo_test_utils.c
@@ -10,11 +10,14 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <getopt.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
#include <errno.h>
#include <stdarg.h>
@@ -166,3 +169,98 @@ void restore_and_read_stage(int state_se
close(mfd);
}
+
+void daemonize_and_wait(void)
+{
+ pid_t pid;
+
+ ksft_print_msg("[STAGE 1] Forking persistent child to hold sessions...\n");
+
+ pid = fork();
+ if (pid < 0)
+ fail_exit("fork failed");
+
+ if (pid > 0) {
+ ksft_print_msg("[STAGE 1] Child PID: %d. Resources are pinned.\n", pid);
+ ksft_print_msg("[STAGE 1] You may now perform kexec reboot.\n");
+ exit(EXIT_SUCCESS);
+ }
+
+ /* Detach from terminal so closing the window doesn't kill us */
+ if (setsid() < 0)
+ fail_exit("setsid failed");
+
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+
+ /* Change dir to root to avoid locking filesystems */
+ if (chdir("/") < 0)
+ exit(EXIT_FAILURE);
+
+ while (1)
+ sleep(60);
+}
+
+static int parse_stage_args(int argc, char *argv[])
+{
+ static struct option long_options[] = {
+ {"stage", required_argument, 0, 's'},
+ {0, 0, 0, 0}
+ };
+ int option_index = 0;
+ int stage = 1;
+ int opt;
+
+ optind = 1;
+ while ((opt = getopt_long(argc, argv, "s:", long_options, &option_index)) != -1) {
+ switch (opt) {
+ case 's':
+ stage = atoi(optarg);
+ if (stage != 1 && stage != 2)
+ fail_exit("Invalid stage argument");
+ break;
+ default:
+ fail_exit("Unknown argument");
+ }
+ }
+ return stage;
+}
+
+int luo_test(int argc, char *argv[],
+ const char *state_session_name,
+ luo_test_stage1_fn stage1,
+ luo_test_stage2_fn stage2)
+{
+ int target_stage = parse_stage_args(argc, argv);
+ int luo_fd = luo_open_device();
+ int state_session_fd;
+ int detected_stage;
+
+ if (luo_fd < 0) {
+ ksft_exit_skip("Failed to open %s. Is the luo module loaded?\n",
+ LUO_DEVICE);
+ }
+
+ state_session_fd = luo_retrieve_session(luo_fd, state_session_name);
+ if (state_session_fd == -ENOENT)
+ detected_stage = 1;
+ else if (state_session_fd >= 0)
+ detected_stage = 2;
+ else
+ fail_exit("Failed to check for state session");
+
+ if (target_stage != detected_stage) {
+ ksft_exit_fail_msg("Stage mismatch Requested --stage %d, but system is in stage %d.\n"
+ "(State session %s: %s)\n",
+ target_stage, detected_stage, state_session_name,
+ (detected_stage == 2) ? "EXISTS" : "MISSING");
+ }
+
+ if (target_stage == 1)
+ stage1(luo_fd);
+ else
+ stage2(luo_fd, state_session_fd);
+
+ return 0;
+}
--- a/tools/testing/selftests/liveupdate/luo_test_utils.h~b
+++ a/tools/testing/selftests/liveupdate/luo_test_utils.h
@@ -21,19 +21,24 @@
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);
+void daemonize_and_wait(void);
+
+typedef void (*luo_test_stage1_fn)(int luo_fd);
+typedef void (*luo_test_stage2_fn)(int luo_fd, int state_session_fd);
+
+int luo_test(int argc, char *argv[], const char *state_session_name,
+ luo_test_stage1_fn stage1, luo_test_stage2_fn stage2);
+
#endif /* LUO_TEST_UTILS_H */
--- a/tools/testing/selftests/liveupdate/Makefile~b
+++ a/tools/testing/selftests/liveupdate/Makefile
@@ -1,40 +1,34 @@
# SPDX-License-Identifier: GPL-2.0-only
-KHDR_INCLUDES ?= -I../../../../usr/include
-CFLAGS += -Wall -O2 -Wno-unused-function
-CFLAGS += $(KHDR_INCLUDES)
-LDFLAGS += -static
-OUTPUT ?= .
+LIB_C += luo_test_utils.c
-# --- Test Configuration (Edit this section when adding new tests) ---
-LUO_SHARED_SRCS := luo_test_utils.c
-LUO_SHARED_HDRS += luo_test_utils.h
+TEST_GEN_PROGS += liveupdate
-LUO_MANUAL_TESTS += luo_kexec_simple
-LUO_MANUAL_TESTS += luo_multi_session
+TEST_GEN_PROGS_EXTENDED += luo_kexec_simple
+TEST_GEN_PROGS_EXTENDED += luo_multi_session
TEST_FILES += do_kexec.sh
-TEST_GEN_PROGS += liveupdate
+include ../lib.mk
-# --- Automatic Rule Generation (Do not edit below) ---
+CFLAGS += $(KHDR_INCLUDES)
+CFLAGS += -Wall -O2 -Wno-unused-function
+CFLAGS += -MD
-TEST_GEN_PROGS_EXTENDED += $(LUO_MANUAL_TESTS)
+LIB_O := $(patsubst %.c, $(OUTPUT)/%.o, $(LIB_C))
+TEST_O := $(patsubst %, %.o, $(TEST_GEN_PROGS))
+TEST_O += $(patsubst %, %.o, $(TEST_GEN_PROGS_EXTENDED))
-# 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 $$@ \
- ) \
-)
+TEST_DEP_FILES := $(patsubst %.o, %.d, $(LIB_O))
+TEST_DEP_FILES += $(patsubst %.o, %.d, $(TEST_O))
+-include $(TEST_DEP_FILES)
-include ../lib.mk
+$(LIB_O): $(OUTPUT)/%.o: %.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
+
+$(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): $(OUTPUT)/%: %.o $(LIB_O)
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) $< $(LIB_O) $(LDLIBS) -o $@
+
+EXTRA_CLEAN += $(LIB_O)
+EXTRA_CLEAN += $(TEST_O)
+EXTRA_CLEAN += $(TEST_DEP_FILES)
diff --git a/tools/testing/selftests/liveupdate/run.sh a/tools/testing/selftests/liveupdate/run.sh
new file mode 100755
--- /dev/null
+++ a/tools/testing/selftests/liveupdate/run.sh
@@ -0,0 +1,68 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+OUTPUT_DIR="results_$(date +%Y%m%d_%H%M%S)"
+SCRIPT_DIR=$(dirname "$(realpath "$0")")
+TEST_RUNNER="$SCRIPT_DIR/luo_test.sh"
+
+TARGETS=("x86_64" "aarch64")
+
+GREEN='\033[0;32m'
+RED='\033[0;31m'
+YELLOW='\033[1;33m'
+NC='\033[0m'
+
+PASSED=()
+FAILED=()
+SKIPPED=()
+
+mkdir -p "$OUTPUT_DIR"
+
+TEST_NAMES=()
+while IFS= read -r file; do
+ TEST_NAMES+=("$(basename "$file" .c)")
+done < <(find "$SCRIPT_DIR" -maxdepth 1 -name "luo_*.c" ! -name "luo_test_utils.c")
+
+if [ ${#TEST_NAMES[@]} -eq 0 ]; then
+ echo "No tests found in $SCRIPT_DIR"
+ exit 1
+fi
+
+for arch in "${TARGETS[@]}"; do
+ for test_name in "${TEST_NAMES[@]}"; do
+ log_file="$OUTPUT_DIR/${arch}_${test_name}.log"
+ echo -n " -> $arch $test_name ... "
+
+ if "$TEST_RUNNER" -t "$arch" -T "$test_name" > "$log_file" 2>&1; then
+ echo -e "${GREEN}PASS${NC}"
+ PASSED+=("${arch}:${test_name}")
+ else
+ exit_code=$?
+ if [ $exit_code -eq 4 ]; then
+ echo -e "${YELLOW}SKIP${NC}"
+ SKIPPED+=("${arch}:${test_name}")
+ else
+ echo -e "${RED}FAIL${NC}"
+ FAILED+=("${arch}:${test_name}")
+ fi
+ fi
+ done
+ echo ""
+done
+
+echo "========================================="
+echo " TEST SUMMARY "
+echo "========================================="
+echo -e "PASSED: ${GREEN}${#PASSED[@]}${NC}"
+echo -e "FAILED: ${RED}${#FAILED[@]}${NC}"
+for fail in "${FAILED[@]}"; do
+ echo -e " - $fail"
+done
+echo -e "SKIPPED: ${YELLOW}${#SKIPPED[@]}${NC}"
+echo "Logs: $OUTPUT_DIR"
+
+if [ ${#FAILED[@]} -eq 0 ]; then
+ exit 0
+else
+ exit 1
+fi
_
© 2016 - 2025 Red Hat, Inc.