diff options
Diffstat (limited to 'xen/arch/x86/hvm/viridian.c')
-rw-r--r-- | xen/arch/x86/hvm/viridian.c | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/xen/arch/x86/hvm/viridian.c b/xen/arch/x86/hvm/viridian.c new file mode 100644 index 0000000000..ca3dd1378c --- /dev/null +++ b/xen/arch/x86/hvm/viridian.c @@ -0,0 +1,350 @@ +/****************************************************************************** + * viridian.c + * + * An implementation of the Viridian hypercall interface. + */ + +#include <xen/sched.h> +#include <xen/version.h> +#include <xen/perfc.h> +#include <xen/hypercall.h> +#include <xen/domain_page.h> +#include <asm/paging.h> +#include <asm/p2m.h> +#include <asm/hvm/support.h> +#include <public/sched.h> +#include <public/hvm/hvm_op.h> + +/* Viridian MSR numbers. */ +#define VIRIDIAN_MSR_GUEST_OS_ID 0x40000000 +#define VIRIDIAN_MSR_HYPERCALL 0x40000001 +#define VIRIDIAN_MSR_VP_INDEX 0x40000002 +#define VIRIDIAN_MSR_EOI 0x40000070 +#define VIRIDIAN_MSR_ICR 0x40000071 +#define VIRIDIAN_MSR_TPR 0x40000072 + +/* Viridian Hypercall Status Codes. */ +#define HV_STATUS_SUCCESS 0x0000 +#define HV_STATUS_INVALID_HYPERCALL_CODE 0x0002 + +/* Viridian Hypercall Codes and Parameters. */ +#define HvNotifyLongSpinWait 8 + +/* Viridian CPUID 4000003, Viridian MSR availability. */ +#define CPUID3A_MSR_APIC_ACCESS (1 << 4) +#define CPUID3A_MSR_HYPERCALL (1 << 5) +#define CPUID3A_MSR_VP_INDEX (1 << 6) + +/* Viridian CPUID 4000004, Implementation Recommendations. */ +#define CPUID4A_MSR_BASED_APIC (1 << 3) + +int cpuid_viridian_leaves(unsigned int leaf, unsigned int *eax, + unsigned int *ebx, unsigned int *ecx, + unsigned int *edx) +{ + struct domain *d = current->domain; + + if ( !is_viridian_domain(d) ) + return 0; + + leaf -= 0x40000000; + if ( leaf > 5 ) + return 0; + + *eax = *ebx = *ecx = *edx = 0; + switch ( leaf ) + { + case 0: + *eax = 0x40000005; /* Maximum leaf */ + *ebx = 0x7263694d; /* Magic numbers */ + *ecx = 0x666F736F; + *edx = 0x76482074; + break; + case 1: + *eax = 0x31237648; /* Version number */ + break; + case 2: + /* Hypervisor information, but only if the guest has set its + own version number. */ + if ( d->arch.hvm_domain.viridian.guest_os_id.raw == 0 ) + break; + *eax = 1; /* Build number */ + *ebx = (xen_major_version() << 16) | xen_minor_version(); + *ecx = 0; /* SP */ + *edx = 0; /* Service branch and number */ + break; + case 3: + /* Which hypervisor MSRs are available to the guest */ + *eax = (CPUID3A_MSR_APIC_ACCESS | + CPUID3A_MSR_HYPERCALL | + CPUID3A_MSR_VP_INDEX); + break; + case 4: + /* Recommended hypercall usage. */ + if ( (d->arch.hvm_domain.viridian.guest_os_id.raw == 0) || + (d->arch.hvm_domain.viridian.guest_os_id.fields.os < 4) ) + break; + *eax = CPUID4A_MSR_BASED_APIC; + *ebx = 2047; /* long spin count */ + break; + } + + return 1; +} + +static void enable_hypercall_page(void) +{ + struct domain *d = current->domain; + unsigned long gmfn = d->arch.hvm_domain.viridian.hypercall_gpa.fields.pfn; + unsigned long mfn = gmfn_to_mfn(d, gmfn); + uint8_t *p; + + if ( !mfn_valid(mfn) || + !get_page_and_type(mfn_to_page(mfn), d, PGT_writable_page) ) + { + gdprintk(XENLOG_WARNING, "Bad GMFN %lx (MFN %lx)\n", gmfn, mfn); + return; + } + + p = map_domain_page(mfn); + + /* + * We set the bit 31 in %eax (reserved field in the Viridian hypercall + * calling convention) to differentiate Xen and Viridian hypercalls. + */ + *(u8 *)(p + 0) = 0x0d; /* orl $0x80000000, %eax */ + *(u32 *)(p + 1) = 0x80000000; + *(u8 *)(p + 5) = 0x0f; /* vmcall/vmmcall */ + *(u8 *)(p + 6) = 0x01; + *(u8 *)(p + 7) = ((boot_cpu_data.x86_vendor == X86_VENDOR_INTEL) + ? 0xc1 : 0xd9); + *(u8 *)(p + 8) = 0xc3; /* ret */ + memset(p + 9, 0xcc, PAGE_SIZE - 9); /* int3, int3, ... */ + + unmap_domain_page(p); + + put_page_and_type(mfn_to_page(mfn)); +} + +int wrmsr_viridian_regs(uint32_t idx, uint32_t eax, uint32_t edx) +{ + struct domain *d = current->domain; + uint64_t val = ((uint64_t)edx << 32) | eax; + + if ( !is_viridian_domain(d) ) + return 0; + + switch ( idx ) + { + case VIRIDIAN_MSR_GUEST_OS_ID: + perfc_incr(mshv_wrmsr_osid); + d->arch.hvm_domain.viridian.guest_os_id.raw = val; + gdprintk(XENLOG_INFO, "Guest os:\n"); + gdprintk(XENLOG_INFO, "\tvendor: %x\n", + d->arch.hvm_domain.viridian.guest_os_id.fields.vendor); + gdprintk(XENLOG_INFO, "\tos: %x\n", + d->arch.hvm_domain.viridian.guest_os_id.fields.os); + gdprintk(XENLOG_INFO, "\tmajor: %x\n", + d->arch.hvm_domain.viridian.guest_os_id.fields.major); + gdprintk(XENLOG_INFO, "\tminor: %x\n", + d->arch.hvm_domain.viridian.guest_os_id.fields.minor); + gdprintk(XENLOG_INFO, "\tsp: %x\n", + d->arch.hvm_domain.viridian.guest_os_id.fields.service_pack); + gdprintk(XENLOG_INFO, "\tbuild: %x\n", + d->arch.hvm_domain.viridian.guest_os_id.fields.build_number); + break; + + case VIRIDIAN_MSR_HYPERCALL: + perfc_incr(mshv_wrmsr_hc_page); + gdprintk(XENLOG_INFO, "Set hypercall page %"PRIx64".\n", val); + if ( d->arch.hvm_domain.viridian.guest_os_id.raw == 0 ) + break; + d->arch.hvm_domain.viridian.hypercall_gpa.raw = val; + if ( d->arch.hvm_domain.viridian.hypercall_gpa.fields.enabled ) + enable_hypercall_page(); + break; + + case VIRIDIAN_MSR_VP_INDEX: + perfc_incr(mshv_wrmsr_vp_index); + gdprintk(XENLOG_INFO, "Set VP index %"PRIu64".\n", val); + break; + + case VIRIDIAN_MSR_EOI: + perfc_incr(mshv_wrmsr_eoi); + vlapic_EOI_set(vcpu_vlapic(current)); + break; + + case VIRIDIAN_MSR_ICR: { + struct vlapic *vlapic = vcpu_vlapic(current); + perfc_incr(mshv_wrmsr_icr); + eax &= ~(1 << 12); + edx &= 0xff000000; + vlapic_set_reg(vlapic, APIC_ICR2, edx); + if ( vlapic_ipi(vlapic, eax, edx) == X86EMUL_OKAY ) + vlapic_set_reg(vlapic, APIC_ICR, eax); + break; + } + + case VIRIDIAN_MSR_TPR: + perfc_incr(mshv_wrmsr_tpr); + vlapic_set_reg(vcpu_vlapic(current), APIC_TASKPRI, eax & 0xff); + break; + + default: + return 0; + } + + return 1; +} + +int rdmsr_viridian_regs(uint32_t idx, uint32_t *eax, uint32_t *edx) +{ + uint64_t val; + struct vcpu *v = current; + + if ( !is_viridian_domain(v->domain) ) + return 0; + + switch ( idx ) + { + case VIRIDIAN_MSR_GUEST_OS_ID: + perfc_incr(mshv_rdmsr_osid); + val = v->domain->arch.hvm_domain.viridian.guest_os_id.raw; + break; + + case VIRIDIAN_MSR_HYPERCALL: + perfc_incr(mshv_rdmsr_hc_page); + val = v->domain->arch.hvm_domain.viridian.hypercall_gpa.raw; + break; + + case VIRIDIAN_MSR_VP_INDEX: + perfc_incr(mshv_rdmsr_vp_index); + val = v->vcpu_id; + break; + + case VIRIDIAN_MSR_ICR: + perfc_incr(mshv_rdmsr_icr); + val = (((uint64_t)vlapic_get_reg(vcpu_vlapic(v), APIC_ICR2) << 32) | + vlapic_get_reg(vcpu_vlapic(v), APIC_ICR)); + break; + + case VIRIDIAN_MSR_TPR: + perfc_incr(mshv_rdmsr_tpr); + val = vlapic_get_reg(vcpu_vlapic(v), APIC_TASKPRI); + break; + + default: + return 0; + } + + *eax = val; + *edx = val >> 32; + return 1; +} + +int viridian_hypercall(struct cpu_user_regs *regs) +{ + struct domain *d = current->domain; + int mode = hvm_guest_x86_mode(current); + unsigned long input_params_gpa, output_params_gpa; + uint16_t status = HV_STATUS_SUCCESS; + + union hypercall_input { + uint64_t raw; + struct { + uint16_t call_code; + uint16_t rsvd1; + unsigned rep_count:12; + unsigned rsvd2:4; + unsigned rep_start:12; + unsigned rsvd3:4; + }; + } input; + + union hypercall_output { + uint64_t raw; + struct { + uint16_t result; + uint16_t rsvd1; + unsigned rep_complete:12; + unsigned rsvd2:20; + }; + } output = { 0 }; + + ASSERT(is_viridian_domain(d)); + + switch ( mode ) + { +#ifdef __x86_64__ + case 8: + input.raw = regs->rcx; + input_params_gpa = regs->rdx; + output_params_gpa = regs->r8; + break; +#endif + case 4: + input.raw = ((uint64_t)regs->edx << 32) | regs->eax; + input_params_gpa = ((uint64_t)regs->ebx << 32) | regs->ecx; + output_params_gpa = ((uint64_t)regs->edi << 32) | regs->esi; + break; + default: + goto out; + } + + switch ( input.call_code ) + { + case HvNotifyLongSpinWait: + perfc_incr(mshv_call_long_wait); + do_sched_op_compat(SCHEDOP_yield, 0); + status = HV_STATUS_SUCCESS; + break; + default: + status = HV_STATUS_INVALID_HYPERCALL_CODE; + break; + } + +out: + output.result = status; + switch (mode) { +#ifdef __x86_64__ + case 8: + regs->rax = output.raw; + break; +#endif + default: + regs->edx = output.raw >> 32; + regs->eax = output.raw; + break; + } + + return HVM_HCALL_completed; +} + +static int viridian_save_cpu_ctxt(struct domain *d, hvm_domain_context_t *h) +{ + struct hvm_viridian_context ctxt; + + if ( !is_viridian_domain(d) ) + return 0; + + ctxt.hypercall_gpa = d->arch.hvm_domain.viridian.hypercall_gpa.raw; + ctxt.guest_os_id = d->arch.hvm_domain.viridian.guest_os_id.raw; + + return (hvm_save_entry(VIRIDIAN, 0, h, &ctxt) != 0); +} + +static int viridian_load_cpu_ctxt(struct domain *d, hvm_domain_context_t *h) +{ + struct hvm_viridian_context ctxt; + + if ( hvm_load_entry(VIRIDIAN, h, &ctxt) != 0 ) + return -EINVAL; + + d->arch.hvm_domain.viridian.hypercall_gpa.raw = ctxt.hypercall_gpa; + d->arch.hvm_domain.viridian.guest_os_id.raw = ctxt.guest_os_id; + + return 0; +} + +HVM_REGISTER_SAVE_RESTORE(VIRIDIAN, viridian_save_cpu_ctxt, + viridian_load_cpu_ctxt, 1, HVMSR_PER_DOM); |