summaryrefslogtreecommitdiffstats
path: root/arch/mips/kvm/kvm_trap_emul.c
diff options
context:
space:
mode:
authorJames Hogan <james.hogan@imgtec.com>2014-05-29 10:16:35 +0100
committerPaolo Bonzini <pbonzini@redhat.com>2014-05-30 13:01:48 +0200
commite30492bbe95a2495930aa7db7eacde5141e45332 (patch)
treeacb0c06fb12ffae1ae9f118080c44a6e84a6b3ef /arch/mips/kvm/kvm_trap_emul.c
parent3a0ba77408f824b1cebf5134c710a8455d7bc8f4 (diff)
downloadlinux-e30492bbe95a2495930aa7db7eacde5141e45332.tar.gz
linux-e30492bbe95a2495930aa7db7eacde5141e45332.tar.bz2
linux-e30492bbe95a2495930aa7db7eacde5141e45332.zip
MIPS: KVM: Rewrite count/compare timer emulation
Previously the emulation of the CPU timer was just enough to get a Linux guest running but some shortcuts were taken: - The guest timer interrupt was hard coded to always happen every 10 ms rather than being timed to when CP0_Count would match CP0_Compare. - The guest's CP0_Count register was based on the host's CP0_Count register. This isn't very portable and fails on cores without a CP_Count register implemented such as Ingenic XBurst. It also meant that the guest's CP0_Cause.DC bit to disable the CP0_Count register took no effect. - The guest's CP0_Count register was emulated by just dividing the host's CP0_Count register by 4. This resulted in continuity problems when used as a clock source, since when the host CP0_Count overflows from 0x7fffffff to 0x80000000, the guest CP0_Count transitions discontinuously from 0x1fffffff to 0xe0000000. Therefore rewrite & fix emulation of the guest timer based on the monotonic kernel time (i.e. ktime_get()). Internally a 32-bit count_bias value is added to the frequency scaled nanosecond monotonic time to get the guest's CP0_Count. The frequency of the timer is initialised to 100MHz and cannot yet be changed, but a later patch will allow the frequency to be configured via the KVM_{GET,SET}_ONE_REG ioctl interface. The timer can now be stopped via the CP0_Cause.DC bit (by the guest or via the KVM_SET_ONE_REG ioctl interface), at which point the current CP0_Count is stored and can be read directly. When it is restarted the bias is recalculated such that the CP0_Count value is continuous. Due to the nature of hrtimer interrupts any read of the guest's CP0_Count register while it is running triggers a check for whether the hrtimer has expired, so that the guest/userland cannot observe the CP0_Count passing CP0_Compare without queuing a timer interrupt. This is also taken advantage of when stopping the timer to ensure that a pending timer interrupt is queued. This replaces the implementation of: - Guest read of CP0_Count - Guest write of CP0_Count - Guest write of CP0_Compare - Guest write of CP0_Cause - Guest read of HWR 2 (CC) with RDHWR - Host read of CP0_Count via KVM_GET_ONE_REG ioctl interface - Host write of CP0_Count via KVM_SET_ONE_REG ioctl interface - Host write of CP0_Compare via KVM_SET_ONE_REG ioctl interface - Host write of CP0_Cause via KVM_SET_ONE_REG ioctl interface Signed-off-by: James Hogan <james.hogan@imgtec.com> Cc: Paolo Bonzini <pbonzini@redhat.com> Cc: Gleb Natapov <gleb@kernel.org> Cc: kvm@vger.kernel.org Cc: Ralf Baechle <ralf@linux-mips.org> Cc: linux-mips@linux-mips.org Cc: Sanjay Lal <sanjayl@kymasys.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Diffstat (limited to 'arch/mips/kvm/kvm_trap_emul.c')
-rw-r--r--arch/mips/kvm/kvm_trap_emul.c27
1 files changed, 23 insertions, 4 deletions
diff --git a/arch/mips/kvm/kvm_trap_emul.c b/arch/mips/kvm/kvm_trap_emul.c
index f1e8389f8d33..9908f2b0ff46 100644
--- a/arch/mips/kvm/kvm_trap_emul.c
+++ b/arch/mips/kvm/kvm_trap_emul.c
@@ -407,8 +407,7 @@ static int kvm_trap_emul_get_one_reg(struct kvm_vcpu *vcpu,
{
switch (reg->id) {
case KVM_REG_MIPS_CP0_COUNT:
- /* XXXKYMA: Run the Guest count register @ 1/4 the rate of the host */
- *v = (read_c0_count() >> 2);
+ *v = kvm_mips_read_count(vcpu);
break;
default:
return -EINVAL;
@@ -424,10 +423,30 @@ static int kvm_trap_emul_set_one_reg(struct kvm_vcpu *vcpu,
switch (reg->id) {
case KVM_REG_MIPS_CP0_COUNT:
- /* Not supported yet */
+ kvm_mips_write_count(vcpu, v);
break;
case KVM_REG_MIPS_CP0_COMPARE:
- kvm_write_c0_guest_compare(cop0, v);
+ kvm_mips_write_compare(vcpu, v);
+ break;
+ case KVM_REG_MIPS_CP0_CAUSE:
+ /*
+ * If the timer is stopped or started (DC bit) it must look
+ * atomic with changes to the interrupt pending bits (TI, IRQ5).
+ * A timer interrupt should not happen in between.
+ */
+ if ((kvm_read_c0_guest_cause(cop0) ^ v) & CAUSEF_DC) {
+ if (v & CAUSEF_DC) {
+ /* disable timer first */
+ kvm_mips_count_disable_cause(vcpu);
+ kvm_change_c0_guest_cause(cop0, ~CAUSEF_DC, v);
+ } else {
+ /* enable timer last */
+ kvm_change_c0_guest_cause(cop0, ~CAUSEF_DC, v);
+ kvm_mips_count_enable_cause(vcpu);
+ }
+ } else {
+ kvm_write_c0_guest_cause(cop0, v);
+ }
break;
default:
return -EINVAL;