From 0ead411665eed0883b475f8309e366d4f7e914e7 Mon Sep 17 00:00:00 2001 From: "kfraser@localhost.localdomain" Date: Tue, 1 Aug 2006 17:18:05 +0100 Subject: [XEN] Add hypercall support for HVM guests. This is fairly useless at the moment, since all of the hypercalls fail, since copy_from_user doesn't work correctly in HVM domains. Signed-off-by: Steven Smith Add a CPUID hypervisor platform interface at leaf 0x40000000. Allow hypercall transfer page to be filled in via MSR 0x40000000. Signed-off-by: Keir Fraser --- xen/arch/x86/dom0_ops.c | 2 +- xen/arch/x86/domain.c | 2 +- xen/arch/x86/domain_build.c | 2 +- xen/arch/x86/hvm/hvm.c | 60 ++++++++++++++++++++++++ xen/arch/x86/hvm/svm/svm.c | 39 +++++++++++++++- xen/arch/x86/hvm/vmx/vmx.c | 45 +++++++++++++++--- xen/arch/x86/traps.c | 97 +++++++++++++++++++++++++++++++++++++-- xen/arch/x86/x86_32/traps.c | 6 ++- xen/arch/x86/x86_64/traps.c | 10 +++- xen/include/asm-x86/domain.h | 2 +- xen/include/asm-x86/hvm/hvm.h | 5 ++ xen/include/asm-x86/hvm/support.h | 2 + xen/include/asm-x86/processor.h | 4 ++ 13 files changed, 256 insertions(+), 20 deletions(-) diff --git a/xen/arch/x86/dom0_ops.c b/xen/arch/x86/dom0_ops.c index 500c7f7ae6..e9c015fd0b 100644 --- a/xen/arch/x86/dom0_ops.c +++ b/xen/arch/x86/dom0_ops.c @@ -429,7 +429,7 @@ long arch_do_dom0_op(struct dom0_op *op, XEN_GUEST_HANDLE(dom0_op_t) u_dom0_op) ret = 0; hypercall_page = map_domain_page(mfn); - hypercall_page_initialise(hypercall_page); + hypercall_page_initialise(d, hypercall_page); unmap_domain_page(hypercall_page); put_page_and_type(mfn_to_page(mfn)); diff --git a/xen/arch/x86/domain.c b/xen/arch/x86/domain.c index 9eb1f53771..cdab497e43 100644 --- a/xen/arch/x86/domain.c +++ b/xen/arch/x86/domain.c @@ -819,7 +819,7 @@ unsigned long hypercall_create_continuation( #if defined(__i386__) regs->eax = op; - if ( supervisor_mode_kernel ) + if ( supervisor_mode_kernel || hvm_guest(current) ) regs->eip &= ~31; /* re-execute entire hypercall entry stub */ else regs->eip -= 2; /* re-execute 'int 0x82' */ diff --git a/xen/arch/x86/domain_build.c b/xen/arch/x86/domain_build.c index 78180898b7..8526d15365 100644 --- a/xen/arch/x86/domain_build.c +++ b/xen/arch/x86/domain_build.c @@ -704,7 +704,7 @@ int construct_dom0(struct domain *d, return -1; } - hypercall_page_initialise((void *)hypercall_page); + hypercall_page_initialise(d, (void *)hypercall_page); } /* Copy the initial ramdisk. */ diff --git a/xen/arch/x86/hvm/hvm.c b/xen/arch/x86/hvm/hvm.c index 6e0808e641..f4dee16ecc 100644 --- a/xen/arch/x86/hvm/hvm.c +++ b/xen/arch/x86/hvm/hvm.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -329,6 +330,65 @@ void hvm_print_line(struct vcpu *v, const char c) pbuf[(*index)++] = c; } +#if defined(__i386__) + +typedef unsigned long hvm_hypercall_t( + unsigned long, unsigned long, unsigned long, unsigned long, unsigned long); +#define HYPERCALL(x) [ __HYPERVISOR_ ## x ] = (hvm_hypercall_t *) do_ ## x +static hvm_hypercall_handler *hvm_hypercall_table[] = { + HYPERCALL(mmu_update), + HYPERCALL(memory_op), + HYPERCALL(multicall), + HYPERCALL(update_va_mapping), + HYPERCALL(event_channel_op_compat), + HYPERCALL(xen_version), + HYPERCALL(grant_table_op), + HYPERCALL(event_channel_op), + HYPERCALL(hvm_op) +}; +#undef HYPERCALL + +void hvm_do_hypercall(struct cpu_user_regs *pregs) +{ + if ( ring_3(pregs) ) + { + pregs->eax = -EPERM; + return; + } + + if ( pregs->eax > ARRAY_SIZE(hvm_hypercall_table) || + !hvm_hypercall_table[pregs->eax] ) + { + DPRINTK("HVM vcpu %d:%d did a bad hypercall %d.\n", + current->domain->domain_id, current->vcpu_id, + pregs->eax); + pregs->eax = -ENOSYS; + } + else + { + pregs->eax = hvm_hypercall_table[pregs->eax]( + pregs->ebx, pregs->ecx, pregs->edx, pregs->esi, pregs->edi); + } +} + +#else /* __x86_64__ */ + +void hvm_do_hypercall(struct cpu_user_regs *pregs) +{ + printk("not supported yet!\n"); +} + +#endif + +/* Initialise a hypercall transfer page for a VMX domain using + paravirtualised drivers. */ +void hvm_hypercall_page_initialise(struct domain *d, + void *hypercall_page) +{ + hvm_funcs.init_hypercall_page(d, hypercall_page); +} + + /* * only called in HVM domain BSP context * when booting, vcpuid is always equal to apic_id diff --git a/xen/arch/x86/hvm/svm/svm.c b/xen/arch/x86/hvm/svm/svm.c index 998611220e..9ee9ee5bf8 100644 --- a/xen/arch/x86/hvm/svm/svm.c +++ b/xen/arch/x86/hvm/svm/svm.c @@ -456,6 +456,28 @@ void svm_init_ap_context(struct vcpu_guest_context *ctxt, ctxt->flags = VGCF_HVM_GUEST; } +static void svm_init_hypercall_page(struct domain *d, void *hypercall_page) +{ + char *p; + int i; + + memset(hypercall_page, 0, PAGE_SIZE); + + for ( i = 0; i < (PAGE_SIZE / 32); i++ ) + { + p = (char *)(hypercall_page + (i * 32)); + *(u8 *)(p + 0) = 0xb8; /* mov imm32, %eax */ + *(u32 *)(p + 1) = i; + *(u8 *)(p + 5) = 0x0f; /* vmmcall */ + *(u8 *)(p + 6) = 0x01; + *(u8 *)(p + 7) = 0xd9; + *(u8 *)(p + 8) = 0xc3; /* ret */ + } + + /* Don't support HYPERVISOR_iret at the moment */ + *(u16 *)(hypercall_page + (__HYPERVISOR_iret * 32)) = 0x0b0f; /* ud2 */ +} + int start_svm(void) { u32 eax, ecx, edx; @@ -504,6 +526,8 @@ int start_svm(void) hvm_funcs.get_guest_ctrl_reg = svm_get_ctrl_reg; hvm_funcs.init_ap_context = svm_init_ap_context; + hvm_funcs.init_hypercall_page = svm_init_hypercall_page; + hvm_enabled = 1; return 1; @@ -1980,11 +2004,13 @@ static int svm_cr_access(struct vcpu *v, unsigned int cr, unsigned int type, return result; } -static inline void svm_do_msr_access(struct vcpu *v, struct cpu_user_regs *regs) +static inline void svm_do_msr_access( + struct vcpu *v, struct cpu_user_regs *regs) { struct vmcb_struct *vmcb = v->arch.hvm_svm.vmcb; int inst_len; u64 msr_content=0; + u32 eax, edx; ASSERT(vmcb); @@ -2018,6 +2044,14 @@ static inline void svm_do_msr_access(struct vcpu *v, struct cpu_user_regs *regs) default: if (long_mode_do_msr_read(regs)) goto done; + + if ( rdmsr_hypervisor_regs(regs->ecx, &eax, &edx) ) + { + regs->eax = eax; + regs->edx = edx; + goto done; + } + rdmsr_safe(regs->ecx, regs->eax, regs->edx); break; } @@ -2047,7 +2081,8 @@ static inline void svm_do_msr_access(struct vcpu *v, struct cpu_user_regs *regs) vlapic_msr_set(VLAPIC(v), msr_content); break; default: - long_mode_do_msr_write(regs); + if ( !long_mode_do_msr_write(regs) ) + wrmsr_hypervisor_regs(regs->ecx, regs->eax, regs->edx); break; } } diff --git a/xen/arch/x86/hvm/vmx/vmx.c b/xen/arch/x86/hvm/vmx/vmx.c index fe00954a0a..f8df7bbcf8 100644 --- a/xen/arch/x86/hvm/vmx/vmx.c +++ b/xen/arch/x86/hvm/vmx/vmx.c @@ -669,6 +669,28 @@ static int check_vmx_controls(u32 ctrls, u32 msr) return 1; } +static void vmx_init_hypercall_page(struct domain *d, void *hypercall_page) +{ + char *p; + int i; + + memset(hypercall_page, 0, PAGE_SIZE); + + for ( i = 0; i < (PAGE_SIZE / 32); i++ ) + { + p = (char *)(hypercall_page + (i * 32)); + *(u8 *)(p + 0) = 0xb8; /* mov imm32, %eax */ + *(u32 *)(p + 1) = i; + *(u8 *)(p + 5) = 0x0f; /* vmcall */ + *(u8 *)(p + 6) = 0x01; + *(u8 *)(p + 7) = 0xc1; + *(u8 *)(p + 8) = 0xc3; /* ret */ + } + + /* Don't support HYPERVISOR_iret at the moment */ + *(u16 *)(hypercall_page + (__HYPERVISOR_iret * 32)) = 0x0b0f; /* ud2 */ +} + int start_vmx(void) { u32 eax, edx; @@ -749,6 +771,8 @@ int start_vmx(void) hvm_funcs.init_ap_context = vmx_init_ap_context; + hvm_funcs.init_hypercall_page = vmx_init_hypercall_page; + hvm_enabled = 1; return 1; @@ -1877,6 +1901,7 @@ static int vmx_cr_access(unsigned long exit_qualification, struct cpu_user_regs static inline void vmx_do_msr_read(struct cpu_user_regs *regs) { u64 msr_content = 0; + u32 eax, edx; struct vcpu *v = current; HVM_DBG_LOG(DBG_LEVEL_1, "vmx_do_msr_read: ecx=%lx, eax=%lx, edx=%lx", @@ -1899,8 +1924,16 @@ static inline void vmx_do_msr_read(struct cpu_user_regs *regs) msr_content = VLAPIC(v) ? VLAPIC(v)->apic_base_msr : 0; break; default: - if(long_mode_do_msr_read(regs)) + if (long_mode_do_msr_read(regs)) return; + + if ( rdmsr_hypervisor_regs(regs->ecx, &eax, &edx) ) + { + regs->eax = eax; + regs->edx = edx; + return; + } + rdmsr_safe(regs->ecx, regs->eax, regs->edx); break; } @@ -1942,7 +1975,8 @@ static inline void vmx_do_msr_write(struct cpu_user_regs *regs) vlapic_msr_set(VLAPIC(v), msr_content); break; default: - long_mode_do_msr_write(regs); + if ( !long_mode_do_msr_write(regs) ) + wrmsr_hypervisor_regs(regs->ecx, regs->eax, regs->edx); break; } @@ -2273,16 +2307,16 @@ asmlinkage void vmx_vmexit_handler(struct cpu_user_regs regs) __update_guest_eip(inst_len); break; } -#if 0 /* keep this for debugging */ case EXIT_REASON_VMCALL: + { __get_instruction_length(inst_len); __vmread(GUEST_RIP, &eip); __vmread(EXIT_QUALIFICATION, &exit_qualification); - hvm_print_line(v, regs.eax); /* provides the current domain */ + hvm_do_hypercall(®s); __update_guest_eip(inst_len); break; -#endif + } case EXIT_REASON_CR_ACCESS: { __vmread(GUEST_RIP, &eip); @@ -2323,7 +2357,6 @@ asmlinkage void vmx_vmexit_handler(struct cpu_user_regs regs) case EXIT_REASON_MWAIT_INSTRUCTION: __hvm_bug(®s); break; - case EXIT_REASON_VMCALL: case EXIT_REASON_VMCLEAR: case EXIT_REASON_VMLAUNCH: case EXIT_REASON_VMPTRLD: diff --git a/xen/arch/x86/traps.c b/xen/arch/x86/traps.c index 44913b2f42..45d73bc511 100644 --- a/xen/arch/x86/traps.c +++ b/xen/arch/x86/traps.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -429,18 +430,95 @@ DO_ERROR_NOCODE(16, "fpu error", coprocessor_error) DO_ERROR(17, "alignment check", alignment_check) DO_ERROR_NOCODE(19, "simd error", simd_coprocessor_error) +int rdmsr_hypervisor_regs( + uint32_t idx, uint32_t *eax, uint32_t *edx) +{ + idx -= 0x40000000; + if ( idx > 0 ) + return 0; + + *eax = *edx = 0; + return 1; +} + +int wrmsr_hypervisor_regs( + uint32_t idx, uint32_t eax, uint32_t edx) +{ + struct domain *d = current->domain; + + idx -= 0x40000000; + if ( idx > 0 ) + return 0; + + switch ( idx ) + { + case 0: + { + void *hypercall_page; + unsigned long mfn; + unsigned long gmfn = ((unsigned long)edx << 20) | (eax >> 12); + unsigned int idx = eax & 0xfff; + + if ( idx > 0 ) + { + DPRINTK("Dom%d: Out of range index %u to MSR %08x\n", + d->domain_id, idx, 0x40000000); + return 0; + } + + mfn = gmfn_to_mfn(d, gmfn); + + if ( !mfn_valid(mfn) || + !get_page_and_type(mfn_to_page(mfn), d, PGT_writable_page) ) + { + DPRINTK("Dom%d: Bad GMFN %lx (MFN %lx) to MSR %08x\n", + d->domain_id, gmfn, mfn, 0x40000000); + return 0; + } + + hypercall_page = map_domain_page(mfn); + hypercall_page_initialise(d, hypercall_page); + unmap_domain_page(hypercall_page); + + put_page_and_type(mfn_to_page(mfn)); + break; + } + + default: + BUG(); + } + + return 1; +} + int cpuid_hypervisor_leaves( uint32_t idx, uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx) { - if ( (idx < 0x40000000) || (idx > 0x40000000) ) + idx -= 0x40000000; + if ( idx > 2 ) return 0; - switch ( idx - 0x40000000 ) + switch ( idx ) { case 0: - *eax = 0x40000000; - *ebx = 0x006e6558; /* "Xen\0" */ - *ecx = *edx = 0; + *eax = 0x40000002; /* Largest leaf */ + *ebx = 0x566e6558; /* Signature 1: "XenV" */ + *ecx = 0x65584d4d; /* Signature 2: "MMXe" */ + *edx = 0x4d4d566e; /* Signature 3: "nVMM" */ + break; + + case 1: + *eax = (xen_major_version() << 16) | xen_minor_version(); + *ebx = 0; /* Reserved */ + *ecx = 0; /* Reserved */ + *edx = 0; /* Reserved */ + break; + + case 2: + *eax = 1; /* Number of hypercall-transfer pages */ + *ebx = 0x40000000; /* MSR base address */ + *ecx = 0; /* Features 1 */ + *edx = 0; /* Features 2 */ break; default: @@ -1297,6 +1375,9 @@ static int emulate_privileged_op(struct cpu_user_regs *regs) break; #endif default: + if ( wrmsr_hypervisor_regs(regs->ecx, regs->eax, regs->edx) ) + break; + if ( (rdmsr_safe(regs->ecx, l, h) != 0) || (regs->eax != l) || (regs->edx != h) ) DPRINTK("Domain attempted WRMSR %p from " @@ -1328,6 +1409,12 @@ static int emulate_privileged_op(struct cpu_user_regs *regs) goto fail; break; default: + if ( rdmsr_hypervisor_regs(regs->ecx, &l, &h) ) + { + regs->eax = l; + regs->edx = h; + break; + } /* Everyone can read the MSR space. */ /*DPRINTK("Domain attempted RDMSR %p.\n", _p(regs->ecx));*/ if ( rdmsr_safe(regs->ecx, regs->eax, regs->edx) ) diff --git a/xen/arch/x86/x86_32/traps.c b/xen/arch/x86/x86_32/traps.c index ae18f654ad..3c6d81f21d 100644 --- a/xen/arch/x86/x86_32/traps.c +++ b/xen/arch/x86/x86_32/traps.c @@ -490,9 +490,11 @@ static void hypercall_page_initialise_ring1_kernel(void *hypercall_page) *(u16 *)(p+ 6) = 0x82cd; /* int $0x82 */ } -void hypercall_page_initialise(void *hypercall_page) +void hypercall_page_initialise(struct domain *d, void *hypercall_page) { - if ( supervisor_mode_kernel ) + if ( hvm_guest(d->vcpu[0]) ) + hvm_hypercall_page_initialise(d, hypercall_page); + else if ( supervisor_mode_kernel ) hypercall_page_initialise_ring0_kernel(hypercall_page); else hypercall_page_initialise_ring1_kernel(hypercall_page); diff --git a/xen/arch/x86/x86_64/traps.c b/xen/arch/x86/x86_64/traps.c index 3cae3e3fc3..6b674a5b53 100644 --- a/xen/arch/x86/x86_64/traps.c +++ b/xen/arch/x86/x86_64/traps.c @@ -432,7 +432,7 @@ long do_set_callbacks(unsigned long event_address, return 0; } -void hypercall_page_initialise(void *hypercall_page) +static void hypercall_page_initialise_ring3_kernel(void *hypercall_page) { char *p; int i; @@ -465,6 +465,14 @@ void hypercall_page_initialise(void *hypercall_page) *(u16 *)(p+ 9) = 0x050f; /* syscall */ } +void hypercall_page_initialise(struct domain *d, void *hypercall_page) +{ + if ( hvm_guest(d->vcpu[0]) ) + hvm_hypercall_page_initialise(d, hypercall_page); + else + hypercall_page_initialise_ring3_kernel(hypercall_page); +} + /* * Local variables: * mode: C diff --git a/xen/include/asm-x86/domain.h b/xen/include/asm-x86/domain.h index 962fa7954b..bdba98ba03 100644 --- a/xen/include/asm-x86/domain.h +++ b/xen/include/asm-x86/domain.h @@ -55,7 +55,7 @@ extern void toggle_guest_mode(struct vcpu *); * Initialise a hypercall-transfer page. The given pointer must be mapped * in Xen virtual address space (accesses are not validated or checked). */ -extern void hypercall_page_initialise(void *); +extern void hypercall_page_initialise(struct domain *d, void *); struct arch_domain { diff --git a/xen/include/asm-x86/hvm/hvm.h b/xen/include/asm-x86/hvm/hvm.h index 8e830977cc..04faa8971c 100644 --- a/xen/include/asm-x86/hvm/hvm.h +++ b/xen/include/asm-x86/hvm/hvm.h @@ -61,6 +61,8 @@ struct hvm_function_table { void (*init_ap_context)(struct vcpu_guest_context *ctxt, int vcpuid, int trampoline_vector); + + void (*init_hypercall_page)(struct domain *d, void *hypercall_page); }; extern struct hvm_function_table hvm_funcs; @@ -121,6 +123,9 @@ hvm_instruction_length(struct vcpu *v) return hvm_funcs.instruction_length(v); } +void hvm_hypercall_page_initialise(struct domain *d, + void *hypercall_page); + static inline unsigned long hvm_get_guest_ctrl_reg(struct vcpu *v, unsigned int num) { diff --git a/xen/include/asm-x86/hvm/support.h b/xen/include/asm-x86/hvm/support.h index 61ad981399..4534e66680 100644 --- a/xen/include/asm-x86/hvm/support.h +++ b/xen/include/asm-x86/hvm/support.h @@ -148,4 +148,6 @@ extern void hvm_hooks_assist(struct vcpu *v); extern void hvm_print_line(struct vcpu *v, const char c); extern void hlt_timer_fn(void *data); +void hvm_do_hypercall(struct cpu_user_regs *pregs); + #endif /* __ASM_X86_HVM_SUPPORT_H__ */ diff --git a/xen/include/asm-x86/processor.h b/xen/include/asm-x86/processor.h index 38c8330446..bc8d95b2c1 100644 --- a/xen/include/asm-x86/processor.h +++ b/xen/include/asm-x86/processor.h @@ -547,6 +547,10 @@ extern void mcheck_init(struct cpuinfo_x86 *c); int cpuid_hypervisor_leaves( uint32_t idx, uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx); +int rdmsr_hypervisor_regs( + uint32_t idx, uint32_t *eax, uint32_t *edx); +int wrmsr_hypervisor_regs( + uint32_t idx, uint32_t eax, uint32_t edx); #endif /* !__ASSEMBLY__ */ -- cgit v1.2.3