* [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 12:56 ` Pasha Tatashin
2026-11-10 5:01 ` [PATCH v4 02/19] kho: block: Record the entry size in the block header Tarun Sahu
` (18 subsequent siblings)
19 siblings, 1 reply; 35+ 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] 35+ messages in thread* Re: [PATCH v4 01/19] liveupdate, kho: Remove __packed from KHO, LUO and memfd ABI structures
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 12:56 ` Pasha Tatashin
2026-11-10 17:28 ` tarunsahu
0 siblings, 1 reply; 35+ messages in thread
From: Pasha Tatashin @ 2026-11-10 12:56 UTC (permalink / raw)
To: Tarun Sahu, 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
On Tue Nov 10, 2026 at 12:01 AM EST, Tarun Sahu wrote:
> 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.
Just state:
Remove __packed, as it may break natural alignment. Instead, ensure that
all padding holes are explicitly covered by reserved or similar fields,
as is already done for KHO/LUO serialized data, so no functional changes
are intended by this change.
>
> 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);
Please remove the two assertions above throughout this patch, as they
are replaced later in the series. In general, introducing add -> remove
-> add churn across a patch series is discouraged, as it indicates a
lack of planning.
>
> #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"
--
Pasha
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [PATCH v4 01/19] liveupdate, kho: Remove __packed from KHO, LUO and memfd ABI structures
2026-11-10 12:56 ` Pasha Tatashin
@ 2026-11-10 17:28 ` tarunsahu
0 siblings, 0 replies; 35+ messages in thread
From: tarunsahu @ 2026-11-10 17:28 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
"Pasha Tatashin" <pasha.tatashin@soleen.com> writes:
> On Tue Nov 10, 2026 at 12:01 AM EST, Tarun Sahu wrote:
>> 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.
>
> Just state:
>
> Remove __packed, as it may break natural alignment. Instead, ensure that
> all padding holes are explicitly covered by reserved or similar fields,
> as is already done for KHO/LUO serialized data, so no functional changes
> are intended by this change.
This look good. I will update it. Thanks.
>
>>
>> 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);
>
> Please remove the two assertions above throughout this patch, as they
> are replaced later in the series. In general, introducing add -> remove
> -> add churn across a patch series is discouraged, as it indicates a
> lack of planning.
Got it. Will remove it.
~Tarun
>
>>
>> #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"
>
> --
> Pasha
^ permalink raw reply [flat|nested] 35+ 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 13:10 ` Pasha Tatashin
2026-11-10 17:03 ` David Matlack
2026-11-10 5:01 ` [PATCH v4 03/19] kho, memfd_luo: Reference struct kho_vmalloc via serialized pointer Tarun Sahu
` (17 subsequent siblings)
19 siblings, 2 replies; 35+ 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] 35+ messages in thread* Re: [PATCH v4 02/19] kho: block: Record the entry size in the block header
2026-11-10 5:01 ` [PATCH v4 02/19] kho: block: Record the entry size in the block header Tarun Sahu
@ 2026-11-10 13:10 ` Pasha Tatashin
2026-11-10 17:03 ` David Matlack
1 sibling, 0 replies; 35+ messages in thread
From: Pasha Tatashin @ 2026-11-10 13:10 UTC (permalink / raw)
To: Tarun Sahu, 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
On Tue Nov 10, 2026 at 12:01 AM EST, Tarun Sahu wrote:
> 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);
The above change goes away once my comment on the previous patch
is addressed.
>
> 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"
These are just examples, don't update them.
> *
> * 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
"newer kernel" does not make sense in the documentation comment, as any
kernel can do that. I would suggest "outgoing kernel." By the way, in
this series you use the terms "source" and "destination" kernel, whereas
traditionally we have used "outgoing" and "incoming." While both
conventions work, it would be preferable to maintain consistency.
> + * 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.
--
Pasha
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [PATCH v4 02/19] kho: block: Record the entry size in the block header
2026-11-10 5:01 ` [PATCH v4 02/19] kho: block: Record the entry size in the block header Tarun Sahu
2026-11-10 13:10 ` Pasha Tatashin
@ 2026-11-10 17:03 ` David Matlack
1 sibling, 0 replies; 35+ messages in thread
From: David Matlack @ 2026-11-10 17:03 UTC (permalink / raw)
To: Tarun Sahu, 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
On Tue Nov 10, 2026 at 5:01 AM UTC, Tarun Sahu wrote:
> 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,
Since the entry size now comes from the block headers, incoming block
sets should not take an entry size at all. I pointed this out on the
PCI patch (16/19) as well. As is, every incoming path still calls
kho_block_set_init() with this kernel's (outgoing) entry size, which
kho_block_set_restore() then silently overwrites. That makes the
callers (e.g. pci_flb_retrieve() and the LUO session/file restore
paths) read as if they iterate incoming entries with the outgoing entry
size. kho_block_set_restore() should initialize the block set itself
from the incoming block headers, and the incoming paths should drop the
kho_block_set_init() call.
Please also add an accessor for the entry size, e.g.
kho_block_set_entry_size(). Users of the block set need it to check that
incoming entries are large enough for the fields they read, and that
the fields of a feature are within the entry.
[...]
> + * 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.
KHO blocks can't guarantee the last part. The iterator returns a void *
that the caller casts to its own struct, so it is up to the caller to
not read fields beyond the entry size. Please document that callers must
check the entry size before accessing fields that may not be present.
^ permalink raw reply [flat|nested] 35+ 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 14:23 ` Pasha Tatashin
2026-11-10 5:01 ` [PATCH v4 04/19] liveupdate: Reference struct luo_file_set_ser " Tarun Sahu
` (16 subsequent siblings)
19 siblings, 1 reply; 35+ 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] 35+ messages in thread* Re: [PATCH v4 03/19] kho, memfd_luo: Reference struct kho_vmalloc via serialized pointer
2026-11-10 5:01 ` [PATCH v4 03/19] kho, memfd_luo: Reference struct kho_vmalloc via serialized pointer Tarun Sahu
@ 2026-11-10 14:23 ` Pasha Tatashin
0 siblings, 0 replies; 35+ messages in thread
From: Pasha Tatashin @ 2026-11-10 14:23 UTC (permalink / raw)
To: Tarun Sahu
Cc: 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, kvmarm, linux-pci,
Pratyush Yadav, kexec, kvm, iommu, linux-arm-kernel, skhawaja,
sagis, linux-arch, linux-mm, linux-kernel
> 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>
>
> 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;
Why do we need DECLARE_KHOSER_PTR() in test_kho.c at all?
DECLARE_KHOSER_PTR() makes sense in shared ABI structs like struct
memfd_luo_ser where both kernels reference the same struct field and get
type checking between KHOSER_STORE_PTR() and KHOSER_LOAD_PTR().
Here, struct kho_test_state is purely local to the saving kernel and the
handover ABI is just the "folios_info" FDT property carrying a u64
physical address. Having two unrelated DECLARE_KHOSER_PTR() declarations
doesn't buy any cross-kernel type safety
Can we drop DECLARE_KHOSER_PTR() here, keep a plain pointer in struct
kho_test_state, serialize a u64 phys addr into the "folios_info" FDT
property, and read it back as const u64 * + phys_to_virt() like the rest
of the KHO FDT code?
--
Pasha
^ permalink raw reply [flat|nested] 35+ 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 14:47 ` Pasha Tatashin
2026-11-10 5:01 ` [PATCH v4 05/19] kho: Add feature header for fine-grained ABI compatibility Tarun Sahu
` (15 subsequent siblings)
19 siblings, 1 reply; 35+ 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] 35+ messages in thread* Re: [PATCH v4 04/19] liveupdate: Reference struct luo_file_set_ser via serialized pointer
2026-11-10 5:01 ` [PATCH v4 04/19] liveupdate: Reference struct luo_file_set_ser " Tarun Sahu
@ 2026-11-10 14:47 ` Pasha Tatashin
0 siblings, 0 replies; 35+ messages in thread
From: Pasha Tatashin @ 2026-11-10 14:47 UTC (permalink / raw)
To: Tarun Sahu
Cc: 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, kvmarm, linux-pci,
Pratyush Yadav, kexec, kvm, iommu, linux-arm-kernel, skhawaja,
sagis, linux-arch, linux-mm, linux-kernel
On Tue, 10 Nov 2026 05:01:47 +0000, Tarun Sahu <tarunsahu@google.com> wrote:
> 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.
>
> [...]
Reviewed-by: Pasha Tatashin <pasha.tatashin@soleen.com>
--
Pasha
^ permalink raw reply [flat|nested] 35+ 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 14:47 ` Pasha Tatashin
2026-11-10 15:12 ` Pasha Tatashin
2026-11-10 5:01 ` [PATCH v4 06/19] kho: kexec_metadata: Add the feature header Tarun Sahu
` (14 subsequent siblings)
19 siblings, 2 replies; 35+ 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] 35+ messages in thread* Re: [PATCH v4 05/19] kho: Add feature header for fine-grained ABI compatibility
2026-11-10 5:01 ` [PATCH v4 05/19] kho: Add feature header for fine-grained ABI compatibility Tarun Sahu
@ 2026-11-10 14:47 ` Pasha Tatashin
2026-11-10 18:13 ` tarunsahu
2026-11-10 15:12 ` Pasha Tatashin
1 sibling, 1 reply; 35+ messages in thread
From: Pasha Tatashin @ 2026-11-10 14:47 UTC (permalink / raw)
To: Tarun Sahu
Cc: 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, kvmarm, linux-pci,
Pratyush Yadav, kexec, kvm, iommu, linux-arm-kernel, skhawaja,
sagis, linux-arch, linux-mm, linux-kernel
> 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>
>
> 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).
via KHO or 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.
The above is not needed in the documentation.
> + *
> + * 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.
> + *
We can shorten the comment below (and commit message as well); there is
no need to include sample code showing how the header is implemented in
the doc comment. Too much stuff that is not really useful to the reader.
> + * 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).
> + */
Let's avoid tab-aligning the new doc comment. Apparently, that is not
ideal because it makes future maintenance harder; each line must be
re-aligned whenever longer fields are added.
> +struct kho_ser_hdr {
> + __u32 size;
> + __u16 nr_required;
> + __u16 nr_optional;
> + __aligned_u64 flags[];
> +};
> +
Please use u32/u16/u64, this is not uapi.
> +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 */
Headers under include/linux/kho/abi/ are meant to describe the ABI
layout only, so include/linux/kho/abi/feature.h should only contain the
struct and declaration macros actually used by kho/abi/*.h headers:
- struct kho_ser_hdr (and its static_asserts)
- DECLARE_KHO_SER()
- DECLARE_KHO_SER_HDR()
Everything else here (KHO_SER_NR_FLAGS(), kho_ser_hdr_size(),
kho_ser_size(), kho_ser_init(), kho_ser_payload(), kho_ser_validate(),
kho_ser_{set,clear,has}_{required,optional}(), and the __kho_ser_*()
helpers) is kernel runtime helper code and should be moved to
include/linux/kexec_handover.h (which also avoids pulling string.h,
math.h, err.h, and errno.h into the ABI header).
--
Pasha
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [PATCH v4 05/19] kho: Add feature header for fine-grained ABI compatibility
2026-11-10 14:47 ` Pasha Tatashin
@ 2026-11-10 18:13 ` tarunsahu
0 siblings, 0 replies; 35+ messages in thread
From: tarunsahu @ 2026-11-10 18:13 UTC (permalink / raw)
To: Pasha Tatashin
Cc: 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, kvmarm, linux-pci,
Pratyush Yadav, kexec, kvm, iommu, linux-arm-kernel, skhawaja,
sagis, linux-arch, linux-mm, linux-kernel
Pasha Tatashin <pasha.tatashin@soleen.com> writes:
>> 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>
>>
>> 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).
>
> via KHO or LUO.
>
Oops! it is wrong. It should only be via KHO.
>> + *
>> + * 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.
>
> The above is not needed in the documentation.
Sure, I will update all of the above (feature.h) with:
"A feature header precedes each serialized structure that outgoing
kernel store along with ser data and incoming kernel use to understand
and decode the preserved data."
A feature header holds the details about required and optional feature
in the serialized data stored as a bit for each features.
>
>> + *
>> + * 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.
>> + *
>
> We can shorten the comment below (and commit message as well); there is
> no need to include sample code showing how the header is implemented in
> the doc comment. Too much stuff that is not really useful to the reader.
>
Okay, I will remove about function related details below.
About feature flags, I added this because at this point none of the
serialized structure use the feature flags, I intended to explain how they can
define new feature and use them.
I have updated the below, let me know if that looks good.
>> + * Defining features:
>> + *
>> + * A serialized structure defines its features using enums and registers with
>> + * DECLARE_KHO_SER():: AN Example to introduce a new feature:
>> + *
>> + * 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);
>> + *
>> + *
>> + * Rules:
>> + * - The fields of a new feature are appended at the end of the
>> + serialized structure to keep the offset of older fields same.
>> + * - Use required for data the target must understand to restore
>> + * correctly, and optional for data it can safely ignore.
>> + *
>> + */
>> +
=======END OF DOC=================
>> +#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).
>> + */
>
> Let's avoid tab-aligning the new doc comment. Apparently, that is not
> ideal because it makes future maintenance harder; each line must be
> re-aligned whenever longer fields are added.
>
Got it. I will update it.
>> +struct kho_ser_hdr {
>> + __u32 size;
>> + __u16 nr_required;
>> + __u16 nr_optional;
>> + __aligned_u64 flags[];
>> +};
>> +
>
> Please use u32/u16/u64, this is not uapi.
>
Got it. I will update it.
>> +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 */
>
> Headers under include/linux/kho/abi/ are meant to describe the ABI
> layout only, so include/linux/kho/abi/feature.h should only contain the
> struct and declaration macros actually used by kho/abi/*.h headers:
>
> - struct kho_ser_hdr (and its static_asserts)
> - DECLARE_KHO_SER()
> - DECLARE_KHO_SER_HDR()
>
> Everything else here (KHO_SER_NR_FLAGS(), kho_ser_hdr_size(),
> kho_ser_size(), kho_ser_init(), kho_ser_payload(), kho_ser_validate(),
> kho_ser_{set,clear,has}_{required,optional}(), and the __kho_ser_*()
> helpers) is kernel runtime helper code and should be moved to
> include/linux/kexec_handover.h (which also avoids pulling string.h,
> math.h, err.h, and errno.h into the ABI header).
Okay will update it.
~Tarun
>
> --
> Pasha
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v4 05/19] kho: Add feature header for fine-grained ABI compatibility
2026-11-10 5:01 ` [PATCH v4 05/19] kho: Add feature header for fine-grained ABI compatibility Tarun Sahu
2026-11-10 14:47 ` Pasha Tatashin
@ 2026-11-10 15:12 ` Pasha Tatashin
2026-11-10 18:25 ` tarunsahu
1 sibling, 1 reply; 35+ messages in thread
From: Pasha Tatashin @ 2026-11-10 15:12 UTC (permalink / raw)
To: Tarun Sahu, 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
On Tue Nov 10, 2026 at 12:01 AM EST, Tarun Sahu wrote:
> +/**
> + * 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; \
Remove this extern.
> + 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
Remove DECLARE_KHO_SER_HDR, use kho_ser_hdr directly.
--
Pasha
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [PATCH v4 05/19] kho: Add feature header for fine-grained ABI compatibility
2026-11-10 15:12 ` Pasha Tatashin
@ 2026-11-10 18:25 ` tarunsahu
0 siblings, 0 replies; 35+ messages in thread
From: tarunsahu @ 2026-11-10 18:25 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
"Pasha Tatashin" <pasha.tatashin@soleen.com> writes:
> On Tue Nov 10, 2026 at 12:01 AM EST, Tarun Sahu wrote:
>
>> +/**
>> + * 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; \
>
> Remove this extern.
>
>> + 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
>
> Remove DECLARE_KHO_SER_HDR, use kho_ser_hdr directly.
I added this, because wherever in the kernel a serialized struture is
preceded with kho_ser_hdr, it will only be called `struct kho_ser_hdr
fhdr` which does not display (to readers) that this declaration of
header variable is not alone, it also holds the serialized structure
after this variable.
Using this macro, it clearly explain, that "Hey, I am header to this
serialized struct and I am compiled time initalized using
DECLARE_KHO_SER for this serialized structure and my pointer holds
location to both feature header followed by serialized structure."
Techinically this is just for readability.
WDYT?
~Tarun
>
> --
> Pasha
^ permalink raw reply [flat|nested] 35+ 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 15:08 ` Pasha Tatashin
2026-11-10 5:01 ` [PATCH v4 07/19] kho: vmalloc: " Tarun Sahu
` (13 subsequent siblings)
19 siblings, 1 reply; 35+ 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] 35+ messages in thread* Re: [PATCH v4 06/19] kho: kexec_metadata: Add the feature header
2026-11-10 5:01 ` [PATCH v4 06/19] kho: kexec_metadata: Add the feature header Tarun Sahu
@ 2026-11-10 15:08 ` Pasha Tatashin
0 siblings, 0 replies; 35+ messages in thread
From: Pasha Tatashin @ 2026-11-10 15:08 UTC (permalink / raw)
To: Tarun Sahu
Cc: 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, kvmarm, linux-pci,
Pratyush Yadav, kexec, kvm, iommu, linux-arm-kernel, skhawaja,
sagis, linux-arch, linux-mm, linux-kernel
> 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>
>
> 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);
Why __packed was not removed in the first commit? __aligned(8) should
not be needed, given that it must follow DECLARE_KHO_SER which is 8-byte
aligned.
> +
> +/* Register kho_kexec_metadata with KHO serialization accessors */
here and in all other places, please remove this comment.
> +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 6f1f55fe3181..2edd772bb6d3 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -1740,6 +1740,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);
Please don't do this, use kho_ser_hdr directly.
--
Pasha
^ permalink raw reply [flat|nested] 35+ 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
` (12 subsequent siblings)
19 siblings, 0 replies; 35+ 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] 35+ 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 17:07 ` David Matlack
2026-11-10 5:01 ` [PATCH v4 09/19] liveupdate: Add feature header to struct luo_ser Tarun Sahu
` (11 subsequent siblings)
19 siblings, 1 reply; 35+ 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] 35+ messages in thread* Re: [PATCH v4 08/19] kho: block: Add the feature header to blocks
2026-11-10 5:01 ` [PATCH v4 08/19] kho: block: Add the feature header to blocks Tarun Sahu
@ 2026-11-10 17:07 ` David Matlack
0 siblings, 0 replies; 35+ messages in thread
From: David Matlack @ 2026-11-10 17:07 UTC (permalink / raw)
To: Tarun Sahu, 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
On Tue Nov 10, 2026 at 5:01 AM UTC, Tarun Sahu wrote:
> 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.
This adds a feature header to every block in the set, but the layout it
describes is a property of the whole set: all blocks are written by the
same kernel with the same header.
Blocks are only ever added and removed at the tail, so the head block
is stable for the lifetime of the set. I'd put a single set-wide
header in the first block only, e.g. a struct kho_block_set_ser with
its own feature header, and keep the per-block header to next and
count.
entry_size (added in patch 2) is another set-wide property that belongs
there. All blocks in a set have the same entry size, so recording it in
every block is redundant, and restore has to check that the copies
agree.
[...]
> 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;
Nothing checks that the blocks agree. Each block's entries start at its
own fhdr->size, and count_per_block ends up being whatever the last
block implies. With a single set-wide header this can't happen.
^ permalink raw reply [flat|nested] 35+ 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
` (10 subsequent siblings)
19 siblings, 0 replies; 35+ 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] 35+ 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
` (9 subsequent siblings)
19 siblings, 0 replies; 35+ 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] 35+ 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
` (8 subsequent siblings)
19 siblings, 0 replies; 35+ 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] 35+ 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
` (7 subsequent siblings)
19 siblings, 0 replies; 35+ 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] 35+ 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
` (6 subsequent siblings)
19 siblings, 0 replies; 35+ 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] 35+ 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
` (5 subsequent siblings)
19 siblings, 0 replies; 35+ 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] 35+ 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 16:39 ` David Matlack
2026-11-10 5:01 ` [PATCH v4 16/19] PCI: liveupdate: Add feature header to struct pci_dev_ser Tarun Sahu
` (4 subsequent siblings)
19 siblings, 1 reply; 35+ 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] 35+ messages in thread* Re: [PATCH v4 15/19] PCI: liveupdate: Add feature header to struct pci_ser
2026-11-10 5:01 ` [PATCH v4 15/19] PCI: liveupdate: Add feature header to struct pci_ser Tarun Sahu
@ 2026-11-10 16:39 ` David Matlack
0 siblings, 0 replies; 35+ messages in thread
From: David Matlack @ 2026-11-10 16:39 UTC (permalink / raw)
To: Tarun Sahu, 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
On Tue Nov 10, 2026 at 5:01 AM UTC, Tarun Sahu wrote:
> 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
Taking the stride from the block header is good, but dropping
PCI_DEV_SER_MIN_SIZE removes the only check that incoming entries are
large enough to hold the fields this kernel reads.
kho_block_set_restore() only rejects an entry_size of 0, so
pci_flb_retrieve() will now read past the end of entries smaller than
offsetofend(struct pci_dev_ser, refcount) instead of panicking. Please
keep an equivalent check against the incoming entry size.
> 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
Please move this to patch 1, which drops __packed from the other ABI
structs.
> pci_ser for 8-byte alignment, and bump PCI_LUO_FLB_COMPATIBLE from pci to
> pci-v2.
The PCI Live Update ABI hasn't been in a release yet, so maybe we don't
need a version bump?
[...]
> - * * 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.
Please keep the rules for extending struct pci_dev_ser. I'd say
something like: fields appended to struct pci_dev_ser must be gated by
a feature in struct pci_ser's header, entries are never smaller than
PCI_DEV_SER_MIN_SIZE, and the next kernel must not read fields beyond
the incoming entry size.
[...]
> struct pci_flb_outgoing {
> - struct pci_ser *ser;
> + DECLARE_KHO_SER_HDR(pci_ser, *fhdr);
> struct kho_block_set block_set;
> };
I'd keep the ser pointer here (and in struct pci_flb_incoming) and add
the header pointer alongside it. The header is only needed to free the
allocation and for args->data, and keeping ser avoids sprinkling
kho_ser_payload(outgoing->fhdr, pci_ser)-> throughout the file.
Also, please call it header rather than fhdr, here and throughout the
series. fhdr isn't obvious to someone reading this code for the first
time, whereas header is very obvious and clear.
[...]
> + * @reserved: Reserved for alignment.
Please document that this must be zero, so it can be used by a future
feature.
^ permalink raw reply [flat|nested] 35+ 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 16:23 ` David Matlack
2026-11-10 5:02 ` [PATCH v4 17/19] mm: memfd_luo: Add feature header to struct memfd_luo_ser Tarun Sahu
` (3 subsequent siblings)
19 siblings, 1 reply; 35+ 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] 35+ messages in thread* Re: [PATCH v4 16/19] PCI: liveupdate: Add feature header to struct pci_dev_ser
2026-11-10 5:01 ` [PATCH v4 16/19] PCI: liveupdate: Add feature header to struct pci_dev_ser Tarun Sahu
@ 2026-11-10 16:23 ` David Matlack
0 siblings, 0 replies; 35+ messages in thread
From: David Matlack @ 2026-11-10 16:23 UTC (permalink / raw)
To: Tarun Sahu, 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
On Tue Nov 10, 2026 at 5:01 AM UTC, Tarun Sahu wrote:
> 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.
Elements of a collection should not have their own feature headers. This
applies to patches 10, 12, 14, 16 and 18. I raised this during the v2
discussion [1][2], but I don't think it was addressed.
All entries in a KHO block set are written by the same kernel using the
same struct, and the block set has a single entry_size, so every entry
header is necessarily identical. They don't convey anything that the
header of the struct that owns the collection can't. In this case,
struct pci_ser's header can define features that describe fields in
struct pci_dev_ser, and the PCI core checks them once before iterating.
If a feature only applies to some devices, the field's value in each
entry says whether that device uses it.
Per-entry headers also cost a lot for small entries. struct pci_dev_ser
goes from 8 to 16 bytes, and to 24 bytes once the first feature is
added. Patch 18 does the same for every preserved memfd folio, which for
a large guest backed by 4K pages adds gigabytes of preserved metadata.
They also add new ways to read out of bounds. kho_ser_payload() trusts
nr_required/nr_optional in each entry, and nothing checks that the entry
header plus payload fit within entry_size. In patches 14 and 18 the walk
uses each entry's fhdr->size to find the next entry, so one bad header
throws off every entry after it.
Instead I would keep struct pci_dev_ser as a plain struct, take the
entry size from the KHO block header (patch 2), and in pci_flb_retrieve()
check once that the entry size covers the base fields, plus the fields
of any features set in struct pci_ser's header, e.g.:
entry_size = kho_block_set_entry_size(&incoming->block_set);
if (entry_size < offsetofend(struct pci_dev_ser, refcount))
panic(...);
That also restores the minimum entry size check that the v10 PCI series
had with PCI_DEV_SER_MIN_SIZE, which this version drops.
[...]
> - 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));
This size is never used. kho_block_set_restore() overwrites it with the
entry size recorded by the previous kernel. Passing this kernel's size
here is misleading: pci_flb_retrieve() (and the LUO session/file restore
paths) read as if they iterate incoming entries with the outgoing entry
size, which would be a bug.
Incoming block sets should not take an entry size at all.
kho_block_set_restore() should initialize the block set itself from the
incoming block headers, and the incoming paths should drop the
kho_block_set_init() call.
[1] https://lore.kernel.org/kexec/atl6K2Juy6Bh29lh@google.com/
[2] https://lore.kernel.org/kexec/at-8vkVpN2SF3XgB@google.com/
^ permalink raw reply [flat|nested] 35+ 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
` (2 subsequent siblings)
19 siblings, 0 replies; 35+ 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] 35+ 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
2026-11-10 12:48 ` [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Pasha Tatashin
19 siblings, 0 replies; 35+ 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] 35+ 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
2026-11-10 12:48 ` [PATCH v4 00/19] kho, liveupdate: Add feature header for fine-grained ABI compatibility Pasha Tatashin
19 siblings, 0 replies; 35+ 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] 35+ messages in thread* Re: [PATCH v4 00/19] kho, liveupdate: 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
` (18 preceding siblings ...)
2026-11-10 5:02 ` [PATCH v4 19/19] kho: Add KUnit test for the feature header helpers Tarun Sahu
@ 2026-11-10 12:48 ` Pasha Tatashin
19 siblings, 0 replies; 35+ messages in thread
From: Pasha Tatashin @ 2026-11-10 12:48 UTC (permalink / raw)
To: Tarun Sahu, 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
On Tue Nov 10, 2026 at 12:01 AM EST, Tarun Sahu wrote:
> 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
I do not believe we need __aligned(8), because the struct immediately
follows the newly introduced kho_ser_hdr, which is 8-byte aligned.
However, we should verify that the combined size (header + payload)
is 8-byte aligned.
> 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
--
Pasha
^ permalink raw reply [flat|nested] 35+ messages in thread