xref: /linux/kernel/liveupdate/luo_session.c (revision bf4afc53b77aeaa48b5409da5c8da6bb4eff7f43)
10153094dSPasha Tatashin // SPDX-License-Identifier: GPL-2.0
20153094dSPasha Tatashin 
30153094dSPasha Tatashin /*
40153094dSPasha Tatashin  * Copyright (c) 2025, Google LLC.
50153094dSPasha Tatashin  * Pasha Tatashin <pasha.tatashin@soleen.com>
60153094dSPasha Tatashin  */
70153094dSPasha Tatashin 
80153094dSPasha Tatashin /**
90153094dSPasha Tatashin  * DOC: LUO Sessions
100153094dSPasha Tatashin  *
110153094dSPasha Tatashin  * LUO Sessions provide the core mechanism for grouping and managing `struct
120153094dSPasha Tatashin  * file *` instances that need to be preserved across a kexec-based live
130153094dSPasha Tatashin  * update. Each session acts as a named container for a set of file objects,
140153094dSPasha Tatashin  * allowing a userspace agent to manage the lifecycle of resources critical to a
150153094dSPasha Tatashin  * workload.
160153094dSPasha Tatashin  *
170153094dSPasha Tatashin  * Core Concepts:
180153094dSPasha Tatashin  *
190153094dSPasha Tatashin  * - Named Containers: Sessions are identified by a unique, user-provided name,
200153094dSPasha Tatashin  *   which is used for both creation in the current kernel and retrieval in the
210153094dSPasha Tatashin  *   next kernel.
220153094dSPasha Tatashin  *
230153094dSPasha Tatashin  * - Userspace Interface: Session management is driven from userspace via
240153094dSPasha Tatashin  *   ioctls on /dev/liveupdate.
250153094dSPasha Tatashin  *
260153094dSPasha Tatashin  * - Serialization: Session metadata is preserved using the KHO framework. When
270153094dSPasha Tatashin  *   a live update is triggered via kexec, an array of `struct luo_session_ser`
280153094dSPasha Tatashin  *   is populated and placed in a preserved memory region. An FDT node is also
290153094dSPasha Tatashin  *   created, containing the count of sessions and the physical address of this
300153094dSPasha Tatashin  *   array.
310153094dSPasha Tatashin  *
320153094dSPasha Tatashin  * Session Lifecycle:
330153094dSPasha Tatashin  *
340153094dSPasha Tatashin  * 1.  Creation: A userspace agent calls `luo_session_create()` to create a
350153094dSPasha Tatashin  *     new, empty session and receives a file descriptor for it.
360153094dSPasha Tatashin  *
370153094dSPasha Tatashin  * 2.  Serialization: When the `reboot(LINUX_REBOOT_CMD_KEXEC)` syscall is
380153094dSPasha Tatashin  *     made, `luo_session_serialize()` is called. It iterates through all
390153094dSPasha Tatashin  *     active sessions and writes their metadata into a memory area preserved
400153094dSPasha Tatashin  *     by KHO.
410153094dSPasha Tatashin  *
420153094dSPasha Tatashin  * 3.  Deserialization (in new kernel): After kexec, `luo_session_deserialize()`
430153094dSPasha Tatashin  *     runs, reading the serialized data and creating a list of `struct
440153094dSPasha Tatashin  *     luo_session` objects representing the preserved sessions.
450153094dSPasha Tatashin  *
460153094dSPasha Tatashin  * 4.  Retrieval: A userspace agent in the new kernel can then call
470153094dSPasha Tatashin  *     `luo_session_retrieve()` with a session name to get a new file
480153094dSPasha Tatashin  *     descriptor and access the preserved state.
490153094dSPasha Tatashin  */
500153094dSPasha Tatashin 
510153094dSPasha Tatashin #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
520153094dSPasha Tatashin 
530153094dSPasha Tatashin #include <linux/anon_inodes.h>
540153094dSPasha Tatashin #include <linux/cleanup.h>
550153094dSPasha Tatashin #include <linux/err.h>
560153094dSPasha Tatashin #include <linux/errno.h>
570153094dSPasha Tatashin #include <linux/file.h>
580153094dSPasha Tatashin #include <linux/fs.h>
590153094dSPasha Tatashin #include <linux/io.h>
600153094dSPasha Tatashin #include <linux/kexec_handover.h>
610153094dSPasha Tatashin #include <linux/kho/abi/luo.h>
620153094dSPasha Tatashin #include <linux/libfdt.h>
630153094dSPasha Tatashin #include <linux/list.h>
640153094dSPasha Tatashin #include <linux/liveupdate.h>
650153094dSPasha Tatashin #include <linux/mutex.h>
660153094dSPasha Tatashin #include <linux/rwsem.h>
670153094dSPasha Tatashin #include <linux/slab.h>
680153094dSPasha Tatashin #include <linux/unaligned.h>
690153094dSPasha Tatashin #include <uapi/linux/liveupdate.h>
700153094dSPasha Tatashin #include "luo_internal.h"
710153094dSPasha Tatashin 
720153094dSPasha Tatashin /* 16 4K pages, give space for 744 sessions */
730153094dSPasha Tatashin #define LUO_SESSION_PGCNT	16ul
740153094dSPasha Tatashin #define LUO_SESSION_MAX		(((LUO_SESSION_PGCNT << PAGE_SHIFT) -	\
750153094dSPasha Tatashin 		sizeof(struct luo_session_header_ser)) /		\
760153094dSPasha Tatashin 		sizeof(struct luo_session_ser))
770153094dSPasha Tatashin 
780153094dSPasha Tatashin /**
790153094dSPasha Tatashin  * struct luo_session_header - Header struct for managing LUO sessions.
800153094dSPasha Tatashin  * @count:      The number of sessions currently tracked in the @list.
810153094dSPasha Tatashin  * @list:       The head of the linked list of `struct luo_session` instances.
820153094dSPasha Tatashin  * @rwsem:      A read-write semaphore providing synchronized access to the
830153094dSPasha Tatashin  *              session list and other fields in this structure.
840153094dSPasha Tatashin  * @header_ser: The header data of serialization array.
850153094dSPasha Tatashin  * @ser:        The serialized session data (an array of
860153094dSPasha Tatashin  *              `struct luo_session_ser`).
870153094dSPasha Tatashin  * @active:     Set to true when first initialized. If previous kernel did not
880153094dSPasha Tatashin  *              send session data, active stays false for incoming.
890153094dSPasha Tatashin  */
900153094dSPasha Tatashin struct luo_session_header {
910153094dSPasha Tatashin 	long count;
920153094dSPasha Tatashin 	struct list_head list;
930153094dSPasha Tatashin 	struct rw_semaphore rwsem;
940153094dSPasha Tatashin 	struct luo_session_header_ser *header_ser;
950153094dSPasha Tatashin 	struct luo_session_ser *ser;
960153094dSPasha Tatashin 	bool active;
970153094dSPasha Tatashin };
980153094dSPasha Tatashin 
990153094dSPasha Tatashin /**
1000153094dSPasha Tatashin  * struct luo_session_global - Global container for managing LUO sessions.
1010153094dSPasha Tatashin  * @incoming:     The sessions passed from the previous kernel.
1020153094dSPasha Tatashin  * @outgoing:     The sessions that are going to be passed to the next kernel.
1030153094dSPasha Tatashin  */
1040153094dSPasha Tatashin struct luo_session_global {
1050153094dSPasha Tatashin 	struct luo_session_header incoming;
1060153094dSPasha Tatashin 	struct luo_session_header outgoing;
1070153094dSPasha Tatashin };
1080153094dSPasha Tatashin 
1090153094dSPasha Tatashin static struct luo_session_global luo_session_global = {
1100153094dSPasha Tatashin 	.incoming = {
1110153094dSPasha Tatashin 		.list = LIST_HEAD_INIT(luo_session_global.incoming.list),
1120153094dSPasha Tatashin 		.rwsem = __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem),
1130153094dSPasha Tatashin 	},
1140153094dSPasha Tatashin 	.outgoing = {
1150153094dSPasha Tatashin 		.list = LIST_HEAD_INIT(luo_session_global.outgoing.list),
1160153094dSPasha Tatashin 		.rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem),
1170153094dSPasha Tatashin 	},
1180153094dSPasha Tatashin };
1190153094dSPasha Tatashin 
luo_session_alloc(const char * name)1200153094dSPasha Tatashin static struct luo_session *luo_session_alloc(const char *name)
1210153094dSPasha Tatashin {
122*bf4afc53SLinus Torvalds 	struct luo_session *session = kzalloc_obj(*session);
1230153094dSPasha Tatashin 
1240153094dSPasha Tatashin 	if (!session)
1250153094dSPasha Tatashin 		return ERR_PTR(-ENOMEM);
1260153094dSPasha Tatashin 
1270153094dSPasha Tatashin 	strscpy(session->name, name, sizeof(session->name));
12816cec0d2SPasha Tatashin 	INIT_LIST_HEAD(&session->file_set.files_list);
12916cec0d2SPasha Tatashin 	luo_file_set_init(&session->file_set);
1300153094dSPasha Tatashin 	INIT_LIST_HEAD(&session->list);
1310153094dSPasha Tatashin 	mutex_init(&session->mutex);
1320153094dSPasha Tatashin 
1330153094dSPasha Tatashin 	return session;
1340153094dSPasha Tatashin }
1350153094dSPasha Tatashin 
luo_session_free(struct luo_session * session)1360153094dSPasha Tatashin static void luo_session_free(struct luo_session *session)
1370153094dSPasha Tatashin {
13816cec0d2SPasha Tatashin 	luo_file_set_destroy(&session->file_set);
1390153094dSPasha Tatashin 	mutex_destroy(&session->mutex);
1400153094dSPasha Tatashin 	kfree(session);
1410153094dSPasha Tatashin }
1420153094dSPasha Tatashin 
luo_session_insert(struct luo_session_header * sh,struct luo_session * session)1430153094dSPasha Tatashin static int luo_session_insert(struct luo_session_header *sh,
1440153094dSPasha Tatashin 			      struct luo_session *session)
1450153094dSPasha Tatashin {
1460153094dSPasha Tatashin 	struct luo_session *it;
1470153094dSPasha Tatashin 
1480153094dSPasha Tatashin 	guard(rwsem_write)(&sh->rwsem);
1490153094dSPasha Tatashin 
1500153094dSPasha Tatashin 	/*
1510153094dSPasha Tatashin 	 * For outgoing we should make sure there is room in serialization array
1520153094dSPasha Tatashin 	 * for new session.
1530153094dSPasha Tatashin 	 */
1540153094dSPasha Tatashin 	if (sh == &luo_session_global.outgoing) {
1550153094dSPasha Tatashin 		if (sh->count == LUO_SESSION_MAX)
1560153094dSPasha Tatashin 			return -ENOMEM;
1570153094dSPasha Tatashin 	}
1580153094dSPasha Tatashin 
1590153094dSPasha Tatashin 	/*
1600153094dSPasha Tatashin 	 * For small number of sessions this loop won't hurt performance
1610153094dSPasha Tatashin 	 * but if we ever start using a lot of sessions, this might
1620153094dSPasha Tatashin 	 * become a bottle neck during deserialization time, as it would
1630153094dSPasha Tatashin 	 * cause O(n*n) complexity.
1640153094dSPasha Tatashin 	 */
1650153094dSPasha Tatashin 	list_for_each_entry(it, &sh->list, list) {
1660153094dSPasha Tatashin 		if (!strncmp(it->name, session->name, sizeof(it->name)))
1670153094dSPasha Tatashin 			return -EEXIST;
1680153094dSPasha Tatashin 	}
1690153094dSPasha Tatashin 	list_add_tail(&session->list, &sh->list);
1700153094dSPasha Tatashin 	sh->count++;
1710153094dSPasha Tatashin 
1720153094dSPasha Tatashin 	return 0;
1730153094dSPasha Tatashin }
1740153094dSPasha Tatashin 
luo_session_remove(struct luo_session_header * sh,struct luo_session * session)1750153094dSPasha Tatashin static void luo_session_remove(struct luo_session_header *sh,
1760153094dSPasha Tatashin 			       struct luo_session *session)
1770153094dSPasha Tatashin {
1780153094dSPasha Tatashin 	guard(rwsem_write)(&sh->rwsem);
1790153094dSPasha Tatashin 	list_del(&session->list);
1800153094dSPasha Tatashin 	sh->count--;
1810153094dSPasha Tatashin }
1820153094dSPasha Tatashin 
luo_session_finish_one(struct luo_session * session)18316cec0d2SPasha Tatashin static int luo_session_finish_one(struct luo_session *session)
18416cec0d2SPasha Tatashin {
18516cec0d2SPasha Tatashin 	guard(mutex)(&session->mutex);
18616cec0d2SPasha Tatashin 	return luo_file_finish(&session->file_set);
18716cec0d2SPasha Tatashin }
18816cec0d2SPasha Tatashin 
luo_session_unfreeze_one(struct luo_session * session,struct luo_session_ser * ser)18916cec0d2SPasha Tatashin static void luo_session_unfreeze_one(struct luo_session *session,
19016cec0d2SPasha Tatashin 				     struct luo_session_ser *ser)
19116cec0d2SPasha Tatashin {
19216cec0d2SPasha Tatashin 	guard(mutex)(&session->mutex);
19316cec0d2SPasha Tatashin 	luo_file_unfreeze(&session->file_set, &ser->file_set_ser);
19416cec0d2SPasha Tatashin }
19516cec0d2SPasha Tatashin 
luo_session_freeze_one(struct luo_session * session,struct luo_session_ser * ser)19616cec0d2SPasha Tatashin static int luo_session_freeze_one(struct luo_session *session,
19716cec0d2SPasha Tatashin 				  struct luo_session_ser *ser)
19816cec0d2SPasha Tatashin {
19916cec0d2SPasha Tatashin 	guard(mutex)(&session->mutex);
20016cec0d2SPasha Tatashin 	return luo_file_freeze(&session->file_set, &ser->file_set_ser);
20116cec0d2SPasha Tatashin }
20216cec0d2SPasha Tatashin 
luo_session_release(struct inode * inodep,struct file * filep)2030153094dSPasha Tatashin static int luo_session_release(struct inode *inodep, struct file *filep)
2040153094dSPasha Tatashin {
2050153094dSPasha Tatashin 	struct luo_session *session = filep->private_data;
2060153094dSPasha Tatashin 	struct luo_session_header *sh;
2070153094dSPasha Tatashin 
2080153094dSPasha Tatashin 	/* If retrieved is set, it means this session is from incoming list */
20916cec0d2SPasha Tatashin 	if (session->retrieved) {
21016cec0d2SPasha Tatashin 		int err = luo_session_finish_one(session);
21116cec0d2SPasha Tatashin 
21216cec0d2SPasha Tatashin 		if (err) {
21316cec0d2SPasha Tatashin 			pr_warn("Unable to finish session [%s] on release\n",
21416cec0d2SPasha Tatashin 				session->name);
21516cec0d2SPasha Tatashin 			return err;
21616cec0d2SPasha Tatashin 		}
2170153094dSPasha Tatashin 		sh = &luo_session_global.incoming;
21816cec0d2SPasha Tatashin 	} else {
21916cec0d2SPasha Tatashin 		scoped_guard(mutex, &session->mutex)
22016cec0d2SPasha Tatashin 			luo_file_unpreserve_files(&session->file_set);
2210153094dSPasha Tatashin 		sh = &luo_session_global.outgoing;
22216cec0d2SPasha Tatashin 	}
2230153094dSPasha Tatashin 
2240153094dSPasha Tatashin 	luo_session_remove(sh, session);
2250153094dSPasha Tatashin 	luo_session_free(session);
2260153094dSPasha Tatashin 
2270153094dSPasha Tatashin 	return 0;
2280153094dSPasha Tatashin }
2290153094dSPasha Tatashin 
luo_session_preserve_fd(struct luo_session * session,struct luo_ucmd * ucmd)23016cec0d2SPasha Tatashin static int luo_session_preserve_fd(struct luo_session *session,
23116cec0d2SPasha Tatashin 				   struct luo_ucmd *ucmd)
23216cec0d2SPasha Tatashin {
23316cec0d2SPasha Tatashin 	struct liveupdate_session_preserve_fd *argp = ucmd->cmd;
23416cec0d2SPasha Tatashin 	int err;
23516cec0d2SPasha Tatashin 
23616cec0d2SPasha Tatashin 	guard(mutex)(&session->mutex);
23716cec0d2SPasha Tatashin 	err = luo_preserve_file(&session->file_set, argp->token, argp->fd);
23816cec0d2SPasha Tatashin 	if (err)
23916cec0d2SPasha Tatashin 		return err;
24016cec0d2SPasha Tatashin 
24116cec0d2SPasha Tatashin 	err = luo_ucmd_respond(ucmd, sizeof(*argp));
24216cec0d2SPasha Tatashin 	if (err)
24316cec0d2SPasha Tatashin 		pr_warn("The file was successfully preserved, but response to user failed\n");
24416cec0d2SPasha Tatashin 
24516cec0d2SPasha Tatashin 	return err;
24616cec0d2SPasha Tatashin }
24716cec0d2SPasha Tatashin 
luo_session_retrieve_fd(struct luo_session * session,struct luo_ucmd * ucmd)24816cec0d2SPasha Tatashin static int luo_session_retrieve_fd(struct luo_session *session,
24916cec0d2SPasha Tatashin 				   struct luo_ucmd *ucmd)
25016cec0d2SPasha Tatashin {
25116cec0d2SPasha Tatashin 	struct liveupdate_session_retrieve_fd *argp = ucmd->cmd;
25216cec0d2SPasha Tatashin 	struct file *file;
25316cec0d2SPasha Tatashin 	int err;
25416cec0d2SPasha Tatashin 
25516cec0d2SPasha Tatashin 	argp->fd = get_unused_fd_flags(O_CLOEXEC);
25616cec0d2SPasha Tatashin 	if (argp->fd < 0)
25716cec0d2SPasha Tatashin 		return argp->fd;
25816cec0d2SPasha Tatashin 
25916cec0d2SPasha Tatashin 	guard(mutex)(&session->mutex);
26016cec0d2SPasha Tatashin 	err = luo_retrieve_file(&session->file_set, argp->token, &file);
26116cec0d2SPasha Tatashin 	if (err < 0)
26216cec0d2SPasha Tatashin 		goto  err_put_fd;
26316cec0d2SPasha Tatashin 
26416cec0d2SPasha Tatashin 	err = luo_ucmd_respond(ucmd, sizeof(*argp));
26516cec0d2SPasha Tatashin 	if (err)
26616cec0d2SPasha Tatashin 		goto err_put_file;
26716cec0d2SPasha Tatashin 
26816cec0d2SPasha Tatashin 	fd_install(argp->fd, file);
26916cec0d2SPasha Tatashin 
27016cec0d2SPasha Tatashin 	return 0;
27116cec0d2SPasha Tatashin 
27216cec0d2SPasha Tatashin err_put_file:
27316cec0d2SPasha Tatashin 	fput(file);
27416cec0d2SPasha Tatashin err_put_fd:
27516cec0d2SPasha Tatashin 	put_unused_fd(argp->fd);
27616cec0d2SPasha Tatashin 
27716cec0d2SPasha Tatashin 	return err;
27816cec0d2SPasha Tatashin }
27916cec0d2SPasha Tatashin 
luo_session_finish(struct luo_session * session,struct luo_ucmd * ucmd)28016cec0d2SPasha Tatashin static int luo_session_finish(struct luo_session *session,
28116cec0d2SPasha Tatashin 			      struct luo_ucmd *ucmd)
28216cec0d2SPasha Tatashin {
28316cec0d2SPasha Tatashin 	struct liveupdate_session_finish *argp = ucmd->cmd;
28416cec0d2SPasha Tatashin 	int err = luo_session_finish_one(session);
28516cec0d2SPasha Tatashin 
28616cec0d2SPasha Tatashin 	if (err)
28716cec0d2SPasha Tatashin 		return err;
28816cec0d2SPasha Tatashin 
28916cec0d2SPasha Tatashin 	return luo_ucmd_respond(ucmd, sizeof(*argp));
29016cec0d2SPasha Tatashin }
29116cec0d2SPasha Tatashin 
29216cec0d2SPasha Tatashin union ucmd_buffer {
29316cec0d2SPasha Tatashin 	struct liveupdate_session_finish finish;
29416cec0d2SPasha Tatashin 	struct liveupdate_session_preserve_fd preserve;
29516cec0d2SPasha Tatashin 	struct liveupdate_session_retrieve_fd retrieve;
29616cec0d2SPasha Tatashin };
29716cec0d2SPasha Tatashin 
29816cec0d2SPasha Tatashin struct luo_ioctl_op {
29916cec0d2SPasha Tatashin 	unsigned int size;
30016cec0d2SPasha Tatashin 	unsigned int min_size;
30116cec0d2SPasha Tatashin 	unsigned int ioctl_num;
30216cec0d2SPasha Tatashin 	int (*execute)(struct luo_session *session, struct luo_ucmd *ucmd);
30316cec0d2SPasha Tatashin };
30416cec0d2SPasha Tatashin 
30516cec0d2SPasha Tatashin #define IOCTL_OP(_ioctl, _fn, _struct, _last)                                  \
30616cec0d2SPasha Tatashin 	[_IOC_NR(_ioctl) - LIVEUPDATE_CMD_SESSION_BASE] = {                    \
30716cec0d2SPasha Tatashin 		.size = sizeof(_struct) +                                      \
30816cec0d2SPasha Tatashin 			BUILD_BUG_ON_ZERO(sizeof(union ucmd_buffer) <          \
30916cec0d2SPasha Tatashin 					  sizeof(_struct)),                    \
31016cec0d2SPasha Tatashin 		.min_size = offsetofend(_struct, _last),                       \
31116cec0d2SPasha Tatashin 		.ioctl_num = _ioctl,                                           \
31216cec0d2SPasha Tatashin 		.execute = _fn,                                                \
31316cec0d2SPasha Tatashin 	}
31416cec0d2SPasha Tatashin 
31516cec0d2SPasha Tatashin static const struct luo_ioctl_op luo_session_ioctl_ops[] = {
31616cec0d2SPasha Tatashin 	IOCTL_OP(LIVEUPDATE_SESSION_FINISH, luo_session_finish,
31716cec0d2SPasha Tatashin 		 struct liveupdate_session_finish, reserved),
31816cec0d2SPasha Tatashin 	IOCTL_OP(LIVEUPDATE_SESSION_PRESERVE_FD, luo_session_preserve_fd,
31916cec0d2SPasha Tatashin 		 struct liveupdate_session_preserve_fd, token),
32016cec0d2SPasha Tatashin 	IOCTL_OP(LIVEUPDATE_SESSION_RETRIEVE_FD, luo_session_retrieve_fd,
32116cec0d2SPasha Tatashin 		 struct liveupdate_session_retrieve_fd, token),
32216cec0d2SPasha Tatashin };
32316cec0d2SPasha Tatashin 
luo_session_ioctl(struct file * filep,unsigned int cmd,unsigned long arg)32416cec0d2SPasha Tatashin static long luo_session_ioctl(struct file *filep, unsigned int cmd,
32516cec0d2SPasha Tatashin 			      unsigned long arg)
32616cec0d2SPasha Tatashin {
32716cec0d2SPasha Tatashin 	struct luo_session *session = filep->private_data;
32816cec0d2SPasha Tatashin 	const struct luo_ioctl_op *op;
32916cec0d2SPasha Tatashin 	struct luo_ucmd ucmd = {};
33016cec0d2SPasha Tatashin 	union ucmd_buffer buf;
33116cec0d2SPasha Tatashin 	unsigned int nr;
33216cec0d2SPasha Tatashin 	int ret;
33316cec0d2SPasha Tatashin 
33416cec0d2SPasha Tatashin 	nr = _IOC_NR(cmd);
33516cec0d2SPasha Tatashin 	if (nr < LIVEUPDATE_CMD_SESSION_BASE || (nr - LIVEUPDATE_CMD_SESSION_BASE) >=
33616cec0d2SPasha Tatashin 	    ARRAY_SIZE(luo_session_ioctl_ops)) {
33716cec0d2SPasha Tatashin 		return -EINVAL;
33816cec0d2SPasha Tatashin 	}
33916cec0d2SPasha Tatashin 
34016cec0d2SPasha Tatashin 	ucmd.ubuffer = (void __user *)arg;
34116cec0d2SPasha Tatashin 	ret = get_user(ucmd.user_size, (u32 __user *)ucmd.ubuffer);
34216cec0d2SPasha Tatashin 	if (ret)
34316cec0d2SPasha Tatashin 		return ret;
34416cec0d2SPasha Tatashin 
34516cec0d2SPasha Tatashin 	op = &luo_session_ioctl_ops[nr - LIVEUPDATE_CMD_SESSION_BASE];
34616cec0d2SPasha Tatashin 	if (op->ioctl_num != cmd)
34716cec0d2SPasha Tatashin 		return -ENOIOCTLCMD;
34816cec0d2SPasha Tatashin 	if (ucmd.user_size < op->min_size)
34916cec0d2SPasha Tatashin 		return -EINVAL;
35016cec0d2SPasha Tatashin 
35116cec0d2SPasha Tatashin 	ucmd.cmd = &buf;
35216cec0d2SPasha Tatashin 	ret = copy_struct_from_user(ucmd.cmd, op->size, ucmd.ubuffer,
35316cec0d2SPasha Tatashin 				    ucmd.user_size);
35416cec0d2SPasha Tatashin 	if (ret)
35516cec0d2SPasha Tatashin 		return ret;
35616cec0d2SPasha Tatashin 
35716cec0d2SPasha Tatashin 	return op->execute(session, &ucmd);
35816cec0d2SPasha Tatashin }
35916cec0d2SPasha Tatashin 
3600153094dSPasha Tatashin static const struct file_operations luo_session_fops = {
3610153094dSPasha Tatashin 	.owner = THIS_MODULE,
3620153094dSPasha Tatashin 	.release = luo_session_release,
36316cec0d2SPasha Tatashin 	.unlocked_ioctl = luo_session_ioctl,
3640153094dSPasha Tatashin };
3650153094dSPasha Tatashin 
3660153094dSPasha Tatashin /* Create a "struct file" for session */
luo_session_getfile(struct luo_session * session,struct file ** filep)3670153094dSPasha Tatashin static int luo_session_getfile(struct luo_session *session, struct file **filep)
3680153094dSPasha Tatashin {
3690153094dSPasha Tatashin 	char name_buf[128];
3700153094dSPasha Tatashin 	struct file *file;
3710153094dSPasha Tatashin 
3720153094dSPasha Tatashin 	lockdep_assert_held(&session->mutex);
3730153094dSPasha Tatashin 	snprintf(name_buf, sizeof(name_buf), "[luo_session] %s", session->name);
3740153094dSPasha Tatashin 	file = anon_inode_getfile(name_buf, &luo_session_fops, session, O_RDWR);
3750153094dSPasha Tatashin 	if (IS_ERR(file))
3760153094dSPasha Tatashin 		return PTR_ERR(file);
3770153094dSPasha Tatashin 
3780153094dSPasha Tatashin 	*filep = file;
3790153094dSPasha Tatashin 
3800153094dSPasha Tatashin 	return 0;
3810153094dSPasha Tatashin }
3820153094dSPasha Tatashin 
luo_session_create(const char * name,struct file ** filep)3830153094dSPasha Tatashin int luo_session_create(const char *name, struct file **filep)
3840153094dSPasha Tatashin {
3850153094dSPasha Tatashin 	struct luo_session *session;
3860153094dSPasha Tatashin 	int err;
3870153094dSPasha Tatashin 
3880153094dSPasha Tatashin 	session = luo_session_alloc(name);
3890153094dSPasha Tatashin 	if (IS_ERR(session))
3900153094dSPasha Tatashin 		return PTR_ERR(session);
3910153094dSPasha Tatashin 
3920153094dSPasha Tatashin 	err = luo_session_insert(&luo_session_global.outgoing, session);
3930153094dSPasha Tatashin 	if (err)
3940153094dSPasha Tatashin 		goto err_free;
3950153094dSPasha Tatashin 
3960153094dSPasha Tatashin 	scoped_guard(mutex, &session->mutex)
3970153094dSPasha Tatashin 		err = luo_session_getfile(session, filep);
3980153094dSPasha Tatashin 	if (err)
3990153094dSPasha Tatashin 		goto err_remove;
4000153094dSPasha Tatashin 
4010153094dSPasha Tatashin 	return 0;
4020153094dSPasha Tatashin 
4030153094dSPasha Tatashin err_remove:
4040153094dSPasha Tatashin 	luo_session_remove(&luo_session_global.outgoing, session);
4050153094dSPasha Tatashin err_free:
4060153094dSPasha Tatashin 	luo_session_free(session);
4070153094dSPasha Tatashin 
4080153094dSPasha Tatashin 	return err;
4090153094dSPasha Tatashin }
4100153094dSPasha Tatashin 
luo_session_retrieve(const char * name,struct file ** filep)4110153094dSPasha Tatashin int luo_session_retrieve(const char *name, struct file **filep)
4120153094dSPasha Tatashin {
4130153094dSPasha Tatashin 	struct luo_session_header *sh = &luo_session_global.incoming;
4140153094dSPasha Tatashin 	struct luo_session *session = NULL;
4150153094dSPasha Tatashin 	struct luo_session *it;
4160153094dSPasha Tatashin 	int err;
4170153094dSPasha Tatashin 
4180153094dSPasha Tatashin 	scoped_guard(rwsem_read, &sh->rwsem) {
4190153094dSPasha Tatashin 		list_for_each_entry(it, &sh->list, list) {
4200153094dSPasha Tatashin 			if (!strncmp(it->name, name, sizeof(it->name))) {
4210153094dSPasha Tatashin 				session = it;
4220153094dSPasha Tatashin 				break;
4230153094dSPasha Tatashin 			}
4240153094dSPasha Tatashin 		}
4250153094dSPasha Tatashin 	}
4260153094dSPasha Tatashin 
4270153094dSPasha Tatashin 	if (!session)
4280153094dSPasha Tatashin 		return -ENOENT;
4290153094dSPasha Tatashin 
4300153094dSPasha Tatashin 	guard(mutex)(&session->mutex);
4310153094dSPasha Tatashin 	if (session->retrieved)
4320153094dSPasha Tatashin 		return -EINVAL;
4330153094dSPasha Tatashin 
4340153094dSPasha Tatashin 	err = luo_session_getfile(session, filep);
4350153094dSPasha Tatashin 	if (!err)
4360153094dSPasha Tatashin 		session->retrieved = true;
4370153094dSPasha Tatashin 
4380153094dSPasha Tatashin 	return err;
4390153094dSPasha Tatashin }
4400153094dSPasha Tatashin 
luo_session_setup_outgoing(void * fdt_out)4410153094dSPasha Tatashin int __init luo_session_setup_outgoing(void *fdt_out)
4420153094dSPasha Tatashin {
4430153094dSPasha Tatashin 	struct luo_session_header_ser *header_ser;
4440153094dSPasha Tatashin 	u64 header_ser_pa;
4450153094dSPasha Tatashin 	int err;
4460153094dSPasha Tatashin 
4470153094dSPasha Tatashin 	header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
4480153094dSPasha Tatashin 	if (IS_ERR(header_ser))
4490153094dSPasha Tatashin 		return PTR_ERR(header_ser);
4500153094dSPasha Tatashin 	header_ser_pa = virt_to_phys(header_ser);
4510153094dSPasha Tatashin 
4520153094dSPasha Tatashin 	err = fdt_begin_node(fdt_out, LUO_FDT_SESSION_NODE_NAME);
4530153094dSPasha Tatashin 	err |= fdt_property_string(fdt_out, "compatible",
4540153094dSPasha Tatashin 				   LUO_FDT_SESSION_COMPATIBLE);
4550153094dSPasha Tatashin 	err |= fdt_property(fdt_out, LUO_FDT_SESSION_HEADER, &header_ser_pa,
4560153094dSPasha Tatashin 			    sizeof(header_ser_pa));
4570153094dSPasha Tatashin 	err |= fdt_end_node(fdt_out);
4580153094dSPasha Tatashin 
4590153094dSPasha Tatashin 	if (err)
4600153094dSPasha Tatashin 		goto err_unpreserve;
4610153094dSPasha Tatashin 
4620153094dSPasha Tatashin 	luo_session_global.outgoing.header_ser = header_ser;
4630153094dSPasha Tatashin 	luo_session_global.outgoing.ser = (void *)(header_ser + 1);
4640153094dSPasha Tatashin 	luo_session_global.outgoing.active = true;
4650153094dSPasha Tatashin 
4660153094dSPasha Tatashin 	return 0;
4670153094dSPasha Tatashin 
4680153094dSPasha Tatashin err_unpreserve:
4690153094dSPasha Tatashin 	kho_unpreserve_free(header_ser);
4700153094dSPasha Tatashin 	return err;
4710153094dSPasha Tatashin }
4720153094dSPasha Tatashin 
luo_session_setup_incoming(void * fdt_in)4730153094dSPasha Tatashin int __init luo_session_setup_incoming(void *fdt_in)
4740153094dSPasha Tatashin {
4750153094dSPasha Tatashin 	struct luo_session_header_ser *header_ser;
4760153094dSPasha Tatashin 	int err, header_size, offset;
4770153094dSPasha Tatashin 	u64 header_ser_pa;
4780153094dSPasha Tatashin 	const void *ptr;
4790153094dSPasha Tatashin 
4800153094dSPasha Tatashin 	offset = fdt_subnode_offset(fdt_in, 0, LUO_FDT_SESSION_NODE_NAME);
4810153094dSPasha Tatashin 	if (offset < 0) {
4820153094dSPasha Tatashin 		pr_err("Unable to get session node: [%s]\n",
4830153094dSPasha Tatashin 		       LUO_FDT_SESSION_NODE_NAME);
4840153094dSPasha Tatashin 		return -EINVAL;
4850153094dSPasha Tatashin 	}
4860153094dSPasha Tatashin 
4870153094dSPasha Tatashin 	err = fdt_node_check_compatible(fdt_in, offset,
4880153094dSPasha Tatashin 					LUO_FDT_SESSION_COMPATIBLE);
4890153094dSPasha Tatashin 	if (err) {
4900153094dSPasha Tatashin 		pr_err("Session node incompatible [%s]\n",
4910153094dSPasha Tatashin 		       LUO_FDT_SESSION_COMPATIBLE);
4920153094dSPasha Tatashin 		return -EINVAL;
4930153094dSPasha Tatashin 	}
4940153094dSPasha Tatashin 
4950153094dSPasha Tatashin 	header_size = 0;
4960153094dSPasha Tatashin 	ptr = fdt_getprop(fdt_in, offset, LUO_FDT_SESSION_HEADER, &header_size);
4970153094dSPasha Tatashin 	if (!ptr || header_size != sizeof(u64)) {
4980153094dSPasha Tatashin 		pr_err("Unable to get session header '%s' [%d]\n",
4990153094dSPasha Tatashin 		       LUO_FDT_SESSION_HEADER, header_size);
5000153094dSPasha Tatashin 		return -EINVAL;
5010153094dSPasha Tatashin 	}
5020153094dSPasha Tatashin 
5030153094dSPasha Tatashin 	header_ser_pa = get_unaligned((u64 *)ptr);
5040153094dSPasha Tatashin 	header_ser = phys_to_virt(header_ser_pa);
5050153094dSPasha Tatashin 
5060153094dSPasha Tatashin 	luo_session_global.incoming.header_ser = header_ser;
5070153094dSPasha Tatashin 	luo_session_global.incoming.ser = (void *)(header_ser + 1);
5080153094dSPasha Tatashin 	luo_session_global.incoming.active = true;
5090153094dSPasha Tatashin 
5100153094dSPasha Tatashin 	return 0;
5110153094dSPasha Tatashin }
5120153094dSPasha Tatashin 
luo_session_deserialize(void)5130153094dSPasha Tatashin int luo_session_deserialize(void)
5140153094dSPasha Tatashin {
5150153094dSPasha Tatashin 	struct luo_session_header *sh = &luo_session_global.incoming;
5160153094dSPasha Tatashin 	static bool is_deserialized;
5170153094dSPasha Tatashin 	static int err;
5180153094dSPasha Tatashin 
5190153094dSPasha Tatashin 	/* If has been deserialized, always return the same error code */
5200153094dSPasha Tatashin 	if (is_deserialized)
5210153094dSPasha Tatashin 		return err;
5220153094dSPasha Tatashin 
5230153094dSPasha Tatashin 	is_deserialized = true;
5240153094dSPasha Tatashin 	if (!sh->active)
5250153094dSPasha Tatashin 		return 0;
5260153094dSPasha Tatashin 
5270153094dSPasha Tatashin 	/*
5280153094dSPasha Tatashin 	 * Note on error handling:
5290153094dSPasha Tatashin 	 *
5300153094dSPasha Tatashin 	 * If deserialization fails (e.g., allocation failure or corrupt data),
5310153094dSPasha Tatashin 	 * we intentionally skip cleanup of sessions that were already restored.
5320153094dSPasha Tatashin 	 *
5330153094dSPasha Tatashin 	 * A partial failure leaves the preserved state inconsistent.
5340153094dSPasha Tatashin 	 * Implementing a safe "undo" to unwind complex dependencies (sessions,
5350153094dSPasha Tatashin 	 * files, hardware state) is error-prone and provides little value, as
5360153094dSPasha Tatashin 	 * the system is effectively in a broken state.
5370153094dSPasha Tatashin 	 *
5380153094dSPasha Tatashin 	 * We treat these resources as leaked. The expected recovery path is for
5390153094dSPasha Tatashin 	 * userspace to detect the failure and trigger a reboot, which will
5400153094dSPasha Tatashin 	 * reliably reset devices and reclaim memory.
5410153094dSPasha Tatashin 	 */
5420153094dSPasha Tatashin 	for (int i = 0; i < sh->header_ser->count; i++) {
5430153094dSPasha Tatashin 		struct luo_session *session;
5440153094dSPasha Tatashin 
5450153094dSPasha Tatashin 		session = luo_session_alloc(sh->ser[i].name);
5460153094dSPasha Tatashin 		if (IS_ERR(session)) {
5470153094dSPasha Tatashin 			pr_warn("Failed to allocate session [%s] during deserialization %pe\n",
5480153094dSPasha Tatashin 				sh->ser[i].name, session);
5490153094dSPasha Tatashin 			return PTR_ERR(session);
5500153094dSPasha Tatashin 		}
5510153094dSPasha Tatashin 
5520153094dSPasha Tatashin 		err = luo_session_insert(sh, session);
5530153094dSPasha Tatashin 		if (err) {
5540153094dSPasha Tatashin 			pr_warn("Failed to insert session [%s] %pe\n",
5550153094dSPasha Tatashin 				session->name, ERR_PTR(err));
5560153094dSPasha Tatashin 			luo_session_free(session);
5570153094dSPasha Tatashin 			return err;
5580153094dSPasha Tatashin 		}
55916cec0d2SPasha Tatashin 
56016cec0d2SPasha Tatashin 		scoped_guard(mutex, &session->mutex) {
56116cec0d2SPasha Tatashin 			luo_file_deserialize(&session->file_set,
56216cec0d2SPasha Tatashin 					     &sh->ser[i].file_set_ser);
56316cec0d2SPasha Tatashin 		}
5640153094dSPasha Tatashin 	}
5650153094dSPasha Tatashin 
5660153094dSPasha Tatashin 	kho_restore_free(sh->header_ser);
5670153094dSPasha Tatashin 	sh->header_ser = NULL;
5680153094dSPasha Tatashin 	sh->ser = NULL;
5690153094dSPasha Tatashin 
5700153094dSPasha Tatashin 	return 0;
5710153094dSPasha Tatashin }
5720153094dSPasha Tatashin 
luo_session_serialize(void)5730153094dSPasha Tatashin int luo_session_serialize(void)
5740153094dSPasha Tatashin {
5750153094dSPasha Tatashin 	struct luo_session_header *sh = &luo_session_global.outgoing;
5760153094dSPasha Tatashin 	struct luo_session *session;
5770153094dSPasha Tatashin 	int i = 0;
57816cec0d2SPasha Tatashin 	int err;
5790153094dSPasha Tatashin 
5800153094dSPasha Tatashin 	guard(rwsem_write)(&sh->rwsem);
5810153094dSPasha Tatashin 	list_for_each_entry(session, &sh->list, list) {
58216cec0d2SPasha Tatashin 		err = luo_session_freeze_one(session, &sh->ser[i]);
58316cec0d2SPasha Tatashin 		if (err)
58416cec0d2SPasha Tatashin 			goto err_undo;
58516cec0d2SPasha Tatashin 
5860153094dSPasha Tatashin 		strscpy(sh->ser[i].name, session->name,
5870153094dSPasha Tatashin 			sizeof(sh->ser[i].name));
5880153094dSPasha Tatashin 		i++;
5890153094dSPasha Tatashin 	}
5900153094dSPasha Tatashin 	sh->header_ser->count = sh->count;
5910153094dSPasha Tatashin 
5920153094dSPasha Tatashin 	return 0;
59316cec0d2SPasha Tatashin 
59416cec0d2SPasha Tatashin err_undo:
59516cec0d2SPasha Tatashin 	list_for_each_entry_continue_reverse(session, &sh->list, list) {
59616cec0d2SPasha Tatashin 		i--;
59716cec0d2SPasha Tatashin 		luo_session_unfreeze_one(session, &sh->ser[i]);
59816cec0d2SPasha Tatashin 		memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name));
59916cec0d2SPasha Tatashin 	}
60016cec0d2SPasha Tatashin 
60116cec0d2SPasha Tatashin 	return err;
6020153094dSPasha Tatashin }
6030153094dSPasha Tatashin 
6040153094dSPasha Tatashin /**
6050153094dSPasha Tatashin  * luo_session_quiesce - Ensure no active sessions exist and lock session lists.
6060153094dSPasha Tatashin  *
6070153094dSPasha Tatashin  * Acquires exclusive write locks on both incoming and outgoing session lists.
6080153094dSPasha Tatashin  * It then validates no sessions exist in either list.
6090153094dSPasha Tatashin  *
6100153094dSPasha Tatashin  * This mechanism is used during file handler un/registration to ensure that no
6110153094dSPasha Tatashin  * sessions are currently using the handler, and no new sessions can be created
6120153094dSPasha Tatashin  * while un/registration is in progress.
6130153094dSPasha Tatashin  *
6140153094dSPasha Tatashin  * This prevents registering new handlers while sessions are active or
6150153094dSPasha Tatashin  * while deserialization is in progress.
6160153094dSPasha Tatashin  *
6170153094dSPasha Tatashin  * Return:
6180153094dSPasha Tatashin  * true  - System is quiescent (0 sessions) and locked.
6190153094dSPasha Tatashin  * false - Active sessions exist. The locks are released internally.
6200153094dSPasha Tatashin  */
luo_session_quiesce(void)6210153094dSPasha Tatashin bool luo_session_quiesce(void)
6220153094dSPasha Tatashin {
6230153094dSPasha Tatashin 	down_write(&luo_session_global.incoming.rwsem);
6240153094dSPasha Tatashin 	down_write(&luo_session_global.outgoing.rwsem);
6250153094dSPasha Tatashin 
6260153094dSPasha Tatashin 	if (luo_session_global.incoming.count ||
6270153094dSPasha Tatashin 	    luo_session_global.outgoing.count) {
6280153094dSPasha Tatashin 		up_write(&luo_session_global.outgoing.rwsem);
6290153094dSPasha Tatashin 		up_write(&luo_session_global.incoming.rwsem);
6300153094dSPasha Tatashin 		return false;
6310153094dSPasha Tatashin 	}
6320153094dSPasha Tatashin 
6330153094dSPasha Tatashin 	return true;
6340153094dSPasha Tatashin }
6350153094dSPasha Tatashin 
6360153094dSPasha Tatashin /**
6370153094dSPasha Tatashin  * luo_session_resume - Unlock session lists and resume normal activity.
6380153094dSPasha Tatashin  *
6390153094dSPasha Tatashin  * Releases the exclusive locks acquired by a successful call to
6400153094dSPasha Tatashin  * luo_session_quiesce().
6410153094dSPasha Tatashin  */
luo_session_resume(void)6420153094dSPasha Tatashin void luo_session_resume(void)
6430153094dSPasha Tatashin {
6440153094dSPasha Tatashin 	up_write(&luo_session_global.outgoing.rwsem);
6450153094dSPasha Tatashin 	up_write(&luo_session_global.incoming.rwsem);
6460153094dSPasha Tatashin }
647