[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20260130145122.368748-3-me@linux.beauty>
Date: Fri, 30 Jan 2026 22:51:18 +0800
From: Li Chen <me@...ux.beauty>
To: Pasha Tatashin <pasha.tatashin@...een.com>,
Mike Rapoport <rppt@...nel.org>,
Pratyush Yadav <pratyush@...nel.org>,
linux-kernel@...r.kernel.org
Cc: Li Chen <me@...ux.beauty>
Subject: [PATCH v1 2/3] liveupdate: bound and control post-kexec restore window
The restore window can remain open indefinitely if userspace never
finishes some incoming sessions. Bound the window with a hard timeout and
add a RESTORE_DONE ioctl on /dev/liveupdate so an orchestrator can end it
explicitly once restoration is complete.
Signed-off-by: Li Chen <me@...ux.beauty>
---
include/linux/liveupdate.h | 4 +++
include/uapi/linux/liveupdate.h | 34 ++++++++++++++++++
kernel/liveupdate/luo_core.c | 15 ++++++++
kernel/liveupdate/luo_internal.h | 1 +
kernel/liveupdate/luo_session.c | 59 ++++++++++++++++++++++++++------
5 files changed, 103 insertions(+), 10 deletions(-)
diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
index 406a6e2dd4a1..301d3e94516e 100644
--- a/include/linux/liveupdate.h
+++ b/include/linux/liveupdate.h
@@ -220,6 +220,10 @@ bool liveupdate_enabled(void);
/*
* Return true during a kexec-based live update boot while userspace is still
* restoring preserved sessions/resources.
+ *
+ * The restore window ends automatically once all incoming sessions have been
+ * retrieved and finished. Userspace can also terminate the window explicitly
+ * (and early) via LIVEUPDATE_IOCTL_RESTORE_DONE.
*/
bool liveupdate_restore_in_progress(void);
diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
index 30bc66ee9436..357137d9a78c 100644
--- a/include/uapi/linux/liveupdate.h
+++ b/include/uapi/linux/liveupdate.h
@@ -51,6 +51,7 @@ enum {
LIVEUPDATE_CMD_BASE = 0x00,
LIVEUPDATE_CMD_CREATE_SESSION = LIVEUPDATE_CMD_BASE,
LIVEUPDATE_CMD_RETRIEVE_SESSION = 0x01,
+ LIVEUPDATE_CMD_RESTORE_DONE = 0x02,
};
/* ioctl commands for session file descriptors */
@@ -118,6 +119,39 @@ struct liveupdate_ioctl_retrieve_session {
#define LIVEUPDATE_IOCTL_RETRIEVE_SESSION \
_IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_RETRIEVE_SESSION)
+/**
+ * struct liveupdate_ioctl_restore_done - ioctl(LIVEUPDATE_IOCTL_RESTORE_DONE)
+ * @size: Input; sizeof(struct liveupdate_ioctl_restore_done)
+ * @reserved: Input; Must be zero. Reserved for future use.
+ *
+ * Marks the completion of the post-kexec restore window.
+ *
+ * After a live update (kexec), userspace needs time to restore preserved
+ * sessions/resources. Some kernel subsystems may apply temporary compatibility
+ * behavior during this window. Userspace should call this ioctl once it has
+ * completed restoration and wants normal kernel behavior to resume, even if
+ * some incoming sessions are left unused.
+ *
+ * This ioctl updates global state and is not tied to a specific live update
+ * session file descriptor. A typical userspace agent calls it once per live
+ * update, after it has restored the required state.
+ *
+ * Note that the restore window may also end automatically once all incoming
+ * sessions have been retrieved and finished (or their session file
+ * descriptors have been closed). This ioctl is intended for cases where an
+ * agent intentionally does not retrieve all incoming sessions or does not want
+ * to wait for them to be finished/closed.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+struct liveupdate_ioctl_restore_done {
+ __u32 size;
+ __u32 reserved;
+};
+
+#define LIVEUPDATE_IOCTL_RESTORE_DONE \
+ _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_RESTORE_DONE)
+
/* Session specific IOCTLs */
/**
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 19c91843fbdb..fb6a73c08979 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -340,6 +340,18 @@ static int luo_ioctl_retrieve_session(struct luo_ucmd *ucmd)
return err;
}
+static int luo_ioctl_restore_done(struct luo_ucmd *ucmd)
+{
+ struct liveupdate_ioctl_restore_done *argp = ucmd->cmd;
+
+ if (argp->reserved)
+ return -EINVAL;
+
+ luo_session_restore_done();
+
+ return luo_ucmd_respond(ucmd, sizeof(*argp));
+}
+
static int luo_open(struct inode *inodep, struct file *filep)
{
struct luo_device_state *ldev = container_of(filep->private_data,
@@ -371,6 +383,7 @@ static int luo_release(struct inode *inodep, struct file *filep)
union ucmd_buffer {
struct liveupdate_ioctl_create_session create;
struct liveupdate_ioctl_retrieve_session retrieve;
+ struct liveupdate_ioctl_restore_done restore_done;
};
struct luo_ioctl_op {
@@ -395,6 +408,8 @@ static const struct luo_ioctl_op luo_ioctl_ops[] = {
struct liveupdate_ioctl_create_session, name),
IOCTL_OP(LIVEUPDATE_IOCTL_RETRIEVE_SESSION, luo_ioctl_retrieve_session,
struct liveupdate_ioctl_retrieve_session, name),
+ IOCTL_OP(LIVEUPDATE_IOCTL_RESTORE_DONE, luo_ioctl_restore_done,
+ struct liveupdate_ioctl_restore_done, reserved),
};
static long luo_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 8aa4c5b0101b..03a5b27498be 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -96,6 +96,7 @@ int luo_session_serialize(void);
int luo_session_deserialize(void);
bool luo_session_quiesce(void);
void luo_session_resume(void);
+void luo_session_restore_done(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);
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index e1d1ab795c40..2c7dd3b12303 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -58,6 +58,7 @@
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/io.h>
+#include <linux/jiffies.h>
#include <linux/kexec_handover.h>
#include <linux/kho/abi/luo.h>
#include <linux/libfdt.h>
@@ -67,6 +68,7 @@
#include <linux/rwsem.h>
#include <linux/slab.h>
#include <linux/unaligned.h>
+#include <linux/workqueue.h>
#include <uapi/linux/liveupdate.h>
#include "luo_internal.h"
@@ -118,8 +120,28 @@ static struct luo_session_global luo_session_global = {
},
};
+/*
+ * Count of incoming sessions that still keep the post-kexec restore window
+ * open. This is initialized from the serialized session header and decremented
+ * when a retrieved session is finished (or closed). Userspace can also end the
+ * restore window explicitly via LIVEUPDATE_IOCTL_RESTORE_DONE.
+ */
static atomic_long_t liveupdate_incoming_sessions_left = ATOMIC_LONG_INIT(0);
+#define LIVEUPDATE_RESTORE_WINDOW_TIMEOUT (15 * 60 * HZ)
+
+static void liveupdate_restore_window_timeout(struct work_struct *work)
+{
+ if (!liveupdate_restore_in_progress())
+ return;
+
+ pr_warn_once("liveupdate restore window timed out\n");
+ atomic_long_set(&liveupdate_incoming_sessions_left, 0);
+}
+
+static DECLARE_DELAYED_WORK(liveupdate_restore_window_timeout_work,
+ liveupdate_restore_window_timeout);
+
bool liveupdate_restore_in_progress(void)
{
return atomic_long_read(&liveupdate_incoming_sessions_left) > 0;
@@ -141,6 +163,16 @@ void __init luo_session_restore_window_init(void)
}
atomic_long_set(&liveupdate_incoming_sessions_left, (long)count);
+
+ if (count > 0)
+ schedule_delayed_work(&liveupdate_restore_window_timeout_work,
+ LIVEUPDATE_RESTORE_WINDOW_TIMEOUT);
+}
+
+void luo_session_restore_done(void)
+{
+ atomic_long_set(&liveupdate_incoming_sessions_left, 0);
+ cancel_delayed_work_sync(&liveupdate_restore_window_timeout_work);
}
static struct luo_session *luo_session_alloc(const char *name)
@@ -209,18 +241,26 @@ static void luo_session_remove(struct luo_session_header *sh,
static int luo_session_finish_one(struct luo_session *session)
{
int err;
+ bool cancel_timeout = false;
- guard(mutex)(&session->mutex);
- if (session->finished)
- return 0;
+ {
+ guard(mutex)(&session->mutex);
+ if (session->finished)
+ return 0;
- err = luo_file_finish(&session->file_set);
- if (err)
- return err;
+ err = luo_file_finish(&session->file_set);
+ if (err)
+ return err;
- session->finished = true;
- if (session->retrieved)
- atomic_long_dec(&liveupdate_incoming_sessions_left);
+ session->finished = true;
+ if (session->retrieved) {
+ if (atomic_long_dec_if_positive(&liveupdate_incoming_sessions_left) == 0)
+ cancel_timeout = true;
+ }
+ }
+
+ if (cancel_timeout)
+ cancel_delayed_work_sync(&liveupdate_restore_window_timeout_work);
return 0;
}
@@ -545,7 +585,6 @@ int __init luo_session_setup_incoming(void *fdt_in)
luo_session_global.incoming.header_ser = header_ser;
luo_session_global.incoming.ser = (void *)(header_ser + 1);
luo_session_global.incoming.active = true;
-
return 0;
}
--
2.52.0
Powered by blists - more mailing lists