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

