From nobody Fri Apr 3 04:38:50 2026 Received: from kansas-city-edge.smtp.mymangomail.com (ip74-208-171-129.pbiaas.com [74.208.171.129]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BC5D33537F0 for ; Tue, 24 Mar 2026 21:31:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.208.171.129 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774387867; cv=none; b=NHkgqfadzG3uGO7/xEyOzfl84G6eZQWlUOJzyoVcF5jcpJAbTXcg9lE3KVwtSO1QEd//6+n4S62udQFKd3kdipnqh3ZhQq+JSenU4ZfQnvqy9sCyQ+o5PyXLqc3QvWz8oerda8dodbybbC0dZ66Eqgatof8hFEW3GKd7ZkFiB5w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774387867; c=relaxed/simple; bh=L7uXEUA/d4BNBAHzLe+JqRbfltbKpjcnFFUj/dBxk7Q=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ne9gdj6EYgrqlZSNresvG1kSMR6h+gDvilECJSiTAGEYIQIAujrxIZreNZ8+gCL7PU0XA/e7w0PadjMNAj/CK91QFMpgaXmOLO256ry1FN6GsD4H+gjeuXP9o5y1/aMxlBhaiadZVTFDUir+cKaFmeGVXsl7KruYYmiTV9Yr/ps= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=gerlicz.space; spf=pass smtp.mailfrom=gerlicz.space; dkim=pass (1024-bit key) header.d=gerlicz.space header.i=@gerlicz.space header.b=KCIaZAsx; arc=none smtp.client-ip=74.208.171.129 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=gerlicz.space Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gerlicz.space Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=gerlicz.space header.i=@gerlicz.space header.b="KCIaZAsx" Received: from [127.0.1.1] (localhost [127.0.0.1]) by hillsboro.smtp.mymangomail.com (Mango Mail) with ESMTP id 582075D9B6; Tue, 24 Mar 2026 17:30:46 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gerlicz.space; s=mango-1; t=1774387846; bh=L7uXEUA/d4BNBAHzLe+JqRbfltbKpjcnFFUj/dBxk7Q=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KCIaZAsx196BMSBvyFO4r8k0Yy34I9hkfUIiC88LJHGkNLKNNSqbHyahkQLTUseUW aO2/y0TiMKPiUXMTXdAj0OReR42kyOA52/BXPI07Chx4khQAoBDLET6CcXoj3wuAJu V03N4sSGahJIy5/fkPDIyP0CbyV5Zg4qM6ZOoHvo= X-Mango-Origin: 1 X-Mango-Origin: 1 X-Mango-Origin: 1 X-Mango-Origin: 1 X-Mango-Origin: 1 X-Mango-Origin: 1 X-Mango-Origin: 1 X-Mango-Origin: 1 X-Mango-Origin: 1 Received: from authenticated-user (smtp.mymangomail.com [205.185.121.143]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by hillsboro.smtp.mymangomail.com (Mango Mail) with ESMTPSA id F18B65D9B4; Tue, 24 Mar 2026 17:28:57 -0400 (EDT) From: Oskar Gerlicz Kowalczuk To: Pasha Tatashin , Mike Rapoport , Baoquan He Cc: Pratyush Yadav , Andrew Morton , linux-kernel@vger.kernel.org, kexec@lists.infradead.org, linux-mm@kvack.org, Oskar Gerlicz Kowalczuk Subject: [PATCH v4 1/5] liveupdate: block outgoing session updates during reboot Date: Tue, 24 Mar 2026 22:27:26 +0100 Message-ID: <20260324212730.65290-2-oskar@gerlicz.space> In-Reply-To: <20260324212730.65290-1-oskar@gerlicz.space> References: <20260324212730.65290-1-oskar@gerlicz.space> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Block new outgoing session mutations with the rebooting gate and serialize in-flight operations with session->mutex. During serialization, pin the outgoing sessions with temporary references instead of relying on an auxiliary in-flight counter. Also make incoming session origin immutable and use retrieved only as the "checked out by userspace" bit. This keeps FINISH, close(), and retrieve-by-name on a single lifetime model without reintroducing a larger session state machine. Signed-off-by: Oskar Gerlicz Kowalczuk --- kernel/liveupdate/luo_internal.h | 9 +- kernel/liveupdate/luo_session.c | 308 +++++++++++++++++++++++++------ 2 files changed, 259 insertions(+), 58 deletions(-) diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_inter= nal.h index 8083d8739b09..e29e1a46b491 100644 --- a/kernel/liveupdate/luo_internal.h +++ b/kernel/liveupdate/luo_internal.h @@ -9,6 +9,7 @@ #define _LINUX_LUO_INTERNAL_H =20 #include +#include #include =20 struct luo_ucmd { @@ -63,8 +64,10 @@ struct luo_file_set { * @list: A list_head member used to link this session into a global= list * of either outgoing (to be preserved) or incoming (restored= from * previous kernel) sessions. - * @retrieved: A boolean flag indicating whether this session has been - * retrieved by a consumer in the new kernel. + * @incoming: True for sessions restored from the previous kernel. This = is + * immutable after publication and distinguishes incoming ses= sions + * from outgoing ones. + * @retrieved: True while an incoming session is checked out by userspace. * @file_set: A set of files that belong to this session. * @mutex: protects fields in the luo_session. */ @@ -72,8 +75,10 @@ struct luo_session { char name[LIVEUPDATE_SESSION_NAME_LENGTH]; struct luo_session_ser *ser; struct list_head list; + bool incoming; bool retrieved; struct luo_file_set file_set; + refcount_t refs; struct mutex mutex; }; =20 diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_sessio= n.c index 783677295640..63aee9cc881d 100644 --- a/kernel/liveupdate/luo_session.c +++ b/kernel/liveupdate/luo_session.c @@ -66,6 +66,7 @@ #include #include #include +#include #include #include "luo_internal.h" =20 @@ -81,9 +82,12 @@ * @list: The head of the linked list of `struct luo_session` instan= ces. * @rwsem: A read-write semaphore providing synchronized access to the * session list and other fields in this structure. + * @reboot_waitq: Wait queue used by close() and serialize() to wait for + * rebooting transitions. * @header_ser: The header data of serialization array. * @ser: The serialized session data (an array of * `struct luo_session_ser`). + * @rebooting: True while outgoing sessions are frozen for kexec handover. * @active: Set to true when first initialized. If previous kernel did= not * send session data, active stays false for incoming. */ @@ -91,8 +95,10 @@ struct luo_session_header { long count; struct list_head list; struct rw_semaphore rwsem; + wait_queue_head_t reboot_waitq; struct luo_session_header_ser *header_ser; struct luo_session_ser *ser; + bool rebooting; bool active; }; =20 @@ -110,13 +116,23 @@ static struct luo_session_global luo_session_global = =3D { .incoming =3D { .list =3D LIST_HEAD_INIT(luo_session_global.incoming.list), .rwsem =3D __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem), + .reboot_waitq =3D + __WAIT_QUEUE_HEAD_INITIALIZER(luo_session_global.incoming.reboot_waitq), }, .outgoing =3D { .list =3D LIST_HEAD_INIT(luo_session_global.outgoing.list), .rwsem =3D __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem), + .reboot_waitq =3D + __WAIT_QUEUE_HEAD_INITIALIZER(luo_session_global.outgoing.reboot_waitq), }, }; =20 +static void luo_session_reboot_done(struct luo_session_header *sh) +{ + WRITE_ONCE(sh->rebooting, false); + wake_up_all(&sh->reboot_waitq); +} + static struct luo_session *luo_session_alloc(const char *name) { struct luo_session *session =3D kzalloc_obj(*session); @@ -128,6 +144,7 @@ static struct luo_session *luo_session_alloc(const char= *name) INIT_LIST_HEAD(&session->file_set.files_list); luo_file_set_init(&session->file_set); INIT_LIST_HEAD(&session->list); + refcount_set(&session->refs, 1); mutex_init(&session->mutex); =20 return session; @@ -140,6 +157,17 @@ static void luo_session_free(struct luo_session *sessi= on) kfree(session); } =20 +static void luo_session_get(struct luo_session *session) +{ + refcount_inc(&session->refs); +} + +static void luo_session_put(struct luo_session *session) +{ + if (refcount_dec_and_test(&session->refs)) + luo_session_free(session); +} + static int luo_session_insert(struct luo_session_header *sh, struct luo_session *session) { @@ -152,6 +180,9 @@ static int luo_session_insert(struct luo_session_header= *sh, * for new session. */ if (sh =3D=3D &luo_session_global.outgoing) { + if (READ_ONCE(sh->rebooting)) + return -EBUSY; + if (sh->count =3D=3D LUO_SESSION_MAX) return -ENOMEM; } @@ -172,58 +203,157 @@ static int luo_session_insert(struct luo_session_hea= der *sh, return 0; } =20 +static void __luo_session_remove(struct luo_session_header *sh, + struct luo_session *session) +{ + list_del_init(&session->list); + sh->count--; +} + 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--; + __luo_session_remove(sh, session); } =20 -static int luo_session_finish_one(struct luo_session *session) +static int luo_session_outgoing_begin(struct luo_session *session, + struct luo_session_header **shp) { - guard(mutex)(&session->mutex); - return luo_file_finish(&session->file_set); + struct luo_session_header *sh; + + if (session->incoming) + return -EINVAL; + + sh =3D &luo_session_global.outgoing; + down_read(&sh->rwsem); + if (READ_ONCE(sh->rebooting)) { + up_read(&sh->rwsem); + return -EBUSY; + } + + *shp =3D sh; + return 0; } =20 -static void luo_session_unfreeze_one(struct luo_session *session, - struct luo_session_ser *ser) +static void luo_session_outgoing_end(struct luo_session_header *sh) { - guard(mutex)(&session->mutex); - luo_file_unfreeze(&session->file_set, &ser->file_set_ser); + if (sh) + up_read(&sh->rwsem); } =20 -static int luo_session_freeze_one(struct luo_session *session, - struct luo_session_ser *ser) +static int luo_session_get_all_outgoing(struct luo_session **sessions) { - guard(mutex)(&session->mutex); - return luo_file_freeze(&session->file_set, &ser->file_set_ser); + struct luo_session_header *sh =3D &luo_session_global.outgoing; + struct luo_session *session; + u64 nr =3D 0; + + down_write(&sh->rwsem); + if (READ_ONCE(sh->rebooting)) { + up_write(&sh->rwsem); + return -EBUSY; + } + + WRITE_ONCE(sh->rebooting, true); + list_for_each_entry(session, &sh->list, list) { + luo_session_get(session); + sessions[nr++] =3D session; + } + up_write(&sh->rwsem); + + return nr; +} + +static void luo_session_put_all(struct luo_session **sessions, u64 nr) +{ + while (nr) + luo_session_put(sessions[--nr]); +} + +static void luo_session_wait_reboot(struct luo_session_header *sh) +{ + DEFINE_WAIT(wait); + + for (;;) { + prepare_to_wait(&sh->reboot_waitq, &wait, + TASK_UNINTERRUPTIBLE | TASK_FREEZABLE); + if (!READ_ONCE(sh->rebooting)) + break; + schedule(); + } + + finish_wait(&sh->reboot_waitq, &wait); +} + +static int luo_session_finish_retrieved(struct luo_session *session) +{ + lockdep_assert_held(&session->mutex); + if (!session->retrieved) + return -EINVAL; + + return luo_file_finish(&session->file_set); } =20 static int luo_session_release(struct inode *inodep, struct file *filep) { struct luo_session *session =3D filep->private_data; - struct luo_session_header *sh; + struct luo_session_header *sh =3D &luo_session_global.incoming; + int ret =3D 0; + bool removed =3D false; =20 - /* If retrieved is set, it means this session is from incoming list */ - if (session->retrieved) { - int err =3D luo_session_finish_one(session); + if (session->incoming) { + scoped_guard(mutex, &session->mutex) { + if (session->retrieved) { + ret =3D luo_session_finish_retrieved(session); + if (!ret) { + down_write(&sh->rwsem); + if (!list_empty(&session->list)) { + __luo_session_remove(sh, session); + removed =3D true; + } + up_write(&sh->rwsem); + } + WRITE_ONCE(session->retrieved, false); + } + } =20 - if (err) { + if (ret) { pr_warn("Unable to finish session [%s] on release\n", session->name); - return err; + luo_session_put(session); + return ret; + } + + if (removed) + luo_session_put(session); + luo_session_put(session); + return 0; + } + + sh =3D &luo_session_global.outgoing; + + for (;;) { + down_read(&sh->rwsem); + if (READ_ONCE(sh->rebooting)) { + up_read(&sh->rwsem); + luo_session_wait_reboot(sh); + continue; } - sh =3D &luo_session_global.incoming; - } else { - scoped_guard(mutex, &session->mutex) - luo_file_unpreserve_files(&session->file_set); - sh =3D &luo_session_global.outgoing; + + mutex_lock(&session->mutex); + up_read(&sh->rwsem); + break; } =20 - luo_session_remove(sh, session); - luo_session_free(session); + luo_file_unpreserve_files(&session->file_set); =20 + down_write(&sh->rwsem); + __luo_session_remove(sh, session); + up_write(&sh->rwsem); + mutex_unlock(&session->mutex); + + luo_session_put(session); + luo_session_put(session); return 0; } =20 @@ -231,10 +361,16 @@ static int luo_session_preserve_fd(struct luo_session= *session, struct luo_ucmd *ucmd) { struct liveupdate_session_preserve_fd *argp =3D ucmd->cmd; + struct luo_session_header *sh =3D NULL; int err; =20 - guard(mutex)(&session->mutex); - err =3D luo_preserve_file(&session->file_set, argp->token, argp->fd); + err =3D luo_session_outgoing_begin(session, &sh); + if (err) + return err; + + scoped_guard(mutex, &session->mutex) + err =3D luo_preserve_file(&session->file_set, argp->token, argp->fd); + luo_session_outgoing_end(sh); if (err) return err; =20 @@ -256,8 +392,13 @@ static int luo_session_retrieve_fd(struct luo_session = *session, if (argp->fd < 0) return argp->fd; =20 - guard(mutex)(&session->mutex); - err =3D luo_retrieve_file(&session->file_set, argp->token, &file); + scoped_guard(mutex, &session->mutex) { + if (!session->retrieved) + err =3D -EINVAL; + else + err =3D luo_retrieve_file(&session->file_set, + argp->token, &file); + } if (err < 0) goto err_put_fd; =20 @@ -281,11 +422,28 @@ static int luo_session_finish(struct luo_session *ses= sion, struct luo_ucmd *ucmd) { struct liveupdate_session_finish *argp =3D ucmd->cmd; - int err =3D luo_session_finish_one(session); + struct luo_session_header *sh =3D &luo_session_global.incoming; + bool removed =3D false; + int err; =20 + scoped_guard(mutex, &session->mutex) { + err =3D luo_session_finish_retrieved(session); + if (!err) { + down_write(&sh->rwsem); + if (!list_empty(&session->list)) { + __luo_session_remove(sh, session); + removed =3D true; + } + up_write(&sh->rwsem); + WRITE_ONCE(session->retrieved, false); + } + } if (err) return err; =20 + if (removed) + luo_session_put(session); + return luo_ucmd_respond(ucmd, sizeof(*argp)); } =20 @@ -371,9 +529,12 @@ static int luo_session_getfile(struct luo_session *ses= sion, struct file **filep) =20 lockdep_assert_held(&session->mutex); snprintf(name_buf, sizeof(name_buf), "[luo_session] %s", session->name); + luo_session_get(session); file =3D anon_inode_getfile(name_buf, &luo_session_fops, session, O_RDWR); - if (IS_ERR(file)) + if (IS_ERR(file)) { + luo_session_put(session); return PTR_ERR(file); + } =20 *filep =3D file; =20 @@ -401,9 +562,10 @@ int luo_session_create(const char *name, struct file *= *filep) return 0; =20 err_remove: - luo_session_remove(&luo_session_global.outgoing, session); + scoped_guard(mutex, &session->mutex) + luo_session_remove(&luo_session_global.outgoing, session); err_free: - luo_session_free(session); + luo_session_put(session); =20 return err; } @@ -418,6 +580,8 @@ int luo_session_retrieve(const char *name, struct file = **filep) scoped_guard(rwsem_read, &sh->rwsem) { list_for_each_entry(it, &sh->list, list) { if (!strncmp(it->name, name, sizeof(it->name))) { + /* Keep the session alive after dropping rwsem. */ + luo_session_get(it); session =3D it; break; } @@ -428,12 +592,14 @@ int luo_session_retrieve(const char *name, struct fil= e **filep) return -ENOENT; =20 guard(mutex)(&session->mutex); - if (session->retrieved) - return -EINVAL; + if (session->retrieved || list_empty(&session->list)) + err =3D -EINVAL; + else + err =3D luo_session_getfile(session, filep); =20 - err =3D luo_session_getfile(session, filep); if (!err) - session->retrieved =3D true; + WRITE_ONCE(session->retrieved, true); + luo_session_put(session); =20 return err; } @@ -548,6 +714,7 @@ int luo_session_deserialize(void) sh->ser[i].name, session); return PTR_ERR(session); } + session->incoming =3D true; =20 err =3D luo_session_insert(sh, session); if (err) { @@ -573,32 +740,61 @@ int luo_session_deserialize(void) int luo_session_serialize(void) { struct luo_session_header *sh =3D &luo_session_global.outgoing; - struct luo_session *session; - int i =3D 0; + struct luo_session **sessions; + u64 nr; + u64 i; + u64 frozen_nr =3D 0; int err; =20 - guard(rwsem_write)(&sh->rwsem); - list_for_each_entry(session, &sh->list, list) { - err =3D luo_session_freeze_one(session, &sh->ser[i]); - if (err) - goto err_undo; + sessions =3D kcalloc(LUO_SESSION_MAX, sizeof(*sessions), GFP_KERNEL); + if (!sessions) + return -ENOMEM; =20 - strscpy(sh->ser[i].name, session->name, - sizeof(sh->ser[i].name)); - i++; - } - sh->header_ser->count =3D sh->count; + err =3D luo_session_get_all_outgoing(sessions); + if (err < 0) + goto out_free_sessions; =20 - return 0; + nr =3D err; + for (i =3D 0; i < nr; i++) { + struct luo_session *session =3D sessions[i]; =20 -err_undo: - list_for_each_entry_continue_reverse(session, &sh->list, list) { - i--; - luo_session_unfreeze_one(session, &sh->ser[i]); - memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name)); + scoped_guard(mutex, &session->mutex) { + if (list_empty(&session->list)) + continue; + + err =3D luo_file_freeze(&session->file_set, + &sh->ser[frozen_nr].file_set_ser); + if (err) + goto err_undo; + + strscpy(sh->ser[frozen_nr].name, session->name, + sizeof(sh->ser[frozen_nr].name)); + swap(sessions[frozen_nr], sessions[i]); + frozen_nr++; + } } + sh->header_ser->count =3D frozen_nr; + err =3D 0; =20 +out_put_sessions: + luo_session_put_all(sessions, nr); +out_free_sessions: + kfree(sessions); return err; + +err_undo: + while (frozen_nr) { + struct luo_session *session =3D sessions[--frozen_nr]; + + scoped_guard(mutex, &session->mutex) { + luo_file_unfreeze(&session->file_set, + &sh->ser[frozen_nr].file_set_ser); + memset(&sh->ser[frozen_nr], 0, sizeof(sh->ser[frozen_nr])); + } + } + sh->header_ser->count =3D 0; + luo_session_reboot_done(sh); + goto out_put_sessions; } =20 /** --=20 2.53.0