hv: support reference time enlightenment
Two time related synthetic MSRs are implemented in this patch. Both of them are partition wide MSR. - HV_X64_MSR_TIME_REF_COUNT is read only and it is used to return the partition's reference counter value in 100ns units. - HV_X64_MSR_REFERENCE_TSC is used to set/get the reference TSC page, a sequence number, an offset and a multiplier are defined in this page by hypervisor and guest OS can use them to calculate the normalized reference time since partition creation, in 100ns units. Tracked-On: #3831 Signed-off-by: Jian Jun Chen <jian.jun.chen@intel.com> Acked-by: Anthony Xu <anthony.xu@intel.com>
This commit is contained in:
parent
048155d3d6
commit
1d194ede61
|
@ -10,14 +10,124 @@
|
|||
#include <types.h>
|
||||
#include <vm.h>
|
||||
#include <logmsg.h>
|
||||
#include <vmx.h>
|
||||
#include <hyperv.h>
|
||||
|
||||
#define ACRN_DBG_HYPERV 6U
|
||||
|
||||
/* Partition Reference Counter (HV_X64_MSR_TIME_REF_COUNT) */
|
||||
#define CPUID3A_TIME_REF_COUNT_MSR (1U << 1U)
|
||||
/* Hypercall MSRs (HV_X64_MSR_GUEST_OS_ID and HV_X64_MSR_HYPERCALL) */
|
||||
#define CPUID3A_HYPERCALL_MSR (1U << 5U)
|
||||
/* Access virtual processor index MSR (HV_X64_MSR_VP_INDEX) */
|
||||
#define CPUID3A_VP_INDEX_MSR (1U << 6U)
|
||||
/* Partition reference TSC MSR (HV_X64_MSR_REFERENCE_TSC) */
|
||||
#define CPUID3A_REFERENCE_TSC_MSR (1U << 9U)
|
||||
|
||||
struct HV_REFERENCE_TSC_PAGE {
|
||||
uint32_t tsc_sequence;
|
||||
uint32_t reserved1;
|
||||
uint64_t tsc_scale;
|
||||
uint64_t tsc_offset;
|
||||
uint64_t reserved2[509];
|
||||
};
|
||||
|
||||
static inline uint64_t
|
||||
u64_shl64_div_u64(uint64_t a, uint64_t divisor)
|
||||
{
|
||||
uint64_t ret, tmp;
|
||||
|
||||
asm volatile ("divq %2" :
|
||||
"=a" (ret), "=d" (tmp) :
|
||||
"rm" (divisor), "0" (0U), "1" (a));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline uint64_t
|
||||
u64_mul_u64_shr64(uint64_t a, uint64_t b)
|
||||
{
|
||||
uint64_t ret, disc;
|
||||
|
||||
asm volatile ("mulq %3" :
|
||||
"=d" (ret), "=a" (disc) :
|
||||
"a" (a), "r" (b));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline void
|
||||
hyperv_get_tsc_scale_offset(struct acrn_vm *vm, uint64_t *scale, uint64_t *offset)
|
||||
{
|
||||
if (vm->arch_vm.hyperv.tsc_scale == 0UL) {
|
||||
/*
|
||||
* The partition reference time is computed by the following formula:
|
||||
* ReferenceTime = ((VirtualTsc * TscScale) >> 64) + TscOffset
|
||||
* ReferenceTime is in 100ns units
|
||||
*
|
||||
* ReferenceTime =
|
||||
* VirtualTsc / (get_tsc_khz() * 1000) * 1000000000 / 100
|
||||
* + TscOffset
|
||||
*/
|
||||
|
||||
uint64_t ret, khz = get_tsc_khz();
|
||||
|
||||
/* ret = (10000U << 64U) / get_tsc_khz() */
|
||||
ret = u64_shl64_div_u64(10000U, khz);
|
||||
|
||||
dev_dbg(ACRN_DBG_HYPERV, "%s, ret = 0x%lx", __func__, ret);
|
||||
|
||||
vm->arch_vm.hyperv.tsc_scale = ret;
|
||||
vm->arch_vm.hyperv.tsc_offset = 0UL;
|
||||
}
|
||||
|
||||
*scale = vm->arch_vm.hyperv.tsc_scale;
|
||||
*offset = vm->arch_vm.hyperv.tsc_offset;
|
||||
}
|
||||
|
||||
static void
|
||||
hyperv_setup_tsc_page(const struct acrn_vcpu *vcpu, uint64_t val)
|
||||
{
|
||||
union hyperv_ref_tsc_page_msr *ref_tsc_page = &vcpu->vm->arch_vm.hyperv.ref_tsc_page;
|
||||
struct HV_REFERENCE_TSC_PAGE *p;
|
||||
uint64_t tsc_scale, tsc_offset;
|
||||
uint32_t tsc_seq;
|
||||
|
||||
ref_tsc_page->val64 = val;
|
||||
|
||||
if (ref_tsc_page->enabled == 1U) {
|
||||
p = (struct HV_REFERENCE_TSC_PAGE *)gpa2hva(vcpu->vm, ref_tsc_page->gpfn << PAGE_SHIFT);
|
||||
if (p != NULL) {
|
||||
hyperv_get_tsc_scale_offset(vcpu->vm, &tsc_scale, &tsc_offset);
|
||||
stac();
|
||||
p->tsc_scale = tsc_scale;
|
||||
p->tsc_offset = tsc_offset;
|
||||
cpu_write_memory_barrier();
|
||||
tsc_seq = p->tsc_sequence + 1U;
|
||||
if ((tsc_seq == 0xFFFFFFFFU) || (tsc_seq == 0U)) {
|
||||
tsc_seq = 1U;
|
||||
}
|
||||
p->tsc_sequence = tsc_seq;
|
||||
clac();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint64_t
|
||||
hyperv_get_ref_count(struct acrn_vm *vm)
|
||||
{
|
||||
uint64_t tsc, tsc_scale, tsc_offset, ret;
|
||||
|
||||
/* currently only "use tsc offsetting" is set to 1 */
|
||||
tsc = rdtsc() + exec_vmread64(VMX_TSC_OFFSET_FULL);
|
||||
|
||||
hyperv_get_tsc_scale_offset(vm, &tsc_scale, &tsc_offset);
|
||||
|
||||
/* ret = ((tsc * tsc_scale) >> 64) + tsc_offset */
|
||||
ret = u64_mul_u64_shr64(tsc, tsc_scale) + tsc_offset;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
hyperv_setup_hypercall_page(const struct acrn_vcpu *vcpu, uint64_t val)
|
||||
|
@ -66,6 +176,12 @@ hyperv_wrmsr(struct acrn_vcpu *vcpu, uint32_t msr, uint64_t wval)
|
|||
case HV_X64_MSR_VP_INDEX:
|
||||
/* read only */
|
||||
break;
|
||||
case HV_X64_MSR_TIME_REF_COUNT:
|
||||
/* read only */
|
||||
break;
|
||||
case HV_X64_MSR_REFERENCE_TSC:
|
||||
hyperv_setup_tsc_page(vcpu, wval);
|
||||
break;
|
||||
default:
|
||||
pr_err("hv: %s: unexpected MSR[0x%x] write", __func__, msr);
|
||||
ret = -1;
|
||||
|
@ -93,6 +209,12 @@ hyperv_rdmsr(struct acrn_vcpu *vcpu, uint32_t msr, uint64_t *rval)
|
|||
case HV_X64_MSR_VP_INDEX:
|
||||
*rval = vcpu->vcpu_id;
|
||||
break;
|
||||
case HV_X64_MSR_TIME_REF_COUNT:
|
||||
*rval = hyperv_get_ref_count(vcpu->vm);
|
||||
break;
|
||||
case HV_X64_MSR_REFERENCE_TSC:
|
||||
*rval = vcpu->vm->arch_vm.hyperv.ref_tsc_page.val64;
|
||||
break;
|
||||
default:
|
||||
pr_err("hv: %s: unexpected MSR[0x%x] read", __func__, msr);
|
||||
ret = -1;
|
||||
|
@ -127,7 +249,8 @@ hyperv_init_vcpuid_entry(uint32_t leaf, uint32_t subleaf, uint32_t flags,
|
|||
entry->edx = 0U;
|
||||
break;
|
||||
case 0x40000003U: /* HV supported feature */
|
||||
entry->eax = CPUID3A_HYPERCALL_MSR | CPUID3A_VP_INDEX_MSR;
|
||||
entry->eax = CPUID3A_HYPERCALL_MSR | CPUID3A_VP_INDEX_MSR |
|
||||
CPUID3A_TIME_REF_COUNT_MSR | CPUID3A_REFERENCE_TSC_MSR;
|
||||
entry->ebx = 0U;
|
||||
entry->ecx = 0U;
|
||||
entry->edx = 0U;
|
||||
|
|
|
@ -401,6 +401,8 @@ int32_t rdmsr_vmexit_handler(struct acrn_vcpu *vcpu)
|
|||
case HV_X64_MSR_GUEST_OS_ID:
|
||||
case HV_X64_MSR_HYPERCALL:
|
||||
case HV_X64_MSR_VP_INDEX:
|
||||
case HV_X64_MSR_REFERENCE_TSC:
|
||||
case HV_X64_MSR_TIME_REF_COUNT:
|
||||
{
|
||||
err = hyperv_rdmsr(vcpu, msr, &v);
|
||||
break;
|
||||
|
@ -689,6 +691,8 @@ int32_t wrmsr_vmexit_handler(struct acrn_vcpu *vcpu)
|
|||
case HV_X64_MSR_GUEST_OS_ID:
|
||||
case HV_X64_MSR_HYPERCALL:
|
||||
case HV_X64_MSR_VP_INDEX:
|
||||
case HV_X64_MSR_REFERENCE_TSC:
|
||||
case HV_X64_MSR_TIME_REF_COUNT:
|
||||
{
|
||||
err = hyperv_wrmsr(vcpu, msr, v);
|
||||
break;
|
||||
|
|
|
@ -14,6 +14,18 @@
|
|||
#define HV_X64_MSR_HYPERCALL 0x40000001U
|
||||
#define HV_X64_MSR_VP_INDEX 0x40000002U
|
||||
|
||||
#define HV_X64_MSR_TIME_REF_COUNT 0x40000020U
|
||||
#define HV_X64_MSR_REFERENCE_TSC 0x40000021U
|
||||
|
||||
union hyperv_ref_tsc_page_msr {
|
||||
uint64_t val64;
|
||||
struct {
|
||||
uint64_t enabled:1;
|
||||
uint64_t rsvdp:11;
|
||||
uint64_t gpfn:52;
|
||||
};
|
||||
};
|
||||
|
||||
union hyperv_hypercall_msr {
|
||||
uint64_t val64;
|
||||
struct {
|
||||
|
@ -40,6 +52,9 @@ union hyperv_guest_os_id_msr {
|
|||
struct acrn_hyperv {
|
||||
union hyperv_hypercall_msr hypercall_page;
|
||||
union hyperv_guest_os_id_msr guest_os_id;
|
||||
union hyperv_ref_tsc_page_msr ref_tsc_page;
|
||||
uint64_t tsc_scale;
|
||||
uint64_t tsc_offset;
|
||||
};
|
||||
|
||||
int32_t hyperv_wrmsr(struct acrn_vcpu *vcpu, uint32_t msr, uint64_t wval);
|
||||
|
|
Loading…
Reference in New Issue