A driver may need to split a high-order allocation that has already been preserved. If the pages are split using split_page() manually, the refcounts would change but KHO won't record the change in the preserved page-type, resulting in a metadata mismatch during restoration in the new kernel. Introduce kho_split_preserved_pages() to handle splitting of preserved pages. The helper follows an unpreserve -> split -> re-preserve sequence, while ensuring that the KHO radix tree is updated with the correct KHO_PAGE_SPLIT type bits. The helper returns 0 on success, or a negative error code if the re-preservation fails. Callers must ensure the provided order matches the original allocation and that the operation is serialized against other preservation API calls. Signed-off-by: Pranjal Shrivastava --- include/linux/kexec_handover.h | 7 +++++++ kernel/liveupdate/kexec_handover.c | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h index 8968c56d2d73..452e38bb2076 100644 --- a/include/linux/kexec_handover.h +++ b/include/linux/kexec_handover.h @@ -24,6 +24,7 @@ int kho_preserve_folio(struct folio *folio); void kho_unpreserve_folio(struct folio *folio); int kho_preserve_pages(struct page *page, unsigned long nr_pages); void kho_unpreserve_pages(struct page *page, unsigned long nr_pages); +int kho_split_preserved_pages(struct page *page, unsigned int order); int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation); void kho_unpreserve_vmalloc(struct kho_vmalloc *preservation); void *kho_alloc_preserve(size_t size); @@ -65,6 +66,12 @@ 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_split_preserved_pages(struct page *page, + unsigned int order) +{ + return -EOPNOTSUPP; +} + 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 f6ca5e24c740..ea08248901b5 100644 --- a/kernel/liveupdate/kexec_handover.c +++ b/kernel/liveupdate/kexec_handover.c @@ -1018,6 +1018,29 @@ void kho_unpreserve_pages(struct page *page, unsigned long nr_pages) } EXPORT_SYMBOL_GPL(kho_unpreserve_pages); +/** + * kho_split_preserved_pages - split contiguous pages that are preserved + * @page: first page in the list. + * @order: the order of the original allocation. + * + * This function allows to split a high-order allocation that has been + * preserved across kexec. It unpreserves the pages, splits them using + * split_page() and then re-preserves them as individual pages. + * + * This function MUST only be called on pages that are currently preserved. + * The @order provided MUST match the order used during the initial + * preservation. + * + * Return: 0 on success, or a negative error code on failure. + */ +int kho_split_preserved_pages(struct page *page, unsigned int order) +{ + kho_unpreserve_pages(page, 1UL << order); + split_page(page, order); + return kho_preserve_pages(page, 1UL << order); +} +EXPORT_SYMBOL_GPL(kho_split_preserved_pages); + /* vmalloc flags KHO supports */ #define KHO_VMALLOC_SUPPORTED_FLAGS (VM_ALLOC | VM_ALLOW_HUGE_VMAP) -- 2.55.0.rc0.799.gd6f94ed593-goog