blob: 6c23abe2a7cc1cc1a13ec549bbc4c782664c7182 [file] [log] [blame]
/* 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;
}