Mike Rapoport (Microsoft)Merge branch 'misc' into next6ef2a1e3 months agoHistory
685 lines · 18.7 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2025, Google LLC. * Pasha Tatashin <pasha.tatashin@soleen.com> */ /** * DOC: LUO Sessions * * LUO Sessions provide the core mechanism for grouping and managing `struct * file *` instances that need to be preserved across a kexec-based live * update. Each session acts as a named container for a set of file objects, * allowing a userspace agent to manage the lifecycle of resources critical to a * workload. * * Core Concepts: * * - Named Containers: Sessions are identified by a unique, user-provided name, *   which is used for both creation in the current kernel and retrieval in the *   next kernel. * * - Userspace Interface: Session management is driven from userspace via *   ioctls on /dev/liveupdate. * * - Serialization: Session metadata is preserved using the KHO framework. When *   a live update is triggered via kexec, session metadata is serialized into *   a chain of linked-blocks and placed in a preserved memory region. The *   physical address of the first block header is stored in the centralized *   `struct luo_ser` structure. * * Session Lifecycle: * * 1.  Creation: A userspace agent calls `luo_session_create()` to create a *     new, empty session and receives a file descriptor for it. * * 2.  Serialization: When the `reboot(LINUX_REBOOT_CMD_KEXEC)` syscall is *     made, `luo_session_serialize()` is called. It iterates through all *     active sessions and writes their metadata into a memory area preserved *     by KHO. * * 3.  Deserialization (in new kernel): After kexec, `luo_session_deserialize()` *     runs, reading the serialized data and creating a list of `struct *     luo_session` objects representing the preserved sessions. * * 4.  Retrieval: A userspace agent in the new kernel can then call *     `luo_session_retrieve()` with a session name to get a new file *     descriptor and access the preserved state. * * Locking: * * The LUO session subsystem uses a three-tier locking hierarchy to ensure thread * safety and prevent deadlocks during concurrent session mutations and kexec * serialization: * * 1. `luo_session_serialize_rwsem` (global rwsem): *    Protects session mutations (creation, retrieval, release, and ioctls) *    against the serialization process during reboot. * *    - Readers: Taken by any path modifying or accessing session state (e.g., *      `luo_session_create()`, `luo_session_retrieve()`, `luo_session_release()`, *      and `luo_session_ioctl()`). *    - Writer: Taken by the serialization process (`luo_session_serialize()`) *      during reboot. On success, the write lock is held indefinitely to freeze *      the subsystem. On failure, it is released to allow recovery. * * 2. `luo_session_header->rwsem` (per-list rwsem): *    Synchronizes list-level operations for the incoming and outgoing session headers. * *    - Writer: Taken during list mutation operations (inserting or removing a *      session from the list). *    - Reader: Taken when traversing the list (e.g., retrieving a session by name). * * 3. `luo_session->mutex` (per-session mutex): *    Protects the internal state and file sets of an individual session. It is *    acquired during per-session operations such as preserving, retrieving, *    or freezing files. * * Lock Hierarchy: *   `luo_session_serialize_rwsem` -> `luo_session_header->rwsem` -> `luo_session->mutex` */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/anon_inodes.h>#include <linux/cleanup.h>#include <linux/err.h>#include <linux/errno.h>#include <linux/file.h>#include <linux/fs.h>#include <linux/io.h>#include <linux/kexec_handover.h>#include <linux/kho_block.h>#include <linux/kho/abi/luo.h>#include <linux/list.h>#include <linux/liveupdate.h>#include <linux/mutex.h>#include <linux/rwsem.h>#include <linux/slab.h>#include <uapi/linux/liveupdate.h>#include "luo_internal.h" static DECLARE_RWSEM(luo_session_serialize_rwsem);/** * struct luo_session_header - Header struct for managing LUO sessions. * @count:       The number of sessions currently tracked in the @list. * @list:        The head of the linked list of `struct luo_session` instances. * @rwsem:       A read-write semaphore providing synchronized access to the *               session list and other fields in this structure. * @block_set:   The set of serialization blocks. * @sessions_pa: Points to the location of sessions_pa within struct luo_ser. * @active:      Set to true when first initialized. If previous kernel did not *               send session data, active stays false for incoming. */struct luo_session_header {	long count;	struct list_head list;	struct rw_semaphore rwsem;	struct kho_block_set block_set;	u64 *sessions_pa;	bool active;}; /** * struct luo_session_global - Global container for managing LUO sessions. * @incoming:     The sessions passed from the previous kernel. * @outgoing:     The sessions that are going to be passed to the next kernel. */struct luo_session_global {	struct luo_session_header incoming;	struct luo_session_header outgoing;}; static struct luo_session_global luo_session_global = {	.incoming = {		.list = LIST_HEAD_INIT(luo_session_global.incoming.list),		.rwsem = __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem),		.block_set = KHO_BLOCK_SET_INIT(luo_session_global.incoming.block_set,						sizeof(struct luo_session_ser)),	},	.outgoing = {		.list = LIST_HEAD_INIT(luo_session_global.outgoing.list),		.rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem),		.block_set = KHO_BLOCK_SET_INIT(luo_session_global.outgoing.block_set,						sizeof(struct luo_session_ser)),	},}; static struct luo_session *luo_session_alloc(const char *name){	struct luo_session *session = kzalloc_obj(*session); 	if (!session)		return ERR_PTR(-ENOMEM); 	strscpy(session->name, name, sizeof(session->name));	luo_file_set_init(&session->file_set);	INIT_LIST_HEAD(&session->list);	mutex_init(&session->mutex); 	return session;} static void luo_session_free(struct luo_session *session){	luo_file_set_destroy(&session->file_set);	mutex_destroy(&session->mutex);	kfree(session);} static int luo_session_insert(struct luo_session_header *sh,			      struct luo_session *session){	struct luo_session *it;	int err; 	guard(rwsem_write)(&sh->rwsem); 	/*	 * For outgoing we should make sure there is room in serialization array	 * for new session.	 */	if (sh == &luo_session_global.outgoing) {		err = kho_block_set_grow(&sh->block_set, sh->count + 1);		if (err)			return err;	} 	/*	 * For small number of sessions this loop won't hurt performance	 * but if we ever start using a lot of sessions, this might	 * become a bottle neck during deserialization time, as it would	 * cause O(n*n) complexity.	 */	list_for_each_entry(it, &sh->list, list) {		if (!strncmp(it->name, session->name, sizeof(it->name)))			return -EEXIST;	}	list_add_tail(&session->list, &sh->list);	sh->count++; 	return 0;} static void luo_session_remove(struct luo_session_header *sh,			       struct luo_session *session){	guard(rwsem_write)(&sh->rwsem);	list_del(&session->list);	sh->count--;	if (sh == &luo_session_global.outgoing)		kho_block_set_shrink(&sh->block_set, sh->count);} static int luo_session_finish_one(struct luo_session *session){	guard(mutex)(&session->mutex);	return luo_file_finish(&session->file_set);} static void luo_session_unfreeze_one(struct luo_session *session,				     struct luo_session_ser *ser){	guard(mutex)(&session->mutex);	luo_file_unfreeze(&session->file_set, &ser->file_set_ser);} static int luo_session_freeze_one(struct luo_session *session,				  struct luo_session_ser *ser){	guard(mutex)(&session->mutex);	return luo_file_freeze(&session->file_set, &ser->file_set_ser);} static int luo_session_release(struct inode *inodep, struct file *filep){	struct luo_session *session = filep->private_data;	struct luo_session_header *sh; 	guard(rwsem_read)(&luo_session_serialize_rwsem);	/* If retrieved is set, it means this session is from incoming list */	if (session->retrieved) {		int err = luo_session_finish_one(session); 		if (err) {			pr_warn("Unable to finish session [%s] on release\n",				session->name);			return err;		}		sh = &luo_session_global.incoming;	} else {		scoped_guard(mutex, &session->mutex)			luo_file_unpreserve_files(&session->file_set);		sh = &luo_session_global.outgoing;	} 	luo_session_remove(sh, session);	luo_session_free(session); 	return 0;} static int luo_session_preserve_fd(struct luo_session *session,				   struct luo_ucmd *ucmd){	struct liveupdate_session_preserve_fd *argp = ucmd->cmd;	int err; 	guard(mutex)(&session->mutex);	err = luo_preserve_file(&session->file_set, argp->token, argp->fd);	if (err)		return err; 	err = luo_ucmd_respond(ucmd, sizeof(*argp));	if (err)		pr_warn("The file was successfully preserved, but response to user failed\n"); 	return err;} static int luo_session_retrieve_fd(struct luo_session *session,				   struct luo_ucmd *ucmd){	struct liveupdate_session_retrieve_fd *argp = ucmd->cmd;	struct file *file;	int err; 	argp->fd = get_unused_fd_flags(O_CLOEXEC);	if (argp->fd < 0)		return argp->fd; 	mutex_lock(&session->mutex);	err = luo_retrieve_file(&session->file_set, argp->token, &file);	mutex_unlock(&session->mutex);	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_session_finish(struct luo_session *session,			      struct luo_ucmd *ucmd){	struct liveupdate_session_finish *argp = ucmd->cmd;	int err; 	if (argp->reserved)		return -EINVAL; 	err = luo_session_finish_one(session);	if (err)		return err; 	return luo_ucmd_respond(ucmd, sizeof(*argp));} static int luo_session_get_name(struct luo_session *session,				struct luo_ucmd *ucmd){	struct liveupdate_session_get_name *argp = ucmd->cmd; 	if (argp->reserved != 0)		return -EINVAL; 	strscpy((char *)argp->name, session->name, sizeof(argp->name)); 	return luo_ucmd_respond(ucmd, sizeof(*argp));} union ucmd_buffer {	struct liveupdate_session_finish finish;	struct liveupdate_session_preserve_fd preserve;	struct liveupdate_session_retrieve_fd retrieve;	struct liveupdate_session_get_name get_name;}; /* Type of sessions the ioctl applies to. */enum luo_ioctl_type {	LUO_IOCTL_INCOMING,	LUO_IOCTL_OUTGOING,	LUO_IOCTL_ALL,}; struct luo_ioctl_op {	unsigned int size;	unsigned int min_size;	unsigned int ioctl_num;	enum luo_ioctl_type type;	int (*execute)(struct luo_session *session, struct luo_ucmd *ucmd);}; #define IOCTL_OP(_ioctl, _fn, _struct, _last, _type)                           \	[_IOC_NR(_ioctl) - LIVEUPDATE_CMD_SESSION_BASE] = {                    \		.size = sizeof(_struct) +                                      \			BUILD_BUG_ON_ZERO(sizeof(union ucmd_buffer) <          \					  sizeof(_struct)),                    \		.min_size = offsetofend(_struct, _last),                       \		.ioctl_num = _ioctl,                                           \		.type = _type,                                                 \		.execute = _fn,                                                \	} static const struct luo_ioctl_op luo_session_ioctl_ops[] = {	IOCTL_OP(LIVEUPDATE_SESSION_FINISH, luo_session_finish,		 struct liveupdate_session_finish, reserved, LUO_IOCTL_INCOMING),	IOCTL_OP(LIVEUPDATE_SESSION_PRESERVE_FD, luo_session_preserve_fd,		 struct liveupdate_session_preserve_fd, token, LUO_IOCTL_OUTGOING),	IOCTL_OP(LIVEUPDATE_SESSION_RETRIEVE_FD, luo_session_retrieve_fd,		 struct liveupdate_session_retrieve_fd, token, LUO_IOCTL_INCOMING),	IOCTL_OP(LIVEUPDATE_SESSION_GET_NAME, luo_session_get_name,		 struct liveupdate_session_get_name, name, LUO_IOCTL_ALL),}; static bool luo_ioctl_type_valid(struct luo_session *session,				 const struct luo_ioctl_op *op){	switch (op->type) {	case LUO_IOCTL_INCOMING:		/* Retrieved is only set on incoming sessions */		return session->retrieved;	case LUO_IOCTL_OUTGOING:		return !session->retrieved;	case LUO_IOCTL_ALL:		return true;	} 	/* Catch-all. */	return false;} static long luo_session_ioctl(struct file *filep, unsigned int cmd,			      unsigned long arg){	struct luo_session *session = filep->private_data;	const struct luo_ioctl_op *op;	struct luo_ucmd ucmd = {};	union ucmd_buffer buf;	unsigned int nr;	int ret; 	nr = _IOC_NR(cmd);	if (nr < LIVEUPDATE_CMD_SESSION_BASE || (nr - LIVEUPDATE_CMD_SESSION_BASE) >=	    ARRAY_SIZE(luo_session_ioctl_ops)) {		return -EINVAL;	} 	ucmd.ubuffer = (void __user *)arg;	ret = get_user(ucmd.user_size, (u32 __user *)ucmd.ubuffer);	if (ret)		return ret; 	op = &luo_session_ioctl_ops[nr - LIVEUPDATE_CMD_SESSION_BASE];	if (op->ioctl_num != cmd)		return -ENOIOCTLCMD;	if (!luo_ioctl_type_valid(session, op))		return -EINVAL;	if (ucmd.user_size < op->min_size)		return -EINVAL; 	ucmd.cmd = &buf;	ret = copy_struct_from_user(ucmd.cmd, op->size, ucmd.ubuffer,				    ucmd.user_size);	if (ret)		return ret; 	guard(rwsem_read)(&luo_session_serialize_rwsem);	return op->execute(session, &ucmd);} static const struct file_operations luo_session_fops = {	.owner = THIS_MODULE,	.release = luo_session_release,	.unlocked_ioctl = luo_session_ioctl,}; /* Create a "struct file" for session */static int luo_session_getfile(struct luo_session *session, struct file **filep){	char name_buf[128];	struct file *file; 	lockdep_assert_held(&session->mutex);	snprintf(name_buf, sizeof(name_buf), "[luo_session] %s", session->name);	file = anon_inode_getfile(name_buf, &luo_session_fops, session, O_RDWR);	if (IS_ERR(file))		return PTR_ERR(file); 	*filep = file; 	return 0;} int luo_session_create(const char *name, struct file **filep){	size_t len = strnlen(name, LIVEUPDATE_SESSION_NAME_LENGTH);	struct luo_session *session;	int err; 	if (len == 0 || len > LIVEUPDATE_SESSION_NAME_LENGTH - 1)		return -EINVAL; 	session = luo_session_alloc(name);	if (IS_ERR(session))		return PTR_ERR(session); 	down_read(&luo_session_serialize_rwsem);	err = luo_session_insert(&luo_session_global.outgoing, session);	if (err)		goto err_free; 	mutex_lock(&session->mutex);	err = luo_session_getfile(session, filep);	mutex_unlock(&session->mutex);	if (err)		goto err_remove;	up_read(&luo_session_serialize_rwsem); 	return 0; err_remove:	luo_session_remove(&luo_session_global.outgoing, session);err_free:	luo_session_free(session);	up_read(&luo_session_serialize_rwsem); 	return err;}

Lines 1–500 of 685.