// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2026, Google LLC. * Pasha Tatashin */ /** * DOC: KHO Serialization Blocks * * KHO provides a mechanism to preserve stateful data across a kexec handover * by serializing it into memory blocks, and provides the common * infrastructure for managing these blocks. * * Each block consists of a header (struct kho_block_header_ser) followed by an * array of serialized entries. Multiple blocks are linked together via a * physical pointer in the header, forming a linked list that can be easily * traversed in both the current and the next kernel. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include /* * Safeguard limit for the number of serialization blocks. This is used to * prevent infinite loops and excessive memory allocation in case of memory * corruption in the preserved state. * * With a 4KB page size, 10k blocks is about 40MB. For 32-byte entries * (e.g. 4 u64s), each block holds up to 127 entries (accounting for the * 16-byte header), allowing the block set to hold up to 1.27M entries. */ #define KHO_MAX_BLOCKS 10000 /** * kho_block_set_init - Initialize a block set. * @bs: The block set to initialize. * @entry_size: The size of each entry in the blocks. */ void kho_block_set_init(struct kho_block_set *bs, size_t entry_size) { *bs = (struct kho_block_set)KHO_BLOCK_SET_INIT(*bs, entry_size); WARN_ON_ONCE(!bs->count_per_block); } /* Serialized entries start immediately after the block header */ static void *kho_block_entries(struct kho_block *block) { return (void *)(block->ser + 1); } /* Get the address of the serialized entry at the specified index */ static void *kho_block_entry(struct kho_block_set_it *it, u64 index) { return kho_block_entries(it->block) + (index * it->bs->entry_size); } /* Free serialized data */ static void kho_block_free_ser(struct kho_block_set *bs, struct kho_block_header_ser *ser) { if (bs->incoming) kho_restore_free(ser); else kho_unpreserve_free(ser); } static struct kho_block_header_ser *kho_block_alloc_ser(struct kho_block_set *bs) { WARN_ON_ONCE(bs->incoming); return kho_alloc_preserve(KHO_BLOCK_SIZE); } static int kho_block_add(struct kho_block_set *bs, struct kho_block_header_ser *ser) { struct kho_block *block, *last; if (bs->nblocks >= KHO_MAX_BLOCKS) return -ENOSPC; block = kzalloc_obj(*block); if (!block) return -ENOMEM; block->ser = ser; last = list_last_entry_or_null(&bs->blocks, struct kho_block, list); list_add_tail(&block->list, &bs->blocks); bs->nblocks++; if (last) last->ser->next = virt_to_phys(ser); else bs->head_pa = virt_to_phys(ser); return 0; } static int kho_block_set_grow_one(struct kho_block_set *bs) { struct kho_block_header_ser *ser; int err; ser = kho_block_alloc_ser(bs); if (IS_ERR(ser)) return PTR_ERR(ser); err = kho_block_add(bs, ser); if (err) { kho_block_free_ser(bs, ser); return err; } return 0; } static void kho_block_set_shrink_one(struct kho_block_set *bs) { struct kho_block *last, *new_last; if (list_empty(&bs->blocks)) return; last = list_last_entry(&bs->blocks, struct kho_block, list); list_del(&last->list); bs->nblocks--; kho_block_free_ser(bs, last->ser); kfree(last); new_last = list_last_entry_or_null(&bs->blocks, struct kho_block, list); if (new_last) new_last->ser->next = 0; else bs->head_pa = 0; } /** * kho_block_set_grow - Expand the block set to accommodate the target count. * @bs: The block set. * @count: The target number of valid entries to accommodate. * * Dynamically preallocates and links preserved memory blocks if the target * entry count exceeds the current total capacity of the set, ensuring they * are available during serialization/deserialization. * * Context: Caller must hold a lock protecting the block set. * Return: 0 on success, or a negative errno on failure. */ int kho_block_set_grow(struct kho_block_set *bs, u64 count) { long orig_nblocks = bs->nblocks; int err; if (WARN_ON_ONCE(bs->incoming)) return -EINVAL; while (count > bs->nblocks * bs->count_per_block) { err = kho_block_set_grow_one(bs); if (err) goto err_shrink; } return 0; err_shrink: while (bs->nblocks > orig_nblocks) kho_block_set_shrink_one(bs); return err; } /** * kho_block_set_shrink - Shrink the block set to accommodate the target count. * @bs: The block set. * @count: The target number of valid entries to accommodate. * * Releases and unallocates redundant preserved memory blocks. Checks if the * last block in the set can be removed because the remaining entry count is * fully accommodated by the preceding blocks. * * Note: It is the caller's responsibility to ensure that entries are removed * in the reverse order of their insertion. Because shrinking destroys the last * block in the set, removing entries in any other order would corrupt active * data. * * Context: Caller must hold a lock protecting the block set. */ void kho_block_set_shrink(struct kho_block_set *bs, u64 count) { while (bs->nblocks > 0 && count <= (bs->nblocks - 1) * bs->count_per_block) kho_block_set_shrink_one(bs); } /* * 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. * * Return: true if a cycle or corruption is detected, false otherwise. */ static bool kho_block_set_is_cyclic(struct kho_block_set *bs) { struct kho_block_header_ser *fast; struct kho_block_header_ser *slow; int count = 0; fast = phys_to_virt(bs->head_pa); slow = fast; while (fast) { 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) break; fast = phys_to_virt(fast->next); slow = phys_to_virt(slow->next); if (slow == fast) { pr_err("Block set is corrupted\n"); return true; } } return false; } /** * kho_block_set_restore - Restore a block set from a physical address. * @bs: The block set to restore. * @head_pa: Physical address of the first block header. * * Restores a serialized block set from a given physical address. The caller is * responsible for ensuring that the block set @bs has been allocated and * initialized prior to calling this function. * * Return: 0 on success, or a negative errno on failure. */ int kho_block_set_restore(struct kho_block_set *bs, u64 head_pa) { struct kho_block_header_ser *ser; u64 next_pa = head_pa; int err; /* Restored block sets use size from the previous kernel */ bs->incoming = true; if (!head_pa) return 0; bs->head_pa = head_pa; if (kho_block_set_is_cyclic(bs)) { bs->head_pa = 0; return -EINVAL; } while (next_pa) { ser = phys_to_virt(next_pa); if (!ser->count || ser->count > bs->count_per_block) { pr_warn("Block contains invalid entry count: %llu\n", ser->count); err = -EINVAL; goto err_destroy; } err = kho_block_add(bs, ser); if (err) goto err_destroy; next_pa = ser->next; } return 0; err_destroy: 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); next_pa = next_ser->next; kho_block_free_ser(bs, next_ser); } return err; } /** * kho_block_set_destroy - Destroy all blocks in a block set. * @bs: The block set. */ void kho_block_set_destroy(struct kho_block_set *bs) { struct kho_block *block, *tmp; list_for_each_entry_safe(block, tmp, &bs->blocks, list) { list_del(&block->list); kho_block_free_ser(bs, block->ser); kfree(block); } bs->nblocks = 0; bs->head_pa = 0; } /** * kho_block_set_clear - Clear all serialized data in a block set. * @bs: The block set to clear. */ void kho_block_set_clear(struct kho_block_set *bs) { struct kho_block *block; list_for_each_entry(block, &bs->blocks, list) { block->ser->count = 0; memset(block->ser + 1, 0, KHO_BLOCK_SIZE - sizeof(*block->ser)); } } /** * kho_block_set_it_init - Initialize a block set iterator. * @it: The iterator to initialize. * @bs: The block set to iterate over. */ void kho_block_set_it_init(struct kho_block_set_it *it, struct kho_block_set *bs) { it->bs = bs; it->block = list_first_entry_or_null(&bs->blocks, struct kho_block, list); it->i = 0; } /** * kho_block_set_it_reserve_entry - Reserve and return the next available slot for writing. * @it: The block iterator. * * Reserves a slot in the current block during state serialization to add a new * entry, advancing the internal index. If the current block is full, it * automatically moves to the next block in the set. * * Return: A pointer to the reserved entry slot, or NULL if the block set's * capacity is fully exhausted. */ void *kho_block_set_it_reserve_entry(struct kho_block_set_it *it) { void *entry; if (!it->block) return NULL; if (it->i == it->bs->count_per_block) { if (list_is_last(&it->block->list, &it->bs->blocks)) return NULL; it->block = list_next_entry(it->block, list); it->i = 0; } entry = kho_block_entry(it, it->i++); it->block->ser->count = it->i; return entry; } /** * kho_block_set_it_read_entry - Read the next serialized entry from the block set. * @it: The block iterator. * * Iterates through previously written entries during state deserialization, * respecting the actual count stored in each block's header. * * Return: A pointer to the next serialized entry, or NULL if all serialized * entries have been read. */ void *kho_block_set_it_read_entry(struct kho_block_set_it *it) { if (!it->block) return NULL; if (it->i == it->block->ser->count) { if (list_is_last(&it->block->list, &it->bs->blocks)) return NULL; it->block = list_next_entry(it->block, list); it->i = 0; } return kho_block_entry(it, it->i++); } /** * kho_block_set_it_prev - Return the previous entry slot in the block set. * @it: The block iterator. * * If the current index is at the start of a block, it automatically moves to * the end of the previous block. * * Return: A pointer to the previous entry slot, or NULL if at the very * beginning of the block set. */ void *kho_block_set_it_prev(struct kho_block_set_it *it) { if (!it->block) return NULL; if (it->i == 0) { if (list_is_first(&it->block->list, &it->bs->blocks)) return NULL; it->block = list_prev_entry(it->block, list); it->i = it->bs->count_per_block; } return kho_block_entry(it, --it->i); }