On 06-02 11:13, Mike Rapoport wrote:
> On Sat, 30 May 2026 22:19:32 +0000, Pasha Tatashin 
> <[email protected]> wrote:
> > diff --git a/include/linux/kho_block.h b/include/linux/kho_block.h
> > new file mode 100644
> > index 000000000000..5e6b87b1befa
> > --- /dev/null
> > +++ b/include/linux/kho_block.h
> > @@ -0,0 +1,79 @@
> > [ ... skip 19 lines ... ]
> > +   struct list_head list;
> > +   struct kho_block_header_ser *ser;
> > +};
> > +
> > +/**
> > + * struct kho_block_set - A set of blocks that belong to the same object.
> 
> "same object" sounds off to me. The blocks belong to the same module?
> user?
> 
> Thoughts?

user and module are not descriptive, as the same client/user/module can 
use multiple kho_block_set for different purposes. I suggest:

"struct kho_block_set - A set of blocks containing serialized entries of 
the same type."

> 
> > + * @blocks:          The list of serialization blocks (struct kho_block).
> > + * @nblocks:         The number of allocated serialization blocks.
> > + * @head_pa:         Physical address of the first block header.
> > + * @entry_size:      The size of each entry in the blocks.
> 
> I think it's "... entry in a block"

It is 'in the blocks' (or 'across the blocks') because a single  
block_set  can contain multiple blocks, and they all share this same 
uniform entry size.

> 
> > [ ... skip 42 lines ... ]
> > +
> > +void kho_block_it_init(struct kho_block_it *it, struct kho_block_set *bs);
> > +void *kho_block_it_next(struct kho_block_it *it);
> > +void *kho_block_it_read(struct kho_block_it *it);
> > +void *kho_block_it_prev(struct kho_block_it *it);
> > +void kho_block_it_finalize(struct kho_block_it *it);
> 
> These operate on block sets, should be reflected in the names.
> Can be kho_blocks_ to avoid too long names.

We have already started using kho_block_set. Although it is longer, I 
prefer to avoid kho_blocks/kho_block because the subtle difference makes 
them difficult to read and prone to typos during coding. Let's use 
kho_block_set for operations on a block_set.

> >
> > diff --git a/kernel/liveupdate/kho_block.c b/kernel/liveupdate/kho_block.c
> > new file mode 100644
> > index 000000000000..a4e650af946f
> > --- /dev/null
> > +++ b/kernel/liveupdate/kho_block.c
> > @@ -0,0 +1,384 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +
> > +/*
> > + * Copyright (c) 2026, Google LLC.
> > + * Pasha Tatashin <[email protected]>
> > + */
> > +
> > +/**
> > + * DOC: KHO Serialization Blocks
> > + *
> > + * KHO provides a mechanism to preserve stateful data across a kexec 
> > handover
> > + * by serializing it into memory blocks. This file provides the common
> 
> "This file" does not look good in HTML docs.

Fixed.

> 
> > [ ... skip 15 lines ... ]
> > +
> > +/*
> > + * 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.
> > + */
> 
> Can you add how much memory it is and how many entries with, say, 4 u64
> it can accommodate?

Done

> 
> > [ ... skip 13 lines ... ]
> > +{
> > +   if (unlikely(!bs->count_per_block)) {
> > +           bs->count_per_block = (KHO_BLOCK_SIZE -
> > +                                  sizeof(struct kho_block_header_ser)) /
> > +                                 bs->entry_size;
> > +           WARN_ON(!bs->count_per_block);
> 
> Don't you want to set count_per_block in _init()?

Done.

> 
> > [ ... skip 29 lines ... ]
> > +   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);
> 
> No locks?

Linked blocks are not internally synchronized; that is a responsibility 
of the caller, similar to linked lists.

> 
> > [ ... skip 12 lines ... ]
> > + * @bs:    The block set.
> > + * @count: The current number of entries.
> > + *
> > + * This function handles the dynamic expansion of a block set. It allocates
> > + * and links a new serialization block if the provided entry count matches
> > + * the current total capacity of the set.
> 
> This is a weird semantics for a generic API. I'd expect _grow() would
> add count - current_count blocks.

Changed the semantics to use target count, i.e. "The target number of 
valid entries to accommodate."

> 
> > [ ... skip 25 lines ... ]
> > +}
> > +
> > +/**
> > + * kho_block_shrink - Conditionally destroy the last block in a block set.
> > + * @bs:              The block set.
> > + * @count:           The current number of entries across all blocks.
> 
> Maybe 
>       ... of valid entries?

OK

> 
> > + *
> > + * This function checks if the last block in the set is redundant based on 
> > the
> > + * total entry count and the capacity of the preceding blocks. If the entry
> > + * count can be accommodated by the blocks that come before the last one, 
> > the
> > + * last block is destroyed and removed from the set.
> 
> This should mention that it's the caller responsibility to ensure that
> entries are removed in the right order.

OK

> 
> > [ ... skip 49 lines ... ]
> > +
> > +           fast = phys_to_virt(fast->next);
> > +           slow = phys_to_virt(slow->next);
> > +
> > +           if (slow == fast) {
> > +                   pr_err("Cyclic list detected\n");
> 
> Maybe "block set is corrupted"?

OK

> 
> > +                   return false;
> > +           }
> > +   }
> > +
> > +   return true;
> > +}
> > +
> > +/**
> > + * kho_block_restore - Restore a block set from a physical address.
> > + * @bs:      The block set to restore.
> > + * @head_pa: Physical address of the first block header.
> 
> I'd mention that the block set should be allocated and initialized

Done

> 
> > [ ... skip 10 lines ... ]
> > +   bs->incoming = true;
> > +   if (!head_pa)
> > +           return 0;
> > +
> > +   bs->head_pa = head_pa;
> > +   if (!kho_cyclic_blocks_check(bs)) {
> 
> if (kho_block_set_cyclic()) 
> 
> reads nicer IMO

Sure, done.

> 
> > [ ... skip 87 lines ... ]
> > +{
> > +   if (!it->block)
> > +           return NULL;
> > +
> > +   if (it->i == kho_block_count_per_block(it->bs)) {
> > +           it->block->ser->count = it->i;
> 
> Why iterator updates ser->count?

The new name  kho_block_set_it_reserve_entry()  clarifies that this is a 
write/reservation path function (unlike the original read-only  next  
name). Reserving a slot to write entries naturally implies 
writing/finalizing the metadata count in the physical block header when 
a block becomes full

> > +           if (list_is_last(&it->block->list, &it->bs->blocks))
> > +                   return NULL;
> > +           it->block = list_next_entry(it->block, list);
> > +           it->i = 0;
> > +   }
> > +
> > +   return (void *)(it->block->ser + 1) + (it->i++ * it->bs->entry_size);
> 
> In a month we'll need an LLM's help to understand what it does.

Good thing in a month we will have even stronger LLMs to help us :-)

Anyways, clean-up ...

> 
> > +}
> > +
> > +/**
> > + * kho_block_it_read - Return the next entry slot for reading.
> > + * @it: The block iterator.
> 
> And what is the conceptual difference between this and _it_next()?

This was updated :-)

> 
> > [ ... skip 49 lines ... ]
> > + * @it: The block iterator.
> > + */
> > +void kho_block_it_finalize(struct kho_block_it *it)
> > +{
> > +   if (it->block)
> > +           it->block->ser->count = it->i;
> 
> So, it looks like the intention of _it_next is for write, and this ends a
> write iteration.
> 
> I think the names should be adjusted to make it clearer.

Done

> 
> -- 
> Sincerely yours,
> Mike.
> 

Reply via email to