|  | /* | 
|  | * MSR emulation | 
|  | * | 
|  | * Copyright 2015 Google Inc. | 
|  | * | 
|  | * See LICENSE for details. | 
|  | */ | 
|  |  | 
|  | #include <stdio.h> | 
|  | #include <sys/types.h> | 
|  | #include <sys/stat.h> | 
|  | #include <fcntl.h> | 
|  | #include <parlib/arch/arch.h> | 
|  | #include <parlib/ros_debug.h> | 
|  | #include <unistd.h> | 
|  | #include <errno.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <sys/uio.h> | 
|  | #include <stdint.h> | 
|  | #include <err.h> | 
|  | #include <sys/mman.h> | 
|  | #include <ros/vmm.h> | 
|  | #include <ros/arch/msr-index.h> | 
|  | #include <vmm/virtio.h> | 
|  | #include <vmm/virtio_mmio.h> | 
|  | #include <vmm/virtio_ids.h> | 
|  | #include <vmm/virtio_config.h> | 
|  | #include <vmm/sched.h> | 
|  | #include <vmm/vmm.h> | 
|  | #include <ros/arch/trapframe.h> | 
|  | #include <parlib/alarm.h> | 
|  |  | 
|  | struct emmsr { | 
|  | uint32_t reg; | 
|  | char *name; | 
|  | int (*f)(struct guest_thread *vm_thread, struct emmsr *, uint32_t); | 
|  | bool written; | 
|  | uint32_t edx, eax; | 
|  | }; | 
|  |  | 
|  | static inline uint64_t read_msr(uint32_t reg) | 
|  | { | 
|  | panic("Not implemented for userspace"); | 
|  | } | 
|  |  | 
|  | static inline void write_msr(uint32_t reg, uint64_t val) | 
|  | { | 
|  | panic("Not implemented for userspace"); | 
|  | } | 
|  |  | 
|  | static int emsr_readonly(struct guest_thread *vm_thread, struct emmsr *, | 
|  | uint32_t); | 
|  | static int emsr_readzero(struct guest_thread *vm_thread, struct emmsr *, | 
|  | uint32_t); | 
|  | static int emsr_fakewrite(struct guest_thread *vm_thread, struct emmsr *, | 
|  | uint32_t); | 
|  | static int emsr_ok(struct guest_thread *vm_thread, struct emmsr *, uint32_t); | 
|  |  | 
|  | struct emmsr emmsrs[] = { | 
|  | {MSR_RAPL_POWER_UNIT, "MSR_RAPL_POWER_UNIT", emsr_readzero}, | 
|  | }; | 
|  |  | 
|  | static inline uint32_t low32(uint64_t val) | 
|  | { | 
|  | return val & 0xffffffff; | 
|  | } | 
|  |  | 
|  | static inline uint32_t high32(uint64_t val) | 
|  | { | 
|  | return val >> 32; | 
|  | } | 
|  |  | 
|  | static int emsr_ok(struct guest_thread *vm_thread, struct emmsr *msr, | 
|  | uint32_t opcode) | 
|  | { | 
|  | struct vm_trapframe *vm_tf = &(vm_thread->uthread.u_ctx.tf.vm_tf); | 
|  | uint64_t msr_val; | 
|  |  | 
|  | if (opcode == EXIT_REASON_MSR_READ) { | 
|  | msr_val = read_msr(msr->reg); | 
|  | vm_tf->tf_rax = low32(msr_val); | 
|  | vm_tf->tf_rdx = high32(msr_val); | 
|  | } else { | 
|  | msr_val = (vm_tf->tf_rdx << 32) | vm_tf->tf_rax; | 
|  | write_msr(msr->reg, msr_val); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int emsr_readonly(struct guest_thread *vm_thread, struct emmsr *msr, | 
|  | uint32_t opcode) | 
|  | { | 
|  | struct vm_trapframe *vm_tf = &(vm_thread->uthread.u_ctx.tf.vm_tf); | 
|  | uint64_t msr_val; | 
|  |  | 
|  | msr_val = read_msr(msr->reg); | 
|  | if (opcode == EXIT_REASON_MSR_READ) { | 
|  | vm_tf->tf_rax = low32(msr_val); | 
|  | vm_tf->tf_rdx = high32(msr_val); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | fprintf(stderr,"%s: Tried to write a readonly register\n", msr->name); | 
|  | return SHUTDOWN_UNHANDLED_EXIT_REASON; | 
|  | } | 
|  |  | 
|  | static int emsr_readzero(struct guest_thread *vm_thread, struct emmsr *msr, | 
|  | uint32_t opcode) | 
|  | { | 
|  | struct vm_trapframe *vm_tf = &(vm_thread->uthread.u_ctx.tf.vm_tf); | 
|  |  | 
|  | if (opcode == EXIT_REASON_MSR_READ) { | 
|  | vm_tf->tf_rax = 0; | 
|  | vm_tf->tf_rdx = 0; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | fprintf(stderr,"%s: Tried to write a readonly register\n", msr->name); | 
|  | return SHUTDOWN_UNHANDLED_EXIT_REASON; | 
|  | } | 
|  |  | 
|  | /* pretend to write it, but don't write it. */ | 
|  | static int emsr_fakewrite(struct guest_thread *vm_thread, struct emmsr *msr, | 
|  | uint32_t opcode) | 
|  | { | 
|  | uint32_t eax, edx; | 
|  | uint64_t msr_val; | 
|  | struct vm_trapframe *vm_tf = &(vm_thread->uthread.u_ctx.tf.vm_tf); | 
|  |  | 
|  | if (!msr->written) { | 
|  | msr_val = read_msr(msr->reg); | 
|  | eax = low32(msr_val); | 
|  | edx = high32(msr_val); | 
|  | } else { | 
|  | eax = msr->eax; | 
|  | edx = msr->edx; | 
|  | } | 
|  | if (opcode == EXIT_REASON_MSR_READ) { | 
|  | vm_tf->tf_rax = eax; | 
|  | vm_tf->tf_rdx = edx; | 
|  | return 0; | 
|  | } else { | 
|  | msr->edx = vm_tf->tf_rdx; | 
|  | msr->eax = vm_tf->tf_rax; | 
|  | msr->written = true; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int apic_icr_write(struct guest_thread *vm_thread, | 
|  | struct vmm_gpcore_init *gpci) | 
|  | { | 
|  | /* We currently only handle physical destinations. | 
|  | * TODO(ganshun): Support logical destinations if needed. */ | 
|  | struct virtual_machine *vm = gth_to_vm(vm_thread); | 
|  | struct vm_trapframe *vm_tf = gth_to_vmtf(vm_thread); | 
|  | uint32_t destination = vm_tf->tf_rdx & 0xffffffff; | 
|  | uint8_t vector = vm_tf->tf_rax & 0xff; | 
|  | uint8_t type = (vm_tf->tf_rax >> 8) & 0x7; | 
|  | int apic_offset = vm_tf->tf_rcx & 0xff; | 
|  |  | 
|  | if (destination >= vm->nr_gpcs && destination != 0xffffffff) { | 
|  | fprintf(stderr, "UNSUPPORTED DESTINATION 0x%02x!\n", | 
|  | destination); | 
|  | return SHUTDOWN_UNHANDLED_EXIT_REASON; | 
|  | } | 
|  | switch (type) { | 
|  | case 0: | 
|  | /* Send IPI */ | 
|  | if (destination == 0xffffffff) { | 
|  | /* Broadcast */ | 
|  | for (int i = 0; i < vm->nr_gpcs; i++) | 
|  | vmm_interrupt_guest(vm, i, vector); | 
|  | } else { | 
|  | /* Send individual IPI */ | 
|  | vmm_interrupt_guest(vm, destination, vector); | 
|  | } | 
|  | break; | 
|  | case 0x5:	/* INIT */ | 
|  | case 0x6:	/* SIPI */ | 
|  | /* We don't use INIT/SIPI for SMP boot.  The guest is still | 
|  | * allowed to try to make them for now. */ | 
|  | break; | 
|  | default: | 
|  | fprintf(stderr, "Unsupported IPI type %d!\n", type); | 
|  | break; | 
|  | } | 
|  |  | 
|  | ((uint32_t *)(gpci->vapic_addr))[apic_offset] = | 
|  | (uint32_t)(vm_tf->tf_rax); | 
|  | ((uint32_t *)(gpci->vapic_addr))[apic_offset + 1] = | 
|  | (uint32_t)(vm_tf->tf_rdx); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int apic_timer_write(struct guest_thread *gth, | 
|  | struct vmm_gpcore_init *gpci) | 
|  | { | 
|  | uint32_t multiplier; | 
|  | uint8_t vector; | 
|  | uint32_t initial_count; | 
|  | uint32_t divide_config_reg; | 
|  | struct alarm_waiter *timer_waiter; | 
|  | struct vm_trapframe *vm_tf = gth_to_vmtf(gth); | 
|  | int apic_offset = vm_tf->tf_rcx & 0xff; | 
|  |  | 
|  | ((uint32_t *)(gpci->vapic_addr))[apic_offset] = | 
|  | (uint32_t)(vm_tf->tf_rax); | 
|  |  | 
|  | /* See if we can set the timer. */ | 
|  | vector = ((uint32_t *)gpci->vapic_addr)[0x32] & 0xff; | 
|  | initial_count = ((uint32_t *)gpci->vapic_addr)[0x38]; | 
|  | divide_config_reg = ((uint32_t *)gpci->vapic_addr)[0x3E]; | 
|  | timer_waiter = (struct alarm_waiter*)gth->user_data; | 
|  |  | 
|  | /* This is a precaution on my part, in case the guest tries to look at | 
|  | * the current count on the lapic. I wanted it to be something other | 
|  | * than 0 just in case. The current count will never be right short of | 
|  | * us properly emulating it. */ | 
|  | ((uint32_t *)(gpci->vapic_addr))[0x39] = initial_count; | 
|  |  | 
|  | if (!timer_waiter) | 
|  | panic("NO WAITER"); | 
|  |  | 
|  | /* Look at the intel manual Vol 3 10.5.4 APIC Timer */ | 
|  | multiplier = (((divide_config_reg & 0x08) >> 1) | | 
|  | (divide_config_reg & 0x03)) + 1; | 
|  | multiplier &= 0x07; | 
|  |  | 
|  | unset_alarm(timer_waiter); | 
|  |  | 
|  | if (vector && initial_count) { | 
|  | set_awaiter_rel(timer_waiter, initial_count << multiplier); | 
|  | set_alarm(timer_waiter); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int emsr_apic(struct guest_thread *vm_thread, | 
|  | struct vmm_gpcore_init *gpci, uint32_t opcode) | 
|  | { | 
|  | struct vm_trapframe *vm_tf = &(vm_thread->uthread.u_ctx.tf.vm_tf); | 
|  | int apic_offset = vm_tf->tf_rcx & 0xff; | 
|  | uint64_t value; | 
|  | int error; | 
|  |  | 
|  | if (opcode == EXIT_REASON_MSR_READ) { | 
|  | if (vm_tf->tf_rcx != MSR_LAPIC_ICR) { | 
|  | vm_tf->tf_rax = | 
|  | ((uint32_t*)(gpci->vapic_addr))[apic_offset]; | 
|  | vm_tf->tf_rdx = 0; | 
|  | } else { | 
|  | vm_tf->tf_rax = | 
|  | ((uint32_t*)(gpci->vapic_addr))[apic_offset]; | 
|  | vm_tf->tf_rdx = | 
|  | ((uint32_t*)(gpci->vapic_addr))[apic_offset +1]; | 
|  | } | 
|  | } else { | 
|  | switch (vm_tf->tf_rcx) { | 
|  | case MSR_LAPIC_ICR: | 
|  | error = apic_icr_write(vm_thread, gpci); | 
|  | if (error != 0) | 
|  | return error; | 
|  | break; | 
|  | case MSR_LAPIC_DIVIDE_CONFIG_REG: | 
|  | case MSR_LAPIC_LVT_TIMER: | 
|  | case MSR_LAPIC_INITIAL_COUNT: | 
|  | apic_timer_write(vm_thread, gpci); | 
|  | break; | 
|  | default: | 
|  | ((uint32_t *)(gpci->vapic_addr))[apic_offset] = | 
|  | (uint32_t)(vm_tf->tf_rax); | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int msrio(struct guest_thread *vm_thread, struct vmm_gpcore_init *gpci, | 
|  | uint32_t opcode) | 
|  | { | 
|  | int i; | 
|  | struct vm_trapframe *vm_tf = &(vm_thread->uthread.u_ctx.tf.vm_tf); | 
|  |  | 
|  | if (vm_tf->tf_rcx >= MSR_LAPIC_ID && vm_tf->tf_rcx < MSR_LAPIC_END) | 
|  | return emsr_apic(vm_thread, gpci, opcode); | 
|  |  | 
|  | for (i = 0; i < sizeof(emmsrs)/sizeof(emmsrs[0]); i++) { | 
|  | if (emmsrs[i].reg != vm_tf->tf_rcx) | 
|  | continue; | 
|  | return emmsrs[i].f(vm_thread, &emmsrs[i], opcode); | 
|  | } | 
|  | printd("msrio for 0x%lx failed\n", vm_tf->tf_rcx); | 
|  | return SHUTDOWN_UNHANDLED_EXIT_REASON; | 
|  | } | 
|  |  |