| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (c) 2025, Google LLC. | |
| * Pasha Tatashin <pasha.tatashin@soleen.com> | |
| */ | |
| /** | |
| * DOC: Live Update Orchestrator (LUO) | |
| * | |
| * Live Update is a specialized, kexec-based reboot process that allows a | |
| * running kernel to be updated from one version to another while preserving | |
| * the state of selected resources and keeping designated hardware devices | |
| * operational. For these devices, DMA activity may continue throughout the | |
| * kernel transition. | |
| * | |
| * While the primary use case driving this work is supporting live updates of | |
| * the Linux kernel when it is used as a hypervisor in cloud environments, the | |
| * LUO framework itself is designed to be workload-agnostic. Live Update | |
| * facilitates a full kernel version upgrade for any type of system. | |
| * | |
| * For example, a non-hypervisor system running an in-memory cache like | |
| * memcached with many gigabytes of data can use LUO. The userspace service | |
| * can place its cache into a memfd, have its state preserved by LUO, and | |
| * restore it immediately after the kernel kexec. | |
| * | |
| * Whether the system is running virtual machines, containers, a | |
| * high-performance database, or networking services, LUO's primary goal is to | |
| * enable a full kernel update by preserving critical userspace state and | |
| * keeping essential devices operational. | |
| * | |
| * The core of LUO is a mechanism that tracks the progress of a live update, | |
| * along with a callback API that allows other kernel subsystems to participate | |
| * in the process. Example subsystems that can hook into LUO include: kvm, | |
| * iommu, interrupts, vfio, participating filesystems, and memory management. | |
| * | |
| * LUO uses Kexec Handover to transfer memory state from the current kernel to | |
| * the next kernel. For more details see Documentation/core-api/kho/index.rst. | |
| * | |
| * .. note:: | |
| * To enable LUO, boot the kernel with the ``liveupdate=on`` command line | |
| * parameter. | |
| */ | |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
| #include <linux/atomic.h> | |
| #include <linux/errno.h> | |
| #include <linux/file.h> | |
| #include <linux/fs.h> | |
| #include <linux/init.h> | |
| #include <linux/io.h> | |
| #include <linux/kernel.h> | |
| #include <linux/kexec_handover.h> | |
| #include <linux/kho/abi/luo.h> | |
| #include <linux/kobject.h> | |
| #include <linux/liveupdate.h> | |
| #include <linux/miscdevice.h> | |
| #include <linux/mm.h> | |
| #include <linux/rwsem.h> | |
| #include <linux/sizes.h> | |
| #include <linux/string.h> | |
| #include "kexec_handover_internal.h" | |
| #include "luo_internal.h" | |
| static struct { | |
| bool enabled; | |
| struct luo_ser *luo_ser_out; | |
| u64 liveupdate_num; | |
| } luo_global; | |
| /* | |
| * luo_register_rwlock - Protects registration of file handlers and FLBs. | |
| */ | |
| DECLARE_RWSEM(luo_register_rwlock); | |
| static int __init early_liveupdate_param(char *buf) | |
| { | |
| return kstrtobool(buf, &luo_global.enabled); | |
| } | |
| early_param("liveupdate", early_liveupdate_param); | |
| static int __init luo_early_startup(void) | |
| { | |
| phys_addr_t luo_ser_phys; | |
| struct luo_ser *luo_ser; | |
| size_t len; | |
| int err; | |
| if (!kho_is_enabled()) { | |
| if (liveupdate_enabled()) | |
| pr_warn("Disabling liveupdate because KHO is disabled\n"); | |
| luo_global.enabled = false; | |
| return 0; | |
| } | |
| /* Retrieve LUO state from KHO. */ | |
| err = kho_retrieve_subtree(LUO_KHO_ENTRY_NAME, &luo_ser_phys, &len); | |
| if (err) { | |
| if (err != -ENOENT) { | |
| pr_err("failed to retrieve LUO state '%s' from KHO: %pe\n", | |
| LUO_KHO_ENTRY_NAME, ERR_PTR(err)); | |
| return err; | |
| } | |
| return 0; | |
| } | |
| if (len < sizeof(*luo_ser)) { | |
| pr_err("LUO state is too small (%zu < %zu)\n", len, sizeof(*luo_ser)); | |
| return -EINVAL; | |
| } | |
| 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; | |
| } | |
| luo_global.liveupdate_num = luo_ser->liveupdate_num; | |
| pr_info("Retrieved live update data, liveupdate number: %lld\n", | |
| luo_global.liveupdate_num); | |
| err = luo_session_setup_incoming(luo_ser->sessions_pa); | |
| if (err) | |
| goto out_free_ser; | |
| luo_flb_setup_incoming(luo_ser->flbs_pa); | |
| err = 0; | |
| out_free_ser: | |
| kho_restore_free(luo_ser); | |
| return err; | |
| } | |
| static int __init liveupdate_early_init(void) | |
| { | |
| int err; | |
| err = luo_early_startup(); | |
| if (err) { | |
| luo_global.enabled = false; | |
| luo_restore_fail("The incoming tree failed to initialize properly [%pe], disabling live update\n", | |
| ERR_PTR(err)); | |
| } | |
| return err; | |
| } | |
| early_initcall(liveupdate_early_init); | |
| /* Called during boot to create outgoing LUO state */ | |
| static int __init luo_state_setup(void) | |
| { | |
| struct luo_ser *luo_ser; | |
| int err; | |
| luo_ser = kho_alloc_preserve(sizeof(*luo_ser)); | |
| if (IS_ERR(luo_ser)) { | |
| pr_err("failed to allocate/preserve LUO state memory\n"); | |
| return PTR_ERR(luo_ser); | |
| } | |
| strscpy(luo_ser->compatible, LUO_ABI_COMPATIBLE, sizeof(luo_ser->compatible)); | |
| luo_ser->liveupdate_num = luo_global.liveupdate_num + 1; | |
| luo_session_setup_outgoing(&luo_ser->sessions_pa); | |
| err = luo_flb_setup_outgoing(&luo_ser->flbs_pa); | |
| if (err) | |
| goto exit_free_luo_ser; | |
| err = kho_add_subtree(LUO_KHO_ENTRY_NAME, luo_ser, sizeof(*luo_ser)); | |
| if (err) | |
| goto exit_free_luo_ser; | |
| luo_global.luo_ser_out = luo_ser; | |
| return 0; | |
| exit_free_luo_ser: | |
| kho_unpreserve_free(luo_ser); | |
| pr_err("failed to prepare LUO state: %d\n", err); | |
| return err; | |
| } | |
| /* | |
| * late initcall because it initializes the outgoing tree that is needed only | |
| * once userspace starts using /dev/liveupdate. | |
| */ | |
| static int __init luo_late_startup(void) | |
| { | |
| int err; | |
| if (!liveupdate_enabled()) | |
| return 0; | |
| err = luo_state_setup(); | |
| if (err) | |
| luo_global.enabled = false; | |
| return err; | |
| } | |
| late_initcall(luo_late_startup); | |
| /* Public Functions */ | |
| /** | |
| * liveupdate_reboot() - Kernel reboot notifier for live update final | |
| * serialization. | |
| * | |
| * This function is invoked directly from the reboot() syscall pathway | |
| * if kexec is in progress. | |
| * | |
| * If any callback fails, this function aborts KHO, undoes the freeze() | |
| * callbacks, and returns an error. | |
| */ | |
| int liveupdate_reboot(void) | |
| { | |
| int err; | |
| if (!liveupdate_enabled()) | |
| return 0; | |
| err = luo_session_serialize(); | |
| if (err) | |
| return err; | |
| luo_flb_serialize(); | |
| return 0; | |
| } | |
| /** | |
| * liveupdate_enabled - Check if the live update feature is enabled. | |
| * | |
| * This function returns the state of the live update feature flag, which | |
| * can be controlled via the ``liveupdate`` kernel command-line parameter. | |
| * | |
| * @return true if live update is enabled, false otherwise. | |
| */ | |
| bool liveupdate_enabled(void) | |
| { | |
| return luo_global.enabled; | |
| } | |
| /** | |
| * DOC: LUO ioctl Interface | |
| * | |
| * The IOCTL user-space control interface for the LUO subsystem. | |
| * It registers a character device, typically found at ``/dev/liveupdate``, | |
| * which allows a userspace agent to manage the LUO state machine and its | |
| * associated resources, such as preservable file descriptors. | |
| * | |
| * To ensure that the state machine is controlled by a single entity, access | |
| * to this device is exclusive: only one process is permitted to have | |
| * ``/dev/liveupdate`` open at any given time. Subsequent open attempts will | |
| * fail with -EBUSY until the first process closes its file descriptor. | |
| * This singleton model simplifies state management by preventing conflicting | |
| * commands from multiple userspace agents. | |
| */ | |
| struct luo_device_state { | |
| struct miscdevice miscdev; | |
| atomic_t in_use; | |
| }; | |
| static int luo_ioctl_create_session(struct luo_ucmd *ucmd) | |
| { | |
| struct liveupdate_ioctl_create_session *argp = ucmd->cmd; | |
| struct file *file; | |
| int err; | |
| argp->fd = get_unused_fd_flags(O_CLOEXEC); | |
| if (argp->fd < 0) | |
| return argp->fd; | |
| err = luo_session_create(argp->name, &file); | |
| if (err) | |
| goto err_put_fd; | |
| err = luo_ucmd_respond(ucmd, sizeof(*argp)); | |
| if (err) | |
| goto err_put_file; | |
| fd_install(argp->fd, file); | |
| return 0; | |
| err_put_file: | |
| fput(file); | |
| err_put_fd: | |
| put_unused_fd(argp->fd); | |
| return err; | |
| } | |
| static int luo_ioctl_retrieve_session(struct luo_ucmd *ucmd) | |
| { | |
| struct liveupdate_ioctl_retrieve_session *argp = ucmd->cmd; | |
| struct file *file; | |
| int err; | |
| argp->fd = get_unused_fd_flags(O_CLOEXEC); | |
| if (argp->fd < 0) | |
| return argp->fd; | |
| err = luo_session_retrieve(argp->name, &file); | |
| if (err < 0) | |
| goto err_put_fd; | |
| err = luo_ucmd_respond(ucmd, sizeof(*argp)); | |
| if (err) | |
| goto err_put_file; | |
| fd_install(argp->fd, file); | |
| return 0; | |
| err_put_file: | |
| fput(file); | |
| err_put_fd: | |
| put_unused_fd(argp->fd); | |
| return err; | |
| } | |
| static int luo_open(struct inode *inodep, struct file *filep) | |
| { | |
| struct luo_device_state *ldev = container_of(filep->private_data, | |
| struct luo_device_state, | |
| miscdev); | |
| if (atomic_cmpxchg(&ldev->in_use, 0, 1)) | |
| return -EBUSY; | |
| /* Always return -EIO to user if deserialization fail */ | |
| if (luo_session_deserialize()) { | |
| atomic_set(&ldev->in_use, 0); | |
| return -EIO; | |
| } | |
| return 0; | |
| } | |
| static int luo_release(struct inode *inodep, struct file *filep) | |
| { | |
| struct luo_device_state *ldev = container_of(filep->private_data, | |
| struct luo_device_state, | |
| miscdev); | |
| atomic_set(&ldev->in_use, 0); | |
| return 0; | |
| } | |
| union ucmd_buffer { | |
| struct liveupdate_ioctl_create_session create; | |
| struct liveupdate_ioctl_retrieve_session retrieve; | |
| }; | |
| struct luo_ioctl_op { | |
| unsigned int size; | |
| unsigned int min_size; | |
| unsigned int ioctl_num; | |
| int (*execute)(struct luo_ucmd *ucmd); | |
| }; | |
| #define IOCTL_OP(_ioctl, _fn, _struct, _last) \ | |
| [_IOC_NR(_ioctl) - LIVEUPDATE_CMD_BASE] = { \ | |
| .size = sizeof(_struct) + \ | |
| BUILD_BUG_ON_ZERO(sizeof(union ucmd_buffer) < \ | |
| sizeof(_struct)), \ | |
| .min_size = offsetofend(_struct, _last), \ | |
| .ioctl_num = _ioctl, \ | |
| .execute = _fn, \ | |
| } | |
| static const struct luo_ioctl_op luo_ioctl_ops[] = { | |
| IOCTL_OP(LIVEUPDATE_IOCTL_CREATE_SESSION, luo_ioctl_create_session, | |
| struct liveupdate_ioctl_create_session, name), | |
| IOCTL_OP(LIVEUPDATE_IOCTL_RETRIEVE_SESSION, luo_ioctl_retrieve_session, | |
| struct liveupdate_ioctl_retrieve_session, name), | |
| }; | |
| static long luo_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) | |
| { | |
| const struct luo_ioctl_op *op; | |
| struct luo_ucmd ucmd = {}; | |
| union ucmd_buffer buf; | |
| unsigned int nr; | |
| int err; | |
| nr = _IOC_NR(cmd); | |
| if (nr - LIVEUPDATE_CMD_BASE >= ARRAY_SIZE(luo_ioctl_ops)) | |
| return -EINVAL; | |
| ucmd.ubuffer = (void __user *)arg; | |
| err = get_user(ucmd.user_size, (u32 __user *)ucmd.ubuffer); | |
| if (err) | |
| return err; | |
| op = &luo_ioctl_ops[nr - LIVEUPDATE_CMD_BASE]; | |
| if (op->ioctl_num != cmd) | |
| return -ENOIOCTLCMD; | |
| if (ucmd.user_size < op->min_size) | |
| return -EINVAL; | |
| ucmd.cmd = &buf; | |
| err = copy_struct_from_user(ucmd.cmd, op->size, ucmd.ubuffer, | |
| ucmd.user_size); | |
| if (err) | |
| return err; | |
| return op->execute(&ucmd); | |
| } | |
| static const struct file_operations luo_fops = { | |
| .owner = THIS_MODULE, | |
| .open = luo_open, | |
| .release = luo_release, | |
| .unlocked_ioctl = luo_ioctl, | |
| }; | |
| static struct luo_device_state luo_dev = { | |
| .miscdev = { | |
| .minor = MISC_DYNAMIC_MINOR, | |
| .name = "liveupdate", | |
| .fops = &luo_fops, | |
| }, | |
| .in_use = ATOMIC_INIT(0), | |
| }; | |
| static int __init liveupdate_ioctl_init(void) | |
| { | |
| if (!liveupdate_enabled()) | |
| return 0; | |
| return misc_register(&luo_dev.miscdev); | |
| } | |
| late_initcall(liveupdate_ioctl_init); |