1433d6423SLionel Sambuc /* Helper functions that allow driver writers to easily busy-wait (spin) for a
2433d6423SLionel Sambuc * condition to become satisfied within a certain maximum time span.
3433d6423SLionel Sambuc */
4433d6423SLionel Sambuc /* This implementation first spins without making any system calls for a
5433d6423SLionel Sambuc * while, and then starts using system calls (specifically, the system call to
6433d6423SLionel Sambuc * obtain the current time) while spinning. The reason for this is that in
7433d6423SLionel Sambuc * many cases, the condition to be checked will become satisfied rather
8433d6423SLionel Sambuc * quickly, and we want to avoid getting descheduled in that case. However,
9433d6423SLionel Sambuc * after a while, running out of scheduling quantum will cause our priority to
10433d6423SLionel Sambuc * be lowered, and we can avoid this by voluntarily giving up the CPU, by
11433d6423SLionel Sambuc * making a system call.
12433d6423SLionel Sambuc */
13433d6423SLionel Sambuc #include "sysutil.h"
14433d6423SLionel Sambuc #include <minix/spin.h>
15433d6423SLionel Sambuc #include <minix/minlib.h>
16433d6423SLionel Sambuc
17433d6423SLionel Sambuc /* Number of microseconds to keep spinning initially, without performing a
18433d6423SLionel Sambuc * system call. We pick a value somewhat smaller than a typical clock tick.
19433d6423SLionel Sambuc * Note that for the above reasons, we want to avoid using sys_hz() here.
20433d6423SLionel Sambuc */
21433d6423SLionel Sambuc #define TSC_SPIN 1000 /* in microseconds */
22433d6423SLionel Sambuc
23433d6423SLionel Sambuc /* Internal spin states. */
24433d6423SLionel Sambuc enum {
25433d6423SLionel Sambuc STATE_INIT, /* simply check the condition (once) */
26433d6423SLionel Sambuc STATE_BASE_TS, /* get the initial TSC value (once) */
27433d6423SLionel Sambuc STATE_TS, /* use the TSC to spin (up to TSC_SPIN us) */
28433d6423SLionel Sambuc STATE_UPTIME /* use the clock to spin */
29433d6423SLionel Sambuc };
30433d6423SLionel Sambuc
spin_init(spin_t * s,u32_t usecs)31433d6423SLionel Sambuc void spin_init(spin_t *s, u32_t usecs)
32433d6423SLionel Sambuc {
33433d6423SLionel Sambuc /* Initialize the given spin state structure, set to spin at most the
34433d6423SLionel Sambuc * given number of microseconds.
35433d6423SLionel Sambuc */
36433d6423SLionel Sambuc s->s_state = STATE_INIT;
37433d6423SLionel Sambuc s->s_usecs = usecs;
38433d6423SLionel Sambuc s->s_timeout = FALSE;
39433d6423SLionel Sambuc }
40433d6423SLionel Sambuc
spin_check(spin_t * s)41433d6423SLionel Sambuc int spin_check(spin_t *s)
42433d6423SLionel Sambuc {
43433d6423SLionel Sambuc /* Check whether a timeout has taken place. Return TRUE if the caller
44433d6423SLionel Sambuc * should continue spinning, and FALSE if a timeout has occurred. The
45433d6423SLionel Sambuc * implementation assumes that it is okay to spin a little bit too long
46433d6423SLionel Sambuc * (up to a full clock tick extra).
47433d6423SLionel Sambuc */
48433d6423SLionel Sambuc u64_t cur_tsc, tsc_delta;
49433d6423SLionel Sambuc clock_t now, micro_delta;
50433d6423SLionel Sambuc
51433d6423SLionel Sambuc switch (s->s_state) {
52433d6423SLionel Sambuc case STATE_INIT:
53433d6423SLionel Sambuc s->s_state = STATE_BASE_TS;
54433d6423SLionel Sambuc break;
55433d6423SLionel Sambuc
56433d6423SLionel Sambuc case STATE_BASE_TS:
57433d6423SLionel Sambuc s->s_state = STATE_TS;
58433d6423SLionel Sambuc read_tsc_64(&s->s_base_tsc);
59433d6423SLionel Sambuc break;
60433d6423SLionel Sambuc
61433d6423SLionel Sambuc case STATE_TS:
62433d6423SLionel Sambuc read_tsc_64(&cur_tsc);
63433d6423SLionel Sambuc
64433d6423SLionel Sambuc tsc_delta = cur_tsc - s->s_base_tsc;
65433d6423SLionel Sambuc
66433d6423SLionel Sambuc micro_delta = tsc_64_to_micros(tsc_delta);
67433d6423SLionel Sambuc
68433d6423SLionel Sambuc if (micro_delta >= s->s_usecs) {
69433d6423SLionel Sambuc s->s_timeout = TRUE;
70433d6423SLionel Sambuc return FALSE;
71433d6423SLionel Sambuc }
72433d6423SLionel Sambuc
73433d6423SLionel Sambuc if (micro_delta >= TSC_SPIN) {
74433d6423SLionel Sambuc s->s_usecs -= micro_delta;
75*d91f738bSDavid van Moolenbroek s->s_base_uptime = getticks();
76433d6423SLionel Sambuc s->s_state = STATE_UPTIME;
77433d6423SLionel Sambuc }
78433d6423SLionel Sambuc
79433d6423SLionel Sambuc break;
80433d6423SLionel Sambuc
81433d6423SLionel Sambuc case STATE_UPTIME:
82*d91f738bSDavid van Moolenbroek now = getticks();
83433d6423SLionel Sambuc
84433d6423SLionel Sambuc /* We assume that sys_hz() caches its return value. */
85433d6423SLionel Sambuc micro_delta = ((now - s->s_base_uptime) * 1000 / sys_hz()) *
86433d6423SLionel Sambuc 1000;
87433d6423SLionel Sambuc
88433d6423SLionel Sambuc if (micro_delta >= s->s_usecs) {
89433d6423SLionel Sambuc s->s_timeout = TRUE;
90433d6423SLionel Sambuc return FALSE;
91433d6423SLionel Sambuc }
92433d6423SLionel Sambuc
93433d6423SLionel Sambuc break;
94433d6423SLionel Sambuc
95433d6423SLionel Sambuc default:
96433d6423SLionel Sambuc panic("spin_check: invalid state %d", s->s_state);
97433d6423SLionel Sambuc }
98433d6423SLionel Sambuc
99433d6423SLionel Sambuc return TRUE;
100433d6423SLionel Sambuc }
101