[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250929010321.3462457-9-pasha.tatashin@soleen.com>
Date: Mon, 29 Sep 2025 01:02:59 +0000
From: Pasha Tatashin <pasha.tatashin@...een.com>
To: pratyush@...nel.org,
jasonmiu@...gle.com,
graf@...zon.com,
changyuanl@...gle.com,
pasha.tatashin@...een.com,
rppt@...nel.org,
dmatlack@...gle.com,
rientjes@...gle.com,
corbet@....net,
rdunlap@...radead.org,
ilpo.jarvinen@...ux.intel.com,
kanie@...ux.alibaba.com,
ojeda@...nel.org,
aliceryhl@...gle.com,
masahiroy@...nel.org,
akpm@...ux-foundation.org,
tj@...nel.org,
yoann.congal@...le.fr,
mmaurer@...gle.com,
roman.gushchin@...ux.dev,
chenridong@...wei.com,
axboe@...nel.dk,
mark.rutland@....com,
jannh@...gle.com,
vincent.guittot@...aro.org,
hannes@...xchg.org,
dan.j.williams@...el.com,
david@...hat.com,
joel.granados@...nel.org,
rostedt@...dmis.org,
anna.schumaker@...cle.com,
song@...nel.org,
zhangguopeng@...inos.cn,
linux@...ssschuh.net,
linux-kernel@...r.kernel.org,
linux-doc@...r.kernel.org,
linux-mm@...ck.org,
gregkh@...uxfoundation.org,
tglx@...utronix.de,
mingo@...hat.com,
bp@...en8.de,
dave.hansen@...ux.intel.com,
x86@...nel.org,
hpa@...or.com,
rafael@...nel.org,
dakr@...nel.org,
bartosz.golaszewski@...aro.org,
cw00.choi@...sung.com,
myungjoo.ham@...sung.com,
yesanishhere@...il.com,
Jonathan.Cameron@...wei.com,
quic_zijuhu@...cinc.com,
aleksander.lobakin@...el.com,
ira.weiny@...el.com,
andriy.shevchenko@...ux.intel.com,
leon@...nel.org,
lukas@...ner.de,
bhelgaas@...gle.com,
wagi@...nel.org,
djeffery@...hat.com,
stuart.w.hayes@...il.com,
ptyadav@...zon.de,
lennart@...ttering.net,
brauner@...nel.org,
linux-api@...r.kernel.org,
linux-fsdevel@...r.kernel.org,
saeedm@...dia.com,
ajayachandra@...dia.com,
jgg@...dia.com,
parav@...dia.com,
leonro@...dia.com,
witu@...dia.com,
hughd@...gle.com,
skhawaja@...gle.com,
chrisl@...nel.org,
steven.sistare@...cle.com
Subject: [PATCH v4 08/30] liveupdate: luo_core: integrate with KHO
Integrate the LUO with the KHO framework to enable passing LUO state
across a kexec reboot.
When LUO is transitioned to a "prepared" state, it tells KHO to
finalize, so all memory segments that were added to KHO preservation
list are getting preserved. After "Prepared" state no new segments
can be preserved. If LUO is canceled, it also tells KHO to cancel the
serialization, and therefore, later LUO can go back into the prepared
state.
This patch introduces the following changes:
- During the KHO finalization phase allocate FDT blob.
- Populate this FDT with a LUO compatibility string ("luo-v1").
LUO now depends on `CONFIG_KEXEC_HANDOVER`. The core state transition
logic (`luo_do_*_calls`) remains unimplemented in this patch.
Signed-off-by: Pasha Tatashin <pasha.tatashin@...een.com>
---
kernel/liveupdate/luo_core.c | 282 ++++++++++++++++++++++++++++++-
kernel/liveupdate/luo_internal.h | 13 ++
2 files changed, 292 insertions(+), 3 deletions(-)
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 954d533bd8c4..10796481447a 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -47,9 +47,13 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/err.h>
+#include <linux/kexec_handover.h>
#include <linux/kobject.h>
+#include <linux/libfdt.h>
#include <linux/liveupdate.h>
+#include <linux/mm.h>
#include <linux/rwsem.h>
+#include <linux/sizes.h>
#include <linux/string.h>
#include "luo_internal.h"
@@ -67,6 +71,21 @@ static const char *const luo_state_str[] = {
static bool luo_enabled;
+static void *luo_fdt_out;
+static void *luo_fdt_in;
+
+/*
+ * The LUO FDT size depends on the number of participating subsystems,
+ *
+ * The current fixed size (4K) is large enough to handle reasonable number of
+ * preserved entities. If this size ever becomes insufficient, it can either be
+ * increased, or a dynamic size calculation mechanism could be implemented in
+ * the future.
+ */
+#define LUO_FDT_SIZE PAGE_SIZE
+#define LUO_KHO_ENTRY_NAME "LUO"
+#define LUO_COMPATIBLE "luo-v1"
+
static int __init early_liveupdate_param(char *buf)
{
return kstrtobool(buf, &luo_enabled);
@@ -91,6 +110,52 @@ static inline void luo_set_state(enum liveupdate_state state)
__luo_set_state(state);
}
+/* Called during the prepare phase, to create LUO fdt tree */
+static int luo_fdt_setup(void)
+{
+ void *fdt_out;
+ int ret;
+
+ fdt_out = luo_contig_alloc_preserve(LUO_FDT_SIZE);
+ if (IS_ERR(fdt_out)) {
+ pr_err("failed to allocate/preserve FDT memory\n");
+ return PTR_ERR(fdt_out);
+ }
+
+ ret = fdt_create_empty_tree(fdt_out, LUO_FDT_SIZE);
+ if (ret)
+ goto exit_free;
+
+ ret = fdt_setprop_string(fdt_out, 0, "compatible", LUO_COMPATIBLE);
+ if (ret)
+ goto exit_free;
+
+ ret = kho_add_subtree(LUO_KHO_ENTRY_NAME, fdt_out);
+ if (ret)
+ goto exit_free;
+ luo_fdt_out = fdt_out;
+
+ return 0;
+
+exit_free:
+ luo_contig_free_unpreserve(fdt_out, LUO_FDT_SIZE);
+ pr_err("failed to prepare LUO FDT: %d\n", ret);
+
+ return ret;
+}
+
+static void luo_fdt_destroy(void)
+{
+ kho_remove_subtree(luo_fdt_out);
+ luo_contig_free_unpreserve(luo_fdt_out, LUO_FDT_SIZE);
+ luo_fdt_out = NULL;
+}
+
+static int luo_do_prepare_calls(void)
+{
+ return 0;
+}
+
static int luo_do_freeze_calls(void)
{
return 0;
@@ -100,6 +165,71 @@ static void luo_do_finish_calls(void)
{
}
+static void luo_do_cancel_calls(void)
+{
+}
+
+static int __luo_prepare(void)
+{
+ int ret;
+
+ if (down_write_killable(&luo_state_rwsem)) {
+ pr_warn("[prepare] event canceled by user\n");
+ return -EAGAIN;
+ }
+
+ if (!is_current_luo_state(LIVEUPDATE_STATE_NORMAL)) {
+ pr_warn("Can't switch to [%s] from [%s] state\n",
+ luo_state_str[LIVEUPDATE_STATE_PREPARED],
+ luo_current_state_str());
+ ret = -EINVAL;
+ goto exit_unlock;
+ }
+
+ ret = luo_fdt_setup();
+ if (ret)
+ goto exit_unlock;
+
+ ret = luo_do_prepare_calls();
+ if (ret) {
+ luo_fdt_destroy();
+ goto exit_unlock;
+ }
+
+ luo_set_state(LIVEUPDATE_STATE_PREPARED);
+
+exit_unlock:
+ up_write(&luo_state_rwsem);
+
+ return ret;
+}
+
+static int __luo_cancel(void)
+{
+ if (down_write_killable(&luo_state_rwsem)) {
+ pr_warn("[cancel] event canceled by user\n");
+ return -EAGAIN;
+ }
+
+ if (!is_current_luo_state(LIVEUPDATE_STATE_PREPARED) &&
+ !is_current_luo_state(LIVEUPDATE_STATE_FROZEN)) {
+ pr_warn("Can't switch to [%s] from [%s] state\n",
+ luo_state_str[LIVEUPDATE_STATE_NORMAL],
+ luo_current_state_str());
+ up_write(&luo_state_rwsem);
+
+ return -EINVAL;
+ }
+
+ luo_do_cancel_calls();
+ luo_fdt_destroy();
+ luo_set_state(LIVEUPDATE_STATE_NORMAL);
+
+ up_write(&luo_state_rwsem);
+
+ return 0;
+}
+
/* Get the current state as a string */
const char *luo_current_state_str(void)
{
@@ -111,9 +241,28 @@ enum liveupdate_state liveupdate_get_state(void)
return READ_ONCE(luo_state);
}
+/**
+ * luo_prepare - Initiate the live update preparation phase.
+ *
+ * This function is called to begin the live update process. It attempts to
+ * transition the luo to the ``LIVEUPDATE_STATE_PREPARED`` state.
+ *
+ * If the calls complete successfully, the orchestrator state is set
+ * to ``LIVEUPDATE_STATE_PREPARED``. If any call fails a
+ * ``LIVEUPDATE_CANCEL`` is sent to roll back any actions.
+ *
+ * @return 0 on success, ``-EAGAIN`` if the state change was cancelled by the
+ * user while waiting for the lock, ``-EINVAL`` if the orchestrator is not in
+ * the normal state, or a negative error code returned by the calls.
+ */
int luo_prepare(void)
{
- return 0;
+ int err = __luo_prepare();
+
+ if (err)
+ return err;
+
+ return kho_finalize();
}
/**
@@ -193,9 +342,28 @@ int luo_finish(void)
return 0;
}
+/**
+ * luo_cancel - Cancel the ongoing live update from prepared or frozen states.
+ *
+ * This function is called to abort a live update that is currently in the
+ * ``LIVEUPDATE_STATE_PREPARED`` state.
+ *
+ * If the state is correct, it triggers the ``LIVEUPDATE_CANCEL`` notifier chain
+ * to allow subsystems to undo any actions performed during the prepare or
+ * freeze events. Finally, the orchestrator state is transitioned back to
+ * ``LIVEUPDATE_STATE_NORMAL``.
+ *
+ * @return 0 on success, or ``-EAGAIN`` if the state change was cancelled by the
+ * user while waiting for the lock.
+ */
int luo_cancel(void)
{
- return 0;
+ int err = kho_abort();
+
+ if (err)
+ return err;
+
+ return __luo_cancel();
}
void luo_state_read_enter(void)
@@ -210,7 +378,36 @@ void luo_state_read_exit(void)
static int __init luo_startup(void)
{
- __luo_set_state(LIVEUPDATE_STATE_NORMAL);
+ phys_addr_t fdt_phys;
+ int ret;
+
+ if (!kho_is_enabled()) {
+ if (luo_enabled)
+ pr_warn("Disabling liveupdate because KHO is disabled\n");
+ luo_enabled = false;
+ return 0;
+ }
+
+ /* Retrieve LUO subtree, and verify its format. */
+ ret = kho_retrieve_subtree(LUO_KHO_ENTRY_NAME, &fdt_phys);
+ if (ret) {
+ if (ret != -ENOENT) {
+ luo_restore_fail("failed to retrieve FDT '%s' from KHO: %d\n",
+ LUO_KHO_ENTRY_NAME, ret);
+ }
+ __luo_set_state(LIVEUPDATE_STATE_NORMAL);
+
+ return 0;
+ }
+
+ luo_fdt_in = __va(fdt_phys);
+ ret = fdt_node_check_compatible(luo_fdt_in, 0, LUO_COMPATIBLE);
+ if (ret) {
+ luo_restore_fail("FDT '%s' is incompatible with '%s' [%d]\n",
+ LUO_KHO_ENTRY_NAME, LUO_COMPATIBLE, ret);
+ }
+
+ __luo_set_state(LIVEUPDATE_STATE_UPDATED);
return 0;
}
@@ -295,3 +492,82 @@ bool liveupdate_enabled(void)
{
return luo_enabled;
}
+
+/**
+ * luo_contig_alloc_preserve - Allocate, zero, and preserve contiguous memory.
+ * @size: The number of bytes to allocate.
+ *
+ * Allocates a physically contiguous block of zeroed pages that is large
+ * enough to hold @size bytes. The allocated memory is then registered with
+ * KHO for preservation across a kexec.
+ *
+ * Note: The actual allocated size will be rounded up to the nearest
+ * power-of-two page boundary.
+ *
+ * @return A virtual pointer to the allocated and preserved memory on success,
+ * or an ERR_PTR() encoded error on failure.
+ */
+void *luo_contig_alloc_preserve(size_t size)
+{
+ int order, ret;
+ void *mem;
+
+ if (!size)
+ return ERR_PTR(-EINVAL);
+
+ order = get_order(size);
+ if (order > MAX_PAGE_ORDER)
+ return ERR_PTR(-E2BIG);
+
+ mem = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, order);
+ if (!mem)
+ return ERR_PTR(-ENOMEM);
+
+ ret = kho_preserve_pages(virt_to_page(mem), 1 << order);
+ if (ret) {
+ free_pages((unsigned long)mem, order);
+ return ERR_PTR(ret);
+ }
+
+ return mem;
+}
+
+/**
+ * luo_contig_free_unpreserve - Unpreserve and free contiguous memory.
+ * @mem: Pointer to the memory allocated by luo_contig_alloc_preserve().
+ * @size: The original size requested during allocation. This is used to
+ * recalculate the correct order for freeing the pages.
+ *
+ * Unregisters the memory from KHO preservation and frees the underlying
+ * pages back to the system. This function should be called to clean up
+ * memory allocated with luo_contig_alloc_preserve().
+ */
+void luo_contig_free_unpreserve(void *mem, size_t size)
+{
+ unsigned int order;
+
+ if (!mem || !size)
+ return;
+
+ order = get_order(size);
+ if (WARN_ON_ONCE(order > MAX_PAGE_ORDER))
+ return;
+
+ WARN_ON_ONCE(kho_unpreserve_pages(virt_to_page(mem), 1 << order));
+ free_pages((unsigned long)mem, order);
+}
+
+void luo_contig_free_restore(void *mem, size_t size)
+{
+ unsigned int order;
+
+ if (!mem || !size)
+ return;
+
+ order = get_order(size);
+ if (WARN_ON_ONCE(order > MAX_PAGE_ORDER))
+ return;
+
+ WARN_ON_ONCE(!kho_restore_pages(__pa(mem), 1 << order));
+ free_pages((unsigned long)mem, order);
+}
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 2e0861781673..c98842caa4a0 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -8,6 +8,15 @@
#ifndef _LINUX_LUO_INTERNAL_H
#define _LINUX_LUO_INTERNAL_H
+/*
+ * Handles a deserialization failure: devices and memory is in unpredictable
+ * state.
+ *
+ * Continuing the boot process after a failure is dangerous because it could
+ * lead to leaks of private data.
+ */
+#define luo_restore_fail(__fmt, ...) panic(__fmt, ##__VA_ARGS__)
+
int luo_cancel(void);
int luo_prepare(void);
int luo_freeze(void);
@@ -19,4 +28,8 @@ extern struct rw_semaphore luo_state_rwsem;
const char *luo_current_state_str(void);
+void *luo_contig_alloc_preserve(size_t size);
+void luo_contig_free_unpreserve(void *mem, size_t size);
+void luo_contig_free_restore(void *mem, size_t size);
+
#endif /* _LINUX_LUO_INTERNAL_H */
--
2.51.0.536.g15c5d4f767-goog
Powered by blists - more mailing lists