mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility
@ 2026-11-10  5:01 Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 01/19] liveupdate, kho: Remove __packed from KHO, LUO and memfd ABI structures Tarun Sahu
                   ` (18 more replies)
  0 siblings, 19 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:01 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

This v4 of feature flags support for LUO and KHO is no longer an RFC.
This has gone through multiple design changes over the past few months,
and I thank everyone for providing their suggestions and reviewing it
promptly. Below I talk briefly about the evolution of this series to set
context and try to make it easy for everyone to review.

Initially, Logan proposed the feature flags in RFC v1 [1], which tried
to solve the problem of dependency on the compatible string. Feature
flags introduced finer-grained compatibility. This series had three
fields: active, supported, and required. The number of flags was limited
and didn't have flexibility, which reduced the complexity of the patch,
but put a limitation on the extensibility of the features.

After LPC and a few design discussions, I proposed RFC v2 [2], which
changed the flags to optional and required and converted the encoding to
2 bits per feature instead of having two separate variables. But This still
has the number of flags limited and fixed the feature header size to
128 bytes. Though each serialized structure is being allocated in
multiples of PAGE_SIZE (folio order = 0, 1, 2...), where 128 bytes is
not a problem, it still carries two fundamental issues: limited feature
flags, and a fixed 128-byte memory overhead for each preserved file. In
the future, as there are plans for moving to KHO slab, the allocation
for serialized structures can be significantly reduced and this 128-byte
size will be a problem again.

Based on the RFC v2 discussion, this v4 (v3 [3] was intentionally ignored)
moves to extensible feature flags with a variable-length flags[] array,
avoiding the fixed 128-byte overhead and feature-count limit.

Along with this, as discussed on RFC v2, v4 uses separate bit ranges for
required and optional flags (1 bit per feature). It builds on the
consideration that a feature in its lifetime will either be required or
optional; it can't be toggled into the other. In the future, if such a
need arises, a new feature flag can be introduced to replicate the
feature as optional or required, opposite to its ancestor flag.

This new layout and encoding in memory is as follows:

struct kho_ser_hdr {
	u32 size;
	u16 nr_required;
	u16 nr_optional;
	u64 flags[];
}

where,
	flags[i] = (BIT_MAP(optional_features) << nr_required) |
		   BIT_MAP(required_features)

This series introduces the feature header in the KHO subsystem, and it
is used by serialized structures across KHO and LUO. A few of the
serialized structures embed other serialized structures directly. To
keep the relative offsets of fields to be same across kernels, we cannot
have an embedded serialized structure's size changed; otherwise, direct
casting will not be possible. To solve this, this series converts those
embedded structures to KHOSER_PTR, which points to the memory just after
the parent serialized structure, and allocation and preservation happen
for the total memory (parent_ser_size + embedded_ser_size).

<Iamparent>_ser {
	u64 a;
	u64 b;
	struct <Iamembedded>_ser e_ser;
	u64 d;
}
	CONVERTS TO ===>

<Iamparent>_ser {
	u64 a;
	u64 b;
	DECLARE_KHOSER_PTR(e_ser, <Iamembedded>_ser);
	u64 d;
}

There are only two such instances: kho_vmalloc and luo_file_set_ser.

This series introduces kho_ser_hdr for the following structures:

  - struct kho_kexec_metadata
  - struct kho_vmalloc
  - struct kho_block_header_ser
  - struct luo_ser
  - struct luo_session_ser
  - struct luo_file_set_ser
  - struct luo_file_ser
  - struct luo_flb_header_ser
  - struct luo_flb_ser
  - struct pci_ser
  - struct pci_dev_ser
  - struct memfd_luo_ser
  - struct memfd_luo_folio_ser

If you notice, this also includes the PCI ser structures. PCI LUO
support is merged in liveupdate/next, and this series is developed on
top of it.

Each serialized structure is preceded in memory by struct kho_ser_hdr
and registered via DECLARE_KHO_SER(name, payload_size, nr_required,
nr_optional). Both struct kho_kexec_metadata and struct pci_dev_ser are
explicitly marked with __aligned(8) because their largest member is a
32-bit integer, and 8-byte alignment is required by DECLARE_KHO_SER() so
trailing feature payloads remain 8-byte aligned across 32-bit and 64-bit
kernels.

Also, Introduce DECLARE_KHO_SER_HDR(ser_name, fhdr) which is just the
wrapper for struct kho_ser_hdr fhdr, This makes it clear
To which ser_name, this fhdr belongs to.


Series structure:
  - Patch 1 removes __packed from KHO, LUO, and memfd ABI structures and
    asserts their natural size and 8-byte alignment under CONFIG_64BIT.
  - Patch 2 records entry_size in struct kho_block_header_ser so the
    incoming kernel derives the entry stride from the block header.
  - Patch 3 converts callers that embedded struct kho_vmalloc by value
    (memfd_luo and test_kho) to reference it via DECLARE_KHOSER_PTR so
    struct kho_vmalloc can grow independently.
  - Patch 4 converts struct luo_session_ser to reference
    struct luo_file_set_ser via DECLARE_KHOSER_PTR instead of embedding
    it by value so both structures can grow independently.
  - Patch 5 introduces struct kho_ser_hdr, DECLARE_KHO_SER(),
    DECLARE_KHO_SER_HDR(), and the serialization/validation helpers in
    include/linux/kho/abi/feature.h.
  - Patches 6-18 add the feature header to each of the 13 KHO, LUO, PCI,
    and memfd serialized structures in independent patches.
  - Patch 19 adds a KUnit test suite (kho_feature) covering the feature
    header helpers.

[1] https://lore.kernel.org/all/20260903023452.721732-1-loganodell@google.com/
[2] https://lore.kernel.org/all/20261021152527.375070-1-tarunsahu@google.com/
[3] https://lore.kernel.org/all/20261026174217.603114-1-tarunsahu@google.com/

Tarun Sahu (19):
  liveupdate, kho: Remove __packed from KHO, LUO and memfd ABI
    structures
  kho: block: Record the entry size in the block header
  kho, memfd_luo: Reference struct kho_vmalloc via serialized pointer
  liveupdate: Reference struct luo_file_set_ser via serialized pointer
  kho: Add feature header for fine-grained ABI compatibility
  kho: kexec_metadata: Add the feature header
  kho: vmalloc: Add the feature header
  kho: block: Add the feature header to blocks
  liveupdate: Add feature header to struct luo_ser
  liveupdate: Add feature header to struct luo_session_ser
  liveupdate: Add feature header to struct luo_file_set_ser
  liveupdate: Add feature header to struct luo_file_ser
  liveupdate: Add feature header to struct luo_flb_header_ser
  liveupdate: Add feature header to struct luo_flb_ser
  PCI: liveupdate: Add feature header to struct pci_ser
  PCI: liveupdate: Add feature header to struct pci_dev_ser
  mm: memfd_luo: Add feature header to struct memfd_luo_ser
  mm: memfd_luo: Add feature header to struct memfd_luo_folio_ser
  kho: Add KUnit test for the feature header helpers

 MAINTAINERS                            |   1 +
 drivers/pci/liveupdate.c               |  99 +++----
 include/linux/kexec_handover.h         |  15 +-
 include/linux/kho/abi/block.h          |  21 +-
 include/linux/kho/abi/feature.h        | 372 +++++++++++++++++++++++++
 include/linux/kho/abi/kexec_handover.h |  11 +-
 include/linux/kho/abi/kexec_metadata.h |  17 +-
 include/linux/kho/abi/luo.h            | 102 ++++---
 include/linux/kho/abi/memfd.h          |  27 +-
 include/linux/kho/abi/pci.h            |  66 ++---
 include/linux/kho_block.h              |   8 +-
 kernel/liveupdate/kexec_handover.c     |  98 ++++---
 kernel/liveupdate/kho_block.c          | 172 +++++++++---
 kernel/liveupdate/luo_core.c           |  43 +--
 kernel/liveupdate/luo_file.c           |  58 ++--
 kernel/liveupdate/luo_flb.c            |  69 +++--
 kernel/liveupdate/luo_internal.h       |   9 +-
 kernel/liveupdate/luo_session.c        |  55 +++-
 lib/Kconfig.debug                      |  13 +
 lib/test_kho.c                         |  38 ++-
 lib/tests/Makefile                     |   1 +
 lib/tests/kho_feature_kunit.c          | 184 ++++++++++++
 mm/memfd_luo.c                         | 183 ++++++++----
 23 files changed, 1271 insertions(+), 391 deletions(-)
 create mode 100644 include/linux/kho/abi/feature.h
 create mode 100644 lib/tests/kho_feature_kunit.c

-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 01/19] liveupdate, kho: Remove __packed from KHO, LUO and memfd ABI structures
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
@ 2026-11-10  5:01 ` Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 02/19] kho: block: Record the entry size in the block header Tarun Sahu
                   ` (17 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:01 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

All structures in include/linux/kho/abi/block.h (struct
kho_block_header_ser), include/linux/kho/abi/luo.h (struct luo_ser,
struct luo_file_ser, struct luo_file_set_ser, struct luo_session_ser,
struct luo_flb_header_ser, struct luo_flb_ser) and
include/linux/kho/abi/memfd.h (struct memfd_luo_folio_ser, struct
memfd_luo_ser) have naturally aligned fields on 64-bit architectures,
with no padding holes between fields or tail padding. Assert both the
structure size and 8-byte alignment under CONFIG_64BIT at compile time.
Any future unaligned field addition should come with reserved field to
keep the structure aligned.

Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 include/linux/kho/abi/block.h | 10 +++++++--
 include/linux/kho/abi/luo.h   | 38 +++++++++++++++++++++++++++++------
 include/linux/kho/abi/memfd.h | 16 ++++++++++++---
 3 files changed, 53 insertions(+), 11 deletions(-)

diff --git a/include/linux/kho/abi/block.h b/include/linux/kho/abi/block.h
index d06d64b963be..36bdbaf9b3b8 100644
--- a/include/linux/kho/abi/block.h
+++ b/include/linux/kho/abi/block.h
@@ -12,7 +12,7 @@
  * pre-update kernel to a post-update kernel.
  *
  * This interface is a contract. Any modification to the structure fields,
- * compatible strings, or the layout of the `__packed` serialization
+ * compatible strings, or the layout of the serialization
  * structures defined here constitutes a breaking change. Such changes require
  * incrementing the version number in the `KHO_FDT_COMPATIBLE` string to
  * prevent a new kernel from misinterpreting data from an old kernel.
@@ -26,6 +26,8 @@
 #define _LINUX_KHO_ABI_BLOCK_H
 
 #include <asm/page.h>
+#include <linux/build_bug.h>
+#include <linux/kconfig.h>
 #include <linux/types.h>
 
 /**
@@ -49,6 +51,10 @@
 struct kho_block_header_ser {
 	u64 next;
 	u64 count;
-} __packed;
+};
+
+static_assert(sizeof(struct kho_block_header_ser) == 16);
+static_assert(!IS_ENABLED(CONFIG_64BIT) ||
+	      __alignof__(struct kho_block_header_ser) == 8);
 
 #endif /* _LINUX_KHO_ABI_BLOCK_H */
diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 288076de6d4a..721949374fa1 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -58,6 +58,8 @@
 #define _LINUX_KHO_ABI_LUO_H
 
 #include <linux/align.h>
+#include <linux/build_bug.h>
+#include <linux/kconfig.h>
 #include <linux/kho/abi/block.h>
 #include <uapi/linux/liveupdate.h>
 
@@ -82,7 +84,11 @@ struct luo_ser {
 	u64 liveupdate_num;
 	u64 sessions_pa;
 	u64 flbs_pa;
-} __packed;
+};
+
+static_assert(sizeof(struct luo_ser) == 32);
+static_assert(!IS_ENABLED(CONFIG_64BIT) ||
+	      __alignof__(struct luo_ser) == 8);
 
 #define LIVEUPDATE_HNDL_COMPAT_LENGTH	48
 
@@ -98,7 +104,11 @@ struct luo_file_ser {
 	char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
 	u64 data;
 	u64 token;
-} __packed;
+};
+
+static_assert(sizeof(struct luo_file_ser) == 64);
+static_assert(!IS_ENABLED(CONFIG_64BIT) ||
+	      __alignof__(struct luo_file_ser) == 8);
 
 /**
  * struct luo_file_set_ser - Represents the serialized metadata for file set
@@ -113,7 +123,11 @@ struct luo_file_ser {
 struct luo_file_set_ser {
 	u64 files;
 	u64 count;
-} __packed;
+};
+
+static_assert(sizeof(struct luo_file_set_ser) == 16);
+static_assert(!IS_ENABLED(CONFIG_64BIT) ||
+	      __alignof__(struct luo_file_set_ser) == 8);
 
 /**
  * struct luo_session_ser - Represents the serialized metadata for a LUO session.
@@ -131,7 +145,11 @@ struct luo_file_set_ser {
 struct luo_session_ser {
 	char name[LIVEUPDATE_SESSION_NAME_LENGTH];
 	struct luo_file_set_ser file_set_ser;
-} __packed;
+};
+
+static_assert(sizeof(struct luo_session_ser) == 80);
+static_assert(!IS_ENABLED(CONFIG_64BIT) ||
+	      __alignof__(struct luo_session_ser) == 8);
 
 /* The max size is set so it can be reliably used during in serialization */
 #define LIVEUPDATE_FLB_COMPAT_LENGTH	48
@@ -152,7 +170,11 @@ struct luo_session_ser {
 struct luo_flb_header_ser {
 	u64 pgcnt;
 	u64 count;
-} __packed;
+};
+
+static_assert(sizeof(struct luo_flb_header_ser) == 16);
+static_assert(!IS_ENABLED(CONFIG_64BIT) ||
+	      __alignof__(struct luo_flb_header_ser) == 8);
 
 /**
  * struct luo_flb_ser - Represents the serialized state of a single FLB object.
@@ -175,7 +197,11 @@ struct luo_flb_ser {
 	char name[LIVEUPDATE_FLB_COMPAT_LENGTH];
 	u64 data;
 	u64 count;
-} __packed;
+};
+
+static_assert(sizeof(struct luo_flb_ser) == 64);
+static_assert(!IS_ENABLED(CONFIG_64BIT) ||
+	      __alignof__(struct luo_flb_ser) == 8);
 
 /* Kernel Live Update Test ABI */
 #ifdef CONFIG_LIVEUPDATE_TEST
diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h
index 08b10fea2afc..376a724a1d8c 100644
--- a/include/linux/kho/abi/memfd.h
+++ b/include/linux/kho/abi/memfd.h
@@ -11,6 +11,8 @@
 #ifndef _LINUX_KHO_ABI_MEMFD_H
 #define _LINUX_KHO_ABI_MEMFD_H
 
+#include <linux/build_bug.h>
+#include <linux/kconfig.h>
 #include <linux/types.h>
 #include <linux/kho/abi/kexec_handover.h>
 
@@ -20,7 +22,7 @@
  * memfd uses the ABI defined below for preserving its state across a kexec
  * reboot using the LUO.
  *
- * The state is serialized into a packed structure `struct memfd_luo_ser`
+ * The state is serialized into `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
@@ -54,7 +56,11 @@ struct memfd_luo_folio_ser {
 	u64 pfn:52;
 	u64 flags:12;
 	u64 index;
-} __packed;
+};
+
+static_assert(sizeof(struct memfd_luo_folio_ser) == 16);
+static_assert(!IS_ENABLED(CONFIG_64BIT) ||
+	      __alignof__(struct memfd_luo_folio_ser) == 8);
 
 /*
  * The set of seals this version supports preserving. If support for any new
@@ -85,7 +91,11 @@ struct memfd_luo_ser {
 	u32 flags;
 	u64 nr_folios;
 	struct kho_vmalloc folios;
-} __packed;
+};
+
+static_assert(sizeof(struct memfd_luo_ser) == 48);
+static_assert(!IS_ENABLED(CONFIG_64BIT) ||
+	      __alignof__(struct memfd_luo_ser) == 8);
 
 /* The compatibility string for memfd file handler */
 #define MEMFD_LUO_FH_COMPATIBLE	"memfd-v2"
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 02/19] kho: block: Record the entry size in the block header
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 01/19] liveupdate, kho: Remove __packed from KHO, LUO and memfd ABI structures Tarun Sahu
@ 2026-11-10  5:01 ` Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 03/19] kho, memfd_luo: Reference struct kho_vmalloc via serialized pointer Tarun Sahu
                   ` (16 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:01 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

Subsystems using the KHO Serialization Blocks framework currently do
not record the serialized entry size in the block header. The entry size
is only known to individual callers, which must track and store it in an
owning serialized data structure. If a newer source kernel adds fields
to an entry, the changed entry size breaks the stride when an older
target kernel iterates over blocks.

Add an entry_size field to struct kho_block_header_ser. On the outgoing
side, record bs->entry_size into every block header during allocation.
During restoration, derive the entry stride directly from the block
header instead of relying on caller-supplied dimensions. The
kho_block_set_init() interface remains unchanged. A zero entry size,
or one that differs between blocks of a set, is rejected during
restoration.

This change modifies the serialized layout of struct kho_block_header_ser
and shifts the offset of serialized entries within preserved blocks.
Bump KHO_FDT_COMPATIBLE from kho-v4 to kho-v5 so that pre-v5 and post-v5
kernels cleanly reject mismatched handover state during boot. Also
update the stale compatible string example in the documentation.

Suggested-by: Pratyush Yadav <pratyush@kernel.org>
Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 include/linux/kho/abi/block.h          | 10 ++++----
 include/linux/kho/abi/kexec_handover.h |  6 ++---
 kernel/liveupdate/kho_block.c          | 32 +++++++++++++++++++++++---
 3 files changed, 38 insertions(+), 10 deletions(-)

diff --git a/include/linux/kho/abi/block.h b/include/linux/kho/abi/block.h
index 36bdbaf9b3b8..a40b0906c821 100644
--- a/include/linux/kho/abi/block.h
+++ b/include/linux/kho/abi/block.h
@@ -40,9 +40,10 @@
 
 /**
  * struct kho_block_header_ser - Header for the serialized data block.
- * @next:  Physical address of the next struct kho_block_header_ser.
- * @count: The number of entries that immediately follow this header in the
- *         memory block.
+ * @next:       Physical address of the next struct kho_block_header_ser.
+ * @count:      The number of entries that immediately follow this header in the
+ *              memory block.
+ * @entry_size: The size in bytes of each serialized entry in the block.
  *
  * This structure is located at the beginning of a block of physical memory
  * preserved across a kexec. It provides the necessary metadata to interpret
@@ -51,9 +52,10 @@
 struct kho_block_header_ser {
 	u64 next;
 	u64 count;
+	u64 entry_size;
 };
 
-static_assert(sizeof(struct kho_block_header_ser) == 16);
+static_assert(sizeof(struct kho_block_header_ser) == 24);
 static_assert(!IS_ENABLED(CONFIG_64BIT) ||
 	      __alignof__(struct kho_block_header_ser) == 8);
 
diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h
index 2f4fb9c63942..6f12f5037ea5 100644
--- a/include/linux/kho/abi/kexec_handover.h
+++ b/include/linux/kho/abi/kexec_handover.h
@@ -41,7 +41,7 @@
  *   restore the preserved data.::
  *
  *     / {
- *         compatible = "kho-v3";
+ *         compatible = "kho-v5";
  *
  *         preserved-memory-map = <0x...>;
  *
@@ -62,7 +62,7 @@
  *     };
  *
  *   Root KHO Node (/):
- *     - compatible: "kho-v3"
+ *     - compatible: "kho-v5"
  *
  *       Identifies the overall KHO ABI version.
  *
@@ -90,7 +90,7 @@
  */
 
 /* The compatible string for the KHO FDT root node. */
-#define KHO_FDT_COMPATIBLE "kho-v4"
+#define KHO_FDT_COMPATIBLE "kho-v5"
 
 /* The FDT property for the preserved memory map. */
 #define KHO_FDT_MEMORY_MAP_PROP_NAME "preserved-memory-map"
diff --git a/kernel/liveupdate/kho_block.c b/kernel/liveupdate/kho_block.c
index 0d2a342ef422..9b148009085e 100644
--- a/kernel/liveupdate/kho_block.c
+++ b/kernel/liveupdate/kho_block.c
@@ -16,6 +16,12 @@
  * array of serialized entries. Multiple blocks are linked together via a
  * physical pointer in the header, forming a linked list that can be easily
  * traversed in both the current and the next kernel.
+ *
+ * Each block header also records the size of its entries. A newer kernel may
+ * append fields to an entry, so the kernel that writes a block and the kernel
+ * that reads it can use different entry sizes. The reader steps through the
+ * entries with the recorded size, so every entry is found, and it does not read
+ * beyond the recorded size of an entry.
  */
 
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -110,6 +116,8 @@ static int kho_block_set_grow_one(struct kho_block_set *bs)
 	if (IS_ERR(ser))
 		return PTR_ERR(ser);
 
+	ser->entry_size = bs->entry_size;
+
 	err = kho_block_add(bs, ser);
 	if (err) {
 		kho_block_free_ser(bs, ser);
@@ -242,7 +250,8 @@ static bool kho_block_set_is_cyclic(struct kho_block_set *bs)
  *
  * Restores a serialized block set from a given physical address. The caller is
  * responsible for ensuring that the block set @bs has been allocated and
- * initialized prior to calling this function.
+ * initialized prior to calling this function. The entry size and the number
+ * of entries per block are taken from the restored block headers.
  *
  * Return: 0 on success, or a negative errno on failure.
  */
@@ -250,6 +259,7 @@ int kho_block_set_restore(struct kho_block_set *bs, u64 head_pa)
 {
 	struct kho_block_header_ser *ser;
 	u64 next_pa = head_pa;
+	u64 entry_size = 0;
 	int err;
 
 	/* Restored block sets use size from the previous kernel */
@@ -264,8 +274,21 @@ int kho_block_set_restore(struct kho_block_set *bs, u64 head_pa)
 	}
 
 	while (next_pa) {
+		u64 capacity;
+
 		ser = phys_to_virt(next_pa);
-		if (!ser->count || ser->count > bs->count_per_block) {
+		if (!ser->entry_size || (entry_size && ser->entry_size != entry_size)) {
+			pr_warn("Block contains invalid entry size: %llu\n",
+				ser->entry_size);
+			err = -EINVAL;
+			goto err_destroy;
+		}
+		entry_size = ser->entry_size;
+		bs->entry_size = entry_size;
+		capacity = (KHO_BLOCK_SIZE - sizeof(*ser)) / bs->entry_size;
+		bs->count_per_block = capacity;
+
+		if (!ser->count || ser->count > capacity) {
 			pr_warn("Block contains invalid entry count: %llu\n",
 				ser->count);
 			err = -EINVAL;
@@ -370,7 +393,10 @@ void *kho_block_set_it_reserve_entry(struct kho_block_set_it *it)
  * @it: The block iterator.
  *
  * Iterates through previously written entries during state deserialization,
- * respecting the actual count stored in each block's header.
+ * respecting the actual count stored in each block's header. Entries are
+ * spaced by the entry size recorded by the kernel that wrote them, which can
+ * be smaller or larger than the caller's structure. Fields beyond that size
+ * are not present in the entry.
  *
  * Return: A pointer to the next serialized entry, or NULL if all serialized
  * entries have been read.
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 03/19] kho, memfd_luo: Reference struct kho_vmalloc via serialized pointer
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 01/19] liveupdate, kho: Remove __packed from KHO, LUO and memfd ABI structures Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 02/19] kho: block: Record the entry size in the block header Tarun Sahu
@ 2026-11-10  5:01 ` Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 04/19] liveupdate: Reference struct luo_file_set_ser " Tarun Sahu
                   ` (15 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:01 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

Subsystems preserving vmalloc allocations currently embed struct
kho_vmalloc directly by value in their serialized structures or device
tree properties. For example, struct memfd_luo_ser embeds struct
kho_vmalloc folios, and test_kho writes the structure directly into the
folios_info FDT property.

Embedding struct kho_vmalloc directly couples the containing layout's
field offsets and size to struct kho_vmalloc. Any modification to struct
kho_vmalloc shifts subsequent fields in the containing structure and
breaks the handover ABI across kexec.

Convert callers to hold a DECLARE_KHOSER_PTR pointer to struct
kho_vmalloc instead of embedding it by value. In memfd_luo, allocate the
main structure and struct kho_vmalloc in the same preserved folio, placing
struct kho_vmalloc in the remaining 8-byte-aligned space, and bump the
compatibility string to memfd-v3. In test_kho, place struct kho_vmalloc
in the remaining space of the FDT folio, store the serialized pointer in
the folios_info property, and bump the compatibility string to
kho-test-v2.

Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 include/linux/kho/abi/kexec_handover.h |  6 +++
 include/linux/kho/abi/memfd.h          |  8 ++--
 lib/test_kho.c                         | 29 ++++++++----
 mm/memfd_luo.c                         | 63 ++++++++++++++++----------
 4 files changed, 68 insertions(+), 38 deletions(-)

diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h
index 6f12f5037ea5..bee75a7006e8 100644
--- a/include/linux/kho/abi/kexec_handover.h
+++ b/include/linux/kho/abi/kexec_handover.h
@@ -11,6 +11,8 @@
 #define _LINUX_KHO_ABI_KEXEC_HANDOVER_H
 
 #include <linux/bits.h>
+#include <linux/build_bug.h>
+#include <linux/kconfig.h>
 #include <linux/log2.h>
 #include <linux/math.h>
 #include <linux/types.h>
@@ -177,6 +179,10 @@ struct kho_vmalloc {
 	unsigned short order;
 };
 
+static_assert(sizeof(struct kho_vmalloc) == 16);
+static_assert(!IS_ENABLED(CONFIG_64BIT) ||
+	      __alignof__(struct kho_vmalloc) == 8);
+
 /**
  * DOC: KHO persistent memory tracker
  *
diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h
index 376a724a1d8c..7b49dd6d7e2a 100644
--- a/include/linux/kho/abi/memfd.h
+++ b/include/linux/kho/abi/memfd.h
@@ -81,7 +81,7 @@ static_assert(!IS_ENABLED(CONFIG_64BIT) ||
  *             to directly use them in the ABI.
  * @flags:     Flags for the file. Unused flag bits must be set to 0.
  * @nr_folios: Number of folios in the folios array.
- * @folios:    KHO vmalloc descriptor pointing to the array of
+ * @folios:    Pointer to KHO vmalloc descriptor for the array of
  *             struct memfd_luo_folio_ser.
  */
 struct memfd_luo_ser {
@@ -90,14 +90,14 @@ struct memfd_luo_ser {
 	u32 seals;
 	u32 flags;
 	u64 nr_folios;
-	struct kho_vmalloc folios;
+	DECLARE_KHOSER_PTR(folios, struct kho_vmalloc *);
 };
 
-static_assert(sizeof(struct memfd_luo_ser) == 48);
+static_assert(sizeof(struct memfd_luo_ser) == 40);
 static_assert(!IS_ENABLED(CONFIG_64BIT) ||
 	      __alignof__(struct memfd_luo_ser) == 8);
 
 /* The compatibility string for memfd file handler */
-#define MEMFD_LUO_FH_COMPATIBLE	"memfd-v2"
+#define MEMFD_LUO_FH_COMPATIBLE	"memfd-v3"
 
 #endif /* _LINUX_KHO_ABI_MEMFD_H */
diff --git a/lib/test_kho.c b/lib/test_kho.c
index aa6a0956bb8b..cd0f183ff68e 100644
--- a/lib/test_kho.c
+++ b/lib/test_kho.c
@@ -25,7 +25,7 @@
 
 #define KHO_TEST_MAGIC	0x4b484f21	/* KHO! */
 #define KHO_TEST_FDT	"kho_test"
-#define KHO_TEST_COMPAT "kho-test-v1"
+#define KHO_TEST_COMPAT "kho-test-v2"
 
 static long max_mem = (PAGE_SIZE << MAX_PAGE_ORDER) * 2;
 module_param(max_mem, long, 0644);
@@ -34,7 +34,7 @@ struct kho_test_state {
 	unsigned int nr_folios;
 	struct folio **folios;
 	phys_addr_t *folios_info;
-	struct kho_vmalloc folios_info_phys;
+	DECLARE_KHOSER_PTR(folios_info_phys, struct kho_vmalloc *);
 	int nr_folios_preserved;
 	struct folio *fdt;
 	__wsum csum;
@@ -47,13 +47,13 @@ static void kho_test_unpreserve_data(struct kho_test_state *state)
 	for (int i = 0; i < state->nr_folios_preserved; i++)
 		kho_unpreserve_folio(state->folios[i]);
 
-	kho_unpreserve_vmalloc(&state->folios_info_phys);
+	if (KHOSER_LOAD_PTR(state->folios_info_phys))
+		kho_unpreserve_vmalloc(KHOSER_LOAD_PTR(state->folios_info_phys));
 	vfree(state->folios_info);
 }
 
 static int kho_test_preserve_data(struct kho_test_state *state)
 {
-	struct kho_vmalloc folios_info_phys;
 	phys_addr_t *folios_info;
 	int err;
 
@@ -61,11 +61,10 @@ static int kho_test_preserve_data(struct kho_test_state *state)
 	if (!folios_info)
 		return -ENOMEM;
 
-	err = kho_preserve_vmalloc(folios_info, &folios_info_phys);
+	err = kho_preserve_vmalloc(folios_info, KHOSER_LOAD_PTR(state->folios_info_phys));
 	if (err)
 		goto err_free_info;
 
-	state->folios_info_phys = folios_info_phys;
 	state->folios_info = folios_info;
 
 	for (int i = 0; i < state->nr_folios; i++) {
@@ -123,14 +122,19 @@ static int kho_test_prepare_fdt(struct kho_test_state *state, ssize_t fdt_size)
 
 static int kho_test_preserve(struct kho_test_state *state)
 {
-	ssize_t fdt_size;
+	struct kho_vmalloc *folios_vmalloc;
+	ssize_t fdt_size, alloc_size;
 	int err;
 
 	fdt_size = state->nr_folios * sizeof(phys_addr_t) + PAGE_SIZE;
-	state->fdt = folio_alloc(GFP_KERNEL, get_order(fdt_size));
+	alloc_size = fdt_size + sizeof(struct kho_vmalloc);
+	state->fdt = folio_alloc(GFP_KERNEL, get_order(alloc_size));
 	if (!state->fdt)
 		return -ENOMEM;
 
+	folios_vmalloc = folio_address(state->fdt) + fdt_size;
+	KHOSER_STORE_PTR(state->folios_info_phys, folios_vmalloc);
+
 	err = kho_preserve_folio(state->fdt);
 	if (err)
 		goto err_free_fdt;
@@ -234,7 +238,8 @@ static int kho_test_save(void)
 
 static int kho_test_restore_data(const void *fdt, int node)
 {
-	const struct kho_vmalloc *folios_info_phys;
+	const DECLARE_KHOSER_PTR(*folios_info_phys, struct kho_vmalloc *);
+	const struct kho_vmalloc *folios_vmalloc;
 	const unsigned int *nr_folios;
 	phys_addr_t *folios_info;
 	const __wsum *old_csum;
@@ -255,7 +260,11 @@ static int kho_test_restore_data(const void *fdt, int node)
 	if (!folios_info_phys || len != sizeof(*folios_info_phys))
 		return -EINVAL;
 
-	folios_info = kho_restore_vmalloc(folios_info_phys);
+	folios_vmalloc = KHOSER_LOAD_PTR(*folios_info_phys);
+	if (!folios_vmalloc)
+		return -EINVAL;
+
+	folios_info = kho_restore_vmalloc(folios_vmalloc);
 	if (!folios_info)
 		return -EINVAL;
 
diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
index 6b68220275eb..4471cced57db 100644
--- a/mm/memfd_luo.c
+++ b/mm/memfd_luo.c
@@ -85,9 +85,8 @@
 #include "internal.h"
 
 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 memfd_luo_ser *ser,
+				     struct memfd_luo_folio_ser **out_folios_ser)
 {
 	struct inode *inode = file_inode(file);
 	struct memfd_luo_folio_ser *folios_ser;
@@ -104,7 +103,8 @@ static int memfd_luo_preserve_folios(struct file *file,
 	 * are not set.
 	 */
 	if (!size) {
-		*nr_foliosp = 0;
+		ser->folios.phys = 0;
+		ser->nr_folios = 0;
 		*out_folios_ser = NULL;
 		return 0;
 	}
@@ -200,12 +200,12 @@ static int memfd_luo_preserve_folios(struct file *file,
 		pfolio->index = folio->index;
 	}
 
-	err = kho_preserve_vmalloc(folios_ser, kho_vmalloc);
+	err = kho_preserve_vmalloc(folios_ser, KHOSER_LOAD_PTR(ser->folios));
 	if (err)
 		goto err_unpreserve;
 
 	kvfree(folios);
-	*nr_foliosp = nr_folios;
+	ser->nr_folios = nr_folios;
 	*out_folios_ser = folios_ser;
 
 	/*
@@ -254,37 +254,43 @@ static void memfd_luo_unpreserve_folios(struct kho_vmalloc *kho_vmalloc,
 	vfree(folios_ser);
 }
 
+static size_t memfd_luo_ser_size(void)
+{
+	return sizeof(struct memfd_luo_ser) + sizeof(struct kho_vmalloc);
+}
+
+static struct memfd_luo_ser *memfd_luo_ser_init(void *mem)
+{
+	struct kho_vmalloc *kho_vmalloc = mem + sizeof(struct memfd_luo_ser);
+	struct memfd_luo_ser *ser = mem;
+
+	KHOSER_STORE_PTR(ser->folios, kho_vmalloc);
+	return ser;
+}
+
 static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
 {
 	struct inode *inode = file_inode(args->file);
 	struct memfd_luo_folio_ser *folios_ser;
 	struct memfd_luo_ser *ser;
-	u64 nr_folios, inode_size;
 	int err = 0, seals;
+	u64 inode_size;
 
 	inode_lock(inode);
 	shmem_freeze(inode, true);
 
-	/* 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;
-	}
-
 	seals = memfd_get_seals(args->file);
 	if (seals < 0) {
 		err = seals;
-		goto err_free_ser;
+		goto err_unlock;
 	}
 
 	/* Make sure the file only has the seals supported by this version. */
 	if (seals & ~MEMFD_LUO_ALL_SEALS) {
 		err = -EOPNOTSUPP;
-		goto err_free_ser;
+		goto err_unlock;
 	}
 
-	ser->pos = args->file->f_pos;
 	inode_size = i_size_read(inode);
 
 	/*
@@ -293,18 +299,26 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
 	 */
 	if (DIV_ROUND_UP_ULL(inode_size, PAGE_SIZE) > UINT_MAX) {
 		err = -EFBIG;
-		goto err_free_ser;
+		goto err_unlock;
+	}
+
+	/* Allocate the main serialization structure in preserved memory */
+	ser = kho_alloc_preserve(memfd_luo_ser_size());
+	if (IS_ERR(ser)) {
+		err = PTR_ERR(ser);
+		goto err_unlock;
 	}
+	ser = memfd_luo_ser_init(ser);
 
+	ser->pos = args->file->f_pos;
 	ser->size = inode_size;
 	ser->seals = seals;
+	ser->flags = 0;
 
-	err = memfd_luo_preserve_folios(args->file, &ser->folios,
-					&folios_ser, &nr_folios);
+	err = memfd_luo_preserve_folios(args->file, ser, &folios_ser);
 	if (err)
 		goto err_free_ser;
 
-	ser->nr_folios = nr_folios;
 	inode_unlock(inode);
 
 	args->private_data = folios_ser;
@@ -351,7 +365,8 @@ static void memfd_luo_unpreserve(struct liveupdate_file_op_args *args)
 
 	ser = phys_to_virt(args->serialized_data);
 
-	memfd_luo_unpreserve_folios(&ser->folios, args->private_data,
+	memfd_luo_unpreserve_folios(KHOSER_LOAD_PTR(ser->folios),
+				    args->private_data,
 				    ser->nr_folios);
 
 	kho_unpreserve_free(ser);
@@ -401,7 +416,7 @@ static void memfd_luo_finish(struct liveupdate_file_op_args *args)
 		return;
 
 	if (ser->nr_folios) {
-		folios_ser = kho_restore_vmalloc(&ser->folios);
+		folios_ser = kho_restore_vmalloc(KHOSER_LOAD_PTR(ser->folios));
 		if (!folios_ser)
 			goto out;
 
@@ -552,7 +567,7 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
 	i_size_write(file_inode(file), ser->size);
 
 	if (ser->nr_folios) {
-		folios_ser = kho_restore_vmalloc(&ser->folios);
+		folios_ser = kho_restore_vmalloc(KHOSER_LOAD_PTR(ser->folios));
 		if (!folios_ser) {
 			err = -EINVAL;
 			goto put_file;
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 04/19] liveupdate: Reference struct luo_file_set_ser via serialized pointer
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
                   ` (2 preceding siblings ...)
  2026-11-10  5:01 ` [PATCH v4 03/19] kho, memfd_luo: Reference struct kho_vmalloc via serialized pointer Tarun Sahu
@ 2026-11-10  5:01 ` Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 05/19] kho: Add feature header for fine-grained ABI compatibility Tarun Sahu
                   ` (14 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:01 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

struct luo_session_ser currently embeds struct luo_file_set_ser directly
by value. Embedding struct luo_file_set_ser couples the session layout's
size and field offsets to struct luo_file_set_ser, preventing either
structure from appending fields independently across kexec.

Convert struct luo_session_ser to hold a DECLARE_KHOSER_PTR pointer to
struct luo_file_set_ser instead of embedding it by value. Reserve space
for both structures in each session block-set entry, placing struct
luo_file_set_ser immediately after struct luo_session_ser, and bump
LUO_ABI_COMPATIBLE from luo-v5 to luo-v6.

Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 include/linux/kho/abi/luo.h     |  9 +++++----
 kernel/liveupdate/luo_session.c | 27 ++++++++++++++++++++++-----
 2 files changed, 27 insertions(+), 9 deletions(-)

diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 721949374fa1..f1152470f71a 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -61,13 +61,14 @@
 #include <linux/build_bug.h>
 #include <linux/kconfig.h>
 #include <linux/kho/abi/block.h>
+#include <linux/kho/abi/kexec_handover.h>
 #include <uapi/linux/liveupdate.h>
 
 /*
  * The LUO state is registered under this KHO entry name.
  */
 #define LUO_KHO_ENTRY_NAME	"LUO"
-#define LUO_ABI_COMPATIBLE	"luo-v5"
+#define LUO_ABI_COMPATIBLE	"luo-v6"
 #define LUO_ABI_COMPAT_LEN	ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
 
 /**
@@ -133,7 +134,7 @@ static_assert(!IS_ENABLED(CONFIG_64BIT) ||
  * 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,
+ * @file_set_ser: Serialized pointer to the 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
@@ -144,10 +145,10 @@ static_assert(!IS_ENABLED(CONFIG_64BIT) ||
  */
 struct luo_session_ser {
 	char name[LIVEUPDATE_SESSION_NAME_LENGTH];
-	struct luo_file_set_ser file_set_ser;
+	DECLARE_KHOSER_PTR(file_set_ser, struct luo_file_set_ser *);
 };
 
-static_assert(sizeof(struct luo_session_ser) == 80);
+static_assert(sizeof(struct luo_session_ser) == 72);
 static_assert(!IS_ENABLED(CONFIG_64BIT) ||
 	      __alignof__(struct luo_session_ser) == 8);
 
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 74bc66929f4b..df667fe15fb2 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -131,18 +131,21 @@ struct luo_session_global {
 	struct luo_session_header outgoing;
 };
 
+#define LUO_SESSION_SER_SIZE (sizeof(struct luo_session_ser) + \
+			      sizeof(struct luo_file_set_ser))
+
 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),
 		.block_set = KHO_BLOCK_SET_INIT(luo_session_global.incoming.block_set,
-						sizeof(struct luo_session_ser)),
+						LUO_SESSION_SER_SIZE),
 	},
 	.outgoing = {
 		.list = LIST_HEAD_INIT(luo_session_global.outgoing.list),
 		.rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem),
 		.block_set = KHO_BLOCK_SET_INIT(luo_session_global.outgoing.block_set,
-						sizeof(struct luo_session_ser)),
+						LUO_SESSION_SER_SIZE),
 	},
 };
 
@@ -223,14 +226,16 @@ static void luo_session_unfreeze_one(struct luo_session *session,
 				     struct luo_session_ser *ser)
 {
 	guard(mutex)(&session->mutex);
-	luo_file_unfreeze(&session->file_set, &ser->file_set_ser);
+	luo_file_unfreeze(&session->file_set,
+			  KHOSER_LOAD_PTR(ser->file_set_ser));
 }
 
 static int luo_session_freeze_one(struct luo_session *session,
 				  struct luo_session_ser *ser)
 {
 	guard(mutex)(&session->mutex);
-	return luo_file_freeze(&session->file_set, &ser->file_set_ser);
+	return luo_file_freeze(&session->file_set,
+			       KHOSER_LOAD_PTR(ser->file_set_ser));
 }
 
 static int luo_session_release(struct inode *inodep, struct file *filep)
@@ -552,6 +557,17 @@ int __init luo_session_setup_incoming(u64 sessions_pa)
 	return 0;
 }
 
+static struct luo_session_ser *luo_session_ser_init(void *mem)
+{
+	struct luo_file_set_ser *file_set_ser;
+	struct luo_session_ser *ser = mem;
+
+	file_set_ser = mem + sizeof(struct luo_session_ser);
+	KHOSER_STORE_PTR(ser->file_set_ser, file_set_ser);
+
+	return ser;
+}
+
 static int luo_session_deserialize_one(struct luo_session_header *sh,
 				       struct luo_session_ser *ser)
 {
@@ -575,7 +591,7 @@ static int luo_session_deserialize_one(struct luo_session_header *sh,
 
 	scoped_guard(mutex, &session->mutex) {
 		err = luo_file_deserialize(&session->file_set,
-					   &ser->file_set_ser);
+					   KHOSER_LOAD_PTR(ser->file_set_ser));
 	}
 	if (err) {
 		pr_warn("Failed to deserialize files for session [%s] %pe\n",
@@ -657,6 +673,7 @@ int luo_session_serialize(void)
 			goto err_undo;
 		}
 
+		ser = luo_session_ser_init(ser);
 		err = luo_session_freeze_one(session, ser);
 		if (err) {
 			kho_block_set_it_prev(&it);
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 05/19] kho: Add feature header for fine-grained ABI compatibility
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
                   ` (3 preceding siblings ...)
  2026-11-10  5:01 ` [PATCH v4 04/19] liveupdate: Reference struct luo_file_set_ser " Tarun Sahu
@ 2026-11-10  5:01 ` Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 06/19] kho: kexec_metadata: Add the feature header Tarun Sahu
                   ` (13 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:01 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

Serialized structures in KHO and Live Update Orchestrator currently rely
on whole-structure compatibility strings or monolithic version fields to
validate handed-over state across kexec. Any change to a serialized
structure, even an optional additive field, requires bumping the
compatibility string or version number and breaks live update between
adjacent kernels.

Introduce a generic feature header (struct kho_ser_hdr) for serialized
binary structures in include/linux/kho/abi/feature.h. Each subsystem
groups the fields of its serialized structure under feature indices,
starting with index 0 for base fields. When a subsystem adds a new
capability, it appends new fields to the end of its payload structure
under a new feature index. Retired fields are kept in place so existing
field offsets never shift; if the type or format of a field needs to
change, the existing field and feature are kept intact until retired and
a new feature index with an appended field of the new type is added.
Only when a change requires a complete break in the serialized layout or
semantics that feature flags cannot handle, a new handler or format can
be introduced with an incremented version number.

Each feature belongs to either a required feature space or an optional
feature space. Both spaces are numbered independently starting from 0.
In the serialized header, required features occupy the first nr_required
bits, followed immediately by nr_optional bits for optional features:

  struct kho_ser_hdr {
          __u32 size;
          __u16 nr_required;
          __u16 nr_optional;
          __aligned_u64 flags[];
  };

A set bit indicates that the feature is present in the payload. The
truth table for deserialization:

  Source bit       Target            Action
  Not set          -                 Accept: feature absent
  Optional set     unknown or known  Accept: ignore or use the data
  Required set     known             Accept: use the feature data
  Required set     unknown           Reject: -EOPNOTSUPP

During deserialization, the target kernel only validates required
features: if the source set any required feature bit at or beyond the
known required feature count of the target kernel, validation fails with
-EOPNOTSUPP. Optional features are never rejected; the target kernel
simply checks whether known optional features are present before reading
their data.

No subsystem defines feature flags yet. Every converted structure is
registered with 0 required and 0 optional features, producing a compact
8-byte header with no flags entries. Feature enums can be added later as
needed, once the first additive feature is introduced for a particular
serialized structure. With nr_required set to 0, kho_ser_validate()
rejects any incoming required feature bit while ignoring optional bits,
allowing future kernels with additive features to evolve smoothly in
both directions.

Each subsystem registers its serialized structure with:

        DECLARE_KHO_SER(name, payload_size, nr_required, nr_optional)

This establishes compile-time assertions and provides type-checked
helpers: kho_ser_size() for total allocation size, kho_ser_init() to
initialize the header, kho_ser_payload() to access the typed payload at
its dynamic offset, and kho_ser_validate() to validate required features
during restoration. DECLARE_KHO_SER_HDR(name, *fhdr) declares a struct
kho_ser_hdr pointer while verifying at compile time that name was
registered with DECLARE_KHO_SER(), allowing callers to document which
structure a feature header pointer represents.

An example demonstrating how a hypothetical structure defines its
features:

  enum foo_ser_required {
          FOO_SER_REQ_BAR,
          __FOO_SER_NR_REQUIRED,
  };

  enum foo_ser_optional {
          FOO_SER_OPT_BAZ,
          __FOO_SER_NR_OPTIONAL,
  };

  struct foo_ser {
          __u64 base;     /* base fields, always present */
          __u64 bar;      /* FOO_SER_REQ_BAR */
          __u64 baz;      /* FOO_SER_OPT_BAZ */
  };

  DECLARE_KHO_SER(foo_ser, 24,
                  __FOO_SER_NR_REQUIRED, __FOO_SER_NR_OPTIONAL);

  /* Serialize */
  kho_ser_init(fhdr, foo_ser);
  ser = kho_ser_payload(fhdr, foo_ser);
  ser->bar = ...;
  kho_ser_set_required(fhdr, FOO_SER_REQ_BAR);
  ser->baz = ...;
  kho_ser_set_optional(fhdr, FOO_SER_OPT_BAZ);

  /* Restore */
  err = kho_ser_validate(fhdr, foo_ser);
  ser = kho_ser_payload(fhdr, foo_ser);
  if (kho_ser_has_required(fhdr, FOO_SER_REQ_BAR))
          use ser->bar;
  if (kho_ser_has_optional(fhdr, FOO_SER_OPT_BAZ))
          use ser->baz;

Required and optional features each have their own enum, numbered
independently from 0. New features are only appended at the end of their
enum, never reordered or reused. The last entry of each enum (e.g.
__FOO_SER_NR_REQUIRED) is the feature count passed to DECLARE_KHO_SER and
stays last. The fields of a new feature are appended at the end of the
payload. A reader accesses the fields of a feature only when its bit is set
in the incoming header (kho_ser_has_required() or kho_ser_has_optional()),
because an older source kernel does not serialize them. Use required for
data the target must understand to restore correctly, and optional for
data it can safely ignore. Variable length data that follows the payload,
such as an array of entries, starts at fhdr->size, the size recorded by the
source kernel, so it stays correct when a later feature appends fields to
the payload. Document that serialization helpers describe this kernel's
layout while deserialization helpers follow the incoming header.

Suggested-by: David Matlack <dmatlack@google.com>
Suggested-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 include/linux/kho/abi/feature.h | 372 ++++++++++++++++++++++++++++++++
 1 file changed, 372 insertions(+)
 create mode 100644 include/linux/kho/abi/feature.h

diff --git a/include/linux/kho/abi/feature.h b/include/linux/kho/abi/feature.h
new file mode 100644
index 000000000000..55ff1b9e9b73
--- /dev/null
+++ b/include/linux/kho/abi/feature.h
@@ -0,0 +1,372 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/**
+ * DOC: KHO Serialization Feature Header ABI
+ *
+ * Generic feature header for serialized binary data structures preserved
+ * across kexec via KHO or Live Update Orchestrator (LUO).
+ *
+ * Serialized structures start with an 8-byte base header (struct kho_ser_hdr)
+ * containing the total structure size (size, including header, flags, and
+ * payload) in bytes, the number of required features (nr_required), and the
+ * number of optional features (nr_optional). Following the header is a
+ * flexible array of 64-bit flags entries (flags[]) containing one bit per
+ * feature.
+ *
+ * Feature bits:
+ *   - Required feature N is at bit N of flags[].
+ *   - Optional feature N is at bit (nr_required + N) of flags[].
+ *   - The number of __u64 entries in flags[] is
+ *     DIV_ROUND_UP(nr_required + nr_optional, 64).
+ *   - When nr_required == 0 and nr_optional == 0, flags[] has zero entries
+ *     and the header is exactly 8 bytes.
+ *
+ * A set bit indicates that the feature is present in the payload.
+ *
+ * Deserialization truth table:
+ *
+ *   Source bit       Target            Action
+ *   ----------       ------            ------
+ *   Not set          -                 Accept: feature absent
+ *   Optional set     unknown or known  Accept: ignore or use the data
+ *   Required set     known             Accept: use the feature data
+ *   Required set     unknown           Reject: -EOPNOTSUPP
+ *
+ * Validation trusts the incoming header: only set required feature bits at or
+ * beyond the target's known required feature count are rejected (-EOPNOTSUPP).
+ * Optional feature bits are never validated.
+ *
+ * Defining features:
+ *
+ * A serialized structure defines its features using enums and registers with
+ * DECLARE_KHO_SER()::
+ *
+ *   enum foo_ser_required {
+ *           FOO_SER_REQ_BAR,
+ *           __FOO_SER_NR_REQUIRED,
+ *   };
+ *
+ *   enum foo_ser_optional {
+ *           FOO_SER_OPT_BAZ,
+ *           __FOO_SER_NR_OPTIONAL,
+ *   };
+ *
+ *   struct foo_ser {
+ *           __u64 base;     // base fields, always present
+ *           __u64 bar;      // FOO_SER_REQ_BAR
+ *           __u64 baz;      // FOO_SER_OPT_BAZ
+ *   };
+ *
+ *   DECLARE_KHO_SER(foo_ser, 24,
+ *                   __FOO_SER_NR_REQUIRED, __FOO_SER_NR_OPTIONAL);
+ *
+ *   // Serialize
+ *   kho_ser_init(fhdr, foo_ser);
+ *   ser = kho_ser_payload(fhdr, foo_ser);
+ *   ser->bar = ...;
+ *   kho_ser_set_required(fhdr, FOO_SER_REQ_BAR);
+ *   ser->baz = ...;
+ *   kho_ser_set_optional(fhdr, FOO_SER_OPT_BAZ);
+ *
+ *   // Restore
+ *   err = kho_ser_validate(fhdr, foo_ser);
+ *   ser = kho_ser_payload(fhdr, foo_ser);
+ *   if (kho_ser_has_required(fhdr, FOO_SER_REQ_BAR))
+ *           use ser->bar;
+ *   if (kho_ser_has_optional(fhdr, FOO_SER_OPT_BAZ))
+ *           use ser->baz;
+ *
+ * Rules:
+ *   - Required and optional features each have their own enum, numbered
+ *     independently from 0.
+ *   - New features are only appended at the end of their enum, never
+ *     reordered or reused.
+ *   - The last entry of each enum (e.g. __FOO_SER_NR_REQUIRED) is the
+ *     feature count passed to DECLARE_KHO_SER and stays last.
+ *   - The fields of a new feature are appended at the end of the payload.
+ *   - A reader accesses the fields of a feature only when its bit is set in
+ *     the incoming header (kho_ser_has_required() or
+ *     kho_ser_has_optional()), because an older source kernel does not
+ *     serialize them.
+ *   - Use required for data the target must understand to restore
+ *     correctly, and optional for data it can safely ignore.
+ *   - Variable length data that follows the payload, such as an array of
+ *     entries, starts at fhdr->size, the size recorded by the source kernel,
+ *     so it stays correct when a later feature appends fields to the
+ *     payload.
+ *
+ * Writing and reading:
+ *
+ * kho_ser_size(), kho_ser_init(), kho_ser_set_required() and
+ * kho_ser_set_optional() (and the clear variants) use the values this
+ * kernel registered with DECLARE_KHO_SER(), so they describe this
+ * kernel's layout and are used when serializing.
+ *
+ * A reader of handed-over data uses only the incoming header:
+ * kho_ser_validate(), kho_ser_hdr_size(), kho_ser_payload(),
+ * kho_ser_has_required(), kho_ser_has_optional() and fhdr->size, so
+ * it follows the layout of the kernel that wrote the data. On data
+ * this kernel serialized, these give the same result.
+ *
+ * Use DECLARE_KHO_SER_HDR(name, *fhdr) (or const
+ * DECLARE_KHO_SER_HDR(name, *fhdr)) to document which registered
+ * structure a feature header pointer belongs to while verifying at
+ * compile time that the structure is registered with DECLARE_KHO_SER().
+ */
+
+#ifndef _LINUX_KHO_ABI_FEATURE_H
+#define _LINUX_KHO_ABI_FEATURE_H
+
+#include <linux/build_bug.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/math.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+/**
+ * struct kho_ser_hdr - Header for serialized data structures in KHO.
+ * @size:        Total size in bytes (header + flags + payload).
+ * @nr_required: Number of required features known to the source kernel.
+ * @nr_optional: Number of optional features known to the source kernel.
+ * @flags:       Bitmask of present features (required, then optional).
+ */
+struct kho_ser_hdr {
+	__u32 size;
+	__u16 nr_required;
+	__u16 nr_optional;
+	__aligned_u64 flags[];
+};
+
+static_assert(sizeof(struct kho_ser_hdr) == 8);
+static_assert(__alignof__(struct kho_ser_hdr) == 8);
+
+#define KHO_SER_NR_FLAGS(nr_required, nr_optional) \
+	DIV_ROUND_UP((__u32)(nr_required) + (__u32)(nr_optional), 64)
+
+/**
+ * kho_ser_hdr_size - Calculate header size in bytes including flags.
+ * @fhdr: Pointer to the serialization header.
+ *
+ * Return: Size of the header and flags array in bytes.
+ */
+static inline size_t kho_ser_hdr_size(const struct kho_ser_hdr *fhdr)
+{
+	return sizeof(*fhdr) + sizeof(__u64) *
+		KHO_SER_NR_FLAGS(fhdr->nr_required, fhdr->nr_optional);
+}
+
+/**
+ * kho_ser_set_required - Mark a required feature present in the payload.
+ * @fhdr:  Pointer to the serialization header.
+ * @feat: Required feature index to set.
+ */
+static inline void kho_ser_set_required(struct kho_ser_hdr *fhdr, unsigned int feat)
+{
+	if (feat < fhdr->nr_required)
+		fhdr->flags[feat / 64] |= 1ULL << (feat % 64);
+}
+
+/**
+ * kho_ser_clear_required - Clear a required feature in the payload.
+ * @fhdr:  Pointer to the serialization header.
+ * @feat: Required feature index to clear.
+ */
+static inline void kho_ser_clear_required(struct kho_ser_hdr *fhdr, unsigned int feat)
+{
+	if (feat < fhdr->nr_required)
+		fhdr->flags[feat / 64] &= ~(1ULL << (feat % 64));
+}
+
+/**
+ * kho_ser_set_optional - Mark an optional feature present in the payload.
+ * @fhdr:  Pointer to the serialization header.
+ * @feat: Optional feature index to set.
+ */
+static inline void kho_ser_set_optional(struct kho_ser_hdr *fhdr, unsigned int feat)
+{
+	if (feat < fhdr->nr_optional) {
+		unsigned int bit = (unsigned int)fhdr->nr_required + feat;
+
+		fhdr->flags[bit / 64] |= 1ULL << (bit % 64);
+	}
+}
+
+/**
+ * kho_ser_clear_optional - Clear an optional feature in the payload.
+ * @fhdr:  Pointer to the serialization header.
+ * @feat: Optional feature index to clear.
+ */
+static inline void kho_ser_clear_optional(struct kho_ser_hdr *fhdr, unsigned int feat)
+{
+	if (feat < fhdr->nr_optional) {
+		unsigned int bit = (unsigned int)fhdr->nr_required + feat;
+
+		fhdr->flags[bit / 64] &= ~(1ULL << (bit % 64));
+	}
+}
+
+/**
+ * kho_ser_has_required - Check if a required feature is present in the payload.
+ * @fhdr:  Pointer to the serialization header.
+ * @feat: Required feature index to check.
+ *
+ * Return: True if the feature is present, false otherwise or if the index
+ *         exceeds the header's known required feature count.
+ */
+static inline bool kho_ser_has_required(const struct kho_ser_hdr *fhdr,
+					unsigned int feat)
+{
+	if (feat >= fhdr->nr_required)
+		return false;
+	return !!(fhdr->flags[feat / 64] & (1ULL << (feat % 64)));
+}
+
+/**
+ * kho_ser_has_optional - Check if an optional feature is present in the payload.
+ * @fhdr:  Pointer to the serialization header.
+ * @feat: Optional feature index to check.
+ *
+ * Return: True if the feature is present, false otherwise or if the index
+ *         exceeds the header's known optional feature count.
+ */
+static inline bool kho_ser_has_optional(const struct kho_ser_hdr *fhdr,
+					unsigned int feat)
+{
+	unsigned int bit;
+
+	if (feat >= fhdr->nr_optional)
+		return false;
+	bit = (unsigned int)fhdr->nr_required + feat;
+	return !!(fhdr->flags[bit / 64] & (1ULL << (bit % 64)));
+}
+
+/**
+ * __kho_ser_init - Initialize a serialization header and flags.
+ * @fhdr:          Pointer to the memory for the serialization header.
+ * @payload_size: Size of the payload structure in bytes.
+ * @nr_required:  Number of required features known by the caller.
+ * @nr_optional:  Number of optional features known by the caller.
+ */
+static inline void __kho_ser_init(struct kho_ser_hdr *fhdr, __u32 payload_size,
+				  __u16 nr_required, __u16 nr_optional)
+{
+	__u32 nr_flags = KHO_SER_NR_FLAGS(nr_required, nr_optional);
+
+	fhdr->size = sizeof(*fhdr) + sizeof(__u64) * nr_flags + payload_size;
+	fhdr->nr_required = nr_required;
+	fhdr->nr_optional = nr_optional;
+	memset(fhdr->flags, 0, sizeof(__u64) * nr_flags);
+}
+
+/**
+ * __kho_ser_payload - Return pointer to the payload following the header.
+ * @fhdr: Pointer to the serialization header.
+ *
+ * Return: Pointer to payload memory immediately after the flags array.
+ */
+static inline void *__kho_ser_payload(const struct kho_ser_hdr *fhdr)
+{
+	return (void *)((const char *)fhdr + kho_ser_hdr_size(fhdr));
+}
+
+/**
+ * __kho_ser_validate - Validate an incoming header against target capabilities.
+ * @fhdr:                Pointer to the serialization header.
+ * @target_nr_required: Number of required features supported by the target.
+ *
+ * Checks that the source did not set any required feature bits that the target
+ * kernel does not support (index >= @target_nr_required).
+ *
+ * Return: 0 if all required features are supported, or -EOPNOTSUPP on failure.
+ */
+static inline int __kho_ser_validate(const struct kho_ser_hdr *fhdr,
+				     __u16 target_nr_required)
+{
+	unsigned int i;
+
+	for (i = target_nr_required; i < fhdr->nr_required; i++) {
+		if (fhdr->flags[i / 64] & (1ULL << (i % 64)))
+			return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+/**
+ * DECLARE_KHO_SER - Register a serialized payload structure with KHO.
+ * @name:         Structure tag name (e.g. luo_ser).
+ * @payload_size: Compile-time expected size of struct @name in bytes.
+ * @nr_required:  Total number of defined required features.
+ * @nr_optional:  Total number of defined optional features.
+ *
+ * Size and 8-byte alignment are asserted under CONFIG_64BIT: KHO and LUO are
+ * 64-bit only, while !CONFIG_KEXEC_HANDOVER stubs pull ABI headers into
+ * 32-bit builds where u64 has 4-byte or 2-byte alignment.
+ */
+#define DECLARE_KHO_SER(name, payload_size, nr_required, nr_optional)	\
+	static_assert(!IS_ENABLED(CONFIG_64BIT) ||			\
+		      sizeof(struct name) == (payload_size),		\
+		      "struct " #name " size mismatch");		\
+	static_assert(!IS_ENABLED(CONFIG_64BIT) ||			\
+		      __alignof__(struct name) == 8,			\
+		      "struct " #name " must be 8-byte aligned");	\
+	extern struct kho_ser_hdr __kho_ser_hdr_##name;			\
+	enum {								\
+		__KHO_SER_SIZE_##name = (payload_size),			\
+		__KHO_SER_NR_REQUIRED_##name = (nr_required),		\
+		__KHO_SER_NR_OPTIONAL_##name = (nr_optional),		\
+	}
+
+/**
+ * DECLARE_KHO_SER_HDR - Declare a feature header variable, field, or parameter.
+ * @name: Structure tag name registered with DECLARE_KHO_SER().
+ * @decl: Declarator (e.g. *fhdr, **out_fhdr, or * inside DECLARE_KHOSER_PTR).
+ *
+ * Declares @decl with type struct kho_ser_hdr while checking at compile time
+ * that @name was registered with DECLARE_KHO_SER(), documenting which
+ * serialized payload structure a feature header pointer belongs to.
+ */
+#define DECLARE_KHO_SER_HDR(name, decl)	typeof(__kho_ser_hdr_##name) decl
+
+/**
+ * kho_ser_size - Total size of header, flags, and payload for a structure.
+ * @name: Structure tag name registered with DECLARE_KHO_SER().
+ */
+#define kho_ser_size(name)						\
+	(sizeof(struct kho_ser_hdr) +					\
+	 sizeof(__u64) * KHO_SER_NR_FLAGS(__KHO_SER_NR_REQUIRED_##name,	\
+					  __KHO_SER_NR_OPTIONAL_##name) +\
+	 __KHO_SER_SIZE_##name)
+
+/**
+ * kho_ser_init - Initialize a serialization header for a registered structure.
+ * @fhdr:  Pointer to allocated memory.
+ * @name: Structure tag name registered with DECLARE_KHO_SER().
+ */
+#define kho_ser_init(fhdr, name)						\
+	__kho_ser_init((fhdr), __KHO_SER_SIZE_##name,			\
+		       __KHO_SER_NR_REQUIRED_##name,			\
+		       __KHO_SER_NR_OPTIONAL_##name)
+
+/**
+ * kho_ser_payload - Return typed pointer to payload following the header.
+ * @fhdr:  Pointer to the serialization header.
+ * @name: Structure tag name registered with DECLARE_KHO_SER().
+ *
+ * The first operand only fails the build when DECLARE_KHO_SER(name) is
+ * missing.
+ */
+#define kho_ser_payload(fhdr, name)					\
+	((void)__KHO_SER_SIZE_##name,					\
+	 (struct name *)__kho_ser_payload(fhdr))
+
+/**
+ * kho_ser_validate - Validate incoming required features for a structure.
+ * @fhdr:  Pointer to the incoming serialization header.
+ * @name: Structure tag name registered with DECLARE_KHO_SER().
+ */
+#define kho_ser_validate(fhdr, name)					\
+	__kho_ser_validate((fhdr), __KHO_SER_NR_REQUIRED_##name)
+
+#endif /* _LINUX_KHO_ABI_FEATURE_H */
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 06/19] kho: kexec_metadata: Add the feature header
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
                   ` (4 preceding siblings ...)
  2026-11-10  5:01 ` [PATCH v4 05/19] kho: Add feature header for fine-grained ABI compatibility Tarun Sahu
@ 2026-11-10  5:01 ` Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 07/19] kho: vmalloc: " Tarun Sahu
                   ` (12 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:01 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

The kexec metadata subtree currently uses a monolithic u32 version field
to identify the metadata structure layout. Any change to the structure,
even an optional additive field, requires incrementing the version and
breaks compatibility between adjacent kernels.

Replace the u32 version field in struct kho_kexec_metadata with
struct kho_ser_hdr using DECLARE_KHO_SER. Later fields can be appended
under new feature indices without breaking compatibility across kexec.
Also make the 3-byte padding hole between previous_release and kexec_count
explicit as reserved[3].

During deserialization, validate the blob size against the header size
and reject unknown required features. A legacy v1 metadata blob is
ignored with a warning.

Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 include/linux/kho/abi/kexec_metadata.h | 17 +++++++-----
 kernel/liveupdate/kexec_handover.c     | 38 ++++++++++++++------------
 2 files changed, 31 insertions(+), 24 deletions(-)

diff --git a/include/linux/kho/abi/kexec_metadata.h b/include/linux/kho/abi/kexec_metadata.h
index e9e3f7e38a7c..316b3e5ad01b 100644
--- a/include/linux/kho/abi/kexec_metadata.h
+++ b/include/linux/kho/abi/kexec_metadata.h
@@ -18,28 +18,31 @@
 #ifndef _LINUX_KHO_ABI_KEXEC_METADATA_H
 #define _LINUX_KHO_ABI_KEXEC_METADATA_H
 
+#include <linux/build_bug.h>
+#include <linux/kho/abi/feature.h>
 #include <linux/types.h>
 #include <linux/utsname.h>
 
-#define KHO_KEXEC_METADATA_VERSION 1
-
 /**
  * struct kho_kexec_metadata - Kexec metadata passed between kernels
- * @version: ABI version of this struct (must be first field)
  * @previous_release: Kernel version string that initiated the kexec
+ * @reserved: Reserved for structure alignment; must be zero
  * @kexec_count: Number of kexec boots since last cold boot
  *
- * This structure is preserved across kexec and allows the new kernel to
+ * This structure is preserved across kexec and allows the target kernel to
  * identify which kernel it was booted from and how many kexec reboots
- * have occurred.
+ * have occurred. Preceded by &struct kho_ser_hdr.
  *
  * __NEW_UTS_LEN is part of uABI, so it safe to use it in here.
  */
 struct kho_kexec_metadata {
-	u32 version;
 	char previous_release[__NEW_UTS_LEN + 1];
+	u8 reserved[3];
 	u32 kexec_count;
-} __packed;
+} __aligned(8);
+
+/* Register kho_kexec_metadata with KHO serialization accessors */
+DECLARE_KHO_SER(kho_kexec_metadata, 72, 0, 0);
 
 #define KHO_METADATA_NODE_NAME "kexec-metadata"
 
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 4b1d173d48ff..641a3b4c88fb 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -1762,6 +1762,7 @@ static __init int kho_out_fdt_setup(void)
 
 static void __init kho_in_kexec_metadata(void)
 {
+	DECLARE_KHO_SER_HDR(kho_kexec_metadata, *fhdr);
 	struct kho_kexec_metadata *metadata;
 	phys_addr_t metadata_phys;
 	size_t blob_size;
@@ -1773,27 +1774,28 @@ static void __init kho_in_kexec_metadata(void)
 		/* This is fine, previous kernel didn't export metadata */
 		return;
 
-	/* Check that, at least, "version" is present */
-	if (blob_size < sizeof(u32)) {
+	if (blob_size < sizeof(*fhdr)) {
 		pr_warn("kexec-metadata blob too small (%zu bytes)\n",
 			blob_size);
 		return;
 	}
 
-	metadata = phys_to_virt(metadata_phys);
-
-	if (metadata->version != KHO_KEXEC_METADATA_VERSION) {
-		pr_warn("kexec-metadata version %u not supported (expected %u)\n",
-			metadata->version, KHO_KEXEC_METADATA_VERSION);
+	fhdr = phys_to_virt(metadata_phys);
+	if (fhdr->size != blob_size) {
+		pr_warn("kexec-metadata size invalid (%u != %zu), ignoring metadata\n",
+			fhdr->size, blob_size);
 		return;
 	}
 
-	if (blob_size < sizeof(*metadata)) {
-		pr_warn("kexec-metadata blob too small for v%u (%zu < %zu)\n",
-			metadata->version, blob_size, sizeof(*metadata));
+	err = kho_ser_validate(fhdr, kho_kexec_metadata);
+	if (err) {
+		pr_warn("kexec-metadata feature validation failed: %pe\n",
+			ERR_PTR(err));
 		return;
 	}
 
+	metadata = kho_ser_payload(fhdr, kho_kexec_metadata);
+
 	/*
 	 * Copy data to the kernel structure that will persist during
 	 * kernel lifetime.
@@ -1813,23 +1815,25 @@ static void __init kho_in_kexec_metadata(void)
  */
 static __init int kho_out_kexec_metadata(void)
 {
+	DECLARE_KHO_SER_HDR(kho_kexec_metadata, *fhdr);
 	struct kho_kexec_metadata *metadata;
 	int err;
 
-	metadata = kho_alloc_preserve(sizeof(*metadata));
-	if (IS_ERR(metadata))
-		return PTR_ERR(metadata);
+	fhdr = kho_alloc_preserve(kho_ser_size(kho_kexec_metadata));
+	if (IS_ERR(fhdr))
+		return PTR_ERR(fhdr);
+	kho_ser_init(fhdr, kho_kexec_metadata);
 
-	metadata->version = KHO_KEXEC_METADATA_VERSION;
+	metadata = kho_ser_payload(fhdr, kho_kexec_metadata);
 	strscpy(metadata->previous_release, init_uts_ns.name.release,
 		sizeof(metadata->previous_release));
+	memset(metadata->reserved, 0, sizeof(metadata->reserved));
 	/* kho_in.kexec_count is set to 0 on cold boot */
 	metadata->kexec_count = kho_in.kexec_count + 1;
 
-	err = kho_add_subtree(KHO_METADATA_NODE_NAME, metadata,
-			      sizeof(*metadata));
+	err = kho_add_subtree(KHO_METADATA_NODE_NAME, fhdr, fhdr->size);
 	if (err)
-		kho_unpreserve_free(metadata);
+		kho_unpreserve_free(fhdr);
 
 	return err;
 }
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 07/19] kho: vmalloc: Add the feature header
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
                   ` (5 preceding siblings ...)
  2026-11-10  5:01 ` [PATCH v4 06/19] kho: kexec_metadata: Add the feature header Tarun Sahu
@ 2026-11-10  5:01 ` Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 08/19] kho: block: Add the feature header to blocks Tarun Sahu
                   ` (11 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:01 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

Register struct kho_vmalloc with the KHO serialization feature header
mechanism to allow future additive fields or feature extensions without
breaking live update ABI compatibility.

Add DECLARE_KHO_SER(kho_vmalloc, 16, 0, 0) to register the 16-byte
structure with 0 required and 0 optional features. Update function
prototypes in include/linux/kexec_handover.h to take
DECLARE_KHO_SER_HDR(kho_vmalloc, *fhdr) parameters, documenting which
registered structure the feature header belongs to while enforcing
compile-time type verification.

In kho_preserve_vmalloc(), initialize the feature header with
kho_ser_init() and access the payload fields via kho_ser_payload().
In kho_restore_vmalloc(), validate incoming required features with
kho_ser_validate() and access payload fields via kho_ser_payload().
In kho_unpreserve_vmalloc(), access payload fields via kho_ser_payload().
The chunk page format and traversal remain unchanged.

Update memfd_luo and test_kho to pass typed feature header pointers
and use kho_ser_size() for allocation sizing.

Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 include/linux/kexec_handover.h         | 15 +++----
 include/linux/kho/abi/kexec_handover.h |  5 +--
 include/linux/kho/abi/memfd.h          |  8 ++--
 kernel/liveupdate/kexec_handover.c     | 60 ++++++++++++++++++--------
 lib/test_kho.c                         | 35 +++++++--------
 mm/memfd_luo.c                         | 21 ++++-----
 6 files changed, 83 insertions(+), 61 deletions(-)

diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
index ef2b188c8ed0..b52e7a5173f8 100644
--- a/include/linux/kexec_handover.h
+++ b/include/linux/kexec_handover.h
@@ -6,10 +6,9 @@
 #include <linux/errno.h>
 #include <linux/types.h>
 #include <linux/mm.h>
+#include <linux/kho/abi/kexec_handover.h>
 #include <asm-generic/kexec_handover.h>
 
-struct kho_vmalloc;
-
 struct folio;
 struct page;
 
@@ -21,14 +20,14 @@ int kho_preserve_folio(struct folio *folio);
 void kho_unpreserve_folio(struct folio *folio);
 int kho_preserve_pages(struct page *page, unsigned long nr_pages);
 void kho_unpreserve_pages(struct page *page, unsigned long nr_pages);
-int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation);
-void kho_unpreserve_vmalloc(struct kho_vmalloc *preservation);
+int kho_preserve_vmalloc(void *ptr, DECLARE_KHO_SER_HDR(kho_vmalloc, *fhdr));
+void kho_unpreserve_vmalloc(DECLARE_KHO_SER_HDR(kho_vmalloc, *fhdr));
 void *kho_alloc_preserve(size_t size);
 void kho_unpreserve_free(void *mem);
 void kho_restore_free(void *mem);
 struct folio *kho_restore_folio(phys_addr_t phys);
 struct page *kho_restore_pages(phys_addr_t phys, unsigned long nr_pages);
-void *kho_restore_vmalloc(const struct kho_vmalloc *preservation);
+void *kho_restore_vmalloc(const DECLARE_KHO_SER_HDR(kho_vmalloc, *fhdr));
 int kho_add_subtree(const char *name, void *blob, size_t size);
 void kho_remove_subtree(void *blob);
 int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size);
@@ -74,12 +73,12 @@ static inline int kho_preserve_pages(struct page *page, unsigned int nr_pages)
 static inline void kho_unpreserve_pages(struct page *page, unsigned int nr_pages) { }
 
 static inline int kho_preserve_vmalloc(void *ptr,
-				       struct kho_vmalloc *preservation)
+				       DECLARE_KHO_SER_HDR(kho_vmalloc, *fhdr))
 {
 	return -EOPNOTSUPP;
 }
 
-static inline void kho_unpreserve_vmalloc(struct kho_vmalloc *preservation) { }
+static inline void kho_unpreserve_vmalloc(DECLARE_KHO_SER_HDR(kho_vmalloc, *fhdr)) { }
 
 static inline void *kho_alloc_preserve(size_t size)
 {
@@ -100,7 +99,7 @@ static inline struct page *kho_restore_pages(phys_addr_t phys,
 	return NULL;
 }
 
-static inline void *kho_restore_vmalloc(const struct kho_vmalloc *preservation)
+static inline void *kho_restore_vmalloc(const DECLARE_KHO_SER_HDR(kho_vmalloc, *fhdr))
 {
 	return NULL;
 }
diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h
index bee75a7006e8..f9b9b18f5820 100644
--- a/include/linux/kho/abi/kexec_handover.h
+++ b/include/linux/kho/abi/kexec_handover.h
@@ -13,6 +13,7 @@
 #include <linux/bits.h>
 #include <linux/build_bug.h>
 #include <linux/kconfig.h>
+#include <linux/kho/abi/feature.h>
 #include <linux/log2.h>
 #include <linux/math.h>
 #include <linux/types.h>
@@ -179,9 +180,7 @@ struct kho_vmalloc {
 	unsigned short order;
 };
 
-static_assert(sizeof(struct kho_vmalloc) == 16);
-static_assert(!IS_ENABLED(CONFIG_64BIT) ||
-	      __alignof__(struct kho_vmalloc) == 8);
+DECLARE_KHO_SER(kho_vmalloc, 16, 0, 0);
 
 /**
  * DOC: KHO persistent memory tracker
diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h
index 7b49dd6d7e2a..b0597c438973 100644
--- a/include/linux/kho/abi/memfd.h
+++ b/include/linux/kho/abi/memfd.h
@@ -80,9 +80,9 @@ static_assert(!IS_ENABLED(CONFIG_64BIT) ||
  * @seals:     The seals present on the memfd. The seals are uABI so it is safe
  *             to directly use them in the ABI.
  * @flags:     Flags for the file. Unused flag bits must be set to 0.
- * @nr_folios: Number of folios in the folios array.
- * @folios:    Pointer to KHO vmalloc descriptor for the array of
- *             struct memfd_luo_folio_ser.
+ * @nr_folios:   Number of folios in the folios array.
+ * @folios_fhdr: Pointer to KHO vmalloc descriptor for the array of
+ *               struct memfd_luo_folio_ser.
  */
 struct memfd_luo_ser {
 	u64 pos;
@@ -90,7 +90,7 @@ struct memfd_luo_ser {
 	u32 seals;
 	u32 flags;
 	u64 nr_folios;
-	DECLARE_KHOSER_PTR(folios, struct kho_vmalloc *);
+	DECLARE_KHOSER_PTR(folios_fhdr, DECLARE_KHO_SER_HDR(kho_vmalloc, *));
 };
 
 static_assert(sizeof(struct memfd_luo_ser) == 40);
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 641a3b4c88fb..6e428df717cc 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -1378,20 +1378,20 @@ static void kho_vmalloc_unpreserve_chunk(struct kho_vmalloc_chunk *chunk,
 /**
  * kho_preserve_vmalloc - preserve memory allocated with vmalloc() across kexec
  * @ptr: pointer to the area in vmalloc address space
- * @preservation: placeholder for preservation metadata
+ * @fhdr:         placeholder for preservation metadata
  *
  * Instructs KHO to preserve the area in vmalloc address space at @ptr. The
  * physical pages mapped at @ptr will be preserved and on successful return
- * @preservation will hold the physical address of a structure that describes
- * the preservation.
+ * @fhdr will hold the preservation metadata.
  *
  * NOTE: The memory allocated with vmalloc_node() variants cannot be reliably
  * restored on the same node
  *
  * Return: 0 on success, error code on failure
  */
-int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation)
+int kho_preserve_vmalloc(void *ptr, DECLARE_KHO_SER_HDR(kho_vmalloc, *fhdr))
 {
+	struct kho_vmalloc *vmalloc_ser;
 	struct kho_vmalloc_chunk *chunk;
 	struct vm_struct *vm = find_vm_area(ptr);
 	unsigned int order, flags, nr_contig_pages;
@@ -1404,13 +1404,18 @@ int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation)
 	if (vm->flags & ~KHO_VMALLOC_SUPPORTED_FLAGS)
 		return -EOPNOTSUPP;
 
+	kho_ser_init(fhdr, kho_vmalloc);
+
+	vmalloc_ser = kho_ser_payload(fhdr, kho_vmalloc);
+
 	flags = vmalloc_flags_to_kho(vm->flags);
 	order = get_vm_area_page_order(vm);
+	vmalloc_ser->order = order;
 
 	chunk = new_vmalloc_chunk(NULL);
 	if (!chunk)
 		return -ENOMEM;
-	KHOSER_STORE_PTR(preservation->first, chunk);
+	KHOSER_STORE_PTR(vmalloc_ser->first, chunk);
 
 	nr_contig_pages = (1 << order);
 	for (int i = 0; i < vm->nr_pages; i += nr_contig_pages) {
@@ -1431,33 +1436,39 @@ int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation)
 		}
 	}
 
-	preservation->total_pages = vm->nr_pages;
-	preservation->flags = flags;
-	preservation->order = order;
+	vmalloc_ser->total_pages = vm->nr_pages;
+	vmalloc_ser->flags = flags;
 
 	return 0;
 
 err_free:
-	kho_unpreserve_vmalloc(preservation);
+	kho_unpreserve_vmalloc(fhdr);
 	return err;
 }
 EXPORT_SYMBOL_GPL(kho_preserve_vmalloc);
 
 /**
  * kho_unpreserve_vmalloc - unpreserve memory allocated with vmalloc()
- * @preservation: preservation metadata returned by kho_preserve_vmalloc()
+ * @fhdr:         preservation metadata returned by kho_preserve_vmalloc()
  *
  * Instructs KHO to unpreserve the area in vmalloc address space that was
  * previously preserved with kho_preserve_vmalloc().
  */
-void kho_unpreserve_vmalloc(struct kho_vmalloc *preservation)
+void kho_unpreserve_vmalloc(DECLARE_KHO_SER_HDR(kho_vmalloc, *fhdr))
 {
-	struct kho_vmalloc_chunk *chunk = KHOSER_LOAD_PTR(preservation->first);
+	struct kho_vmalloc *vmalloc_ser;
+	struct kho_vmalloc_chunk *chunk;
+
+	if (!fhdr)
+		return;
+
+	vmalloc_ser = kho_ser_payload(fhdr, kho_vmalloc);
+	chunk = KHOSER_LOAD_PTR(vmalloc_ser->first);
 
 	while (chunk) {
 		struct kho_vmalloc_chunk *tmp = chunk;
 
-		kho_vmalloc_unpreserve_chunk(chunk, preservation->order);
+		kho_vmalloc_unpreserve_chunk(chunk, vmalloc_ser->order);
 
 		chunk = KHOSER_LOAD_PTR(chunk->hdr.next);
 		free_page((unsigned long)tmp);
@@ -1468,16 +1479,17 @@ EXPORT_SYMBOL_GPL(kho_unpreserve_vmalloc);
 /**
  * kho_restore_vmalloc - recreates and populates an area in vmalloc address
  * space from the preserved memory.
- * @preservation: preservation metadata.
+ * @fhdr:         preservation metadata.
  *
  * Recreates an area in vmalloc address space and populates it with memory that
  * was preserved using kho_preserve_vmalloc().
  *
  * Return: pointer to the area in the vmalloc address space, NULL on failure.
  */
-void *kho_restore_vmalloc(const struct kho_vmalloc *preservation)
+void *kho_restore_vmalloc(const DECLARE_KHO_SER_HDR(kho_vmalloc, *fhdr))
 {
-	struct kho_vmalloc_chunk *chunk = KHOSER_LOAD_PTR(preservation->first);
+	const struct kho_vmalloc *vmalloc_ser;
+	struct kho_vmalloc_chunk *chunk;
 	kasan_vmalloc_flags_t kasan_flags = KASAN_VMALLOC_PROT_NORMAL;
 	unsigned int align, order, shift, vm_flags;
 	unsigned long total_pages, contig_pages;
@@ -1487,19 +1499,29 @@ void *kho_restore_vmalloc(const struct kho_vmalloc *preservation)
 	unsigned int idx = 0;
 	int err;
 
-	vm_flags = kho_flags_to_vmalloc(preservation->flags);
+	if (!fhdr)
+		return NULL;
+
+	err = kho_ser_validate(fhdr, kho_vmalloc);
+	if (err)
+		return NULL;
+
+	vmalloc_ser = kho_ser_payload(fhdr, kho_vmalloc);
+
+	vm_flags = kho_flags_to_vmalloc(vmalloc_ser->flags);
 	if (vm_flags & ~KHO_VMALLOC_SUPPORTED_FLAGS)
 		return NULL;
 
-	total_pages = preservation->total_pages;
+	total_pages = vmalloc_ser->total_pages;
 	pages = kvmalloc_objs(*pages, total_pages);
 	if (!pages)
 		return NULL;
-	order = preservation->order;
+	order = vmalloc_ser->order;
 	contig_pages = (1 << order);
 	shift = PAGE_SHIFT + order;
 	align = 1 << shift;
 
+	chunk = KHOSER_LOAD_PTR(vmalloc_ser->first);
 	while (chunk) {
 		struct page *page;
 
diff --git a/lib/test_kho.c b/lib/test_kho.c
index cd0f183ff68e..cfd80b8b3c6d 100644
--- a/lib/test_kho.c
+++ b/lib/test_kho.c
@@ -34,7 +34,7 @@ struct kho_test_state {
 	unsigned int nr_folios;
 	struct folio **folios;
 	phys_addr_t *folios_info;
-	DECLARE_KHOSER_PTR(folios_info_phys, struct kho_vmalloc *);
+	DECLARE_KHOSER_PTR(folios_info_fhdr, DECLARE_KHO_SER_HDR(kho_vmalloc, *));
 	int nr_folios_preserved;
 	struct folio *fdt;
 	__wsum csum;
@@ -47,8 +47,8 @@ static void kho_test_unpreserve_data(struct kho_test_state *state)
 	for (int i = 0; i < state->nr_folios_preserved; i++)
 		kho_unpreserve_folio(state->folios[i]);
 
-	if (KHOSER_LOAD_PTR(state->folios_info_phys))
-		kho_unpreserve_vmalloc(KHOSER_LOAD_PTR(state->folios_info_phys));
+	if (KHOSER_LOAD_PTR(state->folios_info_fhdr))
+		kho_unpreserve_vmalloc(KHOSER_LOAD_PTR(state->folios_info_fhdr));
 	vfree(state->folios_info);
 }
 
@@ -61,7 +61,7 @@ static int kho_test_preserve_data(struct kho_test_state *state)
 	if (!folios_info)
 		return -ENOMEM;
 
-	err = kho_preserve_vmalloc(folios_info, KHOSER_LOAD_PTR(state->folios_info_phys));
+	err = kho_preserve_vmalloc(folios_info, KHOSER_LOAD_PTR(state->folios_info_fhdr));
 	if (err)
 		goto err_free_info;
 
@@ -109,8 +109,8 @@ static int kho_test_prepare_fdt(struct kho_test_state *state, ssize_t fdt_size)
 	err |= fdt_begin_node(fdt, "data");
 	err |= fdt_property(fdt, "nr_folios", &state->nr_folios,
 			    sizeof(state->nr_folios));
-	err |= fdt_property(fdt, "folios_info", &state->folios_info_phys,
-			    sizeof(state->folios_info_phys));
+	err |= fdt_property(fdt, "folios_info", &state->folios_info_fhdr,
+			    sizeof(state->folios_info_fhdr));
 	err |= fdt_property(fdt, "csum", &state->csum, sizeof(state->csum));
 	err |= fdt_end_node(fdt);
 
@@ -122,18 +122,18 @@ static int kho_test_prepare_fdt(struct kho_test_state *state, ssize_t fdt_size)
 
 static int kho_test_preserve(struct kho_test_state *state)
 {
-	struct kho_vmalloc *folios_vmalloc;
+	DECLARE_KHO_SER_HDR(kho_vmalloc, *folios_fhdr);
 	ssize_t fdt_size, alloc_size;
 	int err;
 
 	fdt_size = state->nr_folios * sizeof(phys_addr_t) + PAGE_SIZE;
-	alloc_size = fdt_size + sizeof(struct kho_vmalloc);
+	alloc_size = fdt_size + kho_ser_size(kho_vmalloc);
 	state->fdt = folio_alloc(GFP_KERNEL, get_order(alloc_size));
 	if (!state->fdt)
 		return -ENOMEM;
 
-	folios_vmalloc = folio_address(state->fdt) + fdt_size;
-	KHOSER_STORE_PTR(state->folios_info_phys, folios_vmalloc);
+	folios_fhdr = folio_address(state->fdt) + fdt_size;
+	KHOSER_STORE_PTR(state->folios_info_fhdr, folios_fhdr);
 
 	err = kho_preserve_folio(state->fdt);
 	if (err)
@@ -238,8 +238,9 @@ static int kho_test_save(void)
 
 static int kho_test_restore_data(const void *fdt, int node)
 {
-	const DECLARE_KHOSER_PTR(*folios_info_phys, struct kho_vmalloc *);
-	const struct kho_vmalloc *folios_vmalloc;
+	const DECLARE_KHOSER_PTR(*folios_fhdr_phys,
+				 DECLARE_KHO_SER_HDR(kho_vmalloc, *));
+	const DECLARE_KHO_SER_HDR(kho_vmalloc, *folios_fhdr);
 	const unsigned int *nr_folios;
 	phys_addr_t *folios_info;
 	const __wsum *old_csum;
@@ -256,15 +257,15 @@ static int kho_test_restore_data(const void *fdt, int node)
 	if (!old_csum || len != sizeof(*old_csum))
 		return -EINVAL;
 
-	folios_info_phys = fdt_getprop(fdt, node, "folios_info", &len);
-	if (!folios_info_phys || len != sizeof(*folios_info_phys))
+	folios_fhdr_phys = fdt_getprop(fdt, node, "folios_info", &len);
+	if (!folios_fhdr_phys || len != sizeof(*folios_fhdr_phys))
 		return -EINVAL;
 
-	folios_vmalloc = KHOSER_LOAD_PTR(*folios_info_phys);
-	if (!folios_vmalloc)
+	folios_fhdr = KHOSER_LOAD_PTR(*folios_fhdr_phys);
+	if (!folios_fhdr)
 		return -EINVAL;
 
-	folios_info = kho_restore_vmalloc(folios_vmalloc);
+	folios_info = kho_restore_vmalloc(folios_fhdr);
 	if (!folios_info)
 		return -EINVAL;
 
diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
index 4471cced57db..74c1ff5877c3 100644
--- a/mm/memfd_luo.c
+++ b/mm/memfd_luo.c
@@ -103,7 +103,7 @@ static int memfd_luo_preserve_folios(struct file *file,
 	 * are not set.
 	 */
 	if (!size) {
-		ser->folios.phys = 0;
+		ser->folios_fhdr.phys = 0;
 		ser->nr_folios = 0;
 		*out_folios_ser = NULL;
 		return 0;
@@ -200,7 +200,7 @@ static int memfd_luo_preserve_folios(struct file *file,
 		pfolio->index = folio->index;
 	}
 
-	err = kho_preserve_vmalloc(folios_ser, KHOSER_LOAD_PTR(ser->folios));
+	err = kho_preserve_vmalloc(folios_ser, KHOSER_LOAD_PTR(ser->folios_fhdr));
 	if (err)
 		goto err_unpreserve;
 
@@ -227,7 +227,7 @@ static int memfd_luo_preserve_folios(struct file *file,
 	return err;
 }
 
-static void memfd_luo_unpreserve_folios(struct kho_vmalloc *kho_vmalloc,
+static void memfd_luo_unpreserve_folios(DECLARE_KHO_SER_HDR(kho_vmalloc, *folios_fhdr),
 					struct memfd_luo_folio_ser *folios_ser,
 					u64 nr_folios)
 {
@@ -236,7 +236,7 @@ static void memfd_luo_unpreserve_folios(struct kho_vmalloc *kho_vmalloc,
 	if (!nr_folios)
 		return;
 
-	kho_unpreserve_vmalloc(kho_vmalloc);
+	kho_unpreserve_vmalloc(folios_fhdr);
 
 	for (i = 0; i < nr_folios; i++) {
 		const struct memfd_luo_folio_ser *pfolio = &folios_ser[i];
@@ -256,15 +256,16 @@ static void memfd_luo_unpreserve_folios(struct kho_vmalloc *kho_vmalloc,
 
 static size_t memfd_luo_ser_size(void)
 {
-	return sizeof(struct memfd_luo_ser) + sizeof(struct kho_vmalloc);
+	return sizeof(struct memfd_luo_ser) + kho_ser_size(kho_vmalloc);
 }
 
 static struct memfd_luo_ser *memfd_luo_ser_init(void *mem)
 {
-	struct kho_vmalloc *kho_vmalloc = mem + sizeof(struct memfd_luo_ser);
+	DECLARE_KHO_SER_HDR(kho_vmalloc, *folios_fhdr);
 	struct memfd_luo_ser *ser = mem;
 
-	KHOSER_STORE_PTR(ser->folios, kho_vmalloc);
+	folios_fhdr = mem + sizeof(struct memfd_luo_ser);
+	KHOSER_STORE_PTR(ser->folios_fhdr, folios_fhdr);
 	return ser;
 }
 
@@ -365,7 +366,7 @@ static void memfd_luo_unpreserve(struct liveupdate_file_op_args *args)
 
 	ser = phys_to_virt(args->serialized_data);
 
-	memfd_luo_unpreserve_folios(KHOSER_LOAD_PTR(ser->folios),
+	memfd_luo_unpreserve_folios(KHOSER_LOAD_PTR(ser->folios_fhdr),
 				    args->private_data,
 				    ser->nr_folios);
 
@@ -416,7 +417,7 @@ static void memfd_luo_finish(struct liveupdate_file_op_args *args)
 		return;
 
 	if (ser->nr_folios) {
-		folios_ser = kho_restore_vmalloc(KHOSER_LOAD_PTR(ser->folios));
+		folios_ser = kho_restore_vmalloc(KHOSER_LOAD_PTR(ser->folios_fhdr));
 		if (!folios_ser)
 			goto out;
 
@@ -567,7 +568,7 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
 	i_size_write(file_inode(file), ser->size);
 
 	if (ser->nr_folios) {
-		folios_ser = kho_restore_vmalloc(KHOSER_LOAD_PTR(ser->folios));
+		folios_ser = kho_restore_vmalloc(KHOSER_LOAD_PTR(ser->folios_fhdr));
 		if (!folios_ser) {
 			err = -EINVAL;
 			goto put_file;
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 08/19] kho: block: Add the feature header to blocks
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
                   ` (6 preceding siblings ...)
  2026-11-10  5:01 ` [PATCH v4 07/19] kho: vmalloc: " Tarun Sahu
@ 2026-11-10  5:01 ` Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 09/19] liveupdate: Add feature header to struct luo_ser Tarun Sahu
                   ` (10 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:01 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

Subsystems using the KHO Serialization Blocks framework currently rely
on whole-structure compatibility strings to validate serialized block
sets across kexec. Any modification to the block structure layout
constitutes a breaking change and breaks compatibility between adjacent
kernels.

Precede struct kho_block_header_ser with struct kho_ser_hdr using
DECLARE_KHO_SER. Each block in a block set starts with struct
kho_ser_hdr followed by struct kho_block_header_ser. Serialized entries
start immediately after the header payload at offset fhdr->size.
Block capacity is computed as (KHO_BLOCK_SIZE - fhdr->size) /
bs->entry_size. Subsystems can append future block-level metadata under
new feature indices without altering existing offsets.

On restore, the location of the first entry and the block capacity no
longer depend on the caller. They are derived from the header size and
entry size the source kernel serialized with, even if the target kernel
structures have a different size. Fields that the source kernel did not
serialize are absent: a subsystem must not access them unless the
corresponding feature is present in the incoming header, as doing so
reads memory outside the serialized data.

During restoration, blocks whose header does not fit the block are
rejected, and the chain walk stops at such a block. Validate each block
header and reject unknown required features.

Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 include/linux/kho/abi/block.h |  15 ++--
 include/linux/kho_block.h     |   8 +-
 kernel/liveupdate/kho_block.c | 144 ++++++++++++++++++++++++----------
 3 files changed, 113 insertions(+), 54 deletions(-)

diff --git a/include/linux/kho/abi/block.h b/include/linux/kho/abi/block.h
index a40b0906c821..5f26c23506e9 100644
--- a/include/linux/kho/abi/block.h
+++ b/include/linux/kho/abi/block.h
@@ -28,6 +28,7 @@
 #include <asm/page.h>
 #include <linux/build_bug.h>
 #include <linux/kconfig.h>
+#include <linux/kho/abi/feature.h>
 #include <linux/types.h>
 
 /**
@@ -40,14 +41,12 @@
 
 /**
  * struct kho_block_header_ser - Header for the serialized data block.
- * @next:       Physical address of the next struct kho_block_header_ser.
- * @count:      The number of entries that immediately follow this header in the
- *              memory block.
+ * @next:       Physical address of the next block.
+ * @count:      The number of entries that follow the header in the memory block.
  * @entry_size: The size in bytes of each serialized entry in the block.
  *
- * This structure is located at the beginning of a block of physical memory
- * preserved across a kexec. It provides the necessary metadata to interpret
- * the array of entries that follow.
+ * This structure is located at the beginning of each block of physical memory
+ * preserved across a kexec, preceded by &struct kho_ser_hdr.
  */
 struct kho_block_header_ser {
 	u64 next;
@@ -55,8 +54,6 @@ struct kho_block_header_ser {
 	u64 entry_size;
 };
 
-static_assert(sizeof(struct kho_block_header_ser) == 24);
-static_assert(!IS_ENABLED(CONFIG_64BIT) ||
-	      __alignof__(struct kho_block_header_ser) == 8);
+DECLARE_KHO_SER(kho_block_header_ser, 24, 0, 0);
 
 #endif /* _LINUX_KHO_ABI_BLOCK_H */
diff --git a/include/linux/kho_block.h b/include/linux/kho_block.h
index 93a7cc2be5f5..776b57ae83bd 100644
--- a/include/linux/kho_block.h
+++ b/include/linux/kho_block.h
@@ -13,12 +13,12 @@
 
 /**
  * struct kho_block - Internal representation of a serialization block.
- * @list: List head for linking blocks in memory.
- * @ser:  Pointer to the serialized header in preserved memory.
+ * @list:     List head for linking blocks in memory.
+ * @fhdr:     Pointer to the serialized feature header in preserved memory.
  */
 struct kho_block {
 	struct list_head list;
-	struct kho_block_header_ser *ser;
+	DECLARE_KHO_SER_HDR(kho_block_header_ser, *fhdr);
 };
 
 /**
@@ -63,7 +63,7 @@ struct kho_block_set_it {
 	.blocks = LIST_HEAD_INIT((_name).blocks),			\
 	.entry_size = _entry_size,					\
 	.count_per_block = (KHO_BLOCK_SIZE -				\
-			    sizeof(struct kho_block_header_ser)) /	\
+			    kho_ser_size(kho_block_header_ser)) /	\
 			   (_entry_size),				\
 }
 
diff --git a/kernel/liveupdate/kho_block.c b/kernel/liveupdate/kho_block.c
index 9b148009085e..20e85ce7fd5f 100644
--- a/kernel/liveupdate/kho_block.c
+++ b/kernel/liveupdate/kho_block.c
@@ -21,7 +21,9 @@
  * append fields to an entry, so the kernel that writes a block and the kernel
  * that reads it can use different entry sizes. The reader steps through the
  * entries with the recorded size, so every entry is found, and it does not read
- * beyond the recorded size of an entry.
+ * beyond the recorded size of an entry. Fields appended to an entry are read
+ * only when the feature header of the structure that owns the block set shows
+ * them present.
  */
 
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -54,10 +56,13 @@ void kho_block_set_init(struct kho_block_set *bs, size_t entry_size)
 	WARN_ON_ONCE(!bs->count_per_block);
 }
 
-/* Serialized entries start immediately after the block header */
+/*
+ * The entries start at the size recorded in the block header, so this works
+ * for blocks this kernel wrote and for blocks handed over by another kernel.
+ */
 static void *kho_block_entries(struct kho_block *block)
 {
-	return (void *)(block->ser + 1);
+	return (void *)block->fhdr + block->fhdr->size;
 }
 
 /* Get the address of the serialized entry at the specified index */
@@ -68,22 +73,23 @@ static void *kho_block_entry(struct kho_block_set_it *it, u64 index)
 
 /* Free serialized data */
 static void kho_block_free_ser(struct kho_block_set *bs,
-			       struct kho_block_header_ser *ser)
+			       DECLARE_KHO_SER_HDR(kho_block_header_ser, *fhdr))
 {
 	if (bs->incoming)
-		kho_restore_free(ser);
+		kho_restore_free(fhdr);
 	else
-		kho_unpreserve_free(ser);
+		kho_unpreserve_free(fhdr);
 }
 
-static struct kho_block_header_ser *kho_block_alloc_ser(struct kho_block_set *bs)
+static DECLARE_KHO_SER_HDR(kho_block_header_ser,
+			   *kho_block_alloc_ser(struct kho_block_set *bs))
 {
 	WARN_ON_ONCE(bs->incoming);
 	return kho_alloc_preserve(KHO_BLOCK_SIZE);
 }
 
 static int kho_block_add(struct kho_block_set *bs,
-			 struct kho_block_header_ser *ser)
+			 DECLARE_KHO_SER_HDR(kho_block_header_ser, *fhdr))
 {
 	struct kho_block *block, *last;
 
@@ -94,33 +100,38 @@ static int kho_block_add(struct kho_block_set *bs,
 	if (!block)
 		return -ENOMEM;
 
-	block->ser = ser;
+	block->fhdr = fhdr;
 	last = list_last_entry_or_null(&bs->blocks, struct kho_block, list);
 	list_add_tail(&block->list, &bs->blocks);
 	bs->nblocks++;
 
-	if (last)
-		last->ser->next = virt_to_phys(ser);
-	else
-		bs->head_pa = virt_to_phys(ser);
+	if (last) {
+		kho_ser_payload(last->fhdr, kho_block_header_ser)->next =
+			virt_to_phys(fhdr);
+	} else {
+		bs->head_pa = virt_to_phys(fhdr);
+	}
 
 	return 0;
 }
 
 static int kho_block_set_grow_one(struct kho_block_set *bs)
 {
+	DECLARE_KHO_SER_HDR(kho_block_header_ser, *fhdr);
 	struct kho_block_header_ser *ser;
 	int err;
 
-	ser = kho_block_alloc_ser(bs);
-	if (IS_ERR(ser))
-		return PTR_ERR(ser);
+	fhdr = kho_block_alloc_ser(bs);
+	if (IS_ERR(fhdr))
+		return PTR_ERR(fhdr);
 
+	kho_ser_init(fhdr, kho_block_header_ser);
+	ser = kho_ser_payload(fhdr, kho_block_header_ser);
 	ser->entry_size = bs->entry_size;
 
-	err = kho_block_add(bs, ser);
+	err = kho_block_add(bs, fhdr);
 	if (err) {
-		kho_block_free_ser(bs, ser);
+		kho_block_free_ser(bs, fhdr);
 		return err;
 	}
 
@@ -137,12 +148,12 @@ static void kho_block_set_shrink_one(struct kho_block_set *bs)
 	last = list_last_entry(&bs->blocks, struct kho_block, list);
 	list_del(&last->list);
 	bs->nblocks--;
-	kho_block_free_ser(bs, last->ser);
+	kho_block_free_ser(bs, last->fhdr);
 	kfree(last);
 
 	new_last = list_last_entry_or_null(&bs->blocks, struct kho_block, list);
 	if (new_last)
-		new_last->ser->next = 0;
+		kho_ser_payload(new_last->fhdr, kho_block_header_ser)->next = 0;
 	else
 		bs->head_pa = 0;
 }
@@ -203,6 +214,23 @@ void kho_block_set_shrink(struct kho_block_set *bs, u64 count)
 		kho_block_set_shrink_one(bs);
 }
 
+/*
+ * Payload of a block, or NULL if its feature header does not fit the
+ * block. Used on handed-over blocks (restore, cycle check and cleanup)
+ * to bound fhdr->size before the payload is used.
+ */
+static struct kho_block_header_ser *
+kho_block_get_header_ser(const DECLARE_KHO_SER_HDR(kho_block_header_ser, *fhdr))
+{
+	if (fhdr->size < kho_ser_hdr_size(fhdr) +
+	    offsetofend(struct kho_block_header_ser, entry_size) ||
+	    fhdr->size >= KHO_BLOCK_SIZE ||
+	    !IS_ALIGNED(fhdr->size, sizeof(u64)))
+		return NULL;
+
+	return kho_ser_payload(fhdr, kho_block_header_ser);
+}
+
 /*
  * kho_block_set_is_cyclic - Check for cycles in a linked list of blocks.
  * Uses Floyd's cycle-finding algorithm to ensure sanity of the incoming list.
@@ -211,28 +239,35 @@ void kho_block_set_shrink(struct kho_block_set *bs, u64 count)
  */
 static bool kho_block_set_is_cyclic(struct kho_block_set *bs)
 {
-	struct kho_block_header_ser *fast;
-	struct kho_block_header_ser *slow;
+	const DECLARE_KHO_SER_HDR(kho_block_header_ser, *fast);
+	const DECLARE_KHO_SER_HDR(kho_block_header_ser, *slow);
+	struct kho_block_header_ser *fast_ser;
+	struct kho_block_header_ser *slow_ser;
 	int count = 0;
 
 	fast = phys_to_virt(bs->head_pa);
 	slow = fast;
 
 	while (fast) {
-		if (count++ >= KHO_MAX_BLOCKS) {
-			pr_err("Block set is corrupted\n");
+		if (++count > KHO_MAX_BLOCKS) {
+			pr_err("Block set exceeds max blocks: %d\n", count);
 			return true;
 		}
 
-		if (!fast->next)
+		fast_ser = kho_block_get_header_ser(fast);
+		if (!fast_ser || !fast_ser->next)
 			break;
 
-		fast = phys_to_virt(fast->next);
-		if (!fast->next)
+		fast = phys_to_virt(fast_ser->next);
+		fast_ser = kho_block_get_header_ser(fast);
+		if (!fast_ser || !fast_ser->next)
 			break;
 
-		fast = phys_to_virt(fast->next);
-		slow = phys_to_virt(slow->next);
+		fast = phys_to_virt(fast_ser->next);
+		slow_ser = kho_block_get_header_ser(slow);
+		if (!slow_ser)
+			break;
+		slow = phys_to_virt(slow_ser->next);
 
 		if (slow == fast) {
 			pr_err("Block set is corrupted\n");
@@ -257,7 +292,7 @@ static bool kho_block_set_is_cyclic(struct kho_block_set *bs)
  */
 int kho_block_set_restore(struct kho_block_set *bs, u64 head_pa)
 {
-	struct kho_block_header_ser *ser;
+	DECLARE_KHO_SER_HDR(kho_block_header_ser, *fhdr);
 	u64 next_pa = head_pa;
 	u64 entry_size = 0;
 	int err;
@@ -274,9 +309,25 @@ int kho_block_set_restore(struct kho_block_set *bs, u64 head_pa)
 	}
 
 	while (next_pa) {
+		struct kho_block_header_ser *ser;
 		u64 capacity;
 
-		ser = phys_to_virt(next_pa);
+		fhdr = phys_to_virt(next_pa);
+		ser = kho_block_get_header_ser(fhdr);
+		if (!ser) {
+			pr_warn("Block has an invalid header size: %u\n",
+				fhdr->size);
+			err = -EINVAL;
+			goto err_destroy;
+		}
+
+		err = kho_ser_validate(fhdr, kho_block_header_ser);
+		if (err) {
+			pr_warn("Block header feature validation failed: %pe\n",
+				ERR_PTR(err));
+			goto err_destroy;
+		}
+
 		if (!ser->entry_size || (entry_size && ser->entry_size != entry_size)) {
 			pr_warn("Block contains invalid entry size: %llu\n",
 				ser->entry_size);
@@ -285,7 +336,7 @@ int kho_block_set_restore(struct kho_block_set *bs, u64 head_pa)
 		}
 		entry_size = ser->entry_size;
 		bs->entry_size = entry_size;
-		capacity = (KHO_BLOCK_SIZE - sizeof(*ser)) / bs->entry_size;
+		capacity = (KHO_BLOCK_SIZE - fhdr->size) / bs->entry_size;
 		bs->count_per_block = capacity;
 
 		if (!ser->count || ser->count > capacity) {
@@ -294,7 +345,7 @@ int kho_block_set_restore(struct kho_block_set *bs, u64 head_pa)
 			err = -EINVAL;
 			goto err_destroy;
 		}
-		err = kho_block_add(bs, ser);
+		err = kho_block_add(bs, fhdr);
 		if (err)
 			goto err_destroy;
 		next_pa = ser->next;
@@ -307,10 +358,12 @@ int kho_block_set_restore(struct kho_block_set *bs, u64 head_pa)
 
 	/* Free the remaining un-restored blocks in the physical chain */
 	while (next_pa) {
-		struct kho_block_header_ser *next_ser = phys_to_virt(next_pa);
+		struct kho_block_header_ser *next_ser;
 
-		next_pa = next_ser->next;
-		kho_block_free_ser(bs, next_ser);
+		fhdr = phys_to_virt(next_pa);
+		next_ser = kho_block_get_header_ser(fhdr);
+		next_pa = next_ser ? next_ser->next : 0;
+		kho_block_free_ser(bs, fhdr);
 	}
 	return err;
 }
@@ -325,7 +378,7 @@ void kho_block_set_destroy(struct kho_block_set *bs)
 
 	list_for_each_entry_safe(block, tmp, &bs->blocks, list) {
 		list_del(&block->list);
-		kho_block_free_ser(bs, block->ser);
+		kho_block_free_ser(bs, block->fhdr);
 		kfree(block);
 	}
 	bs->nblocks = 0;
@@ -341,8 +394,12 @@ void kho_block_set_clear(struct kho_block_set *bs)
 	struct kho_block *block;
 
 	list_for_each_entry(block, &bs->blocks, list) {
-		block->ser->count = 0;
-		memset(block->ser + 1, 0, KHO_BLOCK_SIZE - sizeof(*block->ser));
+		struct kho_block_header_ser *ser =
+			kho_ser_payload(block->fhdr, kho_block_header_ser);
+
+		ser->count = 0;
+		memset(kho_block_entries(block), 0,
+		       KHO_BLOCK_SIZE - block->fhdr->size);
 	}
 }
 
@@ -371,6 +428,7 @@ void kho_block_set_it_init(struct kho_block_set_it *it, struct kho_block_set *bs
  */
 void *kho_block_set_it_reserve_entry(struct kho_block_set_it *it)
 {
+	struct kho_block_header_ser *ser;
 	void *entry;
 
 	if (!it->block)
@@ -384,7 +442,8 @@ void *kho_block_set_it_reserve_entry(struct kho_block_set_it *it)
 	}
 
 	entry = kho_block_entry(it, it->i++);
-	it->block->ser->count = it->i;
+	ser = kho_ser_payload(it->block->fhdr, kho_block_header_ser);
+	ser->count = it->i;
 	return entry;
 }
 
@@ -403,10 +462,13 @@ void *kho_block_set_it_reserve_entry(struct kho_block_set_it *it)
  */
 void *kho_block_set_it_read_entry(struct kho_block_set_it *it)
 {
+	struct kho_block_header_ser *ser;
+
 	if (!it->block)
 		return NULL;
 
-	if (it->i == it->block->ser->count) {
+	ser = kho_ser_payload(it->block->fhdr, kho_block_header_ser);
+	if (it->i == ser->count) {
 		if (list_is_last(&it->block->list, &it->bs->blocks))
 			return NULL;
 		it->block = list_next_entry(it->block, list);
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 09/19] liveupdate: Add feature header to struct luo_ser
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
                   ` (7 preceding siblings ...)
  2026-11-10  5:01 ` [PATCH v4 08/19] kho: block: Add the feature header to blocks Tarun Sahu
@ 2026-11-10  5:01 ` Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 10/19] liveupdate: Add feature header to struct luo_session_ser Tarun Sahu
                   ` (9 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:01 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

LUO and its file handlers currently rely solely on coarse-grained
compatibility strings (such as luo-v6 and memfd-v3) to validate
handed-over state across kexec. Because compatibility is checked only
at the whole-structure level, any change to a serialized structure,
even an optional additive field, requires bumping the compatibility
string and breaks live update between adjacent kernels.

Precede the centralized struct luo_ser with struct kho_ser_hdr using
DECLARE_KHO_SER. Subsystems can now evolve their serialized state at
per-feature granularity. Each subsystem groups the fields of its
serialized structure under feature indices, starting with index 0 for the
base fields. When a subsystem adds a new capability, it appends any new
fields to the end of its payload structure under a new feature index.
Retired fields are kept in place so existing field offsets never shift;
if the type or format of a field needs to change, the existing field and
feature are kept intact until retired and a new feature index with an
appended field of the new type is added. Only when a change requires a
complete break in the serialized layout or semantics that feature flags
cannot handle, a new handler can be introduced with an incremented version
number in its compatibility string.

Signed-off-by: Logan Odell <loganodell@google.com>
Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 include/linux/kho/abi/luo.h  | 64 +++++++++++++++---------------------
 kernel/liveupdate/luo_core.c | 39 +++++++++++++---------
 2 files changed, 50 insertions(+), 53 deletions(-)

diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index f1152470f71a..dfa7ea434d0d 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -9,29 +9,26 @@
  * DOC: Live Update Orchestrator ABI
  *
  * Live Update Orchestrator uses the stable Application Binary Interface
- * defined below to pass state from a pre-update kernel to a post-update
- * kernel. The ABI is built upon the Kexec HandOver framework and registers
- * the central `struct luo_ser` via the KHO raw subtree API.
+ * defined below to pass state from a source kernel to a target kernel.
+ * The ABI is built upon the Kexec HandOver framework and registers the
+ * central `struct luo_ser` via the KHO raw subtree API.
  *
- * This interface is a contract. Any modification to the structure fields,
- * 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.
- *
- * Changes are allowed provided the compatibility version is incremented;
- * however, backward/forward compatibility is only guaranteed for kernels
- * supporting the same ABI version.
+ * This interface is a contract. `struct luo_ser`, and the serialized state
+ * of each file handler that uses it, are preceded by `struct kho_ser_hdr`.
+ * See the KHO Serialization Feature Header ABI documentation in
+ * include/linux/kho/abi/feature.h for the header layout, the feature rules
+ * and validation.
  *
  * KHO Structure Overview:
  *   The entire LUO state is encapsulated within a single KHO entry named "LUO".
- *   This entry contains the `struct luo_ser` structure.
+ *   This entry contains a `struct kho_ser_hdr` followed by the
+ *   `struct luo_ser` payload.
  *
  * Serialization Structures:
  *   - struct luo_ser:
- *     The central ABI structure that contains the overall state of the LUO.
- *     It includes the compatibility string, the liveupdate-number, and pointers
- *     to sessions and FLBs.
+ *     The central ABI payload structure that contains the overall state of
+ *     the LUO (the liveupdate-number and physical pointers to sessions and
+ *     FLBs), preceded by `struct kho_ser_hdr`.
  *
  *   - struct luo_session_ser:
  *     Metadata for a single session, including its name and a physical pointer
@@ -40,7 +37,7 @@
  *
  *   - 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
+ *     find the correct handler in the target kernel, a user-provided `token` for
  *     identification, and an opaque `data` handle for the handler to use.
  *
  *   - struct luo_flb_header_ser:
@@ -57,39 +54,36 @@
 #ifndef _LINUX_KHO_ABI_LUO_H
 #define _LINUX_KHO_ABI_LUO_H
 
-#include <linux/align.h>
 #include <linux/build_bug.h>
 #include <linux/kconfig.h>
 #include <linux/kho/abi/block.h>
+#include <linux/kho/abi/feature.h>
 #include <linux/kho/abi/kexec_handover.h>
+#include <linux/types.h>
 #include <uapi/linux/liveupdate.h>
 
 /*
  * The LUO state is registered under this KHO entry name.
  */
 #define LUO_KHO_ENTRY_NAME	"LUO"
-#define LUO_ABI_COMPATIBLE	"luo-v6"
-#define LUO_ABI_COMPAT_LEN	ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
 
 /**
- * struct luo_ser - Centralized LUO ABI header.
- * @compatible:     Compatibility string identifying the LUO ABI version.
+ * struct luo_ser - Centralized LUO ABI payload.
  * @liveupdate_num: A counter tracking the number of successful live updates.
  * @sessions_pa:    Physical address of the first session block header.
  * @flbs_pa:        Physical address of the FLB header.
  *
- * This structure is the root of all preserved LUO state.
+ * This structure is the root payload of all preserved LUO state, preceded by
+ * &struct kho_ser_hdr.
  */
 struct luo_ser {
-	char compatible[LUO_ABI_COMPAT_LEN];
 	u64 liveupdate_num;
 	u64 sessions_pa;
 	u64 flbs_pa;
 };
 
-static_assert(sizeof(struct luo_ser) == 32);
-static_assert(!IS_ENABLED(CONFIG_64BIT) ||
-	      __alignof__(struct luo_ser) == 8);
+/* Register luo_ser with KHO serialization accessors */
+DECLARE_KHO_SER(luo_ser, 24, 0, 0);
 
 #define LIVEUPDATE_HNDL_COMPAT_LENGTH	48
 
@@ -98,8 +92,6 @@ static_assert(!IS_ENABLED(CONFIG_64BIT) ||
  * @compatible:  File handler compatible string.
  * @data:        Private data
  * @token:       User provided token for this file
- *
- * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
  */
 struct luo_file_ser {
 	char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
@@ -138,10 +130,8 @@ static_assert(!IS_ENABLED(CONFIG_64BIT) ||
  *
  * 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_ABI_COMPATIBLE` must be updated.
+ * session) is created and passed to the target kernel, allowing it to
+ * reconstruct the session context.
  */
 struct luo_session_ser {
 	char name[LIVEUPDATE_SESSION_NAME_LENGTH];
@@ -180,13 +170,13 @@ static_assert(!IS_ENABLED(CONFIG_64BIT) ||
 /**
  * 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.
+ *           corresponding &struct liveupdate_flb in the target 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.
+ *           in the source 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.
+ *           target 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
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 1b2bda22902d..d0f72f27001d 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -66,7 +66,7 @@
 
 static struct {
 	bool enabled;
-	struct luo_ser *luo_ser_out;
+	DECLARE_KHO_SER_HDR(luo_ser, *luo_ser_out);
 	u64 liveupdate_num;
 } luo_global;
 
@@ -83,6 +83,7 @@ early_param("liveupdate", early_liveupdate_param);
 
 static int __init luo_early_startup(void)
 {
+	DECLARE_KHO_SER_HDR(luo_ser, *fhdr);
 	phys_addr_t luo_ser_phys;
 	struct luo_ser *luo_ser;
 	size_t len;
@@ -107,17 +108,21 @@ static int __init luo_early_startup(void)
 		return 0;
 	}
 
-	if (len < sizeof(*luo_ser)) {
-		pr_err("LUO state is too small (%zu < %zu)\n", len, sizeof(*luo_ser));
-		return -EINVAL;
+	fhdr = phys_to_virt(luo_ser_phys);
+	if (len < sizeof(*fhdr) || fhdr->size > len) {
+		pr_err("LUO state size invalid (source kernel header size=%u, KHO len=%zu)\n",
+		       len >= sizeof(*fhdr) ? fhdr->size : 0, len);
+		err = -EINVAL;
+		goto out_free_ser;
 	}
 
-	luo_ser = phys_to_virt(luo_ser_phys);
-	if (strncmp(luo_ser->compatible, LUO_ABI_COMPATIBLE, LUO_ABI_COMPAT_LEN)) {
-		pr_err("LUO state is incompatible with '%s'\n", LUO_ABI_COMPATIBLE);
-		return -EINVAL;
+	err = kho_ser_validate(fhdr, luo_ser);
+	if (err) {
+		pr_err("LUO state feature validation failed: %pe\n", ERR_PTR(err));
+		goto out_free_ser;
 	}
 
+	luo_ser = kho_ser_payload(fhdr, luo_ser);
 	luo_global.liveupdate_num = luo_ser->liveupdate_num;
 	pr_info("Retrieved live update data, liveupdate number: %lld\n",
 		luo_global.liveupdate_num);
@@ -131,7 +136,7 @@ static int __init luo_early_startup(void)
 	err = 0;
 
 out_free_ser:
-	kho_restore_free(luo_ser);
+	kho_restore_free(fhdr);
 	return err;
 }
 
@@ -153,16 +158,18 @@ early_initcall(liveupdate_early_init);
 /* Called during boot to create outgoing LUO state */
 static int __init luo_state_setup(void)
 {
+	DECLARE_KHO_SER_HDR(luo_ser, *fhdr);
 	struct luo_ser *luo_ser;
 	int err;
 
-	luo_ser = kho_alloc_preserve(sizeof(*luo_ser));
-	if (IS_ERR(luo_ser)) {
+	fhdr = kho_alloc_preserve(kho_ser_size(luo_ser));
+	if (IS_ERR(fhdr)) {
 		pr_err("failed to allocate/preserve LUO state memory\n");
-		return PTR_ERR(luo_ser);
+		return PTR_ERR(fhdr);
 	}
+	kho_ser_init(fhdr, luo_ser);
 
-	strscpy(luo_ser->compatible, LUO_ABI_COMPATIBLE, sizeof(luo_ser->compatible));
+	luo_ser = kho_ser_payload(fhdr, luo_ser);
 	luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
 
 	luo_session_setup_outgoing(&luo_ser->sessions_pa);
@@ -171,16 +178,16 @@ static int __init luo_state_setup(void)
 	if (err)
 		goto exit_free_luo_ser;
 
-	err = kho_add_subtree(LUO_KHO_ENTRY_NAME, luo_ser, sizeof(*luo_ser));
+	err = kho_add_subtree(LUO_KHO_ENTRY_NAME, fhdr, fhdr->size);
 	if (err)
 		goto exit_free_luo_ser;
 
-	luo_global.luo_ser_out = luo_ser;
+	luo_global.luo_ser_out = fhdr;
 
 	return 0;
 
 exit_free_luo_ser:
-	kho_unpreserve_free(luo_ser);
+	kho_unpreserve_free(fhdr);
 	pr_err("failed to prepare LUO state: %d\n", err);
 
 	return err;
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 10/19] liveupdate: Add feature header to struct luo_session_ser
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
                   ` (8 preceding siblings ...)
  2026-11-10  5:01 ` [PATCH v4 09/19] liveupdate: Add feature header to struct luo_ser Tarun Sahu
@ 2026-11-10  5:01 ` Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 11/19] liveupdate: Add feature header to struct luo_file_set_ser Tarun Sahu
                   ` (8 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:01 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

Precede each serialized session entry in the LUO session KHO block set
with struct kho_ser_hdr via DECLARE_KHO_SER so future session features can
append fields to struct luo_session_ser without breaking compatibility
between adjacent kernels.

Size session block-set entries using kho_ser_size(luo_session_ser),
initialize each entry feature header during luo_session_serialize(), and
validate the incoming feature header in luo_session_deserialize_one()
before accessing the struct luo_session_ser payload.

Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 include/linux/kho/abi/luo.h     |  6 +++---
 kernel/liveupdate/luo_session.c | 38 ++++++++++++++++++++++-----------
 2 files changed, 29 insertions(+), 15 deletions(-)

diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index dfa7ea434d0d..425c52c494f1 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -132,15 +132,15 @@ static_assert(!IS_ENABLED(CONFIG_64BIT) ||
  * between kernels via Kexec Handover. An array of these structures (one per
  * session) is created and passed to the target kernel, allowing it to
  * reconstruct the session context.
+ *
+ * Preceded by &struct kho_ser_hdr.
  */
 struct luo_session_ser {
 	char name[LIVEUPDATE_SESSION_NAME_LENGTH];
 	DECLARE_KHOSER_PTR(file_set_ser, struct luo_file_set_ser *);
 };
 
-static_assert(sizeof(struct luo_session_ser) == 72);
-static_assert(!IS_ENABLED(CONFIG_64BIT) ||
-	      __alignof__(struct luo_session_ser) == 8);
+DECLARE_KHO_SER(luo_session_ser, 72, 0, 0);
 
 /* The max size is set so it can be reliably used during in serialization */
 #define LIVEUPDATE_FLB_COMPAT_LENGTH	48
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index df667fe15fb2..641bd67ce76e 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -131,7 +131,7 @@ struct luo_session_global {
 	struct luo_session_header outgoing;
 };
 
-#define LUO_SESSION_SER_SIZE (sizeof(struct luo_session_ser) + \
+#define LUO_SESSION_SER_SIZE (kho_ser_size(luo_session_ser) + \
 			      sizeof(struct luo_file_set_ser))
 
 static struct luo_session_global luo_session_global = {
@@ -557,23 +557,33 @@ int __init luo_session_setup_incoming(u64 sessions_pa)
 	return 0;
 }
 
-static struct luo_session_ser *luo_session_ser_init(void *mem)
+static struct luo_session_ser *
+luo_session_ser_init(DECLARE_KHO_SER_HDR(luo_session_ser, *fhdr))
 {
 	struct luo_file_set_ser *file_set_ser;
-	struct luo_session_ser *ser = mem;
+	struct luo_session_ser *ser;
+
+	kho_ser_init(fhdr, luo_session_ser);
+	file_set_ser = (void *)fhdr + kho_ser_size(luo_session_ser);
 
-	file_set_ser = mem + sizeof(struct luo_session_ser);
+	ser = kho_ser_payload(fhdr, luo_session_ser);
 	KHOSER_STORE_PTR(ser->file_set_ser, file_set_ser);
 
 	return ser;
 }
 
 static int luo_session_deserialize_one(struct luo_session_header *sh,
-				       struct luo_session_ser *ser)
+				       DECLARE_KHO_SER_HDR(luo_session_ser, *fhdr))
 {
+	struct luo_session_ser *ser;
 	struct luo_session *session;
 	int err;
 
+	err = kho_ser_validate(fhdr, luo_session_ser);
+	if (err)
+		return err;
+
+	ser = kho_ser_payload(fhdr, luo_session_ser);
 	session = luo_session_alloc(ser->name);
 	if (IS_ERR(session)) {
 		pr_warn("Failed to allocate session [%.*s] during deserialization %pe\n",
@@ -606,7 +616,7 @@ int luo_session_deserialize(void)
 {
 	struct luo_session_header *sh = &luo_session_global.incoming;
 	static bool is_deserialized;
-	struct luo_session_ser *ser;
+	DECLARE_KHO_SER_HDR(luo_session_ser, *fhdr);
 	struct kho_block_set_it it;
 	static int saved_err;
 	int err;
@@ -635,8 +645,8 @@ int luo_session_deserialize(void)
 	 * reliably reset devices and reclaim memory.
 	 */
 	kho_block_set_it_init(&it, &sh->block_set);
-	while ((ser = kho_block_set_it_read_entry(&it))) {
-		err = luo_session_deserialize_one(sh, ser);
+	while ((fhdr = kho_block_set_it_read_entry(&it))) {
+		err = luo_session_deserialize_one(sh, fhdr);
 		if (err)
 			goto save_err;
 	}
@@ -665,15 +675,17 @@ int luo_session_serialize(void)
 	kho_block_set_it_init(&it, &sh->block_set);
 
 	list_for_each_entry(session, &sh->list, list) {
-		struct luo_session_ser *ser = kho_block_set_it_reserve_entry(&it);
+		DECLARE_KHO_SER_HDR(luo_session_ser, *fhdr) =
+			kho_block_set_it_reserve_entry(&it);
+		struct luo_session_ser *ser;
 
 		/* This should not fail normally as blocks were pre-allocated */
-		if (WARN_ON_ONCE(!ser)) {
+		if (WARN_ON_ONCE(!fhdr)) {
 			err = -ENOSPC;
 			goto err_undo;
 		}
 
-		ser = luo_session_ser_init(ser);
+		ser = luo_session_ser_init(fhdr);
 		err = luo_session_freeze_one(session, ser);
 		if (err) {
 			kho_block_set_it_prev(&it);
@@ -691,7 +703,9 @@ int luo_session_serialize(void)
 
 err_undo:
 	list_for_each_entry_continue_reverse(session, &sh->list, list) {
-		struct luo_session_ser *ser = kho_block_set_it_prev(&it);
+		DECLARE_KHO_SER_HDR(luo_session_ser, *fhdr) =
+			kho_block_set_it_prev(&it);
+		struct luo_session_ser *ser = kho_ser_payload(fhdr, luo_session_ser);
 
 		luo_session_unfreeze_one(session, ser);
 		memset(ser->name, 0, sizeof(ser->name));
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 11/19] liveupdate: Add feature header to struct luo_file_set_ser
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
                   ` (9 preceding siblings ...)
  2026-11-10  5:01 ` [PATCH v4 10/19] liveupdate: Add feature header to struct luo_session_ser Tarun Sahu
@ 2026-11-10  5:01 ` Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 12/19] liveupdate: Add feature header to struct luo_file_ser Tarun Sahu
                   ` (7 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:01 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

Precede struct luo_file_set_ser with struct kho_ser_hdr via
DECLARE_KHO_SER so future file-set metadata features can append fields
to struct luo_file_set_ser without breaking compatibility between
adjacent kernels.

Reserve space for both the session feature header and the file-set
feature header in each session block-set entry, initialize the file-set
feature header in luo_file_freeze(), and validate incoming required
features in luo_file_deserialize() before accessing payload fields.

Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 include/linux/kho/abi/luo.h      | 15 ++++++++-------
 kernel/liveupdate/luo_file.c     | 32 ++++++++++++++++++++++----------
 kernel/liveupdate/luo_internal.h |  7 ++++---
 kernel/liveupdate/luo_session.c  | 14 +++++++-------
 4 files changed, 41 insertions(+), 27 deletions(-)

diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 425c52c494f1..bd19211c64e1 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -112,21 +112,21 @@ static_assert(!IS_ENABLED(CONFIG_64BIT) ||
  * @count:   The total number of files that were part of this session during
  *           serialization. Used for iteration and validation during
  *           restoration.
+ *
+ * Preceded by &struct kho_ser_hdr.
  */
 struct luo_file_set_ser {
 	u64 files;
 	u64 count;
 };
 
-static_assert(sizeof(struct luo_file_set_ser) == 16);
-static_assert(!IS_ENABLED(CONFIG_64BIT) ||
-	      __alignof__(struct luo_file_set_ser) == 8);
+DECLARE_KHO_SER(luo_file_set_ser, 16, 0, 0);
 
 /**
  * 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 pointer to the files belonging to this session.
+ * @name:          The unique name of the session, provided by the userspace at
+ *                 the time of session creation.
+ * @file_set_fhdr: Serialized pointer to the 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
@@ -137,7 +137,8 @@ static_assert(!IS_ENABLED(CONFIG_64BIT) ||
  */
 struct luo_session_ser {
 	char name[LIVEUPDATE_SESSION_NAME_LENGTH];
-	DECLARE_KHOSER_PTR(file_set_ser, struct luo_file_set_ser *);
+	DECLARE_KHOSER_PTR(file_set_fhdr,
+			   DECLARE_KHO_SER_HDR(luo_file_set_ser, *));
 };
 
 DECLARE_KHO_SER(luo_session_ser, 72, 0, 0);
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index a39992da504f..02d09a80d785 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -440,8 +440,8 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
 
 /**
  * luo_file_freeze - Freezes all preserved files and serializes their metadata.
- * @file_set:     The file_set whose files are to be frozen.
- * @file_set_ser: Where to put the serialized file_set.
+ * @file_set:      The file_set whose files are to be frozen.
+ * @file_set_fhdr: 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
@@ -472,12 +472,14 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
  * Return: 0 on success, or a negative errno on failure.
  */
 int luo_file_freeze(struct luo_file_set *file_set,
-		    struct luo_file_set_ser *file_set_ser)
+		    DECLARE_KHO_SER_HDR(luo_file_set_ser, *file_set_fhdr))
 {
+	struct luo_file_set_ser *file_set_ser;
 	struct luo_file *luo_file;
 	struct kho_block_set_it it;
 	int err;
 
+	kho_ser_init(file_set_fhdr, luo_file_set_ser);
 	if (!file_set->count)
 		return 0;
 
@@ -506,6 +508,7 @@ int luo_file_freeze(struct luo_file_set *file_set,
 		file_ser->token = luo_file->token;
 	}
 
+	file_set_ser = kho_ser_payload(file_set_fhdr, luo_file_set_ser);
 	file_set_ser->count = file_set->count;
 	file_set_ser->files = kho_block_set_head_pa(&file_set->block_set);
 
@@ -519,8 +522,8 @@ int luo_file_freeze(struct luo_file_set *file_set,
 
 /**
  * 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.
+ * @file_set:      The file_set whose files are to be unfrozen.
+ * @file_set_fhdr: Serialized file_set.
  *
  * 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
@@ -534,13 +537,13 @@ int luo_file_freeze(struct luo_file_set *file_set,
  *          the reboot() syscall, after luo_file_freeze() has been called.
  */
 void luo_file_unfreeze(struct luo_file_set *file_set,
-		       struct luo_file_set_ser *file_set_ser)
+		       DECLARE_KHO_SER_HDR(luo_file_set_ser, *file_set_fhdr))
 {
 	if (!file_set->count)
 		return;
 
 	__luo_file_unfreeze(file_set, NULL);
-	memset(file_set_ser, 0, sizeof(*file_set_ser));
+	memset(file_set_fhdr, 0, kho_ser_size(luo_file_set_ser));
 }
 
 /**
@@ -780,8 +783,8 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
 
 /**
  * luo_file_deserialize - Reconstructs the list of preserved files in the new kernel.
- * @file_set:     The incoming file_set to fill with deserialized data.
- * @file_set_ser: Serialized KHO file_set data from the previous kernel.
+ * @file_set:      The incoming file_set to fill with deserialized data.
+ * @file_set_fhdr: 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,
@@ -804,12 +807,21 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
  * Context: Called from session deserialization.
  */
 int luo_file_deserialize(struct luo_file_set *file_set,
-			 struct luo_file_set_ser *file_set_ser)
+			 DECLARE_KHO_SER_HDR(luo_file_set_ser, *file_set_fhdr))
 {
+	struct luo_file_set_ser *file_set_ser;
 	struct luo_file_ser *file_ser;
 	struct kho_block_set_it it;
 	int err;
 
+	if (!file_set_fhdr)
+		return -EINVAL;
+
+	err = kho_ser_validate(file_set_fhdr, luo_file_set_ser);
+	if (err)
+		return err;
+
+	file_set_ser = kho_ser_payload(file_set_fhdr, luo_file_set_ser);
 	if (!file_set_ser->files) {
 		WARN_ON(file_set_ser->count);
 		return 0;
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index dac6644bfb18..e22a8cf39d91 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -8,6 +8,7 @@
 #ifndef _LINUX_LUO_INTERNAL_H
 #define _LINUX_LUO_INTERNAL_H
 
+#include <linux/kho/abi/luo.h>
 #include <linux/liveupdate.h>
 #include <linux/uaccess.h>
 #include <linux/kho_block.h>
@@ -104,14 +105,14 @@ int luo_session_deserialize(void);
 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);
+		    DECLARE_KHO_SER_HDR(luo_file_set_ser, *file_set_fhdr));
 void luo_file_unfreeze(struct luo_file_set *file_set,
-		       struct luo_file_set_ser *file_set_ser);
+		       DECLARE_KHO_SER_HDR(luo_file_set_ser, *file_set_fhdr));
 int luo_retrieve_file(struct luo_file_set *file_set, u64 token,
 		      struct file **filep);
 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);
+			 DECLARE_KHO_SER_HDR(luo_file_set_ser, *file_set_fhdr));
 void luo_file_set_init(struct luo_file_set *file_set);
 void luo_file_set_destroy(struct luo_file_set *file_set);
 
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 641bd67ce76e..6513fde3ecfe 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -132,7 +132,7 @@ struct luo_session_global {
 };
 
 #define LUO_SESSION_SER_SIZE (kho_ser_size(luo_session_ser) + \
-			      sizeof(struct luo_file_set_ser))
+			      kho_ser_size(luo_file_set_ser))
 
 static struct luo_session_global luo_session_global = {
 	.incoming = {
@@ -227,7 +227,7 @@ static void luo_session_unfreeze_one(struct luo_session *session,
 {
 	guard(mutex)(&session->mutex);
 	luo_file_unfreeze(&session->file_set,
-			  KHOSER_LOAD_PTR(ser->file_set_ser));
+			  KHOSER_LOAD_PTR(ser->file_set_fhdr));
 }
 
 static int luo_session_freeze_one(struct luo_session *session,
@@ -235,7 +235,7 @@ static int luo_session_freeze_one(struct luo_session *session,
 {
 	guard(mutex)(&session->mutex);
 	return luo_file_freeze(&session->file_set,
-			       KHOSER_LOAD_PTR(ser->file_set_ser));
+			       KHOSER_LOAD_PTR(ser->file_set_fhdr));
 }
 
 static int luo_session_release(struct inode *inodep, struct file *filep)
@@ -560,14 +560,14 @@ int __init luo_session_setup_incoming(u64 sessions_pa)
 static struct luo_session_ser *
 luo_session_ser_init(DECLARE_KHO_SER_HDR(luo_session_ser, *fhdr))
 {
-	struct luo_file_set_ser *file_set_ser;
+	DECLARE_KHO_SER_HDR(luo_file_set_ser, *file_set_fhdr);
 	struct luo_session_ser *ser;
 
 	kho_ser_init(fhdr, luo_session_ser);
-	file_set_ser = (void *)fhdr + kho_ser_size(luo_session_ser);
+	file_set_fhdr = (void *)fhdr + kho_ser_size(luo_session_ser);
 
 	ser = kho_ser_payload(fhdr, luo_session_ser);
-	KHOSER_STORE_PTR(ser->file_set_ser, file_set_ser);
+	KHOSER_STORE_PTR(ser->file_set_fhdr, file_set_fhdr);
 
 	return ser;
 }
@@ -601,7 +601,7 @@ static int luo_session_deserialize_one(struct luo_session_header *sh,
 
 	scoped_guard(mutex, &session->mutex) {
 		err = luo_file_deserialize(&session->file_set,
-					   KHOSER_LOAD_PTR(ser->file_set_ser));
+					   KHOSER_LOAD_PTR(ser->file_set_fhdr));
 	}
 	if (err) {
 		pr_warn("Failed to deserialize files for session [%s] %pe\n",
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 12/19] liveupdate: Add feature header to struct luo_file_ser
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
                   ` (10 preceding siblings ...)
  2026-11-10  5:01 ` [PATCH v4 11/19] liveupdate: Add feature header to struct luo_file_set_ser Tarun Sahu
@ 2026-11-10  5:01 ` Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 13/19] liveupdate: Add feature header to struct luo_flb_header_ser Tarun Sahu
                   ` (6 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:01 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

Precede each serialized file entry in a LUO file set KHO block set with
struct kho_ser_hdr via DECLARE_KHO_SER so future per-file metadata
features can append fields to struct luo_file_ser without breaking
compatibility between adjacent kernels.

Initialize file set block sets with kho_ser_size(luo_file_ser), initialize
each entry feature header in luo_file_freeze(), and validate the incoming
feature header in luo_file_deserialize_one() before accessing the struct
luo_file_ser payload.

Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 include/linux/kho/abi/luo.h  |  6 +++---
 kernel/liveupdate/luo_file.c | 26 +++++++++++++++++++-------
 2 files changed, 22 insertions(+), 10 deletions(-)

diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index bd19211c64e1..38b4658183a2 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -92,6 +92,8 @@ DECLARE_KHO_SER(luo_ser, 24, 0, 0);
  * @compatible:  File handler compatible string.
  * @data:        Private data
  * @token:       User provided token for this file
+ *
+ * Preceded by &struct kho_ser_hdr.
  */
 struct luo_file_ser {
 	char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
@@ -99,9 +101,7 @@ struct luo_file_ser {
 	u64 token;
 };
 
-static_assert(sizeof(struct luo_file_ser) == 64);
-static_assert(!IS_ENABLED(CONFIG_64BIT) ||
-	      __alignof__(struct luo_file_ser) == 8);
+DECLARE_KHO_SER(luo_file_ser, 64, 0, 0);
 
 /**
  * struct luo_file_set_ser - Represents the serialized metadata for file set
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index 02d09a80d785..2da327f8bdae 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -486,10 +486,12 @@ int luo_file_freeze(struct luo_file_set *file_set,
 	kho_block_set_it_init(&it, &file_set->block_set);
 
 	list_for_each_entry(luo_file, &file_set->files_list, list) {
-		struct luo_file_ser *file_ser = kho_block_set_it_reserve_entry(&it);
+		DECLARE_KHO_SER_HDR(luo_file_ser, *fhdr) =
+			kho_block_set_it_reserve_entry(&it);
+		struct luo_file_ser *file_ser;
 
 		/* This should not fail normally as blocks were pre-allocated */
-		if (WARN_ON_ONCE(!file_ser)) {
+		if (WARN_ON_ONCE(!fhdr)) {
 			err = -ENOSPC;
 			goto err_unfreeze;
 		}
@@ -502,6 +504,8 @@ int luo_file_freeze(struct luo_file_set *file_set,
 			goto err_unfreeze;
 		}
 
+		kho_ser_init(fhdr, luo_file_ser);
+		file_ser = kho_ser_payload(fhdr, luo_file_ser);
 		strscpy(file_ser->compatible, luo_file->fh->compatible,
 			sizeof(file_ser->compatible));
 		file_ser->data = luo_file->serialized_data;
@@ -742,11 +746,19 @@ int luo_file_finish(struct luo_file_set *file_set)
 }
 
 static int luo_file_deserialize_one(struct luo_file_set *file_set,
-				    struct luo_file_ser *ser)
+				    DECLARE_KHO_SER_HDR(luo_file_ser, *fhdr))
 {
 	struct liveupdate_file_handler *fh;
 	bool handler_found = false;
+	struct luo_file_ser *ser;
 	struct luo_file *luo_file;
+	int err;
+
+	err = kho_ser_validate(fhdr, luo_file_ser);
+	if (err)
+		return err;
+
+	ser = kho_ser_payload(fhdr, luo_file_ser);
 
 	down_read(&luo_register_rwlock);
 	list_private_for_each_entry(fh, &luo_file_handler_list, list) {
@@ -810,7 +822,7 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 			 DECLARE_KHO_SER_HDR(luo_file_set_ser, *file_set_fhdr))
 {
 	struct luo_file_set_ser *file_set_ser;
-	struct luo_file_ser *file_ser;
+	DECLARE_KHO_SER_HDR(luo_file_ser, *fhdr);
 	struct kho_block_set_it it;
 	int err;
 
@@ -848,8 +860,8 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 	 * reliably reset devices and reclaim memory.
 	 */
 	kho_block_set_it_init(&it, &file_set->block_set);
-	while ((file_ser = kho_block_set_it_read_entry(&it))) {
-		err = luo_file_deserialize_one(file_set, file_ser);
+	while ((fhdr = kho_block_set_it_read_entry(&it))) {
+		err = luo_file_deserialize_one(file_set, fhdr);
 		if (err)
 			goto err_destroy_blocks;
 		file_set->count++;
@@ -883,7 +895,7 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 void luo_file_set_init(struct luo_file_set *file_set)
 {
 	INIT_LIST_HEAD(&file_set->files_list);
-	kho_block_set_init(&file_set->block_set, sizeof(struct luo_file_ser));
+	kho_block_set_init(&file_set->block_set, kho_ser_size(luo_file_ser));
 }
 
 void luo_file_set_destroy(struct luo_file_set *file_set)
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 13/19] liveupdate: Add feature header to struct luo_flb_header_ser
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
                   ` (11 preceding siblings ...)
  2026-11-10  5:01 ` [PATCH v4 12/19] liveupdate: Add feature header to struct luo_file_ser Tarun Sahu
@ 2026-11-10  5:01 ` Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 14/19] liveupdate: Add feature header to struct luo_flb_ser Tarun Sahu
                   ` (5 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:01 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

Precede struct luo_flb_header_ser with struct kho_ser_hdr via
DECLARE_KHO_SER so future File-Lifecycle-Bound (FLB) header features can
append fields to struct luo_flb_header_ser without breaking compatibility
between adjacent kernels.

Initialize the header in luo_flb_setup_outgoing() and place the outgoing
entry array immediately after kho_ser_size(luo_flb_header_ser). On the
incoming path in luo_flb_setup_incoming(), validate the feature header
with kho_ser_validate() and locate the incoming entry array at
fhdr->size so entries remain properly aligned even if the source kernel
serialized a different header layout.

Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 include/linux/kho/abi/luo.h      |  6 ++----
 kernel/liveupdate/luo_core.c     |  4 +---
 kernel/liveupdate/luo_flb.c      | 36 +++++++++++++++++++++-----------
 kernel/liveupdate/luo_internal.h |  2 +-
 4 files changed, 28 insertions(+), 20 deletions(-)

diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 38b4658183a2..4913707ba0d3 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -157,16 +157,14 @@ DECLARE_KHO_SER(luo_session_ser, 72, 0, 0);
  * This structure is located at the physical address specified by the
  * flbs_pa in luo_ser.
  *
- * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
+ * Preceded by &struct kho_ser_hdr.
  */
 struct luo_flb_header_ser {
 	u64 pgcnt;
 	u64 count;
 };
 
-static_assert(sizeof(struct luo_flb_header_ser) == 16);
-static_assert(!IS_ENABLED(CONFIG_64BIT) ||
-	      __alignof__(struct luo_flb_header_ser) == 8);
+DECLARE_KHO_SER(luo_flb_header_ser, 16, 0, 0);
 
 /**
  * struct luo_flb_ser - Represents the serialized state of a single FLB object.
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index d0f72f27001d..79bb5f64bfac 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -131,9 +131,7 @@ static int __init luo_early_startup(void)
 	if (err)
 		goto out_free_ser;
 
-	luo_flb_setup_incoming(luo_ser->flbs_pa);
-
-	err = 0;
+	err = luo_flb_setup_incoming(luo_ser->flbs_pa);
 
 out_free_ser:
 	kho_restore_free(fhdr);
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index cd715a7c1d99..a8dcdb8456fd 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -53,7 +53,7 @@
 
 #define LUO_FLB_PGCNT		1ul
 #define LUO_FLB_MAX		(((LUO_FLB_PGCNT << PAGE_SHIFT) -	\
-		sizeof(struct luo_flb_header_ser)) / sizeof(struct luo_flb_ser))
+		kho_ser_size(luo_flb_header_ser)) / sizeof(struct luo_flb_ser))
 
 struct luo_flb_header {
 	struct luo_flb_header_ser *header_ser;
@@ -559,33 +559,45 @@ int liveupdate_flb_get_outgoing(struct liveupdate_flb *flb, void **objp)
 
 int __init luo_flb_setup_outgoing(u64 *flbs_pa)
 {
+	DECLARE_KHO_SER_HDR(luo_flb_header_ser, *fhdr);
 	struct luo_flb_header_ser *header_ser;
 
-	header_ser = kho_alloc_preserve(LUO_FLB_PGCNT << PAGE_SHIFT);
-	if (IS_ERR(header_ser))
-		return PTR_ERR(header_ser);
+	fhdr = kho_alloc_preserve(LUO_FLB_PGCNT << PAGE_SHIFT);
+	if (IS_ERR(fhdr))
+		return PTR_ERR(fhdr);
 
-	*flbs_pa = virt_to_phys(header_ser);
+	kho_ser_init(fhdr, luo_flb_header_ser);
+	header_ser = kho_ser_payload(fhdr, luo_flb_header_ser);
+
+	*flbs_pa = virt_to_phys(fhdr);
 
 	header_ser->pgcnt = LUO_FLB_PGCNT;
 	luo_flb_global.outgoing.header_ser = header_ser;
-	luo_flb_global.outgoing.ser = (void *)(header_ser + 1);
+	luo_flb_global.outgoing.ser = (void *)fhdr + kho_ser_size(luo_flb_header_ser);
 	luo_flb_global.outgoing.active = true;
 
 	return 0;
 }
 
-void __init luo_flb_setup_incoming(u64 flbs_pa)
+int __init luo_flb_setup_incoming(u64 flbs_pa)
 {
-	struct luo_flb_header_ser *header_ser;
+	DECLARE_KHO_SER_HDR(luo_flb_header_ser, *fhdr);
+	int err;
 
 	if (!flbs_pa)
-		return;
+		return 0;
 
-	header_ser = phys_to_virt(flbs_pa);
-	luo_flb_global.incoming.header_ser = header_ser;
-	luo_flb_global.incoming.ser = (void *)(header_ser + 1);
+	fhdr = phys_to_virt(flbs_pa);
+	err = kho_ser_validate(fhdr, luo_flb_header_ser);
+	if (err)
+		return err;
+
+	luo_flb_global.incoming.header_ser =
+		kho_ser_payload(fhdr, luo_flb_header_ser);
+	luo_flb_global.incoming.ser = (void *)fhdr + fhdr->size;
 	luo_flb_global.incoming.active = true;
+
+	return 0;
 }
 
 /**
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index e22a8cf39d91..df8306d73768 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -121,7 +121,7 @@ void luo_flb_file_unpreserve(struct liveupdate_file_handler *fh);
 void luo_flb_file_finish(struct liveupdate_file_handler *fh);
 void luo_flb_unregister_all(struct liveupdate_file_handler *fh);
 int __init luo_flb_setup_outgoing(u64 *flbs_pa);
-void __init luo_flb_setup_incoming(u64 flbs_pa);
+int __init luo_flb_setup_incoming(u64 flbs_pa);
 void luo_flb_serialize(void);
 
 #ifdef CONFIG_LIVEUPDATE_TEST
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 14/19] liveupdate: Add feature header to struct luo_flb_ser
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
                   ` (12 preceding siblings ...)
  2026-11-10  5:01 ` [PATCH v4 13/19] liveupdate: Add feature header to struct luo_flb_header_ser Tarun Sahu
@ 2026-11-10  5:01 ` Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 15/19] PCI: liveupdate: Add feature header to struct pci_ser Tarun Sahu
                   ` (4 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:01 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

Precede each serialized File-Lifecycle-Bound (FLB) entry in the preserved
FLB array with struct kho_ser_hdr via DECLARE_KHO_SER so future FLB
metadata features can append fields to struct luo_flb_ser without breaking
compatibility between adjacent kernels.

Compute LUO_FLB_MAX and outgoing entry offsets using
kho_ser_size(luo_flb_ser), and initialize each entry feature header in
luo_flb_serialize(). On the incoming path in luo_flb_retrieve_one(),
validate each entry feature header with kho_ser_validate() and advance
between entries using fhdr->size so entries written with a different
feature layout are traversed accurately.

Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 include/linux/kho/abi/luo.h |  6 ++----
 kernel/liveupdate/luo_flb.c | 39 +++++++++++++++++++++++++------------
 2 files changed, 29 insertions(+), 16 deletions(-)

diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 4913707ba0d3..1b660d41bb90 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -181,7 +181,7 @@ DECLARE_KHO_SER(luo_flb_header_ser, 16, 0, 0);
  * passed to the new kernel. Each entry allows the LUO core to restore one
  * global, shared object.
  *
- * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
+ * Preceded by &struct kho_ser_hdr.
  */
 struct luo_flb_ser {
 	char name[LIVEUPDATE_FLB_COMPAT_LENGTH];
@@ -189,9 +189,7 @@ struct luo_flb_ser {
 	u64 count;
 };
 
-static_assert(sizeof(struct luo_flb_ser) == 64);
-static_assert(!IS_ENABLED(CONFIG_64BIT) ||
-	      __alignof__(struct luo_flb_ser) == 8);
+DECLARE_KHO_SER(luo_flb_ser, 64, 0, 0);
 
 /* Kernel Live Update Test ABI */
 #ifdef CONFIG_LIVEUPDATE_TEST
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index a8dcdb8456fd..d38c223741ff 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -53,11 +53,11 @@
 
 #define LUO_FLB_PGCNT		1ul
 #define LUO_FLB_MAX		(((LUO_FLB_PGCNT << PAGE_SHIFT) -	\
-		kho_ser_size(luo_flb_header_ser)) / sizeof(struct luo_flb_ser))
+		kho_ser_size(luo_flb_header_ser)) / kho_ser_size(luo_flb_ser))
 
 struct luo_flb_header {
 	struct luo_flb_header_ser *header_ser;
-	struct luo_flb_ser *ser;
+	DECLARE_KHO_SER_HDR(luo_flb_ser, *fhdr);
 	bool active;
 };
 
@@ -159,6 +159,7 @@ 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;
+	DECLARE_KHO_SER_HDR(luo_flb_ser, *fhdr) = fh->fhdr;
 	struct liveupdate_flb_op_args args = {0};
 	bool found = false;
 	int err;
@@ -177,10 +178,18 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
 	if (!fh->active)
 		return -ENODATA;
 
-	for (int i = 0; i < fh->header_ser->count; i++) {
-		if (!strcmp(fh->ser[i].name, flb->compatible)) {
-			private->incoming.data = fh->ser[i].data;
-			refcount_set(&private->incoming.count, fh->ser[i].count);
+	for (u64 i = 0; i < fh->header_ser->count;
+	     i++, fhdr = (void *)fhdr + fhdr->size) {
+		struct luo_flb_ser *ser;
+
+		err = kho_ser_validate(fhdr, luo_flb_ser);
+		if (err)
+			return err;
+
+		ser = kho_ser_payload(fhdr, luo_flb_ser);
+		if (!strcmp(ser->name, flb->compatible)) {
+			private->incoming.data = ser->data;
+			refcount_set(&private->incoming.count, ser->count);
 			found = true;
 			break;
 		}
@@ -573,7 +582,7 @@ int __init luo_flb_setup_outgoing(u64 *flbs_pa)
 
 	header_ser->pgcnt = LUO_FLB_PGCNT;
 	luo_flb_global.outgoing.header_ser = header_ser;
-	luo_flb_global.outgoing.ser = (void *)fhdr + kho_ser_size(luo_flb_header_ser);
+	luo_flb_global.outgoing.fhdr = (void *)fhdr + kho_ser_size(luo_flb_header_ser);
 	luo_flb_global.outgoing.active = true;
 
 	return 0;
@@ -594,7 +603,7 @@ int __init luo_flb_setup_incoming(u64 flbs_pa)
 
 	luo_flb_global.incoming.header_ser =
 		kho_ser_payload(fhdr, luo_flb_header_ser);
-	luo_flb_global.incoming.ser = (void *)fhdr + fhdr->size;
+	luo_flb_global.incoming.fhdr = (void *)fhdr + fhdr->size;
 	luo_flb_global.incoming.active = true;
 
 	return 0;
@@ -626,10 +635,16 @@ void luo_flb_serialize(void)
 		long count = refcount_read(&private->outgoing.count);
 
 		if (count > 0) {
-			strscpy(fh->ser[i].name, gflb->compatible,
-				sizeof(fh->ser[i].name));
-			fh->ser[i].data = private->outgoing.data;
-			fh->ser[i].count = count;
+			DECLARE_KHO_SER_HDR(luo_flb_ser, *fhdr) =
+				(void *)fh->fhdr + i * kho_ser_size(luo_flb_ser);
+			struct luo_flb_ser *ser;
+
+			kho_ser_init(fhdr, luo_flb_ser);
+			ser = kho_ser_payload(fhdr, luo_flb_ser);
+			strscpy(ser->name, gflb->compatible,
+				sizeof(ser->name));
+			ser->data = private->outgoing.data;
+			ser->count = count;
 			i++;
 		}
 	}
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 15/19] PCI: liveupdate: Add feature header to struct pci_ser
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
                   ` (13 preceding siblings ...)
  2026-11-10  5:01 ` [PATCH v4 14/19] liveupdate: Add feature header to struct luo_flb_ser Tarun Sahu
@ 2026-11-10  5:01 ` Tarun Sahu
  2026-11-10  5:01 ` [PATCH v4 16/19] PCI: liveupdate: Add feature header to struct pci_dev_ser Tarun Sahu
                   ` (3 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:01 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

struct pci_ser currently defines its own PCI-specific required_flags and
optional_flags bitmasks (capped at 64 bits without recorded structure
size) and stores dev_ser_size in the parent structure even though KHO
block headers now record entry_size directly. In addition, struct
pci_ser and struct pci_dev_ser still carry __packed despite having
naturally aligned fields on 64-bit architectures.

Precede struct pci_ser with struct kho_ser_hdr via DECLARE_KHO_SER,
removing required_flags, optional_flags, and
PCI_SER_REQUIRED_FLAGS_SUPPORTED. Remove dev_ser_size and
PCI_DEV_SER_MIN_SIZE so restoration derives the struct pci_dev_ser stride
from the KHO block header entry_size. Drop __packed from struct pci_ser
and struct pci_dev_ser, adding an explicit u32 reserved field to struct
pci_ser for 8-byte alignment, and bump PCI_LUO_FLB_COMPATIBLE from pci to
pci-v2.

Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 drivers/pci/liveupdate.c    | 74 +++++++++++++++++--------------------
 include/linux/kho/abi/pci.h | 63 +++++++++++--------------------
 2 files changed, 55 insertions(+), 82 deletions(-)

diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 6f92d4ddca2b..0bda94f4a7e4 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -233,7 +233,7 @@ static struct pci_liveupdate_global pci_liveupdate = {
 
 /**
  * struct pci_flb_outgoing - Outgoing PCI FLB object
- * @ser: Pointer to the preserved struct pci_ser.
+ * @fhdr: Pointer to the serialization feature header.
  * @block_set: The KHO block set holding the outgoing devices.
  *
  * This structure holds the runtime state for the outgoing PCI Live Update
@@ -241,14 +241,14 @@ static struct pci_liveupdate_global pci_liveupdate = {
  * the serialized entries.
  */
 struct pci_flb_outgoing {
-	struct pci_ser *ser;
+	DECLARE_KHO_SER_HDR(pci_ser, *fhdr);
 	struct kho_block_set block_set;
 };
 
 /**
  * struct pci_flb_incoming - Incoming PCI FLB object
- * @ser: The incoming struct pci_ser from the previous kernel.
- * @xa: Xarray used to quickly lookup devices in @ser.
+ * @fhdr: Pointer to the serialization feature header.
+ * @xa: Xarray used to quickly lookup preserved devices.
  * @block_set: The KHO block set holding the incoming devices.
  *
  * This structure holds the runtime state for the incoming PCI Live Update
@@ -256,17 +256,11 @@ struct pci_flb_outgoing {
  * the serialized entries, and an xarray for fast lookups.
  */
 struct pci_flb_incoming {
-	struct pci_ser *ser;
+	DECLARE_KHO_SER_HDR(pci_ser, *fhdr);
 	struct xarray xa;
 	struct kho_block_set block_set;
 };
 
-/*
- * Bits in struct pci_ser::required_flags that this kernel knows how to handle.
- * No flags are currently defined.
- */
-#define PCI_SER_REQUIRED_FLAGS_SUPPORTED	0ULL
-
 /* PCI_LIVEUPDATE depends on 64BIT, so domain and bdf always fit. */
 static unsigned long pci_ser_xa_key(u32 domain, u16 bdf)
 {
@@ -276,27 +270,28 @@ static unsigned long pci_ser_xa_key(u32 domain, u16 bdf)
 static int pci_flb_preserve(struct liveupdate_flb_op_args *args)
 {
 	struct pci_flb_outgoing *outgoing __free(kfree) = NULL;
+	DECLARE_KHO_SER_HDR(pci_ser, *fhdr);
 	struct pci_ser *ser;
 
 	outgoing = kzalloc_obj(*outgoing);
 	if (!outgoing)
 		return -ENOMEM;
 
-	ser = kho_alloc_preserve(sizeof(*ser));
-	if (IS_ERR(ser))
-		return PTR_ERR(ser);
+	fhdr = kho_alloc_preserve(kho_ser_size(pci_ser));
+	if (IS_ERR(fhdr))
+		return PTR_ERR(fhdr);
 
-	ser->required_flags = 0;
-	ser->optional_flags = 0;
-	ser->dev_ser_size = sizeof(struct pci_dev_ser);
+	kho_ser_init(fhdr, pci_ser);
+	ser = kho_ser_payload(fhdr, pci_ser);
 	ser->nr_devices = 0;
+	ser->reserved = 0;
 	ser->devices = 0;
 
-	outgoing->ser = ser;
+	outgoing->fhdr = fhdr;
 	kho_block_set_init(&outgoing->block_set, sizeof(struct pci_dev_ser));
 
 	args->obj = no_free_ptr(outgoing);
-	args->data = virt_to_phys(ser);
+	args->data = virt_to_phys(fhdr);
 	pr_debug("Preserved struct pci_ser (0x%llx)\n", args->data);
 	return 0;
 }
@@ -307,9 +302,9 @@ static void pci_flb_unpreserve(struct liveupdate_flb_op_args *args)
 
 	pr_debug("Unpreserving struct pci_ser (0x%llx)\n", args->data);
 
-	WARN_ON(outgoing->ser->nr_devices);
+	WARN_ON(kho_ser_payload(outgoing->fhdr, pci_ser)->nr_devices);
 	kho_block_set_destroy(&outgoing->block_set);
-	kho_unpreserve_free(outgoing->ser);
+	kho_unpreserve_free(outgoing->fhdr);
 	kfree(outgoing);
 }
 
@@ -321,35 +316,29 @@ static void pci_flb_unpreserve(struct liveupdate_flb_op_args *args)
  */
 static int pci_flb_retrieve(struct liveupdate_flb_op_args *args)
 {
-	struct pci_ser *ser = phys_to_virt(args->data);
+	DECLARE_KHO_SER_HDR(pci_ser, *fhdr) = phys_to_virt(args->data);
 	struct pci_flb_incoming *incoming;
 	struct pci_dev_ser *dev_ser;
 	struct kho_block_set_it it;
+	struct pci_ser *ser;
 	int ret;
 
 	pr_debug("Retrieving struct pci_ser (0x%llx)\n", args->data);
 
-	if (ser->required_flags & ~PCI_SER_REQUIRED_FLAGS_SUPPORTED)
-		panic("Incoming PCI FLB requires unsupported features (0x%llx)\n",
-		      ser->required_flags & ~PCI_SER_REQUIRED_FLAGS_SUPPORTED);
+	ret = kho_ser_validate(fhdr, pci_ser);
+	if (ret)
+		panic("Failed to validate incoming PCI FLB feature header (%d)\n", ret);
 
-	/*
-	 * The previous kernel may have appended fields to struct pci_dev_ser,
-	 * which this kernel ignores, but every entry must be at least the
-	 * minimum size.
-	 */
-	if (ser->dev_ser_size < PCI_DEV_SER_MIN_SIZE)
-		panic("Incoming PCI FLB has unsupported struct pci_dev_ser size (%u)\n",
-		      ser->dev_ser_size);
+	ser = kho_ser_payload(fhdr, pci_ser);
 
 	incoming = kzalloc_obj(*incoming);
 	if (!incoming)
 		panic("Failed to allocate struct pci_flb_incoming\n");
 
-	incoming->ser = ser;
+	incoming->fhdr = fhdr;
 	xa_init(&incoming->xa);
 
-	kho_block_set_init(&incoming->block_set, ser->dev_ser_size);
+	kho_block_set_init(&incoming->block_set, sizeof(struct pci_dev_ser));
 	ret = kho_block_set_restore(&incoming->block_set, ser->devices);
 	if (ret)
 		panic("Failed to restore devices KHO block set (%d)\n", ret);
@@ -393,7 +382,7 @@ static void pci_check_all_devices_finished(struct pci_flb_incoming *incoming)
 	 * This runs once the last reference to the incoming FLB is dropped, so
 	 * there are no finishers left in flight.
 	 */
-	nr_devices = incoming->ser->nr_devices;
+	nr_devices = kho_ser_payload(incoming->fhdr, pci_ser)->nr_devices;
 	if (nr_devices == 0)
 		return;
 
@@ -428,7 +417,7 @@ static void pci_flb_finish(struct liveupdate_flb_op_args *args)
 
 	xa_destroy(&incoming->xa);
 	kho_block_set_destroy(&incoming->block_set);
-	kho_restore_free(incoming->ser);
+	kho_restore_free(incoming->fhdr);
 	kfree(incoming);
 }
 
@@ -522,7 +511,7 @@ static int pci_liveupdate_unpreserve_device(struct pci_flb_outgoing *outgoing,
 		return 0;
 
 	pci_info(dev, "Device will no longer be preserved across next Live Update\n");
-	outgoing->ser->nr_devices--;
+	kho_ser_payload(outgoing->fhdr, pci_ser)->nr_devices--;
 	memset(dev_ser, 0, sizeof(*dev_ser));
 	dev->liveupdate.outgoing = NULL;
 	return 0;
@@ -580,6 +569,7 @@ static int pci_liveupdate_preserve_device(struct pci_flb_outgoing *outgoing,
 	}
 
 	if (!dev->liveupdate.outgoing) {
+		struct pci_ser *ser = kho_ser_payload(outgoing->fhdr, pci_ser);
 		struct pci_dev_ser *dev_ser;
 
 		dev_ser = pci_flb_alloc_dev_ser(outgoing);
@@ -587,8 +577,8 @@ static int pci_liveupdate_preserve_device(struct pci_flb_outgoing *outgoing,
 			return PTR_ERR(dev_ser);
 
 		pci_info(dev, "Device will be preserved across next Live Update\n");
-		outgoing->ser->nr_devices++;
-		outgoing->ser->devices = kho_block_set_head_pa(&outgoing->block_set);
+		ser->nr_devices++;
+		ser->devices = kho_block_set_head_pa(&outgoing->block_set);
 
 		dev_ser->domain = pci_domain_nr(dev->bus);
 		dev_ser->bdf = pci_dev_id(dev);
@@ -876,6 +866,7 @@ static int pci_liveupdate_finish_device(struct pci_ser *ser, struct pci_dev *dev
 void pci_liveupdate_finish(struct pci_dev *dev)
 {
 	struct pci_flb_incoming *incoming;
+	struct pci_ser *ser;
 
 	guard(rwsem_write)(&pci_liveupdate.rwsem);
 
@@ -885,8 +876,9 @@ void pci_liveupdate_finish(struct pci_dev *dev)
 		return;
 	}
 
+	ser = kho_ser_payload(incoming->fhdr, pci_ser);
 	for_each_pci_dev_in_path(dev) {
-		if (pci_liveupdate_finish_device(incoming->ser, dev))
+		if (pci_liveupdate_finish_device(ser, dev))
 			break;
 	}
 
diff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h
index c2e3a5949f73..f53f6350e53d 100644
--- a/include/linux/kho/abi/pci.h
+++ b/include/linux/kho/abi/pci.h
@@ -8,9 +8,8 @@
 #ifndef _LINUX_KHO_ABI_PCI_H
 #define _LINUX_KHO_ABI_PCI_H
 
-#include <linux/bug.h>
-#include <linux/compiler.h>
-#include <linux/stddef.h>
+#include <linux/build_bug.h>
+#include <linux/kho/abi/feature.h>
 #include <linux/types.h>
 
 /**
@@ -18,33 +17,20 @@
  *
  * The PCI core preserves its state across kexec as Live Update
  * File-Lifecycle Bound (FLB) data, identified by the compatible string
- * ``"pci"`` and laid out as described by the structs below.
+ * ``"pci-v2"`` and laid out as described by the structs below.
  *
- * This layout is a contract between kernels. Rather than being versioned, it
- * is extended according to the following rules:
+ * struct pci_ser is preceded by &struct kho_ser_hdr, which records the
+ * total size of the serialization and feature flags. See
+ * include/linux/kho/abi/feature.h for the feature header layout, feature
+ * rules, and validation.
  *
- *  * Existing fields must never be moved, resized, removed, or have their
- *    meaning changed. New fields may only be appended to the end of a struct.
- *
- *  * A bit set in ``pci_ser.required_flags`` marks a feature that the next
- *    kernel must understand to safely take over the preserved devices. The
- *    next kernel panics if it finds a bit it does not know how to handle.
- *
- *  * A bit set in ``pci_ser.optional_flags`` marks a feature that the next
- *    kernel can safely ignore if it does not understand it.
- *
- *  * Fields appended to struct pci_ser must be accompanied by a flag that
- *    indicates they are present, since the size of struct pci_ser is not
- *    recorded.
- *
- *  * The size of each struct pci_dev_ser is recorded in
- *    ``pci_ser.dev_ser_size`` and is never smaller than
- *    ``PCI_DEV_SER_MIN_SIZE``. The next kernel ignores any fields beyond the
- *    ones it knows about, and must handle entries that are smaller than its
- *    own struct pci_dev_ser.
+ * Preserved devices are serialized as struct pci_dev_ser entries stored in
+ * a KHO block set. The block header records the entry size, allowing the
+ * next kernel to step through entries using the source kernel's stride
+ * even if fields are added in later versions.
  */
 
-#define PCI_LUO_FLB_COMPATIBLE "pci"
+#define PCI_LUO_FLB_COMPATIBLE "pci-v2"
 
 /**
  * struct pci_dev_ser - Serialized state about a single PCI device.
@@ -61,13 +47,9 @@ struct pci_dev_ser {
 	u32 domain;
 	u16 bdf;
 	u16 refcount;
-} __packed;
+};
 
-/*
- * The size of struct pci_dev_ser when this ABI was introduced. Since fields
- * can only be appended, every struct pci_dev_ser is at least this large.
- */
-#define PCI_DEV_SER_MIN_SIZE offsetofend(struct pci_dev_ser, refcount)
+static_assert(sizeof(struct pci_dev_ser) == 8);
 
 /**
  * struct pci_ser - PCI Subsystem Live Update State
@@ -77,20 +59,19 @@ struct pci_dev_ser {
  * core; the state a driver needs to resume its device is preserved separately
  * by that driver.
  *
- * @required_flags: Features that the next kernel must support to take over
- *                  the preserved devices. No flags are currently defined.
- * @optional_flags: Features that the next kernel may ignore if it does not
- *                  support them. No flags are currently defined.
- * @dev_ser_size: The size in bytes of each struct pci_dev_ser in @devices.
+ * Preceded by &struct kho_ser_hdr.
+ *
  * @nr_devices: The number of devices that were preserved.
+ * @reserved: Reserved for alignment.
  * @devices: Physical address of the first KHO block containing pci_dev_ser.
  */
 struct pci_ser {
-	u64 required_flags;
-	u64 optional_flags;
-	u32 dev_ser_size;
 	u32 nr_devices;
+	u32 reserved;
 	u64 devices;
-} __packed;
+};
+
+/* Register pci_ser with KHO serialization accessors */
+DECLARE_KHO_SER(pci_ser, 16, 0, 0);
 
 #endif /* _LINUX_KHO_ABI_PCI_H */
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 16/19] PCI: liveupdate: Add feature header to struct pci_dev_ser
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
                   ` (14 preceding siblings ...)
  2026-11-10  5:01 ` [PATCH v4 15/19] PCI: liveupdate: Add feature header to struct pci_ser Tarun Sahu
@ 2026-11-10  5:01 ` Tarun Sahu
  2026-11-10  5:02 ` [PATCH v4 17/19] mm: memfd_luo: Add feature header to struct memfd_luo_ser Tarun Sahu
                   ` (2 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:01 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

Precede each serialized PCI device entry in the PCI Live Update KHO block
set with struct kho_ser_hdr via DECLARE_KHO_SER so future per-device PCI
Live Update features can append fields to struct pci_dev_ser without
breaking compatibility between adjacent kernels.

Mark struct pci_dev_ser as 8-byte aligned since its largest member is u32,
initialize the device block sets with kho_ser_size(pci_dev_ser),
initialize each entry feature header in pci_flb_alloc_dev_ser(), and
validate incoming device feature headers in pci_flb_retrieve() before
accessing the struct pci_dev_ser payload.

Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 drivers/pci/liveupdate.c    | 27 +++++++++++++++++++--------
 include/linux/kho/abi/pci.h |  9 ++++++---
 2 files changed, 25 insertions(+), 11 deletions(-)

diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 0bda94f4a7e4..f4c84bcc0629 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -288,7 +288,7 @@ static int pci_flb_preserve(struct liveupdate_flb_op_args *args)
 	ser->devices = 0;
 
 	outgoing->fhdr = fhdr;
-	kho_block_set_init(&outgoing->block_set, sizeof(struct pci_dev_ser));
+	kho_block_set_init(&outgoing->block_set, kho_ser_size(pci_dev_ser));
 
 	args->obj = no_free_ptr(outgoing);
 	args->data = virt_to_phys(fhdr);
@@ -318,6 +318,7 @@ static int pci_flb_retrieve(struct liveupdate_flb_op_args *args)
 {
 	DECLARE_KHO_SER_HDR(pci_ser, *fhdr) = phys_to_virt(args->data);
 	struct pci_flb_incoming *incoming;
+	DECLARE_KHO_SER_HDR(pci_dev_ser, *dev_fhdr);
 	struct pci_dev_ser *dev_ser;
 	struct kho_block_set_it it;
 	struct pci_ser *ser;
@@ -338,15 +339,20 @@ static int pci_flb_retrieve(struct liveupdate_flb_op_args *args)
 	incoming->fhdr = fhdr;
 	xa_init(&incoming->xa);
 
-	kho_block_set_init(&incoming->block_set, sizeof(struct pci_dev_ser));
+	kho_block_set_init(&incoming->block_set, kho_ser_size(pci_dev_ser));
 	ret = kho_block_set_restore(&incoming->block_set, ser->devices);
 	if (ret)
 		panic("Failed to restore devices KHO block set (%d)\n", ret);
 
 	kho_block_set_it_init(&it, &incoming->block_set);
-	while ((dev_ser = kho_block_set_it_read_entry(&it))) {
+	while ((dev_fhdr = kho_block_set_it_read_entry(&it))) {
 		unsigned long key;
 
+		ret = kho_ser_validate(dev_fhdr, pci_dev_ser);
+		if (ret)
+			panic("Failed to validate incoming PCI device feature header (%d)\n", ret);
+
+		dev_ser = kho_ser_payload(dev_fhdr, pci_dev_ser);
 		if (!dev_ser->refcount)
 			continue;
 
@@ -456,6 +462,7 @@ static struct pci_flb_outgoing *pci_liveupdate_flb_get_outgoing(void)
 
 static struct pci_dev_ser *pci_flb_alloc_dev_ser(struct pci_flb_outgoing *outgoing)
 {
+	DECLARE_KHO_SER_HDR(pci_dev_ser, *fhdr);
 	struct pci_dev_ser *dev_ser;
 	struct kho_block_set_it it;
 	u64 count = 0;
@@ -464,9 +471,12 @@ static struct pci_dev_ser *pci_flb_alloc_dev_ser(struct pci_flb_outgoing *outgoi
 	kho_block_set_it_init(&it, &outgoing->block_set);
 
 	/* Try to find an existing, previously unpreserved, entry. */
-	while ((dev_ser = kho_block_set_it_read_entry(&it))) {
-		if (!dev_ser->refcount)
+	while ((fhdr = kho_block_set_it_read_entry(&it))) {
+		dev_ser = kho_ser_payload(fhdr, pci_dev_ser);
+		if (!dev_ser->refcount) {
+			kho_ser_init(fhdr, pci_dev_ser);
 			return dev_ser;
+		}
 
 		count++;
 	}
@@ -480,11 +490,12 @@ static struct pci_dev_ser *pci_flb_alloc_dev_ser(struct pci_flb_outgoing *outgoi
 		kho_block_set_it_init(&it, &outgoing->block_set);
 
 	/* This should always succeed since kho_block_set_grow() succeeded. */
-	dev_ser = kho_block_set_it_reserve_entry(&it);
-	if (WARN_ON_ONCE(!dev_ser))
+	fhdr = kho_block_set_it_reserve_entry(&it);
+	if (WARN_ON_ONCE(!fhdr))
 		return ERR_PTR(-ENOSPC);
 
-	return dev_ser;
+	kho_ser_init(fhdr, pci_dev_ser);
+	return kho_ser_payload(fhdr, pci_dev_ser);
 }
 
 static int pci_liveupdate_unpreserve_device(struct pci_flb_outgoing *outgoing,
diff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h
index f53f6350e53d..77ddf5938822 100644
--- a/include/linux/kho/abi/pci.h
+++ b/include/linux/kho/abi/pci.h
@@ -19,7 +19,8 @@
  * File-Lifecycle Bound (FLB) data, identified by the compatible string
  * ``"pci-v2"`` and laid out as described by the structs below.
  *
- * struct pci_ser is preceded by &struct kho_ser_hdr, which records the
+ * Both &struct pci_ser and each &struct pci_dev_ser entry are preceded by
+ * &struct kho_ser_hdr, which records the
  * total size of the serialization and feature flags. See
  * include/linux/kho/abi/feature.h for the feature header layout, feature
  * rules, and validation.
@@ -42,14 +43,16 @@
  *            reference count is equal to the number of preserved devices at or
  *            below it in the PCI hierarchy when the struct pci_dev_ser is in
  *            use, and 0 otherwise.
+ *
+ * Preceded by &struct kho_ser_hdr.
  */
 struct pci_dev_ser {
 	u32 domain;
 	u16 bdf;
 	u16 refcount;
-};
+} __aligned(8);
 
-static_assert(sizeof(struct pci_dev_ser) == 8);
+DECLARE_KHO_SER(pci_dev_ser, 8, 0, 0);
 
 /**
  * struct pci_ser - PCI Subsystem Live Update State
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 17/19] mm: memfd_luo: Add feature header to struct memfd_luo_ser
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
                   ` (15 preceding siblings ...)
  2026-11-10  5:01 ` [PATCH v4 16/19] PCI: liveupdate: Add feature header to struct pci_dev_ser Tarun Sahu
@ 2026-11-10  5:02 ` Tarun Sahu
  2026-11-10  5:02 ` [PATCH v4 18/19] mm: memfd_luo: Add feature header to struct memfd_luo_folio_ser Tarun Sahu
  2026-11-10  5:02 ` [PATCH v4 19/19] kho: Add KUnit test for the feature header helpers Tarun Sahu
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:02 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

Precede struct memfd_luo_ser with struct kho_ser_hdr using
DECLARE_KHO_SER. Later memfd features can be appended under new
feature indices without breaking compatibility between adjacent kernels.

Allocate both the memfd_luo_ser feature header and the kho_vmalloc
feature header in the same preserved folio. On restore and retrieve,
validate incoming required features before accessing payload fields.

Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 include/linux/kho/abi/memfd.h |  7 ++--
 mm/memfd_luo.c                | 67 ++++++++++++++++++++++++-----------
 2 files changed, 51 insertions(+), 23 deletions(-)

diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h
index b0597c438973..27f8880ee593 100644
--- a/include/linux/kho/abi/memfd.h
+++ b/include/linux/kho/abi/memfd.h
@@ -83,6 +83,8 @@ static_assert(!IS_ENABLED(CONFIG_64BIT) ||
  * @nr_folios:   Number of folios in the folios array.
  * @folios_fhdr: Pointer to KHO vmalloc descriptor for the array of
  *               struct memfd_luo_folio_ser.
+ *
+ * Preceded by &struct kho_ser_hdr.
  */
 struct memfd_luo_ser {
 	u64 pos;
@@ -93,9 +95,8 @@ struct memfd_luo_ser {
 	DECLARE_KHOSER_PTR(folios_fhdr, DECLARE_KHO_SER_HDR(kho_vmalloc, *));
 };
 
-static_assert(sizeof(struct memfd_luo_ser) == 40);
-static_assert(!IS_ENABLED(CONFIG_64BIT) ||
-	      __alignof__(struct memfd_luo_ser) == 8);
+/* Register memfd_luo_ser with KHO serialization accessors */
+DECLARE_KHO_SER(memfd_luo_ser, 40, 0, 0);
 
 /* The compatibility string for memfd file handler */
 #define MEMFD_LUO_FH_COMPATIBLE	"memfd-v3"
diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
index 74c1ff5877c3..94510f73a606 100644
--- a/mm/memfd_luo.c
+++ b/mm/memfd_luo.c
@@ -256,16 +256,21 @@ static void memfd_luo_unpreserve_folios(DECLARE_KHO_SER_HDR(kho_vmalloc, *folios
 
 static size_t memfd_luo_ser_size(void)
 {
-	return sizeof(struct memfd_luo_ser) + kho_ser_size(kho_vmalloc);
+	return kho_ser_size(memfd_luo_ser) + kho_ser_size(kho_vmalloc);
 }
 
-static struct memfd_luo_ser *memfd_luo_ser_init(void *mem)
+static struct memfd_luo_ser *
+memfd_luo_ser_init(DECLARE_KHO_SER_HDR(memfd_luo_ser, *fhdr))
 {
 	DECLARE_KHO_SER_HDR(kho_vmalloc, *folios_fhdr);
-	struct memfd_luo_ser *ser = mem;
+	struct memfd_luo_ser *ser;
+
+	kho_ser_init(fhdr, memfd_luo_ser);
+	folios_fhdr = (void *)fhdr + kho_ser_size(memfd_luo_ser);
 
-	folios_fhdr = mem + sizeof(struct memfd_luo_ser);
+	ser = kho_ser_payload(fhdr, memfd_luo_ser);
 	KHOSER_STORE_PTR(ser->folios_fhdr, folios_fhdr);
+
 	return ser;
 }
 
@@ -273,6 +278,7 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
 {
 	struct inode *inode = file_inode(args->file);
 	struct memfd_luo_folio_ser *folios_ser;
+	DECLARE_KHO_SER_HDR(memfd_luo_ser, *fhdr);
 	struct memfd_luo_ser *ser;
 	int err = 0, seals;
 	u64 inode_size;
@@ -304,12 +310,12 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
 	}
 
 	/* Allocate the main serialization structure in preserved memory */
-	ser = kho_alloc_preserve(memfd_luo_ser_size());
-	if (IS_ERR(ser)) {
-		err = PTR_ERR(ser);
+	fhdr = kho_alloc_preserve(memfd_luo_ser_size());
+	if (IS_ERR(fhdr)) {
+		err = PTR_ERR(fhdr);
 		goto err_unlock;
 	}
-	ser = memfd_luo_ser_init(ser);
+	ser = memfd_luo_ser_init(fhdr);
 
 	ser->pos = args->file->f_pos;
 	ser->size = inode_size;
@@ -323,12 +329,12 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
 	inode_unlock(inode);
 
 	args->private_data = folios_ser;
-	args->serialized_data = virt_to_phys(ser);
+	args->serialized_data = virt_to_phys(fhdr);
 
 	return 0;
 
 err_free_ser:
-	kho_unpreserve_free(ser);
+	kho_unpreserve_free(fhdr);
 err_unlock:
 	shmem_freeze(inode, false);
 	inode_unlock(inode);
@@ -337,12 +343,14 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
 
 static int memfd_luo_freeze(struct liveupdate_file_op_args *args)
 {
+	DECLARE_KHO_SER_HDR(memfd_luo_ser, *fhdr);
 	struct memfd_luo_ser *ser;
 
 	if (WARN_ON_ONCE(!args->serialized_data))
 		return -EINVAL;
 
-	ser = phys_to_virt(args->serialized_data);
+	fhdr = phys_to_virt(args->serialized_data);
+	ser = kho_ser_payload(fhdr, memfd_luo_ser);
 
 	/*
 	 * The pos might have changed since prepare. Everything else stays the
@@ -356,6 +364,7 @@ static int memfd_luo_freeze(struct liveupdate_file_op_args *args)
 static void memfd_luo_unpreserve(struct liveupdate_file_op_args *args)
 {
 	struct inode *inode = file_inode(args->file);
+	DECLARE_KHO_SER_HDR(memfd_luo_ser, *fhdr);
 	struct memfd_luo_ser *ser;
 
 	if (WARN_ON_ONCE(!args->serialized_data))
@@ -364,13 +373,14 @@ static void memfd_luo_unpreserve(struct liveupdate_file_op_args *args)
 	inode_lock(inode);
 	shmem_freeze(inode, false);
 
-	ser = phys_to_virt(args->serialized_data);
+	fhdr = phys_to_virt(args->serialized_data);
+	ser = kho_ser_payload(fhdr, memfd_luo_ser);
 
 	memfd_luo_unpreserve_folios(KHOSER_LOAD_PTR(ser->folios_fhdr),
 				    args->private_data,
 				    ser->nr_folios);
 
-	kho_unpreserve_free(ser);
+	kho_unpreserve_free(fhdr);
 	inode_unlock(inode);
 }
 
@@ -402,6 +412,7 @@ static void memfd_luo_discard_folios(const struct memfd_luo_folio_ser *folios_se
 static void memfd_luo_finish(struct liveupdate_file_op_args *args)
 {
 	struct memfd_luo_folio_ser *folios_ser;
+	DECLARE_KHO_SER_HDR(memfd_luo_ser, *fhdr);
 	struct memfd_luo_ser *ser;
 
 	/*
@@ -412,10 +423,16 @@ static void memfd_luo_finish(struct liveupdate_file_op_args *args)
 	if (args->retrieve_status)
 		return;
 
-	ser = phys_to_virt(args->serialized_data);
-	if (!ser)
+	if (WARN_ON_ONCE(!args->serialized_data))
 		return;
 
+	fhdr = phys_to_virt(args->serialized_data);
+	if (kho_ser_validate(fhdr, memfd_luo_ser)) {
+		pr_warn("invalid feature header, folios not discarded\n");
+		goto out;
+	}
+
+	ser = kho_ser_payload(fhdr, memfd_luo_ser);
 	if (ser->nr_folios) {
 		folios_ser = kho_restore_vmalloc(KHOSER_LOAD_PTR(ser->folios_fhdr));
 		if (!folios_ser)
@@ -426,7 +443,7 @@ static void memfd_luo_finish(struct liveupdate_file_op_args *args)
 	}
 
 out:
-	kho_restore_free(ser);
+	kho_restore_free(fhdr);
 }
 
 static int memfd_luo_retrieve_folios(struct file *file,
@@ -533,14 +550,24 @@ static int memfd_luo_retrieve_folios(struct file *file,
 static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
 {
 	struct memfd_luo_folio_ser *folios_ser;
+	DECLARE_KHO_SER_HDR(memfd_luo_ser, *fhdr);
 	struct memfd_luo_ser *ser;
 	struct file *file;
 	int err;
 
-	ser = phys_to_virt(args->serialized_data);
-	if (!ser)
+	if (WARN_ON_ONCE(!args->serialized_data))
 		return -EINVAL;
 
+	fhdr = phys_to_virt(args->serialized_data);
+	err = kho_ser_validate(fhdr, memfd_luo_ser);
+	if (err) {
+		pr_err("memfd_luo_ser feature validation failed: %pe\n",
+		       ERR_PTR(err));
+		goto free_ser;
+	}
+
+	ser = kho_ser_payload(fhdr, memfd_luo_ser);
+
 	/* Make sure the file only has seals supported by this version. */
 	if (ser->seals & ~MEMFD_LUO_ALL_SEALS) {
 		err = -EOPNOTSUPP;
@@ -581,14 +608,14 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
 	}
 
 	args->file = file;
-	kho_restore_free(ser);
+	kho_restore_free(fhdr);
 
 	return 0;
 
 put_file:
 	fput(file);
 free_ser:
-	kho_restore_free(ser);
+	kho_restore_free(fhdr);
 	return err;
 }
 
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 18/19] mm: memfd_luo: Add feature header to struct memfd_luo_folio_ser
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
                   ` (16 preceding siblings ...)
  2026-11-10  5:02 ` [PATCH v4 17/19] mm: memfd_luo: Add feature header to struct memfd_luo_ser Tarun Sahu
@ 2026-11-10  5:02 ` Tarun Sahu
  2026-11-10  5:02 ` [PATCH v4 19/19] kho: Add KUnit test for the feature header helpers Tarun Sahu
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:02 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

Precede each serialized folio entry in the preserved memfd folio array
with struct kho_ser_hdr via DECLARE_KHO_SER so future per-folio memfd
features can append fields to struct memfd_luo_folio_ser without breaking
compatibility between adjacent kernels.

Allocate the preserved folio array using kho_ser_size(memfd_luo_folio_ser)
per entry and initialize each entry feature header in
memfd_luo_preserve_folios(). On the incoming path in
memfd_luo_discard_folios() and memfd_luo_retrieve_folios(), validate each
entry feature header with kho_ser_validate() and advance between entries
using fhdr->size so entries written with a different feature layout are
traversed accurately.

Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 include/linux/kho/abi/memfd.h |  6 ++--
 mm/memfd_luo.c                | 66 +++++++++++++++++++++++++----------
 2 files changed, 51 insertions(+), 21 deletions(-)

diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h
index 27f8880ee593..1aad47ce7322 100644
--- a/include/linux/kho/abi/memfd.h
+++ b/include/linux/kho/abi/memfd.h
@@ -51,6 +51,8 @@
  * @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.
+ *
+ * Preceded by &struct kho_ser_hdr.
  */
 struct memfd_luo_folio_ser {
 	u64 pfn:52;
@@ -58,9 +60,7 @@ struct memfd_luo_folio_ser {
 	u64 index;
 };
 
-static_assert(sizeof(struct memfd_luo_folio_ser) == 16);
-static_assert(!IS_ENABLED(CONFIG_64BIT) ||
-	      __alignof__(struct memfd_luo_folio_ser) == 8);
+DECLARE_KHO_SER(memfd_luo_folio_ser, 16, 0, 0);
 
 /*
  * The set of seals this version supports preserving. If support for any new
diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
index 94510f73a606..c00daa8a508e 100644
--- a/mm/memfd_luo.c
+++ b/mm/memfd_luo.c
@@ -86,10 +86,10 @@
 
 static int memfd_luo_preserve_folios(struct file *file,
 				     struct memfd_luo_ser *ser,
-				     struct memfd_luo_folio_ser **out_folios_ser)
+				     DECLARE_KHO_SER_HDR(memfd_luo_folio_ser, **out_folios_ser))
 {
 	struct inode *inode = file_inode(file);
-	struct memfd_luo_folio_ser *folios_ser;
+	DECLARE_KHO_SER_HDR(memfd_luo_folio_ser, *folios_ser);
 	unsigned int max_folios;
 	long i, size, nr_pinned;
 	struct folio **folios;
@@ -138,14 +138,16 @@ static int memfd_luo_preserve_folios(struct file *file,
 	}
 	nr_folios = nr_pinned;
 
-	folios_ser = vcalloc(nr_folios, sizeof(*folios_ser));
+	folios_ser = vcalloc(nr_folios, kho_ser_size(memfd_luo_folio_ser));
 	if (!folios_ser) {
 		err = -ENOMEM;
 		goto err_unpin;
 	}
 
 	for (i = 0; i < nr_folios; i++) {
-		struct memfd_luo_folio_ser *pfolio = &folios_ser[i];
+		DECLARE_KHO_SER_HDR(memfd_luo_folio_ser, *fhdr) =
+			(void *)folios_ser + i * kho_ser_size(memfd_luo_folio_ser);
+		struct memfd_luo_folio_ser *pfolio;
 		struct folio *folio = folios[i];
 
 		err = kho_preserve_folio(folio);
@@ -195,6 +197,8 @@ static int memfd_luo_preserve_folios(struct file *file,
 
 		folio_unlock(folio);
 
+		kho_ser_init(fhdr, memfd_luo_folio_ser);
+		pfolio = kho_ser_payload(fhdr, memfd_luo_folio_ser);
 		pfolio->pfn = folio_pfn(folio);
 		pfolio->flags = MEMFD_LUO_FOLIO_DIRTY | MEMFD_LUO_FOLIO_UPTODATE;
 		pfolio->index = folio->index;
@@ -228,7 +232,7 @@ static int memfd_luo_preserve_folios(struct file *file,
 }
 
 static void memfd_luo_unpreserve_folios(DECLARE_KHO_SER_HDR(kho_vmalloc, *folios_fhdr),
-					struct memfd_luo_folio_ser *folios_ser,
+					const DECLARE_KHO_SER_HDR(memfd_luo_folio_ser, *folios_ser),
 					u64 nr_folios)
 {
 	long i;
@@ -239,7 +243,10 @@ static void memfd_luo_unpreserve_folios(DECLARE_KHO_SER_HDR(kho_vmalloc, *folios
 	kho_unpreserve_vmalloc(folios_fhdr);
 
 	for (i = 0; i < nr_folios; i++) {
-		const struct memfd_luo_folio_ser *pfolio = &folios_ser[i];
+		const DECLARE_KHO_SER_HDR(memfd_luo_folio_ser, *fhdr) =
+			(const void *)folios_ser + i * kho_ser_size(memfd_luo_folio_ser);
+		const struct memfd_luo_folio_ser *pfolio =
+			kho_ser_payload(fhdr, memfd_luo_folio_ser);
 		struct folio *folio;
 
 		if (!pfolio->pfn)
@@ -277,7 +284,7 @@ memfd_luo_ser_init(DECLARE_KHO_SER_HDR(memfd_luo_ser, *fhdr))
 static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
 {
 	struct inode *inode = file_inode(args->file);
-	struct memfd_luo_folio_ser *folios_ser;
+	DECLARE_KHO_SER_HDR(memfd_luo_folio_ser, *folios_ser);
 	DECLARE_KHO_SER_HDR(memfd_luo_ser, *fhdr);
 	struct memfd_luo_ser *ser;
 	int err = 0, seals;
@@ -384,16 +391,23 @@ static void memfd_luo_unpreserve(struct liveupdate_file_op_args *args)
 	inode_unlock(inode);
 }
 
-static void memfd_luo_discard_folios(const struct memfd_luo_folio_ser *folios_ser,
+static void memfd_luo_discard_folios(const DECLARE_KHO_SER_HDR(memfd_luo_folio_ser, *folios_ser),
 				     u64 nr_folios)
 {
+	const DECLARE_KHO_SER_HDR(memfd_luo_folio_ser, *fhdr) = folios_ser;
 	u64 i;
 
-	for (i = 0; i < nr_folios; i++) {
-		const struct memfd_luo_folio_ser *pfolio = &folios_ser[i];
+	for (i = 0; i < nr_folios; i++, fhdr = (const void *)fhdr + fhdr->size) {
+		const struct memfd_luo_folio_ser *pfolio;
 		struct folio *folio;
 		phys_addr_t phys;
 
+		if (kho_ser_validate(fhdr, memfd_luo_folio_ser)) {
+			pr_warn("invalid folio feature header, folios not discarded\n");
+			return;
+		}
+
+		pfolio = kho_ser_payload(fhdr, memfd_luo_folio_ser);
 		if (!pfolio->pfn)
 			continue;
 
@@ -411,7 +425,7 @@ static void memfd_luo_discard_folios(const struct memfd_luo_folio_ser *folios_se
 
 static void memfd_luo_finish(struct liveupdate_file_op_args *args)
 {
-	struct memfd_luo_folio_ser *folios_ser;
+	DECLARE_KHO_SER_HDR(memfd_luo_folio_ser, *folios_ser);
 	DECLARE_KHO_SER_HDR(memfd_luo_ser, *fhdr);
 	struct memfd_luo_ser *ser;
 
@@ -447,9 +461,10 @@ static void memfd_luo_finish(struct liveupdate_file_op_args *args)
 }
 
 static int memfd_luo_retrieve_folios(struct file *file,
-				     struct memfd_luo_folio_ser *folios_ser,
+				     const DECLARE_KHO_SER_HDR(memfd_luo_folio_ser, *folios_ser),
 				     u64 nr_folios)
 {
+	const DECLARE_KHO_SER_HDR(memfd_luo_folio_ser, *fhdr) = folios_ser;
 	struct inode *inode = file_inode(file);
 	struct address_space *mapping = inode->i_mapping;
 	struct folio *folio;
@@ -457,12 +472,20 @@ static int memfd_luo_retrieve_folios(struct file *file,
 	int err = -EIO;
 	long i;
 
-	for (i = 0; i < nr_folios; i++) {
-		const struct memfd_luo_folio_ser *pfolio = &folios_ser[i];
+	for (i = 0; i < nr_folios; i++, fhdr = (const void *)fhdr + fhdr->size) {
+		const struct memfd_luo_folio_ser *pfolio;
 		phys_addr_t phys;
 		u64 index;
 		int flags;
 
+		err = kho_ser_validate(fhdr, memfd_luo_folio_ser);
+		if (err) {
+			pr_err("memfd_luo_folio_ser feature validation failed: %pe\n",
+			       ERR_PTR(err));
+			goto put_folios;
+		}
+
+		pfolio = kho_ser_payload(fhdr, memfd_luo_folio_ser);
 		if (!pfolio->pfn)
 			continue;
 
@@ -472,7 +495,7 @@ static int memfd_luo_retrieve_folios(struct file *file,
 			pr_err("Unable to restore folio at physical address: %llx\n",
 			       phys);
 			err = -EIO;
-			goto put_folios;
+			goto next_put_folios;
 		}
 		index = pfolio->index;
 		flags = pfolio->flags;
@@ -524,15 +547,22 @@ static int memfd_luo_retrieve_folios(struct file *file,
 unlock_folio:
 	folio_unlock(folio);
 	folio_put(folio);
+next_put_folios:
+	i++;
+	fhdr = (const void *)fhdr + fhdr->size;
 put_folios:
 	/*
 	 * Note: don't free the folios already added to the file. They will be
 	 * freed when the file is freed. Free the ones not added yet here.
 	 */
-	for (long j = i + 1; j < nr_folios; j++) {
-		const struct memfd_luo_folio_ser *pfolio = &folios_ser[j];
+	for (; i < nr_folios; i++, fhdr = (const void *)fhdr + fhdr->size) {
+		const struct memfd_luo_folio_ser *pfolio;
 		phys_addr_t phys;
 
+		if (kho_ser_validate(fhdr, memfd_luo_folio_ser))
+			break;
+
+		pfolio = kho_ser_payload(fhdr, memfd_luo_folio_ser);
 		if (!pfolio->pfn)
 			continue;
 
@@ -549,7 +579,7 @@ static int memfd_luo_retrieve_folios(struct file *file,
 
 static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
 {
-	struct memfd_luo_folio_ser *folios_ser;
+	DECLARE_KHO_SER_HDR(memfd_luo_folio_ser, *folios_ser);
 	DECLARE_KHO_SER_HDR(memfd_luo_ser, *fhdr);
 	struct memfd_luo_ser *ser;
 	struct file *file;
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 19/19] kho: Add KUnit test for the feature header helpers
  2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
                   ` (17 preceding siblings ...)
  2026-11-10  5:02 ` [PATCH v4 18/19] mm: memfd_luo: Add feature header to struct memfd_luo_folio_ser Tarun Sahu
@ 2026-11-10  5:02 ` Tarun Sahu
  18 siblings, 0 replies; 20+ messages in thread
From: Tarun Sahu @ 2026-11-10  5:02 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, robin.murphy, maz, will,
	David Matlack, Alexander Graf, seanjc, jgg, loganodell, arnd,
	dwmw2, baolu.lu, Bjorn Helgaas, oupton, alex, gle.com, pbonzini,
	Andrew Morton, kevin.tian, joro
  Cc: kvmarm, linux-pci, Pratyush Yadav, kexec, kvm, iommu,
	linux-arm-kernel, skhawaja, sagis, linux-arch, linux-mm,
	linux-kernel, Tarun Sahu

Add a KUnit test suite in lib/tests/kho_feature_kunit.c covering
the Kexec HandOver feature header declared in
include/linux/kho/abi/feature.h.

The suite contains three focused test cases:
- kho_ser_hdr_zero_flags_test: verifies zero-feature header
  initialization, payload sizing, and empty feature queries.
- kho_ser_hdr_set_get_test: verifies DECLARE_KHO_SER structure sizing,
  header initialization, payload pointer resolution, and 1-bit
  feature flag setting and retrieval across flags entry boundaries,
  including out-of-range queries.
- kho_ser_hdr_validate_test: verifies __kho_ser_validate rejecting
  unsupported required feature flags while ignoring optional flags.

Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
 MAINTAINERS                   |   1 +
 lib/Kconfig.debug             |  13 +++
 lib/tests/Makefile            |   1 +
 lib/tests/kho_feature_kunit.c | 184 ++++++++++++++++++++++++++++++++++
 4 files changed, 199 insertions(+)
 create mode 100644 lib/tests/kho_feature_kunit.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 1572f6c905aa..0b50de45140f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14786,6 +14786,7 @@ F:	include/linux/kho/
 F:	include/linux/kho_block.h
 F:	kernel/liveupdate/kexec_handover*
 F:	lib/test_kho.c
+F:	lib/tests/kho_feature_kunit.c
 F:	tools/testing/selftests/kho/
 
 KEYS-ENCRYPTED
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 2d22638ec13a..3d74d32b181d 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2967,6 +2967,19 @@ config DIMLIB_KUNIT_TEST
 	  For more information on KUnit and unit tests in general please refer
 	  to the KUnit documentation in Documentation/dev-tools/kunit/.
 
+config KHO_FEATURE_KUNIT_TEST
+	tristate "KUnit test for the Kexec HandOver feature header helpers" if !KUNIT_ALL_TESTS
+	depends on KUNIT
+	default KUNIT_ALL_TESTS
+	help
+	  This option enables the KUnit test suite for the Kexec HandOver
+	  feature header helpers defined in include/linux/kho/abi/feature.h.
+	  It checks the feature flag encoding, the flags array sizing, the
+	  feature bit helpers, and header validation rules.
+
+	  The helpers are self-contained, so the test does not depend on
+	  CONFIG_KEXEC_HANDOVER and can also run under UML.
+
 	  If unsure, say N.
 
 config CMDLINE_KUNIT_TEST
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
index df0537e1b91f..f22a4552f755 100644
--- a/lib/tests/Makefile
+++ b/lib/tests/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_KFIFO_KUNIT_TEST) += kfifo_kunit.o
 obj-$(CONFIG_TEST_LIST_SORT) += test_list_sort.o
 obj-$(CONFIG_LINEAR_RANGES_TEST) += test_linear_ranges.o
 obj-$(CONFIG_LIVEUPDATE_TEST) += liveupdate.o
+obj-$(CONFIG_KHO_FEATURE_KUNIT_TEST) += kho_feature_kunit.o
 
 CFLAGS_longest_symbol_kunit.o += $(call cc-disable-warning, missing-prototypes)
 obj-$(CONFIG_LONGEST_SYM_KUNIT_TEST) += longest_symbol_kunit.o
diff --git a/lib/tests/kho_feature_kunit.c b/lib/tests/kho_feature_kunit.c
new file mode 100644
index 000000000000..db36895d53f5
--- /dev/null
+++ b/lib/tests/kho_feature_kunit.c
@@ -0,0 +1,184 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for the Kexec HandOver feature header helpers
+ * in include/linux/kho/abi/feature.h.
+ *
+ * Copyright (c) 2026, Google LLC.
+ * Tarun Sahu <tarunsahu@google.com>
+ */
+
+#include <kunit/test.h>
+#include <linux/kho/abi/feature.h>
+#include <linux/module.h>
+
+struct test_kho_payload {
+	u64 val;
+};
+
+enum test_kho_req_feat {
+	TEST_KHO_REQ_0 = 0,
+	TEST_KHO_REQ_63 = 63,
+	TEST_KHO_REQ_64 = 64,
+	TEST_KHO_REQ_69 = 69,
+	TEST_KHO_NR_REQUIRED = 70,
+};
+
+enum test_kho_opt_feat {
+	TEST_KHO_OPT_0 = 0,
+	TEST_KHO_OPT_63 = 63,
+	TEST_KHO_OPT_64 = 64,
+	TEST_KHO_OPT_69 = 69,
+	TEST_KHO_NR_OPTIONAL = 70,
+};
+
+DECLARE_KHO_SER(test_kho_payload, 8, TEST_KHO_NR_REQUIRED, TEST_KHO_NR_OPTIONAL);
+
+struct test_kho_zero {
+	u64 val;
+};
+
+DECLARE_KHO_SER(test_kho_zero, 8, 0, 0);
+
+static void kho_ser_hdr_zero_flags_test(struct kunit *test)
+{
+	DECLARE_KHO_SER_HDR(test_kho_zero, *fhdr);
+	const DECLARE_KHO_SER_HDR(test_kho_zero, *const_fhdr);
+	u8 buf[kho_ser_size(test_kho_zero)] __aligned(8);
+	struct test_kho_zero *p;
+
+	KUNIT_EXPECT_EQ(test, sizeof(struct kho_ser_hdr), 8UL);
+	KUNIT_EXPECT_EQ(test, sizeof(*fhdr), sizeof(struct kho_ser_hdr));
+	KUNIT_EXPECT_EQ(test, kho_ser_size(test_kho_zero), 16UL);
+
+	fhdr = (void *)buf;
+	kho_ser_init(fhdr, test_kho_zero);
+	KUNIT_EXPECT_EQ(test, fhdr->size, 16U);
+	KUNIT_EXPECT_EQ(test, fhdr->nr_required, 0U);
+	KUNIT_EXPECT_EQ(test, fhdr->nr_optional, 0U);
+	KUNIT_EXPECT_EQ(test, kho_ser_hdr_size(fhdr), 8UL);
+
+	const_fhdr = fhdr;
+	KUNIT_EXPECT_EQ(test, const_fhdr->size, 16U);
+
+	p = kho_ser_payload(fhdr, test_kho_zero);
+	KUNIT_EXPECT_PTR_EQ(test, p, (void *)(buf + 8));
+
+	p = kho_ser_payload(const_fhdr, test_kho_zero);
+	KUNIT_EXPECT_PTR_EQ(test, p, (void *)(buf + 8));
+
+	KUNIT_EXPECT_FALSE(test, kho_ser_has_required(fhdr, 0));
+	KUNIT_EXPECT_FALSE(test, kho_ser_has_optional(fhdr, 0));
+	KUNIT_EXPECT_EQ(test, kho_ser_validate(fhdr, test_kho_zero), 0);
+	KUNIT_EXPECT_EQ(test, kho_ser_validate(const_fhdr, test_kho_zero), 0);
+}
+
+static void kho_ser_hdr_set_get_test(struct kunit *test)
+{
+	DECLARE_KHO_SER_HDR(test_kho_payload, *fhdr);
+	const DECLARE_KHO_SER_HDR(test_kho_payload, *const_fhdr);
+	u8 buf[kho_ser_size(test_kho_payload)] __aligned(8);
+	struct test_kho_payload *p;
+
+	KUNIT_EXPECT_EQ(test, kho_ser_size(test_kho_payload), 40UL);
+
+	fhdr = (void *)buf;
+	kho_ser_init(fhdr, test_kho_payload);
+	KUNIT_EXPECT_EQ(test, fhdr->size, 40U);
+	KUNIT_EXPECT_EQ(test, fhdr->nr_required, 70U);
+	KUNIT_EXPECT_EQ(test, fhdr->nr_optional, 70U);
+	KUNIT_EXPECT_EQ(test, kho_ser_hdr_size(fhdr), 32UL);
+
+	const_fhdr = fhdr;
+	KUNIT_EXPECT_EQ(test, const_fhdr->size, 40U);
+	KUNIT_EXPECT_EQ(test, const_fhdr->nr_required, 70U);
+	KUNIT_EXPECT_EQ(test, const_fhdr->nr_optional, 70U);
+
+	p = kho_ser_payload(fhdr, test_kho_payload);
+	KUNIT_EXPECT_PTR_EQ(test, p, (void *)(buf + 32));
+
+	p = kho_ser_payload(const_fhdr, test_kho_payload);
+	KUNIT_EXPECT_PTR_EQ(test, p, (void *)(buf + 32));
+
+	kho_ser_set_required(fhdr, TEST_KHO_REQ_0);
+	kho_ser_set_required(fhdr, TEST_KHO_REQ_63);
+	kho_ser_set_required(fhdr, TEST_KHO_REQ_64);
+	kho_ser_set_required(fhdr, TEST_KHO_REQ_69);
+
+	KUNIT_EXPECT_TRUE(test, kho_ser_has_required(fhdr, TEST_KHO_REQ_0));
+	KUNIT_EXPECT_FALSE(test, kho_ser_has_required(fhdr, 1));
+	KUNIT_EXPECT_TRUE(test, kho_ser_has_required(fhdr, TEST_KHO_REQ_63));
+	KUNIT_EXPECT_TRUE(test, kho_ser_has_required(fhdr, TEST_KHO_REQ_64));
+	KUNIT_EXPECT_FALSE(test, kho_ser_has_required(fhdr, 65));
+	KUNIT_EXPECT_TRUE(test, kho_ser_has_required(fhdr, TEST_KHO_REQ_69));
+
+	KUNIT_EXPECT_TRUE(test, !!(fhdr->flags[0] & (1ULL << 0)));
+	KUNIT_EXPECT_TRUE(test, !!(fhdr->flags[0] & (1ULL << 63)));
+	KUNIT_EXPECT_TRUE(test, !!(fhdr->flags[1] & (1ULL << 0)));
+	KUNIT_EXPECT_TRUE(test, !!(fhdr->flags[1] & (1ULL << 5)));
+
+	kho_ser_set_optional(fhdr, TEST_KHO_OPT_0);
+	kho_ser_set_optional(fhdr, TEST_KHO_OPT_63);
+	kho_ser_set_optional(fhdr, TEST_KHO_OPT_64);
+	kho_ser_set_optional(fhdr, TEST_KHO_OPT_69);
+
+	KUNIT_EXPECT_TRUE(test, kho_ser_has_optional(fhdr, TEST_KHO_OPT_0));
+	KUNIT_EXPECT_FALSE(test, kho_ser_has_optional(fhdr, 1));
+	KUNIT_EXPECT_TRUE(test, kho_ser_has_optional(fhdr, TEST_KHO_OPT_63));
+	KUNIT_EXPECT_TRUE(test, kho_ser_has_optional(fhdr, TEST_KHO_OPT_64));
+	KUNIT_EXPECT_FALSE(test, kho_ser_has_optional(fhdr, 65));
+	KUNIT_EXPECT_TRUE(test, kho_ser_has_optional(fhdr, TEST_KHO_OPT_69));
+
+	KUNIT_EXPECT_TRUE(test, !!(fhdr->flags[1] & (1ULL << 6)));
+	KUNIT_EXPECT_TRUE(test, !!(fhdr->flags[2] & (1ULL << 5)));
+	KUNIT_EXPECT_TRUE(test, !!(fhdr->flags[2] & (1ULL << 6)));
+	KUNIT_EXPECT_TRUE(test, !!(fhdr->flags[2] & (1ULL << 11)));
+
+	KUNIT_EXPECT_FALSE(test, kho_ser_has_required(fhdr, 70));
+	KUNIT_EXPECT_FALSE(test, kho_ser_has_required(fhdr, 100));
+	KUNIT_EXPECT_FALSE(test, kho_ser_has_optional(fhdr, 70));
+	KUNIT_EXPECT_FALSE(test, kho_ser_has_optional(fhdr, 100));
+}
+
+static void kho_ser_hdr_validate_test(struct kunit *test)
+{
+	u8 buf[sizeof(struct kho_ser_hdr) + sizeof(u64) * 2] __aligned(8);
+	struct kho_ser_hdr *fhdr = (void *)buf;
+
+	__kho_ser_init(fhdr, 0, 4, 10);
+	KUNIT_EXPECT_EQ(test, __kho_ser_validate(fhdr, 4), 0);
+	KUNIT_EXPECT_EQ(test, __kho_ser_validate(fhdr, 2), 0);
+
+	kho_ser_set_optional(fhdr, 0);
+	kho_ser_set_optional(fhdr, 9);
+	KUNIT_EXPECT_EQ(test, __kho_ser_validate(fhdr, 2), 0);
+
+	kho_ser_set_required(fhdr, 0);
+	kho_ser_set_required(fhdr, 1);
+	KUNIT_EXPECT_EQ(test, __kho_ser_validate(fhdr, 2), 0);
+
+	kho_ser_set_required(fhdr, 2);
+	KUNIT_EXPECT_EQ(test, __kho_ser_validate(fhdr, 2), -EOPNOTSUPP);
+
+	kho_ser_clear_required(fhdr, 2);
+	KUNIT_EXPECT_EQ(test, __kho_ser_validate(fhdr, 2), 0);
+
+	kho_ser_set_required(fhdr, 3);
+	KUNIT_EXPECT_EQ(test, __kho_ser_validate(fhdr, 2), -EOPNOTSUPP);
+}
+
+static struct kunit_case kho_feature_test_cases[] = {
+	KUNIT_CASE(kho_ser_hdr_zero_flags_test),
+	KUNIT_CASE(kho_ser_hdr_set_get_test),
+	KUNIT_CASE(kho_ser_hdr_validate_test),
+	{}
+};
+
+static struct kunit_suite kho_feature_test_suite = {
+	.name = "kho_feature",
+	.test_cases = kho_feature_test_cases,
+};
+
+kunit_test_suite(kho_feature_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for the Kexec HandOver feature header helpers");
+MODULE_LICENSE("GPL");
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2026-11-10  5:02 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-10  5:01 [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Tarun Sahu
2026-11-10  5:01 ` [PATCH v4 01/19] liveupdate, kho: Remove __packed from KHO, LUO and memfd ABI structures Tarun Sahu
2026-11-10  5:01 ` [PATCH v4 02/19] kho: block: Record the entry size in the block header Tarun Sahu
2026-11-10  5:01 ` [PATCH v4 03/19] kho, memfd_luo: Reference struct kho_vmalloc via serialized pointer Tarun Sahu
2026-11-10  5:01 ` [PATCH v4 04/19] liveupdate: Reference struct luo_file_set_ser " Tarun Sahu
2026-11-10  5:01 ` [PATCH v4 05/19] kho: Add feature header for fine-grained ABI compatibility Tarun Sahu
2026-11-10  5:01 ` [PATCH v4 06/19] kho: kexec_metadata: Add the feature header Tarun Sahu
2026-11-10  5:01 ` [PATCH v4 07/19] kho: vmalloc: " Tarun Sahu
2026-11-10  5:01 ` [PATCH v4 08/19] kho: block: Add the feature header to blocks Tarun Sahu
2026-11-10  5:01 ` [PATCH v4 09/19] liveupdate: Add feature header to struct luo_ser Tarun Sahu
2026-11-10  5:01 ` [PATCH v4 10/19] liveupdate: Add feature header to struct luo_session_ser Tarun Sahu
2026-11-10  5:01 ` [PATCH v4 11/19] liveupdate: Add feature header to struct luo_file_set_ser Tarun Sahu
2026-11-10  5:01 ` [PATCH v4 12/19] liveupdate: Add feature header to struct luo_file_ser Tarun Sahu
2026-11-10  5:01 ` [PATCH v4 13/19] liveupdate: Add feature header to struct luo_flb_header_ser Tarun Sahu
2026-11-10  5:01 ` [PATCH v4 14/19] liveupdate: Add feature header to struct luo_flb_ser Tarun Sahu
2026-11-10  5:01 ` [PATCH v4 15/19] PCI: liveupdate: Add feature header to struct pci_ser Tarun Sahu
2026-11-10  5:01 ` [PATCH v4 16/19] PCI: liveupdate: Add feature header to struct pci_dev_ser Tarun Sahu
2026-11-10  5:02 ` [PATCH v4 17/19] mm: memfd_luo: Add feature header to struct memfd_luo_ser Tarun Sahu
2026-11-10  5:02 ` [PATCH v4 18/19] mm: memfd_luo: Add feature header to struct memfd_luo_folio_ser Tarun Sahu
2026-11-10  5:02 ` [PATCH v4 19/19] kho: Add KUnit test for the feature header helpers Tarun Sahu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®