Fixes event_q "get" interfaces to work with UCQs
[akaros.git] / user / parlib / vcore.c
1 #include <arch/arch.h>
2 #include <stdbool.h>
3 #include <errno.h>
4 #include <vcore.h>
5 #include <mcs.h>
6 #include <sys/param.h>
7 #include <parlib.h>
8 #include <unistd.h>
9 #include <stdlib.h>
10 #include <sys/mman.h>
11 #include <stdio.h>
12 #include <glibc-tls.h>
13 #include <event.h>
14 #include <uthread.h>
15 #include <ucq.h>
16 #include <ros/arch/membar.h>
17
18 /* starting with 1 since we alloc vcore0's stacks and TLS in vcore_init(). */
19 static size_t _max_vcores_ever_wanted = 1;
20 static mcs_lock_t _vcore_lock = MCS_LOCK_INIT;
21
22 extern void** vcore_thread_control_blocks;
23
24 /* Get a TLS, returns 0 on failure.  Vcores have their own TLS, and any thread
25  * created by a user-level scheduler needs to create a TLS as well. */
26 void *allocate_tls(void)
27 {
28         extern void *_dl_allocate_tls(void *mem) internal_function;
29         void *tcb = _dl_allocate_tls(NULL);
30         if (!tcb)
31                 return 0;
32         /* Make sure the TLS is set up properly - its tcb pointer points to itself.
33          * Keep this in sync with sysdeps/ros/XXX/tls.h.  For whatever reason,
34          * dynamically linked programs do not need this to be redone, but statics
35          * do. */
36         tcbhead_t *head = (tcbhead_t*)tcb;
37         head->tcb = tcb;
38         head->self = tcb;
39         return tcb;
40 }
41
42 /* Free a previously allocated TLS region */
43 void free_tls(void *tcb)
44 {
45         extern void _dl_deallocate_tls (void *tcb, bool dealloc_tcb) internal_function;
46         assert(tcb);
47         _dl_deallocate_tls(tcb, TRUE);
48 }
49
50 /* TODO: probably don't want to dealloc.  Considering caching */
51 static void free_transition_tls(int id)
52 {
53         if(vcore_thread_control_blocks[id])
54         {
55                 free_tls(vcore_thread_control_blocks[id]);
56                 vcore_thread_control_blocks[id] = NULL;
57         }
58 }
59
60 static int allocate_transition_tls(int id)
61 {
62         /* We want to free and then reallocate the tls rather than simply 
63          * reinitializing it because its size may have changed.  TODO: not sure if
64          * this is right.  0-ing is one thing, but freeing and reallocating can be
65          * expensive, esp if syscalls are involved.  Check out glibc's
66          * allocatestack.c for what might work. */
67         free_transition_tls(id);
68
69         void *tcb = allocate_tls();
70
71         if ((vcore_thread_control_blocks[id] = tcb) == NULL) {
72                 errno = ENOMEM;
73                 return -1;
74         }
75         return 0;
76 }
77
78 static void free_transition_stack(int id)
79 {
80         // don't actually free stacks
81 }
82
83 static int allocate_transition_stack(int id)
84 {
85         struct preempt_data *vcpd = &__procdata.vcore_preempt_data[id];
86         if (vcpd->transition_stack)
87                 return 0; // reuse old stack
88
89         void* stackbot = mmap(0, TRANSITION_STACK_SIZE,
90                               PROT_READ|PROT_WRITE|PROT_EXEC,
91                               MAP_POPULATE|MAP_ANONYMOUS, -1, 0);
92
93         if(stackbot == MAP_FAILED)
94                 return -1; // errno set by mmap
95
96         vcpd->transition_stack = (uintptr_t)stackbot + TRANSITION_STACK_SIZE;
97
98         return 0;
99 }
100
101 int vcore_init()
102 {
103         static int initialized = 0;
104         uintptr_t mmap_block;
105         if(initialized)
106                 return 0;
107
108         vcore_thread_control_blocks = (void**)calloc(max_vcores(),sizeof(void*));
109
110         if(!vcore_thread_control_blocks)
111                 goto vcore_init_fail;
112
113         /* Need to alloc vcore0's transition stuff here (technically, just the TLS)
114          * so that schedulers can use vcore0's transition TLS before it comes up in
115          * vcore_entry() */
116         if(allocate_transition_stack(0) || allocate_transition_tls(0))
117                 goto vcore_init_tls_fail;
118
119         /* Initialize our VCPD event queues' ucqs, two pages per vcore */
120         mmap_block = (uintptr_t)mmap(0, PGSIZE * 2 * max_vcores(),
121                                      PROT_WRITE | PROT_READ,
122                                      MAP_POPULATE | MAP_ANONYMOUS, -1, 0);
123         /* Yeah, this doesn't fit in the error-handling scheme, but this whole
124          * system doesn't really handle failure, and needs a rewrite involving less
125          * mmaps/munmaps. */
126         assert(mmap_block);
127         /* Note we may end up doing vcore 0's elsewhere, for _Ss, or else have a
128          * separate ev_q for that. */
129         for (int i = 0; i < max_vcores(); i++) {
130                 /* two pages each from the big block */
131                 ucq_init_raw(&__procdata.vcore_preempt_data[i].ev_mbox.ev_msgs,
132                              mmap_block + (2 * i    ) * PGSIZE, 
133                              mmap_block + (2 * i + 1) * PGSIZE); 
134         }
135         assert(!in_vcore_context());
136         initialized = 1;
137         return 0;
138 vcore_init_tls_fail:
139         free(vcore_thread_control_blocks);
140 vcore_init_fail:
141         errno = ENOMEM;
142         return -1;
143 }
144
145 /* Returns -1 with errno set on error, or 0 on success.  This does not return
146  * the number of cores actually granted (though some parts of the kernel do
147  * internally).
148  *
149  * Note the doesn't block or anything (despite the min number requested is
150  * 1), since the kernel won't block the call. */
151 int vcore_request(size_t k)
152 {
153         struct mcs_lock_qnode local_qn = {0};
154         int ret = -1;
155         size_t i,j;
156
157         if(vcore_init() < 0)
158                 return -1;
159
160         // TODO: could do this function without a lock once we 
161         // have atomic fetch and add in user space
162         mcs_lock_notifsafe(&_vcore_lock, &local_qn);
163
164         size_t vcores_wanted = num_vcores() + k;
165         if(k < 0 || vcores_wanted > max_vcores())
166         {
167                 errno = EAGAIN;
168                 goto fail;
169         }
170
171         for(i = _max_vcores_ever_wanted; i < vcores_wanted; i++)
172         {
173                 if(allocate_transition_stack(i) || allocate_transition_tls(i))
174                         goto fail; // errno set by the call that failed
175                 _max_vcores_ever_wanted++;
176         }
177         /* Ugly hack, but we need to be able to transition to _M mode */
178         if (num_vcores() == 0)
179                 __enable_notifs(vcore_id());
180         ret = sys_resource_req(RES_CORES, vcores_wanted, 1, 0);
181
182 fail:
183         mcs_unlock_notifsafe(&_vcore_lock, &local_qn);
184         return ret;
185 }
186
187 void vcore_yield()
188 {
189         sys_yield(0);
190 }
191
192 /* Clear pending, and try to handle events that came in between a previous call
193  * to handle_events() and the clearing of pending.  While it's not a big deal,
194  * we'll loop in case we catch any.  Will break out of this once there are no
195  * events, and we will have send pending to 0. 
196  *
197  * Note that this won't catch every race/case of an incoming event.  Future
198  * events will get caught in pop_ros_tf() */
199 void clear_notif_pending(uint32_t vcoreid)
200 {
201         do {
202                 cmb();
203                 __procdata.vcore_preempt_data[vcoreid].notif_pending = 0;
204         } while (handle_events(vcoreid));
205 }
206
207 /* Enables notifs, and deals with missed notifs by self notifying.  This should
208  * be rare, so the syscall overhead isn't a big deal. */
209 void enable_notifs(uint32_t vcoreid)
210 {
211         __enable_notifs(vcoreid);
212         if (__procdata.vcore_preempt_data[vcoreid].notif_pending)
213                 sys_self_notify(vcoreid, EV_NONE, 0);
214 }
215
216 /* Like smp_idle(), this will put the core in a state that it can only be woken
217  * up by an IPI.  In the future, we may halt or something. */
218 void __attribute__((noreturn)) vcore_idle(void)
219 {
220         uint32_t vcoreid = vcore_id();
221         clear_notif_pending(vcoreid);
222         enable_notifs(vcoreid);
223         while (1) {
224                 cpu_relax();
225         }
226 }