Something went wrong. Try again.
Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Something went wrong. Try again.
11 kB · 442 lines
C
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443// 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);