Re: [RFC PATCH v2 2/2] kho: Introduce preserve/restore APIs for unsplit pages

Mike Rapoport <[email protected]> Mon, 20 Jul 2026 19:57:15 +0300
Newsgroups org.infradead.lists.kexec,org.kernel.vger.linux-kernel,org.kvack.linux-mm
Message-ID <178456663513.143128.14871307686052703561.b4-review@b4>
> The current KHO page preservation APIs (e.g. kho_preserve_pages) assume
> that multi-page blocks are split into independent 4KB pages during
> restoration. This is incompatible with high-order non-compound pages,
> such as DMA buffers, which must be restored with tail pages having a
> zero reference count.
> 
> Introduce explicit preserve and restore APIs for unsplit pages,

                                 high-order non-compound ^

> which preserve and restore a high-order page block as a single unit,
> applying a refcount of 1 to the head page while leaving tail pages at 0.
> Rename the existing internal helper to __kho_restore_page().
> 
> Signed-off-by: Pranjal Shrivastava <[email protected]>
>
> diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
> index 8968c56d2d73e..6e2c75b16209c 100644
> --- a/include/linux/kexec_handover.h
> +++ b/include/linux/kexec_handover.h
> @@ -22,6 +22,8 @@ bool is_kho_boot(void);
>  
>  int kho_preserve_folio(struct folio *folio);
>  void kho_unpreserve_folio(struct folio *folio);
> +int kho_preserve_page(struct page *page, unsigned int order);
> +void kho_unpreserve_page(struct page *page, unsigned int order);
>  int kho_preserve_pages(struct page *page, unsigned long nr_pages);
>  void kho_unpreserve_pages(struct page *page, unsigned long nr_pages);
>  int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation);
> @@ -30,6 +32,7 @@ void *kho_alloc_preserve(size_t size);
>  void kho_unpreserve_free(void *mem);
>  void kho_restore_free(void *mem);
>  struct folio *kho_restore_folio(phys_addr_t phys);
> +struct page *kho_restore_page(phys_addr_t phys);
>  struct page *kho_restore_pages(phys_addr_t phys, unsigned long nr_pages);
>  void *kho_restore_vmalloc(const struct kho_vmalloc *preservation);
>  int kho_add_subtree(const char *name, void *blob, size_t size);
> @@ -65,6 +68,13 @@ static inline int kho_preserve_pages(struct page *page, unsigned int nr_pages)
>  
>  static inline void kho_unpreserve_pages(struct page *page, unsigned int nr_pages) { }
>  
> +static inline int kho_preserve_page(struct page *page, unsigned int order)
> +{
> +	return -EOPNOTSUPP;
> +}
> +
> +static inline void kho_unpreserve_page(struct page *page, unsigned int order) { }
> +
>  static inline int kho_preserve_vmalloc(void *ptr,
>  				       struct kho_vmalloc *preservation)
>  {
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index 07f6f453cd69d..43ae3e2de5da2 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -393,7 +393,7 @@ static void kho_init_folio(struct page *page, unsigned int order)
>  		prep_compound_page(page, order);
>  }
>  
> -static struct page *kho_restore_page(phys_addr_t phys, bool is_folio)
> +static struct page *__kho_restore_page(phys_addr_t phys, bool is_folio)
>  {
>  	struct page *page = pfn_to_online_page(PHYS_PFN(phys));
>  	unsigned long nr_pages;
> @@ -432,12 +432,44 @@ static struct page *kho_restore_page(phys_addr_t phys, bool is_folio)
>   */
>  struct folio *kho_restore_folio(phys_addr_t phys)
>  {
> -	struct page *page = kho_restore_page(phys, true);
> +	struct page *page = __kho_restore_page(phys, true);
>  
>  	return page ? page_folio(page) : NULL;
>  }
>  EXPORT_SYMBOL_GPL(kho_restore_folio);
>  
> +/**
> + * kho_restore_page - restore a higher-order unsplit page block.
> + * @phys: physical address of the first page.
> + *
> + * Restore a higher-order unsplit page block that was preserved with
> + * kho_preserve_page().
> + *
> + * Return: the head page on success, NULL on failure.
> + */
> +struct page *kho_restore_page(phys_addr_t phys)
> +{
> +	struct page *page = pfn_to_online_page(PHYS_PFN(phys));
> +	unsigned long nr_pages;
> +	union kho_page_info info;
> +
> +	if (!page)
> +		return NULL;
> +
> +	info.page_private = page->private;
> +	if (WARN_ON_ONCE(info.magic != KHO_PAGE_MAGIC))
> +		return NULL;
> +
> +	nr_pages = (1UL << info.order);
> +	page->private = 0;
> +
> +	kho_init_unsplit_pages(page, info.order);
> +	adjust_managed_page_count(page, nr_pages);
> +
> +	return page;

Hmm, it copies a lot from __kho_init_page().
We should keep the common part of order-0 page/folio/high-order page
initialization in a single place.

-- 
Sincerely yours,
Mike.