| 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 | |