From: Fred Griffoul <[email protected]>

Replace kvm_host_map usage with gfn_to_pfn_cache for the L1 pages whose
physical addresses are handed to the CPU in vmcs02 for direct use while
running L2: the APIC-access page, the virtual-APIC page, and the posted
interrupt descriptor. This eliminates the map/unmap (with unmanaged
memory: memremap/memunmap) cycle on every L2 VM-entry/exit.

The caches are initialized with kvm_gpc_init_for_vcpu(), naming
KVM_REQ_GET_NESTED_STATE_PAGES as the service request, and their pfns
are pinned for guest use (GPC_GUEST_USING) when written into vmcs02 by
nested_get_vmcs12_pages(). If an MMU notifier invalidation hits a
pinned cache, the pfncache core posts that request, forces the vCPU
out of guest mode, and waits for it to leave; the request handler
re-runs nested_get_vmcs12_pages(), whose refresh cannot complete until
the invalidation ends, and rewrites the vmcs02 fields before the next
entry. The pins are dropped at nested VM-exit (including the
vmentry_fail_vmexit path, which does not pass through
__nested_vmx_vmexit()) and the caches deactivated when VMX operation
ends.

Note that no validity check is added to the guest-entry path: the
request substitutes for it. The invariant is that a stale physical
address latched in vmcs02 always implies a pending request, which the
existing kvm_request_pending() check catches after vcpu->mode is set
to IN_GUEST_MODE. It is maintained from both directions:

 - Every path which consumes a pin (the notifier walk, or a
   refresh/deactivate from any context, including the owning vCPU
   refreshing its own cache from a host-side reader) posts the
   service request.

 - The request handler cannot lose it: vcpu_enter_guest() consumes
   the request bit before calling in, so ANY failure of
   vmx_get_nested_state_pages() — including early exits which touch
   no cache at all, such as a failed load_pdptrs() — re-posts the
   request before exiting to userspace. A persistently failing
   configuration bounces to userspace on every KVM_RUN rather than
   ever entering L2 with a stale address. (The old kvm_host_map code
   could tolerate losing this race because the map held a page
   reference; the pfncache deliberately holds none.)

This is what closes the historical KVM_GUEST_USES_PFN gaps¹: the kick
is guaranteed to be acted upon before re-entry, without polling cache
validity on every entry.

Host-side (khva) readers of the virtual-APIC and PI descriptor pages
do not need the pin, which exists only for the benefit of the
physical addresses latched in vmcs02.
vmx_complete_nested_posted_interrupt() runs in a sleepable context
and uses the SRCU-protected check/refresh protocol.
vmx_has_nested_events() is reachable from the kvm_vcpu_block() loop
under set_current_state(TASK_INTERRUPTIBLE), where a refresh (mutex,
GUP, synchronize_srcu) must not sleep: it instead uses a non-sleeping
try-lock and reports "event pending" when a cache has been
invalidated. The resulting bounce through vcpu_run services the
pending request in a sleepable context and re-evaluates events for
real; a spurious wakeup is safe, whereas returning false could miss
a wakeup (the service request is posted with KVM_REQUEST_NO_WAKEUP).
To keep an unbackable page from turning that conservative answer into
a wakeup livelock, a failed activation deactivates the cache rather
than leaving it active-but-invalid.

Dirty marking for these pages, which the CPU writes via the vmcs02
physical addresses and thus bypasses EPT-based dirty tracking, moves
from the unmap path to gfn-based marking from the vmcs12 fields on
every L2 exit (__vmx_handle_exit() already does this on every exit
from L2, so unmap-time marking was redundant); the one software
writer, __kvm_apic_update_irr() from
vmx_complete_nested_posted_interrupt(), marks at the write site.

¹ https://lore.kernel.org/all/[email protected]

Signed-off-by: Fred Griffoul <[email protected]>
Co-developed-by: David Woodhouse <[email protected]>
Failure to resolve a page distinguishes three cases. At entry time
(VMLAUNCH/VMRESUME, or resume from KVM_SET_NESTED_STATE, tracked in
nested.resume_pending) an unbackable page is a misconfiguration:
report KVM_EXIT_INTERNAL_ERROR as ever (vmx_apic_access_test asserts
this). On lazy revalidation — a pending KVM_REQ_GET_NESTED_STATE_PAGES
consumed while L2 runs — a slot mid-update (-EAGAIN from the
pfncache) re-posts the request and continues; the pending request
prevents VM entry, so the vCPU retries until the memslot update
completes. And a genuinely unbacked gfn on lazy revalidation means
userspace yanked the backing from under a running L2: clear
SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES in vmcs02 instead of killing
the VM, so L2's accesses take ordinary EPT violations and are
handled as MMIO, exactly as a non-nested guest touching the same
unbacked gpa would be — and as upstream KVM (which never re-resolves)
effectively behaves. The control is recomputed from vmcs12 on the
next nested entry, and a later memslot update which re-backs the gfn
re-latches the page, so the degradation heals itself
(vmx_apic_update_test's move-memslot phase exercises this).

Signed-off-by: David Woodhouse <[email protected]>
Assisted-by: Claude:claude-mythos-5
---
 arch/x86/kvm/vmx/nested.c | 345 +++++++++++++++++++++++++++++++++-----
 arch/x86/kvm/vmx/vmx.c    |  11 +-
 arch/x86/kvm/vmx/vmx.h    |  14 +-
 3 files changed, 319 insertions(+), 51 deletions(-)

diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 5fe7e5d1f72d..105ff6cbfe58 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -352,14 +352,107 @@ static void nested_gpc_unlock(struct gfn_to_pfn_cache 
*gpc, int idx)
        srcu_read_unlock_atomic(&gpc->kvm->gpc_srcu, idx);
 }
 
+/*
+ * Map a page of L1 memory, pin it for direct use by the guest (i.e. by
+ * the CPU while running the L2 guest, via a physical address in vmcs02),
+ * and return the host physical address to write into vmcs02. The pin
+ * guarantees that if the mapping is invalidated, this vCPU is forced out
+ * of guest mode and KVM_REQ_GET_NESTED_STATE_PAGES is posted so that the
+ * address is re-established (or the vCPU exits to userspace) before the
+ * next entry to guest mode.
+ */
+static int nested_gpc_hpa(struct gfn_to_pfn_cache *gpc, gpa_t gpa, hpa_t *hpa)
+{
+       int idx;
+
+       do {
+               idx = nested_gpc_lock(gpc, gpa);
+               if (idx < 0)
+                       return idx;
+
+               /*
+                * The pin can fail only if an invalidation cleared the
+                * valid bit after nested_gpc_lock() checked it; go back
+                * around to refresh (which will not complete until the
+                * invalidation is over) and try again.
+                */
+               if (kvm_gpc_pin_for_guest(gpc))
+                       break;
+
+               nested_gpc_unlock(gpc, idx);
+       } while (1);
+
+       *hpa = pfn_to_hpa(gpc->pfn);
+       nested_gpc_unlock(gpc, idx);
+       return 0;
+}
+
+/*
+ * Try to lock an already-configured cache for host-side (khva) access,
+ * without sleeping. Returns -ENOENT if the cache is not active (e.g.
+ * posted interrupts not configured for this L2), -EWOULDBLOCK if it is
+ * active but currently invalidated (only a sleeping refresh would make
+ * it usable). Does not pin; the mapping is stable only until
+ * nested_gpc_unlock().
+ */
+static int nested_gpc_try_lock_if_active(struct gfn_to_pfn_cache *gpc)
+{
+       int idx;
+
+       /*
+        * Check for an inactive (or never-initialized: gpc->kvm is NULL
+        * before VMXON) cache before dereferencing gpc->kvm. Deactivation
+        * only happens on this vCPU (under vcpu->mutex), so the check
+        * cannot race with the lock which follows it.
+        */
+       if (!gpc->active)
+               return -ENOENT;
+
+       idx = srcu_read_lock_atomic(&gpc->kvm->gpc_srcu);
+       if (!gpc->active) {
+               srcu_read_unlock_atomic(&gpc->kvm->gpc_srcu, idx);
+               return -ENOENT;
+       }
+
+       if (!kvm_gpc_check(gpc, PAGE_SIZE)) {
+               srcu_read_unlock_atomic(&gpc->kvm->gpc_srcu, idx);
+               return -EWOULDBLOCK;
+       }
+
+       return idx;
+}
+
+/*
+ * As above, but refresh the cache if it has been invalidated. May sleep;
+ * callers must be in a sleepable context (in particular, NOT under
+ * set_current_state() in a block loop, and not in a VM-exit fastpath).
+ */
+static int nested_gpc_lock_if_active(struct gfn_to_pfn_cache *gpc)
+{
+       int idx, err;
+
+       while ((idx = nested_gpc_try_lock_if_active(gpc)) == -EWOULDBLOCK) {
+               err = kvm_gpc_refresh(gpc, PAGE_SIZE);
+               if (err)
+                       return err;
+       }
+
+       return idx;
+}
+
+static struct pi_desc *nested_pi_desc(struct vcpu_vmx *vmx)
+{
+       return (struct pi_desc *)((u8 *)vmx->nested.pi_desc_cache.khva +
+                                 vmx->nested.pi_desc_offset);
+}
+
 static void nested_put_vmcs12_pages(struct kvm_vcpu *vcpu)
 {
        struct vcpu_vmx *vmx = to_vmx(vcpu);
 
-       kvm_vcpu_unmap(vcpu, &vmx->nested.apic_access_page_map);
-       kvm_vcpu_unmap(vcpu, &vmx->nested.virtual_apic_map);
-       kvm_vcpu_unmap(vcpu, &vmx->nested.pi_desc_map);
-       vmx->nested.pi_desc = NULL;
+       kvm_gpc_unpin_for_guest(&vmx->nested.apic_access_page_cache);
+       kvm_gpc_unpin_for_guest(&vmx->nested.virtual_apic_cache);
+       kvm_gpc_unpin_for_guest(&vmx->nested.pi_desc_cache);
 }
 
 /*
@@ -378,6 +471,7 @@ static void free_nested(struct kvm_vcpu *vcpu)
                return;
 
        kvm_clear_request(KVM_REQ_GET_NESTED_STATE_PAGES, vcpu);
+       vmx->nested.resume_pending = false;
 
        vmx->nested.vmxon = false;
        vmx->nested.smm.vmxon = false;
@@ -402,10 +496,13 @@ static void free_nested(struct kvm_vcpu *vcpu)
        kfree(vmx->nested.cached_shadow_vmcs12);
        vmx->nested.cached_shadow_vmcs12 = NULL;
 
-       kvm_gpc_deactivate(&vmx->nested.msr_bitmap_cache);
-
        nested_put_vmcs12_pages(vcpu);
 
+       kvm_gpc_deactivate(&vmx->nested.pi_desc_cache);
+       kvm_gpc_deactivate(&vmx->nested.virtual_apic_cache);
+       kvm_gpc_deactivate(&vmx->nested.apic_access_page_cache);
+       kvm_gpc_deactivate(&vmx->nested.msr_bitmap_cache);
+
        kvm_mmu_free_roots(vcpu->kvm, &vcpu->arch.guest_mmu, KVM_MMU_ROOTS_ALL);
 
        nested_release_evmcs(vcpu);
@@ -3475,11 +3572,11 @@ static bool nested_get_evmcs_page(struct kvm_vcpu *vcpu)
 }
 #endif
 
-static bool nested_get_vmcs12_pages(struct kvm_vcpu *vcpu)
+static bool nested_get_vmcs12_pages(struct kvm_vcpu *vcpu, bool lazy)
 {
        struct vmcs12 *vmcs12 = get_vmcs12(vcpu);
        struct vcpu_vmx *vmx = to_vmx(vcpu);
-       struct kvm_host_map *map;
+       hpa_t hpa;
 
        if (!vcpu->arch.pdptrs_from_userspace &&
            !nested_cpu_has_ept(vmcs12) && is_pae_paging(vcpu)) {
@@ -3494,26 +3591,74 @@ static bool nested_get_vmcs12_pages(struct kvm_vcpu 
*vcpu)
 
 
        if (nested_cpu_has2(vmcs12, SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES)) {
-               map = &vmx->nested.apic_access_page_map;
-
-               if (!kvm_vcpu_map(vcpu, gpa_to_gfn(vmcs12->apic_access_addr), 
map)) {
-                       vmcs_write64(APIC_ACCESS_ADDR, pfn_to_hpa(map->pfn));
-               } else {
+               int err = nested_gpc_hpa(&vmx->nested.apic_access_page_cache,
+                                        vmcs12->apic_access_addr, &hpa);
+               if (!err) {
+                       vmcs_write64(APIC_ACCESS_ADDR, hpa);
+               } else if (err == -EAGAIN) {
+                       
kvm_gpc_unpin_for_guest(&vmx->nested.apic_access_page_cache);
+                       kvm_gpc_deactivate(&vmx->nested.apic_access_page_cache);
+                       goto retry;
+               } else if (!lazy) {
+                       /*
+                        * Entry-time resolution (VMLAUNCH/VMRESUME, or resume
+                        * from KVM_SET_NESTED_STATE): an unbackable
+                        * APIC-access page is a misconfiguration to report,
+                        * not a transient to ride out.
+                        */
                        pr_debug_ratelimited("%s: no backing for APIC-access 
address in vmcs12\n",
                                             __func__);
                        vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR;
                        vcpu->run->internal.suberror =
                                KVM_INTERNAL_ERROR_EMULATION;
                        vcpu->run->internal.ndata = 0;
+                       
kvm_gpc_unpin_for_guest(&vmx->nested.apic_access_page_cache);
+                       kvm_gpc_deactivate(&vmx->nested.apic_access_page_cache);
                        return false;
+               } else {
+                       /*
+                        * Lazy revalidation (a memslot update or invalidation
+                        * while L2 runs) found the gfn unbacked: userspace
+                        * yanked the backing from under a running L2. Turn
+                        * off the APIC-access match for vmcs02 instead of
+                        * killing the VM; L2's accesses to the page then take
+                        * ordinary EPT violations on an unbacked gfn and are
+                        * handled (or reported) as MMIO, exactly as a
+                        * non-nested guest touching the same gpa would be.
+                        * prepare_vmcs02_early() recomputes the control from
+                        * vmcs12 on the next nested entry, and a later
+                        * memslot update which re-backs the gfn re-posts the
+                        * request and re-latches the page, so the
+                        * degradation heals itself.
+                        */
+                       
kvm_gpc_unpin_for_guest(&vmx->nested.apic_access_page_cache);
+                       kvm_gpc_deactivate(&vmx->nested.apic_access_page_cache);
+                       secondary_exec_controls_clearbit(vmx,
+                               SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES);
                }
        }
 
        if (nested_cpu_has(vmcs12, CPU_BASED_TPR_SHADOW)) {
-               map = &vmx->nested.virtual_apic_map;
+               int vapic_err = nested_gpc_hpa(&vmx->nested.virtual_apic_cache,
+                                              vmcs12->virtual_apic_page_addr, 
&hpa);
 
-               if (!kvm_vcpu_map(vcpu, 
gpa_to_gfn(vmcs12->virtual_apic_page_addr), map)) {
-                       vmcs_write64(VIRTUAL_APIC_PAGE_ADDR, 
pfn_to_hpa(map->pfn));
+               /*
+                * On failure, deactivate the cache: an active-but-invalid
+                * cache would read as "might have an event" to the
+                * non-sleeping check in vmx_has_nested_events(), causing
+                * spurious wakeups for as long as the page remains
+                * unbackable.
+                */
+               if (vapic_err) {
+                       
kvm_gpc_unpin_for_guest(&vmx->nested.virtual_apic_cache);
+                       kvm_gpc_deactivate(&vmx->nested.virtual_apic_cache);
+               }
+
+               if (vapic_err == -EAGAIN)
+                       goto retry;
+
+               if (!vapic_err) {
+                       vmcs_write64(VIRTUAL_APIC_PAGE_ADDR, hpa);
                } else if (nested_cpu_has(vmcs12, CPU_BASED_CR8_LOAD_EXITING) &&
                           nested_cpu_has(vmcs12, CPU_BASED_CR8_STORE_EXITING) 
&&
                           !nested_cpu_has2(vmcs12, 
SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES)) {
@@ -3536,14 +3681,13 @@ static bool nested_get_vmcs12_pages(struct kvm_vcpu 
*vcpu)
        }
 
        if (nested_cpu_has_posted_intr(vmcs12)) {
-               map = &vmx->nested.pi_desc_map;
-
-               if (!kvm_vcpu_map(vcpu, 
gpa_to_gfn(vmcs12->posted_intr_desc_addr), map)) {
-                       vmx->nested.pi_desc =
-                               (struct pi_desc *)(((void *)map->hva) +
-                               offset_in_page(vmcs12->posted_intr_desc_addr));
+               int pi_err = nested_gpc_hpa(&vmx->nested.pi_desc_cache,
+                                           vmcs12->posted_intr_desc_addr & 
PAGE_MASK, &hpa);
+               if (!pi_err) {
+                       vmx->nested.pi_desc_offset =
+                               offset_in_page(vmcs12->posted_intr_desc_addr);
                        vmcs_write64(POSTED_INTR_DESC_ADDR,
-                                    pfn_to_hpa(map->pfn) + 
offset_in_page(vmcs12->posted_intr_desc_addr));
+                                    hpa + 
offset_in_page(vmcs12->posted_intr_desc_addr));
                } else {
                        /*
                         * Defer the KVM_INTERNAL_EXIT until KVM tries to
@@ -3551,7 +3695,10 @@ static bool nested_get_vmcs12_pages(struct kvm_vcpu 
*vcpu)
                         * descriptor. (Note that KVM may do this when it
                         * should not, per the architectural specification.)
                         */
-                       vmx->nested.pi_desc = NULL;
+                       kvm_gpc_unpin_for_guest(&vmx->nested.pi_desc_cache);
+                       kvm_gpc_deactivate(&vmx->nested.pi_desc_cache);
+                       if (pi_err == -EAGAIN)
+                               goto retry;
                        pin_controls_clearbit(vmx, PIN_BASED_POSTED_INTR);
                }
        }
@@ -3561,9 +3708,27 @@ static bool nested_get_vmcs12_pages(struct kvm_vcpu 
*vcpu)
                exec_controls_clearbit(vmx, CPU_BASED_USE_MSR_BITMAPS);
 
        return true;
+
+retry:
+       /*
+        * The gfn's memslot is being moved or deleted: present but
+        * flagged invalid, so the lookup failure is transient and the
+        * gfn must not (yet) be treated as unbacked. Re-post the
+        * request and report success: the pending request prevents
+        * any VM entry (vcpu_enter_guest() bails and reprocesses
+        * requests), so L2 cannot run with the partially-updated
+        * vmcs02, and the vCPU retries here until the memslot update
+        * completes or reverts. This mirrors RET_PF_RETRY for
+        * ordinary guest faults on an invalid slot; as there, a
+        * userspace which never completes the update leaves the vCPU
+        * retrying indefinitely, with signals and request processing
+        * still serviced.
+        */
+       kvm_make_request(KVM_REQ_GET_NESTED_STATE_PAGES, vcpu);
+       return true;
 }
 
-static bool vmx_get_nested_state_pages(struct kvm_vcpu *vcpu)
+static bool __vmx_get_nested_state_pages(struct kvm_vcpu *vcpu)
 {
 #ifdef CONFIG_KVM_HYPERV
        /*
@@ -3584,9 +3749,39 @@ static bool vmx_get_nested_state_pages(struct kvm_vcpu 
*vcpu)
        }
 #endif
 
-       if (is_guest_mode(vcpu) && !nested_get_vmcs12_pages(vcpu))
+       if (is_guest_mode(vcpu) &&
+           !nested_get_vmcs12_pages(vcpu, 
!to_vmx(vcpu)->nested.resume_pending))
                return false;
 
+       /*
+        * Success consumes the resume classification (a -EAGAIN retry
+        * "succeeds" too: the resume did all it could against a memslot
+        * mid-update, and the re-posted request's consumption is a fresh
+        * revalidation like any other).
+        */
+       to_vmx(vcpu)->nested.resume_pending = false;
+       return true;
+}
+
+static bool vmx_get_nested_state_pages(struct kvm_vcpu *vcpu)
+{
+       if (!__vmx_get_nested_state_pages(vcpu)) {
+               /*
+                * The caller (vcpu_enter_guest) consumed
+                * KVM_REQ_GET_NESTED_STATE_PAGES before calling in, and a
+                * failure here may leave a previously latched — and, after
+                * an invalidation, stale and unreferenced — physical
+                * address in vmcs02. Re-post the request so that no path
+                * can re-enter L2 without retrying this function: the
+                * invariant is that a stale latched address always implies
+                * a pending request. If the failure persists, the vCPU
+                * bounces to userspace on every KVM_RUN rather than ever
+                * entering L2, which is the invariant doing its job.
+                */
+               kvm_make_request(KVM_REQ_GET_NESTED_STATE_PAGES, vcpu);
+               return false;
+       }
+
        return true;
 }
 
@@ -3715,7 +3910,7 @@ enum nvmx_vmentry_status 
nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu,
        prepare_vmcs02_early(vmx, &vmx->vmcs01, vmcs12);
 
        if (from_vmentry) {
-               if (unlikely(!nested_get_vmcs12_pages(vcpu))) {
+               if (unlikely(!nested_get_vmcs12_pages(vcpu, false))) {
                        vmx_switch_vmcs(vcpu, &vmx->vmcs01);
                        return NVMX_VMENTRY_KVM_INTERNAL_ERROR;
                }
@@ -3753,6 +3948,7 @@ enum nvmx_vmentry_status 
nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu,
                 * to nested_get_vmcs12_pages before the next VM-entry.  The 
MSRs
                 * have already been set at vmentry time and should not be 
reset.
                 */
+               vmx->nested.resume_pending = true;
                kvm_make_request(KVM_REQ_GET_NESTED_STATE_PAGES, vcpu);
        }
 
@@ -3809,6 +4005,14 @@ enum nvmx_vmentry_status 
nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu,
 vmentry_fail_vmexit:
        vmx_switch_vmcs(vcpu, &vmx->vmcs01);
 
+       /*
+        * Release any guest-mode pins taken by nested_get_vmcs12_pages()
+        * during this failed entry: this path returns to L1 without going
+        * through __nested_vmx_vmexit(), which is where they are normally
+        * dropped. (Idempotent if a later vmexit drops them again.)
+        */
+       nested_put_vmcs12_pages(vcpu);
+
        if (!from_vmentry)
                return NVMX_VMENTRY_VMEXIT;
 
@@ -4065,6 +4269,8 @@ static void vmcs12_save_pending_event(struct kvm_vcpu 
*vcpu,
 static int vmx_complete_nested_posted_interrupt(struct kvm_vcpu *vcpu)
 {
        struct vcpu_vmx *vmx = to_vmx(vcpu);
+       int pi_idx, vapic_idx;
+       struct pi_desc *pi_desc;
        int max_irr;
        void *vapic_page;
        u16 status;
@@ -4072,22 +4278,32 @@ static int vmx_complete_nested_posted_interrupt(struct 
kvm_vcpu *vcpu)
        if (!vmx->nested.pi_pending)
                return 0;
 
-       if (!vmx->nested.pi_desc)
+       pi_idx = nested_gpc_lock_if_active(&vmx->nested.pi_desc_cache);
+       if (pi_idx < 0)
                goto mmio_needed;
 
+       pi_desc = nested_pi_desc(vmx);
        vmx->nested.pi_pending = false;
 
-       if (!pi_test_and_clear_on(vmx->nested.pi_desc))
+       if (!pi_test_and_clear_on(pi_desc)) {
+               nested_gpc_unlock(&vmx->nested.pi_desc_cache, pi_idx);
                return 0;
+       }
 
-       max_irr = pi_find_highest_vector(vmx->nested.pi_desc);
+       max_irr = pi_find_highest_vector(pi_desc);
        if (max_irr > 0) {
-               vapic_page = vmx->nested.virtual_apic_map.hva;
-               if (!vapic_page)
+               vapic_idx = 
nested_gpc_lock_if_active(&vmx->nested.virtual_apic_cache);
+               if (vapic_idx < 0) {
+                       nested_gpc_unlock(&vmx->nested.pi_desc_cache, pi_idx);
                        goto mmio_needed;
+               }
+               vapic_page = vmx->nested.virtual_apic_cache.khva;
+
+               __kvm_apic_update_irr(pi_desc->pir, vapic_page, &max_irr);
+
+               kvm_gpc_mark_dirty_in_slot(&vmx->nested.virtual_apic_cache);
+               nested_gpc_unlock(&vmx->nested.virtual_apic_cache, vapic_idx);
 
-               __kvm_apic_update_irr(vmx->nested.pi_desc->pir,
-                       vapic_page, &max_irr);
                status = vmcs_read16(GUEST_INTR_STATUS);
                if ((u8)max_irr > ((u8)status & 0xff)) {
                        status &= ~0xff;
@@ -4096,8 +4312,8 @@ static int vmx_complete_nested_posted_interrupt(struct 
kvm_vcpu *vcpu)
                }
        }
 
-       kvm_vcpu_map_mark_dirty(vcpu, &vmx->nested.virtual_apic_map);
-       kvm_vcpu_map_mark_dirty(vcpu, &vmx->nested.pi_desc_map);
+       kvm_gpc_mark_dirty_in_slot(&vmx->nested.pi_desc_cache);
+       nested_gpc_unlock(&vmx->nested.pi_desc_cache, pi_idx);
        return 0;
 
 mmio_needed:
@@ -4216,8 +4432,9 @@ static bool nested_vmx_preemption_timer_pending(struct 
kvm_vcpu *vcpu)
 static bool vmx_has_nested_events(struct kvm_vcpu *vcpu, bool for_injection)
 {
        struct vcpu_vmx *vmx = to_vmx(vcpu);
-       void *vapic = vmx->nested.virtual_apic_map.hva;
-       int max_irr, vppr;
+       int max_irr, vppr, idx;
+       struct pi_desc *pi_desc;
+       void *vapic;
 
        if (nested_vmx_preemption_timer_pending(vcpu) ||
            vmx->nested.mtf_pending)
@@ -4236,20 +4453,48 @@ static bool vmx_has_nested_events(struct kvm_vcpu 
*vcpu, bool for_injection)
            __vmx_interrupt_blocked(vcpu))
                return false;
 
-       if (!vapic)
+       /*
+        * This is called from non-sleeping contexts (notably the
+        * kvm_vcpu_block() loop, under set_current_state()), so it must
+        * not refresh an invalidated cache. Claim a pending event
+        * instead: the bounce through vcpu_run services the pending
+        * KVM_REQ_GET_NESTED_STATE_PAGES (posted by whatever invalidated
+        * the cache) in a sleepable context, re-establishing the cache,
+        * and re-evaluates events for real. A spurious wakeup is safe;
+        * sleeping here is not, and returning false could miss a wakeup.
+        */
+       idx = nested_gpc_try_lock_if_active(&vmx->nested.virtual_apic_cache);
+       if (idx == -EWOULDBLOCK)
+               return true;
+       if (idx < 0)
                return false;
 
+       vapic = vmx->nested.virtual_apic_cache.khva;
        vppr = *((u32 *)(vapic + APIC_PROCPRI));
 
+       nested_gpc_unlock(&vmx->nested.virtual_apic_cache, idx);
+
        max_irr = vmx_get_rvi();
        if ((max_irr & 0xf0) > (vppr & 0xf0))
                return true;
 
-       if (vmx->nested.pi_pending && vmx->nested.pi_desc &&
-           pi_test_on(vmx->nested.pi_desc)) {
-               max_irr = pi_find_highest_vector(vmx->nested.pi_desc);
-               if (max_irr > 0 && (max_irr & 0xf0) > (vppr & 0xf0))
+       if (vmx->nested.pi_pending) {
+               idx = nested_gpc_try_lock_if_active(&vmx->nested.pi_desc_cache);
+               if (idx == -EWOULDBLOCK)
                        return true;
+               if (idx < 0)
+                       return false;
+
+               pi_desc = nested_pi_desc(vmx);
+               if (pi_test_on(pi_desc)) {
+                       max_irr = pi_find_highest_vector(pi_desc);
+                       if (max_irr > 0 && (max_irr & 0xf0) > (vppr & 0xf0)) {
+                               nested_gpc_unlock(&vmx->nested.pi_desc_cache, 
idx);
+                               return true;
+                       }
+               }
+
+               nested_gpc_unlock(&vmx->nested.pi_desc_cache, idx);
        }
 
        return false;
@@ -5482,6 +5727,20 @@ static int enter_vmx_operation(struct kvm_vcpu *vcpu)
        /* KVM only ever reads the L1 MSR bitmap, so never mark it dirty. */
        __kvm_gpc_init(&vmx->nested.msr_bitmap_cache, vcpu->kvm, true, NULL, 0);
 
+       /*
+        * The pfns of these three caches are handed to the CPU in vmcs02
+        * fields, for direct use while running the L2 guest. Pinning them
+        * for guest use makes an invalidation force this vCPU out of
+        * guest mode and post KVM_REQ_GET_NESTED_STATE_PAGES, whose
+        * handler re-establishes them before the next entry.
+        */
+       kvm_gpc_init_for_vcpu(&vmx->nested.apic_access_page_cache, vcpu,
+                             KVM_REQ_GET_NESTED_STATE_PAGES);
+       kvm_gpc_init_for_vcpu(&vmx->nested.virtual_apic_cache, vcpu,
+                             KVM_REQ_GET_NESTED_STATE_PAGES);
+       kvm_gpc_init_for_vcpu(&vmx->nested.pi_desc_cache, vcpu,
+                             KVM_REQ_GET_NESTED_STATE_PAGES);
+
        /*
         * Clear last_vpid to ensure that the VPID is flushed on the first
         * nested VM-Enter. Otherwise, stale TLB entries from a previous life of
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 612ab07d4100..25b0d114f2bf 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -6467,11 +6467,14 @@ static void vmx_flush_pml_buffer(struct kvm_vcpu *vcpu)
 
 static void nested_vmx_mark_all_vmcs12_pages_dirty(struct kvm_vcpu *vcpu)
 {
-       struct vcpu_vmx *vmx = to_vmx(vcpu);
+       struct vmcs12 *vmcs12 = get_vmcs12(vcpu);
 
-       kvm_vcpu_map_mark_dirty(vcpu, &vmx->nested.apic_access_page_map);
-       kvm_vcpu_map_mark_dirty(vcpu, &vmx->nested.virtual_apic_map);
-       kvm_vcpu_map_mark_dirty(vcpu, &vmx->nested.pi_desc_map);
+       if (nested_cpu_has2(vmcs12, SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES))
+               kvm_vcpu_mark_page_dirty(vcpu, 
gpa_to_gfn(vmcs12->apic_access_addr));
+       if (nested_cpu_has(vmcs12, CPU_BASED_TPR_SHADOW))
+               kvm_vcpu_mark_page_dirty(vcpu, 
gpa_to_gfn(vmcs12->virtual_apic_page_addr));
+       if (nested_cpu_has_posted_intr(vmcs12))
+               kvm_vcpu_mark_page_dirty(vcpu, 
gpa_to_gfn(vmcs12->posted_intr_desc_addr));
 }
 
 static void vmx_dump_sel(char *name, uint32_t sel)
diff --git a/arch/x86/kvm/vmx/vmx.h b/arch/x86/kvm/vmx/vmx.h
index d5aa5aa83c95..f88e0031be4e 100644
--- a/arch/x86/kvm/vmx/vmx.h
+++ b/arch/x86/kvm/vmx/vmx.h
@@ -73,6 +73,12 @@ struct pt_desc {
 struct nested_vmx {
        /* Has the level1 guest done vmxon? */
        bool vmxon;
+       /*
+        * The pending KVM_REQ_GET_NESTED_STATE_PAGES was posted by
+        * KVM_SET_NESTED_STATE: an unbackable page is then a userspace
+        * error to report, not a transient state to ride out.
+        */
+       bool resume_pending;
        gpa_t vmxon_ptr;
        bool pml_full;
 
@@ -148,11 +154,11 @@ struct nested_vmx {
         */
        struct gfn_to_pfn_cache msr_bitmap_cache;
 
-       struct kvm_host_map apic_access_page_map;
-       struct kvm_host_map virtual_apic_map;
-       struct kvm_host_map pi_desc_map;
+       struct gfn_to_pfn_cache apic_access_page_cache;
+       struct gfn_to_pfn_cache virtual_apic_cache;
+       struct gfn_to_pfn_cache pi_desc_cache;
 
-       struct pi_desc *pi_desc;
+       u64 pi_desc_offset;
        bool pi_pending;
        u16 posted_intr_nv;
 
-- 
2.55.0


Reply via email to