| /* Copyright (c) 2016 Google Inc. | 
 |  * | 
 |  * See LICENSE for details. | 
 |  * | 
 |  * Helper functions for virtual machines */ | 
 |  | 
 | #include <errno.h> | 
 | #include <stdlib.h> | 
 | #include <parlib/bitmask.h> | 
 | #include <parlib/uthread.h> | 
 | #include <sys/mman.h> | 
 | #include <sys/syscall.h> | 
 | #include <sys/queue.h> | 
 | #include <vmm/vmm.h> | 
 | #include <vmm/vthread.h> | 
 |  | 
 | static struct vmm_thread_tq parked_vths = TAILQ_HEAD_INITIALIZER(parked_vths); | 
 | static struct spin_pdr_lock park_lock = SPINPDR_INITIALIZER; | 
 |  | 
 | static void *pages(size_t count) | 
 | { | 
 | 	void *v; | 
 | 	unsigned long flags = MAP_POPULATE | MAP_ANONYMOUS | MAP_PRIVATE; | 
 |  | 
 | 	return mmap(0, count * PGSIZE, PROT_READ | PROT_WRITE, flags, -1, 0); | 
 | } | 
 |  | 
 | static void vmsetup(void *arg) | 
 | { | 
 | 	struct virtual_machine *vm = (struct virtual_machine *)arg; | 
 |  | 
 | 	setup_paging(vm); | 
 | 	vm->nr_gpcs = 0; | 
 | 	vm->__gths = NULL; | 
 | 	vm->gth_array_elem = 0; | 
 | 	uthread_mcp_init(); | 
 | } | 
 |  | 
 | void gpci_init(struct vmm_gpcore_init *gpci) | 
 | { | 
 | 	uint8_t *p; | 
 |  | 
 | 	/* Technically, we don't need these pages for the all guests. Currently, | 
 | 	 * the kernel requires them. */ | 
 | 	p = pages(3); | 
 | 	if (!p) | 
 | 		panic("Can't allocate 3 pages for guest: %r"); | 
 | 	gpci->posted_irq_desc = &p[0]; | 
 | 	gpci->vapic_addr = &p[4096]; | 
 | 	gpci->apic_addr = &p[8192]; | 
 | 	/* TODO: once we are making these GPCs at the same time as vthreads, we | 
 | 	 * should set fsbase == the TLS desc of the vthread (if any). */ | 
 | 	gpci->fsbase = 0; | 
 | 	gpci->gsbase = 0; | 
 | } | 
 |  | 
 | /* Helper, grows the array of guest_threads in vm.  Concurrent readers | 
 |  * (gpcid_to_gth()) need to use a seq-lock-style of concurrency.  They could | 
 |  * read the old array even after we free it. | 
 |  * | 
 |  * Unlike in the kernel, concurrent readers in userspace shouldn't even read | 
 |  * freed memory.  Electric fence could catch that fault.  Until we have a decent | 
 |  * userspace RCU, we can avoid these faults WHP by just sleeping. */ | 
 | static void __grow_gth_array(struct virtual_machine *vm, | 
 |                              unsigned int new_nr_gths) | 
 | { | 
 | 	struct guest_thread **new_array, **old_array; | 
 | 	size_t new_nr_elem; | 
 |  | 
 | 	if (new_nr_gths <= vm->gth_array_elem) | 
 | 		return; | 
 | 	/* TODO: (RCU) we could defer the free */ | 
 | 	old_array = vm->__gths; | 
 | 	new_nr_elem = MAX(vm->gth_array_elem * 2, new_nr_gths); | 
 | 	new_array = calloc(new_nr_elem, sizeof(void*)); | 
 | 	assert(new_array); | 
 | 	memcpy(new_array, vm->__gths, sizeof(void*) * vm->nr_gpcs); | 
 | 	wmb();	/* all elements written before changing pointer */ | 
 | 	vm->__gths = new_array; | 
 | 	wmb();	/* ptr written before potentially clobbering freed memory. */ | 
 | 	uthread_usleep(1000);	/* hack for electric fence */ | 
 | 	free(old_array); | 
 | } | 
 |  | 
 | void __add_gth_to_vm(struct virtual_machine *vm, struct guest_thread *gth) | 
 | { | 
 | 	__grow_gth_array(vm, vm->nr_gpcs + 1); | 
 | 	vm->__gths[vm->nr_gpcs] = gth; | 
 | 	wmb();	/* concurrent readers will check nr_gpcs first */ | 
 | 	vm->nr_gpcs++; | 
 | } | 
 |  | 
 | /* If we fully destroy these uthreads, we'll need to call uthread_cleanup() */ | 
 | void __vthread_exited(struct vthread *vth) | 
 | { | 
 | 	struct virtual_machine *vm = vth_to_vm(vth); | 
 |  | 
 | 	spin_pdr_lock(&park_lock); | 
 | 	TAILQ_INSERT_HEAD(&parked_vths, (struct vmm_thread*)vth, tq_next); | 
 | 	spin_pdr_unlock(&park_lock); | 
 | } | 
 |  | 
 | /* The tricky part is that we need to reinit the threads */ | 
 | static struct vthread *get_parked_vth(struct virtual_machine *vm) | 
 | { | 
 | 	struct vmm_thread *vmth; | 
 | 	struct guest_thread *gth; | 
 | 	struct ctlr_thread *cth; | 
 | 	/* These are from create_guest_thread() */ | 
 | 	struct uth_thread_attr gth_attr = {.want_tls = FALSE}; | 
 | 	struct uth_thread_attr cth_attr = {.want_tls = TRUE}; | 
 |  | 
 | 	spin_pdr_lock(&park_lock); | 
 | 	vmth = TAILQ_FIRST(&parked_vths); | 
 | 	if (!vmth) { | 
 | 		spin_pdr_unlock(&park_lock); | 
 | 		return NULL; | 
 | 	} | 
 | 	TAILQ_REMOVE(&parked_vths, vmth, tq_next); | 
 | 	spin_pdr_unlock(&park_lock); | 
 |  | 
 | 	gth = (struct guest_thread*)vmth; | 
 | 	cth = gth->buddy; | 
 | 	uthread_init((struct uthread*)gth, >h_attr); | 
 | 	uthread_init((struct uthread*)cth, &cth_attr); | 
 | 	return (struct vthread*)gth; | 
 | } | 
 |  | 
 | struct vthread *vthread_alloc(struct virtual_machine *vm, | 
 |                               struct vmm_gpcore_init *gpci) | 
 | { | 
 | 	static parlib_once_t once = PARLIB_ONCE_INIT; | 
 | 	struct guest_thread *gth; | 
 | 	struct vthread *vth; | 
 | 	int ret; | 
 |  | 
 | 	parlib_run_once(&once, vmsetup, vm); | 
 |  | 
 | 	vth = get_parked_vth(vm); | 
 | 	if (vth) | 
 | 		return vth; | 
 | 	uth_mutex_lock(&vm->mtx); | 
 | 	ret = syscall(SYS_vmm_add_gpcs, 1, gpci); | 
 | 	assert(ret == 1); | 
 | 	gth = create_guest_thread(vm, vm->nr_gpcs, gpci); | 
 | 	assert(gth); | 
 | 	__add_gth_to_vm(vm, gth); | 
 | 	uth_mutex_unlock(&vm->mtx); | 
 | 	/* TODO: somewhat arch specific */ | 
 | 	gth_to_vmtf(gth)->tf_cr3 = (uintptr_t)vm->root; | 
 | 	return (struct vthread*)gth; | 
 | } | 
 |  | 
 | /* TODO: this is arch specific */ | 
 | void vthread_init_ctx(struct vthread *vth, uintptr_t entry_pt, uintptr_t arg, | 
 |                       uintptr_t stacktop) | 
 | { | 
 | 	struct vm_trapframe *vm_tf = vth_to_vmtf(vth); | 
 |  | 
 | 	vm_tf->tf_rip = entry_pt; | 
 | 	vm_tf->tf_rdi = arg; | 
 | 	vm_tf->tf_rsp = stacktop; | 
 | 	vm_tf->tf_rflags = FL_RSVD_1; | 
 | } | 
 |  | 
 | void vthread_run(struct vthread *vthread) | 
 | { | 
 | 	start_guest_thread((struct guest_thread*)vthread); | 
 | } | 
 |  | 
 | #define DEFAULT_STACK_SIZE 65536 | 
 | static uintptr_t alloc_stacktop(struct virtual_machine *vm) | 
 | { | 
 | 	int ret; | 
 | 	uintptr_t *stack, *tos; | 
 |  | 
 | 	ret = posix_memalign((void **)&stack, PGSIZE, DEFAULT_STACK_SIZE); | 
 | 	if (ret) | 
 | 		return 0; | 
 | 	add_pte_entries(vm, (uintptr_t)stack, | 
 | 	                (uintptr_t)stack + DEFAULT_STACK_SIZE); | 
 | 	/* touch the top word on the stack so we don't page fault | 
 | 	 * on that in the VM. */ | 
 | 	tos = &stack[DEFAULT_STACK_SIZE / sizeof(uint64_t) - 1]; | 
 | 	*tos = 0; | 
 | 	return (uintptr_t)tos; | 
 | } | 
 |  | 
 | static uintptr_t vth_get_stack(struct vthread *vth) | 
 | { | 
 | 	struct guest_thread *gth = (struct guest_thread*)vth; | 
 | 	struct vthread_info *info = (struct vthread_info*)gth->user_data; | 
 | 	uintptr_t stacktop; | 
 |  | 
 | 	if (info) { | 
 | 		assert(info->stacktop); | 
 | 		return info->stacktop; | 
 | 	} | 
 | 	stacktop = alloc_stacktop(vth_to_vm(vth)); | 
 | 	assert(stacktop); | 
 | 	/* Yes, an evil part of me thought of using the top of the stack for | 
 | 	 * this struct's storage. */ | 
 | 	gth->user_data = malloc(sizeof(struct vthread_info)); | 
 | 	assert(gth->user_data); | 
 | 	info = (struct vthread_info*)gth->user_data; | 
 | 	info->stacktop = stacktop; | 
 | 	return stacktop; | 
 | } | 
 |  | 
 | struct vthread *vthread_create(struct virtual_machine *vm, void *entry, | 
 |                                void *arg) | 
 | { | 
 | 	struct vthread *vth; | 
 | 	struct vmm_gpcore_init gpci[1]; | 
 |  | 
 | 	gpci_init(gpci); | 
 | 	vth = vthread_alloc(vm, gpci); | 
 | 	if (!vth) | 
 | 		return NULL; | 
 | 	vthread_init_ctx(vth, (uintptr_t)entry, (uintptr_t)arg, | 
 | 			 vth_get_stack(vth)); | 
 | 	vthread_run(vth); | 
 | 	return vth; | 
 | } | 
 |  | 
 | void vthread_join(struct vthread *vth, void **retval_loc) | 
 | { | 
 | 	struct ctlr_thread *cth = ((struct guest_thread*)vth)->buddy; | 
 |  | 
 | 	uthread_join((struct uthread*)cth, retval_loc); | 
 | } | 
 |  | 
 | long vmcall(unsigned int vmcall_nr, ...) | 
 | { | 
 | 	va_list vl; | 
 | 	long a0, a1, a2, a3, a4; | 
 |  | 
 | 	va_start(vl, vmcall_nr); | 
 | 	a0 = va_arg(vl, long); | 
 | 	a1 = va_arg(vl, long); | 
 | 	a2 = va_arg(vl, long); | 
 | 	a3 = va_arg(vl, long); | 
 | 	a4 = va_arg(vl, long); | 
 | 	va_end(vl); | 
 | 	return raw_vmcall(a0, a1, a2, a3, a4, vmcall_nr); | 
 | } | 
 |  | 
 | bool vth_handle_vmcall(struct guest_thread *gth, struct vm_trapframe *vm_tf) | 
 | { | 
 | 	switch (vm_tf->tf_rax) { | 
 | 	case VTH_VMCALL_NULL: | 
 | 		goto out_ok; | 
 | 	case VTH_VMCALL_PRINTC: | 
 | 		fprintf(stdout, "%c", vm_tf->tf_rdi); | 
 | 		fflush(stdout); | 
 | 		goto out_ok; | 
 | 	case VTH_VMCALL_EXIT: | 
 | 		uth_2ls_thread_exit((void*)vm_tf->tf_rdi); | 
 | 		assert(0); | 
 | 	default: | 
 | 		fprintf(stderr, "Unknown syscall nr %d\n", vm_tf->tf_rax); | 
 | 		return FALSE; | 
 | 	} | 
 | 	assert(0); | 
 | out_ok: | 
 | 	vm_tf->tf_rip += 3; | 
 | 	return TRUE; | 
 | } |