| /* 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 <parlib/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); | 
 | 	} | 
 | } |