1 | // The local APIC manages internal (non-I/O) interrupts. |
2 | // See Chapter 8 & Appendix C of Intel processor manual volume 3. |
3 | |
4 | #include "param.h" |
5 | #include "types.h" |
6 | #include "defs.h" |
7 | #include "date.h" |
8 | #include "memlayout.h" |
9 | #include "traps.h" |
10 | #include "mmu.h" |
11 | #include "x86.h" |
12 | |
13 | // Local APIC registers, divided by 4 for use as uint[] indices. |
14 | #define ID (0x0020/4) // ID |
15 | #define VER (0x0030/4) // Version |
16 | #define TPR (0x0080/4) // Task Priority |
17 | #define EOI (0x00B0/4) // EOI |
18 | #define SVR (0x00F0/4) // Spurious Interrupt Vector |
19 | #define ENABLE 0x00000100 // Unit Enable |
20 | #define ESR (0x0280/4) // Error Status |
21 | #define ICRLO (0x0300/4) // Interrupt Command |
22 | #define INIT 0x00000500 // INIT/RESET |
23 | #define STARTUP 0x00000600 // Startup IPI |
24 | #define DELIVS 0x00001000 // Delivery status |
25 | #define ASSERT 0x00004000 // Assert interrupt (vs deassert) |
26 | #define DEASSERT 0x00000000 |
27 | #define LEVEL 0x00008000 // Level triggered |
28 | #define BCAST 0x00080000 // Send to all APICs, including self. |
29 | #define BUSY 0x00001000 |
30 | #define FIXED 0x00000000 |
31 | #define ICRHI (0x0310/4) // Interrupt Command [63:32] |
32 | #define TIMER (0x0320/4) // Local Vector Table 0 (TIMER) |
33 | #define X1 0x0000000B // divide counts by 1 |
34 | #define PERIODIC 0x00020000 // Periodic |
35 | #define PCINT (0x0340/4) // Performance Counter LVT |
36 | #define LINT0 (0x0350/4) // Local Vector Table 1 (LINT0) |
37 | #define LINT1 (0x0360/4) // Local Vector Table 2 (LINT1) |
38 | #define ERROR (0x0370/4) // Local Vector Table 3 (ERROR) |
39 | #define MASKED 0x00010000 // Interrupt masked |
40 | #define TICR (0x0380/4) // Timer Initial Count |
41 | #define TCCR (0x0390/4) // Timer Current Count |
42 | #define TDCR (0x03E0/4) // Timer Divide Configuration |
43 | |
44 | volatile uint *lapic; // Initialized in mp.c |
45 | |
46 | //PAGEBREAK! |
47 | static void |
48 | lapicw(int index, int value) |
49 | { |
50 | lapic[index] = value; |
51 | lapic[ID]; // wait for write to finish, by reading |
52 | } |
53 | |
54 | void |
55 | lapicinit(void) |
56 | { |
57 | if(!lapic) |
58 | return; |
59 | |
60 | // Enable local APIC; set spurious interrupt vector. |
61 | lapicw(SVR, ENABLE | (T_IRQ0 + IRQ_SPURIOUS)); |
62 | |
63 | // The timer repeatedly counts down at bus frequency |
64 | // from lapic[TICR] and then issues an interrupt. |
65 | // If xv6 cared more about precise timekeeping, |
66 | // TICR would be calibrated using an external time source. |
67 | lapicw(TDCR, X1); |
68 | lapicw(TIMER, PERIODIC | (T_IRQ0 + IRQ_TIMER)); |
69 | lapicw(TICR, 10000000); |
70 | |
71 | // Disable logical interrupt lines. |
72 | lapicw(LINT0, MASKED); |
73 | lapicw(LINT1, MASKED); |
74 | |
75 | // Disable performance counter overflow interrupts |
76 | // on machines that provide that interrupt entry. |
77 | if(((lapic[VER]>>16) & 0xFF) >= 4) |
78 | lapicw(PCINT, MASKED); |
79 | |
80 | // Map error interrupt to IRQ_ERROR. |
81 | lapicw(ERROR, T_IRQ0 + IRQ_ERROR); |
82 | |
83 | // Clear error status register (requires back-to-back writes). |
84 | lapicw(ESR, 0); |
85 | lapicw(ESR, 0); |
86 | |
87 | // Ack any outstanding interrupts. |
88 | lapicw(EOI, 0); |
89 | |
90 | // Send an Init Level De-Assert to synchronise arbitration ID's. |
91 | lapicw(ICRHI, 0); |
92 | lapicw(ICRLO, BCAST | INIT | LEVEL); |
93 | while(lapic[ICRLO] & DELIVS) |
94 | ; |
95 | |
96 | // Enable interrupts on the APIC (but not on the processor). |
97 | lapicw(TPR, 0); |
98 | } |
99 | |
100 | int |
101 | lapicid(void) |
102 | { |
103 | if (!lapic) |
104 | return 0; |
105 | return lapic[ID] >> 24; |
106 | } |
107 | |
108 | // Acknowledge interrupt. |
109 | void |
110 | lapiceoi(void) |
111 | { |
112 | if(lapic) |
113 | lapicw(EOI, 0); |
114 | } |
115 | |
116 | // Spin for a given number of microseconds. |
117 | // On real hardware would want to tune this dynamically. |
118 | void |
119 | microdelay(int us) |
120 | { |
121 | } |
122 | |
123 | #define CMOS_PORT 0x70 |
124 | #define CMOS_RETURN 0x71 |
125 | |
126 | // Start additional processor running entry code at addr. |
127 | // See Appendix B of MultiProcessor Specification. |
128 | void |
129 | lapicstartap(uchar apicid, uint addr) |
130 | { |
131 | int i; |
132 | ushort *wrv; |
133 | |
134 | // "The BSP must initialize CMOS shutdown code to 0AH |
135 | // and the warm reset vector (DWORD based at 40:67) to point at |
136 | // the AP startup code prior to the [universal startup algorithm]." |
137 | outb(CMOS_PORT, 0xF); // offset 0xF is shutdown code |
138 | outb(CMOS_PORT+1, 0x0A); |
139 | wrv = (ushort*)P2V((0x40<<4 | 0x67)); // Warm reset vector |
140 | wrv[0] = 0; |
141 | wrv[1] = addr >> 4; |
142 | |
143 | // "Universal startup algorithm." |
144 | // Send INIT (level-triggered) interrupt to reset other CPU. |
145 | lapicw(ICRHI, apicid<<24); |
146 | lapicw(ICRLO, INIT | LEVEL | ASSERT); |
147 | microdelay(200); |
148 | lapicw(ICRLO, INIT | LEVEL); |
149 | microdelay(100); // should be 10ms, but too slow in Bochs! |
150 | |
151 | // Send startup IPI (twice!) to enter code. |
152 | // Regular hardware is supposed to only accept a STARTUP |
153 | // when it is in the halted state due to an INIT. So the second |
154 | // should be ignored, but it is part of the official Intel algorithm. |
155 | // Bochs complains about the second one. Too bad for Bochs. |
156 | for(i = 0; i < 2; i++){ |
157 | lapicw(ICRHI, apicid<<24); |
158 | lapicw(ICRLO, STARTUP | (addr>>12)); |
159 | microdelay(200); |
160 | } |
161 | } |
162 | |
163 | #define CMOS_STATA 0x0a |
164 | #define CMOS_STATB 0x0b |
165 | #define CMOS_UIP (1 << 7) // RTC update in progress |
166 | |
167 | #define SECS 0x00 |
168 | #define MINS 0x02 |
169 | #define HOURS 0x04 |
170 | #define DAY 0x07 |
171 | #define MONTH 0x08 |
172 | #define YEAR 0x09 |
173 | |
174 | static uint |
175 | cmos_read(uint reg) |
176 | { |
177 | outb(CMOS_PORT, reg); |
178 | microdelay(200); |
179 | |
180 | return inb(CMOS_RETURN); |
181 | } |
182 | |
183 | static void |
184 | fill_rtcdate(struct rtcdate *r) |
185 | { |
186 | r->second = cmos_read(SECS); |
187 | r->minute = cmos_read(MINS); |
188 | r->hour = cmos_read(HOURS); |
189 | r->day = cmos_read(DAY); |
190 | r->month = cmos_read(MONTH); |
191 | r->year = cmos_read(YEAR); |
192 | } |
193 | |
194 | // qemu seems to use 24-hour GWT and the values are BCD encoded |
195 | void |
196 | cmostime(struct rtcdate *r) |
197 | { |
198 | struct rtcdate t1, t2; |
199 | int sb, bcd; |
200 | |
201 | sb = cmos_read(CMOS_STATB); |
202 | |
203 | bcd = (sb & (1 << 2)) == 0; |
204 | |
205 | // make sure CMOS doesn't modify time while we read it |
206 | for(;;) { |
207 | fill_rtcdate(&t1); |
208 | if(cmos_read(CMOS_STATA) & CMOS_UIP) |
209 | continue; |
210 | fill_rtcdate(&t2); |
211 | if(memcmp(&t1, &t2, sizeof(t1)) == 0) |
212 | break; |
213 | } |
214 | |
215 | // convert |
216 | if(bcd) { |
217 | #define CONV(x) (t1.x = ((t1.x >> 4) * 10) + (t1.x & 0xf)) |
218 | CONV(second); |
219 | CONV(minute); |
220 | CONV(hour ); |
221 | CONV(day ); |
222 | CONV(month ); |
223 | CONV(year ); |
224 | #undef CONV |
225 | } |
226 | |
227 | *r = t1; |
228 | r->year += 2000; |
229 | } |
230 | |