[PATCH v2 7/9] kho: support read-only KHO mode in kho_block

From: Pasha Tatashin

Date: Sun Oct 25 2026 - 13:43:55 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 skip 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/kho_block.c | 62 ++++++++++++++++++++++++++---------
1 file changed, 46 insertions(+), 16 deletions(-)

diff --git a/kernel/liveupdate/kho_block.c b/kernel/liveupdate/kho_block.c
index a4e945f28fad..5b08bd8a9623 100644
--- a/kernel/liveupdate/kho_block.c
+++ b/kernel/liveupdate/kho_block.c
@@ -60,10 +60,24 @@ 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);
+}
+
/* Free serialized data */
static void kho_block_free_ser(struct kho_block_set *bs,
struct kho_block_header_ser *ser)
{
+ if (kho_is_ro())
+ return;
+
if (bs->incoming)
kho_restore_free(ser);
else
@@ -197,6 +211,19 @@ 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;
+
+ 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 +232,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,7 +289,11 @@ 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 = -EINVAL;
+ goto err_destroy;
+ }
if (!ser->count || ser->count > bs->count_per_block) {
pr_warn("Block contains invalid entry count: %llu\n",
ser->count);
@@ -283,11 +310,14 @@ int kho_block_set_restore(struct kho_block_set *bs, u64 head_pa)

err_destroy:
kho_block_set_destroy(bs);
+ if (kho_is_ro())
+ return err;

/* 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);
+ 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.307.g5fe7bd680c-goog