| /* Copyright (c) 2016 Google Inc. |
| * Barret Rhoden <brho@cs.berkeley.edu> |
| * See LICENSE for details. |
| * |
| * Vcore timer ticks. */ |
| |
| #include <parlib/vcore.h> |
| #include <parlib/uthread.h> |
| #include <parlib/assert.h> |
| #include <parlib/tsc-compat.h> |
| #include <parlib/arch/bitmask.h> |
| #include <parlib/alarm.h> |
| #include <parlib/vcore_tick.h> |
| |
| /* TODO: if we use some other form of per-vcore memory, we can also have a |
| * per-vcore init function that we run before the VC spools up, removing the |
| * need to check for PREINIT. */ |
| |
| enum { |
| VC_TICK_PREINIT = 0, |
| VC_TICK_ENABLED = 1, |
| VC_TICK_DISABLED = 2, |
| }; |
| |
| struct vcore_tick { |
| int state; |
| int ctl_fd; |
| int timer_fd; |
| uint64_t next_deadline; |
| uint64_t period_ticks; |
| struct event_queue *ev_q; |
| }; |
| |
| static struct vcore_tick *__vc_ticks; |
| |
| static void __attribute__((constructor)) vcore_tick_lib_ctor(void) |
| { |
| if (__in_fake_parlib()) |
| return; |
| __vc_ticks = calloc(max_vcores(), sizeof(struct vcore_tick)); |
| assert(__vc_ticks); |
| } |
| |
| /* Only call this from vcore context or with notifs disabled. */ |
| static struct vcore_tick *get_my_tick(void) |
| { |
| return &__vc_ticks[vcore_id()]; |
| } |
| |
| static void vcore_tick_init(struct vcore_tick *vc_tick) |
| { |
| int ret; |
| |
| ret = devalarm_get_fds(&vc_tick->ctl_fd, &vc_tick->timer_fd, 0); |
| assert(!ret); |
| /* We want an IPI and a bit set in the bitmask. But no wakeups, in case |
| * we're offline. */ |
| vc_tick->ev_q = get_eventq(EV_MBOX_BITMAP); |
| assert(vc_tick->ev_q); |
| vc_tick->ev_q->ev_flags = EVENT_IPI; |
| vc_tick->ev_q->ev_vcore = vcore_id(); |
| ret = devalarm_set_evq(vc_tick->timer_fd, vc_tick->ev_q, 0); |
| assert(!ret); |
| vc_tick->state = VC_TICK_DISABLED; |
| } |
| |
| static void __vcore_tick_start(struct vcore_tick *vc_tick, uint64_t from_now) |
| { |
| int ret; |
| |
| ret = devalarm_set_time(vc_tick->timer_fd, read_tsc() + from_now); |
| assert(!ret); |
| } |
| |
| /* Starts a timer tick for this vcore, in virtual time (time the vcore is |
| * actually online). You can call this repeatedly, even if the timer is already |
| * on. You also can update the period of an already-running tick. */ |
| void vcore_tick_enable(uint64_t period_usec) |
| { |
| struct vcore_tick *vc_tick; |
| |
| uth_disable_notifs(); |
| vc_tick = get_my_tick(); |
| if (vc_tick->state == VC_TICK_PREINIT) |
| vcore_tick_init(vc_tick); |
| |
| vc_tick->period_ticks = usec2tsc(period_usec); |
| if (vc_tick->state == VC_TICK_DISABLED) { |
| vc_tick->next_deadline = vcore_account_uptime_ticks(vcore_id()) + |
| vc_tick->period_ticks; |
| __vcore_tick_start(vc_tick, vc_tick->period_ticks); |
| vc_tick->state = VC_TICK_ENABLED; |
| } |
| uth_enable_notifs(); |
| } |
| |
| /* Disables the timer tick. You can call this repeatedly. It is possible that |
| * you will still have a timer tick pending after this returns. */ |
| void vcore_tick_disable(void) |
| { |
| struct vcore_tick *vc_tick; |
| int ret; |
| |
| uth_disable_notifs(); |
| vc_tick = get_my_tick(); |
| if (vc_tick->state == VC_TICK_PREINIT) |
| vcore_tick_init(vc_tick); |
| |
| if (vc_tick->state == VC_TICK_ENABLED) { |
| ret = devalarm_disable(vc_tick->timer_fd); |
| assert(!ret); |
| vc_tick->state = VC_TICK_DISABLED; |
| } |
| uth_enable_notifs(); |
| } |
| |
| /* Polls the vcore timer tick. Returns the number of times it has expired, 0 |
| * for not yet otherwise. Either way, it will ensure that the underlying alarm |
| * is still turned on. */ |
| int vcore_tick_poll(void) |
| { |
| struct vcore_tick *vc_tick; |
| struct evbitmap *evbm; |
| int ret = 0; |
| uint64_t from_now, virtual_now; |
| |
| uth_disable_notifs(); |
| vc_tick = get_my_tick(); |
| if (vc_tick->state == VC_TICK_PREINIT) |
| vcore_tick_init(vc_tick); |
| |
| evbm = &vc_tick->ev_q->ev_mbox->evbm; |
| if (!GET_BITMASK_BIT(evbm->bitmap, EV_ALARM)) { |
| /* It might be possible that the virtual time has passed, but |
| * the alarm hasn't arrived yet. |
| * |
| * We assume that if the bit is not set and the tick is enabled |
| * that the kernel still has an alarm set for us. It is |
| * possible for the bit to be set more than expected (disable an |
| * alarm, but fail to cancel the alarm before it goes off, then |
| * enable it, and then we'll have the bit set before the alarm |
| * expired). However, it is not possible that the bit is clear |
| * and there is no alarm pending at this point. This is because |
| * the only time we clear the bit is below, and then right after |
| * that we set an alarm. (The bit is also clear at init time, |
| * and we start the alarm when we enable the tick). |
| * |
| * Anyway, the alarm should be arriving shortly. In this case, |
| * as in the case where the bit gets set right after we check, |
| * we missed polling for the event. The kernel will still |
| * __notify us, setting notif_pending, and we'll notice the next |
| * time we attempt to leave vcore context. */ |
| uth_enable_notifs(); |
| return 0; |
| } |
| /* Don't care about clobbering neighboring bits (non-atomic op) */ |
| CLR_BITMASK_BIT(evbm->bitmap, EV_ALARM); |
| /* As mentioned above, it is possible to still have an active alarm in |
| * the kernel. We can still set a new time for the alarm, and it will |
| * just update the kernel's awaiter. And if that alarm has fired, then |
| * we'll just have a spurious setting of the bit. This does not affect |
| * our return value, which is based on virtual time, not alarm resets. |
| * */ |
| virtual_now = vcore_account_uptime_ticks(vcore_id()); |
| /* It's possible that we've fallen multiple ticks behind virtual now. |
| * In that case, we'll just jump ahead a bit */ |
| while (vc_tick->next_deadline <= virtual_now) { |
| ret++; |
| vc_tick->next_deadline += vc_tick->period_ticks; |
| } |
| /* There's a slight chance we miss an alarm if the period is very small. |
| * virtual_now is a little old. If the period is so small that this is |
| * a problem and if we updated virtual now in the while loop, then we'd |
| * also get caught in the while loop forever. */ |
| from_now = vc_tick->next_deadline - virtual_now; |
| __vcore_tick_start(vc_tick, from_now); |
| uth_enable_notifs(); |
| return ret; |
| } |