From: Mushahid Hussain <[email protected]> The reference TSC page is a page the hypervisor maintains for the guest. A higher Virtual Trust Level keeps its own page, and it protects the memory that holds that page against the lower levels. KVM writes the page through kvm_write_guest(), which refuses a write to protected memory. The refusal aborts the update of every page: KVM has written sequence zero to all of them and leaves them that way for the rest of the guest's life, so the guest falls back to the time reference count register for every read of time.
Memory protections restrict the guest. They do not restrict the hypervisor's update of a page the hypervisor owns. Add kvm_write_guest_unprotected() for such a page and use it for the reference TSC page. Assisted-by: LLM [dwmw2: the original message called the function kvm_write_guest_hv(); it is kvm_write_guest_unprotected() in the code, say so. ] Signed-off-by: David Woodhouse <[email protected]> --- arch/x86/kvm/hyperv.c | 2 +- include/linux/kvm_host.h | 2 ++ virt/kvm/kvm_main.c | 27 +++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c index 2098daede7d3..eebe48f5be23 100644 --- a/arch/x86/kvm/hyperv.c +++ b/arch/x86/kvm/hyperv.c @@ -1260,7 +1260,7 @@ static int hv_write_tsc_pages(struct kvm *kvm, size_t len) if (!hv_tsc_page_gfn(hv, i, &gfn)) continue; - if (kvm_write_guest(kvm, gfn_to_gpa(gfn), &hv->tsc_ref, len)) + if (kvm_write_guest_unprotected(kvm, gfn_to_gpa(gfn), &hv->tsc_ref, len)) return -EFAULT; } diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index fdcf401ccb46..beae4f2a982a 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -1551,6 +1551,8 @@ int kvm_write_guest_page(struct kvm *kvm, gfn_t gfn, const void *data, int offset, int len); int kvm_write_guest(struct kvm *kvm, gpa_t gpa, const void *data, unsigned long len); +int kvm_write_guest_unprotected(struct kvm *kvm, gpa_t gpa, const void *data, + unsigned long len); int kvm_write_guest_cached(struct kvm *kvm, struct gfn_to_hva_cache *ghc, void *data, unsigned long len); int kvm_write_guest_offset_cached(struct kvm *kvm, struct gfn_to_hva_cache *ghc, diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index d37961cc5235..72fe64e5d4a1 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -3832,6 +3832,33 @@ int kvm_write_guest(struct kvm *kvm, gpa_t gpa, const void *data, } EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_write_guest); +/* Write a page KVM maintains for the guest; plane protections do not apply. */ +int kvm_write_guest_unprotected(struct kvm *kvm, gpa_t gpa, const void *data, + unsigned long len) +{ + gfn_t gfn = gpa >> PAGE_SHIFT; + int seg; + int offset = offset_in_page(gpa); + + while ((seg = next_segment(len, offset)) != 0) { + struct kvm_memory_slot *slot = gfn_to_memslot(kvm, gfn); + unsigned long addr; + + addr = gfn_to_hva_many(slot, gfn, NULL); + if (kvm_is_error_hva(addr)) + return -EFAULT; + if (__copy_to_user((void __user *)addr + offset, data, seg)) + return -EFAULT; + mark_page_dirty_in_slot(kvm, slot, gfn); + offset = 0; + len -= seg; + data += seg; + ++gfn; + } + return 0; +} +EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_write_guest_unprotected); + int kvm_vcpu_write_guest(struct kvm_vcpu *vcpu, gpa_t gpa, const void *data, unsigned long len) { -- 2.50.1

