Fix synchronization bug in pvcalarms
[akaros.git] / user / benchutil / pvcalarm.c
1 /* Copyright (c) 2014 The Regents of the University of California
2  * Kevin Klues <klueska@cs.berkeley.edu>
3  * See LICENSE for details. */
4
5 #include <stdint.h>
6 #include <stdbool.h>
7 #include <stdlib.h>
8 #include <stdio.h>
9 #include <errno.h>
10 #include <parlib.h>
11 #include <vcore.h>
12 #include <event.h>
13 #include <spinlock.h>
14 #include <arch/atomic.h>
15 #include <arch/bitmask.h>
16 #include <sys/queue.h>
17 #include <fcntl.h>
18 #include <pvcalarm.h>
19 #include <alarm_dispatch.h>
20
21 /* Different states for enabling/disabling the per-vcore alarms. */
22 enum {
23         S_ENABLING,
24         S_ENABLED,
25         S_DISABLING,
26         S_DISABLED,
27 };
28
29 /* The data associated with each per-vcore alarm that needs to be tracked by
30  * each vcore. It is ultimately stored in an __thread variable. */
31 struct pvcalarm_data {
32         int ctlfd;
33         int timerfd;
34         int alarmid;
35         int state;
36         uint64_t start_uptime;
37         SLIST_ENTRY(pvcalarm_data) next;
38 };
39 SLIST_HEAD(pvcalarm_data_list, pvcalarm_data);
40
41 /* The global state of the pvcalarm service itself */
42 struct pvcalarm {
43         uint64_t interval;
44         void (*callback) (void);
45
46         atomic_t state;
47         int busy_count;
48         DECL_BITMASK(vcores, MAX_VCORES);
49         struct spin_pdr_lock list_lock;
50         struct pvcalarm_data_list list;
51         void (*handler) (struct event_msg *ev_msg, unsigned int ev_type);
52 };
53
54 /* The only state we need to make sure is set for the global alarm service is
55  * to make sure it s in the disabled state at bootup */
56 static struct pvcalarm global_pvcalarm = { .state = (void*)S_DISABLED };
57 /* Thread local pointer to the pvcalarm_data.  The memory for this is allocated
58  * on demand as new vcores pop up */
59 static __thread struct pvcalarm_data *__pvcalarm_data; 
60 /* If this function is non-null, then the per-vcore alarm service is active and
61  * the function should be called early on inside vcore_entry(). */
62 void (*vcore_poke_pvcalarm) (void);
63
64 static void __vcore_poke_pvcalarm();
65 static void handle_pvcalarm(struct event_msg *ev_msg, unsigned int ev_type);
66 static void handle_alarm_real(struct event_msg *ev_msg, unsigned int ev_type);
67 static void handle_alarm_prof(struct event_msg *ev_msg, unsigned int ev_type);
68
69 /* Initialize the pvcalarm service. Only call this function once */
70 static int init_global_pvcalarm()
71 {
72         CLR_BITMASK(global_pvcalarm.vcores, MAX_VCORES);
73         spin_pdr_init(&global_pvcalarm.list_lock);
74         SLIST_INIT(&global_pvcalarm.list);
75         global_pvcalarm.interval = 0;
76         global_pvcalarm.callback = NULL;
77         global_pvcalarm.handler = NULL;
78 }
79
80 /* Run the pvc alarm associated with pvcalarm_data for the given amount of
81  * time */
82 static void run_pvcalarm(struct pvcalarm_data *pvcalarm_data, uint64_t offset)
83 {
84         int ret;
85         char buf[20];
86         ret = snprintf(buf, sizeof(buf), "%llx", read_tsc() + offset);
87         ret = write(pvcalarm_data->timerfd, buf, ret);
88         if (ret <= 0) {
89                 perror("Useralarm: Failed to set timer");
90                 return;
91         }
92 }
93
94 /* Run the pvc alarm associated with pvcalarm_data for the given amount of
95  * time. Also mark the start time of the alarm so we can use it for accounting
96  * later. */
97 static void start_pvcalarm(struct pvcalarm_data *pvcalarm_data, uint64_t offset)
98 {
99         pvcalarm_data->start_uptime = vcore_account_uptime_ticks(vcore_id());
100         run_pvcalarm(__pvcalarm_data, offset);
101 }
102
103 /* Stop the pvc alarm associated with pvcalarm_data */
104 static void stop_pvcalarm(struct pvcalarm_data *pvcalarm_data)
105 {
106         int ret;
107         ret = write(pvcalarm_data->ctlfd, "cancel", sizeof("cancel"));
108         if (ret <= 0) {
109                 printf("Useralarm: unable to disarm alarm!\n");
110                 return;
111         }
112 }
113
114 /* Enable the per-vcore alarm service according to one of the policies listed
115  * above.  Every interval usecs the provided callback will be called on each
116  * active vcore according to that policy. */
117 int enable_pvcalarms(int method, uint64_t interval, void (*callback) (void))
118 {
119         assert(!in_vcore_context());
120         if (method != PVCALARM_REAL && method != PVCALARM_PROF)
121                 return EINVAL;
122
123         if (atomic_cas(&global_pvcalarm.state, S_ENABLED, S_ENABLED))
124                 return EALREADY;
125
126         if (!atomic_cas(&global_pvcalarm.state, S_DISABLED, S_ENABLING))
127                 return EBUSY;
128
129         run_once_racy(init_global_pvcalarm());
130
131         global_pvcalarm.interval = usec2tsc(interval);
132         global_pvcalarm.callback = callback;
133         global_pvcalarm.busy_count = 0;
134         switch (method) {
135                 case PVCALARM_REAL:
136                         global_pvcalarm.handler = handle_alarm_real;
137                         break;
138                 case PVCALARM_PROF:
139                         global_pvcalarm.handler = handle_alarm_prof;
140                         break;
141         }
142         vcore_poke_pvcalarm = __vcore_poke_pvcalarm;
143
144         /* Poke all existing vcores so they run the newly initialized
145          * vcore_poke_pvcalarm function */
146         for (int i=0; i<num_vcores(); i++)
147                 sys_self_notify(i, EV_NONE, 0, TRUE);
148
149         atomic_set(&global_pvcalarm.state, S_ENABLED);
150         return 0;
151 }
152
153 /* Disable the currently active per-vcore alarm service */
154 int disable_pvcalarms()
155 {
156         assert(!in_vcore_context());
157         if (atomic_cas(&global_pvcalarm.state, S_DISABLED, S_DISABLED))
158                 return EALREADY;
159
160         if (!atomic_cas(&global_pvcalarm.state, S_ENABLED, S_DISABLING))
161                 return EBUSY;
162
163         /* We loop here to let any vcores currently running code associated with
164          * the pvcalarms to finish what they are doing before we disable the
165          * pvcalarm service.  Since we ensure that this function is only called
166          * from non-vcore context, this is OK. */
167         while(global_pvcalarm.busy_count != 0)
168                 cpu_relax();
169         
170         global_pvcalarm.interval = 0;
171         global_pvcalarm.callback = NULL;
172         global_pvcalarm.handler = NULL;
173         vcore_poke_pvcalarm = NULL;
174
175         /* Loop through all allocated pvcalarm_data structs and disable any alarms
176          * associated with them */
177         struct pvcalarm_data *pvcalarm_data;
178         SLIST_FOREACH(pvcalarm_data, &global_pvcalarm.list, next) {
179                 stop_pvcalarm(pvcalarm_data);
180                 pvcalarm_data->state = S_DISABLED;
181         }
182
183         atomic_set(&global_pvcalarm.state, S_DISABLED);
184 }
185
186
187 /* Allocate a new pvcalarm_data structure and add it to the global list of
188  * registered pvcalarm_data structures */
189 static struct pvcalarm_data *new_pvcalarm_data()
190 {
191         struct pvcalarm_data *pvcalarm_data = malloc(sizeof(struct pvcalarm_data));
192         pvcalarm_data->ctlfd = 0;
193         pvcalarm_data->timerfd = 0;
194         pvcalarm_data->alarmid = 0;
195         pvcalarm_data->state = S_DISABLED;
196
197         spin_pdr_lock(&global_pvcalarm.list_lock);
198         SLIST_INSERT_HEAD(&global_pvcalarm.list, pvcalarm_data, next);
199         spin_pdr_unlock(&global_pvcalarm.list_lock);
200         return pvcalarm_data;
201 }
202
203 /* Free a pvcalarm_data structure allocated with new_pvcalarm_data() and remove
204  * it from the global list of registered pvcalarm_data structures */
205 static void delete_pvcalarm_data(struct pvcalarm_data *pvcalarm_data)
206 {
207         SLIST_REMOVE(&global_pvcalarm.list, pvcalarm_data, pvcalarm_data, next);
208         free(pvcalarm_data);
209 }
210
211 /* Initialize a specific pvcalarm.  This happens once per vcore as it comes
212  * online and the pvcalarm service is active */
213 static void init_pvcalarm(struct pvcalarm_data *pvcalarm_data)
214 {
215         int ctlfd, timerfd, alarmid, vcoreid, ev_flags, ret;
216         char buf[20];
217         char path[32];
218         struct event_queue *ev_q;
219
220         ctlfd = open("#A/clone", O_RDWR | O_CLOEXEC);
221         if (ctlfd < 0) {
222                 perror("Pvcalarm: Can't clone an alarm");
223                 return;
224         }
225         ret = read(ctlfd, buf, sizeof(buf) - 1);
226         if (ret <= 0) {
227                 if (!ret)
228                         printf("Pvcalarm: Got early EOF from ctl\n");
229                 else
230                         perror("Pvcalarm: Can't read ctl");
231                 return;
232         }
233         buf[ret] = 0;
234         alarmid = atoi(buf);
235         snprintf(path, sizeof(path), "#A/a%s/timer", buf);
236         timerfd = open(path, O_RDWR | O_CLOEXEC);
237         if (timerfd < 0) {
238                 perror("Pvcalarm: Can't open timer");
239                 return;
240         }
241         alarm_dispatch_register(alarmid, handle_pvcalarm);
242         vcoreid = vcore_id();
243         ev_flags = EVENT_IPI | EVENT_VCORE_PRIVATE;
244         ev_q = get_event_q_vcpd(vcoreid, ev_flags);
245         if (!ev_q) {
246                 perror("Pvcalarm: Failed ev_q");
247                 return;
248         }
249         ev_q->ev_vcore = vcoreid;
250         ev_q->ev_flags = ev_flags;
251         ret = snprintf(path, sizeof(path), "evq %llx", ev_q);
252         ret = write(ctlfd, path, ret);
253         if (ret <= 0) {
254                 perror("Pvcalarm: Failed to write ev_q");
255                 return;
256         }
257         /* now the alarm is all set, just need to write the timer whenever we want
258          * it to go off. */
259         pvcalarm_data->alarmid = alarmid;
260         pvcalarm_data->ctlfd = ctlfd;
261         pvcalarm_data->timerfd = timerfd;
262 }
263
264 /* TODO: implement a way to completely remove each per-vcore alarm and
265  * deregister it from the #A device */
266
267 /* A preamble function to run anytime we are about to do anything on behalf of
268  * the pvcalarms while in vcore context.  This preamble is necessary to ensure
269  * we maintain proper invariants when enabling and disabling the pvcalarm
270  * service in a running application. */
271 static inline bool __vcore_preamble()
272 {
273         assert(in_vcore_context());
274         __sync_fetch_and_add(&global_pvcalarm.busy_count, 1);
275         if (atomic_cas(&global_pvcalarm.state, S_DISABLED, S_DISABLED))
276                 goto disabled;
277         if (atomic_cas(&global_pvcalarm.state, S_DISABLING, S_DISABLING))
278                 goto disabled;
279         return true;
280 disabled:
281         __sync_fetch_and_add(&global_pvcalarm.busy_count, -1);
282         return false;
283 }
284
285 /* The counterpart to the __vcore_preamble() function */
286 static inline void __vcore_postamble()
287 {
288         __sync_fetch_and_add(&global_pvcalarm.busy_count, -1);
289 }
290
291 /* The actual implementation of the vcore_poke_pvcalarm() function. When the
292  * pvcalarm service is enabled vcore_poke_pvcalarm will point to this function,
293  * otherwise vcore_poke_pvcalarm == NULL.  In this way, vcore_entry() can avoid
294  * the overhead of always running this function by doing  a simple check for
295  * NULL before running it. */
296 void __vcore_poke_pvcalarm()
297 {
298         if (!__vcore_preamble()) return;
299         uint32_t vcoreid = vcore_id();
300         if (!GET_BITMASK_BIT(global_pvcalarm.vcores, vcoreid)) {
301                 SET_BITMASK_BIT(global_pvcalarm.vcores, vcoreid);
302                 __pvcalarm_data = new_pvcalarm_data();
303                 init_pvcalarm(__pvcalarm_data);
304         }
305         if (__pvcalarm_data->state == S_DISABLED) {
306                 __pvcalarm_data->state = S_ENABLED;
307                 start_pvcalarm(__pvcalarm_data, global_pvcalarm.interval);
308         }
309         __vcore_postamble();
310 }
311
312 /* The global handler function.  It simply calls the proper underlying handler
313  * function depending on whether the service is set for the REAL or PERF
314  * policy. */
315 static void handle_pvcalarm(struct event_msg *ev_msg, unsigned int ev_type)
316 {
317         if (!__vcore_preamble()) return;
318         global_pvcalarm.handler(ev_msg, ev_type);
319         __vcore_postamble();
320 }
321
322 /* The pvcalarm handler for the REAL policy.  Simply call the registered
323  * callback and restart the interval alarm. */
324 static void handle_alarm_real(struct event_msg *ev_msg, unsigned int ev_type)
325 {
326         global_pvcalarm.callback();
327         start_pvcalarm(__pvcalarm_data, global_pvcalarm.interval);
328 }
329
330 /* The pvcalarm handler for the PROF policy.  Account for any time the vcore
331  * has been offline.  Only when the uptime since the last interval is equal to
332  * the interval time do we run the callback function.  Otherwise we restart the
333  * alarm to make up the difference. */
334 static void handle_alarm_prof(struct event_msg *ev_msg, unsigned int ev_type)
335 {
336         uint32_t uptime = vcore_account_uptime_ticks(vcore_id());
337         uint64_t diff = uptime - __pvcalarm_data->start_uptime;
338
339         if (diff < global_pvcalarm.interval) {
340                 uint64_t remaining = global_pvcalarm.interval - diff;
341                 run_pvcalarm(__pvcalarm_data, remaining);
342         } else {
343                 global_pvcalarm.callback();
344                 start_pvcalarm(__pvcalarm_data, global_pvcalarm.interval);
345         }
346 }
347