--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+/**
+ * DOC: Kexec Metadata ABI
+ *
+ * The "kexec-metadata" subtree stores optional metadata about the kexec chain.
+ * It is registered via kho_add_subtree(), keeping it independent from the core
+ * KHO ABI. This allows the metadata format to evolve without affecting other
+ * KHO consumers.
+ *
+ * The metadata is stored as a plain C struct rather than FDT format for
+ * simplicity and direct field access.
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ * Copyright (c) 2026 Breno Leitao <leitao@debian.org>
+ */
+
+#ifndef _LINUX_KHO_ABI_KEXEC_METADATA_H
+#define _LINUX_KHO_ABI_KEXEC_METADATA_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
+ * @kexec_count: Number of kexec boots since last cold boot
+ *
+ * This structure is preserved across kexec and allows the new kernel to
+ * identify which kernel it was booted from and how many kexec reboots
+ * have occurred.
+ *
+ * __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];
+ u32 kexec_count;
+} __packed;
+
+#define KHO_METADATA_NODE_NAME "kexec-metadata"
+
+#endif /* _LINUX_KHO_ABI_KEXEC_METADATA_H */
#include <linux/kexec.h>
#include <linux/kexec_handover.h>
#include <linux/kho_radix_tree.h>
+#include <linux/utsname.h>
#include <linux/kho/abi/kexec_handover.h>
+#include <linux/kho/abi/kexec_metadata.h>
#include <linux/libfdt.h>
#include <linux/list.h>
#include <linux/memblock.h>
struct kho_in {
phys_addr_t fdt_phys;
phys_addr_t scratch_phys;
+ char previous_release[__NEW_UTS_LEN + 1];
+ u32 kexec_count;
struct kho_debugfs dbg;
};
return err;
}
+static void __init kho_in_kexec_metadata(void)
+{
+ struct kho_kexec_metadata *metadata;
+ phys_addr_t metadata_phys;
+ size_t blob_size;
+ int err;
+
+ err = kho_retrieve_subtree(KHO_METADATA_NODE_NAME, &metadata_phys,
+ &blob_size);
+ if (err)
+ /* This is fine, previous kernel didn't export metadata */
+ return;
+
+ /* Check that, at least, "version" is present */
+ if (blob_size < sizeof(u32)) {
+ 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);
+ 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));
+ return;
+ }
+
+ /*
+ * Copy data to the kernel structure that will persist during
+ * kernel lifetime.
+ */
+ kho_in.kexec_count = metadata->kexec_count;
+ strscpy(kho_in.previous_release, metadata->previous_release,
+ sizeof(kho_in.previous_release));
+
+ pr_info("exec from: %s (count %u)\n",
+ kho_in.previous_release, kho_in.kexec_count);
+}
+
+/*
+ * Create kexec metadata to pass kernel version and boot count to the
+ * next kernel. This keeps the core KHO ABI minimal and allows the
+ * metadata format to evolve independently.
+ */
+static __init int kho_out_kexec_metadata(void)
+{
+ struct kho_kexec_metadata *metadata;
+ int err;
+
+ metadata = kho_alloc_preserve(sizeof(*metadata));
+ if (IS_ERR(metadata))
+ return PTR_ERR(metadata);
+
+ metadata->version = KHO_KEXEC_METADATA_VERSION;
+ strscpy(metadata->previous_release, init_uts_ns.name.release,
+ sizeof(metadata->previous_release));
+ /* 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));
+ if (err)
+ kho_unpreserve_free(metadata);
+
+ return err;
+}
+
+static int __init kho_kexec_metadata_init(const void *fdt)
+{
+ int err;
+
+ if (fdt)
+ kho_in_kexec_metadata();
+
+ /* Populate kexec metadata for the possible next kexec */
+ err = kho_out_kexec_metadata();
+ if (err)
+ pr_warn("failed to initialize kexec-metadata subtree: %d\n",
+ err);
+
+ return err;
+}
+
static __init int kho_init(void)
{
struct kho_radix_tree *tree = &kho_out.radix_tree;
if (err)
goto err_free_fdt;
+ err = kho_kexec_metadata_init(fdt);
+ if (err)
+ goto err_free_fdt;
+
if (fdt) {
kho_in_debugfs_init(&kho_in.dbg, fdt);
return 0;