Expose the KVM guest CP0_Count frequency to userland via a new KVM_REG_MIPS_COUNT_HZ register accessible with the KVM_{GET,SET}_ONE_REG ioctls. When the frequency is altered the bias is adjusted such that the guest CP0_Count doesn't jump discontinuously or lose any timer interrupts. Signed-off-by: James Hogan <james.hogan@xxxxxxxxxx> Cc: Paolo Bonzini <pbonzini@xxxxxxxxxx> Cc: Gleb Natapov <gleb@xxxxxxxxxx> Cc: kvm@xxxxxxxxxxxxxxx Cc: Ralf Baechle <ralf@xxxxxxxxxxxxxx> Cc: linux-mips@xxxxxxxxxxxxxx Cc: David Daney <david.daney@xxxxxxxxxx> Cc: Sanjay Lal <sanjayl@xxxxxxxxxxx> --- arch/mips/include/asm/kvm_host.h | 1 + arch/mips/include/uapi/asm/kvm.h | 7 +++++ arch/mips/kvm/kvm_mips.c | 3 +++ arch/mips/kvm/kvm_mips_emul.c | 58 ++++++++++++++++++++++++++++++++++++++++ arch/mips/kvm/kvm_trap_emul.c | 6 +++++ 5 files changed, 75 insertions(+) diff --git a/arch/mips/include/asm/kvm_host.h b/arch/mips/include/asm/kvm_host.h index 2a7d4e1be25a..94a7299e1f6c 100644 --- a/arch/mips/include/asm/kvm_host.h +++ b/arch/mips/include/asm/kvm_host.h @@ -721,6 +721,7 @@ void kvm_mips_write_compare(struct kvm_vcpu *vcpu, uint32_t compare); void kvm_mips_init_count(struct kvm_vcpu *vcpu); int kvm_mips_set_count_ctl(struct kvm_vcpu *vcpu, s64 count_ctl); void kvm_mips_set_count_user_bias(struct kvm_vcpu *vcpu, s64 user_bias); +int kvm_mips_set_count_hz(struct kvm_vcpu *vcpu, s64 count_hz); void kvm_mips_migrate_count(struct kvm_vcpu *vcpu); void kvm_mips_count_enable_cause(struct kvm_vcpu *vcpu); void kvm_mips_count_disable_cause(struct kvm_vcpu *vcpu); diff --git a/arch/mips/include/uapi/asm/kvm.h b/arch/mips/include/uapi/asm/kvm.h index c3d9f6697885..f9487f8b3394 100644 --- a/arch/mips/include/uapi/asm/kvm.h +++ b/arch/mips/include/uapi/asm/kvm.h @@ -138,6 +138,13 @@ struct kvm_fpu { */ #define KVM_REG_MIPS_COUNT_BIAS (KVM_REG_MIPS | KVM_REG_SIZE_U64 | \ 0x20000 | 2) +/* + * CP0_Count rate in Hz + * Specifies the rate of the CP0_Count timer in Hz. Modifications occur without + * discontinuities in CP0_Count. + */ +#define KVM_REG_MIPS_COUNT_HZ (KVM_REG_MIPS | KVM_REG_SIZE_U64 | \ + 0x20000 | 3) /* * KVM MIPS specific structures and definitions diff --git a/arch/mips/kvm/kvm_mips.c b/arch/mips/kvm/kvm_mips.c index 9efb9d4c0882..cbb3cdca2878 100644 --- a/arch/mips/kvm/kvm_mips.c +++ b/arch/mips/kvm/kvm_mips.c @@ -550,6 +550,7 @@ static u64 kvm_mips_get_one_regs[] = { KVM_REG_MIPS_COUNT_CTL, KVM_REG_MIPS_COUNT_RESUME, KVM_REG_MIPS_COUNT_BIAS, + KVM_REG_MIPS_COUNT_HZ, }; static int kvm_mips_get_reg(struct kvm_vcpu *vcpu, @@ -626,6 +627,7 @@ static int kvm_mips_get_reg(struct kvm_vcpu *vcpu, case KVM_REG_MIPS_COUNT_CTL: case KVM_REG_MIPS_COUNT_RESUME: case KVM_REG_MIPS_COUNT_BIAS: + case KVM_REG_MIPS_COUNT_HZ: ret = kvm_mips_callbacks->get_one_reg(vcpu, reg, &v); if (ret) return ret; @@ -718,6 +720,7 @@ static int kvm_mips_set_reg(struct kvm_vcpu *vcpu, case KVM_REG_MIPS_COUNT_CTL: case KVM_REG_MIPS_COUNT_RESUME: case KVM_REG_MIPS_COUNT_BIAS: + case KVM_REG_MIPS_COUNT_HZ: return kvm_mips_callbacks->set_one_reg(vcpu, reg, v); default: return -EINVAL; diff --git a/arch/mips/kvm/kvm_mips_emul.c b/arch/mips/kvm/kvm_mips_emul.c index 8397f6b3a327..fcbc5ff6c347 100644 --- a/arch/mips/kvm/kvm_mips_emul.c +++ b/arch/mips/kvm/kvm_mips_emul.c @@ -546,6 +546,64 @@ void kvm_mips_set_count_user_bias(struct kvm_vcpu *vcpu, s64 user_bias) } /** + * kvm_mips_set_count_hz() - Update the frequency of the timer. + * @vcpu: Virtual CPU. + * @count_hz: Frequency of CP0_Count timer in Hz. + * + * Change the frequency of the CP0_Count timer. This is done atomically so that + * CP0_Count is continuous and no timer interrupt is lost. + * + * Returns: -EINVAL if @count_hz is out of range. + * 0 on success. + */ +int kvm_mips_set_count_hz(struct kvm_vcpu *vcpu, s64 count_hz) +{ + struct mips_coproc *cop0 = vcpu->arch.cop0; + int dc; + ktime_t now; + u32 count, bias; + s64 user_bias; + + /* ensure the frequency is in a sensible range... */ + if (count_hz <= 0 || count_hz > NSEC_PER_SEC) + return -EINVAL; + /* ... and has actually changed */ + if (vcpu->arch.count_hz == count_hz) + return 0; + + /* Safely freeze timer so we can keep it continuous */ + dc = kvm_mips_count_disabled(vcpu); + if (dc) { + now = kvm_mips_count_time(vcpu); + count = kvm_read_c0_guest_count(cop0); + } else { + now = kvm_mips_freeze_hrtimer(vcpu, &count); + } + + /* Update the frequency */ + vcpu->arch.count_hz = count_hz; + vcpu->arch.count_period = div_u64((u64)NSEC_PER_SEC << 32, count_hz); + vcpu->arch.count_dyn_bias = 0; + + /* Calculate adjusted bias so dynamic count is unchanged */ + bias = count - kvm_mips_ktime_to_count(vcpu, now); + + /* Calculate matching user-visible nanosecond bias */ + user_bias = vcpu->arch.count_dyn_bias + + div_u64((u64)bias * NSEC_PER_SEC, count_hz); + if (ktime_to_ns(now) + user_bias >= vcpu->arch.count_period) + user_bias -= vcpu->arch.count_period; + + vcpu->arch.count_bias = bias; + vcpu->arch.count_user_bias = user_bias; + + /* Update and resume hrtimer */ + if (!dc) + kvm_mips_resume_hrtimer(vcpu, now, count); + return 0; +} + +/** * kvm_mips_write_compare() - Modify compare and update timer. * @vcpu: Virtual CPU. * @compare: New CP0_Compare value. diff --git a/arch/mips/kvm/kvm_trap_emul.c b/arch/mips/kvm/kvm_trap_emul.c index 7aedc822ac09..30cef8e6da81 100644 --- a/arch/mips/kvm/kvm_trap_emul.c +++ b/arch/mips/kvm/kvm_trap_emul.c @@ -418,6 +418,9 @@ static int kvm_trap_emul_get_one_reg(struct kvm_vcpu *vcpu, case KVM_REG_MIPS_COUNT_BIAS: *v = vcpu->arch.count_user_bias; break; + case KVM_REG_MIPS_COUNT_HZ: + *v = vcpu->arch.count_hz; + break; default: return -EINVAL; } @@ -464,6 +467,9 @@ static int kvm_trap_emul_set_one_reg(struct kvm_vcpu *vcpu, case KVM_REG_MIPS_COUNT_BIAS: kvm_mips_set_count_user_bias(vcpu, v); break; + case KVM_REG_MIPS_COUNT_HZ: + ret = kvm_mips_set_count_hz(vcpu, v); + break; default: return -EINVAL; } -- 1.8.1.2 -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html