From 1d194ede6159bdb7c310231bcdaa97b7d400c62c Mon Sep 17 00:00:00 2001 From: Jian Jun Chen Date: Tue, 15 Oct 2019 10:50:34 +0800 Subject: [PATCH] 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 Acked-by: Anthony Xu --- hypervisor/arch/x86/guest/hyperv.c | 125 ++++++++++++++++++++- hypervisor/arch/x86/guest/vmsr.c | 4 + hypervisor/include/arch/x86/guest/hyperv.h | 15 +++ 3 files changed, 143 insertions(+), 1 deletion(-) diff --git a/hypervisor/arch/x86/guest/hyperv.c b/hypervisor/arch/x86/guest/hyperv.c index 442433f4b..9dfa04c4d 100644 --- a/hypervisor/arch/x86/guest/hyperv.c +++ b/hypervisor/arch/x86/guest/hyperv.c @@ -10,14 +10,124 @@ #include #include #include +#include #include #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; diff --git a/hypervisor/arch/x86/guest/vmsr.c b/hypervisor/arch/x86/guest/vmsr.c index f2b069ffd..b414e8621 100644 --- a/hypervisor/arch/x86/guest/vmsr.c +++ b/hypervisor/arch/x86/guest/vmsr.c @@ -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; diff --git a/hypervisor/include/arch/x86/guest/hyperv.h b/hypervisor/include/arch/x86/guest/hyperv.h index 7dfc35c7f..32515748b 100644 --- a/hypervisor/include/arch/x86/guest/hyperv.h +++ b/hypervisor/include/arch/x86/guest/hyperv.h @@ -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);