aboutsummaryrefslogtreecommitdiffstats
diff options
authorMark Brown <broonie@kernel.org>2026-05-29 17:41:32 +0100
committerMark Brown <broonie@kernel.org>2026-05-29 17:41:32 +0100
commitbc9692464146ff31a0722831634f07a5810b8731 (patch)
tree3542807a55ea3cf7674119ec38c58c80bb93455b
parent9ce2244719b3b771d93ff0b8c6e150f7a618b6ec (diff)
parent3a29a9841f4bfb79840f7d1f8115cc7b25e744e3 (diff)
downloadlinux-next-history-bc9692464146ff31a0722831634f07a5810b8731.tar.gz
Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
-rw-r--r--include/linux/kho/abi/kexec_handover.h4
-rw-r--r--kernel/liveupdate/kexec_handover.c88
-rw-r--r--kernel/liveupdate/luo_core.c4
-rw-r--r--kernel/liveupdate/luo_session.c36
4 files changed, 91 insertions, 41 deletions
diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h
index 7e847a2339b09..fb2d37417ad9c 100644
--- a/include/linux/kho/abi/kexec_handover.h
+++ b/include/linux/kho/abi/kexec_handover.h
@@ -64,7 +64,7 @@
* Root KHO Node (/):
* - compatible: "kho-v3"
*
- * Indentifies the overall KHO ABI version.
+ * Identifies the overall KHO ABI version.
*
* - preserved-memory-map: u64
*
@@ -274,7 +274,7 @@ enum kho_radix_consts {
* and 1 bitmap level.
*/
KHO_TREE_MAX_DEPTH =
- DIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2,
+ DIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2 + 1,
KHO_TABLE_SIZE_LOG2) + 1,
};
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 2592f7ca16e2e..48d98fa9621e3 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -357,20 +357,6 @@ int kho_radix_walk_tree(struct kho_radix_tree *tree,
}
EXPORT_SYMBOL_GPL(kho_radix_walk_tree);
-static void __kho_unpreserve(struct kho_radix_tree *tree,
- unsigned long pfn, unsigned long end_pfn)
-{
- unsigned int order;
-
- while (pfn < end_pfn) {
- order = min(count_trailing_zeros(pfn), ilog2(end_pfn - pfn));
-
- kho_radix_del_page(tree, pfn, order);
-
- pfn += 1 << order;
- }
-}
-
/* For physically contiguous 0-order pages. */
static void kho_init_pages(struct page *page, unsigned long nr_pages)
{
@@ -607,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)
@@ -860,6 +856,37 @@ void kho_unpreserve_folio(struct folio *folio)
}
EXPORT_SYMBOL_GPL(kho_unpreserve_folio);
+static unsigned int __kho_preserve_pages_order(unsigned long start_pfn,
+ unsigned long end_pfn)
+{
+ unsigned int order = min(count_trailing_zeros(start_pfn),
+ ilog2(end_pfn - start_pfn));
+
+ /*
+ * Make sure all the pages in a single preservation are in the same NUMA
+ * node. The restore machinery can not cope with a preservation spanning
+ * multiple NUMA nodes.
+ */
+ while (pfn_to_nid(start_pfn) != pfn_to_nid(start_pfn + (1UL << order) - 1))
+ order--;
+
+ return order;
+}
+
+static void __kho_unpreserve(struct kho_radix_tree *tree,
+ unsigned long pfn, unsigned long end_pfn)
+{
+ unsigned int order;
+
+ while (pfn < end_pfn) {
+ order = __kho_preserve_pages_order(pfn, end_pfn);
+
+ kho_radix_del_page(tree, pfn, order);
+
+ pfn += 1 << order;
+ }
+}
+
/**
* kho_preserve_pages - preserve contiguous pages across kexec
* @page: first page in the list.
@@ -885,16 +912,7 @@ int kho_preserve_pages(struct page *page, unsigned long nr_pages)
}
while (pfn < end_pfn) {
- unsigned int order =
- min(count_trailing_zeros(pfn), ilog2(end_pfn - pfn));
-
- /*
- * Make sure all the pages in a single preservation are in the
- * same NUMA node. The restore machinery can not cope with a
- * preservation spanning multiple NUMA nodes.
- */
- while (pfn_to_nid(pfn) != pfn_to_nid(pfn + (1UL << order) - 1))
- order--;
+ unsigned int order = __kho_preserve_pages_order(pfn, end_pfn);
err = kho_radix_add_page(tree, pfn, order);
if (err) {
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 803f51c842756..5d5827ced73c8 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -36,6 +36,10 @@
*
* LUO uses Kexec Handover to transfer memory state from the current kernel to
* the next kernel. For more details see Documentation/core-api/kho/index.rst.
+ *
+ * .. note::
+ * To enable LUO, boot the kernel with the ``liveupdate=on`` command line
+ * parameter.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 7a42385dabe27..74c39d93d45a1 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -295,32 +295,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 +371,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;