[PATCH v1 6/8] kho: support read-only KHO mode in kho_block
From: Pasha Tatashin
Date: Sun Oct 18 2026 - 22:16:29 EST
In read-only KHO mode, incoming serialized kho_block chains lie outside
the kernel's direct map and must not be modified or freed back into the
page allocator.
Map incoming serialized blocks read-only via kho_ro_map_folio(...,
PAGE_KERNEL_RO) during cycle detection and restoration, and unmap them
via kho_ro_unmap() instead of calling kho_restore_free() when destroying
or shrinking a block set in read-only mode.
Signed-off-by: Pasha Tatashin <pasha.tatashin@xxxxxxxxxx>
---
kernel/liveupdate/Makefile | 5 +--
kernel/liveupdate/kho_block.c | 76 ++++++++++++++++++++++++++---------
2 files changed, 60 insertions(+), 21 deletions(-)
diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
index f8b09e04bea2..d3587048e619 100644
--- a/kernel/liveupdate/Makefile
+++ b/kernel/liveupdate/Makefile
@@ -1,14 +1,13 @@
# SPDX-License-Identifier: GPL-2.0
luo-y := \
- kho_block.o \
luo_core.o \
luo_file.o \
luo_flb.o \
luo_session.o
-obj-$(CONFIG_KEXEC_HANDOVER) += kexec_handover.o
-obj-$(CONFIG_KEXEC_HANDOVER_READONLY) += kexec_handover_ro.o
+obj-$(CONFIG_KEXEC_HANDOVER) += kexec_handover.o kho_block.o
+obj-$(CONFIG_KEXEC_HANDOVER_READONLY) += kexec_handover_ro.o kho_block.o
obj-$(CONFIG_KEXEC_HANDOVER_DEBUGFS) += kexec_handover_debugfs.o
obj-$(CONFIG_LIVEUPDATE) += luo.o
diff --git a/kernel/liveupdate/kho_block.c b/kernel/liveupdate/kho_block.c
index a4e945f28fad..a3cf5b964b26 100644
--- a/kernel/liveupdate/kho_block.c
+++ b/kernel/liveupdate/kho_block.c
@@ -60,10 +60,32 @@ static void *kho_block_entry(struct kho_block_set_it *it, u64 index)
return kho_block_entries(it->block) + (index * it->bs->entry_size);
}
+static struct kho_block_header_ser *kho_block_map_ser(phys_addr_t pa)
+{
+ if (!pa)
+ return NULL;
+
+ if (kho_is_ro())
+ return kho_ro_map_folio(pa, PAGE_KERNEL_RO);
+
+ return phys_to_virt(pa);
+}
+
+static void kho_block_unmap_ser(struct kho_block_header_ser *ser)
+{
+ if (kho_is_ro())
+ kho_ro_unmap(ser);
+}
+
/* Free serialized data */
static void kho_block_free_ser(struct kho_block_set *bs,
struct kho_block_header_ser *ser)
{
+ if (kho_is_ro()) {
+ kho_block_unmap_ser(ser);
+ return;
+ }
+
if (bs->incoming)
kho_restore_free(ser);
else
@@ -197,6 +219,20 @@ void kho_block_set_shrink(struct kho_block_set *bs, u64 count)
kho_block_set_shrink_one(bs);
}
+static bool kho_block_step_pa(phys_addr_t pa, phys_addr_t *next_pa)
+{
+ struct kho_block_header_ser *ser;
+
+ ser = kho_block_map_ser(pa);
+ if (!ser)
+ return false;
+
+ *next_pa = ser->next;
+ kho_block_unmap_ser(ser);
+
+ return true;
+}
+
/*
* kho_block_set_is_cyclic - Check for cycles in a linked list of blocks.
* Uses Floyd's cycle-finding algorithm to ensure sanity of the incoming list.
@@ -205,30 +241,26 @@ void kho_block_set_shrink(struct kho_block_set *bs, u64 count)
*/
static bool kho_block_set_is_cyclic(struct kho_block_set *bs)
{
- struct kho_block_header_ser *fast;
- struct kho_block_header_ser *slow;
+ phys_addr_t fast_pa = bs->head_pa;
+ phys_addr_t slow_pa = bs->head_pa;
int count = 0;
- fast = phys_to_virt(bs->head_pa);
- slow = fast;
-
- while (fast) {
+ while (fast_pa) {
if (count++ >= KHO_MAX_BLOCKS) {
pr_err("Block set is corrupted\n");
return true;
}
- if (!fast->next)
- break;
-
- fast = phys_to_virt(fast->next);
- if (!fast->next)
+ if (!kho_block_step_pa(fast_pa, &fast_pa))
+ return true;
+ if (!fast_pa)
break;
- fast = phys_to_virt(fast->next);
- slow = phys_to_virt(slow->next);
+ if (!kho_block_step_pa(fast_pa, &fast_pa) ||
+ !kho_block_step_pa(slow_pa, &slow_pa))
+ return true;
- if (slow == fast) {
+ if (slow_pa == fast_pa) {
pr_err("Block set is corrupted\n");
return true;
}
@@ -266,16 +298,23 @@ int kho_block_set_restore(struct kho_block_set *bs, u64 head_pa)
}
while (next_pa) {
- ser = phys_to_virt(next_pa);
+ ser = kho_block_map_ser(next_pa);
+ if (!ser) {
+ err = -ENOMEM;
+ goto err_destroy;
+ }
if (!ser->count || ser->count > bs->count_per_block) {
pr_warn("Block contains invalid entry count: %llu\n",
ser->count);
+ kho_block_unmap_ser(ser);
err = -EINVAL;
goto err_destroy;
}
err = kho_block_add(bs, ser);
- if (err)
+ if (err) {
+ kho_block_unmap_ser(ser);
goto err_destroy;
+ }
next_pa = ser->next;
}
@@ -285,9 +324,10 @@ int kho_block_set_restore(struct kho_block_set *bs, u64 head_pa)
kho_block_set_destroy(bs);
/* Free the remaining un-restored blocks in the physical chain */
- while (next_pa) {
- struct kho_block_header_ser *next_ser = phys_to_virt(next_pa);
+ while (!kho_is_ro() && next_pa) {
+ struct kho_block_header_ser *next_ser;
+ next_ser = phys_to_virt(next_pa);
next_pa = next_ser->next;
kho_block_free_ser(bs, next_ser);
}
--
2.56.0.176.g3575e65682-goog