summaryrefslogtreecommitdiff
path: root/kernel/liveupdate
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/liveupdate')
-rw-r--r--kernel/liveupdate/kexec_handover.c32
-rw-r--r--kernel/liveupdate/luo_file.c5
-rw-r--r--kernel/liveupdate/luo_flb.c52
-rw-r--r--kernel/liveupdate/luo_session.c99
4 files changed, 135 insertions, 53 deletions
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 1b592d86dc48..48d98fa9621e 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -593,20 +593,30 @@ early_param("kho_scratch", kho_parse_scratch_size);
static void __init scratch_size_update(void)
{
- phys_addr_t size;
+ /*
+ * If fixed sizes are not provided via command line, calculate them
+ * now.
+ */
+ if (scratch_scale) {
+ phys_addr_t size;
- if (!scratch_scale)
- return;
+ size = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT,
+ NUMA_NO_NODE);
+ size = size * scratch_scale / 100;
+ scratch_size_lowmem = size;
- size = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT,
- NUMA_NO_NODE);
- size = size * scratch_scale / 100;
- scratch_size_lowmem = round_up(size, CMA_MIN_ALIGNMENT_BYTES);
+ size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,
+ NUMA_NO_NODE);
+ size = size * scratch_scale / 100 - scratch_size_lowmem;
+ scratch_size_global = size;
+ }
- size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,
- NUMA_NO_NODE);
- size = size * scratch_scale / 100 - scratch_size_lowmem;
- scratch_size_global = round_up(size, CMA_MIN_ALIGNMENT_BYTES);
+ /*
+ * Scratch areas are released as MIGRATE_CMA. Round them up to the right
+ * size.
+ */
+ scratch_size_lowmem = round_up(scratch_size_lowmem, CMA_MIN_ALIGNMENT_BYTES);
+ scratch_size_global = round_up(scratch_size_global, CMA_MIN_ALIGNMENT_BYTES);
}
static phys_addr_t __init scratch_size_node(int nid)
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index a0a419085e28..208987502f73 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -385,10 +385,11 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set)
args.private_data = luo_file->private_data;
luo_file->fh->ops->unpreserve(&args);
luo_flb_file_unpreserve(luo_file->fh);
- module_put(luo_file->fh->ops->owner);
xa_erase(&luo_preserved_files,
luo_get_id(luo_file->fh, luo_file->file));
+ module_put(luo_file->fh->ops->owner);
+
list_del(&luo_file->list);
file_set->count--;
@@ -677,7 +678,6 @@ static void luo_file_finish_one(struct luo_file_set *file_set,
luo_file->fh->ops->finish(&args);
luo_flb_file_finish(luo_file->fh);
- module_put(luo_file->fh->ops->owner);
}
/**
@@ -738,6 +738,7 @@ int luo_file_finish(struct luo_file_set *file_set)
luo_get_id(luo_file->fh, luo_file->file));
fput(luo_file->file);
}
+ module_put(luo_file->fh->ops->owner);
list_del(&luo_file->list);
file_set->count--;
mutex_destroy(&luo_file->mutex);
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index 00f5494812c4..8f5c5dd01cd0 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -111,7 +111,7 @@ static int luo_flb_file_preserve_one(struct liveupdate_flb *flb)
struct luo_flb_private *private = luo_flb_get_private(flb);
scoped_guard(mutex, &private->outgoing.lock) {
- if (!private->outgoing.count) {
+ if (!refcount_read(&private->outgoing.count)) {
struct liveupdate_flb_op_args args = {0};
int err;
@@ -126,8 +126,10 @@ static int luo_flb_file_preserve_one(struct liveupdate_flb *flb)
}
private->outgoing.data = args.data;
private->outgoing.obj = args.obj;
+ refcount_set(&private->outgoing.count, 1);
+ } else {
+ refcount_inc(&private->outgoing.count);
}
- private->outgoing.count++;
}
return 0;
@@ -138,8 +140,7 @@ static void luo_flb_file_unpreserve_one(struct liveupdate_flb *flb)
struct luo_flb_private *private = luo_flb_get_private(flb);
scoped_guard(mutex, &private->outgoing.lock) {
- private->outgoing.count--;
- if (!private->outgoing.count) {
+ if (refcount_dec_and_test(&private->outgoing.count)) {
struct liveupdate_flb_op_args args = {0};
args.flb = flb;
@@ -164,7 +165,7 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
bool found = false;
int err;
- guard(mutex)(&private->incoming.lock);
+ lockdep_assert_held(&private->incoming.lock);
if (private->incoming.finished)
return -ENODATA;
@@ -178,7 +179,7 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
for (int i = 0; i < fh->header_ser->count; i++) {
if (!strcmp(fh->ser[i].name, flb->compatible)) {
private->incoming.data = fh->ser[i].data;
- private->incoming.count = fh->ser[i].count;
+ refcount_set(&private->incoming.count, fh->ser[i].count);
found = true;
break;
}
@@ -205,16 +206,14 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
return 0;
}
-static void luo_flb_file_finish_one(struct liveupdate_flb *flb)
+void liveupdate_flb_put_incoming(struct liveupdate_flb *flb)
{
struct luo_flb_private *private = luo_flb_get_private(flb);
- u64 count;
-
- scoped_guard(mutex, &private->incoming.lock)
- count = --private->incoming.count;
+ struct liveupdate_flb_op_args args = {0};
- if (!count) {
- struct liveupdate_flb_op_args args = {0};
+ scoped_guard(mutex, &private->incoming.lock) {
+ if (!refcount_dec_and_test(&private->incoming.count))
+ return;
if (!private->incoming.retrieved) {
int err = luo_flb_retrieve_one(flb);
@@ -223,16 +222,14 @@ static void luo_flb_file_finish_one(struct liveupdate_flb *flb)
return;
}
- scoped_guard(mutex, &private->incoming.lock) {
- args.flb = flb;
- args.obj = private->incoming.obj;
- flb->ops->finish(&args);
+ args.flb = flb;
+ args.obj = private->incoming.obj;
+ flb->ops->finish(&args);
- private->incoming.data = 0;
- private->incoming.obj = NULL;
- private->incoming.finished = true;
- module_put(flb->ops->owner);
- }
+ private->incoming.data = 0;
+ private->incoming.obj = NULL;
+ private->incoming.finished = true;
+ module_put(flb->ops->owner);
}
}
@@ -315,7 +312,7 @@ void luo_flb_file_finish(struct liveupdate_file_handler *fh)
guard(rwsem_read)(&luo_register_rwlock);
list_for_each_entry_reverse(iter, flb_list, list)
- luo_flb_file_finish_one(iter->flb);
+ liveupdate_flb_put_incoming(iter->flb);
}
static void luo_flb_unregister_one(struct liveupdate_file_handler *fh,
@@ -512,6 +509,8 @@ int liveupdate_flb_get_incoming(struct liveupdate_flb *flb, void **objp)
if (!liveupdate_enabled())
return -EOPNOTSUPP;
+ guard(mutex)(&private->incoming.lock);
+
if (!private->incoming.obj) {
int err = luo_flb_retrieve_one(flb);
@@ -519,7 +518,7 @@ int liveupdate_flb_get_incoming(struct liveupdate_flb *flb, void **objp)
return err;
}
- guard(mutex)(&private->incoming.lock);
+ refcount_inc(&private->incoming.count);
*objp = private->incoming.obj;
return 0;
@@ -652,12 +651,13 @@ void luo_flb_serialize(void)
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 (private->outgoing.count > 0) {
+ 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 = private->outgoing.count;
+ fh->ser[i].count = count;
i++;
}
}
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index ec7aebc15a80..174de2cc56a6 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -46,6 +46,38 @@
* 4. Retrieval: A userspace agent in the new kernel can then call
* `luo_session_retrieve()` with a session name to get a new file
* descriptor and access the preserved state.
+ *
+ * Locking:
+ *
+ * The LUO session subsystem uses a three-tier locking hierarchy to ensure thread
+ * safety and prevent deadlocks during concurrent session mutations and kexec
+ * serialization:
+ *
+ * 1. `luo_session_serialize_rwsem` (global rwsem):
+ * Protects session mutations (creation, retrieval, release, and ioctls)
+ * against the serialization process during reboot.
+ *
+ * - Readers: Taken by any path modifying or accessing session state (e.g.,
+ * `luo_session_create()`, `luo_session_retrieve()`, `luo_session_release()`,
+ * and `luo_session_ioctl()`).
+ * - Writer: Taken by the serialization process (`luo_session_serialize()`)
+ * during reboot. On success, the write lock is held indefinitely to freeze
+ * the subsystem. On failure, it is released to allow recovery.
+ *
+ * 2. `luo_session_header->rwsem` (per-list rwsem):
+ * Synchronizes list-level operations for the incoming and outgoing session headers.
+ *
+ * - Writer: Taken during list mutation operations (inserting or removing a
+ * session from the list).
+ * - Reader: Taken when traversing the list (e.g., retrieving a session by name).
+ *
+ * 3. `luo_session->mutex` (per-session mutex):
+ * Protects the internal state and file sets of an individual session. It is
+ * acquired during per-session operations such as preserving, retrieving,
+ * or freezing files.
+ *
+ * Lock Hierarchy:
+ * `luo_session_serialize_rwsem` -> `luo_session_header->rwsem` -> `luo_session->mutex`
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -75,6 +107,8 @@
sizeof(struct luo_session_header_ser)) / \
sizeof(struct luo_session_ser))
+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.
@@ -205,6 +239,7 @@ static int luo_session_release(struct inode *inodep, struct file *filep)
struct luo_session *session = filep->private_data;
struct luo_session_header *sh;
+ guard(rwsem_read)(&luo_session_serialize_rwsem);
/* If retrieved is set, it means this session is from incoming list */
if (session->retrieved) {
int err = luo_session_finish_one(session);
@@ -295,32 +330,58 @@ union ucmd_buffer {
struct liveupdate_session_retrieve_fd retrieve;
};
+/* Type of sessions the ioctl applies to. */
+enum luo_ioctl_type {
+ LUO_IOCTL_INCOMING,
+ LUO_IOCTL_OUTGOING,
+ LUO_IOCTL_ALL,
+};
+
struct luo_ioctl_op {
unsigned int size;
unsigned int min_size;
unsigned int ioctl_num;
+ enum luo_ioctl_type type;
int (*execute)(struct luo_session *session, struct luo_ucmd *ucmd);
};
-#define IOCTL_OP(_ioctl, _fn, _struct, _last) \
+#define IOCTL_OP(_ioctl, _fn, _struct, _last, _type) \
[_IOC_NR(_ioctl) - LIVEUPDATE_CMD_SESSION_BASE] = { \
.size = sizeof(_struct) + \
BUILD_BUG_ON_ZERO(sizeof(union ucmd_buffer) < \
sizeof(_struct)), \
.min_size = offsetofend(_struct, _last), \
.ioctl_num = _ioctl, \
+ .type = _type, \
.execute = _fn, \
}
static const struct luo_ioctl_op luo_session_ioctl_ops[] = {
IOCTL_OP(LIVEUPDATE_SESSION_FINISH, luo_session_finish,
- struct liveupdate_session_finish, reserved),
+ struct liveupdate_session_finish, reserved, LUO_IOCTL_INCOMING),
IOCTL_OP(LIVEUPDATE_SESSION_PRESERVE_FD, luo_session_preserve_fd,
- struct liveupdate_session_preserve_fd, token),
+ struct liveupdate_session_preserve_fd, token, LUO_IOCTL_OUTGOING),
IOCTL_OP(LIVEUPDATE_SESSION_RETRIEVE_FD, luo_session_retrieve_fd,
- struct liveupdate_session_retrieve_fd, token),
+ struct liveupdate_session_retrieve_fd, token, LUO_IOCTL_INCOMING),
};
+static bool luo_ioctl_type_valid(struct luo_session *session,
+ const struct luo_ioctl_op *op)
+{
+ switch (op->type) {
+ case LUO_IOCTL_INCOMING:
+ /* Retrieved is only set on incoming sessions */
+ return session->retrieved;
+ case LUO_IOCTL_OUTGOING:
+ return !session->retrieved;
+ case LUO_IOCTL_ALL:
+ return true;
+ }
+
+ /* Catch-all. */
+ return false;
+}
+
static long luo_session_ioctl(struct file *filep, unsigned int cmd,
unsigned long arg)
{
@@ -345,6 +406,8 @@ static long luo_session_ioctl(struct file *filep, unsigned int cmd,
op = &luo_session_ioctl_ops[nr - LIVEUPDATE_CMD_SESSION_BASE];
if (op->ioctl_num != cmd)
return -ENOIOCTLCMD;
+ if (!luo_ioctl_type_valid(session, op))
+ return -EINVAL;
if (ucmd.user_size < op->min_size)
return -EINVAL;
@@ -354,6 +417,7 @@ static long luo_session_ioctl(struct file *filep, unsigned int cmd,
if (ret)
return ret;
+ guard(rwsem_read)(&luo_session_serialize_rwsem);
return op->execute(session, &ucmd);
}
@@ -393,14 +457,17 @@ int luo_session_create(const char *name, struct file **filep)
if (IS_ERR(session))
return PTR_ERR(session);
+ down_read(&luo_session_serialize_rwsem);
err = luo_session_insert(&luo_session_global.outgoing, session);
if (err)
goto err_free;
- scoped_guard(mutex, &session->mutex)
- err = luo_session_getfile(session, filep);
+ mutex_lock(&session->mutex);
+ err = luo_session_getfile(session, filep);
+ mutex_unlock(&session->mutex);
if (err)
goto err_remove;
+ up_read(&luo_session_serialize_rwsem);
return 0;
@@ -408,6 +475,7 @@ err_remove:
luo_session_remove(&luo_session_global.outgoing, session);
err_free:
luo_session_free(session);
+ up_read(&luo_session_serialize_rwsem);
return err;
}
@@ -419,12 +487,12 @@ int luo_session_retrieve(const char *name, struct file **filep)
struct luo_session *it;
int err;
- scoped_guard(rwsem_read, &sh->rwsem) {
- list_for_each_entry(it, &sh->list, list) {
- if (!strncmp(it->name, name, sizeof(it->name))) {
- session = it;
- break;
- }
+ guard(rwsem_read)(&luo_session_serialize_rwsem);
+ guard(rwsem_read)(&sh->rwsem);
+ list_for_each_entry(it, &sh->list, list) {
+ if (!strncmp(it->name, name, sizeof(it->name))) {
+ session = it;
+ break;
}
}
@@ -592,7 +660,8 @@ int luo_session_serialize(void)
int i = 0;
int err;
- guard(rwsem_write)(&sh->rwsem);
+ down_write(&luo_session_serialize_rwsem);
+ down_write(&sh->rwsem);
list_for_each_entry(session, &sh->list, list) {
err = luo_session_freeze_one(session, &sh->ser[i]);
if (err)
@@ -603,6 +672,7 @@ int luo_session_serialize(void)
i++;
}
sh->header_ser->count = sh->count;
+ up_write(&sh->rwsem);
return 0;
@@ -612,7 +682,8 @@ err_undo:
luo_session_unfreeze_one(session, &sh->ser[i]);
memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name));
}
+ up_write(&sh->rwsem);
+ up_write(&luo_session_serialize_rwsem);
return err;
}
-