xref: /minix3/minix/lib/libsys/arch/i386/spin.c (revision d91f738bd8d93aa6befa2a8d07581040607a512a)
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