| /* Copyright 2015 Google Inc. |
| * |
| * See LICENSE for details. |
| */ |
| |
| /* We're not going to falll into the trap of only compiling support |
| * for AMD OR Intel for an image. It all gets compiled in, and which |
| * one you use depends on on cpuinfo, not a compile-time |
| * switch. That's proven to be the best strategy. Conditionally |
| * compiling in support is the path to hell. |
| */ |
| #include <assert.h> |
| #include <pmap.h> |
| #include <smp.h> |
| #include <kmalloc.h> |
| |
| #include <ros/vmm.h> |
| #include "intel/vmx.h" |
| #include "vmm.h" |
| #include <trap.h> |
| #include <umem.h> |
| |
| #include <arch/x86.h> |
| #include <ros/procinfo.h> |
| |
| |
| /* TODO: have better cpuid info storage and checks */ |
| bool x86_supports_vmx = FALSE; |
| |
| /* Figure out what kind of CPU we are on, and if it supports any reasonable |
| * virtualization. For now, if we're not some sort of newer intel, don't |
| * bother. This does all cores. Again, note, we make these decisions at runtime, |
| * to avoid getting into the problems that compile-time decisions can cause. |
| * At this point, of course, it's still all intel. |
| */ |
| void vmm_init(void) |
| { |
| int ret; |
| /* Check first for intel capabilities. This is hence two back-to-back |
| * implementationd-dependent checks. That's ok, it's all msr dependent. |
| */ |
| |
| ret = intel_vmm_init(); |
| if (! ret) { |
| x86_supports_vmx = TRUE; |
| return; |
| } |
| |
| /* TODO: AMD. Will we ever care? It's not clear. */ |
| printk("vmm_init failed, ret %d\n", ret); |
| return; |
| } |
| |
| void vmm_pcpu_init(void) |
| { |
| struct per_cpu_info *pcpui = &per_cpu_info[core_id()]; |
| |
| pcpui->guest_pcoreid = -1; |
| if (!x86_supports_vmx) |
| return; |
| if (! intel_vmm_pcpu_init()) { |
| printd("vmm_pcpu_init worked\n"); |
| return; |
| } |
| /* TODO: AMD. Will we ever care? It's not clear. */ |
| printk("vmm_pcpu_init failed\n"); |
| } |
| |
| /* Ensures a process is ready to run virtual machines, though it may have no |
| * guest pcores yet. Typically, this is called by other vmm functions. Caller |
| * holds the qlock. Throws on error. */ |
| void __vmm_struct_init(struct proc *p) |
| { |
| struct vmm *vmm = &p->vmm; |
| |
| if (vmm->vmmcp) |
| return; |
| if (!x86_supports_vmx) |
| error(ENODEV, "This CPU does not support VMX"); |
| vmm->vmmcp = TRUE; |
| vmm->amd = 0; |
| vmx_setup_vmx_vmm(&vmm->vmx); |
| for (int i = 0; i < VMM_VMEXIT_NR_TYPES; i++) |
| vmm->vmexits[i] = 0; |
| vmm->nr_guest_pcores = 0; |
| vmm->guest_pcores = NULL; |
| vmm->gpc_array_elem = 0; |
| } |
| |
| /* Helper, grows the array of guest_pcores in vmm. Concurrent readers |
| * (lookup_guest_pcore) need to use a seq-lock-style of concurrency. They could |
| * read the old array even after we free it. */ |
| static void __vmm_grow_gpc_array(struct vmm *vmm, unsigned int new_nr_gpcs) |
| { |
| struct guest_pcore **new_array, **old_array; |
| size_t new_nr_elem; |
| |
| if (new_nr_gpcs <= vmm->gpc_array_elem) |
| return; |
| /* TODO: (RCU) we could defer the free, maybe with an RCU-safe krealloc. |
| */ |
| old_array = vmm->guest_pcores; |
| new_nr_elem = MAX(vmm->gpc_array_elem * 2, new_nr_gpcs); |
| new_array = kzmalloc(new_nr_elem * sizeof(void*), MEM_WAIT); |
| memcpy(new_array, vmm->guest_pcores, |
| sizeof(void*) * vmm->nr_guest_pcores); |
| wmb(); /* all elements written before changing pointer */ |
| vmm->guest_pcores = new_array; |
| wmb(); /* ptr written before potentially clobbering it. */ |
| kfree(old_array); |
| } |
| |
| /* Adds gpcs to the VMM. Caller holds the qlock; throws on error. */ |
| void __vmm_add_gpcs(struct proc *p, unsigned int nr_more_gpcs, |
| struct vmm_gpcore_init *u_gpcis) |
| { |
| struct vmm *vmm = &p->vmm; |
| struct vmm_gpcore_init gpci; |
| unsigned int new_nr_gpcs; |
| |
| if (!nr_more_gpcs) |
| return; |
| new_nr_gpcs = vmm->nr_guest_pcores + nr_more_gpcs; |
| if ((new_nr_gpcs < vmm->nr_guest_pcores) || (new_nr_gpcs > 10000)) |
| error(EINVAL, "Can't add %u new gpcs", new_nr_gpcs); |
| __vmm_grow_gpc_array(vmm, new_nr_gpcs); |
| for (int i = 0; i < nr_more_gpcs; i++) { |
| if (copy_from_user(&gpci, &u_gpcis[i], |
| sizeof(struct vmm_gpcore_init))) |
| error(EINVAL, "Bad pointer %p for gps", u_gpcis); |
| vmm->guest_pcores[vmm->nr_guest_pcores] = |
| create_guest_pcore(p, &gpci); |
| /* concurrent readers will check nr_guest_pcores first */ |
| wmb(); |
| vmm->nr_guest_pcores++; |
| } |
| } |
| |
| /* Has no concurrency protection - only call this when you know you have the |
| * only ref to vmm. For instance, from __proc_free, where there is only one ref |
| * to the proc (and thus proc.vmm). */ |
| void __vmm_struct_cleanup(struct proc *p) |
| { |
| struct vmm *vmm = &p->vmm; |
| |
| if (!vmm->vmmcp) |
| return; |
| for (int i = 0; i < vmm->nr_guest_pcores; i++) { |
| if (vmm->guest_pcores[i]) |
| destroy_guest_pcore(vmm->guest_pcores[i]); |
| } |
| kfree(vmm->guest_pcores); |
| ept_flush(p->env_pgdir.eptp); |
| vmm->vmmcp = FALSE; |
| } |
| |
| int vmm_poke_guest(struct proc *p, int guest_pcoreid) |
| { |
| struct guest_pcore *gpc; |
| int pcoreid; |
| |
| gpc = lookup_guest_pcore(p, guest_pcoreid); |
| if (!gpc) { |
| set_error(ENOENT, "Bad guest_pcoreid %d", guest_pcoreid); |
| return -1; |
| } |
| /* We're doing an unlocked peek; it could change immediately. This is a |
| * best effort service. */ |
| pcoreid = ACCESS_ONCE(gpc->cpu); |
| if (pcoreid == -1) { |
| /* So we know that we'll miss the poke for the posted IRQ. We |
| * could return an error. However, error handling for this case |
| * isn't particularly helpful (yet). The absence of the error |
| * does not mean the IRQ was posted. We'll still return 0, |
| * meaning "the user didn't mess up; we tried." */ |
| return 0; |
| } |
| send_ipi(pcoreid, I_POKE_GUEST); |
| return 0; |
| } |
| |
| struct guest_pcore *lookup_guest_pcore(struct proc *p, int guest_pcoreid) |
| { |
| struct guest_pcore **array; |
| struct guest_pcore *ret; |
| |
| if (guest_pcoreid < 0) |
| return NULL; |
| /* nr_guest_pcores is written once at setup and never changed */ |
| if (guest_pcoreid >= p->vmm.nr_guest_pcores) |
| return NULL; |
| /* TODO: (RCU) Synchronizing with __vmm_grow_gpc_array() */ |
| do { |
| array = ACCESS_ONCE(p->vmm.guest_pcores); |
| ret = array[guest_pcoreid]; |
| rmb(); /* read ret before rereading array pointer */ |
| } while (array != ACCESS_ONCE(p->vmm.guest_pcores)); |
| return ret; |
| } |
| |
| struct guest_pcore *load_guest_pcore(struct proc *p, int guest_pcoreid) |
| { |
| struct guest_pcore *gpc; |
| struct per_cpu_info *pcpui = &per_cpu_info[core_id()]; |
| |
| gpc = lookup_guest_pcore(p, guest_pcoreid); |
| if (!gpc) |
| return 0; |
| assert(pcpui->guest_pcoreid == -1); |
| spin_lock(&p->vmm.lock); |
| if (gpc->cpu != -1) { |
| spin_unlock(&p->vmm.lock); |
| return 0; |
| } |
| gpc->cpu = core_id(); |
| spin_unlock(&p->vmm.lock); |
| /* We've got dibs on the gpc; we don't need to hold the lock any longer. |
| */ |
| pcpui->guest_pcoreid = guest_pcoreid; |
| vmx_load_guest_pcore(gpc); |
| /* Load guest's xcr0 */ |
| lxcr0(gpc->xcr0); |
| |
| /* Manual MSR save/restore */ |
| write_kern_gsbase(gpc->msr_kern_gs_base); |
| if (gpc->msr_star != AKAROS_MSR_STAR) |
| write_msr(MSR_STAR, gpc->msr_star); |
| if (gpc->msr_lstar != AKAROS_MSR_LSTAR) |
| write_msr(MSR_LSTAR, gpc->msr_lstar); |
| if (gpc->msr_sfmask != AKAROS_MSR_SFMASK) |
| write_msr(MSR_SFMASK, gpc->msr_sfmask); |
| |
| return gpc; |
| } |
| |
| void unload_guest_pcore(struct proc *p, int guest_pcoreid) |
| { |
| struct guest_pcore *gpc; |
| struct per_cpu_info *pcpui = &per_cpu_info[core_id()]; |
| |
| gpc = lookup_guest_pcore(p, guest_pcoreid); |
| assert(gpc); |
| spin_lock(&p->vmm.lock); |
| assert(gpc->cpu != -1); |
| vmx_unload_guest_pcore(gpc); |
| gpc->cpu = -1; |
| |
| /* Save guest's xcr0 and restore Akaros's default. */ |
| gpc->xcr0 = rxcr0(); |
| lxcr0(__proc_global_info.x86_default_xcr0); |
| |
| /* We manage these MSRs manually. */ |
| gpc->msr_kern_gs_base = read_kern_gsbase(); |
| gpc->msr_star = read_msr(MSR_STAR); |
| gpc->msr_lstar = read_msr(MSR_LSTAR); |
| gpc->msr_sfmask = read_msr(MSR_SFMASK); |
| |
| write_kern_gsbase((uint64_t)pcpui); |
| if (gpc->msr_star != AKAROS_MSR_STAR) |
| write_msr(MSR_STAR, AKAROS_MSR_STAR); |
| if (gpc->msr_lstar != AKAROS_MSR_LSTAR) |
| write_msr(MSR_LSTAR, AKAROS_MSR_LSTAR); |
| if (gpc->msr_sfmask, AKAROS_MSR_SFMASK) |
| write_msr(MSR_SFMASK, AKAROS_MSR_SFMASK); |
| |
| /* As soon as we unlock, this gpc can be started on another core */ |
| spin_unlock(&p->vmm.lock); |
| pcpui->guest_pcoreid = -1; |
| } |
| |
| /* emulated msr. For now, an msr value and a pointer to a helper that |
| * performs the requested operation. |
| */ |
| struct emmsr { |
| uint32_t reg; |
| char *name; |
| bool (*f)(struct emmsr *msr, struct vm_trapframe *vm_tf, |
| uint32_t opcode); |
| bool written; |
| uint32_t edx, eax; |
| }; |
| |
| static bool emsr_miscenable(struct emmsr *msr, struct vm_trapframe *vm_tf, |
| uint32_t opcode); |
| static bool emsr_readonly(struct emmsr *msr, struct vm_trapframe *vm_tf, |
| uint32_t opcode); |
| static bool emsr_readzero(struct emmsr *msr, struct vm_trapframe *vm_tf, |
| uint32_t opcode); |
| static bool emsr_fakewrite(struct emmsr *msr, struct vm_trapframe *vm_tf, |
| uint32_t opcode); |
| static bool emsr_ok(struct emmsr *msr, struct vm_trapframe *vm_tf, |
| uint32_t opcode); |
| static bool emsr_fake_apicbase(struct emmsr *msr, struct vm_trapframe *vm_tf, |
| uint32_t opcode); |
| static bool emsr_lapic_icr(struct emmsr *msr, struct vm_trapframe *vm_tf, |
| uint32_t opcode); |
| |
| struct emmsr emmsrs[] = { |
| {MSR_LAPIC_ICR, "MSR_LAPIC_ICR", emsr_lapic_icr}, |
| {MSR_IA32_MISC_ENABLE, "MSR_IA32_MISC_ENABLE", emsr_miscenable}, |
| {MSR_IA32_SYSENTER_CS, "MSR_IA32_SYSENTER_CS", emsr_ok}, |
| {MSR_IA32_SYSENTER_EIP, "MSR_IA32_SYSENTER_EIP", emsr_ok}, |
| {MSR_IA32_SYSENTER_ESP, "MSR_IA32_SYSENTER_ESP", emsr_ok}, |
| {MSR_IA32_UCODE_REV, "MSR_IA32_UCODE_REV", emsr_fakewrite}, |
| {MSR_CSTAR, "MSR_CSTAR", emsr_fakewrite}, |
| {MSR_IA32_VMX_BASIC_MSR, "MSR_IA32_VMX_BASIC_MSR", emsr_fakewrite}, |
| {MSR_IA32_VMX_PINBASED_CTLS_MSR, "MSR_IA32_VMX_PINBASED_CTLS_MSR", |
| emsr_fakewrite}, |
| {MSR_IA32_VMX_PROCBASED_CTLS_MSR, "MSR_IA32_VMX_PROCBASED_CTLS_MSR", |
| emsr_fakewrite}, |
| {MSR_IA32_VMX_PROCBASED_CTLS2, "MSR_IA32_VMX_PROCBASED_CTLS2", |
| emsr_fakewrite}, |
| {MSR_IA32_VMX_EXIT_CTLS_MSR, "MSR_IA32_VMX_EXIT_CTLS_MSR", |
| emsr_fakewrite}, |
| {MSR_IA32_VMX_ENTRY_CTLS_MSR, "MSR_IA32_VMX_ENTRY_CTLS_MSR", |
| emsr_fakewrite}, |
| {MSR_IA32_ENERGY_PERF_BIAS, "MSR_IA32_ENERGY_PERF_BIAS", |
| emsr_fakewrite}, |
| {MSR_LBR_SELECT, "MSR_LBR_SELECT", emsr_ok}, |
| {MSR_LBR_TOS, "MSR_LBR_TOS", emsr_ok}, |
| {MSR_LBR_NHM_FROM, "MSR_LBR_NHM_FROM", emsr_ok}, |
| {MSR_LBR_NHM_TO, "MSR_LBR_NHM_TO", emsr_ok}, |
| {MSR_LBR_CORE_FROM, "MSR_LBR_CORE_FROM", emsr_ok}, |
| {MSR_LBR_CORE_TO, "MSR_LBR_CORE_TO", emsr_ok}, |
| |
| // grumble. |
| {MSR_OFFCORE_RSP_0, "MSR_OFFCORE_RSP_0", emsr_ok}, |
| {MSR_OFFCORE_RSP_1, "MSR_OFFCORE_RSP_1", emsr_ok}, |
| // louder. |
| {MSR_PEBS_LD_LAT_THRESHOLD, "MSR_PEBS_LD_LAT_THRESHOLD", emsr_ok}, |
| // aaaaaahhhhhhhhhhhhhhhhhhhhh |
| {MSR_ARCH_PERFMON_EVENTSEL0, "MSR_ARCH_PERFMON_EVENTSEL0", emsr_ok}, |
| {MSR_ARCH_PERFMON_EVENTSEL1, "MSR_ARCH_PERFMON_EVENTSEL1", emsr_ok}, |
| {MSR_IA32_PERF_CAPABILITIES, "MSR_IA32_PERF_CAPABILITIES", emsr_readzero}, |
| // unsafe. |
| {MSR_IA32_APICBASE, "MSR_IA32_APICBASE", emsr_fake_apicbase}, |
| |
| // mostly harmless. |
| {MSR_TSC_AUX, "MSR_TSC_AUX", emsr_fakewrite}, |
| {MSR_RAPL_POWER_UNIT, "MSR_RAPL_POWER_UNIT", emsr_readzero}, |
| {MSR_IA32_MCG_CAP, "MSR_IA32_MCG_CAP", emsr_readzero}, |
| {MSR_IA32_DEBUGCTLMSR, "MSR_IA32_DEBUGCTLMSR", emsr_fakewrite}, |
| |
| // TBD |
| {MSR_IA32_TSC_DEADLINE, "MSR_IA32_TSC_DEADLINE", emsr_fakewrite}, |
| }; |
| |
| /* Here are the rules for IPI injection: |
| * 1) The guest can't sleep if notif is set. |
| * 2) Userspace must wake the guest if notif is set, unconditionally |
| * 3) Whoever sets notif must make sure the interrupt gets injected. |
| * |
| * This allows the kernel to set notif and possibly lose a race with a |
| * concurrently halting / vmexiting guest. |
| * |
| * Guest sleeping happens in userspace in the halt/mwait vmexit handler. If |
| * userspace (vmm_interrupt_guest() sees notif set, it must try to wake the |
| * guest - even if the user didn't set notif. If the kernel sets notif, it |
| * might be able to know the guest is running. But if that fails, we have to |
| * kick it back to userspace (return false here). In that case, even though |
| * userspace didn't set notif, it must attempt to wake the guest. |
| * |
| * For 3, the kernel can often know if the guest is running. Then it can send |
| * the posted IPI, then reconfirm the guest is running. If that fails, or if it |
| * *might* have failed, the guest still needs to get the IRQ. The next time the |
| * guest runs after notif was set, the interrupt will be injected. If the |
| * kernel kicks it back to userspace, the guest will wake or will fail to halt |
| * (due to notif being set), and the next time it runs, the kernel will inject |
| * the IPI (when we pop the vmtf). |
| * |
| * There's another case: the kernel sets notif, reads the coreid, sends the IPI, |
| * and then sees the coreid is changed. If the coreid is -1, the GPC isn't |
| * loaded/running, and we kick back to userspace (as above). If the coreid is |
| * not -1, it is running somewhere else. It might have missed the IPI, but |
| * since the guest was popped on a core after notif was set, the IRQ was |
| * posted/injected. */ |
| static bool emsr_lapic_icr_write(struct emmsr *msr, struct vm_trapframe *tf) |
| { |
| uint32_t destination = tf->tf_rdx & 0xffffffff; |
| uint8_t vector = tf->tf_rax & 0xff; |
| uint8_t type = (tf->tf_rax >> 8) & 0x7; |
| struct guest_pcore *gpc; |
| int target_coreid; |
| |
| if (type != 0 || destination == 0xffffffff) |
| return false; |
| gpc = lookup_guest_pcore(current, destination); |
| if (!gpc) |
| return false; |
| SET_BITMASK_BIT_ATOMIC((void*)gpc->posted_irq_desc, vector); |
| cmb(); /* atomic does the MB, order set write before test read */ |
| /* We got lucky and squeezed our IRQ in with someone else's */ |
| if (test_bit(VMX_POSTED_OUTSTANDING_NOTIF, (void*)gpc->posted_irq_desc)) |
| return true; |
| SET_BITMASK_BIT_ATOMIC((void*)gpc->posted_irq_desc, |
| VMX_POSTED_OUTSTANDING_NOTIF); |
| cmb(); /* atomic does the MB, order set write before read of cpu */ |
| target_coreid = ACCESS_ONCE(gpc->cpu); |
| if (target_coreid == -1) |
| return false; |
| /* If it's us, we'll send_ipi when we restart the VMTF. Note this is |
| * rare: the guest will usually use the self_ipi virtualization. */ |
| if (target_coreid != core_id()) |
| send_ipi(target_coreid, I_POKE_GUEST); |
| /* No MBs needed here: only that it happens after setting notif */ |
| if (ACCESS_ONCE(gpc->cpu) == -1) |
| return false; |
| return true; |
| } |
| |
| static bool emsr_lapic_icr(struct emmsr *msr, struct vm_trapframe *tf, |
| uint32_t opcode) |
| { |
| if (opcode == VMM_MSR_EMU_READ) |
| return false; |
| return emsr_lapic_icr_write(msr, tf); |
| } |
| |
| /* this may be the only register that needs special handling. |
| * If there others then we might want to extend the emmsr struct. |
| */ |
| bool emsr_miscenable(struct emmsr *msr, struct vm_trapframe *vm_tf, |
| uint32_t opcode) |
| { |
| uint64_t val; |
| uint32_t eax, edx; |
| |
| if (read_msr_safe(msr->reg, &val)) |
| return FALSE; |
| eax = low32(val); |
| eax |= MSR_IA32_MISC_ENABLE_PEBS_UNAVAIL; |
| edx = high32(val); |
| /* we just let them read the misc msr for now. */ |
| if (opcode == VMM_MSR_EMU_READ) { |
| vm_tf->tf_rax = eax; |
| vm_tf->tf_rdx = edx; |
| return TRUE; |
| } else { |
| /* if they are writing what is already written, that's ok. */ |
| if (((uint32_t) vm_tf->tf_rax == eax) |
| && ((uint32_t) vm_tf->tf_rdx == edx)) |
| return TRUE; |
| } |
| printk("%s: Wanted to write 0x%x%x, but could not; value was 0x%x%x\n", |
| msr->name, (uint32_t) vm_tf->tf_rdx, (uint32_t) vm_tf->tf_rax, |
| edx, eax); |
| return FALSE; |
| } |
| |
| bool emsr_readonly(struct emmsr *msr, struct vm_trapframe *vm_tf, |
| uint32_t opcode) |
| { |
| uint64_t val; |
| |
| if (read_msr_safe(msr->reg, &val)) |
| return FALSE; |
| if (opcode == VMM_MSR_EMU_READ) { |
| vm_tf->tf_rax = low32(val); |
| vm_tf->tf_rdx = high32(val); |
| return TRUE; |
| } |
| |
| printk("%s: Tried to write a readonly register\n", msr->name); |
| return FALSE; |
| } |
| |
| bool emsr_readzero(struct emmsr *msr, struct vm_trapframe *vm_tf, |
| uint32_t opcode) |
| { |
| if (opcode == VMM_MSR_EMU_READ) { |
| vm_tf->tf_rax = 0; |
| vm_tf->tf_rdx = 0; |
| return TRUE; |
| } |
| |
| printk("%s: Tried to write a readonly register\n", msr->name); |
| return FALSE; |
| } |
| |
| /* pretend to write it, but don't write it. */ |
| bool emsr_fakewrite(struct emmsr *msr, struct vm_trapframe *vm_tf, |
| uint32_t opcode) |
| { |
| uint32_t eax, edx; |
| uint64_t val; |
| |
| if (!msr->written) { |
| if (read_msr_safe(msr->reg, &val)) |
| return FALSE; |
| eax = low32(val); |
| edx = high32(val); |
| } else { |
| eax = msr->eax; |
| edx = msr->edx; |
| } |
| /* we just let them read the misc msr for now. */ |
| if (opcode == VMM_MSR_EMU_READ) { |
| vm_tf->tf_rax = eax; |
| vm_tf->tf_rdx = edx; |
| return TRUE; |
| } else { |
| msr->edx = vm_tf->tf_rdx; |
| msr->eax = vm_tf->tf_rax; |
| msr->written = TRUE; |
| } |
| return TRUE; |
| } |
| |
| bool emsr_ok(struct emmsr *msr, struct vm_trapframe *vm_tf, |
| uint32_t opcode) |
| { |
| uint64_t val; |
| |
| if (opcode == VMM_MSR_EMU_READ) { |
| if (read_msr_safe(msr->reg, &val)) |
| return FALSE; |
| vm_tf->tf_rax = low32(val); |
| vm_tf->tf_rdx = high32(val); |
| } else { |
| val = (vm_tf->tf_rdx << 32) | (vm_tf->tf_rax & 0xffffffff); |
| if (write_msr_safe(msr->reg, val)) |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| /* pretend to write it, but don't write it. */ |
| bool emsr_fake_apicbase(struct emmsr *msr, struct vm_trapframe *vm_tf, |
| uint32_t opcode) |
| { |
| uint32_t eax, edx; |
| |
| if (!msr->written) { |
| /* TODO: tightly coupled to the addr in vmrunkernel. We want |
| * this func to return the val that vmrunkernel put into the |
| * VMCS. */ |
| eax = 0xfee00d00; |
| if (vm_tf->tf_guest_pcoreid != 0) { |
| // Remove BSP bit if not core 0 |
| eax = 0xfee00c00; |
| } |
| edx = 0; |
| } else { |
| edx = msr->edx; |
| eax = msr->eax; |
| } |
| /* we just let them read the misc msr for now. */ |
| if (opcode == VMM_MSR_EMU_READ) { |
| vm_tf->tf_rax = eax; |
| vm_tf->tf_rdx = edx; |
| return TRUE; |
| } else { |
| /* if they are writing what is already written, that's ok. */ |
| if (((uint32_t) vm_tf->tf_rax == eax) |
| && ((uint32_t) vm_tf->tf_rdx == edx)) |
| return 0; |
| msr->edx = vm_tf->tf_rdx; |
| msr->eax = vm_tf->tf_rax; |
| msr->written = TRUE; |
| } |
| return TRUE; |
| } |
| |
| bool vmm_emulate_msr(struct vm_trapframe *vm_tf, int op) |
| { |
| for (int i = 0; i < ARRAY_SIZE(emmsrs); i++) { |
| if (emmsrs[i].reg != vm_tf->tf_rcx) |
| continue; |
| return emmsrs[i].f(&emmsrs[i], vm_tf, op); |
| } |
| return FALSE; |
| } |