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
44volatile uint *lapic; // Initialized in mp.c
45
46//PAGEBREAK!
47static void
48lapicw(int index, int value)
49{
50 lapic[index] = value;
51 lapic[ID]; // wait for write to finish, by reading
52}
53
54void
55lapicinit(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
100int
101lapicid(void)
102{
103 if (!lapic)
104 return 0;
105 return lapic[ID] >> 24;
106}
107
108// Acknowledge interrupt.
109void
110lapiceoi(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.
118void
119microdelay(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.
128void
129lapicstartap(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
174static uint
175cmos_read(uint reg)
176{
177 outb(CMOS_PORT, reg);
178 microdelay(200);
179
180 return inb(CMOS_RETURN);
181}
182
183static void
184fill_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
195void
196cmostime(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