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