Allows uthread_init() to be called repeatedly
[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 /* Reinitialize / reset / refresh a TLS to its initial values.  This doesn't do
51  * it properly yet, it merely frees and re-allocates the TLS, which is why we're
52  * slightly ghetto and return the pointer you should use for the TCB. */
53 void *reinit_tls(void *tcb)
54 {
55         /* TODO: keep this in sync with the methods used in
56          * allocate_transition_tls() */
57         free_tls(tcb);
58         return allocate_tls();
59 }
60
61 /* TODO: probably don't want to dealloc.  Considering caching */
62 static void free_transition_tls(int id)
63 {
64         if(vcore_thread_control_blocks[id])
65         {
66                 free_tls(vcore_thread_control_blocks[id]);
67                 vcore_thread_control_blocks[id] = NULL;
68         }
69 }
70
71 static int allocate_transition_tls(int id)
72 {
73         /* We want to free and then reallocate the tls rather than simply 
74          * reinitializing it because its size may have changed.  TODO: not sure if
75          * this is right.  0-ing is one thing, but freeing and reallocating can be
76          * expensive, esp if syscalls are involved.  Check out glibc's
77          * allocatestack.c for what might work. */
78         free_transition_tls(id);
79
80         void *tcb = allocate_tls();
81
82         if ((vcore_thread_control_blocks[id] = tcb) == NULL) {
83                 errno = ENOMEM;
84                 return -1;
85         }
86         return 0;
87 }
88
89 static void free_transition_stack(int id)
90 {
91         // don't actually free stacks
92 }
93
94 static int allocate_transition_stack(int id)
95 {
96         struct preempt_data *vcpd = &__procdata.vcore_preempt_data[id];
97         if (vcpd->transition_stack)
98                 return 0; // reuse old stack
99
100         void* stackbot = mmap(0, TRANSITION_STACK_SIZE,
101                               PROT_READ|PROT_WRITE|PROT_EXEC,
102                               MAP_POPULATE|MAP_ANONYMOUS, -1, 0);
103
104         if(stackbot == MAP_FAILED)
105                 return -1; // errno set by mmap
106
107         vcpd->transition_stack = (uintptr_t)stackbot + TRANSITION_STACK_SIZE;
108
109         return 0;
110 }
111
112 int vcore_init()
113 {
114         static int initialized = 0;
115         uintptr_t mmap_block;
116         if(initialized)
117                 return 0;
118
119         vcore_thread_control_blocks = (void**)calloc(max_vcores(),sizeof(void*));
120
121         if(!vcore_thread_control_blocks)
122                 goto vcore_init_fail;
123
124         /* Need to alloc vcore0's transition stuff here (technically, just the TLS)
125          * so that schedulers can use vcore0's transition TLS before it comes up in
126          * vcore_entry() */
127         if(allocate_transition_stack(0) || allocate_transition_tls(0))
128                 goto vcore_init_tls_fail;
129
130         /* Initialize our VCPD event queues' ucqs, two pages per vcore */
131         mmap_block = (uintptr_t)mmap(0, PGSIZE * 2 * max_vcores(),
132                                      PROT_WRITE | PROT_READ,
133                                      MAP_POPULATE | MAP_ANONYMOUS, -1, 0);
134         /* Yeah, this doesn't fit in the error-handling scheme, but this whole
135          * system doesn't really handle failure, and needs a rewrite involving less
136          * mmaps/munmaps. */
137         assert(mmap_block);
138         /* Note we may end up doing vcore 0's elsewhere, for _Ss, or else have a
139          * separate ev_q for that. */
140         for (int i = 0; i < max_vcores(); i++) {
141                 /* two pages each from the big block */
142                 ucq_init_raw(&__procdata.vcore_preempt_data[i].ev_mbox.ev_msgs,
143                              mmap_block + (2 * i    ) * PGSIZE, 
144                              mmap_block + (2 * i + 1) * PGSIZE); 
145         }
146         assert(!in_vcore_context());
147         initialized = 1;
148         return 0;
149 vcore_init_tls_fail:
150         free(vcore_thread_control_blocks);
151 vcore_init_fail:
152         errno = ENOMEM;
153         return -1;
154 }
155
156 /* Returns -1 with errno set on error, or 0 on success.  This does not return
157  * the number of cores actually granted (though some parts of the kernel do
158  * internally).
159  *
160  * Note the doesn't block or anything (despite the min number requested is
161  * 1), since the kernel won't block the call. */
162 int vcore_request(size_t k)
163 {
164         struct mcs_lock_qnode local_qn = {0};
165         int ret = -1;
166         size_t i,j;
167
168         if(vcore_init() < 0)
169                 return -1;
170
171         // TODO: could do this function without a lock once we 
172         // have atomic fetch and add in user space
173         mcs_lock_notifsafe(&_vcore_lock, &local_qn);
174
175         size_t vcores_wanted = num_vcores() + k;
176         if(k < 0 || vcores_wanted > max_vcores())
177         {
178                 errno = EAGAIN;
179                 goto fail;
180         }
181
182         for(i = _max_vcores_ever_wanted; i < vcores_wanted; i++)
183         {
184                 if(allocate_transition_stack(i) || allocate_transition_tls(i))
185                         goto fail; // errno set by the call that failed
186                 _max_vcores_ever_wanted++;
187         }
188         /* Ugly hack, but we need to be able to transition to _M mode */
189         if (num_vcores() == 0)
190                 __enable_notifs(vcore_id());
191         ret = sys_resource_req(RES_CORES, vcores_wanted, 1, 0);
192
193 fail:
194         mcs_unlock_notifsafe(&_vcore_lock, &local_qn);
195         return ret;
196 }
197
198 /* This can return, if you failed to yield due to a concurrent event. */
199 void vcore_yield()
200 {
201         uint32_t vcoreid = vcore_id();
202         struct preempt_data *vcpd = &__procdata.vcore_preempt_data[vcoreid];
203         vcpd->can_rcv_msg = FALSE;
204         wmb();
205         if (handle_events(vcoreid)) {
206                 /* we handled outstanding events, turn the flag back on and return */
207                 vcpd->can_rcv_msg = TRUE;
208                 return;
209         }
210         /* o/w, we can safely yield */
211         sys_yield(0);
212 }
213
214 /* Clear pending, and try to handle events that came in between a previous call
215  * to handle_events() and the clearing of pending.  While it's not a big deal,
216  * we'll loop in case we catch any.  Will break out of this once there are no
217  * events, and we will have send pending to 0. 
218  *
219  * Note that this won't catch every race/case of an incoming event.  Future
220  * events will get caught in pop_ros_tf() */
221 void clear_notif_pending(uint32_t vcoreid)
222 {
223         do {
224                 cmb();
225                 __procdata.vcore_preempt_data[vcoreid].notif_pending = 0;
226         } while (handle_events(vcoreid));
227 }
228
229 /* Enables notifs, and deals with missed notifs by self notifying.  This should
230  * be rare, so the syscall overhead isn't a big deal. */
231 void enable_notifs(uint32_t vcoreid)
232 {
233         __enable_notifs(vcoreid);
234         if (__procdata.vcore_preempt_data[vcoreid].notif_pending)
235                 sys_self_notify(vcoreid, EV_NONE, 0);
236 }
237
238 /* Like smp_idle(), this will put the core in a state that it can only be woken
239  * up by an IPI.  In the future, we may halt or something. */
240 void __attribute__((noreturn)) vcore_idle(void)
241 {
242         uint32_t vcoreid = vcore_id();
243         clear_notif_pending(vcoreid);
244         enable_notifs(vcoreid);
245         while (1) {
246                 cpu_relax();
247         }
248 }