From: Paul Durrant Date: Mon, 13 Nov 2023 20:56:20 +0000 (+0000) Subject: KVM: pfncache: allow a cache to be activated with a fixed (userspace) HVA X-Git-Url: http://xenbits.xensource.com/gitweb?a=commitdiff_plain;h=ada9d064a6a47d42cda36d6faf9fb47f931314b4;p=people%2Fpauldu%2Flinux.git KVM: pfncache: allow a cache to be activated with a fixed (userspace) HVA Some cached pages may actually be overlays on guest memory that have a fixed HVA within the VMM. It's pointless to invalidate such cached mappings if the overlay is moved so allow a cache to be activated directly with the HVA to cater for such cases. A subsequent patch will make use of this facility. Signed-off-by: Paul Durrant --- Cc: Sean Christopherson Cc: Paolo Bonzini Cc: David Woodhouse v8: - Re-worked to avoid messing with struct gfn_to_pfn_cache. --- diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index b1dc2e5a64f3..484c587e8290 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -1312,6 +1312,22 @@ void kvm_gpc_init(struct gfn_to_pfn_cache *gpc, struct kvm *kvm); */ int kvm_gpc_activate(struct gfn_to_pfn_cache *gpc, gpa_t gpa, unsigned long len); +/** + * kvm_gpc_activate_hva - prepare a cached kernel mapping and HPA for a given HVA. + * + * @gpc: struct gfn_to_pfn_cache object. + * @hva: userspace virtual address to map. + * @len: sanity check; the range being access must fit a single page. + * + * @return: 0 for success. + * -EINVAL for a mapping which would cross a page boundary. + * -EFAULT for an untranslatable guest physical address. + * + * The semantics of this function are the same as those of kvm_gpc_activate(). It + * merely bypasses a layer of address translation. + */ +int kvm_gpc_activate_hva(struct gfn_to_pfn_cache *gpc, unsigned long hva, unsigned long len); + /** * kvm_gpc_check - check validity of a gfn_to_pfn_cache. * @@ -1365,7 +1381,8 @@ void kvm_gpc_deactivate(struct gfn_to_pfn_cache *gpc); */ static inline void kvm_gpc_mark_dirty(struct gfn_to_pfn_cache *gpc) { - mark_page_dirty_in_slot(gpc->kvm, gpc->memslot, gpc->gpa >> PAGE_SHIFT); + if (gpc->gpa != KVM_XEN_INVALID_GPA) + mark_page_dirty_in_slot(gpc->kvm, gpc->memslot, gpc->gpa >> PAGE_SHIFT); } void kvm_sigset_activate(struct kvm_vcpu *vcpu); diff --git a/virt/kvm/pfncache.c b/virt/kvm/pfncache.c index 610ff72fb631..e0ffb6696cf2 100644 --- a/virt/kvm/pfncache.c +++ b/virt/kvm/pfncache.c @@ -210,11 +210,11 @@ out_error: return -EFAULT; } -static int __kvm_gpc_refresh(struct gfn_to_pfn_cache *gpc, gpa_t gpa, +static int __kvm_gpc_refresh(struct gfn_to_pfn_cache *gpc, u64 addr, bool addr_is_gpa, unsigned long len) { struct kvm_memslots *slots = kvm_memslots(gpc->kvm); - unsigned long page_offset = offset_in_page(gpa); + unsigned long page_offset = offset_in_page(addr); bool unmap_old = false; unsigned long old_page_offset; kvm_pfn_t old_pfn; @@ -247,29 +247,42 @@ static int __kvm_gpc_refresh(struct gfn_to_pfn_cache *gpc, gpa_t gpa, old_page_offset = offset_in_page(gpc->khva); old_khva = gpc->khva - old_page_offset; - /* If the userspace HVA is invalid, refresh that first */ - if (gpc->gpa != gpa || gpc->generation != slots->generation || - kvm_is_error_hva(gpc->uhva)) { - gfn_t gfn = gpa_to_gfn(gpa); + if (addr_is_gpa) { + if (gpc->gpa != addr || + gpc->generation != slots->generation || + kvm_is_error_hva(gpc->uhva)) { + gfn_t gfn = gpa_to_gfn(addr); - gpc->gpa = gpa; - gpc->generation = slots->generation; - gpc->memslot = __gfn_to_memslot(slots, gfn); - gpc->uhva = gfn_to_hva_memslot(gpc->memslot, gfn); + gpc->gpa = addr; + gpc->generation = slots->generation; + gpc->memslot = __gfn_to_memslot(slots, gfn); + gpc->uhva = gfn_to_hva_memslot(gpc->memslot, gfn); - if (kvm_is_error_hva(gpc->uhva)) { - ret = -EFAULT; - goto out; - } + if (kvm_is_error_hva(gpc->uhva)) { + ret = -EFAULT; + goto out; + } - hva_change = true; + hva_change = true; + } else { + /* + * No need to do any re-mapping if the only thing that has + * changed is the page offset. Just subtract the old offset + * before the new one is added in. + */ + gpc->uhva -= old_page_offset; + } } else { - /* - * No need to do any re-mapping if the only thing that has - * changed is the page offset. Just subtract the old offset - * before the new one is added in. - */ + gpc->gpa = KVM_XEN_INVALID_GPA; + + /* Align to page boundary before checking for a change */ gpc->uhva -= old_page_offset; + addr -= page_offset; + + if (gpc->uhva != addr) { + gpc->uhva = addr; + hva_change = true; + } } gpc->uhva += page_offset; @@ -319,7 +332,10 @@ out_unlock: int kvm_gpc_refresh(struct gfn_to_pfn_cache *gpc, unsigned long len) { - return __kvm_gpc_refresh(gpc, gpc->gpa, len); + if (gpc->gpa != KVM_XEN_INVALID_GPA) + return __kvm_gpc_refresh(gpc, gpc->gpa, true, len); + + return __kvm_gpc_refresh(gpc, gpc->uhva, false, len); } void kvm_gpc_init(struct gfn_to_pfn_cache *gpc, struct kvm *kvm) @@ -332,7 +348,8 @@ void kvm_gpc_init(struct gfn_to_pfn_cache *gpc, struct kvm *kvm) gpc->uhva = KVM_HVA_ERR_BAD; } -int kvm_gpc_activate(struct gfn_to_pfn_cache *gpc, gpa_t gpa, unsigned long len) +static int __kvm_gpc_activate(struct gfn_to_pfn_cache *gpc, u64 addr, bool addr_is_gpa, + unsigned long len) { struct kvm *kvm = gpc->kvm; @@ -353,7 +370,17 @@ int kvm_gpc_activate(struct gfn_to_pfn_cache *gpc, gpa_t gpa, unsigned long len) gpc->active = true; write_unlock_irq(&gpc->lock); } - return __kvm_gpc_refresh(gpc, gpa, len); + return __kvm_gpc_refresh(gpc, addr, addr_is_gpa, len); +} + +int kvm_gpc_activate(struct gfn_to_pfn_cache *gpc, gpa_t gpa, unsigned long len) +{ + return __kvm_gpc_activate(gpc, gpa, true, len); +} + +int kvm_gpc_activate_hva(struct gfn_to_pfn_cache *gpc, unsigned long hva, unsigned long len) +{ + return __kvm_gpc_activate(gpc, hva, false, len); } void kvm_gpc_deactivate(struct gfn_to_pfn_cache *gpc)