Integrate the LUO with the KHO framework to enable passing LUO state across a kexec reboot. This patch introduces the following changes: - During the KHO finalization phase allocate FDT blob. - Populate this FDT with a LUO compatibility string ("luo-v1") and the current LUO state (`luo_state`). - Implement a KHO notifier 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@xxxxxxxxxx> --- drivers/misc/liveupdate/luo_core.c | 222 ++++++++++++++++++++++++++++- 1 file changed, 219 insertions(+), 3 deletions(-) diff --git a/drivers/misc/liveupdate/luo_core.c b/drivers/misc/liveupdate/luo_core.c index 919c37b0b4d1..a76e886bc3b1 100644 --- a/drivers/misc/liveupdate/luo_core.c +++ b/drivers/misc/liveupdate/luo_core.c @@ -36,9 +36,12 @@ #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/rwsem.h> +#include <linux/sizes.h> #include <linux/string.h> #include "luo_internal.h" @@ -55,6 +58,12 @@ const char *const luo_state_str[] = { bool luo_enabled; +static void *luo_fdt_out; +static void *luo_fdt_in; +#define LUO_FDT_SIZE SZ_1M +#define LUO_KHO_ENTRY_NAME "LUO" +#define LUO_COMPATIBLE "luo-v1" + static int __init early_liveupdate_param(char *buf) { return kstrtobool(buf, &luo_enabled); @@ -79,6 +88,60 @@ 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(struct kho_serialization *ser) +{ + void *fdt_out; + int ret; + + fdt_out = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, + get_order(LUO_FDT_SIZE)); + if (!fdt_out) { + pr_err("failed to allocate FDT memory\n"); + return -ENOMEM; + } + + ret = fdt_create_empty_tree(fdt_out, LUO_FDT_SIZE); + if (ret) + goto exit_free; + + ret = fdt_setprop(fdt_out, 0, "compatible", LUO_COMPATIBLE, + strlen(LUO_COMPATIBLE) + 1); + if (ret) + goto exit_free; + + ret = kho_preserve_phys(__pa(fdt_out), LUO_FDT_SIZE); + if (ret) + goto exit_free; + + ret = kho_add_subtree(ser, LUO_KHO_ENTRY_NAME, fdt_out); + if (ret) + goto exit_unpreserve; + luo_fdt_out = fdt_out; + + return 0; + +exit_unpreserve: + kho_unpreserve_phys(__pa(fdt_out), LUO_FDT_SIZE); +exit_free: + free_pages((unsigned long)fdt_out, get_order(LUO_FDT_SIZE)); + pr_err("failed to prepare LUO FDT: %d\n", ret); + + return ret; +} + +static void luo_fdt_destroy(void) +{ + kho_unpreserve_phys(__pa(luo_fdt_out), LUO_FDT_SIZE); + free_pages((unsigned long)luo_fdt_out, get_order(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; @@ -88,11 +151,111 @@ static void luo_do_finish_calls(void) { } -int luo_prepare(void) +static void luo_do_cancel_calls(void) +{ +} + +static int __luo_prepare(struct kho_serialization *ser) { + 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_STATE_STR); + ret = -EINVAL; + goto exit_unlock; + } + + ret = luo_fdt_setup(ser); + if (ret) + goto exit_unlock; + + ret = luo_do_prepare_calls(); + if (ret) + 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_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; } +static int luo_kho_notifier(struct notifier_block *self, + unsigned long cmd, void *v) +{ + int ret; + + switch (cmd) { + case KEXEC_KHO_FINALIZE: + ret = __luo_prepare((struct kho_serialization *)v); + break; + case KEXEC_KHO_ABORT: + ret = __luo_cancel(); + break; + default: + return NOTIFY_BAD; + } + + return notifier_from_errno(ret); +} + +static struct notifier_block luo_kho_notifier_nb = { + .notifier_call = luo_kho_notifier, +}; + +/** + * 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 kho_finalize(); +} + /** * luo_freeze() - Initiate the final freeze notification phase for live update. * @@ -188,9 +351,23 @@ 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; + return kho_abort(); } void luo_state_read_enter(void) @@ -205,7 +382,46 @@ 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; + } + + ret = register_kho_notifier(&luo_kho_notifier_nb); + if (ret) { + luo_enabled = false; + pr_warn("Failed to register with KHO [%d]\n", ret); + } + + /* + * Retrieve LUO subtree, and verify its format. Panic in case of + * exceptions, since machine devices and memory is in unpredictable + * state. + */ + ret = kho_retrieve_subtree(LUO_KHO_ENTRY_NAME, &fdt_phys); + if (ret) { + if (ret != -ENOENT) { + panic("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) { + panic("FDT '%s' is incompatible with '%s' [%d]\n", + LUO_KHO_ENTRY_NAME, LUO_COMPATIBLE, ret); + } + + __luo_set_state(LIVEUPDATE_STATE_UPDATED); return 0; } -- 2.49.0.1101.gccaa498523-goog