From: David Woodhouse <[email protected]>

The per-intercept IOPM check on every trapped L2 IO instruction reads
L1's IO permissions map with kvm_vcpu_read_guest() — a
memremap/memunmap cycle per access with unmanaged guest memory. Cache
the three IOPM pages in gfn_to_pfn_caches keyed on iopm_base_pa, as
for the MSR permissions map.

Unlike the MSRPM accesses, an IOPM read is not naturally aligned: a
two-byte read for a port range straddling a page boundary (e.g. ports
0x7ff8-0x7fff at bytes 0xfff-0x1000) spans two pages, so read byte by
byte, each from its own page's cache.

Failure semantics are preserved: an unreadable IOPM reflects the
intercept to L1 (NESTED_EXIT_DONE).

Signed-off-by: David Woodhouse <[email protected]>
Assisted-by: Claude:claude-mythos-5
---
 arch/x86/kvm/svm/nested.c | 33 +++++++++++++++++++++++++++++----
 arch/x86/kvm/svm/svm.h    |  6 ++++++
 2 files changed, 35 insertions(+), 4 deletions(-)

diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index 07609e4802f9..0fa3183a509c 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -1532,6 +1532,8 @@ int svm_allocate_nested(struct vcpu_svm *svm)
        kvm_gpc_init(&svm->nested.vmcb12_cache, svm->vcpu.kvm);
        for (i = 0; i < ARRAY_SIZE(svm->nested.msrpm12_cache); i++)
                kvm_gpc_init(&svm->nested.msrpm12_cache[i], svm->vcpu.kvm);
+       for (i = 0; i < ARRAY_SIZE(svm->nested.iopm12_cache); i++)
+               kvm_gpc_init(&svm->nested.iopm12_cache[i], svm->vcpu.kvm);
 
        svm->nested.initialized = true;
        return 0;
@@ -1557,6 +1559,8 @@ void svm_free_nested(struct vcpu_svm *svm)
        kvm_gpc_deactivate(&svm->nested.vmcb12_cache);
        for (i = 0; i < ARRAY_SIZE(svm->nested.msrpm12_cache); i++)
                kvm_gpc_deactivate(&svm->nested.msrpm12_cache[i]);
+       for (i = 0; i < ARRAY_SIZE(svm->nested.iopm12_cache); i++)
+               kvm_gpc_deactivate(&svm->nested.iopm12_cache[i]);
 
        __free_page(virt_to_page(svm->nested.vmcb02.ptr));
        svm->nested.vmcb02.ptr = NULL;
@@ -1635,7 +1639,7 @@ static int nested_svm_intercept_ioio(struct vcpu_svm *svm)
        unsigned port, size, iopm_len;
        u16 val, mask;
        u8 start_bit;
-       u64 gpa;
+       int i;
 
        if (!(vmcb12_is_intercept(&svm->nested.ctl, INTERCEPT_IOIO_PROT)))
                return NESTED_EXIT_HOST;
@@ -1643,14 +1647,35 @@ static int nested_svm_intercept_ioio(struct vcpu_svm 
*svm)
        port = svm->vmcb->control.exit_info_1 >> 16;
        size = (svm->vmcb->control.exit_info_1 & SVM_IOIO_SIZE_MASK) >>
                SVM_IOIO_SIZE_SHIFT;
-       gpa  = svm->nested.ctl.iopm_base_pa + (port / 8);
        start_bit = port % 8;
        iopm_len = (start_bit + size > 8) ? 2 : 1;
        mask = (0xf >> (4 - size)) << start_bit;
        val = 0;
 
-       if (kvm_vcpu_read_guest(&svm->vcpu, gpa, &val, iopm_len))
-               return NESTED_EXIT_DONE;
+       /*
+        * Read byte by byte: a two-byte read at the end of a page (e.g.
+        * ports 0x7ff8-0x7fff spanning bytes 0xfff-0x1000) crosses into
+        * the next page of the IOPM, i.e. the next cache.
+        */
+       for (i = 0; i < iopm_len; i++) {
+               unsigned long offset = port / 8 + i;
+               struct gfn_to_pfn_cache *gpc;
+               gpa_t gpa;
+               int idx;
+
+               if (WARN_ON_ONCE(offset >= IOPM_SIZE))
+                       return NESTED_EXIT_DONE;
+
+               gpc = &svm->nested.iopm12_cache[offset >> PAGE_SHIFT];
+               gpa = svm->nested.ctl.iopm_base_pa + (offset & PAGE_MASK);
+
+               idx = kvm_gpc_lock_page(gpc, gpa);
+               if (idx < 0)
+                       return NESTED_EXIT_DONE;
+
+               val |= *(u8 *)(gpc->khva + offset_in_page(offset)) << (i * 8);
+               kvm_gpc_unlock(gpc, idx);
+       }
 
        return (val & mask) ? NESTED_EXIT_DONE : NESTED_EXIT_HOST;
 }
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index ac2f9731bf5d..8ccd5a2d942e 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -228,6 +228,12 @@ struct svm_nested_state {
         */
        struct gfn_to_pfn_cache msrpm12_cache[MSRPM_SIZE / PAGE_SIZE];
 
+       /*
+        * Cached mappings of the three pages of L1's IO permissions map,
+        * keyed on ctl.iopm_base_pa, for the per-intercept checks.
+        */
+       struct gfn_to_pfn_cache iopm12_cache[IOPM_SIZE / PAGE_SIZE];
+
        /*
         * The MSR permissions map used for vmcb02, which is the merge result
         * of vmcb01 and vmcb12
-- 
2.55.0


Reply via email to