Skip to content

Commit

Permalink
KVM: MIPS/VZ: Support hardware guest timer
Browse files Browse the repository at this point in the history
Transfer timer state to the VZ guest context (CP0_GTOffset & guest
CP0_Count) when entering guest mode, enabling direct guest access to it,
and transfer back to soft timer when saving guest register state.

This usually allows guest code to directly read CP0_Count (via MFC0 and
RDHWR) and read/write CP0_Compare, without trapping to the hypervisor
for it to emulate the guest timer. Writing to CP0_Count or CP0_Cause.DC
is much less common and still triggers a hypervisor GPSI exception, in
which case the timer state is transferred back to an hrtimer before
emulating the write.

We are careful to prevent small amounts of drift from building up due to
undeterministic time intervals between reading of the ktime and reading
of CP0_Count. Some drift is expected however, since the system
clocksource may use a different timer to the local CP0_Count timer used
by VZ. This is permitted to prevent guest CP0_Count from appearing to go
backwards.

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: linux-mips@linux-mips.org
Cc: kvm@vger.kernel.org
  • Loading branch information
James Hogan committed Mar 28, 2017
1 parent d42a008 commit f4474d5
Show file tree
Hide file tree
Showing 4 changed files with 287 additions and 11 deletions.
14 changes: 14 additions & 0 deletions arch/mips/include/asm/kvm_host.h
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,20 @@ void kvm_mips_count_enable_cause(struct kvm_vcpu *vcpu);
void kvm_mips_count_disable_cause(struct kvm_vcpu *vcpu);
enum hrtimer_restart kvm_mips_count_timeout(struct kvm_vcpu *vcpu);

/* fairly internal functions requiring some care to use */
int kvm_mips_count_disabled(struct kvm_vcpu *vcpu);
ktime_t kvm_mips_freeze_hrtimer(struct kvm_vcpu *vcpu, u32 *count);
int kvm_mips_restore_hrtimer(struct kvm_vcpu *vcpu, ktime_t before,
u32 count, int min_drift);

#ifdef CONFIG_KVM_MIPS_VZ
void kvm_vz_acquire_htimer(struct kvm_vcpu *vcpu);
void kvm_vz_lose_htimer(struct kvm_vcpu *vcpu);
#else
static inline void kvm_vz_acquire_htimer(struct kvm_vcpu *vcpu) {}
static inline void kvm_vz_lose_htimer(struct kvm_vcpu *vcpu) {}
#endif

enum emulation_result kvm_mips_check_privilege(u32 cause,
u32 *opc,
struct kvm_run *run,
Expand Down
81 changes: 79 additions & 2 deletions arch/mips/kvm/emulate.c
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ int kvm_get_badinstrp(u32 *opc, struct kvm_vcpu *vcpu, u32 *out)
* CP0_Cause.DC bit or the count_ctl.DC bit.
* 0 otherwise (in which case CP0_Count timer is running).
*/
static inline int kvm_mips_count_disabled(struct kvm_vcpu *vcpu)
int kvm_mips_count_disabled(struct kvm_vcpu *vcpu)
{
struct mips_coproc *cop0 = vcpu->arch.cop0;

Expand Down Expand Up @@ -467,7 +467,7 @@ u32 kvm_mips_read_count(struct kvm_vcpu *vcpu)
*
* Returns: The ktime at the point of freeze.
*/
static ktime_t kvm_mips_freeze_hrtimer(struct kvm_vcpu *vcpu, u32 *count)
ktime_t kvm_mips_freeze_hrtimer(struct kvm_vcpu *vcpu, u32 *count)
{
ktime_t now;

Expand Down Expand Up @@ -516,6 +516,82 @@ static void kvm_mips_resume_hrtimer(struct kvm_vcpu *vcpu,
hrtimer_start(&vcpu->arch.comparecount_timer, expire, HRTIMER_MODE_ABS);
}

/**
* kvm_mips_restore_hrtimer() - Restore hrtimer after a gap, updating expiry.
* @vcpu: Virtual CPU.
* @before: Time before Count was saved, lower bound of drift calculation.
* @count: CP0_Count at point of restore.
* @min_drift: Minimum amount of drift permitted before correction.
* Must be <= 0.
*
* Restores the timer from a particular @count, accounting for drift. This can
* be used in conjunction with kvm_mips_freeze_timer() when a hardware timer is
* to be used for a period of time, but the exact ktime corresponding to the
* final Count that must be restored is not known.
*
* It is gauranteed that a timer interrupt immediately after restore will be
* handled, but not if CP0_Compare is exactly at @count. That case should
* already be handled when the hardware timer state is saved.
*
* Assumes !kvm_mips_count_disabled(@vcpu) (guest CP0_Count timer is not
* stopped).
*
* Returns: Amount of correction to count_bias due to drift.
*/
int kvm_mips_restore_hrtimer(struct kvm_vcpu *vcpu, ktime_t before,
u32 count, int min_drift)
{
ktime_t now, count_time;
u32 now_count, before_count;
u64 delta;
int drift, ret = 0;

/* Calculate expected count at before */
before_count = vcpu->arch.count_bias +
kvm_mips_ktime_to_count(vcpu, before);

/*
* Detect significantly negative drift, where count is lower than
* expected. Some negative drift is expected when hardware counter is
* set after kvm_mips_freeze_timer(), and it is harmless to allow the
* time to jump forwards a little, within reason. If the drift is too
* significant, adjust the bias to avoid a big Guest.CP0_Count jump.
*/
drift = count - before_count;
if (drift < min_drift) {
count_time = before;
vcpu->arch.count_bias += drift;
ret = drift;
goto resume;
}

/* Calculate expected count right now */
now = ktime_get();
now_count = vcpu->arch.count_bias + kvm_mips_ktime_to_count(vcpu, now);

/*
* Detect positive drift, where count is higher than expected, and
* adjust the bias to avoid guest time going backwards.
*/
drift = count - now_count;
if (drift > 0) {
count_time = now;
vcpu->arch.count_bias += drift;
ret = drift;
goto resume;
}

/* Subtract nanosecond delta to find ktime when count was read */
delta = (u64)(u32)(now_count - count);
delta = div_u64(delta * NSEC_PER_SEC, vcpu->arch.count_hz);
count_time = ktime_sub_ns(now, delta);

resume:
/* Resume using the calculated ktime */
kvm_mips_resume_hrtimer(vcpu, count_time, count);
return ret;
}

/**
* kvm_mips_write_count() - Modify the count and update timer.
* @vcpu: Virtual CPU.
Expand Down Expand Up @@ -897,6 +973,7 @@ enum emulation_result kvm_mips_emul_wait(struct kvm_vcpu *vcpu)
++vcpu->stat.wait_exits;
trace_kvm_exit(vcpu, KVM_TRACE_EXIT_WAIT);
if (!vcpu->arch.pending_exceptions) {
kvm_vz_lose_htimer(vcpu);
vcpu->arch.wait = 1;
kvm_vcpu_block(vcpu);

Expand Down
6 changes: 5 additions & 1 deletion arch/mips/kvm/mips.c
Original file line number Diff line number Diff line change
Expand Up @@ -1094,7 +1094,8 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)

int kvm_cpu_has_pending_timer(struct kvm_vcpu *vcpu)
{
return kvm_mips_pending_timer(vcpu);
return kvm_mips_pending_timer(vcpu) ||
kvm_read_c0_guest_cause(vcpu->arch.cop0) & C_TI;
}

int kvm_arch_vcpu_dump_regs(struct kvm_vcpu *vcpu)
Expand Down Expand Up @@ -1382,6 +1383,9 @@ int kvm_mips_handle_exit(struct kvm_run *run, struct kvm_vcpu *vcpu)
skip_emul:
local_irq_disable();

if (ret == RESUME_GUEST)
kvm_vz_acquire_htimer(vcpu);

if (er == EMULATE_DONE && !(ret & RESUME_HOST))
kvm_mips_deliver_interrupts(vcpu, cause);

Expand Down
Loading

0 comments on commit f4474d5

Please sign in to comment.