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