* [PATCH v5 1/6] liveupdate: Move file_set_ser handling to luo_session_serialize()
2026-11-05 5:00 [PATCH v5 0/6] liveupdate: Add LIVEUPDATE_SESSION_FREEZE ioctl Pasha Tatashin
@ 2026-11-05 5:00 ` Pasha Tatashin
2026-11-05 5:00 ` [PATCH v5 2/6] kho: support incremental entry publication in kho_block Pasha Tatashin
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Pasha Tatashin @ 2026-11-05 5:00 UTC (permalink / raw)
To: linux-kselftest, rppt, shuah, linux-mm, skhan, linux-doc,
vipinsh, linux-kernel, corbet, pasha.tatashin, dmatlack, kexec,
pratyush, rdunlap, graf
In preparation for allowing userspace to freeze a session explicitly
before kexec reboot, luo_file_freeze() and luo_file_unfreeze() need to
be called on session->file_set before any struct luo_session_ser entry
is reserved in luo_session_serialize().
Currently, struct luo_session_ser is populated and cleared across two
layers: luo_session_serialize() writes and clears ser->name, while
luo_file_freeze() and luo_file_unfreeze() write and clear
ser->file_set_ser.
Drop the struct luo_file_set_ser pointer parameter from
luo_file_freeze() and luo_file_unfreeze() (and struct luo_session_ser
from luo_session_freeze_one() and luo_session_unfreeze_one()), and
populate and clear all fields of struct luo_session_ser in
luo_session_serialize().
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/liveupdate/luo_file.c | 24 ++++++++----------------
kernel/liveupdate/luo_internal.h | 6 ++----
kernel/liveupdate/luo_session.c | 19 ++++++++++---------
3 files changed, 20 insertions(+), 29 deletions(-)
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index a39992da504f..988eb313a135 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -440,8 +440,7 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
/**
* luo_file_freeze - Freezes all preserved files and serializes their metadata.
- * @file_set: The file_set whose files are to be frozen.
- * @file_set_ser: Where to put the serialized file_set.
+ * @file_set: The file_set whose files are to be frozen.
*
* This function is called from the reboot() syscall path, just before the
* kernel transitions to the new image via kexec. Its purpose is to perform the
@@ -457,8 +456,8 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
*
* 2. Serializes Metadata: After a successful freeze, it copies the final file
* metadata—the handler's compatible string, the user token, and the final
- * private data handle—into the pre-allocated contiguous memory buffer
- * (file_set->files) that will be handed over to the next kernel via KHO.
+ * private data handle—into the pre-allocated serialization blocks
+ * (file_set->block_set) that will be handed over to the next kernel via KHO.
*
* Error Handling (Rollback):
* This function is atomic. If any handler's .freeze() operation fails, the
@@ -471,8 +470,7 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
* Context: Called only from the liveupdate_reboot() path.
* Return: 0 on success, or a negative errno on failure.
*/
-int luo_file_freeze(struct luo_file_set *file_set,
- struct luo_file_set_ser *file_set_ser)
+int luo_file_freeze(struct luo_file_set *file_set)
{
struct luo_file *luo_file;
struct kho_block_set_it it;
@@ -506,9 +504,6 @@ int luo_file_freeze(struct luo_file_set *file_set,
file_ser->token = luo_file->token;
}
- file_set_ser->count = file_set->count;
- file_set_ser->files = kho_block_set_head_pa(&file_set->block_set);
-
return 0;
err_unfreeze:
@@ -519,28 +514,25 @@ int luo_file_freeze(struct luo_file_set *file_set,
/**
* luo_file_unfreeze - Unfreezes all files in a file_set and clear serialization
- * @file_set: The file_set whose files are to be unfrozen.
- * @file_set_ser: Serialized file_set.
+ * @file_set: The file_set whose files are to be unfrozen.
*
* This function rolls back the state of all files in a file_set after the
* freeze phase has begun but must be aborted. It is the counterpart to
* luo_file_freeze().
*
* It invokes the __luo_file_unfreeze() helper with a NULL argument, which
- * signals the helper to iterate through all files in the file_set and call
- * their respective .unfreeze() handler callbacks.
+ * signals the helper to iterate through all files in the file_set, call their
+ * respective .unfreeze() handler callbacks, and clear the serialized blocks.
*
* Context: This is called when the live update is aborted during
* the reboot() syscall, after luo_file_freeze() has been called.
*/
-void luo_file_unfreeze(struct luo_file_set *file_set,
- struct luo_file_set_ser *file_set_ser)
+void luo_file_unfreeze(struct luo_file_set *file_set)
{
if (!file_set->count)
return;
__luo_file_unfreeze(file_set, NULL);
- memset(file_set_ser, 0, sizeof(*file_set_ser));
}
/**
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index dac6644bfb18..01a172cd9d7a 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -103,10 +103,8 @@ int luo_session_deserialize(void);
int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd);
void luo_file_unpreserve_files(struct luo_file_set *file_set);
-int luo_file_freeze(struct luo_file_set *file_set,
- struct luo_file_set_ser *file_set_ser);
-void luo_file_unfreeze(struct luo_file_set *file_set,
- struct luo_file_set_ser *file_set_ser);
+int luo_file_freeze(struct luo_file_set *file_set);
+void luo_file_unfreeze(struct luo_file_set *file_set);
int luo_retrieve_file(struct luo_file_set *file_set, u64 token,
struct file **filep);
int luo_file_finish(struct luo_file_set *file_set);
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 74bc66929f4b..6dea35911181 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -219,18 +219,16 @@ static int luo_session_finish_one(struct luo_session *session)
return luo_file_finish(&session->file_set);
}
-static void luo_session_unfreeze_one(struct luo_session *session,
- struct luo_session_ser *ser)
+static void luo_session_unfreeze_one(struct luo_session *session)
{
guard(mutex)(&session->mutex);
- luo_file_unfreeze(&session->file_set, &ser->file_set_ser);
+ luo_file_unfreeze(&session->file_set);
}
-static int luo_session_freeze_one(struct luo_session *session,
- struct luo_session_ser *ser)
+static int luo_session_freeze_one(struct luo_session *session)
{
guard(mutex)(&session->mutex);
- return luo_file_freeze(&session->file_set, &ser->file_set_ser);
+ return luo_file_freeze(&session->file_set);
}
static int luo_session_release(struct inode *inodep, struct file *filep)
@@ -657,13 +655,16 @@ int luo_session_serialize(void)
goto err_undo;
}
- err = luo_session_freeze_one(session, ser);
+ err = luo_session_freeze_one(session);
if (err) {
kho_block_set_it_prev(&it);
goto err_undo;
}
strscpy(ser->name, session->name, sizeof(ser->name));
+ ser->file_set_ser.count = session->file_set.count;
+ ser->file_set_ser.files =
+ kho_block_set_head_pa(&session->file_set.block_set);
}
if (sh->count > 0)
@@ -676,8 +677,8 @@ int luo_session_serialize(void)
list_for_each_entry_continue_reverse(session, &sh->list, list) {
struct luo_session_ser *ser = kho_block_set_it_prev(&it);
- luo_session_unfreeze_one(session, ser);
- memset(ser->name, 0, sizeof(ser->name));
+ luo_session_unfreeze_one(session);
+ memset(ser, 0, sizeof(*ser));
}
up_write(&sh->rwsem);
up_write(&luo_session_serialize_rwsem);
--
2.56.0.398.g882cbad275-goog
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v5 2/6] kho: support incremental entry publication in kho_block
2026-11-05 5:00 [PATCH v5 0/6] liveupdate: Add LIVEUPDATE_SESSION_FREEZE ioctl Pasha Tatashin
2026-11-05 5:00 ` [PATCH v5 1/6] liveupdate: Move file_set_ser handling to luo_session_serialize() Pasha Tatashin
@ 2026-11-05 5:00 ` Pasha Tatashin
2026-11-05 5:00 ` [PATCH v5 3/6] liveupdate: luo: serialize sessions via per-session publish helper Pasha Tatashin
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Pasha Tatashin @ 2026-11-05 5:00 UTC (permalink / raw)
To: linux-kselftest, rppt, shuah, linux-mm, skhan, linux-doc,
vipinsh, linux-kernel, corbet, pasha.tatashin, dmatlack, kexec,
pratyush, rdunlap, graf
To allow LUO sessions to be frozen and committed into the outgoing KHO
subtree from userspace ahead of reboot, kho_block needs to support
publishing entries one by one into a live serialized block chain while
new blocks may still be preallocated or shrunk for uncommitted sessions.
Currently, kho_block assumes outgoing block sets are populated in a
single batch during reboot() before the chain is exposed: preallocated
blocks are linked onto the chain immediately even while empty
(ser->count == 0), and reserving a slot increments ser->count before the
entry is populated.
Address this by keeping preallocated outgoing blocks unlinked in
kho_block_add(), linking blocks lazily only as entries are written. Add
kho_block_set_it_append() to copy each entry into its slot before
publishing the updated ser->count and next-block link via
smp_store_release(), paired with smp_load_acquire() in
kho_block_set_it_read_entry().
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
include/linux/kho_block.h | 1 +
kernel/liveupdate/kho_block.c | 86 +++++++++++++++++++++++++++++++----
2 files changed, 77 insertions(+), 10 deletions(-)
diff --git a/include/linux/kho_block.h b/include/linux/kho_block.h
index 93a7cc2be5f5..a9e696ef3e08 100644
--- a/include/linux/kho_block.h
+++ b/include/linux/kho_block.h
@@ -100,6 +100,7 @@ static inline bool kho_block_set_is_empty(struct kho_block_set *bs)
void kho_block_set_it_init(struct kho_block_set_it *it, struct kho_block_set *bs);
void *kho_block_set_it_reserve_entry(struct kho_block_set_it *it);
+void *kho_block_set_it_append(struct kho_block_set_it *it, const void *src);
void *kho_block_set_it_read_entry(struct kho_block_set_it *it);
void *kho_block_set_it_prev(struct kho_block_set_it *it);
diff --git a/kernel/liveupdate/kho_block.c b/kernel/liveupdate/kho_block.c
index 0d2a342ef422..2813c6fd593c 100644
--- a/kernel/liveupdate/kho_block.c
+++ b/kernel/liveupdate/kho_block.c
@@ -16,6 +16,12 @@
* array of serialized entries. Multiple blocks are linked together via a
* physical pointer in the header, forming a linked list that can be easily
* traversed in both the current and the next kernel.
+ *
+ * Outgoing blocks preallocated via kho_block_set_grow() remain unlinked until
+ * entries are written into them. Callers publishing entries incrementally into
+ * a live KHO subtree can use kho_block_set_it_append(), which populates each
+ * entry before exposing the updated block count or next-block link via
+ * release-store ordering.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -93,9 +99,7 @@ static int kho_block_add(struct kho_block_set *bs,
list_add_tail(&block->list, &bs->blocks);
bs->nblocks++;
- if (last)
- last->ser->next = virt_to_phys(ser);
- else
+ if (!last)
bs->head_pa = virt_to_phys(ser);
return 0;
@@ -129,14 +133,15 @@ static void kho_block_set_shrink_one(struct kho_block_set *bs)
last = list_last_entry(&bs->blocks, struct kho_block, list);
list_del(&last->list);
bs->nblocks--;
- kho_block_free_ser(bs, last->ser);
- kfree(last);
new_last = list_last_entry_or_null(&bs->blocks, struct kho_block, list);
if (new_last)
- new_last->ser->next = 0;
+ WRITE_ONCE(new_last->ser->next, 0);
else
bs->head_pa = 0;
+
+ kho_block_free_ser(bs, last->ser);
+ kfree(last);
}
/**
@@ -144,9 +149,9 @@ static void kho_block_set_shrink_one(struct kho_block_set *bs)
* @bs: The block set.
* @count: The target number of valid entries to accommodate.
*
- * Dynamically preallocates and links preserved memory blocks if the target
- * entry count exceeds the current total capacity of the set, ensuring they
- * are available during serialization/deserialization.
+ * Dynamically preallocates preserved memory blocks if the target entry count
+ * exceeds the current total capacity of the set, ensuring they are available
+ * during serialization/deserialization.
*
* Context: Caller must hold a lock protecting the block set.
* Return: 0 on success, or a negative errno on failure.
@@ -318,6 +323,7 @@ void kho_block_set_clear(struct kho_block_set *bs)
struct kho_block *block;
list_for_each_entry(block, &bs->blocks, list) {
+ block->ser->next = 0;
block->ser->count = 0;
memset(block->ser + 1, 0, KHO_BLOCK_SIZE - sizeof(*block->ser));
}
@@ -348,6 +354,7 @@ void kho_block_set_it_init(struct kho_block_set_it *it, struct kho_block_set *bs
*/
void *kho_block_set_it_reserve_entry(struct kho_block_set_it *it)
{
+ struct kho_block *prev = NULL;
void *entry;
if (!it->block)
@@ -356,15 +363,73 @@ void *kho_block_set_it_reserve_entry(struct kho_block_set_it *it)
if (it->i == it->bs->count_per_block) {
if (list_is_last(&it->block->list, &it->bs->blocks))
return NULL;
+ prev = it->block;
it->block = list_next_entry(it->block, list);
it->i = 0;
}
+ if (!it->i)
+ it->block->ser->next = 0;
+
entry = kho_block_entry(it, it->i++);
it->block->ser->count = it->i;
+ if (prev)
+ prev->ser->next = virt_to_phys(it->block->ser);
+
return entry;
}
+/**
+ * kho_block_set_it_append - Write and publish an entry at the next slot.
+ * @it: The block iterator.
+ * @src: Entry data of size @it->bs->entry_size to copy into the slot.
+ *
+ * Copies @src into the next available slot before updating the block's entry
+ * count or linking a newly occupied block to the previous block, so concurrent
+ * readers never observe an uninitialized slot or an empty block.
+ *
+ * Return: A pointer to the written entry slot, or NULL if the block set's
+ * capacity is fully exhausted.
+ */
+void *kho_block_set_it_append(struct kho_block_set_it *it, const void *src)
+{
+ struct kho_block *prev = NULL;
+ void *dst;
+
+ if (!it->block)
+ return NULL;
+
+ if (it->i == it->bs->count_per_block) {
+ if (list_is_last(&it->block->list, &it->bs->blocks))
+ return NULL;
+ prev = it->block;
+ it->block = list_next_entry(it->block, list);
+ it->i = 0;
+ }
+
+ if (!it->i)
+ WRITE_ONCE(it->block->ser->next, 0);
+
+ dst = kho_block_entry(it, it->i++);
+ memcpy(dst, src, it->bs->entry_size);
+
+ /*
+ * Ensure the serialized entry is visible before exposing the
+ * incremented count to concurrent readers.
+ */
+ smp_store_release(&it->block->ser->count, it->i);
+ if (prev) {
+ /*
+ * Ensure the new block's first entry and header are visible
+ * before linking prev->ser->next for concurrent readers.
+ */
+ smp_store_release(&prev->ser->next,
+ virt_to_phys(it->block->ser));
+ }
+
+ return dst;
+}
+
/**
* kho_block_set_it_read_entry - Read the next serialized entry from the block set.
* @it: The block iterator.
@@ -380,7 +445,8 @@ void *kho_block_set_it_read_entry(struct kho_block_set_it *it)
if (!it->block)
return NULL;
- if (it->i == it->block->ser->count) {
+ /* Pairs with smp_store_release() in kho_block_set_it_append(). */
+ while (it->i >= smp_load_acquire(&it->block->ser->count)) {
if (list_is_last(&it->block->list, &it->bs->blocks))
return NULL;
it->block = list_next_entry(it->block, list);
--
2.56.0.398.g882cbad275-goog
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v5 3/6] liveupdate: luo: serialize sessions via per-session publish helper
2026-11-05 5:00 [PATCH v5 0/6] liveupdate: Add LIVEUPDATE_SESSION_FREEZE ioctl Pasha Tatashin
2026-11-05 5:00 ` [PATCH v5 1/6] liveupdate: Move file_set_ser handling to luo_session_serialize() Pasha Tatashin
2026-11-05 5:00 ` [PATCH v5 2/6] kho: support incremental entry publication in kho_block Pasha Tatashin
@ 2026-11-05 5:00 ` Pasha Tatashin
2026-11-05 5:00 ` [PATCH v5 4/6] liveupdate: luo_flb: support incremental outgoing FLB publication Pasha Tatashin
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Pasha Tatashin @ 2026-11-05 5:00 UTC (permalink / raw)
To: linux-kselftest, rppt, shuah, linux-mm, skhan, linux-doc,
vipinsh, linux-kernel, corbet, pasha.tatashin, dmatlack, kexec,
pratyush, rdunlap, graf
With incremental kho_block publication in place, LUO sessions can be
published into sh->block_set individually as they are committed, rather
than rebuilding the entire session serialization chain during reboot.
Currently, luo_session_serialize() resets *sh->sessions_pa and
serializes all outgoing sessions from scratch in a single pass at reboot
time.
Introduce luo_session_publish_one() to append a session's
luo_session_ser entry via kho_block_set_it_append() and move the session
onto sh->published_list. Update luo_session_serialize() to freeze all
remaining unpublished sessions first (rolling back via
luo_session_unfreeze_one() on failure) and then publish them in a second
pass.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/liveupdate/luo_session.c | 92 ++++++++++++++++++++++-----------
1 file changed, 63 insertions(+), 29 deletions(-)
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 6dea35911181..caf0e85081e8 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -103,20 +103,30 @@
static DECLARE_RWSEM(luo_session_serialize_rwsem);
/**
* struct luo_session_header - Header struct for managing LUO sessions.
- * @count: The number of sessions currently tracked in the @list.
+ * @count: The number of sessions currently tracked in the header.
+ * @published_count: The number of outgoing sessions currently published in
+ * @block_set.
* @list: The head of the linked list of `struct luo_session` instances.
+ * @published_list: The head of the linked list of published outgoing
+ * sessions, ordered identically to their entries in
+ * @block_set.
* @rwsem: A read-write semaphore providing synchronized access to the
* session list and other fields in this structure.
* @block_set: The set of serialization blocks.
+ * @it: Iterator tracking the next slot in @block_set for outgoing
+ * session serialization.
* @sessions_pa: Points to the location of sessions_pa within struct luo_ser.
* @active: Set to true when first initialized. If previous kernel did not
* send session data, active stays false for incoming.
*/
struct luo_session_header {
long count;
+ long published_count;
struct list_head list;
+ struct list_head published_list;
struct rw_semaphore rwsem;
struct kho_block_set block_set;
+ struct kho_block_set_it it;
u64 *sessions_pa;
bool active;
};
@@ -134,12 +144,14 @@ struct luo_session_global {
static struct luo_session_global luo_session_global = {
.incoming = {
.list = LIST_HEAD_INIT(luo_session_global.incoming.list),
+ .published_list = LIST_HEAD_INIT(luo_session_global.incoming.published_list),
.rwsem = __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem),
.block_set = KHO_BLOCK_SET_INIT(luo_session_global.incoming.block_set,
sizeof(struct luo_session_ser)),
},
.outgoing = {
.list = LIST_HEAD_INIT(luo_session_global.outgoing.list),
+ .published_list = LIST_HEAD_INIT(luo_session_global.outgoing.published_list),
.rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem),
.block_set = KHO_BLOCK_SET_INIT(luo_session_global.outgoing.block_set,
sizeof(struct luo_session_ser)),
@@ -186,6 +198,10 @@ static int luo_session_insert(struct luo_session_header *sh,
if (!strncmp(it->name, session->name, sizeof(it->name)))
return -EEXIST;
}
+ list_for_each_entry(it, &sh->published_list, list) {
+ if (!strncmp(it->name, session->name, sizeof(it->name)))
+ return -EEXIST;
+ }
/*
* For outgoing we should make sure there is room in serialization array
@@ -203,6 +219,42 @@ static int luo_session_insert(struct luo_session_header *sh,
return 0;
}
+static void luo_session_publish_one(struct luo_session *session)
+{
+ struct luo_session_header *sh = &luo_session_global.outgoing;
+ struct luo_session_ser entry = {};
+
+ lockdep_assert_held_write(&sh->rwsem);
+ lockdep_assert_held(&session->mutex);
+
+ if (!sh->active || !sh->sessions_pa)
+ return;
+
+ if (!sh->published_count)
+ kho_block_set_it_init(&sh->it, &sh->block_set);
+
+ strscpy(entry.name, session->name, sizeof(entry.name));
+ entry.file_set_ser.count = session->file_set.count;
+ entry.file_set_ser.files =
+ kho_block_set_head_pa(&session->file_set.block_set);
+
+ /* This should not fail normally as blocks were pre-allocated */
+ if (WARN_ON_ONCE(!kho_block_set_it_append(&sh->it, &entry)))
+ return;
+
+ if (!sh->published_count) {
+ /*
+ * Ensure the first block header and entry are visible before
+ * publishing sessions_pa to concurrent KHO readers.
+ */
+ smp_store_release(sh->sessions_pa,
+ kho_block_set_head_pa(&sh->block_set));
+ }
+
+ list_move_tail(&session->list, &sh->published_list);
+ sh->published_count++;
+}
+
static void luo_session_remove(struct luo_session_header *sh,
struct luo_session *session)
{
@@ -636,50 +688,32 @@ int luo_session_deserialize(void)
int luo_session_serialize(void)
{
struct luo_session_header *sh = &luo_session_global.outgoing;
- struct luo_session *session;
- struct kho_block_set_it it;
+ struct luo_session *session, *tmp;
int err;
down_write(&luo_session_serialize_rwsem);
down_write(&sh->rwsem);
- *sh->sessions_pa = 0;
-
- kho_block_set_it_init(&it, &sh->block_set);
list_for_each_entry(session, &sh->list, list) {
- struct luo_session_ser *ser = kho_block_set_it_reserve_entry(&it);
-
- /* This should not fail normally as blocks were pre-allocated */
- if (WARN_ON_ONCE(!ser)) {
- err = -ENOSPC;
- goto err_undo;
- }
-
err = luo_session_freeze_one(session);
if (err) {
- kho_block_set_it_prev(&it);
- goto err_undo;
+ list_for_each_entry_continue_reverse(session, &sh->list, list)
+ luo_session_unfreeze_one(session);
+ goto err_unlock;
}
+ }
- strscpy(ser->name, session->name, sizeof(ser->name));
- ser->file_set_ser.count = session->file_set.count;
- ser->file_set_ser.files =
- kho_block_set_head_pa(&session->file_set.block_set);
+ list_for_each_entry_safe(session, tmp, &sh->list, list) {
+ mutex_lock(&session->mutex);
+ luo_session_publish_one(session);
+ mutex_unlock(&session->mutex);
}
- if (sh->count > 0)
- *sh->sessions_pa = kho_block_set_head_pa(&sh->block_set);
up_write(&sh->rwsem);
return 0;
-err_undo:
- list_for_each_entry_continue_reverse(session, &sh->list, list) {
- struct luo_session_ser *ser = kho_block_set_it_prev(&it);
-
- luo_session_unfreeze_one(session);
- memset(ser, 0, sizeof(*ser));
- }
+err_unlock:
up_write(&sh->rwsem);
up_write(&luo_session_serialize_rwsem);
--
2.56.0.398.g882cbad275-goog
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v5 4/6] liveupdate: luo_flb: support incremental outgoing FLB publication
2026-11-05 5:00 [PATCH v5 0/6] liveupdate: Add LIVEUPDATE_SESSION_FREEZE ioctl Pasha Tatashin
` (2 preceding siblings ...)
2026-11-05 5:00 ` [PATCH v5 3/6] liveupdate: luo: serialize sessions via per-session publish helper Pasha Tatashin
@ 2026-11-05 5:00 ` Pasha Tatashin
2026-11-05 5:00 ` [PATCH v5 5/6] liveupdate: Add LIVEUPDATE_SESSION_FREEZE ioctl to freeze sessions early Pasha Tatashin
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Pasha Tatashin @ 2026-11-05 5:00 UTC (permalink / raw)
To: linux-kselftest, rppt, shuah, linux-mm, skhan, linux-doc,
vipinsh, linux-kernel, corbet, pasha.tatashin, dmatlack, kexec,
pratyush, rdunlap, graf
When a session is published to KHO via luo_session_publish_one() ahead
of reboot, any FLBs that its preserved files depend on must already be
present in the outgoing KHO FLB array so the KHO tree remains
self-consistent.
Currently, outgoing FLB metadata in luo_ser->flbs_pa is populated only
during reboot by luo_flb_serialize(), leaving luo_ser->flbs_pa empty
when sessions are published earlier.
Publish outgoing FLBs from luo_session_publish_one() via
luo_file_publish_flbs() -> luo_flb_file_publish() ->
luo_flb_ser_publish_one(), and drop the now-redundant
luo_flb_serialize() pass from liveupdate_reboot(). For each published
file, luo_flb_ser_publish_one() either appends a new luo_flb_ser entry
with count = 1 using smp_store_release() on header_ser->count or
increments the existing entry's count in place, keeping the serialized
count equal to the exact number of published files in KHO without
including uncommitted sessions or transient
liveupdate_flb_get_outgoing() references.
FLB publication is one-way and never needs to unpublish: before reboot
it runs only when a session is committed, and a committed session never
unpreserves its files, keeping private->outgoing.count >= 1 until
reboot so flb->ops->unpreserve() is never called on a published FLB.
If more FDs using an already-published FLB are preserved later,
flb->ops->preserve() is not called again; file handlers update the
shared live object via liveupdate_flb_get_outgoing(). Consequently, FLB
handlers must:
- return a stable root KHO handle in argp->data from .preserve() whose
address does not change as FDs are added later;
- synchronize concurrent mutations to outgoing.obj with their own lock;
- support per-FD rollback when uncommitted sessions close without
disturbing already-committed entries.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/liveupdate/luo_core.c | 10 +--
kernel/liveupdate/luo_file.c | 16 +++++
kernel/liveupdate/luo_flb.c | 111 ++++++++++++++++++++-----------
kernel/liveupdate/luo_internal.h | 3 +-
kernel/liveupdate/luo_session.c | 2 +
5 files changed, 95 insertions(+), 47 deletions(-)
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 1b2bda22902d..9fef5161a1f3 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -219,18 +219,10 @@ late_initcall(luo_late_startup);
*/
int liveupdate_reboot(void)
{
- int err;
-
if (!liveupdate_enabled())
return 0;
- err = luo_session_serialize();
- if (err)
- return err;
-
- luo_flb_serialize();
-
- return 0;
+ return luo_session_serialize();
}
/**
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index 988eb313a135..611b9967a3a9 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -380,6 +380,22 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set)
kho_block_set_destroy(&file_set->block_set);
}
+/**
+ * luo_file_publish_flbs - Publish FLBs for all preserved files in a file_set.
+ * @file_set: The file_set being published to the outgoing KHO subtree.
+ *
+ * Iterates through all preserved files in @file_set and ensures their
+ * associated FLB metadata is published in the outgoing KHO FLB array before
+ * the session itself becomes visible in KHO.
+ */
+void luo_file_publish_flbs(struct luo_file_set *file_set)
+{
+ struct luo_file *luo_file;
+
+ list_for_each_entry(luo_file, &file_set->files_list, list)
+ luo_flb_file_publish(luo_file->fh);
+}
+
static int luo_file_freeze_one(struct luo_file_set *file_set,
struct luo_file *luo_file)
{
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index cd715a7c1d99..6cd87a9f8307 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -34,6 +34,15 @@
* This reference-counted approach ensures that shared state is saved exactly
* once and restored exactly once, regardless of how many files depend on it,
* and that its lifecycle is correctly managed across the kexec transition.
+ *
+ * Serialization: Outgoing FLB metadata is published into the KHO FLB array
+ * incrementally as sessions are published to KHO (when committed early via
+ * LIVEUPDATE_SESSION_FREEZE_COMMIT or during reboot). Because a committed
+ * session never unpreserves its files, FLB publication is one-way: once
+ * published, the FLB's .unpreserve() callback is never invoked and its root
+ * KHO handle from .preserve() remains immutable in KHO, while later sessions
+ * may still add or remove per-FD state in the shared live object under the FLB
+ * handler's own synchronization.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -72,6 +81,13 @@ static struct luo_flb_global luo_flb_global = {
.list = LIST_HEAD_INIT(luo_flb_global.list),
};
+/*
+ * Serializes updates to luo_flb_global.outgoing.header_ser and .ser[].
+ * Lock hierarchy:
+ * luo_register_rwlock -> private->outgoing.lock -> luo_flb_ser_lock
+ */
+static DEFINE_MUTEX(luo_flb_ser_lock);
+
/*
* struct luo_flb_link - Links an FLB definition to a file handler's internal
* list of dependencies.
@@ -104,6 +120,36 @@ static struct luo_flb_private *luo_flb_get_private(struct liveupdate_flb *flb)
return private;
}
+static void luo_flb_ser_publish_one(const struct liveupdate_flb *flb, u64 data)
+{
+ struct luo_flb_header *fh = &luo_flb_global.outgoing;
+ u64 nr;
+
+ guard(mutex)(&luo_flb_ser_lock);
+ if (!fh->active)
+ return;
+
+ nr = fh->header_ser->count;
+ for (u64 i = 0; i < nr; i++) {
+ if (!strcmp(fh->ser[i].name, flb->compatible)) {
+ WRITE_ONCE(fh->ser[i].count, fh->ser[i].count + 1);
+ return;
+ }
+ }
+
+ if (WARN_ON_ONCE(nr >= LUO_FLB_MAX))
+ return;
+
+ strscpy(fh->ser[nr].name, flb->compatible, sizeof(fh->ser[nr].name));
+ fh->ser[nr].data = data;
+ fh->ser[nr].count = 1;
+ /*
+ * Ensure the serialized FLB entry is visible before exposing the
+ * incremented count to concurrent KHO readers.
+ */
+ smp_store_release(&fh->header_ser->count, nr + 1);
+}
+
static int luo_flb_file_preserve_one(struct liveupdate_flb *flb)
{
struct luo_flb_private *private = luo_flb_get_private(flb);
@@ -296,6 +342,34 @@ void luo_flb_file_unpreserve(struct liveupdate_file_handler *fh)
liveupdate_flb_put_outgoing(iter->flb);
}
+/**
+ * luo_flb_file_publish - Publish FLB metadata for a preserved file's handler.
+ * @fh: The file handler of the preserved file being published into KHO.
+ *
+ * Publishes each FLB associated with @fh into the outgoing KHO FLB array on
+ * first publication, or increments its serialized file reference count by one.
+ *
+ * Context: Called when a session containing preserved files is published into
+ * the outgoing KHO subtree.
+ */
+void luo_flb_file_publish(struct liveupdate_file_handler *fh)
+{
+ struct list_head *flb_list = &ACCESS_PRIVATE(fh, flb_list);
+ struct luo_flb_link *iter;
+
+ guard(rwsem_read)(&luo_register_rwlock);
+ list_for_each_entry(iter, flb_list, list) {
+ struct luo_flb_private *private = luo_flb_get_private(iter->flb);
+
+ scoped_guard(mutex, &private->outgoing.lock) {
+ if (WARN_ON_ONCE(!refcount_read(&private->outgoing.count)))
+ continue;
+ luo_flb_ser_publish_one(iter->flb,
+ private->outgoing.data);
+ }
+ }
+}
+
/**
* luo_flb_file_finish - Notifies FLBs that a dependent file has been finished.
* @fh: The file handler for the finished file.
@@ -587,40 +661,3 @@ void __init luo_flb_setup_incoming(u64 flbs_pa)
luo_flb_global.incoming.ser = (void *)(header_ser + 1);
luo_flb_global.incoming.active = true;
}
-
-/**
- * luo_flb_serialize - Serializes all active FLB objects for KHO.
- *
- * This function is called from the reboot path. It iterates through all
- * registered File-Lifecycle-Bound (FLB) objects. For each FLB that has been
- * preserved (i.e., its reference count is greater than zero), it writes its
- * metadata into the memory region designated for Kexec Handover.
- *
- * The serialized data includes the FLB's compatibility string, its opaque
- * data handle, and the final reference count. This allows the new kernel to
- * find the appropriate handler and reconstruct the FLB's state.
- *
- * Context: Called from liveupdate_reboot() just before kho_finalize().
- */
-void luo_flb_serialize(void)
-{
- struct luo_flb_header *fh = &luo_flb_global.outgoing;
- struct liveupdate_flb *gflb;
- int i = 0;
-
- guard(rwsem_read)(&luo_register_rwlock);
- list_private_for_each_entry(gflb, &luo_flb_global.list, private.list) {
- struct luo_flb_private *private = luo_flb_get_private(gflb);
- long count = refcount_read(&private->outgoing.count);
-
- if (count > 0) {
- strscpy(fh->ser[i].name, gflb->compatible,
- sizeof(fh->ser[i].name));
- fh->ser[i].data = private->outgoing.data;
- fh->ser[i].count = count;
- i++;
- }
- }
-
- fh->header_ser->count = i;
-}
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 01a172cd9d7a..a7253b48df80 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -103,6 +103,7 @@ int luo_session_deserialize(void);
int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd);
void luo_file_unpreserve_files(struct luo_file_set *file_set);
+void luo_file_publish_flbs(struct luo_file_set *file_set);
int luo_file_freeze(struct luo_file_set *file_set);
void luo_file_unfreeze(struct luo_file_set *file_set);
int luo_retrieve_file(struct luo_file_set *file_set, u64 token,
@@ -115,11 +116,11 @@ void luo_file_set_destroy(struct luo_file_set *file_set);
int luo_flb_file_preserve(struct liveupdate_file_handler *fh);
void luo_flb_file_unpreserve(struct liveupdate_file_handler *fh);
+void luo_flb_file_publish(struct liveupdate_file_handler *fh);
void luo_flb_file_finish(struct liveupdate_file_handler *fh);
void luo_flb_unregister_all(struct liveupdate_file_handler *fh);
int __init luo_flb_setup_outgoing(u64 *flbs_pa);
void __init luo_flb_setup_incoming(u64 flbs_pa);
-void luo_flb_serialize(void);
#ifdef CONFIG_LIVEUPDATE_TEST
void liveupdate_test_register(struct liveupdate_file_handler *fh);
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index caf0e85081e8..713b9b400f07 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -233,6 +233,8 @@ static void luo_session_publish_one(struct luo_session *session)
if (!sh->published_count)
kho_block_set_it_init(&sh->it, &sh->block_set);
+ luo_file_publish_flbs(&session->file_set);
+
strscpy(entry.name, session->name, sizeof(entry.name));
entry.file_set_ser.count = session->file_set.count;
entry.file_set_ser.files =
--
2.56.0.398.g882cbad275-goog
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v5 5/6] liveupdate: Add LIVEUPDATE_SESSION_FREEZE ioctl to freeze sessions early
2026-11-05 5:00 [PATCH v5 0/6] liveupdate: Add LIVEUPDATE_SESSION_FREEZE ioctl Pasha Tatashin
` (3 preceding siblings ...)
2026-11-05 5:00 ` [PATCH v5 4/6] liveupdate: luo_flb: support incremental outgoing FLB publication Pasha Tatashin
@ 2026-11-05 5:00 ` Pasha Tatashin
2026-11-05 5:00 ` [PATCH v5 6/6] selftests: liveupdate: Add tests for LIVEUPDATE_SESSION_FREEZE Pasha Tatashin
2026-11-11 9:56 ` [PATCH v5 0/6] liveupdate: Add LIVEUPDATE_SESSION_FREEZE ioctl Mike Rapoport
6 siblings, 0 replies; 8+ messages in thread
From: Pasha Tatashin @ 2026-11-05 5:00 UTC (permalink / raw)
To: linux-kselftest, rppt, shuah, linux-mm, skhan, linux-doc,
vipinsh, linux-kernel, corbet, pasha.tatashin, dmatlack, kexec,
pratyush, rdunlap, graf
Currently, LUO sessions are frozen automatically only inside the
reboot(LINUX_REBOOT_CMD_KEXEC) pathway, where luo_session_serialize()
iterates through all outgoing sessions under global write locks and
freezes them serially during shutdown. On hosts running multiple
workloads that quiesce one by one ahead of kexec, userspace needs an
explicit per-session freeze point so each session can capture final file
state under its per-session mutex as soon as its workload pauses.
Introduce LIVEUPDATE_SESSION_FREEZE on session FDs to freeze an outgoing
session early. The ioctl runs .freeze() across all preserved files in
the session and serializes their metadata. Once frozen, no additional
files can be added to the session, and subsequent freeze calls return
-EBUSY. Freeze is only valid on outgoing sessions and returns -EINVAL on
incoming ones.
By default (flags == 0), session lifetime remains bound to the session
FD: the session stays frozen while the FD remains open, and closing the
session FD before reboot (for example, to abort the session or on
process exit/crash) unfreezes it via .unfreeze(), unpreserves its files,
and reclaims all resources. During kexec, already-frozen sessions skip
the late auto-freeze and are published into the outgoing KHO subtree.
With LIVEUPDATE_SESSION_FREEZE_COMMIT set in
struct liveupdate_session_freeze.flags, the frozen session is committed:
it and its FLB dependencies are immediately inserted into the outgoing
KHO subtree via luo_session_publish_one(), and the session survives
close() with full ownership retained by LUO until reboot. Once
committed, closing the session FD (or another session failing to freeze
during reboot) never unfreezes or unpreserves the session, allowing
processes to hand off preserved state and exit prior to kexec.
Update the uAPI and core LUO documentation to describe session freeze
and commit semantics.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
Documentation/userspace-api/liveupdate.rst | 30 +++++++++
include/linux/liveupdate.h | 3 +-
include/uapi/linux/liveupdate.h | 46 +++++++++++++
kernel/liveupdate/luo_file.c | 54 +++++++--------
kernel/liveupdate/luo_internal.h | 7 ++
kernel/liveupdate/luo_session.c | 77 ++++++++++++++++++++--
6 files changed, 184 insertions(+), 33 deletions(-)
diff --git a/Documentation/userspace-api/liveupdate.rst b/Documentation/userspace-api/liveupdate.rst
index cdd74627ab75..d36537c556cc 100644
--- a/Documentation/userspace-api/liveupdate.rst
+++ b/Documentation/userspace-api/liveupdate.rst
@@ -22,6 +22,36 @@ change, so compatibility is not guaranteed when kexec rebooting between
different kernel versions. This is expected to change and stabilize in a future
version.
+Session Freeze and Commit
+=========================
+
+By default, LUO freezes active sessions automatically inside the
+``reboot(LINUX_REBOOT_CMD_KEXEC)`` syscall path, and an outgoing session's
+lifetime is bound to its open session FD: closing the FD before reboot aborts
+preservation and returns all resources.
+
+Userspace may also freeze an outgoing session explicitly ahead of reboot via
+``ioctl(session_fd, LIVEUPDATE_SESSION_FREEZE, &arg)`` once it has finished
+preserving and modifying the session's files:
+
+- **Freeze** (``flags == 0``): Executes ``.freeze()`` on all preserved files in
+ the session under the per-session lock (allowing multiple sessions to freeze
+ concurrently), while keeping session lifetime tied to ``session_fd``. When
+ ``session_fd`` is closed before reboot (for example, to abort the session or
+ on process exit/crash), LUO runs ``.unfreeze()`` and ``.unpreserve()`` on all
+ files in the session and reclaims all resources. As long as ``session_fd``
+ remains open, the session stays frozen across any failed ``reboot()`` attempt;
+ at kexec time, already-frozen sessions skip the late auto-freeze step and are
+ published into the outgoing KHO subtree.
+- **Freeze and commit** (``LIVEUPDATE_SESSION_FREEZE_COMMIT``): Freezes the
+ session's files, immediately inserts the session and its FLB dependencies
+ into the outgoing KHO subtree, and commits the session so that it survives
+ closing ``session_fd`` with ownership retained by LUO until reboot. Once
+ committed, closing ``session_fd`` (or another session failing to freeze
+ during ``reboot()``) will neither unfreeze nor unpreserve the session,
+ allowing processes to hand off preserved state to LUO and exit prior to
+ kexec.
+
Userspace Integration
=====================
diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
index 6051abc0612c..851692571e13 100644
--- a/include/linux/liveupdate.h
+++ b/include/linux/liveupdate.h
@@ -59,7 +59,8 @@ struct liveupdate_file_op_args {
* compatible with the given file.
* @preserve: Required. Performs state-saving for the file.
* @unpreserve: Required. Cleans up any resources allocated by @preserve.
- * @freeze: Optional. Final actions just before kernel transition.
+ * @freeze: Optional. Prepares final state before kexec (via
+ * LIVEUPDATE_SESSION_FREEZE or during reboot).
* @unfreeze: Optional. Undo freeze operations.
* @retrieve: Required. Restores the file in the new kernel.
* @can_finish: Optional. Check if this FD can finish, i.e. all restoration
diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
index 4043d4038712..ba424dc51578 100644
--- a/include/uapi/linux/liveupdate.h
+++ b/include/uapi/linux/liveupdate.h
@@ -60,6 +60,7 @@ enum {
LIVEUPDATE_CMD_SESSION_RETRIEVE_FD = 0x41,
LIVEUPDATE_CMD_SESSION_FINISH = 0x42,
LIVEUPDATE_CMD_SESSION_GET_NAME = 0x43,
+ LIVEUPDATE_CMD_SESSION_FREEZE = 0x44,
};
/**
@@ -236,4 +237,49 @@ struct liveupdate_session_get_name {
#define LIVEUPDATE_SESSION_GET_NAME \
_IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SESSION_GET_NAME)
+/**
+ * enum liveupdate_session_freeze_flags - Flags for LIVEUPDATE_SESSION_FREEZE
+ * @LIVEUPDATE_SESSION_FREEZE_COMMIT: Commit the frozen session. The session is
+ * immediately inserted into the outgoing KHO
+ * tree and survives closing the session FD
+ * until reboot.
+ */
+enum liveupdate_session_freeze_flags {
+ LIVEUPDATE_SESSION_FREEZE_COMMIT = 1 << 0,
+};
+
+/**
+ * struct liveupdate_session_freeze - ioctl(LIVEUPDATE_SESSION_FREEZE)
+ * @size: Input; sizeof(struct liveupdate_session_freeze)
+ * @flags: Input; Combination of enum liveupdate_session_freeze_flags.
+ *
+ * Freezes the session from userspace before the kexec reboot phase.
+ *
+ * When this ioctl is called, the kernel executes the .freeze() callback on all
+ * preserved files in the session and serializes their metadata. Once frozen,
+ * no further files can be preserved in the session, and subsequent freeze
+ * attempts return -EBUSY.
+ *
+ * By default (@flags is 0), session lifetime remains tied to the session FD:
+ * if the session FD is closed before reboot (for example, if the application
+ * exits or crashes), the session is unfrozen, its preserved files are
+ * unpreserved, and all resources are returned.
+ *
+ * If %LIVEUPDATE_SESSION_FREEZE_COMMIT is set in @flags, the frozen session is
+ * committed: it is immediately inserted into the outgoing KHO tree and survives
+ * closing the session FD, transferring full ownership of the session and its
+ * preserved resources to LUO until reboot. Once committed, the session is never
+ * unfrozen or unpreserved, even if the session FD is closed or if another
+ * session fails to freeze during reboot.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+struct liveupdate_session_freeze {
+ __u32 size;
+ __u32 flags;
+};
+
+#define LIVEUPDATE_SESSION_FREEZE \
+ _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SESSION_FREEZE)
+
#endif /* _UAPI_LIVEUPDATE_H */
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index 611b9967a3a9..afed2b59f489 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -31,9 +31,9 @@
* - unpreserve(): Cleans up any resources allocated by .preserve(), called
* if the preservation process is aborted before the reboot (i.e. session is
* closed).
- * - freeze(): A final pre-reboot opportunity to prepare the state for kexec.
- * We are already in reboot syscall, and therefore userspace cannot mutate
- * the file anymore.
+ * - freeze(): Prepares the final state for kexec. Invoked either via
+ * the LIVEUPDATE_SESSION_FREEZE ioctl once userspace is done modifying
+ * the session's files, or during the reboot() syscall.
* - unfreeze(): Undoes the actions of .freeze(), called if the live update
* is aborted after the freeze phase.
* - retrieve(): Reconstructs the file in the new kernel from the preserved
@@ -48,7 +48,8 @@
* handler, calls its .preserve() operation, and creates an internal &struct
* luo_file to track the live state.
*
- * 2. Freeze (Pre-Reboot): Just before the kexec, luo_file_freeze() is called.
+ * 2. Freeze (Pre-Reboot): Either explicitly via LIVEUPDATE_SESSION_FREEZE or
+ * just before the kexec during reboot(), luo_file_freeze() is called.
* It iterates through all preserved files, calls their respective .freeze()
* operation, and serializes their final metadata (compatible string, token,
* and data handle) into a contiguous memory block for KHO.
@@ -72,17 +73,19 @@
*
* File Preservation Lifecycle unhappy paths:
*
- * 1. Abort Before Reboot: If the userspace agent aborts the live update
- * process before calling reboot (e.g., by closing the session file
- * descriptor), the session's release handler calls
- * luo_file_unpreserve_files(). This invokes the .unpreserve() callback on
- * all preserved files, ensuring all allocated resources are cleaned up and
- * returning the system to a clean state.
+ * 1. Abort Before Reboot: When an uncommitted session FD is closed before
+ * reboot (e.g., to abort preservation or on process exit), the session's
+ * release handler unfreezes the files if the session was frozen via
+ * LIVEUPDATE_SESSION_FREEZE and calls luo_file_unpreserve_files(). This
+ * invokes the .unpreserve() callback on all preserved files, ensuring all
+ * allocated resources are cleaned up and returning the system to a clean
+ * state. Committed sessions survive closing their FD and remain owned by
+ * LUO until reboot.
*
- * 2. Freeze Failure: During the reboot() syscall, if any handler's .freeze()
- * op fails, the .unfreeze() op is invoked on all previously *successful*
- * freezes to roll back their state. The reboot() syscall then returns an
- * error to userspace, canceling the live update.
+ * 2. Freeze Failure: During LIVEUPDATE_SESSION_FREEZE or the reboot() syscall,
+ * if any handler's .freeze() op fails, the .unfreeze() op is invoked on all
+ * previously *successful* freezes to roll back their state, and an error is
+ * returned to userspace, canceling the live update if called from reboot().
*
* 3. Finish Failure: In the new kernel, if a handler's .finish() op fails,
* the luo_file_finish() operation is aborted. LUO retains ownership of
@@ -458,9 +461,9 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
* luo_file_freeze - Freezes all preserved files and serializes their metadata.
* @file_set: The file_set whose files are to be frozen.
*
- * This function is called from the reboot() syscall path, just before the
- * kernel transitions to the new image via kexec. Its purpose is to perform the
- * final preparation and serialization of all preserved files in the file_set.
+ * This function is called from LIVEUPDATE_SESSION_FREEZE or from the reboot()
+ * syscall path. Its purpose is to perform the final preparation and
+ * serialization of all preserved files in the file_set.
*
* It iterates through each preserved file in FIFO order (the order of
* preservation) and performs two main actions:
@@ -477,13 +480,12 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
*
* Error Handling (Rollback):
* This function is atomic. If any handler's .freeze() operation fails, the
- * entire live update is aborted. The __luo_file_unfreeze() helper is
- * immediately called to invoke the .unfreeze() op on all files that were
- * successfully frozen before the point of failure, rolling them back to a
- * running state. The function then returns an error, causing the reboot()
- * syscall to fail.
+ * __luo_file_unfreeze() helper is immediately called to invoke the .unfreeze()
+ * op on all files that were successfully frozen before the point of failure,
+ * rolling them back to an unfrozen state, and an error is returned to the
+ * caller, canceling the live update if called from the reboot() path.
*
- * Context: Called only from the liveupdate_reboot() path.
+ * Context: Called from LIVEUPDATE_SESSION_FREEZE or the liveupdate_reboot() path.
* Return: 0 on success, or a negative errno on failure.
*/
int luo_file_freeze(struct luo_file_set *file_set)
@@ -533,15 +535,15 @@ int luo_file_freeze(struct luo_file_set *file_set)
* @file_set: The file_set whose files are to be unfrozen.
*
* This function rolls back the state of all files in a file_set after the
- * freeze phase has begun but must be aborted. It is the counterpart to
- * luo_file_freeze().
+ * freeze phase has begun. It is the counterpart to luo_file_freeze().
*
* It invokes the __luo_file_unfreeze() helper with a NULL argument, which
* signals the helper to iterate through all files in the file_set, call their
* respective .unfreeze() handler callbacks, and clear the serialized blocks.
*
* Context: This is called when the live update is aborted during
- * the reboot() syscall, after luo_file_freeze() has been called.
+ * the reboot() syscall, after luo_file_freeze() has been called,
+ * or when a frozen uncommitted session is released before kexec.
*/
void luo_file_unfreeze(struct luo_file_set *file_set)
{
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index a7253b48df80..f1ecc8586ea1 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -64,6 +64,11 @@ struct luo_file_set {
* previous kernel) sessions.
* @retrieved: A boolean flag indicating whether this session has been
* retrieved by a consumer in the new kernel.
+ * @user_frozen: A boolean flag indicating whether this session has been
+ * frozen explicitly from userspace.
+ * @committed: A boolean flag indicating whether this session has been
+ * committed (frozen, published into the outgoing KHO tree,
+ * and owned by LUO across session FD close until reboot).
* @file_set: A set of files that belong to this session.
* @mutex: protects fields in the luo_session.
*/
@@ -71,6 +76,8 @@ struct luo_session {
char name[LIVEUPDATE_SESSION_NAME_LENGTH];
struct list_head list;
bool retrieved;
+ bool user_frozen;
+ bool committed;
struct luo_file_set file_set;
struct mutex mutex;
};
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 713b9b400f07..4358a5e77c38 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -34,10 +34,16 @@
* 1. Creation: A userspace agent calls `luo_session_create()` to create a
* new, empty session and receives a file descriptor for it.
*
- * 2. Serialization: When the `reboot(LINUX_REBOOT_CMD_KEXEC)` syscall is
- * made, `luo_session_serialize()` is called. It iterates through all
- * active sessions and writes their metadata into a memory area preserved
- * by KHO.
+ * 2. Freeze / Serialization: Sessions may be frozen explicitly from userspace
+ * via `LIVEUPDATE_SESSION_FREEZE`. With `flags == 0`, the session's files
+ * are frozen while keeping the session tied to its FD so closing it
+ * unfreezes and unpreserves the session. With
+ * `LIVEUPDATE_SESSION_FREEZE_COMMIT`, the frozen session is committed: it
+ * is immediately published with its FLBs into the outgoing KHO tree and
+ * survives closing the session FD until reboot. When the
+ * `reboot(LINUX_REBOOT_CMD_KEXEC)` syscall is made,
+ * `luo_session_serialize()` freezes any remaining unfrozen sessions and
+ * publishes all remaining unpublished sessions into the KHO tree.
*
* 3. Deserialization (in new kernel): After kexec, `luo_session_deserialize()`
* runs, reading the serialized data and creating a list of `struct
@@ -276,12 +282,21 @@ static int luo_session_finish_one(struct luo_session *session)
static void luo_session_unfreeze_one(struct luo_session *session)
{
guard(mutex)(&session->mutex);
- luo_file_unfreeze(&session->file_set);
+ /*
+ * Only unfreeze sessions that were auto-frozen by
+ * luo_session_freeze_one(); sessions frozen explicitly by
+ * userspace stay frozen until the session FD is closed.
+ */
+ if (!session->user_frozen)
+ luo_file_unfreeze(&session->file_set);
}
static int luo_session_freeze_one(struct luo_session *session)
{
guard(mutex)(&session->mutex);
+ if (session->user_frozen)
+ return 0;
+
return luo_file_freeze(&session->file_set);
}
@@ -302,8 +317,13 @@ static int luo_session_release(struct inode *inodep, struct file *filep)
}
sh = &luo_session_global.incoming;
} else {
- scoped_guard(mutex, &session->mutex)
+ scoped_guard(mutex, &session->mutex) {
+ if (session->committed)
+ return 0;
+ if (session->user_frozen)
+ luo_file_unfreeze(&session->file_set);
luo_file_unpreserve_files(&session->file_set);
+ }
sh = &luo_session_global.outgoing;
}
@@ -320,6 +340,9 @@ static int luo_session_preserve_fd(struct luo_session *session,
int err;
guard(mutex)(&session->mutex);
+ if (session->user_frozen)
+ return -EBUSY;
+
err = luo_preserve_file(&session->file_set, argp->token, argp->fd);
if (err)
return err;
@@ -393,11 +416,51 @@ static int luo_session_get_name(struct luo_session *session,
return luo_ucmd_respond(ucmd, sizeof(*argp));
}
+static int luo_session_freeze(struct luo_session *session,
+ struct luo_ucmd *ucmd)
+{
+ struct liveupdate_session_freeze *argp = ucmd->cmd;
+ int err;
+
+ if (argp->flags & ~LIVEUPDATE_SESSION_FREEZE_COMMIT)
+ return -EOPNOTSUPP;
+
+ scoped_guard(mutex, &session->mutex) {
+ if (session->user_frozen)
+ return -EBUSY;
+
+ err = luo_file_freeze(&session->file_set);
+ if (err)
+ return err;
+
+ err = luo_ucmd_respond(ucmd, sizeof(*argp));
+ if (err) {
+ luo_file_unfreeze(&session->file_set);
+ return err;
+ }
+
+ session->user_frozen = true;
+ }
+
+ if (argp->flags & LIVEUPDATE_SESSION_FREEZE_COMMIT) {
+ struct luo_session_header *sh = &luo_session_global.outgoing;
+
+ guard(rwsem_write)(&sh->rwsem);
+ guard(mutex)(&session->mutex);
+
+ luo_session_publish_one(session);
+ session->committed = true;
+ }
+
+ return 0;
+}
+
union ucmd_buffer {
struct liveupdate_session_finish finish;
struct liveupdate_session_preserve_fd preserve;
struct liveupdate_session_retrieve_fd retrieve;
struct liveupdate_session_get_name get_name;
+ struct liveupdate_session_freeze freeze;
};
/* Type of sessions the ioctl applies to. */
@@ -435,6 +498,8 @@ static const struct luo_ioctl_op luo_session_ioctl_ops[] = {
struct liveupdate_session_retrieve_fd, token, LUO_IOCTL_INCOMING),
IOCTL_OP(LIVEUPDATE_SESSION_GET_NAME, luo_session_get_name,
struct liveupdate_session_get_name, name, LUO_IOCTL_ALL),
+ IOCTL_OP(LIVEUPDATE_SESSION_FREEZE, luo_session_freeze,
+ struct liveupdate_session_freeze, flags, LUO_IOCTL_OUTGOING),
};
static bool luo_ioctl_type_valid(struct luo_session *session,
--
2.56.0.398.g882cbad275-goog
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v5 6/6] selftests: liveupdate: Add tests for LIVEUPDATE_SESSION_FREEZE
2026-11-05 5:00 [PATCH v5 0/6] liveupdate: Add LIVEUPDATE_SESSION_FREEZE ioctl Pasha Tatashin
` (4 preceding siblings ...)
2026-11-05 5:00 ` [PATCH v5 5/6] liveupdate: Add LIVEUPDATE_SESSION_FREEZE ioctl to freeze sessions early Pasha Tatashin
@ 2026-11-05 5:00 ` Pasha Tatashin
2026-11-11 9:56 ` [PATCH v5 0/6] liveupdate: Add LIVEUPDATE_SESSION_FREEZE ioctl Mike Rapoport
6 siblings, 0 replies; 8+ messages in thread
From: Pasha Tatashin @ 2026-11-05 5:00 UTC (permalink / raw)
To: linux-kselftest, rppt, shuah, linux-mm, skhan, linux-doc,
vipinsh, linux-kernel, corbet, pasha.tatashin, dmatlack, kexec,
pratyush, rdunlap, graf
Add a luo_session_freeze(session_fd, flags) helper and selftests for the
LIVEUPDATE_SESSION_FREEZE ioctl:
- liveupdate.c: verify unsupported flags return -EOPNOTSUPP, repeated
freeze and post-freeze preserve return -EBUSY, closing a frozen
uncommitted session unfreezes and returns resources, and closing a
committed session (LIVEUPDATE_SESSION_FREEZE_COMMIT) retains LUO
ownership of the session and its preserved files.
- luo_kexec_simple.c: verify end-to-end kexec preservation of committed
sessions after closing all session FDs and exiting without
daemonize_and_wait(), and verify freezing an incoming session returns
-EINVAL.
- luo_multi_session.c: verify auto-frozen, kept-open frozen, closed
uncommitted frozen, and closed committed sessions together across a
kexec reboot.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
.../liveupdate/lib/include/libliveupdate.h | 5 +-
.../selftests/liveupdate/lib/lu_utils.c | 21 +++-
.../testing/selftests/liveupdate/liveupdate.c | 107 ++++++++++++++++++
.../selftests/liveupdate/luo_kexec_simple.c | 22 +++-
.../selftests/liveupdate/luo_multi_session.c | 33 +++++-
5 files changed, 179 insertions(+), 9 deletions(-)
diff --git a/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h b/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h
index fa07fed08364..a35871cefb82 100644
--- a/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h
+++ b/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h
@@ -26,6 +26,7 @@ int luo_create_session(int luo_fd, const char *name);
int luo_retrieve_session(int luo_fd, const char *name);
int luo_session_finish(int session_fd);
int luo_get_session_name(int session_fd, char *name, size_t name_len);
+int luo_session_freeze(int session_fd, __u32 flags);
int luo_ensure_nofile_limit(long min_limit);
int luo_session_preserve_fd(int session_fd, int fd, __u64 token);
@@ -34,8 +35,8 @@ int luo_session_retrieve_fd(int session_fd, __u64 token);
int create_and_preserve_memfd(int session_fd, int token, const char *data);
int restore_and_verify_memfd(int session_fd, int token, const char *expected_data);
-void create_state_file(int luo_fd, const char *session_name, int token,
- int next_stage);
+int create_state_file(int luo_fd, const char *session_name, int token,
+ int next_stage);
void restore_and_read_stage(int state_session_fd, int token, int *stage);
void daemonize_and_wait(void);
diff --git a/tools/testing/selftests/liveupdate/lib/lu_utils.c b/tools/testing/selftests/liveupdate/lib/lu_utils.c
index 74d41115c281..e71e7e90da27 100644
--- a/tools/testing/selftests/liveupdate/lib/lu_utils.c
+++ b/tools/testing/selftests/liveupdate/lib/lu_utils.c
@@ -198,8 +198,21 @@ int luo_session_finish(int session_fd)
return 0;
}
-void create_state_file(int luo_fd, const char *session_name, int token,
- int next_stage)
+int luo_session_freeze(int session_fd, __u32 flags)
+{
+ struct liveupdate_session_freeze arg = {
+ .size = sizeof(arg),
+ .flags = flags,
+ };
+
+ if (ioctl(session_fd, LIVEUPDATE_SESSION_FREEZE, &arg) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int create_state_file(int luo_fd, const char *session_name, int token,
+ int next_stage)
{
char buf[32];
int state_session_fd;
@@ -213,8 +226,10 @@ void create_state_file(int luo_fd, const char *session_name, int token,
fail_exit("create_and_preserve_memfd for state tracking");
/*
- * DO NOT close session FD, otherwise it is going to be unpreserved
+ * DO NOT close session FD unless committed, otherwise it is going to
+ * be unpreserved.
*/
+ return state_session_fd;
}
void restore_and_read_stage(int state_session_fd, int token, int *stage)
diff --git a/tools/testing/selftests/liveupdate/liveupdate.c b/tools/testing/selftests/liveupdate/liveupdate.c
index 2dedd5fc2534..16b210da37a9 100644
--- a/tools/testing/selftests/liveupdate/liveupdate.c
+++ b/tools/testing/selftests/liveupdate/liveupdate.c
@@ -530,4 +530,111 @@ TEST_F(liveupdate_device, preserve_many_files)
ASSERT_EQ(close(session_fd), 0);
}
+/*
+ * Test Case: Session Freeze
+ *
+ * Verifies LIVEUPDATE_SESSION_FREEZE with flags == 0: unsupported flags return
+ * -EOPNOTSUPP, repeated freeze and post-freeze preserve return -EBUSY, and
+ * closing the session FD unfreezes the session and returns all resources.
+ */
+TEST_F(liveupdate_device, session_freeze)
+{
+ int session_fd, mem_fd1, mem_fd2;
+
+ self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+ if (self->fd1 < 0 && errno == ENOENT)
+ SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+ ASSERT_GE(self->fd1, 0);
+
+ session_fd = luo_create_session(self->fd1, "session-freeze");
+ ASSERT_GE(session_fd, 0);
+
+ mem_fd1 = memfd_create("freeze-memfd-1", 0);
+ ASSERT_GE(mem_fd1, 0);
+ ASSERT_EQ(luo_session_preserve_fd(session_fd, mem_fd1, 0x1111), 0);
+
+ /* Unsupported flag bits must fail with -EOPNOTSUPP */
+ EXPECT_EQ(luo_session_freeze(session_fd, 1U << 1), -EOPNOTSUPP);
+
+ /* Freeze the session */
+ ASSERT_EQ(luo_session_freeze(session_fd, 0), 0);
+
+ /* Subsequent freeze attempts must fail with -EBUSY */
+ EXPECT_EQ(luo_session_freeze(session_fd, 0), -EBUSY);
+ EXPECT_EQ(luo_session_freeze(session_fd,
+ LIVEUPDATE_SESSION_FREEZE_COMMIT),
+ -EBUSY);
+
+ /* Preserving a file after freeze must fail with -EBUSY */
+ mem_fd2 = memfd_create("freeze-memfd-2", 0);
+ ASSERT_GE(mem_fd2, 0);
+ EXPECT_EQ(luo_session_preserve_fd(session_fd, mem_fd2, 0x2222), -EBUSY);
+
+ /* Closing the session FD must unfreeze and unpreserve resources */
+ ASSERT_EQ(close(session_fd), 0);
+
+ session_fd = luo_create_session(self->fd1, "session-freeze");
+ ASSERT_GE(session_fd, 0);
+ ASSERT_EQ(luo_session_preserve_fd(session_fd, mem_fd1, 0x1111), 0);
+
+ ASSERT_EQ(close(session_fd), 0);
+ ASSERT_EQ(close(mem_fd1), 0);
+ ASSERT_EQ(close(mem_fd2), 0);
+}
+
+/*
+ * Test Case: Session Freeze and Commit
+ *
+ * Verifies LIVEUPDATE_SESSION_FREEZE with LIVEUPDATE_SESSION_FREEZE_COMMIT:
+ * once committed, the frozen session is inserted into KHO and survives closing
+ * the session FD with ownership retained by LUO until reboot.
+ */
+TEST_F(liveupdate_device, session_freeze_commit)
+{
+ char name[LIVEUPDATE_SESSION_NAME_LENGTH];
+ int session_fd, session_fd2, mem_fd1, mem_fd2;
+
+ self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+ if (self->fd1 < 0 && errno == ENOENT)
+ SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+ ASSERT_GE(self->fd1, 0);
+
+ snprintf(name, sizeof(name), "freeze-commit-%d", getpid());
+ session_fd = luo_create_session(self->fd1, name);
+ ASSERT_GE(session_fd, 0);
+
+ mem_fd1 = memfd_create("freeze-commit-memfd-1", 0);
+ ASSERT_GE(mem_fd1, 0);
+ ASSERT_EQ(luo_session_preserve_fd(session_fd, mem_fd1, 0x1111), 0);
+
+ /* Freeze and commit the session */
+ ASSERT_EQ(luo_session_freeze(session_fd,
+ LIVEUPDATE_SESSION_FREEZE_COMMIT), 0);
+
+ /* Subsequent freeze attempts must fail with -EBUSY */
+ EXPECT_EQ(luo_session_freeze(session_fd, 0), -EBUSY);
+ EXPECT_EQ(luo_session_freeze(session_fd,
+ LIVEUPDATE_SESSION_FREEZE_COMMIT),
+ -EBUSY);
+
+ /* Preserving a file after freeze must fail with -EBUSY */
+ mem_fd2 = memfd_create("freeze-commit-memfd-2", 0);
+ ASSERT_GE(mem_fd2, 0);
+ EXPECT_EQ(luo_session_preserve_fd(session_fd, mem_fd2, 0x2222), -EBUSY);
+
+ /* Closing the session FD must retain LUO ownership of session & files */
+ ASSERT_EQ(close(session_fd), 0);
+
+ EXPECT_EQ(luo_create_session(self->fd1, name), -EEXIST);
+
+ session_fd2 = luo_create_session(self->fd1, "freeze-commit-check");
+ ASSERT_GE(session_fd2, 0);
+ EXPECT_EQ(luo_session_preserve_fd(session_fd2, mem_fd1, 0x3333),
+ -EBUSY);
+
+ ASSERT_EQ(close(session_fd2), 0);
+ ASSERT_EQ(close(mem_fd1), 0);
+ ASSERT_EQ(close(mem_fd2), 0);
+}
+
TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/liveupdate/luo_kexec_simple.c b/tools/testing/selftests/liveupdate/luo_kexec_simple.c
index 786ac93b9ae3..f065418a047f 100644
--- a/tools/testing/selftests/liveupdate/luo_kexec_simple.c
+++ b/tools/testing/selftests/liveupdate/luo_kexec_simple.c
@@ -21,12 +21,18 @@
/* Stage 1: Executed before the kexec reboot. */
static void run_stage_1(int luo_fd)
{
- int session_fd;
+ int session_fd, state_session_fd;
ksft_print_msg("[STAGE 1] Starting pre-kexec setup...\n");
ksft_print_msg("[STAGE 1] Creating state file for next stage (2)...\n");
- create_state_file(luo_fd, STATE_SESSION_NAME, STATE_MEMFD_TOKEN, 2);
+ state_session_fd = create_state_file(luo_fd, STATE_SESSION_NAME,
+ STATE_MEMFD_TOKEN, 2);
+ if (luo_session_freeze(state_session_fd,
+ LIVEUPDATE_SESSION_FREEZE_COMMIT) < 0) {
+ fail_exit("luo_session_freeze for '%s'", STATE_SESSION_NAME);
+ }
+ close(state_session_fd);
ksft_print_msg("[STAGE 1] Creating session '%s' and preserving memfd...\n",
TEST_SESSION_NAME);
@@ -40,8 +46,15 @@ static void run_stage_1(int luo_fd)
TEST_MEMFD_TOKEN);
}
+ ksft_print_msg("[STAGE 1] Freezing and committing session '%s' and closing FD...\n",
+ TEST_SESSION_NAME);
+ if (luo_session_freeze(session_fd,
+ LIVEUPDATE_SESSION_FREEZE_COMMIT) < 0) {
+ fail_exit("luo_session_freeze for '%s'", TEST_SESSION_NAME);
+ }
+
+ close(session_fd);
close(luo_fd);
- daemonize_and_wait();
}
/* Stage 2: Executed after the kexec reboot. */
@@ -60,6 +73,9 @@ static void run_stage_2(int luo_fd, int state_session_fd)
if (session_fd < 0)
fail_exit("luo_retrieve_session for '%s'", TEST_SESSION_NAME);
+ if (luo_session_freeze(session_fd, 0) != -EINVAL)
+ fail_exit("luo_session_freeze on incoming session did not return -EINVAL");
+
ksft_print_msg("[STAGE 2] Restoring and verifying memfd (token %#x)...\n",
TEST_MEMFD_TOKEN);
mfd = restore_and_verify_memfd(session_fd, TEST_MEMFD_TOKEN,
diff --git a/tools/testing/selftests/liveupdate/luo_multi_session.c b/tools/testing/selftests/liveupdate/luo_multi_session.c
index aac24a5f5ce3..6b532ec6b82f 100644
--- a/tools/testing/selftests/liveupdate/luo_multi_session.c
+++ b/tools/testing/selftests/liveupdate/luo_multi_session.c
@@ -15,14 +15,17 @@
#define SESSION_EMPTY_2 "multi-test-empty-2"
#define SESSION_FILES_1 "multi-test-files-1"
#define SESSION_FILES_2 "multi-test-files-2"
+#define SESSION_CLOSED "multi-test-closed"
#define MFD1_TOKEN 0x1001
#define MFD2_TOKEN 0x2002
#define MFD3_TOKEN 0x3003
+#define MFD_CLOSED_TOKEN 0x4004
#define MFD1_DATA "Data for session files 1"
#define MFD2_DATA "First file for session files 2"
#define MFD3_DATA "Second file for session files 2"
+#define MFD_CLOSED_DATA "Closed session data"
#define STATE_SESSION_NAME "kexec_multi_state"
#define STATE_MEMFD_TOKEN 998
@@ -30,7 +33,7 @@
/* Stage 1: Executed before the kexec reboot. */
static void run_stage_1(int luo_fd)
{
- int s_empty1_fd, s_empty2_fd, s_files1_fd, s_files2_fd;
+ int s_empty1_fd, s_empty2_fd, s_files1_fd, s_files2_fd, s_closed_fd;
ksft_print_msg("[STAGE 1] Starting pre-kexec setup for multi-session test...\n");
@@ -57,6 +60,8 @@ static void run_stage_1(int luo_fd)
fail_exit("create_and_preserve_memfd for token %#x",
MFD1_TOKEN);
}
+ if (luo_session_freeze(s_files1_fd, 0) < 0)
+ fail_exit("luo_session_freeze for '%s'", SESSION_FILES_1);
ksft_print_msg("[STAGE 1] Creating session '%s' with two memfds...\n",
SESSION_FILES_2);
@@ -72,6 +77,25 @@ static void run_stage_1(int luo_fd)
fail_exit("create_and_preserve_memfd for token %#x",
MFD3_TOKEN);
}
+ if (luo_session_freeze(s_files2_fd,
+ LIVEUPDATE_SESSION_FREEZE_COMMIT) < 0) {
+ fail_exit("luo_session_freeze for '%s'", SESSION_FILES_2);
+ }
+ close(s_files2_fd);
+
+ ksft_print_msg("[STAGE 1] Creating frozen uncommitted session '%s' and closing FD...\n",
+ SESSION_CLOSED);
+ s_closed_fd = luo_create_session(luo_fd, SESSION_CLOSED);
+ if (s_closed_fd < 0)
+ fail_exit("luo_create_session for '%s'", SESSION_CLOSED);
+ if (create_and_preserve_memfd(s_closed_fd, MFD_CLOSED_TOKEN,
+ MFD_CLOSED_DATA) < 0) {
+ fail_exit("create_and_preserve_memfd for token %#x",
+ MFD_CLOSED_TOKEN);
+ }
+ if (luo_session_freeze(s_closed_fd, 0) < 0)
+ fail_exit("luo_session_freeze for '%s'", SESSION_CLOSED);
+ close(s_closed_fd);
close(luo_fd);
daemonize_and_wait();
@@ -91,6 +115,13 @@ static void run_stage_2(int luo_fd, int state_session_fd)
stage);
}
+ ksft_print_msg("[STAGE 2] Verifying closed uncommitted session '%s' was not preserved...\n",
+ SESSION_CLOSED);
+ if (luo_retrieve_session(luo_fd, SESSION_CLOSED) != -ENOENT) {
+ fail_exit("Expected -ENOENT for closed uncommitted session '%s'",
+ SESSION_CLOSED);
+ }
+
ksft_print_msg("[STAGE 2] Retrieving all sessions...\n");
s_empty1_fd = luo_retrieve_session(luo_fd, SESSION_EMPTY_1);
if (s_empty1_fd < 0)
--
2.56.0.398.g882cbad275-goog
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH v5 0/6] liveupdate: Add LIVEUPDATE_SESSION_FREEZE ioctl
2026-11-05 5:00 [PATCH v5 0/6] liveupdate: Add LIVEUPDATE_SESSION_FREEZE ioctl Pasha Tatashin
` (5 preceding siblings ...)
2026-11-05 5:00 ` [PATCH v5 6/6] selftests: liveupdate: Add tests for LIVEUPDATE_SESSION_FREEZE Pasha Tatashin
@ 2026-11-11 9:56 ` Mike Rapoport
6 siblings, 0 replies; 8+ messages in thread
From: Mike Rapoport @ 2026-11-11 9:56 UTC (permalink / raw)
To: Pasha Tatashin
Cc: linux-api, linux-kselftest, shuah, linux-mm, skhan, linux-doc,
vipinsh, linux-kernel, corbet, dmatlack, kexec, pratyush,
rdunlap, graf
(adding linux-api@)
On Thu, Nov 05, 2026 at 12:00:46AM -0500, Pasha Tatashin wrote:
> Currently, LUO freezes outgoing sessions only inside the
> reboot(LINUX_REBOOT_CMD_KEXEC) syscall path, serially under global write
> locks, and binds every outgoing session's lifetime strictly to an open
> session FD until reboot.
>
> On hosts running many workloads (such as VMs or stateful daemons),
> workloads do not all pause simultaneously inside reboot(): they quiesce
> one by one ahead of the host kexec. Userspace needs an explicit
> per-session freeze point to snapshot final file state (.freeze()) as
> each workload pauses -- concurrently across sessions under per-session
> locks -- along with a way to commit a frozen session into KHO so it
> survives closing the session FD.
>
> This series introduces LIVEUPDATE_SESSION_FREEZE on session FDs:
>
> 1. Freeze (flags == 0):
> Freezes all preserved files in the session under the per-session
> mutex while keeping session lifetime tied to the session FD. The
> session stays frozen as long as the FD remains open; closing the
> session FD before reboot (for example, to abort the session or on
> process exit/crash) runs .unfreeze() and .unpreserve() and reclaims
> all resources. At kexec time, already-frozen sessions skip the late
> auto-freeze step and are published into the outgoing KHO subtree.
>
> 2. Freeze and commit (LIVEUPDATE_SESSION_FREEZE_COMMIT):
> Freezes preserved files, immediately inserts the session and its FLB
> dependencies into the outgoing KHO subtree in O(1), and commits the
> session so it survives close() with full ownership retained by LUO
> until reboot. Once committed, closing the session FD (or another
> session failing to freeze during reboot) never unfreezes or
> unpreserves it. This allows processes to hand off preserved state to
> LUO and exit prior to kexec, and makes committed sessions immediately
> visible in the outgoing KHO subtree before the host reboots.
>
> Overview of the series:
> - Patch 1 decouples luo_file_freeze()/unfreeze() from
> struct luo_file_set_ser.
> - Patches 2-4 add incremental KHO publication helpers for kho_block
> (kho_block_set_it_append()), LUO sessions (luo_session_publish_one()),
> and outgoing FLBs (luo_flb_ser_publish_one(), replacing the late
> luo_flb_serialize() reboot pass).
> - Patch 5 introduces LIVEUPDATE_SESSION_FREEZE and
> LIVEUPDATE_SESSION_FREEZE_COMMIT and updates the LUO core and uAPI
> documentation.
> - Patch 6 adds selftests covering both uncommitted and committed session
> freezes across unit and multi-session kexec tests.
>
> This series is based on liveupdate/next commit d80a5528df4c ("Merge
> branch 'lu-pci-preservation' into next").
>
> Changelog:
> v5:
> - Rebase onto latest liveupdate/next.
> - Replace reserved field in struct liveupdate_session_freeze with flags
> and add LIVEUPDATE_SESSION_FREEZE_COMMIT so userspace can either
> freeze a session while keeping it bound to its FD (rolled back on
> close()) or commit the frozen session (inserted immediately into KHO
> and retained by LUO across close() until reboot).
> - Add incremental KHO publication helpers in kho_block, luo_session, and
> luo_flb so committed sessions and their FLB dependencies are inserted
> into the outgoing KHO subtree immediately on freeze, dropping the
> separate luo_flb_serialize() pass at reboot.
> - Keep luo_session_freeze_one() / luo_session_unfreeze_one() in Patch 1
> rather than renaming them to luo_session_serialize_one() /
> luo_session_unserialize_one() and back once luo_session_publish_one()
> is introduced.
> - Update Documentation/userspace-api/liveupdate.rst and kernel-doc
> across kho_block, luo_flb, luo_file, and luo_session.
> - Drop v4 Reviewed-by tag on the modified freeze and selftest patches,
> and expand selftests to cover both uncommitted and committed freezes.
> v4:
> - Split out a preparatory patch that drops struct luo_file_set_ser *
> from luo_file_freeze() / luo_file_unfreeze() and moves file_set_ser
> handling into luo_session_serialize_one() /
> luo_session_unserialize_one() (Suggested by Mike Rapoport).
> - Few more nits from Mike.
> v3:
> - Renamed session->frozen to session->user_frozen, - Pratyush
> - Added Reviewed-by's
> v2:
> - Rebased onto liveupdate/next (commit 27be83a12b24 ("Merge branch
> 'lu-pci-preservation' into next")) and split out early session freeze
> into a standalone series (tmpfs preservation and its per-file user
> argument plumbing were spun out into a separate series).
> - Patch 1:
> - Drop struct luo_file_set_ser * from luo_file_freeze() and
> luo_file_unfreeze(), and rename the session helpers to
> luo_session_serialize_one() / luo_session_unserialize_one() so all
> of struct luo_session_ser is populated and cleared in one place
> (Suggested by Pratyush Yadav).
> - Unfreeze the session's files if luo_ucmd_respond() fails in
> luo_session_freeze().
> - Update remaining reboot-only wording in luo_file.c kernel-doc
> comments.
> - Patch 2:
> - Add checks verifying that non-zero reserved bits and freezing an
> incoming post-kexec session fail with -EINVAL.
> Link: https://lore.kernel.org/all/20261015093816.2438942-1-pasha.tatashin@soleen.com/ # v1
>
> Pasha Tatashin (6):
> liveupdate: Move file_set_ser handling to luo_session_serialize()
> kho: support incremental entry publication in kho_block
> liveupdate: luo: serialize sessions via per-session publish helper
> liveupdate: luo_flb: support incremental outgoing FLB publication
> liveupdate: Add LIVEUPDATE_SESSION_FREEZE ioctl to freeze sessions
> early
> selftests: liveupdate: Add tests for LIVEUPDATE_SESSION_FREEZE
>
> Documentation/userspace-api/liveupdate.rst | 30 +++
> include/linux/kho_block.h | 1 +
> include/linux/liveupdate.h | 3 +-
> include/uapi/linux/liveupdate.h | 46 +++++
> kernel/liveupdate/kho_block.c | 86 ++++++++-
> kernel/liveupdate/luo_core.c | 10 +-
> kernel/liveupdate/luo_file.c | 94 ++++-----
> kernel/liveupdate/luo_flb.c | 111 +++++++----
> kernel/liveupdate/luo_internal.h | 16 +-
> kernel/liveupdate/luo_session.c | 178 ++++++++++++++----
> .../liveupdate/lib/include/libliveupdate.h | 5 +-
> .../selftests/liveupdate/lib/lu_utils.c | 21 ++-
> .../testing/selftests/liveupdate/liveupdate.c | 107 +++++++++++
> .../selftests/liveupdate/luo_kexec_simple.c | 22 ++-
> .../selftests/liveupdate/luo_multi_session.c | 33 +++-
> 15 files changed, 612 insertions(+), 151 deletions(-)
>
>
> base-commit: d80a5528df4cdee5871243e392da08c2d5c92e43
> --
> 2.56.0.398.g882cbad275-goog
>
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 8+ messages in thread