aef0c2e096d7f6ab2923960cb65836e2a65d80c9
[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 <sys/queue.h>
14 #include <vmm/vmm.h>
15 #include <vmm/vthread.h>
16
17 static struct vmm_thread_tq parked_vths = TAILQ_HEAD_INITIALIZER(parked_vths);
18 static struct spin_pdr_lock park_lock = SPINPDR_INITIALIZER;
19
20 static void *pages(size_t count)
21 {
22         void *v;
23         unsigned long flags = MAP_POPULATE | MAP_ANONYMOUS | MAP_PRIVATE;
24
25         return mmap(0, count * PGSIZE, PROT_READ | PROT_WRITE, flags, -1, 0);
26 }
27
28 static void vmsetup(void *arg)
29 {
30         struct virtual_machine *vm = (struct virtual_machine *)arg;
31
32         setup_paging(vm);
33         vm->nr_gpcs = 0;
34         vm->__gths = NULL;
35         vm->gth_array_elem = 0;
36         uthread_mcp_init();
37 }
38
39 void gpci_init(struct vmm_gpcore_init *gpci)
40 {
41         uint8_t *p;
42
43         /* Technically, we don't need these pages for the all guests. Currently,
44          * the kernel requires them. */
45         p = pages(3);
46         if (!p)
47                 panic("Can't allocate 3 pages for guest: %r");
48         gpci->posted_irq_desc = &p[0];
49         gpci->vapic_addr = &p[4096];
50         gpci->apic_addr = &p[8192];
51         /* TODO: once we are making these GPCs at the same time as vthreads, we
52          * should set fsbase == the TLS desc of the vthread (if any). */
53         gpci->fsbase = 0;
54         gpci->gsbase = 0;
55 }
56
57 /* Helper, grows the array of guest_threads in vm.  Concurrent readers
58  * (gpcid_to_gth()) need to use a seq-lock-style of concurrency.  They could
59  * read the old array even after we free it.
60  *
61  * Unlike in the kernel, concurrent readers in userspace shouldn't even read
62  * freed memory.  Electric fence could catch that fault.  Until we have a decent
63  * userspace RCU, we can avoid these faults WHP by just sleeping. */
64 static void __grow_gth_array(struct virtual_machine *vm,
65                              unsigned int new_nr_gths)
66 {
67         struct guest_thread **new_array, **old_array;
68         size_t new_nr_elem;
69
70         if (new_nr_gths <= vm->gth_array_elem)
71                 return;
72         /* TODO: (RCU) we could defer the free */
73         old_array = vm->__gths;
74         new_nr_elem = MAX(vm->gth_array_elem * 2, new_nr_gths);
75         new_array = calloc(new_nr_elem, sizeof(void*));
76         assert(new_array);
77         memcpy(new_array, vm->__gths, sizeof(void*) * vm->nr_gpcs);
78         wmb();  /* all elements written before changing pointer */
79         vm->__gths = new_array;
80         wmb();  /* ptr written before potentially clobbering freed memory. */
81         uthread_usleep(1000);   /* hack for electric fence */
82         free(old_array);
83 }
84
85 void __add_gth_to_vm(struct virtual_machine *vm, struct guest_thread *gth)
86 {
87         __grow_gth_array(vm, vm->nr_gpcs + 1);
88         vm->__gths[vm->nr_gpcs] = gth;
89         wmb();  /* concurrent readers will check nr_gpcs first */
90         vm->nr_gpcs++;
91 }
92
93 /* If we fully destroy these uthreads, we'll need to call uthread_cleanup() */
94 void __vthread_exited(struct vthread *vth)
95 {
96         struct virtual_machine *vm = vth_to_vm(vth);
97
98         spin_pdr_lock(&park_lock);
99         TAILQ_INSERT_HEAD(&parked_vths, (struct vmm_thread*)vth, tq_next);
100         spin_pdr_unlock(&park_lock);
101 }
102
103 /* The tricky part is that we need to reinit the threads */
104 static struct vthread *get_parked_vth(struct virtual_machine *vm)
105 {
106         struct vmm_thread *vmth;
107         struct guest_thread *gth;
108         struct ctlr_thread *cth;
109         /* These are from create_guest_thread() */
110         struct uth_thread_attr gth_attr = {.want_tls = FALSE};
111         struct uth_thread_attr cth_attr = {.want_tls = TRUE};
112
113         spin_pdr_lock(&park_lock);
114         vmth = TAILQ_FIRST(&parked_vths);
115         if (!vmth) {
116                 spin_pdr_unlock(&park_lock);
117                 return NULL;
118         }
119         TAILQ_REMOVE(&parked_vths, vmth, tq_next);
120         spin_pdr_unlock(&park_lock);
121
122         gth = (struct guest_thread*)vmth;
123         cth = gth->buddy;
124         uthread_init((struct uthread*)gth, &gth_attr);
125         uthread_init((struct uthread*)cth, &cth_attr);
126         return (struct vthread*)gth;
127 }
128
129 struct vthread *vthread_alloc(struct virtual_machine *vm,
130                               struct vmm_gpcore_init *gpci)
131 {
132         static parlib_once_t once = PARLIB_ONCE_INIT;
133         struct guest_thread *gth;
134         struct vthread *vth;
135         int ret;
136
137         parlib_run_once(&once, vmsetup, vm);
138
139         vth = get_parked_vth(vm);
140         if (vth)
141                 return vth;
142         uth_mutex_lock(&vm->mtx);
143         ret = syscall(SYS_vmm_add_gpcs, 1, gpci);
144         assert(ret == 1);
145         gth = create_guest_thread(vm, vm->nr_gpcs, gpci);
146         assert(gth);
147         __add_gth_to_vm(vm, gth);
148         uth_mutex_unlock(&vm->mtx);
149         /* TODO: somewhat arch specific */
150         gth_to_vmtf(gth)->tf_cr3 = (uintptr_t)vm->root;
151         return (struct vthread*)gth;
152 }
153
154 /* TODO: this is arch specific */
155 void vthread_init_ctx(struct vthread *vth, uintptr_t entry_pt, uintptr_t arg,
156                       uintptr_t stacktop)
157 {
158         struct vm_trapframe *vm_tf = vth_to_vmtf(vth);
159
160         vm_tf->tf_rip = entry_pt;
161         vm_tf->tf_rdi = arg;
162         vm_tf->tf_rsp = stacktop;
163 }
164
165 void vthread_run(struct vthread *vthread)
166 {
167         start_guest_thread((struct guest_thread*)vthread);
168 }
169
170 #define DEFAULT_STACK_SIZE 65536
171 static uintptr_t alloc_stacktop(struct virtual_machine *vm)
172 {
173         int ret;
174         uintptr_t *stack, *tos;
175
176         ret = posix_memalign((void **)&stack, PGSIZE, DEFAULT_STACK_SIZE);
177         if (ret)
178                 return 0;
179         add_pte_entries(vm, (uintptr_t)stack,
180                         (uintptr_t)stack + DEFAULT_STACK_SIZE);
181         /* touch the top word on the stack so we don't page fault
182          * on that in the VM. */
183         tos = &stack[DEFAULT_STACK_SIZE / sizeof(uint64_t) - 1];
184         *tos = 0;
185         return (uintptr_t)tos;
186 }
187
188 static uintptr_t vth_get_stack(struct vthread *vth)
189 {
190         struct guest_thread *gth = (struct guest_thread*)vth;
191         struct vthread_info *info = (struct vthread_info*)gth->user_data;
192         uintptr_t stacktop;
193
194         if (info) {
195                 assert(info->stacktop);
196                 return info->stacktop;
197         }
198         stacktop = alloc_stacktop(vth_to_vm(vth));
199         assert(stacktop);
200         /* Yes, an evil part of me thought of using the top of the stack for
201          * this struct's storage. */
202         gth->user_data = malloc(sizeof(struct vthread_info));
203         assert(gth->user_data);
204         info = (struct vthread_info*)gth->user_data;
205         info->stacktop = stacktop;
206         return stacktop;
207 }
208
209 struct vthread *vthread_create(struct virtual_machine *vm, void *entry,
210                                void *arg)
211 {
212         struct vthread *vth;
213         struct vmm_gpcore_init gpci[1];
214
215         gpci_init(gpci);
216         vth = vthread_alloc(vm, gpci);
217         if (!vth)
218                 return NULL;
219         vthread_init_ctx(vth, (uintptr_t)entry, (uintptr_t)arg,
220                          vth_get_stack(vth));
221         vthread_run(vth);
222         return vth;
223 }
224
225 void vthread_join(struct vthread *vth, void **retval_loc)
226 {
227         struct ctlr_thread *cth = ((struct guest_thread*)vth)->buddy;
228
229         uthread_join((struct uthread*)cth, retval_loc);
230 }
231
232 long vmcall(unsigned int vmcall_nr, ...)
233 {
234         va_list vl;
235         long a0, a1, a2, a3, a4;
236
237         va_start(vl, vmcall_nr);
238         a0 = va_arg(vl, long);
239         a1 = va_arg(vl, long);
240         a2 = va_arg(vl, long);
241         a3 = va_arg(vl, long);
242         a4 = va_arg(vl, long);
243         va_end(vl);
244         return raw_vmcall(a0, a1, a2, a3, a4, vmcall_nr);
245 }
246
247 bool vth_handle_vmcall(struct guest_thread *gth, struct vm_trapframe *vm_tf)
248 {
249         switch (vm_tf->tf_rax) {
250         case VTH_VMCALL_NULL:
251                 goto out_ok;
252         case VTH_VMCALL_PRINTC:
253                 fprintf(stdout, "%c", vm_tf->tf_rdi);
254                 fflush(stdout);
255                 goto out_ok;
256         case VTH_VMCALL_EXIT:
257                 uth_2ls_thread_exit((void*)vm_tf->tf_rdi);
258                 assert(0);
259         default:
260                 fprintf(stderr, "Unknown syscall nr %d\n", vm_tf->tf_rax);
261                 return FALSE;
262         }
263         assert(0);
264 out_ok:
265         vm_tf->tf_rip += 3;
266         return TRUE;
267 }