Re: [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header
From: Pasha Tatashin
Date: Sun Oct 25 2026 - 16:39:13 EST
On Fri, Oct 23, 2026 at 9:50 AM David Matlack <dmatlack@xxxxxxxxxx> wrote:
>
> On 2026-10-22 04:53 PM, Pasha Tatashin wrote:
> > On Thu, Oct 22, 2026 at 1:51 PM David Matlack <dmatlack@xxxxxxxxxx> wrote:
> > > On 2026-10-22 12:11 AM, Pasha Tatashin wrote:
>
> > > > Lets drop the fourth macro argument and generate the combined struct name:
> > > >
> > > > #define DECLARE_LUO_SER(name, expected_size, ext_flags_count) \
> > > > struct name##_hdr { \
> > > > __u32 size; \
> > > > __u32 flags; \
> > > > __u64 ext_flags[ext_flags_count]; \
> > > > struct name payload; \
> > > > }; \
> > > > static_assert(sizeof(struct name##_hdr) == (expected_size), \
> > > > "LUO struct " #name " size mismatch!")
> > > >
> > > > For example:
> > > >
> > > > struct luo_memfd_ser {
> > > > __u64 pos;
> > > > __u64 size;
> > > > };
> > > >
> > > > DECLARE_LUO_SER_HDR(luo_memfd_ser, 24, 0);
> > >
> > > I think we are getting close, bu I have two remaining concerns:
> > >
> > > * Reserving bits in each flags word for continuation leaves gaps
> > > in the flag space that users have to know to skip.
> >
> > Yes, but callers should never manipulate the raw flag words directly,
> > and we can provide simple accessor macros or inlines that take a
> > contiguous feature ID (0, 1, 2, ...)
> >
> > enum luo_memfd_feats {
> > LUO_MEMFD_FEAT_HUGETLB = 0,
> > LUO_MEMFD_FEAT_SEALS = 1,
> > /* ... */
> > LUO_MEMFD_FEAT_FOO = 15, /* Automatically lands in
> > ext_flags[0] bits 0:1 */
> > LUO_MEMFD_NR_FEATS
> > };
> >
> > if (luo_ser_get_feat(hdr, LUO_MEMFD_FEAT_HUGETLB)) {
> > /* handle hugetlb */
> > }
>
> Sure but why deal with all the complexity just to save 8 bytes? All of
> these structs are allocated on folios and there are kilobytes of unused
> space to fit this header. And even if we optimize that we are talking
> about 8 bytes per FH/FLB... or 16 bytes for structs with arch-specific
> sub-structs like the KVM ones.
The reason I care about keeping the header compact is that we are
defining a foundational kernel ABI and want to use this header across
almost every serialized data structure so we can evolve them later
without breaking compatibility. While some top-level structs are on
sparse folios today, other serialized structs are tightly packed, and
we are looking to add KHO slab support in the future where tightly
packed allocations will be common.
That said, in your proposal, as long as nr_flags == 0 is allowed when
a struct has no feature flags yet, luo_ser_hdr_size(hdr) is already 8
bytes and only grows to 16 bytes on the first feature flag. I am OK
with that.
> > > * struct name##_hdr only matches structs written by the current kernel
> > > (in terms of length of array and offset of payload).
> >
> > This is not an issue on the incoming path, we never cast incoming data
> > to struct name##_hdr or access ->payload directly. Instead, we parse
> > the incoming buffer, dynamically compute the header size from the
> > continuation bits, and cast the payload offset to "struct name *"
> > based on the negotiated feature flags.
>
> How?
>
> This is the only data structure that describes the header. Are you
> saying the incoming path will parse the header purely with a void *? Or
> use this header but just be careful not to access payload?
>
> We should have one header structure that can be used on the incoming and
> outgoing path. We should not have one header data structure that can be
> used on the outgoing path and one that can be used on the incoming
> path.
>
> Also we need a standard header that can be shared across all structs,
> not a per struct header. Otherwise you will need to do weird casting and
> overlays to implement the shared helper routines that parse it.
Having DECLARE_KHO_SER_WRAP() in include/linux/kho/abi/ makes it
immediately clear just by looking at kho/abi/ which serialized structs
have the versioned header and which do not, while also enforcing the
header when preserving and static-asserting the combined struct size a
compile time.
So with your header (and let's use KHO as we want to version KHO
structs as well):
struct kho_ser_hdr {
__u32 size; /* Size of header + payload in bytes */
__u32 nr_flags; /* Number of features encoded in flags[] */
__u64 flags[];
};
The wrapped struct will look like:
#define DECLARE_KHO_SER_WRAP(name, expected_size, nr_feats) \
struct name##_wrap { \
__u32 size; \
__u32 nr_flags; \
__u64 flags[DIV_ROUND_UP(nr_feats, 32)]; \
struct name payload; \
}; \
static_assert(sizeof(struct name##_wrap) == (expected_size), \
"struct " #name " size mismatch!")
On the incoming kernel we don't use struct name##_wrap, but we use
struct kho_ser_hdr for the wrapped data, validate the flags, and
unwrap it via kho_ser_payload(hdr) to get back the struct name *
payload type.
Pasha