timer works in bochs and on physical machine. need global definition for bochs...
[akaros.git] / kern / apic.c
1 /*
2  * Copyright (c) 2009 The Regents of the University of California
3  * See LICENSE for details.
4  */
5
6 #include <inc/mmu.h>
7 #include <inc/x86.h>
8 #include <inc/assert.h>
9
10 #include <kern/apic.h>
11
12 uint64_t tsc_freq = 0;
13
14 /*
15  * Remaps the Programmable Interrupt Controller to use IRQs 32-47
16  * http://wiki.osdev.org/PIC
17  * Not 100% on this stuff, after looking over 
18  * http://bochs.sourceforge.net/techspec/PORTS.LST  The cascading and other 
19  * stuff might need to be in one command, and after that all we are doing
20  * is toggling masks.
21  */
22 void pic_remap() 
23 {
24         // start initialization
25         outb(PIC1_CMD, 0x11);
26         outb(PIC2_CMD, 0x11);
27         // set new offsets
28         outb(PIC1_DATA, PIC1_OFFSET);
29         outb(PIC2_DATA, PIC2_OFFSET);
30         // set up cascading
31         outb(PIC1_DATA, 0x04);
32         outb(PIC2_DATA, 0x02);
33         // other stuff (put in 8086/88 mode, or whatever)
34         outb(PIC1_DATA, 0x01);
35         outb(PIC2_DATA, 0x01);
36         // set masks, defaulting to all masked for now
37         outb(PIC1_DATA, 0xff);
38         outb(PIC2_DATA, 0xff);
39 }
40
41 void pic_mask_irq(uint8_t irq)
42 {
43         if (irq > 7)
44                 outb(PIC2_DATA, inb(PIC2_DATA) | (1 << (irq - 8)));
45         else
46                 outb(PIC1_DATA, inb(PIC1_DATA) | (1 << irq));
47 }
48
49 void pic_unmask_irq(uint8_t irq)
50 {
51         if (irq > 7) {
52                 outb(PIC2_DATA, inb(PIC2_DATA) & ~(1 << (irq - 8)));
53                 outb(PIC1_DATA, inb(PIC1_DATA) & 0xfd); // make sure irq2 is unmasked
54         } else
55                 outb(PIC1_DATA, inb(PIC1_DATA) & ~(1 << irq));
56 }
57
58 /*
59  * Sets the LAPIC timer to go off after a certain number of ticks.  The primary
60  * clock freq is actually the bus clock, so we really will need to figure out
61  * the timing of the LAPIC timer via other timing.  For now, set it to a
62  * certain number of ticks, and specify an interrupt vector to send to the CPU.
63  * Unmasking is implied.  Ref SDM, 3A, 9.6.4
64  */
65 void lapic_set_timer(uint32_t ticks, uint8_t vector, bool periodic)
66 {
67         // divide the bus clock.  going with the max (128) for now (which is slow)
68         write_mmreg32(LAPIC_TIMER_DIVIDE, 0xa);
69         // set LVT with interrupt handling information
70         write_mmreg32(LAPIC_LVT_TIMER, vector | (periodic << 17));
71         write_mmreg32(LAPIC_TIMER_INIT, ticks);
72         // For debugging when we expand this
73         //cprintf("LAPIC LVT Timer: 0x%08x\n", read_mmreg32(LAPIC_LVT_TIMER));
74         //cprintf("LAPIC Init Count: 0x%08x\n", read_mmreg32(LAPIC_TIMER_INIT));
75         //cprintf("LAPIC Current Count: 0x%08x\n", read_mmreg32(LAPIC_TIMER_CURRENT));
76 }
77
78 uint32_t lapic_get_default_id(void)
79 {
80         uint32_t ebx;
81         cpuid(1, 0, &ebx, 0, 0);
82         // p6 family only uses 4 bits here, and 0xf is reserved for the IOAPIC
83         return (ebx & 0xFF000000) >> 24;
84 }
85
86 void timer_init(void){
87         uint64_t tscval[2];
88         pit_set_timer(0xffff, TIMER_RATEGEN, 1);
89         // assume tsc exist
90         tscval[0] = read_tsc();
91         udelay_pit(1000000);
92         tscval[1] = read_tsc();
93         tsc_freq = tscval[1] - tscval[0];
94         cprintf("tsc_freq %lu\n", tsc_freq);
95 }
96
97 void pit_set_timer(uint32_t divisor, uint32_t mode, bool periodic)
98 {
99         if (divisor & 0xffff0000)
100                 warn("Divisor too large!");
101         // TODO: review periodic
102         mode = TIMER_SEL0|TIMER_16BIT|mode;
103         outb(TIMER_MODE, mode | (periodic << 2));
104         outb(TIMER_CNTR0, divisor & 0xff);
105         outb(TIMER_CNTR0, (divisor >> 8) );
106         cprintf("timer mode set to %d, divisor %d\n",mode|(periodic << 2), divisor);
107 }
108
109 static int getpit()
110 {
111     int high, low;
112         // TODO: need a lock to protect access to PIT
113
114     /* Select timer0 and latch counter value. */
115     outb(TIMER_MODE, TIMER_SEL0 | TIMER_LATCH);
116     
117     low = inb(TIMER_CNTR0);
118     high = inb(TIMER_CNTR0);
119
120     return ((high << 8) | low);
121 }
122
123 // forces cpu to relax for usec miliseconds
124 void udelay(uint64_t usec)
125 {
126         if (tsc_freq != 0)
127         {
128                 uint64_t start, end, now;
129
130                 start = read_tsc();
131         end = start + (tsc_freq * usec) / 1000000;
132         //cprintf("start %llu, end %llu\n", start, end);
133                 if (end == 0) cprintf("This is terribly wrong \n");
134                 do {
135             cpu_relax();
136             now = read_tsc();
137                         //cprintf("now %llu\n", now);
138                 } while (now < end || (now > start && end < start));
139         return;
140
141         } else
142         {
143                 udelay_pit(usec);
144         }
145 }
146
147 void udelay_pit(uint64_t usec)
148 {
149         
150         int64_t delta, prev_tick, tick, ticks_left;
151         prev_tick = getpit();
152         /*
153          * Calculate (n * (i8254_freq / 1e6)) without using floating point
154          * and without any avoidable overflows.
155          */
156         if (usec <= 0)
157                 ticks_left = 0;
158         // some optimization from bsd code
159         else if (usec < 256)
160                 /*
161                  * Use fixed point to avoid a slow division by 1000000.
162                  * 39099 = 1193182 * 2^15 / 10^6 rounded to nearest.
163                  * 2^15 is the first power of 2 that gives exact results
164                  * for n between 0 and 256.
165                  */
166                 ticks_left = ((uint64_t)usec * 39099 + (1 << 15) - 1) >> 15;
167         else
168                 // round up the ticks left
169                 ticks_left = ((uint64_t)usec * (long long)PIT_FREQ+ 999999)
170                              / 1000000; 
171         cprintf("ticks left %llu \n" , ticks_left);
172         while (ticks_left > 0) {
173                 tick = getpit();
174                 delta = prev_tick - tick;
175                 prev_tick = tick;
176                 if (delta < 0) {
177                         // counter looped around during the delta time period
178                         delta += 0xffff; // maximum count 
179                         if (delta < 0)
180                                 delta = 0;
181                 }
182                 ticks_left -= delta;
183         }
184 }