vmm: Allow dynamic vthread creation
[akaros.git] / user / vmm / vthread.c
1 /* Copyright (c) 2016 Google Inc.
2  *
3  * See LICENSE for details.
4  *
5  * Helper functions for virtual machines */
6
7 #include <errno.h>
8 #include <stdlib.h>
9 #include <parlib/bitmask.h>
10 #include <parlib/uthread.h>
11 #include <sys/mman.h>
12 #include <sys/syscall.h>
13 #include <vmm/vmm.h>
14 #include <vmm/vthread.h>
15
16 static void *pages(size_t count)
17 {
18         void *v;
19         unsigned long flags = MAP_POPULATE | MAP_ANONYMOUS | MAP_PRIVATE;
20
21         return mmap(0, count * PGSIZE, PROT_READ | PROT_WRITE, flags, -1, 0);
22 }
23
24 static void vmsetup(void *arg)
25 {
26         struct virtual_machine *vm = (struct virtual_machine *)arg;
27
28         setup_paging(vm);
29         vm->nr_gpcs = 0;
30         vm->__gths = NULL;
31         vm->gth_array_elem = 0;
32         uthread_mcp_init();
33 }
34
35 void gpci_init(struct vmm_gpcore_init *gpci)
36 {
37         uint8_t *p;
38
39         /* Technically, we don't need these pages for the all guests. Currently, the
40          * kernel requires them. */
41         p = pages(3);
42         if (!p)
43                 panic("Can't allocate 3 pages for guest: %r");
44         gpci->posted_irq_desc = &p[0];
45         gpci->vapic_addr = &p[4096];
46         gpci->apic_addr = &p[8192];
47         /* TODO: once we are making these GPCs at the same time as vthreads, we
48          * should set fsbase == the TLS desc of the vthread (if any). */
49         gpci->fsbase = 0;
50         gpci->gsbase = 0;
51 }
52
53 /* Helper, grows the array of guest_threads in vm.  Concurrent readers
54  * (gpcid_to_gth()) need to use a seq-lock-style of concurrency.  They could
55  * read the old array even after we free it.
56  *
57  * Unlike in the kernel, concurrent readers in userspace shouldn't even read
58  * freed memory.  Electric fence could catch that fault.  Until we have a decent
59  * userspace RCU, we can avoid these faults WHP by just sleeping. */
60 static void __grow_gth_array(struct virtual_machine *vm,
61                              unsigned int new_nr_gths)
62 {
63         struct guest_thread **new_array, **old_array;
64         size_t new_nr_elem;
65
66         if (new_nr_gths <= vm->gth_array_elem)
67                 return;
68         /* TODO: (RCU) we could defer the free */
69         old_array = vm->__gths;
70         new_nr_elem = MAX(vm->gth_array_elem * 2, new_nr_gths);
71         new_array = calloc(new_nr_elem, sizeof(void*));
72         assert(new_array);
73         memcpy(new_array, vm->__gths, sizeof(void*) * vm->nr_gpcs);
74         wmb();  /* all elements written before changing pointer */
75         vm->__gths = new_array;
76         wmb();  /* ptr written before potentially clobbering freed memory. */
77         uthread_usleep(1000);   /* hack for electric fence */
78         free(old_array);
79 }
80
81 void __add_gth_to_vm(struct virtual_machine *vm, struct guest_thread *gth)
82 {
83         __grow_gth_array(vm, vm->nr_gpcs + 1);
84         vm->__gths[vm->nr_gpcs] = gth;
85         wmb();  /* concurrent readers will check nr_gpcs first */
86         vm->nr_gpcs++;
87 }
88
89 struct vthread *vthread_alloc(struct virtual_machine *vm,
90                               struct vmm_gpcore_init *gpci)
91 {
92         static parlib_once_t once = PARLIB_ONCE_INIT;
93         struct guest_thread *gth;
94         int ret;
95
96         parlib_run_once(&once, vmsetup, vm);
97
98         uth_mutex_lock(&vm->mtx);
99         ret = syscall(SYS_vmm_add_gpcs, 1, gpci);
100         assert(ret == 1);
101         gth = create_guest_thread(vm, vm->nr_gpcs, gpci);
102         assert(gth);
103         __add_gth_to_vm(vm, gth);
104         uth_mutex_unlock(&vm->mtx);
105         /* TODO: somewhat arch specific */
106         gth_to_vmtf(gth)->tf_cr3 = (uintptr_t)vm->root;
107         return (struct vthread*)gth;
108 }
109
110 /* TODO: this is arch specific */
111 void vthread_init_ctx(struct vthread *vth, uintptr_t entry_pt, uintptr_t arg,
112                       uintptr_t stacktop)
113 {
114         struct vm_trapframe *vm_tf = vth_to_vmtf(vth);
115
116         vm_tf->tf_rip = entry_pt;
117         vm_tf->tf_rdi = arg;
118         vm_tf->tf_rsp = stacktop;
119 }
120
121 void vthread_run(struct vthread *vthread)
122 {
123         start_guest_thread((struct guest_thread*)vthread);
124 }
125
126 #define DEFAULT_STACK_SIZE 65536
127 static uintptr_t alloc_stacktop(struct virtual_machine *vm)
128 {
129         int ret;
130         uintptr_t *stack, *tos;
131
132         ret = posix_memalign((void **)&stack, PGSIZE, DEFAULT_STACK_SIZE);
133         if (ret)
134                 return 0;
135         add_pte_entries(vm, (uintptr_t)stack,
136                         (uintptr_t)stack + DEFAULT_STACK_SIZE);
137         /* touch the top word on the stack so we don't page fault
138          * on that in the VM. */
139         tos = &stack[DEFAULT_STACK_SIZE / sizeof(uint64_t) - 1];
140         *tos = 0;
141         return (uintptr_t)tos;
142 }
143
144 static uintptr_t vth_get_stack(struct vthread *vth)
145 {
146         struct guest_thread *gth = (struct guest_thread*)vth;
147         struct vthread_info *info = (struct vthread_info*)gth->user_data;
148         uintptr_t stacktop;
149
150         if (info) {
151                 assert(info->stacktop);
152                 return info->stacktop;
153         }
154         stacktop = alloc_stacktop(vth_to_vm(vth));
155         assert(stacktop);
156         /* Yes, an evil part of me thought of using the top of the stack for this
157          * struct's storage. */
158         gth->user_data = malloc(sizeof(struct vthread_info));
159         assert(gth->user_data);
160         info = (struct vthread_info*)gth->user_data;
161         info->stacktop = stacktop;
162         return stacktop;
163 }
164
165 struct vthread *vthread_create(struct virtual_machine *vm, void *entry,
166                                void *arg)
167 {
168         struct vthread *vth;
169         struct vmm_gpcore_init gpci[1];
170
171         gpci_init(gpci);
172         vth = vthread_alloc(vm, gpci);
173         if (!vth)
174                 return NULL;
175         vthread_init_ctx(vth, (uintptr_t)entry, (uintptr_t)arg, vth_get_stack(vth));
176         vthread_run(vth);
177         return vth;
178 }