Add arch/x86/kvm/mmu/kho.c to preserve and adopt TDP MMU EPT/NPT root page tables across Kexec Handover (KHO) live updates.
Signed-off-by: Pasha Tatashin <[email protected]> --- arch/x86/kvm/mmu.h | 7 ++ arch/x86/kvm/mmu/kho.c | 193 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 arch/x86/kvm/mmu/kho.c diff --git a/arch/x86/kvm/mmu.h b/arch/x86/kvm/mmu.h index 2ae7f9ed4cf8..f35948f0906c 100644 --- a/arch/x86/kvm/mmu.h +++ b/arch/x86/kvm/mmu.h @@ -410,4 +410,11 @@ static inline bool kvm_is_gfn_alias(struct kvm *kvm, gfn_t gfn) { return gfn & kvm_gfn_direct_bits(kvm); } + +/* + * Declared here rather than in asm/kvm_host.h: it is internal to + * arch/x86/kvm and has no callers outside it. Defined in mmu/kho.c. + */ +int kvm_mmu_preserve_kho(struct kvm *kvm); + #endif diff --git a/arch/x86/kvm/mmu/kho.c b/arch/x86/kvm/mmu/kho.c new file mode 100644 index 000000000000..a04600f0c3e0 --- /dev/null +++ b/arch/x86/kvm/mmu/kho.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026, Google LLC. + * Pasha Tatashin <[email protected]> + * + * KHO preservation of the x86 KVM MMU page tables. + * + * An orphaned vCPU keeps running its guest out of the shadow/TDP page tables + * while the VM is detached, so every page those tables are built from has to + * survive the kexec. + */ + +#include <linux/kexec_handover.h> +#include <linux/kvm_host.h> + +#include "mmu.h" +#include "mmu_internal.h" +#include "spte.h" +#include "tdp_iter.h" +#include "tdp_mmu.h" + +/* + * Page-pointer accumulator. + * + * kho_preserve_pages() cannot be called while holding kvm->mmu_lock: it is a + * rwlock_t, so the section is atomic, whereas kho_radix_add_key() below it + * calls might_sleep(), takes a mutex and allocates with GFP_KERNEL. So the + * walk runs in two phases -- collect the pages under the lock, preserve them + * after dropping it. + * + * A NULL @pages simply counts, which is how the caller sizes the array. + */ +struct kvm_mmu_kho_pages { + struct page **pages; + unsigned long nr; + unsigned long capacity; + bool overflow; +}; + +static void kvm_mmu_kho_add(struct kvm_mmu_kho_pages *acc, struct page *page) +{ + if (!acc->pages) { + acc->nr++; + return; + } + + if (acc->nr >= acc->capacity) { + acc->overflow = true; + return; + } + + acc->pages[acc->nr++] = page; +} + +static void kvm_tdp_mmu_collect(struct kvm *kvm, + struct kvm_mmu_kho_pages *acc) +{ + gfn_t end = kvm_mmu_max_gfn() + 1; + struct kvm_mmu_page *root; + struct tdp_iter iter; + + lockdep_assert_held_write(&kvm->mmu_lock); + + rcu_read_lock(); + list_for_each_entry_rcu(root, &kvm->arch.tdp_mmu_roots, link) { + if (root->spt) + kvm_mmu_kho_add(acc, virt_to_page(root->spt)); + + for_each_tdp_pte(iter, kvm, root, 0, end) { + struct page *page; + + if (!is_shadow_present_pte(iter.old_spte) || + is_last_spte(iter.old_spte, iter.level)) + continue; + + page = pfn_to_page(spte_to_pfn(iter.old_spte)); + kvm_mmu_kho_add(acc, page); + } + } + rcu_read_unlock(); +} + +/* + * The per-vCPU root page tables are not linked into active_mmu_pages, so they + * have to be walked separately. pae_root, pml4_root and pml5_root are each + * NULL unless the corresponding paging mode is in use. + */ +static void kvm_mmu_collect_roots(struct kvm_mmu *mmu, + struct kvm_mmu_kho_pages *acc) +{ + void *const roots[] = { mmu->pae_root, mmu->pml4_root, mmu->pml5_root }; + int i; + + for (i = 0; i < ARRAY_SIZE(roots); i++) { + if (roots[i]) + kvm_mmu_kho_add(acc, virt_to_page(roots[i])); + } +} + +/* Collect every page backing this VM's MMU. Must be called under mmu_lock. */ +static void kvm_mmu_collect_all(struct kvm *kvm, + struct kvm_mmu_kho_pages *acc) +{ + struct kvm_mmu_page *sp; + struct kvm_vcpu *vcpu; + unsigned long i; + + lockdep_assert_held_write(&kvm->mmu_lock); + + acc->nr = 0; + acc->overflow = false; + + if (tdp_mmu_enabled) + kvm_tdp_mmu_collect(kvm, acc); + + list_for_each_entry(sp, &kvm->arch.active_mmu_pages, link) { + if (sp->spt) + kvm_mmu_kho_add(acc, virt_to_page(sp->spt)); + } + + kvm_for_each_vcpu(i, vcpu, kvm) { + if (vcpu->arch.mmu) + kvm_mmu_collect_roots(vcpu->arch.mmu, acc); + kvm_mmu_collect_roots(&vcpu->arch.guest_mmu, acc); + } +} + +int kvm_mmu_preserve_kho(struct kvm *kvm) +{ + struct kvm_mmu_kho_pages acc = {}; + struct kvm_kho_folios_ser *kp; + unsigned long i; + int ret = 0; + int attempt; + + /* + * Size the array, then fill it. The guest can fault in new page + * tables between the two passes, so re-check for overflow and retry + * with a larger array; the slack makes repeated growth unlikely. + */ + for (attempt = 0; attempt < 5; attempt++) { + write_lock(&kvm->mmu_lock); + kvm_mmu_collect_all(kvm, &acc); + write_unlock(&kvm->mmu_lock); + + if (acc.pages && !acc.overflow) + break; + + acc.capacity = acc.nr + (acc.nr >> 2) + 16; + kvfree(acc.pages); + acc.pages = kvmalloc_array(acc.capacity, sizeof(*acc.pages), + GFP_KERNEL); + if (!acc.pages) + return -ENOMEM; + } + + if (acc.overflow) { + ret = -EAGAIN; + goto out; + } + + if (!acc.nr) + goto out; + + kp = kvm_kho_folios_alloc(acc.nr); + if (IS_ERR(kp)) { + ret = PTR_ERR(kp); + goto out; + } + + for (i = 0; i < acc.nr; i++) { + ret = kho_preserve_folio(page_folio(acc.pages[i])); + if (ret) { + /* + * Undo the partial preservation: leaving pages marked + * would pin them in the incoming kernel forever with + * nothing owning them. + */ + while (i--) + kho_unpreserve_folio(page_folio(acc.pages[i])); + kho_unpreserve_free(kp); + goto out; + } + kp->folios_pa[i] = page_to_phys(acc.pages[i]); + } + + kp->nr_folios = acc.nr; + kvm->kho_folios = kp; + +out: + kvfree(acc.pages); + return ret; +} -- 2.55.0.1082.g2b9226bbc0-goog

