--- /dev/null
+#include "types.h"
+#include "defs.h"
+#include "date.h"
+#include "x86.h"
+#include "cmos.h"
+
+uint cmosread(uint reg)
+{
+ if(reg >= CMOS_NMI_BIT)
+ panic("cmosread: invalid register");
+
+ outb(CMOS_PORT, reg);
+ microdelay(200);
+ return inb(CMOS_RETURN);
+}
+
+void cmoswrite(uint reg, uint data)
+{
+ if(reg >= CMOS_NMI_BIT)
+ panic("cmoswrite: invalid register");
+ if(data > 0xff)
+ panic("cmoswrite: invalid data");
+
+ outb(CMOS_PORT, reg);
+ microdelay(200);
+ outb(CMOS_RETURN, data);
+}
+
+static void fill_rtcdate(struct rtcdate *r)
+{
+ r->second = cmosread(CMOS_SECS);
+ r->minute = cmosread(CMOS_MINS);
+ r->hour = cmosread(CMOS_HOURS);
+ r->day = cmosread(CMOS_DAY);
+ r->month = cmosread(CMOS_MONTH);
+ r->year = cmosread(CMOS_YEAR);
+}
+
+// qemu seems to use 24-hour GWT and the values are BCD encoded
+void cmostime(struct rtcdate *r)
+{
+ struct rtcdate t1, t2;
+ int sb, bcd;
+
+ sb = cmosread(CMOS_STATB);
+
+ bcd = (sb & CMOS_BINARY_BIT) == 0;
+
+ // make sure CMOS doesn't modify time while we read it
+ for (;;) {
+ fill_rtcdate(&t1);
+ if (cmosread(CMOS_STATA) & CMOS_UIP_BIT)
+ continue;
+ fill_rtcdate(&t2);
+ if (memcmp(&t1, &t2, sizeof(t1)) == 0)
+ break;
+ }
+
+ // convert
+ if (bcd) {
+#define CONV(x) (t1.x = ((t1.x >> 4) * 10) + (t1.x & 0xf))
+ CONV(second);
+ CONV(minute);
+ CONV(hour );
+ CONV(day );
+ CONV(month );
+ CONV(year );
+#undef CONV
+ }
+
+ *r = t1;
+ r->year += 2000;
+}
--- /dev/null
+// Access is through these two I/O addresses. Select a register by writing to
+// CMOS_PORT, then read/write data from/to that register through CMOS_RETURN.
+//
+// Note that due to general IBM insanity bit 7 of CMOS_PORT controls whether
+// NMIs get to "the" CPU or not. All of xv6 has ignored this issue for years,
+// so we do the same. But this technically makes CMOS_PORT a 7-bit address
+// and the CMOS cannot have more than 128 registers. The original Motorola
+// MC146818 in fact had 64 registers (only AD0-AD5 were used for addresses).
+#define CMOS_PORT 0x70
+ #define CMOS_NMI_BIT (1 << 7) // NMI enabled
+#define CMOS_RETURN 0x71
+
+// The CMOS registers for the real-time clock.
+#define CMOS_SECS 0x00
+#define CMOS_MINS 0x02
+#define CMOS_HOURS 0x04
+#define CMOS_DAY 0x07
+#define CMOS_MONTH 0x08
+#define CMOS_YEAR 0x09
+#define CMOS_STATA 0x0a
+ #define CMOS_UIP_BIT (1 << 7) // RTC update in progress
+#define CMOS_STATB 0x0b
+ #define CMOS_24H_BIT (1 << 1) // RTC 24/12 hour format
+ #define CMOS_BINARY_BIT (1 << 2) // RTC binary/bcd format
+
+// The shutdown status register is used in lapic.c to bring up APs.
+#define CMOS_SHUTDOWN_STAT 0x0f
+ #define CMOS_JMP_DWORD_NO_EOI 0x0a
#include "types.h"
#include "defs.h"
-#include "date.h"
#include "memlayout.h"
#include "traps.h"
#include "mmu.h"
#include "x86.h"
+#include "cmos.h"
// Local APIC registers, divided by 4 for use as uint[] indices.
#define ID (0x0020/4) // ID
void
lapicinit(void)
{
- if(!lapic)
+ if(!lapic)
return;
// Enable local APIC; set spurious interrupt vector.
lapicw(SVR, ENABLE | (T_IRQ0 + IRQ_SPURIOUS));
// The timer repeatedly counts down at bus frequency
- // from lapic[TICR] and then issues an interrupt.
+ // from lapic[TICR] and then issues an interrupt.
// If xv6 cared more about precise timekeeping,
// TICR would be calibrated using an external time source.
lapicw(TDCR, X1);
lapicw(TIMER, PERIODIC | (T_IRQ0 + IRQ_TIMER));
- lapicw(TICR, 10000000);
+ lapicw(TICR, 10000000);
// Disable logical interrupt lines.
lapicw(LINT0, MASKED);
{
}
-#define CMOS_PORT 0x70
-#define CMOS_RETURN 0x71
-
// Start additional processor running entry code at addr.
// See Appendix B of MultiProcessor Specification.
void
{
int i;
ushort *wrv;
-
+
// "The BSP must initialize CMOS shutdown code to 0AH
// and the warm reset vector (DWORD based at 40:67) to point at
// the AP startup code prior to the [universal startup algorithm]."
- outb(CMOS_PORT, 0xF); // offset 0xF is shutdown code
- outb(CMOS_PORT+1, 0x0A);
+ cmoswrite(CMOS_SHUTDOWN_STAT, CMOS_JMP_DWORD_NO_EOI);
wrv = (ushort*)P2V((0x40<<4 | 0x67)); // Warm reset vector
wrv[0] = 0;
wrv[1] = addr >> 4;
microdelay(200);
lapicw(ICRLO, INIT | LEVEL);
microdelay(100); // should be 10ms, but too slow in Bochs!
-
+
// Send startup IPI (twice!) to enter code.
// Regular hardware is supposed to only accept a STARTUP
// when it is in the halted state due to an INIT. So the second
}
}
-#define CMOS_STATA 0x0a
-#define CMOS_STATB 0x0b
-#define CMOS_UIP (1 << 7) // RTC update in progress
-
-#define SECS 0x00
-#define MINS 0x02
-#define HOURS 0x04
-#define DAY 0x07
-#define MONTH 0x08
-#define YEAR 0x09
-
-static uint cmos_read(uint reg)
-{
- outb(CMOS_PORT, reg);
- microdelay(200);
-
- return inb(CMOS_RETURN);
-}
-
-static void fill_rtcdate(struct rtcdate *r)
-{
- r->second = cmos_read(SECS);
- r->minute = cmos_read(MINS);
- r->hour = cmos_read(HOURS);
- r->day = cmos_read(DAY);
- r->month = cmos_read(MONTH);
- r->year = cmos_read(YEAR);
-}
-
-// qemu seems to use 24-hour GWT and the values are BCD encoded
-void cmostime(struct rtcdate *r)
-{
- struct rtcdate t1, t2;
- int sb, bcd;
-
- sb = cmos_read(CMOS_STATB);
-
- bcd = (sb & (1 << 2)) == 0;
-
- // make sure CMOS doesn't modify time while we read it
- for (;;) {
- fill_rtcdate(&t1);
- if (cmos_read(CMOS_STATA) & CMOS_UIP)
- continue;
- fill_rtcdate(&t2);
- if (memcmp(&t1, &t2, sizeof(t1)) == 0)
- break;
- }
-
- // convert
- if (bcd) {
-#define CONV(x) (t1.x = ((t1.x >> 4) * 10) + (t1.x & 0xf))
- CONV(second);
- CONV(minute);
- CONV(hour );
- CONV(day );
- CONV(month );
- CONV(year );
-#undef CONV
- }
-
- *r = t1;
- r->year += 2000;
-}