| // 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); |