summaryrefslogtreecommitdiff
path: root/kernel/liveupdate
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/liveupdate')
-rw-r--r--kernel/liveupdate/kexec_handover.c158
-rw-r--r--kernel/liveupdate/kexec_handover_debugfs.c55
-rw-r--r--kernel/liveupdate/kexec_handover_internal.h15
-rw-r--r--kernel/liveupdate/luo_core.c11
-rw-r--r--kernel/liveupdate/luo_file.c112
-rw-r--r--kernel/liveupdate/luo_flb.c182
-rw-r--r--kernel/liveupdate/luo_internal.h7
-rw-r--r--kernel/liveupdate/luo_session.c46
8 files changed, 348 insertions, 238 deletions
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 532f455c5d4f..94762de1fe5f 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -18,7 +18,9 @@
#include <linux/kexec.h>
#include <linux/kexec_handover.h>
#include <linux/kho_radix_tree.h>
+#include <linux/utsname.h>
#include <linux/kho/abi/kexec_handover.h>
+#include <linux/kho/abi/kexec_metadata.h>
#include <linux/libfdt.h>
#include <linux/list.h>
#include <linux/memblock.h>
@@ -724,12 +726,13 @@ err_disable_kho:
}
/**
- * kho_add_subtree - record the physical address of a sub FDT in KHO root tree.
+ * kho_add_subtree - record the physical address of a sub blob in KHO root tree.
* @name: name of the sub tree.
- * @fdt: the sub tree blob.
+ * @blob: the sub tree blob.
+ * @size: size of the blob in bytes.
*
* Creates a new child node named @name in KHO root FDT and records
- * the physical address of @fdt. The pages of @fdt must also be preserved
+ * the physical address of @blob. The pages of @blob must also be preserved
* by KHO for the new kernel to retrieve it after kexec.
*
* A debugfs blob entry is also created at
@@ -738,10 +741,11 @@ err_disable_kho:
*
* Return: 0 on success, error code on failure
*/
-int kho_add_subtree(const char *name, void *fdt)
+int kho_add_subtree(const char *name, void *blob, size_t size)
{
- phys_addr_t phys = virt_to_phys(fdt);
+ phys_addr_t phys = virt_to_phys(blob);
void *root_fdt = kho_out.fdt;
+ u64 size_u64 = size;
int err = -ENOMEM;
int off, fdt_err;
@@ -758,12 +762,18 @@ int kho_add_subtree(const char *name, void *fdt)
goto out_pack;
}
- err = fdt_setprop(root_fdt, off, KHO_FDT_SUB_TREE_PROP_NAME,
+ err = fdt_setprop(root_fdt, off, KHO_SUB_TREE_PROP_NAME,
&phys, sizeof(phys));
if (err < 0)
goto out_pack;
- WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, name, fdt, false));
+ err = fdt_setprop(root_fdt, off, KHO_SUB_TREE_SIZE_PROP_NAME,
+ &size_u64, sizeof(size_u64));
+ if (err < 0)
+ goto out_pack;
+
+ WARN_ON_ONCE(kho_debugfs_blob_add(&kho_out.dbg, name, blob,
+ size, false));
out_pack:
fdt_pack(root_fdt);
@@ -772,9 +782,9 @@ out_pack:
}
EXPORT_SYMBOL_GPL(kho_add_subtree);
-void kho_remove_subtree(void *fdt)
+void kho_remove_subtree(void *blob)
{
- phys_addr_t target_phys = virt_to_phys(fdt);
+ phys_addr_t target_phys = virt_to_phys(blob);
void *root_fdt = kho_out.fdt;
int off;
int err;
@@ -790,13 +800,13 @@ void kho_remove_subtree(void *fdt)
const u64 *val;
int len;
- val = fdt_getprop(root_fdt, off, KHO_FDT_SUB_TREE_PROP_NAME, &len);
+ val = fdt_getprop(root_fdt, off, KHO_SUB_TREE_PROP_NAME, &len);
if (!val || len != sizeof(phys_addr_t))
continue;
if ((phys_addr_t)*val == target_phys) {
fdt_del_node(root_fdt, off);
- kho_debugfs_fdt_remove(&kho_out.dbg, fdt);
+ kho_debugfs_blob_remove(&kho_out.dbg, blob);
break;
}
}
@@ -1260,6 +1270,8 @@ EXPORT_SYMBOL_GPL(kho_restore_free);
struct kho_in {
phys_addr_t fdt_phys;
phys_addr_t scratch_phys;
+ char previous_release[__NEW_UTS_LEN + 1];
+ u32 kexec_count;
struct kho_debugfs dbg;
};
@@ -1292,16 +1304,17 @@ bool is_kho_boot(void)
EXPORT_SYMBOL_GPL(is_kho_boot);
/**
- * kho_retrieve_subtree - retrieve a preserved sub FDT by its name.
- * @name: the name of the sub FDT passed to kho_add_subtree().
- * @phys: if found, the physical address of the sub FDT is stored in @phys.
+ * kho_retrieve_subtree - retrieve a preserved sub blob by its name.
+ * @name: the name of the sub blob passed to kho_add_subtree().
+ * @phys: if found, the physical address of the sub blob is stored in @phys.
+ * @size: if not NULL and found, the size of the sub blob is stored in @size.
*
- * Retrieve a preserved sub FDT named @name and store its physical
- * address in @phys.
+ * Retrieve a preserved sub blob named @name and store its physical
+ * address in @phys and optionally its size in @size.
*
* Return: 0 on success, error code on failure
*/
-int kho_retrieve_subtree(const char *name, phys_addr_t *phys)
+int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size)
{
const void *fdt = kho_get_fdt();
const u64 *val;
@@ -1317,12 +1330,22 @@ int kho_retrieve_subtree(const char *name, phys_addr_t *phys)
if (offset < 0)
return -ENOENT;
- val = fdt_getprop(fdt, offset, KHO_FDT_SUB_TREE_PROP_NAME, &len);
+ val = fdt_getprop(fdt, offset, KHO_SUB_TREE_PROP_NAME, &len);
if (!val || len != sizeof(*val))
return -EINVAL;
*phys = (phys_addr_t)*val;
+ val = fdt_getprop(fdt, offset, KHO_SUB_TREE_SIZE_PROP_NAME, &len);
+ if (!val || len != sizeof(*val)) {
+ pr_warn("broken KHO subnode '%s': missing or invalid blob-size property\n",
+ name);
+ return -EINVAL;
+ }
+
+ if (size)
+ *size = (size_t)*val;
+
return 0;
}
EXPORT_SYMBOL_GPL(kho_retrieve_subtree);
@@ -1373,6 +1396,96 @@ static __init int kho_out_fdt_setup(void)
return err;
}
+static void __init kho_in_kexec_metadata(void)
+{
+ struct kho_kexec_metadata *metadata;
+ phys_addr_t metadata_phys;
+ size_t blob_size;
+ int err;
+
+ err = kho_retrieve_subtree(KHO_METADATA_NODE_NAME, &metadata_phys,
+ &blob_size);
+ if (err)
+ /* This is fine, previous kernel didn't export metadata */
+ return;
+
+ /* Check that, at least, "version" is present */
+ if (blob_size < sizeof(u32)) {
+ pr_warn("kexec-metadata blob too small (%zu bytes)\n",
+ blob_size);
+ return;
+ }
+
+ metadata = phys_to_virt(metadata_phys);
+
+ if (metadata->version != KHO_KEXEC_METADATA_VERSION) {
+ pr_warn("kexec-metadata version %u not supported (expected %u)\n",
+ metadata->version, KHO_KEXEC_METADATA_VERSION);
+ return;
+ }
+
+ if (blob_size < sizeof(*metadata)) {
+ pr_warn("kexec-metadata blob too small for v%u (%zu < %zu)\n",
+ metadata->version, blob_size, sizeof(*metadata));
+ return;
+ }
+
+ /*
+ * Copy data to the kernel structure that will persist during
+ * kernel lifetime.
+ */
+ kho_in.kexec_count = metadata->kexec_count;
+ strscpy(kho_in.previous_release, metadata->previous_release,
+ sizeof(kho_in.previous_release));
+
+ pr_info("exec from: %s (count %u)\n",
+ kho_in.previous_release, kho_in.kexec_count);
+}
+
+/*
+ * Create kexec metadata to pass kernel version and boot count to the
+ * next kernel. This keeps the core KHO ABI minimal and allows the
+ * metadata format to evolve independently.
+ */
+static __init int kho_out_kexec_metadata(void)
+{
+ struct kho_kexec_metadata *metadata;
+ int err;
+
+ metadata = kho_alloc_preserve(sizeof(*metadata));
+ if (IS_ERR(metadata))
+ return PTR_ERR(metadata);
+
+ metadata->version = KHO_KEXEC_METADATA_VERSION;
+ strscpy(metadata->previous_release, init_uts_ns.name.release,
+ sizeof(metadata->previous_release));
+ /* kho_in.kexec_count is set to 0 on cold boot */
+ metadata->kexec_count = kho_in.kexec_count + 1;
+
+ err = kho_add_subtree(KHO_METADATA_NODE_NAME, metadata,
+ sizeof(*metadata));
+ if (err)
+ kho_unpreserve_free(metadata);
+
+ return err;
+}
+
+static int __init kho_kexec_metadata_init(const void *fdt)
+{
+ int err;
+
+ if (fdt)
+ kho_in_kexec_metadata();
+
+ /* Populate kexec metadata for the possible next kexec */
+ err = kho_out_kexec_metadata();
+ if (err)
+ pr_warn("failed to initialize kexec-metadata subtree: %d\n",
+ err);
+
+ return err;
+}
+
static __init int kho_init(void)
{
struct kho_radix_tree *tree = &kho_out.radix_tree;
@@ -1406,6 +1519,10 @@ static __init int kho_init(void)
if (err)
goto err_free_fdt;
+ err = kho_kexec_metadata_init(fdt);
+ if (err)
+ goto err_free_fdt;
+
if (fdt) {
kho_in_debugfs_init(&kho_in.dbg, fdt);
return 0;
@@ -1430,8 +1547,9 @@ static __init int kho_init(void)
init_cma_reserved_pageblock(pfn_to_page(pfn));
}
- WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, "fdt",
- kho_out.fdt, true));
+ WARN_ON_ONCE(kho_debugfs_blob_add(&kho_out.dbg, "fdt",
+ kho_out.fdt,
+ fdt_totalsize(kho_out.fdt), true));
return 0;
diff --git a/kernel/liveupdate/kexec_handover_debugfs.c b/kernel/liveupdate/kexec_handover_debugfs.c
index acf368222682..257ee8a52be6 100644
--- a/kernel/liveupdate/kexec_handover_debugfs.c
+++ b/kernel/liveupdate/kexec_handover_debugfs.c
@@ -24,8 +24,9 @@ struct fdt_debugfs {
struct dentry *file;
};
-static int __kho_debugfs_fdt_add(struct list_head *list, struct dentry *dir,
- const char *name, const void *fdt)
+static int __kho_debugfs_blob_add(struct list_head *list, struct dentry *dir,
+ const char *name, const void *blob,
+ size_t size)
{
struct fdt_debugfs *f;
struct dentry *file;
@@ -34,8 +35,8 @@ static int __kho_debugfs_fdt_add(struct list_head *list, struct dentry *dir,
if (!f)
return -ENOMEM;
- f->wrapper.data = (void *)fdt;
- f->wrapper.size = fdt_totalsize(fdt);
+ f->wrapper.data = (void *)blob;
+ f->wrapper.size = size;
file = debugfs_create_blob(name, 0400, dir, &f->wrapper);
if (IS_ERR(file)) {
@@ -49,8 +50,8 @@ static int __kho_debugfs_fdt_add(struct list_head *list, struct dentry *dir,
return 0;
}
-int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
- const void *fdt, bool root)
+int kho_debugfs_blob_add(struct kho_debugfs *dbg, const char *name,
+ const void *blob, size_t size, bool root)
{
struct dentry *dir;
@@ -59,15 +60,15 @@ int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
else
dir = dbg->sub_fdt_dir;
- return __kho_debugfs_fdt_add(&dbg->fdt_list, dir, name, fdt);
+ return __kho_debugfs_blob_add(&dbg->fdt_list, dir, name, blob, size);
}
-void kho_debugfs_fdt_remove(struct kho_debugfs *dbg, void *fdt)
+void kho_debugfs_blob_remove(struct kho_debugfs *dbg, void *blob)
{
struct fdt_debugfs *ff;
list_for_each_entry(ff, &dbg->fdt_list, list) {
- if (ff->wrapper.data == fdt) {
+ if (ff->wrapper.data == blob) {
debugfs_remove(ff->file);
list_del(&ff->list);
kfree(ff);
@@ -113,28 +114,42 @@ __init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
goto err_rmdir;
}
- err = __kho_debugfs_fdt_add(&dbg->fdt_list, dir, "fdt", fdt);
+ err = __kho_debugfs_blob_add(&dbg->fdt_list, dir, "fdt", fdt,
+ fdt_totalsize(fdt));
if (err)
goto err_rmdir;
fdt_for_each_subnode(child, fdt, 0) {
int len = 0;
const char *name = fdt_get_name(fdt, child, NULL);
- const u64 *fdt_phys;
+ const u64 *blob_phys;
+ const u64 *blob_size;
+ void *blob;
- fdt_phys = fdt_getprop(fdt, child, KHO_FDT_SUB_TREE_PROP_NAME, &len);
- if (!fdt_phys)
+ blob_phys = fdt_getprop(fdt, child,
+ KHO_SUB_TREE_PROP_NAME, &len);
+ if (!blob_phys)
continue;
- if (len != sizeof(*fdt_phys)) {
- pr_warn("node %s prop fdt has invalid length: %d\n",
- name, len);
+ if (len != sizeof(*blob_phys)) {
+ pr_warn("node %s prop %s has invalid length: %d\n",
+ name, KHO_SUB_TREE_PROP_NAME, len);
continue;
}
- err = __kho_debugfs_fdt_add(&dbg->fdt_list, sub_fdt_dir, name,
- phys_to_virt(*fdt_phys));
+
+ blob_size = fdt_getprop(fdt, child,
+ KHO_SUB_TREE_SIZE_PROP_NAME, &len);
+ if (!blob_size || len != sizeof(*blob_size)) {
+ pr_warn("node %s missing or invalid %s property\n",
+ name, KHO_SUB_TREE_SIZE_PROP_NAME);
+ continue;
+ }
+
+ blob = phys_to_virt(*blob_phys);
+ err = __kho_debugfs_blob_add(&dbg->fdt_list, sub_fdt_dir, name,
+ blob, *blob_size);
if (err) {
- pr_warn("failed to add fdt %s to debugfs: %pe\n", name,
- ERR_PTR(err));
+ pr_warn("failed to add blob %s to debugfs: %pe\n",
+ name, ERR_PTR(err));
continue;
}
}
diff --git a/kernel/liveupdate/kexec_handover_internal.h b/kernel/liveupdate/kexec_handover_internal.h
index 9a832a35254c..0399ff107775 100644
--- a/kernel/liveupdate/kexec_handover_internal.h
+++ b/kernel/liveupdate/kexec_handover_internal.h
@@ -26,18 +26,19 @@ extern unsigned int kho_scratch_cnt;
int kho_debugfs_init(void);
void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt);
int kho_out_debugfs_init(struct kho_debugfs *dbg);
-int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
- const void *fdt, bool root);
-void kho_debugfs_fdt_remove(struct kho_debugfs *dbg, void *fdt);
+int kho_debugfs_blob_add(struct kho_debugfs *dbg, const char *name,
+ const void *blob, size_t size, bool root);
+void kho_debugfs_blob_remove(struct kho_debugfs *dbg, void *blob);
#else
static inline int kho_debugfs_init(void) { return 0; }
static inline void kho_in_debugfs_init(struct kho_debugfs *dbg,
const void *fdt) { }
static inline int kho_out_debugfs_init(struct kho_debugfs *dbg) { return 0; }
-static inline int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
- const void *fdt, bool root) { return 0; }
-static inline void kho_debugfs_fdt_remove(struct kho_debugfs *dbg,
- void *fdt) { }
+static inline int kho_debugfs_blob_add(struct kho_debugfs *dbg,
+ const char *name, const void *blob,
+ size_t size, bool root) { return 0; }
+static inline void kho_debugfs_blob_remove(struct kho_debugfs *dbg,
+ void *blob) { }
#endif /* CONFIG_KEXEC_HANDOVER_DEBUGFS */
#ifdef CONFIG_KEXEC_HANDOVER_DEBUG
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 84ac728d63ba..803f51c84275 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -54,6 +54,7 @@
#include <linux/liveupdate.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
+#include <linux/rwsem.h>
#include <linux/sizes.h>
#include <linux/string.h>
#include <linux/unaligned.h>
@@ -68,6 +69,11 @@ static struct {
u64 liveupdate_num;
} luo_global;
+/*
+ * luo_register_rwlock - Protects registration of file handlers and FLBs.
+ */
+DECLARE_RWSEM(luo_register_rwlock);
+
static int __init early_liveupdate_param(char *buf)
{
return kstrtobool(buf, &luo_global.enabled);
@@ -88,7 +94,7 @@ static int __init luo_early_startup(void)
}
/* Retrieve LUO subtree, and verify its format. */
- err = kho_retrieve_subtree(LUO_FDT_KHO_ENTRY_NAME, &fdt_phys);
+ err = kho_retrieve_subtree(LUO_FDT_KHO_ENTRY_NAME, &fdt_phys, NULL);
if (err) {
if (err != -ENOENT) {
pr_err("failed to retrieve FDT '%s' from KHO: %pe\n",
@@ -172,7 +178,8 @@ static int __init luo_fdt_setup(void)
if (err)
goto exit_free;
- err = kho_add_subtree(LUO_FDT_KHO_ENTRY_NAME, fdt_out);
+ err = kho_add_subtree(LUO_FDT_KHO_ENTRY_NAME, fdt_out,
+ fdt_totalsize(fdt_out));
if (err)
goto exit_free;
luo_global.fdt_out = fdt_out;
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index 5acee4174bf0..a0a419085e28 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -108,12 +108,16 @@
#include <linux/liveupdate.h>
#include <linux/module.h>
#include <linux/sizes.h>
+#include <linux/xarray.h>
#include <linux/slab.h>
#include <linux/string.h>
#include "luo_internal.h"
static LIST_HEAD(luo_file_handler_list);
+/* Keep track of files being preserved by LUO */
+static DEFINE_XARRAY(luo_preserved_files);
+
/* 2 4K pages, give space for 128 files per file_set */
#define LUO_FILE_PGCNT 2ul
#define LUO_FILE_MAX \
@@ -203,6 +207,12 @@ static void luo_free_files_mem(struct luo_file_set *file_set)
file_set->files = NULL;
}
+static unsigned long luo_get_id(struct liveupdate_file_handler *fh,
+ struct file *file)
+{
+ return fh->ops->get_id ? fh->ops->get_id(file) : (unsigned long)file;
+}
+
static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
{
struct luo_file *iter;
@@ -248,6 +258,7 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
* Context: Can be called from an ioctl handler during normal system operation.
* Return: 0 on success. Returns a negative errno on failure:
* -EEXIST if the token is already used.
+ * -EBUSY if the file descriptor is already preserved by another session.
* -EBADF if the file descriptor is invalid.
* -ENOSPC if the file_set is full.
* -ENOENT if no compatible handler is found.
@@ -277,20 +288,28 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
goto err_fput;
err = -ENOENT;
+ down_read(&luo_register_rwlock);
list_private_for_each_entry(fh, &luo_file_handler_list, list) {
if (fh->ops->can_preserve(fh, file)) {
- err = 0;
+ if (try_module_get(fh->ops->owner))
+ err = 0;
break;
}
}
+ up_read(&luo_register_rwlock);
/* err is still -ENOENT if no handler was found */
if (err)
goto err_free_files_mem;
+ err = xa_insert(&luo_preserved_files, luo_get_id(fh, file),
+ file, GFP_KERNEL);
+ if (err)
+ goto err_module_put;
+
err = luo_flb_file_preserve(fh);
if (err)
- goto err_free_files_mem;
+ goto err_erase_xa;
luo_file = kzalloc_obj(*luo_file);
if (!luo_file) {
@@ -320,6 +339,10 @@ err_kfree:
kfree(luo_file);
err_flb_unpreserve:
luo_flb_file_unpreserve(fh);
+err_erase_xa:
+ xa_erase(&luo_preserved_files, luo_get_id(fh, file));
+err_module_put:
+ module_put(fh->ops->owner);
err_free_files_mem:
luo_free_files_mem(file_set);
err_fput:
@@ -362,7 +385,10 @@ 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));
list_del(&luo_file->list);
file_set->count--;
@@ -606,6 +632,11 @@ int luo_retrieve_file(struct luo_file_set *file_set, u64 token,
luo_file->file = args.file;
/* Get reference so we can keep this file in LUO until finish */
get_file(luo_file->file);
+
+ WARN_ON(xa_insert(&luo_preserved_files,
+ luo_get_id(luo_file->fh, luo_file->file),
+ luo_file->file, GFP_KERNEL));
+
*filep = luo_file->file;
luo_file->retrieve_status = 1;
@@ -646,6 +677,7 @@ 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);
}
/**
@@ -701,8 +733,11 @@ int luo_file_finish(struct luo_file_set *file_set)
luo_file_finish_one(file_set, luo_file);
- if (luo_file->file)
+ if (luo_file->file) {
+ xa_erase(&luo_preserved_files,
+ luo_get_id(luo_file->fh, luo_file->file));
fput(luo_file->file);
+ }
list_del(&luo_file->list);
file_set->count--;
mutex_destroy(&luo_file->mutex);
@@ -777,22 +812,28 @@ int luo_file_deserialize(struct luo_file_set *file_set,
bool handler_found = false;
struct luo_file *luo_file;
+ down_read(&luo_register_rwlock);
list_private_for_each_entry(fh, &luo_file_handler_list, list) {
if (!strcmp(fh->compatible, file_ser[i].compatible)) {
- handler_found = true;
+ if (try_module_get(fh->ops->owner))
+ handler_found = true;
break;
}
}
+ up_read(&luo_register_rwlock);
if (!handler_found) {
- pr_warn("No registered handler for compatible '%s'\n",
+ pr_warn("No registered handler for compatible '%.*s'\n",
+ (int)sizeof(file_ser[i].compatible),
file_ser[i].compatible);
return -ENOENT;
}
luo_file = kzalloc_obj(*luo_file);
- if (!luo_file)
+ if (!luo_file) {
+ module_put(fh->ops->owner);
return -ENOMEM;
+ }
luo_file->fh = fh;
luo_file->file = NULL;
@@ -842,41 +883,28 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
return -EINVAL;
}
- /*
- * Ensure the system is quiescent (no active sessions).
- * This prevents registering new handlers while sessions are active or
- * while deserialization is in progress.
- */
- if (!luo_session_quiesce())
- return -EBUSY;
-
+ down_write(&luo_register_rwlock);
/* Check for duplicate compatible strings */
list_private_for_each_entry(fh_iter, &luo_file_handler_list, list) {
if (!strcmp(fh_iter->compatible, fh->compatible)) {
pr_err("File handler registration failed: Compatible string '%s' already registered.\n",
fh->compatible);
err = -EEXIST;
- goto err_resume;
+ goto err_unlock;
}
}
- /* Pin the module implementing the handler */
- if (!try_module_get(fh->ops->owner)) {
- err = -EAGAIN;
- goto err_resume;
- }
-
INIT_LIST_HEAD(&ACCESS_PRIVATE(fh, flb_list));
INIT_LIST_HEAD(&ACCESS_PRIVATE(fh, list));
list_add_tail(&ACCESS_PRIVATE(fh, list), &luo_file_handler_list);
- luo_session_resume();
+ up_write(&luo_register_rwlock);
liveupdate_test_register(fh);
return 0;
-err_resume:
- luo_session_resume();
+err_unlock:
+ up_write(&luo_register_rwlock);
return err;
}
@@ -886,41 +914,13 @@ err_resume:
*
* Unregisters the file handler from the liveupdate core. This function
* reverses the operations of liveupdate_register_file_handler().
- *
- * It ensures safe removal by checking that:
- * No live update session is currently in progress.
- * No FLB registered with this file handler.
- *
- * If the unregistration fails, the internal test state is reverted.
- *
- * Return: 0 Success. -EOPNOTSUPP when live update is not enabled. -EBUSY A live
- * update is in progress, can't quiesce live update or FLB is registred with
- * this file handler.
*/
-int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
+void liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
{
- int err = -EBUSY;
-
if (!liveupdate_enabled())
- return -EOPNOTSUPP;
-
- liveupdate_test_unregister(fh);
-
- if (!luo_session_quiesce())
- goto err_register;
-
- if (!list_empty(&ACCESS_PRIVATE(fh, flb_list)))
- goto err_resume;
+ return;
+ guard(rwsem_write)(&luo_register_rwlock);
+ luo_flb_unregister_all(fh);
list_del(&ACCESS_PRIVATE(fh, list));
- module_put(fh->ops->owner);
- luo_session_resume();
-
- return 0;
-
-err_resume:
- luo_session_resume();
-err_register:
- liveupdate_test_register(fh);
- return err;
}
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index f52e8114837e..00f5494812c4 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -89,13 +89,18 @@ struct luo_flb_link {
static struct luo_flb_private *luo_flb_get_private(struct liveupdate_flb *flb)
{
struct luo_flb_private *private = &ACCESS_PRIVATE(flb, private);
+ static DEFINE_SPINLOCK(luo_flb_init_lock);
+ if (smp_load_acquire(&private->initialized))
+ return private;
+
+ guard(spinlock)(&luo_flb_init_lock);
if (!private->initialized) {
mutex_init(&private->incoming.lock);
mutex_init(&private->outgoing.lock);
INIT_LIST_HEAD(&private->list);
private->users = 0;
- private->initialized = true;
+ smp_store_release(&private->initialized, true);
}
return private;
@@ -110,10 +115,15 @@ static int luo_flb_file_preserve_one(struct liveupdate_flb *flb)
struct liveupdate_flb_op_args args = {0};
int err;
+ if (!try_module_get(flb->ops->owner))
+ return -ENODEV;
+
args.flb = flb;
err = flb->ops->preserve(&args);
- if (err)
+ if (err) {
+ module_put(flb->ops->owner);
return err;
+ }
private->outgoing.data = args.data;
private->outgoing.obj = args.obj;
}
@@ -141,6 +151,7 @@ static void luo_flb_file_unpreserve_one(struct liveupdate_flb *flb)
private->outgoing.data = 0;
private->outgoing.obj = NULL;
+ module_put(flb->ops->owner);
}
}
}
@@ -176,12 +187,17 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
if (!found)
return -ENOENT;
+ if (!try_module_get(flb->ops->owner))
+ return -ENODEV;
+
args.flb = flb;
args.data = private->incoming.data;
err = flb->ops->retrieve(&args);
- if (err)
+ if (err) {
+ module_put(flb->ops->owner);
return err;
+ }
private->incoming.obj = args.obj;
private->incoming.retrieved = true;
@@ -215,6 +231,7 @@ static void luo_flb_file_finish_one(struct liveupdate_flb *flb)
private->incoming.data = 0;
private->incoming.obj = NULL;
private->incoming.finished = true;
+ module_put(flb->ops->owner);
}
}
}
@@ -240,17 +257,20 @@ int luo_flb_file_preserve(struct liveupdate_file_handler *fh)
struct luo_flb_link *iter;
int err = 0;
+ down_read(&luo_register_rwlock);
list_for_each_entry(iter, flb_list, list) {
err = luo_flb_file_preserve_one(iter->flb);
if (err)
goto exit_err;
}
+ up_read(&luo_register_rwlock);
return 0;
exit_err:
list_for_each_entry_continue_reverse(iter, flb_list, list)
luo_flb_file_unpreserve_one(iter->flb);
+ up_read(&luo_register_rwlock);
return err;
}
@@ -272,6 +292,7 @@ void luo_flb_file_unpreserve(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_reverse(iter, flb_list, list)
luo_flb_file_unpreserve_one(iter->flb);
}
@@ -292,10 +313,67 @@ void luo_flb_file_finish(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_reverse(iter, flb_list, list)
luo_flb_file_finish_one(iter->flb);
}
+static void luo_flb_unregister_one(struct liveupdate_file_handler *fh,
+ struct liveupdate_flb *flb)
+{
+ struct luo_flb_private *private = luo_flb_get_private(flb);
+ struct list_head *flb_list = &ACCESS_PRIVATE(fh, flb_list);
+ struct luo_flb_link *iter;
+ bool found = false;
+
+ /* Find and remove the link from the file handler's list */
+ list_for_each_entry(iter, flb_list, list) {
+ if (iter->flb == flb) {
+ list_del(&iter->list);
+ kfree(iter);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ pr_warn("Failed to unregister FLB '%s': not found in file handler '%s'\n",
+ flb->compatible, fh->compatible);
+ return;
+ }
+
+ private->users--;
+
+ /*
+ * If this is the last file-handler with which we are registred, remove
+ * from the global list.
+ */
+ if (!private->users) {
+ list_del_init(&private->list);
+ luo_flb_global.count--;
+ }
+}
+
+/**
+ * luo_flb_unregister_all - Unregister all FLBs associated with a file handler.
+ * @fh: The file handler whose FLBs should be unregistered.
+ *
+ * This function iterates through the list of FLBs associated with the given
+ * file handler and unregisters them all one by one.
+ */
+void luo_flb_unregister_all(struct liveupdate_file_handler *fh)
+{
+ struct list_head *flb_list = &ACCESS_PRIVATE(fh, flb_list);
+ struct luo_flb_link *iter, *tmp;
+
+ if (!liveupdate_enabled())
+ return;
+
+ lockdep_assert_held_write(&luo_register_rwlock);
+ list_for_each_entry_safe(iter, tmp, flb_list, list)
+ luo_flb_unregister_one(fh, iter->flb);
+}
+
/**
* liveupdate_register_flb - Associate an FLB with a file handler and register it globally.
* @fh: The file handler that will now depend on the FLB.
@@ -326,7 +404,6 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
struct luo_flb_link *link __free(kfree) = NULL;
struct liveupdate_flb *gflb;
struct luo_flb_link *iter;
- int err;
if (!liveupdate_enabled())
return -EOPNOTSUPP;
@@ -347,19 +424,12 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
if (!link)
return -ENOMEM;
- /*
- * Ensure the system is quiescent (no active sessions).
- * This acts as a global lock for registration: no other thread can
- * be in this section, and no sessions can be creating/using FDs.
- */
- if (!luo_session_quiesce())
- return -EBUSY;
+ guard(rwsem_write)(&luo_register_rwlock);
/* Check that this FLB is not already linked to this file handler */
- err = -EEXIST;
list_for_each_entry(iter, flb_list, list) {
if (iter->flb == flb)
- goto err_resume;
+ return -EEXIST;
}
/*
@@ -367,25 +437,16 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
* is registered
*/
if (!private->users) {
- if (WARN_ON(!list_empty(&private->list))) {
- err = -EINVAL;
- goto err_resume;
- }
+ if (WARN_ON(!list_empty(&private->list)))
+ return -EINVAL;
- if (luo_flb_global.count == LUO_FLB_MAX) {
- err = -ENOSPC;
- goto err_resume;
- }
+ if (luo_flb_global.count == LUO_FLB_MAX)
+ return -ENOSPC;
/* Check that compatible string is unique in global list */
list_private_for_each_entry(gflb, &luo_flb_global.list, private.list) {
if (!strcmp(gflb->compatible, flb->compatible))
- goto err_resume;
- }
-
- if (!try_module_get(flb->ops->owner)) {
- err = -EAGAIN;
- goto err_resume;
+ return -EEXIST;
}
list_add_tail(&private->list, &luo_flb_global.list);
@@ -396,13 +457,8 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
private->users++;
link->flb = flb;
list_add_tail(&no_free_ptr(link)->list, flb_list);
- luo_session_resume();
return 0;
-
-err_resume:
- luo_session_resume();
- return err;
}
/**
@@ -418,63 +474,17 @@ err_resume:
* the FLB is removed from the global registry and the reference to its
* owner module (acquired during registration) is released.
*
- * Context: This function ensures the session is quiesced (no active FDs
- * being created) during the update. It is typically called from a
- * subsystem's module exit function.
- * Return: 0 on success.
- * -EOPNOTSUPP if live update is disabled.
- * -EBUSY if the live update session is active and cannot be quiesced.
- * -ENOENT if the FLB was not found in the file handler's list.
+ * Context: It is typically called from a subsystem's module exit function.
*/
-int liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
- struct liveupdate_flb *flb)
+void liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
+ struct liveupdate_flb *flb)
{
- struct luo_flb_private *private = luo_flb_get_private(flb);
- struct list_head *flb_list = &ACCESS_PRIVATE(fh, flb_list);
- struct luo_flb_link *iter;
- int err = -ENOENT;
-
if (!liveupdate_enabled())
- return -EOPNOTSUPP;
+ return;
- /*
- * Ensure the system is quiescent (no active sessions).
- * This acts as a global lock for unregistration.
- */
- if (!luo_session_quiesce())
- return -EBUSY;
+ guard(rwsem_write)(&luo_register_rwlock);
- /* Find and remove the link from the file handler's list */
- list_for_each_entry(iter, flb_list, list) {
- if (iter->flb == flb) {
- list_del(&iter->list);
- kfree(iter);
- err = 0;
- break;
- }
- }
-
- if (err)
- goto err_resume;
-
- private->users--;
- /*
- * If this is the last file-handler with which we are registred, remove
- * from the global list, and relese module reference.
- */
- if (!private->users) {
- list_del_init(&private->list);
- luo_flb_global.count--;
- module_put(flb->ops->owner);
- }
-
- luo_session_resume();
-
- return 0;
-
-err_resume:
- luo_session_resume();
- return err;
+ luo_flb_unregister_one(fh, flb);
}
/**
@@ -492,7 +502,8 @@ err_resume:
*
* Return: 0 on success, or a negative errno on failure. -ENODATA means no
* incoming FLB data, -ENOENT means specific flb not found in the incoming
- * data, and -EOPNOTSUPP when live update is disabled or not configured.
+ * data, -ENODEV if the FLB's module is unloading, and -EOPNOTSUPP when
+ * live update is disabled or not configured.
*/
int liveupdate_flb_get_incoming(struct liveupdate_flb *flb, void **objp)
{
@@ -638,6 +649,7 @@ void luo_flb_serialize(void)
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);
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 8083d8739b09..875844d7a41d 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -77,14 +77,14 @@ struct luo_session {
struct mutex mutex;
};
+extern struct rw_semaphore luo_register_rwlock;
+
int luo_session_create(const char *name, struct file **filep);
int luo_session_retrieve(const char *name, struct file **filep);
int __init luo_session_setup_outgoing(void *fdt);
int __init luo_session_setup_incoming(void *fdt);
int luo_session_serialize(void);
int luo_session_deserialize(void);
-bool luo_session_quiesce(void);
-void luo_session_resume(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);
@@ -103,16 +103,15 @@ 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_finish(struct liveupdate_file_handler *fh);
+void luo_flb_unregister_all(struct liveupdate_file_handler *fh);
int __init luo_flb_setup_outgoing(void *fdt);
int __init luo_flb_setup_incoming(void *fdt);
void luo_flb_serialize(void);
#ifdef CONFIG_LIVEUPDATE_TEST
void liveupdate_test_register(struct liveupdate_file_handler *fh);
-void liveupdate_test_unregister(struct liveupdate_file_handler *fh);
#else
static inline void liveupdate_test_register(struct liveupdate_file_handler *fh) { }
-static inline void liveupdate_test_unregister(struct liveupdate_file_handler *fh) { }
#endif
#endif /* _LINUX_LUO_INTERNAL_H */
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 25ae704d7787..a3327a28fc1f 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -544,7 +544,8 @@ int luo_session_deserialize(void)
session = luo_session_alloc(sh->ser[i].name);
if (IS_ERR(session)) {
- pr_warn("Failed to allocate session [%s] during deserialization %pe\n",
+ pr_warn("Failed to allocate session [%.*s] during deserialization %pe\n",
+ (int)sizeof(sh->ser[i].name),
sh->ser[i].name, session);
return PTR_ERR(session);
}
@@ -606,46 +607,3 @@ err_undo:
return err;
}
-/**
- * luo_session_quiesce - Ensure no active sessions exist and lock session lists.
- *
- * Acquires exclusive write locks on both incoming and outgoing session lists.
- * It then validates no sessions exist in either list.
- *
- * This mechanism is used during file handler un/registration to ensure that no
- * sessions are currently using the handler, and no new sessions can be created
- * while un/registration is in progress.
- *
- * This prevents registering new handlers while sessions are active or
- * while deserialization is in progress.
- *
- * Return:
- * true - System is quiescent (0 sessions) and locked.
- * false - Active sessions exist. The locks are released internally.
- */
-bool luo_session_quiesce(void)
-{
- down_write(&luo_session_global.incoming.rwsem);
- down_write(&luo_session_global.outgoing.rwsem);
-
- if (luo_session_global.incoming.count ||
- luo_session_global.outgoing.count) {
- up_write(&luo_session_global.outgoing.rwsem);
- up_write(&luo_session_global.incoming.rwsem);
- return false;
- }
-
- return true;
-}
-
-/**
- * luo_session_resume - Unlock session lists and resume normal activity.
- *
- * Releases the exclusive locks acquired by a successful call to
- * luo_session_quiesce().
- */
-void luo_session_resume(void)
-{
- up_write(&luo_session_global.outgoing.rwsem);
- up_write(&luo_session_global.incoming.rwsem);
-}