2 * GeekOS timer interrupt support
3 * Copyright (c) 2001,2003 David H. Hovemeyer <daveho@cs.umd.edu>
4 * Copyright (c) 2003, Jeffrey K. Hollingsworth <hollings@cs.umd.edu>
7 * This is free software. You are permitted to use,
8 * redistribute, and modify it as specified in the file "COPYING".
12 #include <geekos/io.h>
13 #include <geekos/int.h>
14 #include <geekos/irq.h>
15 #include <geekos/kthread.h>
16 #include <geekos/timer.h>
18 #include <geekos/serial.h>
19 #include <geekos/debug.h>
22 /* PAD this currently is in nvram.c */
23 extern void deliver_timer_interrupt_to_vmm(uint_t period_us);
25 /* JRL Add a cpu frequency measurement */
29 #define __SLOW_DOWN_IO "\noutb %%al,$0x80"
32 #define __FULL_SLOW_DOWN_IO __SLOW_DOWN_IO __SLOW_DOWN_IO __SLOW_DOWN_IO __SLOW_DOWN_IO
34 #define __FULL_SLOW_DOWN_IO __SLOW_DOWN_IO
40 static inline void out##s(unsigned x value, unsigned short port) {
42 #define __OUT2(s,s1,s2) \
43 __asm__ __volatile__ ("out" #s " %" s1 "0,%" s2 "1"
45 #define __OUT(s,s1,x) \
46 __OUT1(s,x) __OUT2(s,s1,"w") : : "a" (value), "Nd" (port)); } \
47 __OUT1(s##_p,x) __OUT2(s,s1,"w") __FULL_SLOW_DOWN_IO : : "a" (value), "Nd" (port));} \
51 static inline RETURN_TYPE in##s(unsigned short port) { RETURN_TYPE _v;
53 #define __IN2(s,s1,s2) \
54 __asm__ __volatile__ ("in" #s " %" s2 "1,%" s1 "0"
56 #define __IN(s,s1,i...) \
57 __IN1(s) __IN2(s,s1,"w") : "=a" (_v) : "Nd" (port) ,##i ); return _v; } \
58 __IN1(s##_p) __IN2(s,s1,"w") __FULL_SLOW_DOWN_IO : "=a" (_v) : "Nd" (port) ,##i ); return _v; } \
61 #define RETURN_TYPE unsigned char
64 #define RETURN_TYPE unsigned short
67 #define RETURN_TYPE unsigned int
82 #define rdtscll(val) \
83 __asm__ __volatile__("rdtsc" : "=A" (val))
85 #elif defined(__x86_64__)
87 #define rdtscll(val) do { \
89 asm volatile("rdtsc" : "=a" (a), "=d" (d)); \
90 (val) = ((unsigned long)a) | (((unsigned long)d)<<32); \
95 #define do_div(n,base) ({ \
96 unsigned long __upper, __low, __high, __mod, __base; \
98 asm("":"=a" (__low), "=d" (__high):"A" (n)); \
101 __upper = __high % (__base); \
102 __high = __high / (__base); \
104 asm("divl %2":"=a" (__low), "=d" (__mod):"rm" (__base), "0" (__low), "1" (__upper)); \
105 asm("":"=A" (n):"a" (__low),"d" (__high)); \
111 * This uses the Programmable Interval Timer that is standard on all
112 * PC-compatible systems to determine the time stamp counter frequency.
114 * This uses the speaker output (channel 2) of the PIT. This is better than
115 * using the timer interrupt output because we can read the value of the
116 * speaker with just one inb(), where we need three i/o operations for the
117 * interrupt channel. We count how many ticks the TSC does in 50 ms.
119 * Returns the detected time stamp counter frequency in KHz.
124 pit_calibrate_tsc(void)
128 // unsigned long flags;
129 unsigned long pit_tick_rate = 1193182UL; /* 1.193182 MHz */
131 // spin_lock_irqsave(&pit_lock, flags);
133 outb((inb(0x61) & ~0x02) | 0x01, 0x61);
136 outb((pit_tick_rate / (1000 / 50)) & 0xff, 0x42);
137 outb((pit_tick_rate / (1000 / 50)) >> 8, 0x42);
138 // start = get_cycles_sync();
140 while ((inb(0x61) & 0x20) == 0);
142 // end = get_cycles_sync();
144 // spin_unlock_irqrestore(&pit_lock, flags);
147 // return (end - start) / 50;
163 * Global tick counter
165 volatile ulong_t g_numTicks;
167 ulong_t clock_time(void){
173 * Number of times the spin loop can execute during one timer tick
175 static int s_spinCountPerTick;
178 * Number of ticks to wait before calibrating the delay loop.
180 #define CALIBRATE_NUM_TICKS 3
183 * The default quantum; maximum number of ticks a thread can use before
184 * we suspend it and choose another.
186 #define DEFAULT_MAX_TICKS 4
191 int g_Quantum = DEFAULT_MAX_TICKS;
195 * FIXME: should set this to something more reasonable, like 100.
198 //#define TICKS_PER_SEC 18
200 /*#define DEBUG_TIMER */
202 # define Debug(args...) Print(args)
204 # define Debug(args...)
207 /* ----------------------------------------------------------------------
209 * ---------------------------------------------------------------------- */
211 static void Timer_Interrupt_Handler(struct Interrupt_State* state)
213 struct Kernel_Thread* current = g_currentThread;
220 #define STACK_LEN 256
222 SerialPrint("Host Timer Interrupt Handler running\n");
223 SerialPrint("Timer====\n");
224 Dump_Interrupt_State(state);
225 // SerialMemDump((unsigned char*)(¤t),STACK_LEN);
226 SerialPrint("Timer done===\n");
229 /* Update global and per-thread number of ticks */
234 * If thread has been running for an entire quantum,
235 * inform the interrupt return code that we want
236 * to choose a new thread.
238 if (current->numTicks >= g_Quantum) {
239 g_needReschedule = true;
243 deliver_timer_interrupt_to_vmm(1000000/HZ);
249 * Temporary timer interrupt handler used to calibrate
252 static void Timer_Calibrate(struct Interrupt_State* state)
255 if (g_numTicks < CALIBRATE_NUM_TICKS)
259 * Now we can look at EAX, which reflects how many times
260 * the loop has executed
262 /*Print("Timer_Calibrate: eax==%d\n", state->eax);*/
263 s_spinCountPerTick = INT_MAX - state->eax;
264 state->eax = 0; /* make the loop terminate */
270 * Delay loop; spins for given number of iterations.
272 static void Spin(int count)
275 * The assembly code is the logical equivalent of
276 * while (count-- > 0) { // waste some time }
277 * We rely on EAX being used as the counter
282 __asm__ __volatile__ (
285 "nop; nop; nop; nop; nop; nop\n\t"
286 "nop; nop; nop; nop; nop; nop\n\t"
294 * Calibrate the delay loop.
295 * This will initialize s_spinCountPerTick, which indicates
296 * how many iterations of the loop are executed per timer tick.
298 static void Calibrate_Delay(void)
300 Disable_Interrupts();
302 /* Install temporarily interrupt handler */
303 Install_IRQ(TIMER_IRQ, &Timer_Calibrate);
304 Enable_IRQ(TIMER_IRQ);
308 /* Wait a few ticks */
309 while (g_numTicks < CALIBRATE_NUM_TICKS)
313 * Execute the spin loop.xs
314 * The temporary interrupt handler will overwrite the
315 * loop counter when the next tick occurs.
323 Disable_Interrupts();
326 * Mask out the timer IRQ again,
327 * since we will be installing a real timer interrupt handler.
329 Disable_IRQ(TIMER_IRQ);
333 /* ----------------------------------------------------------------------
335 * ---------------------------------------------------------------------- */
337 void Init_Timer(void)
339 ushort_t foo = 1193182L / HZ;
341 cpu_khz_freq = pit_calibrate_tsc();
342 PrintBoth("CPU KHZ=%lu\n", (ulong_t)cpu_khz_freq);
344 PrintBoth("Initializing timer and setting to %d Hz...\n",HZ);
346 /* Calibrate for delay loop */
348 PrintBoth("Delay loop: %d iterations per tick\n", s_spinCountPerTick);
352 Out_Byte(0x43,0x36); // channel 0, LSB/MSB, mode 3, binary
353 Out_Byte(0x40, foo & 0xff); // LSB
354 Out_Byte(0x40, foo >>8); // MSB
356 /* Install an interrupt handler for the timer IRQ */
358 Install_IRQ(TIMER_IRQ, &Timer_Interrupt_Handler);
359 Enable_IRQ(TIMER_IRQ);
363 #define US_PER_TICK (HZ * 1000000)
366 * Spin for at least given number of microseconds.
367 * FIXME: I'm sure this implementation leaves a lot to
370 void Micro_Delay(int us)
372 int num = us * s_spinCountPerTick;
373 int denom = US_PER_TICK;
375 int numSpins = num / denom;
376 int rem = num % denom;
381 Debug("Micro_Delay(): num=%d, denom=%d, spin count = %d\n", num, denom, numSpins);