2010-07-13 01:14:40 +02:00
|
|
|
/* Helper functions that allow driver writers to easily busy-wait (spin) for a
|
|
|
|
* condition to become satisfied within a certain maximum time span.
|
|
|
|
*/
|
|
|
|
/* This implementation first spins without making any system calls for a
|
|
|
|
* while, and then starts using system calls (specifically, the system call to
|
|
|
|
* obtain the current time) while spinning. The reason for this is that in
|
|
|
|
* many cases, the condition to be checked will become satisfied rather
|
|
|
|
* quickly, and we want to avoid getting descheduled in that case. However,
|
|
|
|
* after a while, running out of scheduling quantum will cause our priority to
|
|
|
|
* be lowered, and we can avoid this by voluntarily giving up the CPU, by
|
|
|
|
* making a system call.
|
|
|
|
*/
|
|
|
|
#include "sysutil.h"
|
|
|
|
#include <minix/spin.h>
|
2011-06-23 19:25:36 +02:00
|
|
|
#include <minix/minlib.h>
|
2010-07-13 01:14:40 +02:00
|
|
|
|
|
|
|
/* Number of microseconds to keep spinning initially, without performing a
|
|
|
|
* system call. We pick a value somewhat smaller than a typical clock tick.
|
|
|
|
* Note that for the above reasons, we want to avoid using sys_hz() here.
|
|
|
|
*/
|
|
|
|
#define TSC_SPIN 1000 /* in microseconds */
|
|
|
|
|
|
|
|
/* Internal spin states. */
|
|
|
|
enum {
|
|
|
|
STATE_INIT, /* simply check the condition (once) */
|
|
|
|
STATE_BASE_TS, /* get the initial TSC value (once) */
|
|
|
|
STATE_TS, /* use the TSC to spin (up to TSC_SPIN us) */
|
|
|
|
STATE_UPTIME /* use the clock to spin */
|
|
|
|
};
|
|
|
|
|
2012-03-25 20:25:53 +02:00
|
|
|
void spin_init(spin_t *s, u32_t usecs)
|
2010-07-13 01:14:40 +02:00
|
|
|
{
|
|
|
|
/* Initialize the given spin state structure, set to spin at most the
|
|
|
|
* given number of microseconds.
|
|
|
|
*/
|
|
|
|
s->s_state = STATE_INIT;
|
|
|
|
s->s_usecs = usecs;
|
|
|
|
s->s_timeout = FALSE;
|
|
|
|
}
|
|
|
|
|
2012-03-25 20:25:53 +02:00
|
|
|
int spin_check(spin_t *s)
|
2010-07-13 01:14:40 +02:00
|
|
|
{
|
|
|
|
/* Check whether a timeout has taken place. Return TRUE if the caller
|
|
|
|
* should continue spinning, and FALSE if a timeout has occurred. The
|
|
|
|
* implementation assumes that it is okay to spin a little bit too long
|
|
|
|
* (up to a full clock tick extra).
|
|
|
|
*/
|
|
|
|
u64_t cur_tsc, tsc_delta;
|
|
|
|
clock_t now, micro_delta;
|
|
|
|
|
|
|
|
switch (s->s_state) {
|
|
|
|
case STATE_INIT:
|
|
|
|
s->s_state = STATE_BASE_TS;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case STATE_BASE_TS:
|
|
|
|
s->s_state = STATE_TS;
|
|
|
|
read_tsc_64(&s->s_base_tsc);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case STATE_TS:
|
|
|
|
read_tsc_64(&cur_tsc);
|
|
|
|
|
|
|
|
tsc_delta = sub64(cur_tsc, s->s_base_tsc);
|
|
|
|
|
|
|
|
micro_delta = tsc_64_to_micros(tsc_delta);
|
|
|
|
|
|
|
|
if (micro_delta >= s->s_usecs) {
|
|
|
|
s->s_timeout = TRUE;
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (micro_delta >= TSC_SPIN) {
|
|
|
|
s->s_usecs -= micro_delta;
|
|
|
|
getuptime(&s->s_base_uptime);
|
|
|
|
s->s_state = STATE_UPTIME;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case STATE_UPTIME:
|
|
|
|
getuptime(&now);
|
|
|
|
|
|
|
|
/* We assume that sys_hz() caches its return value. */
|
|
|
|
micro_delta = ((now - s->s_base_uptime) * 1000 / sys_hz()) *
|
|
|
|
1000;
|
|
|
|
|
|
|
|
if (micro_delta >= s->s_usecs) {
|
|
|
|
s->s_timeout = TRUE;
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
panic("spin_check: invalid state %d", s->s_state);
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|