blob: 36091345048cba1992ec708f95c23ceb720c9499 [file] [log] [blame]
/* Copyright (c) 2014 The Regents of the University of California
* Kevin Klues <klueska@cs.berkeley.edu>
* See LICENSE for details. */
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <parlib/parlib.h>
#include <parlib/vcore.h>
#include <parlib/event.h>
#include <parlib/spinlock.h>
#include <parlib/arch/atomic.h>
#include <parlib/arch/bitmask.h>
#include <sys/queue.h>
#include <fcntl.h>
#include <unistd.h>
#include <parlib/pvcalarm.h>
#include <parlib/alarm.h>
/* Different states for enabling/disabling the per-vcore alarms. */
enum {
S_ENABLING,
S_ENABLED,
S_DISABLING,
S_DISABLED,
};
/* The data associated with each per-vcore alarm that needs to be tracked by
* each vcore. It is ultimately stored in an __thread variable. */
struct pvcalarm_data {
int ctlfd;
int timerfd;
int alarmid;
uint64_t start_uptime;
};
/* The global state of the pvcalarm service itself */
struct pvcalarm {
uint64_t interval;
void (*callback) (void);
atomic_t state;
int busy_count;
handle_event_t handler;
struct pvcalarm_data *data;
};
/* The only state we need to make sure is set for the global alarm service is
* to make sure it s in the disabled state at bootup */
static struct pvcalarm global_pvcalarm = { .state = (void*)S_DISABLED };
/* Helper functions */
static void init_pvcalarm(struct pvcalarm_data *pvcalarm_data, int vcoreid);
static void handle_pvcalarm(struct event_msg *ev_msg, unsigned int ev_type,
void *data);
static void handle_alarm_real(struct event_msg *ev_msg, unsigned int ev_type,
void *data);
static void handle_alarm_prof(struct event_msg *ev_msg, unsigned int ev_type,
void *data);
/* Initialize the pvcalarm service. Only call this function once */
static void init_global_pvcalarm(void *arg)
{
global_pvcalarm.interval = 0;
global_pvcalarm.callback = NULL;
global_pvcalarm.busy_count = 0;
global_pvcalarm.handler = NULL;
/* Preemptively setup timers for all possible vcores */
global_pvcalarm.data = malloc(max_vcores() *
sizeof(struct pvcalarm_data));
for (int i=0; i<max_vcores(); i++) {
init_pvcalarm(&global_pvcalarm.data[i], i);
}
}
/* Run the pvc alarm associated with pvcalarm_data for the given amount of
* time */
static void run_pvcalarm(struct pvcalarm_data *pvcalarm_data, uint64_t offset)
{
if (devalarm_set_time(pvcalarm_data->timerfd, read_tsc() + offset)) {
perror("Useralarm: Failed to set timer");
return;
}
}
/* Run the pvc alarm associated with pvcalarm_data for the given amount of
* time. Also mark the start time of the alarm so we can use it for accounting
* later. */
static void start_pvcalarm(struct pvcalarm_data *pvcalarm_data, uint64_t offset)
{
pvcalarm_data->start_uptime = vcore_account_uptime_ticks(vcore_id());
run_pvcalarm(pvcalarm_data, offset);
}
/* Stop the pvc alarm associated with pvcalarm_data */
static void stop_pvcalarm(struct pvcalarm_data *pvcalarm_data)
{
if (devalarm_disable(pvcalarm_data->timerfd)) {
printf("Useralarm: unable to disarm alarm!\n");
return;
}
}
/* Enable the per-vcore alarm service according to one of the policies listed
* above. Every interval usecs the provided callback will be called on each
* active vcore according to that policy. */
int enable_pvcalarms(int method, uint64_t interval, void (*callback) (void))
{
static parlib_once_t once = PARLIB_ONCE_INIT;
assert(!in_vcore_context());
if (method != PVCALARM_REAL && method != PVCALARM_PROF)
return EINVAL;
if (atomic_cas(&global_pvcalarm.state, S_ENABLED, S_ENABLED))
return EALREADY;
if (!atomic_cas(&global_pvcalarm.state, S_DISABLED, S_ENABLING))
return EBUSY;
parlib_run_once(&once, init_global_pvcalarm, NULL);
global_pvcalarm.interval = usec2tsc(interval);
global_pvcalarm.callback = callback;
global_pvcalarm.busy_count = 0;
switch (method) {
case PVCALARM_REAL:
global_pvcalarm.handler = handle_alarm_real;
break;
case PVCALARM_PROF:
global_pvcalarm.handler = handle_alarm_prof;
break;
}
/* Start the timer on all vcores to go off after interval usecs */
for (int i=0; i<max_vcores(); i++) {
start_pvcalarm(&global_pvcalarm.data[i],
global_pvcalarm.interval);
}
atomic_set(&global_pvcalarm.state, S_ENABLED);
return 0;
}
/* Disable the currently active per-vcore alarm service */
int disable_pvcalarms()
{
assert(!in_vcore_context());
if (atomic_cas(&global_pvcalarm.state, S_DISABLED, S_DISABLED))
return EALREADY;
if (!atomic_cas(&global_pvcalarm.state, S_ENABLED, S_DISABLING))
return EBUSY;
/* We loop here to let any vcores currently running code associated with
* the pvcalarms to finish what they are doing before we disable the
* pvcalarm service. Since we ensure that this function is only called
* from non-vcore context, this is OK. */
while(global_pvcalarm.busy_count != 0)
cpu_relax();
global_pvcalarm.interval = 0;
global_pvcalarm.callback = NULL;
global_pvcalarm.handler = NULL;
/* Stop the timer on all vcores */
for (int i=0; i<max_vcores(); i++)
stop_pvcalarm(&global_pvcalarm.data[i]);
atomic_set(&global_pvcalarm.state, S_DISABLED);
return 0;
}
/* Initialize a specific pvcalarm. This happens once per vcore as it comes
* online and the pvcalarm service is active */
static void init_pvcalarm(struct pvcalarm_data *pvcalarm_data, int vcoreid)
{
int ctlfd, timerfd, alarmid, ev_flags;
struct event_queue *ev_q;
if (devalarm_get_fds(&ctlfd, &timerfd, &alarmid)) {
perror("Pvcalarm: alarm setup");
return;
}
register_ev_handler(EV_ALARM, handle_pvcalarm, 0);
ev_flags = EVENT_IPI | EVENT_VCORE_PRIVATE;
ev_q = get_eventq_vcpd(vcoreid, ev_flags);
if (!ev_q) {
perror("Pvcalarm: Failed ev_q");
return;
}
ev_q->ev_vcore = vcoreid;
ev_q->ev_flags = ev_flags;
if (devalarm_set_evq(timerfd, ev_q, alarmid)) {
perror("Pvcalarm: Failed to set evq");
return;
}
/* now the alarm is all set, just need to write the timer whenever we
* want it to go off. */
pvcalarm_data->alarmid = alarmid;
pvcalarm_data->ctlfd = ctlfd;
pvcalarm_data->timerfd = timerfd;
}
/* TODO: implement a way to completely remove each per-vcore alarm and
* deregister it from the #alarm device */
/* A preamble function to run anytime we are about to do anything on behalf of
* the pvcalarms while in vcore context. This preamble is necessary to ensure
* we maintain proper invariants when enabling and disabling the pvcalarm
* service in a running application. */
static inline bool __vcore_preamble()
{
int state;
assert(in_vcore_context());
__sync_fetch_and_add(&global_pvcalarm.busy_count, 1);
/* order the state read after the incref. __sync provides cpu mb */
cmb();
state = atomic_read(&global_pvcalarm.state);
if (state == S_DISABLED || state == S_DISABLING)
goto disabled;
return true;
disabled:
__sync_fetch_and_add(&global_pvcalarm.busy_count, -1);
return false;
}
/* The counterpart to the __vcore_preamble() function */
static inline void __vcore_postamble()
{
__sync_fetch_and_add(&global_pvcalarm.busy_count, -1);
}
/* The global handler function. It simply calls the proper underlying handler
* function depending on whether the service is set for the REAL or PERF
* policy. */
static void handle_pvcalarm(struct event_msg *ev_msg, unsigned int ev_type,
void *data)
{
struct pvcalarm_data *pvcalarm_data = &global_pvcalarm.data[vcore_id()];
if (devalarm_get_id(ev_msg) != pvcalarm_data->alarmid)
return;
if (!__vcore_preamble()) return;
global_pvcalarm.handler(ev_msg, ev_type, data);
__vcore_postamble();
}
/* The pvcalarm handler for the REAL policy. Simply call the registered
* callback and restart the interval alarm. */
static void handle_alarm_real(struct event_msg *ev_msg, unsigned int ev_type,
void *data)
{
global_pvcalarm.callback();
start_pvcalarm(&global_pvcalarm.data[vcore_id()],
global_pvcalarm.interval);
}
/* The pvcalarm handler for the PROF policy. Account for any time the vcore
* has been offline. Only when the uptime since the last interval is equal to
* the interval time do we run the callback function. Otherwise we restart the
* alarm to make up the difference. */
static void handle_alarm_prof(struct event_msg *ev_msg, unsigned int ev_type,
void *data)
{
int vcoreid = vcore_id();
struct pvcalarm_data *pvcalarm_data = &global_pvcalarm.data[vcoreid];
uint64_t uptime = vcore_account_uptime_ticks(vcoreid);
uint64_t diff = uptime - pvcalarm_data->start_uptime;
if (diff < global_pvcalarm.interval) {
uint64_t remaining = global_pvcalarm.interval - diff;
run_pvcalarm(pvcalarm_data, remaining);
} else {
global_pvcalarm.callback();
start_pvcalarm(pvcalarm_data, global_pvcalarm.interval);
}
}