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