| /* | 
 |  * MSR emulation | 
 |  * | 
 |  * Copyright 2015 Google Inc. | 
 |  * | 
 |  * See LICENSE for details. | 
 |  */ | 
 |  | 
 | #include <parlib/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 (opcode == EXIT_REASON_MSR_READ) { | 
 | 		if (!msr->written) { | 
 | 			msr_val = read_msr(msr->reg); | 
 | 			eax = low32(msr_val); | 
 | 			edx = high32(msr_val); | 
 | 		} else { | 
 | 			eax = msr->eax; | 
 | 			edx = msr->edx; | 
 | 		} | 
 | 		vm_tf->tf_rax = eax; | 
 | 		vm_tf->tf_rdx = edx; | 
 | 	} 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; | 
 | } | 
 |  |