1 // SPDX-License-Identifier: GPL-2.0 2 3 /* 4 * Copyright (c) 2025, Google LLC. 5 * Pasha Tatashin <pasha.tatashin@soleen.com> 6 */ 7 8 /** 9 * DOC: LUO Sessions 10 * 11 * LUO Sessions provide the core mechanism for grouping and managing `struct 12 * file *` instances that need to be preserved across a kexec-based live 13 * update. Each session acts as a named container for a set of file objects, 14 * allowing a userspace agent to manage the lifecycle of resources critical to a 15 * workload. 16 * 17 * Core Concepts: 18 * 19 * - Named Containers: Sessions are identified by a unique, user-provided name, 20 * which is used for both creation in the current kernel and retrieval in the 21 * next kernel. 22 * 23 * - Userspace Interface: Session management is driven from userspace via 24 * ioctls on /dev/liveupdate. 25 * 26 * - Serialization: Session metadata is preserved using the KHO framework. When 27 * a live update is triggered via kexec, session metadata is serialized into 28 * a chain of linked-blocks and placed in a preserved memory region. The 29 * physical address of the first block header is stored in the centralized 30 * `struct luo_ser` structure. 31 * 32 * Session Lifecycle: 33 * 34 * 1. Creation: A userspace agent calls `luo_session_create()` to create a 35 * new, empty session and receives a file descriptor for it. 36 * 37 * 2. Serialization: When the `reboot(LINUX_REBOOT_CMD_KEXEC)` syscall is 38 * made, `luo_session_serialize()` is called. It iterates through all 39 * active sessions and writes their metadata into a memory area preserved 40 * by KHO. 41 * 42 * 3. Deserialization (in new kernel): After kexec, `luo_session_deserialize()` 43 * runs, reading the serialized data and creating a list of `struct 44 * luo_session` objects representing the preserved sessions. 45 * 46 * 4. Retrieval: A userspace agent in the new kernel can then call 47 * `luo_session_retrieve()` with a session name to get a new file 48 * descriptor and access the preserved state. 49 * 50 * Locking: 51 * 52 * The LUO session subsystem uses a three-tier locking hierarchy to ensure thread 53 * safety and prevent deadlocks during concurrent session mutations and kexec 54 * serialization: 55 * 56 * 1. `luo_session_serialize_rwsem` (global rwsem): 57 * Protects session mutations (creation, retrieval, release, and ioctls) 58 * against the serialization process during reboot. 59 * 60 * - Readers: Taken by any path modifying or accessing session state (e.g., 61 * `luo_session_create()`, `luo_session_retrieve()`, `luo_session_release()`, 62 * and `luo_session_ioctl()`). 63 * - Writer: Taken by the serialization process (`luo_session_serialize()`) 64 * during reboot. On success, the write lock is held indefinitely to freeze 65 * the subsystem. On failure, it is released to allow recovery. 66 * 67 * 2. `luo_session_header->rwsem` (per-list rwsem): 68 * Synchronizes list-level operations for the incoming and outgoing session headers. 69 * 70 * - Writer: Taken during list mutation operations (inserting or removing a 71 * session from the list). 72 * - Reader: Taken when traversing the list (e.g., retrieving a session by name). 73 * 74 * 3. `luo_session->mutex` (per-session mutex): 75 * Protects the internal state and file sets of an individual session. It is 76 * acquired during per-session operations such as preserving, retrieving, 77 * or freezing files. 78 * 79 * Lock Hierarchy: 80 * `luo_session_serialize_rwsem` -> `luo_session_header->rwsem` -> `luo_session->mutex` 81 */ 82 83 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 84 85 #include <linux/anon_inodes.h> 86 #include <linux/cleanup.h> 87 #include <linux/err.h> 88 #include <linux/errno.h> 89 #include <linux/file.h> 90 #include <linux/fs.h> 91 #include <linux/io.h> 92 #include <linux/kexec_handover.h> 93 #include <linux/kho_block.h> 94 #include <linux/kho/abi/luo.h> 95 #include <linux/list.h> 96 #include <linux/liveupdate.h> 97 #include <linux/mutex.h> 98 #include <linux/rwsem.h> 99 #include <linux/slab.h> 100 #include <uapi/linux/liveupdate.h> 101 #include "luo_internal.h" 102 103 static DECLARE_RWSEM(luo_session_serialize_rwsem); 104 /** 105 * struct luo_session_header - Header struct for managing LUO sessions. 106 * @count: The number of sessions currently tracked in the @list. 107 * @list: The head of the linked list of `struct luo_session` instances. 108 * @rwsem: A read-write semaphore providing synchronized access to the 109 * session list and other fields in this structure. 110 * @block_set: The set of serialization blocks. 111 * @sessions_pa: Points to the location of sessions_pa within struct luo_ser. 112 * @active: Set to true when first initialized. If previous kernel did not 113 * send session data, active stays false for incoming. 114 */ 115 struct luo_session_header { 116 long count; 117 struct list_head list; 118 struct rw_semaphore rwsem; 119 struct kho_block_set block_set; 120 u64 *sessions_pa; 121 bool active; 122 }; 123 124 /** 125 * struct luo_session_global - Global container for managing LUO sessions. 126 * @incoming: The sessions passed from the previous kernel. 127 * @outgoing: The sessions that are going to be passed to the next kernel. 128 */ 129 struct luo_session_global { 130 struct luo_session_header incoming; 131 struct luo_session_header outgoing; 132 }; 133 134 static struct luo_session_global luo_session_global = { 135 .incoming = { 136 .list = LIST_HEAD_INIT(luo_session_global.incoming.list), 137 .rwsem = __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem), 138 .block_set = KHO_BLOCK_SET_INIT(luo_session_global.incoming.block_set, 139 sizeof(struct luo_session_ser)), 140 }, 141 .outgoing = { 142 .list = LIST_HEAD_INIT(luo_session_global.outgoing.list), 143 .rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem), 144 .block_set = KHO_BLOCK_SET_INIT(luo_session_global.outgoing.block_set, 145 sizeof(struct luo_session_ser)), 146 }, 147 }; 148 149 static struct luo_session *luo_session_alloc(const char *name) 150 { 151 struct luo_session *session = kzalloc_obj(*session); 152 153 if (!session) 154 return ERR_PTR(-ENOMEM); 155 156 strscpy(session->name, name, sizeof(session->name)); 157 luo_file_set_init(&session->file_set); 158 INIT_LIST_HEAD(&session->list); 159 mutex_init(&session->mutex); 160 161 return session; 162 } 163 164 static void luo_session_free(struct luo_session *session) 165 { 166 luo_file_set_destroy(&session->file_set); 167 mutex_destroy(&session->mutex); 168 kfree(session); 169 } 170 171 static int luo_session_insert(struct luo_session_header *sh, 172 struct luo_session *session) 173 { 174 struct luo_session *it; 175 int err; 176 177 guard(rwsem_write)(&sh->rwsem); 178 179 /* 180 * For outgoing we should make sure there is room in serialization array 181 * for new session. 182 */ 183 if (sh == &luo_session_global.outgoing) { 184 err = kho_block_set_grow(&sh->block_set, sh->count + 1); 185 if (err) 186 return err; 187 } 188 189 /* 190 * For small number of sessions this loop won't hurt performance 191 * but if we ever start using a lot of sessions, this might 192 * become a bottle neck during deserialization time, as it would 193 * cause O(n*n) complexity. 194 */ 195 list_for_each_entry(it, &sh->list, list) { 196 if (!strncmp(it->name, session->name, sizeof(it->name))) 197 return -EEXIST; 198 } 199 list_add_tail(&session->list, &sh->list); 200 sh->count++; 201 202 return 0; 203 } 204 205 static void luo_session_remove(struct luo_session_header *sh, 206 struct luo_session *session) 207 { 208 guard(rwsem_write)(&sh->rwsem); 209 list_del(&session->list); 210 sh->count--; 211 if (sh == &luo_session_global.outgoing) 212 kho_block_set_shrink(&sh->block_set, sh->count); 213 } 214 215 static int luo_session_finish_one(struct luo_session *session) 216 { 217 guard(mutex)(&session->mutex); 218 return luo_file_finish(&session->file_set); 219 } 220 221 static void luo_session_unfreeze_one(struct luo_session *session, 222 struct luo_session_ser *ser) 223 { 224 guard(mutex)(&session->mutex); 225 luo_file_unfreeze(&session->file_set, &ser->file_set_ser); 226 } 227 228 static int luo_session_freeze_one(struct luo_session *session, 229 struct luo_session_ser *ser) 230 { 231 guard(mutex)(&session->mutex); 232 return luo_file_freeze(&session->file_set, &ser->file_set_ser); 233 } 234 235 static int luo_session_release(struct inode *inodep, struct file *filep) 236 { 237 struct luo_session *session = filep->private_data; 238 struct luo_session_header *sh; 239 240 guard(rwsem_read)(&luo_session_serialize_rwsem); 241 /* If retrieved is set, it means this session is from incoming list */ 242 if (session->retrieved) { 243 int err = luo_session_finish_one(session); 244 245 if (err) { 246 pr_warn("Unable to finish session [%s] on release\n", 247 session->name); 248 return err; 249 } 250 sh = &luo_session_global.incoming; 251 } else { 252 scoped_guard(mutex, &session->mutex) 253 luo_file_unpreserve_files(&session->file_set); 254 sh = &luo_session_global.outgoing; 255 } 256 257 luo_session_remove(sh, session); 258 luo_session_free(session); 259 260 return 0; 261 } 262 263 static int luo_session_preserve_fd(struct luo_session *session, 264 struct luo_ucmd *ucmd) 265 { 266 struct liveupdate_session_preserve_fd *argp = ucmd->cmd; 267 int err; 268 269 guard(mutex)(&session->mutex); 270 err = luo_preserve_file(&session->file_set, argp->token, argp->fd); 271 if (err) 272 return err; 273 274 err = luo_ucmd_respond(ucmd, sizeof(*argp)); 275 if (err) 276 pr_warn("The file was successfully preserved, but response to user failed\n"); 277 278 return err; 279 } 280 281 static int luo_session_retrieve_fd(struct luo_session *session, 282 struct luo_ucmd *ucmd) 283 { 284 struct liveupdate_session_retrieve_fd *argp = ucmd->cmd; 285 struct file *file; 286 int err; 287 288 argp->fd = get_unused_fd_flags(O_CLOEXEC); 289 if (argp->fd < 0) 290 return argp->fd; 291 292 mutex_lock(&session->mutex); 293 err = luo_retrieve_file(&session->file_set, argp->token, &file); 294 mutex_unlock(&session->mutex); 295 if (err < 0) 296 goto err_put_fd; 297 298 err = luo_ucmd_respond(ucmd, sizeof(*argp)); 299 if (err) 300 goto err_put_file; 301 302 fd_install(argp->fd, file); 303 304 return 0; 305 306 err_put_file: 307 fput(file); 308 err_put_fd: 309 put_unused_fd(argp->fd); 310 311 return err; 312 } 313 314 static int luo_session_finish(struct luo_session *session, 315 struct luo_ucmd *ucmd) 316 { 317 struct liveupdate_session_finish *argp = ucmd->cmd; 318 int err; 319 320 if (argp->reserved) 321 return -EINVAL; 322 323 err = luo_session_finish_one(session); 324 if (err) 325 return err; 326 327 return luo_ucmd_respond(ucmd, sizeof(*argp)); 328 } 329 330 static int luo_session_get_name(struct luo_session *session, 331 struct luo_ucmd *ucmd) 332 { 333 struct liveupdate_session_get_name *argp = ucmd->cmd; 334 335 if (argp->reserved != 0) 336 return -EINVAL; 337 338 strscpy((char *)argp->name, session->name, sizeof(argp->name)); 339 340 return luo_ucmd_respond(ucmd, sizeof(*argp)); 341 } 342 343 union ucmd_buffer { 344 struct liveupdate_session_finish finish; 345 struct liveupdate_session_preserve_fd preserve; 346 struct liveupdate_session_retrieve_fd retrieve; 347 struct liveupdate_session_get_name get_name; 348 }; 349 350 /* Type of sessions the ioctl applies to. */ 351 enum luo_ioctl_type { 352 LUO_IOCTL_INCOMING, 353 LUO_IOCTL_OUTGOING, 354 LUO_IOCTL_ALL, 355 }; 356 357 struct luo_ioctl_op { 358 unsigned int size; 359 unsigned int min_size; 360 unsigned int ioctl_num; 361 enum luo_ioctl_type type; 362 int (*execute)(struct luo_session *session, struct luo_ucmd *ucmd); 363 }; 364 365 #define IOCTL_OP(_ioctl, _fn, _struct, _last, _type) \ 366 [_IOC_NR(_ioctl) - LIVEUPDATE_CMD_SESSION_BASE] = { \ 367 .size = sizeof(_struct) + \ 368 BUILD_BUG_ON_ZERO(sizeof(union ucmd_buffer) < \ 369 sizeof(_struct)), \ 370 .min_size = offsetofend(_struct, _last), \ 371 .ioctl_num = _ioctl, \ 372 .type = _type, \ 373 .execute = _fn, \ 374 } 375 376 static const struct luo_ioctl_op luo_session_ioctl_ops[] = { 377 IOCTL_OP(LIVEUPDATE_SESSION_FINISH, luo_session_finish, 378 struct liveupdate_session_finish, reserved, LUO_IOCTL_INCOMING), 379 IOCTL_OP(LIVEUPDATE_SESSION_PRESERVE_FD, luo_session_preserve_fd, 380 struct liveupdate_session_preserve_fd, token, LUO_IOCTL_OUTGOING), 381 IOCTL_OP(LIVEUPDATE_SESSION_RETRIEVE_FD, luo_session_retrieve_fd, 382 struct liveupdate_session_retrieve_fd, token, LUO_IOCTL_INCOMING), 383 IOCTL_OP(LIVEUPDATE_SESSION_GET_NAME, luo_session_get_name, 384 struct liveupdate_session_get_name, name, LUO_IOCTL_ALL), 385 }; 386 387 static bool luo_ioctl_type_valid(struct luo_session *session, 388 const struct luo_ioctl_op *op) 389 { 390 switch (op->type) { 391 case LUO_IOCTL_INCOMING: 392 /* Retrieved is only set on incoming sessions */ 393 return session->retrieved; 394 case LUO_IOCTL_OUTGOING: 395 return !session->retrieved; 396 case LUO_IOCTL_ALL: 397 return true; 398 } 399 400 /* Catch-all. */ 401 return false; 402 } 403 404 static long luo_session_ioctl(struct file *filep, unsigned int cmd, 405 unsigned long arg) 406 { 407 struct luo_session *session = filep->private_data; 408 const struct luo_ioctl_op *op; 409 struct luo_ucmd ucmd = {}; 410 union ucmd_buffer buf; 411 unsigned int nr; 412 int ret; 413 414 nr = _IOC_NR(cmd); 415 if (nr < LIVEUPDATE_CMD_SESSION_BASE || (nr - LIVEUPDATE_CMD_SESSION_BASE) >= 416 ARRAY_SIZE(luo_session_ioctl_ops)) { 417 return -EINVAL; 418 } 419 420 ucmd.ubuffer = (void __user *)arg; 421 ret = get_user(ucmd.user_size, (u32 __user *)ucmd.ubuffer); 422 if (ret) 423 return ret; 424 425 op = &luo_session_ioctl_ops[nr - LIVEUPDATE_CMD_SESSION_BASE]; 426 if (op->ioctl_num != cmd) 427 return -ENOIOCTLCMD; 428 if (!luo_ioctl_type_valid(session, op)) 429 return -EINVAL; 430 if (ucmd.user_size < op->min_size) 431 return -EINVAL; 432 433 ucmd.cmd = &buf; 434 ret = copy_struct_from_user(ucmd.cmd, op->size, ucmd.ubuffer, 435 ucmd.user_size); 436 if (ret) 437 return ret; 438 439 guard(rwsem_read)(&luo_session_serialize_rwsem); 440 return op->execute(session, &ucmd); 441 } 442 443 static const struct file_operations luo_session_fops = { 444 .owner = THIS_MODULE, 445 .release = luo_session_release, 446 .unlocked_ioctl = luo_session_ioctl, 447 }; 448 449 /* Create a "struct file" for session */ 450 static int luo_session_getfile(struct luo_session *session, struct file **filep) 451 { 452 char name_buf[128]; 453 struct file *file; 454 455 lockdep_assert_held(&session->mutex); 456 snprintf(name_buf, sizeof(name_buf), "[luo_session] %s", session->name); 457 file = anon_inode_getfile(name_buf, &luo_session_fops, session, O_RDWR); 458 if (IS_ERR(file)) 459 return PTR_ERR(file); 460 461 *filep = file; 462 463 return 0; 464 } 465 466 int luo_session_create(const char *name, struct file **filep) 467 { 468 size_t len = strnlen(name, LIVEUPDATE_SESSION_NAME_LENGTH); 469 struct luo_session *session; 470 int err; 471 472 if (len == 0 || len > LIVEUPDATE_SESSION_NAME_LENGTH - 1) 473 return -EINVAL; 474 475 session = luo_session_alloc(name); 476 if (IS_ERR(session)) 477 return PTR_ERR(session); 478 479 down_read(&luo_session_serialize_rwsem); 480 err = luo_session_insert(&luo_session_global.outgoing, session); 481 if (err) 482 goto err_free; 483 484 mutex_lock(&session->mutex); 485 err = luo_session_getfile(session, filep); 486 mutex_unlock(&session->mutex); 487 if (err) 488 goto err_remove; 489 up_read(&luo_session_serialize_rwsem); 490 491 return 0; 492 493 err_remove: 494 luo_session_remove(&luo_session_global.outgoing, session); 495 err_free: 496 luo_session_free(session); 497 up_read(&luo_session_serialize_rwsem); 498 499 return err; 500 } 501 502 int luo_session_retrieve(const char *name, struct file **filep) 503 { 504 struct luo_session_header *sh = &luo_session_global.incoming; 505 struct luo_session *session = NULL; 506 struct luo_session *it; 507 int err; 508 509 guard(rwsem_read)(&luo_session_serialize_rwsem); 510 guard(rwsem_read)(&sh->rwsem); 511 list_for_each_entry(it, &sh->list, list) { 512 if (!strncmp(it->name, name, sizeof(it->name))) { 513 session = it; 514 break; 515 } 516 } 517 518 if (!session) 519 return -ENOENT; 520 521 guard(mutex)(&session->mutex); 522 if (session->retrieved) 523 return -EINVAL; 524 525 err = luo_session_getfile(session, filep); 526 if (!err) 527 session->retrieved = true; 528 529 return err; 530 } 531 532 void __init luo_session_setup_outgoing(u64 *sessions_pa) 533 { 534 luo_session_global.outgoing.sessions_pa = sessions_pa; 535 luo_session_global.outgoing.active = true; 536 } 537 538 int __init luo_session_setup_incoming(u64 sessions_pa) 539 { 540 struct luo_session_header *sh = &luo_session_global.incoming; 541 int err; 542 543 if (!sessions_pa) 544 return 0; 545 546 err = kho_block_set_restore(&sh->block_set, sessions_pa); 547 if (err) 548 return err; 549 550 sh->active = true; 551 return 0; 552 } 553 554 static int luo_session_deserialize_one(struct luo_session_header *sh, 555 struct luo_session_ser *ser) 556 { 557 struct luo_session *session; 558 int err; 559 560 session = luo_session_alloc(ser->name); 561 if (IS_ERR(session)) { 562 pr_warn("Failed to allocate session [%.*s] during deserialization %pe\n", 563 (int)sizeof(ser->name), ser->name, session); 564 return PTR_ERR(session); 565 } 566 567 err = luo_session_insert(sh, session); 568 if (err) { 569 pr_warn("Failed to insert session [%s] %pe\n", 570 session->name, ERR_PTR(err)); 571 luo_session_free(session); 572 return err; 573 } 574 575 scoped_guard(mutex, &session->mutex) { 576 err = luo_file_deserialize(&session->file_set, 577 &ser->file_set_ser); 578 } 579 if (err) { 580 pr_warn("Failed to deserialize files for session [%s] %pe\n", 581 session->name, ERR_PTR(err)); 582 return err; 583 } 584 585 return 0; 586 } 587 588 int luo_session_deserialize(void) 589 { 590 struct luo_session_header *sh = &luo_session_global.incoming; 591 static bool is_deserialized; 592 struct luo_session_ser *ser; 593 struct kho_block_set_it it; 594 static int saved_err; 595 int err; 596 597 /* If has been deserialized, always return the same error code */ 598 if (is_deserialized) 599 return saved_err; 600 601 is_deserialized = true; 602 if (!sh->active) 603 return 0; 604 605 /* 606 * Note on error handling: 607 * 608 * If deserialization fails (e.g., allocation failure or corrupt data), 609 * we intentionally skip cleanup of sessions that were already restored. 610 * 611 * A partial failure leaves the preserved state inconsistent. 612 * Implementing a safe "undo" to unwind complex dependencies (sessions, 613 * files, hardware state) is error-prone and provides little value, as 614 * the system is effectively in a broken state. 615 * 616 * We treat these resources as leaked. The expected recovery path is for 617 * userspace to detect the failure and trigger a reboot, which will 618 * reliably reset devices and reclaim memory. 619 */ 620 kho_block_set_it_init(&it, &sh->block_set); 621 while ((ser = kho_block_set_it_read_entry(&it))) { 622 err = luo_session_deserialize_one(sh, ser); 623 if (err) 624 goto save_err; 625 } 626 627 kho_block_set_destroy(&sh->block_set); 628 629 return 0; 630 631 save_err: 632 kho_block_set_destroy(&sh->block_set); 633 saved_err = err; 634 return err; 635 } 636 637 int luo_session_serialize(void) 638 { 639 struct luo_session_header *sh = &luo_session_global.outgoing; 640 struct luo_session *session; 641 struct kho_block_set_it it; 642 int err; 643 644 down_write(&luo_session_serialize_rwsem); 645 down_write(&sh->rwsem); 646 *sh->sessions_pa = 0; 647 648 kho_block_set_it_init(&it, &sh->block_set); 649 650 list_for_each_entry(session, &sh->list, list) { 651 struct luo_session_ser *ser = kho_block_set_it_reserve_entry(&it); 652 653 /* This should not fail normally as blocks were pre-allocated */ 654 if (WARN_ON_ONCE(!ser)) { 655 err = -ENOSPC; 656 goto err_undo; 657 } 658 659 err = luo_session_freeze_one(session, ser); 660 if (err) { 661 kho_block_set_it_prev(&it); 662 goto err_undo; 663 } 664 665 strscpy(ser->name, session->name, sizeof(ser->name)); 666 } 667 668 if (sh->count > 0) 669 *sh->sessions_pa = kho_block_set_head_pa(&sh->block_set); 670 up_write(&sh->rwsem); 671 672 return 0; 673 674 err_undo: 675 list_for_each_entry_continue_reverse(session, &sh->list, list) { 676 struct luo_session_ser *ser = kho_block_set_it_prev(&it); 677 678 luo_session_unfreeze_one(session, ser); 679 memset(ser->name, 0, sizeof(ser->name)); 680 } 681 up_write(&sh->rwsem); 682 up_write(&luo_session_serialize_rwsem); 683 684 return err; 685 } 686