blob: 9c8e98215be706586d8eb17b57d66f05df8cc374 [file] [log] [blame]
/*
* 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;
}