xref: /xv6-public/lapic.c (revision e559fd2c)
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
lapicw(int index,int value)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
lapicinit(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
lapicid(void)101 lapicid(void)
102 {
103   if (!lapic)
104     return 0;
105   return lapic[ID] >> 24;
106 }
107 
108 // Acknowledge interrupt.
109 void
lapiceoi(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
microdelay(int us)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
lapicstartap(uchar apicid,uint addr)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
cmos_read(uint reg)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
fill_rtcdate(struct rtcdate * r)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
cmostime(struct rtcdate * r)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