| From 797179bc4fe06c89e47a9f36f886f68640b423f8 Mon Sep 17 00:00:00 2001 |
| From: James Hogan <james.hogan@imgtec.com> |
| Date: Thu, 9 Jun 2016 10:50:43 +0100 |
| Subject: MIPS: KVM: Fix modular KVM under QEMU |
| MIME-Version: 1.0 |
| Content-Type: text/plain; charset=UTF-8 |
| Content-Transfer-Encoding: 8bit |
| |
| From: James Hogan <james.hogan@imgtec.com> |
| |
| commit 797179bc4fe06c89e47a9f36f886f68640b423f8 upstream. |
| |
| Copy __kvm_mips_vcpu_run() into unmapped memory, so that we can never |
| get a TLB refill exception in it when KVM is built as a module. |
| |
| This was observed to happen with the host MIPS kernel running under |
| QEMU, due to a not entirely transparent optimisation in the QEMU TLB |
| handling where TLB entries replaced with TLBWR are copied to a separate |
| part of the TLB array. Code in those pages continue to be executable, |
| but those mappings persist only until the next ASID switch, even if they |
| are marked global. |
| |
| An ASID switch happens in __kvm_mips_vcpu_run() at exception level after |
| switching to the guest exception base. Subsequent TLB mapped kernel |
| instructions just prior to switching to the guest trigger a TLB refill |
| exception, which enters the guest exception handlers without updating |
| EPC. This appears as a guest triggered TLB refill on a host kernel |
| mapped (host KSeg2) address, which is not handled correctly as user |
| (guest) mode accesses to kernel (host) segments always generate address |
| error exceptions. |
| |
| Signed-off-by: James Hogan <james.hogan@imgtec.com> |
| Cc: Paolo Bonzini <pbonzini@redhat.com> |
| Cc: Radim Krčmář <rkrcmar@redhat.com> |
| Cc: Ralf Baechle <ralf@linux-mips.org> |
| Cc: kvm@vger.kernel.org |
| Cc: linux-mips@linux-mips.org |
| Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| arch/mips/include/asm/kvm_host.h | 1 + |
| arch/mips/kvm/interrupt.h | 1 + |
| arch/mips/kvm/locore.S | 1 + |
| arch/mips/kvm/mips.c | 11 ++++++++++- |
| 4 files changed, 13 insertions(+), 1 deletion(-) |
| |
| --- a/arch/mips/include/asm/kvm_host.h |
| +++ b/arch/mips/include/asm/kvm_host.h |
| @@ -372,6 +372,7 @@ struct kvm_mips_tlb { |
| #define KVM_MIPS_GUEST_TLB_SIZE 64 |
| struct kvm_vcpu_arch { |
| void *host_ebase, *guest_ebase; |
| + int (*vcpu_run)(struct kvm_run *run, struct kvm_vcpu *vcpu); |
| unsigned long host_stack; |
| unsigned long host_gp; |
| |
| --- a/arch/mips/kvm/interrupt.h |
| +++ b/arch/mips/kvm/interrupt.h |
| @@ -28,6 +28,7 @@ |
| #define MIPS_EXC_MAX 12 |
| /* XXXSL More to follow */ |
| |
| +extern char __kvm_mips_vcpu_run_end[]; |
| extern char mips32_exception[], mips32_exceptionEnd[]; |
| extern char mips32_GuestException[], mips32_GuestExceptionEnd[]; |
| |
| --- a/arch/mips/kvm/locore.S |
| +++ b/arch/mips/kvm/locore.S |
| @@ -227,6 +227,7 @@ FEXPORT(__kvm_mips_load_k0k1) |
| |
| /* Jump to guest */ |
| eret |
| +EXPORT(__kvm_mips_vcpu_run_end) |
| |
| VECTOR(MIPSX(exception), unknown) |
| /* Find out what mode we came from and jump to the proper handler. */ |
| --- a/arch/mips/kvm/mips.c |
| +++ b/arch/mips/kvm/mips.c |
| @@ -314,6 +314,15 @@ struct kvm_vcpu *kvm_arch_vcpu_create(st |
| memcpy(gebase + offset, mips32_GuestException, |
| mips32_GuestExceptionEnd - mips32_GuestException); |
| |
| +#ifdef MODULE |
| + offset += mips32_GuestExceptionEnd - mips32_GuestException; |
| + memcpy(gebase + offset, (char *)__kvm_mips_vcpu_run, |
| + __kvm_mips_vcpu_run_end - (char *)__kvm_mips_vcpu_run); |
| + vcpu->arch.vcpu_run = gebase + offset; |
| +#else |
| + vcpu->arch.vcpu_run = __kvm_mips_vcpu_run; |
| +#endif |
| + |
| /* Invalidate the icache for these ranges */ |
| local_flush_icache_range((unsigned long)gebase, |
| (unsigned long)gebase + ALIGN(size, PAGE_SIZE)); |
| @@ -403,7 +412,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_v |
| /* Disable hardware page table walking while in guest */ |
| htw_stop(); |
| |
| - r = __kvm_mips_vcpu_run(run, vcpu); |
| + r = vcpu->arch.vcpu_run(run, vcpu); |
| |
| /* Re-enable HTW before enabling interrupts */ |
| htw_start(); |