Caps, Num and Scroll Lock now light their LEDs on PS/2 and USB keyboards, share one state between keyboards, and stop toggling on key repeat. X sets them through evdev writes while it holds the keyboard. Unmapping memory in a process with threads on other CPUs now flushes their TLBs with an IPI. Without it a freed page stayed reachable through stale translations, which corrupted Epiphany's heap on SMP. ACPI batteries and AC adapters appear under /sys/class/power_supply. /proc/<pid>/maps lists file mappings, and a process killed by a fault names the file and offset it died in. r8169 gets a 256-entry RX ring. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
599 lines
20 KiB
C
599 lines
20 KiB
C
// The local APIC manages internal (non-I/O) interrupts.
|
|
// See Chapter 8 & Appendix C of Intel processor manual volume 3.
|
|
|
|
#include "core/param.h"
|
|
#include "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "core/date.h"
|
|
#include "arch/memlayout.h"
|
|
#include "arch/traps.h"
|
|
#include "arch/mmu.h"
|
|
#include "arch/x86.h"
|
|
#include "core/proc.h" // ncpu
|
|
|
|
// Local APIC registers, divided by 4 for use as uint[] indices.
|
|
#define ID (0x0020/4) // ID
|
|
#define VER (0x0030/4) // Version
|
|
#define TPR (0x0080/4) // Task Priority
|
|
#define EOI (0x00B0/4) // EOI
|
|
#define SVR (0x00F0/4) // Spurious Interrupt Vector
|
|
#define ENABLE 0x00000100 // Unit Enable
|
|
#define ESR (0x0280/4) // Error Status
|
|
#define ICRLO (0x0300/4) // Interrupt Command
|
|
#define SINIT 0x00000500 // INIT/RESET
|
|
#define STARTUP 0x00000600 // Startup IPI
|
|
#define DELIVS 0x00001000 // Delivery status
|
|
#define ASSERT 0x00004000 // Assert interrupt (vs deassert)
|
|
#define LEVEL 0x00008000 // Level triggered
|
|
#define BCAST 0x00080000 // Send to all APICs, including self.
|
|
#define BUSY 0x00001000
|
|
#define ICRHI (0x0310/4) // Interrupt Command [63:32]
|
|
#define TIMER (0x0320/4) // Local Vector Table 0 (TIMER)
|
|
#define X1 0x0000000B // divide counts by 1
|
|
#define PERIODIC 0x00020000 // Periodic
|
|
#define PCINT (0x0340/4) // Performance Counter LVT
|
|
#define LINT0 (0x0350/4) // Local Vector Table 1 (LINT0)
|
|
#define LINT1 (0x0360/4) // Local Vector Table 2 (LINT1)
|
|
#define ERROR (0x0370/4) // Local Vector Table 3 (ERROR)
|
|
#define MASKED 0x00010000 // Interrupt masked
|
|
#define TICR (0x0380/4) // Timer Initial Count
|
|
#define TCCR (0x0390/4) // Timer Current Count
|
|
#define TDCR (0x03E0/4) // Timer Divide Configuration
|
|
|
|
volatile uint *lapic; // Initialized in mp.c
|
|
|
|
static void
|
|
lapicw(int index, int value)
|
|
{
|
|
lapic[index] = value;
|
|
lapic[ID]; // wait for write to finish, by reading
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Timer calibration
|
|
// ---------------------------------------------------------------------
|
|
// The LAPIC timer counts down at the APIC bus frequency, and nothing in the
|
|
// architecture tells you what that frequency is. xv6 loaded TICR with
|
|
// 10000000 and hoped; its own comment admitted the gap -- "if xv6 cared more
|
|
// about precise timekeeping, TICR would be calibrated using an external time
|
|
// source." That value yields the intended 100 Hz only if the bus happens to
|
|
// run at exactly 1 GHz.
|
|
//
|
|
// Under KVM it does not, and the error is not subtle. The kernel believed
|
|
// ticks arrived 100 times a second while they were arriving roughly 18 times
|
|
// a second, so every number derived from ticks was wrong by that same factor,
|
|
// in the same direction, everywhere at once: uptime ran slow, `time` reported
|
|
// three seconds for twenty, ping's one-second interval stretched to six. It
|
|
// read as three unrelated bugs in three unrelated programs. It was this line.
|
|
//
|
|
// So measure it. The 8254 PIT is exactly the external time source the comment
|
|
// asked for: its input clock is fixed at 1193182 Hz by the hardware, it is
|
|
// always present, and channel 2 is the one whose gate software can raise and
|
|
// whose output software can poll -- a stopwatch that needs no interrupt, which
|
|
// matters because this runs before the IDT is ready to take one.
|
|
#define PIT_HZ 1193182 // 8254 input clock, fixed by definition
|
|
#define PIT_CH2 0x42
|
|
#define PIT_CMD 0x43
|
|
#define PIT_GATE 0x61 // bit 0: ch2 gate, bit 1: speaker, bit 5: ch2 output
|
|
|
|
uint lapic_timer_hz; // measured LAPIC timer counts per second
|
|
uint64 tsc_hz; // measured TSC cycles per second
|
|
uint64 boot_tsc; // the TSC when calibration finished
|
|
|
|
// The TSC's frequency from the processor itself, for when there is no PIT to
|
|
// time it against. 0 if the processor does not say.
|
|
//
|
|
// Leaf 0x15 is the ratio of the TSC to the core crystal, and the crystal's
|
|
// frequency when the part reports it; leaf 0x16 is the base frequency in MHz,
|
|
// which is the TSC's on every part that has an invariant one. The first is
|
|
// exact and the second is a nominal figure, so the first is preferred.
|
|
static uint64
|
|
tsc_hz_from_cpuid(void)
|
|
{
|
|
uint32 max, a, b, c, d;
|
|
|
|
cpuid_leaf(0, &max, 0, 0, 0);
|
|
if (max >= 0x15) {
|
|
cpuid_leaf(0x15, &a, &b, &c, &d);
|
|
if (a && b && c)
|
|
return (uint64)c * b / a;
|
|
}
|
|
if (max >= 0x16) {
|
|
cpuid_leaf(0x16, &a, 0, 0, 0);
|
|
if (a & 0xFFFF)
|
|
return (uint64)(a & 0xFFFF) * 1000000;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// The fallback when the PIT does not count.
|
|
//
|
|
// Something else has to be the stopwatch. In order: the ACPI PM timer, which
|
|
// is a real clock at a fixed 3.579545 MHz on every PC that is not
|
|
// hardware-reduced; then the TSC frequency the processor states in CPUID,
|
|
// with the TSC itself as the stopwatch; and only then a guess.
|
|
extern uint32 acpi_pmtmr_port;
|
|
extern int acpi_pmtmr_32;
|
|
|
|
#define PMTMR_HZ 3579545
|
|
|
|
// 50 ms of the PM timer, with the LAPIC and the TSC counted across it.
|
|
// Returns 0 if the timer did not move, which is a port that is not one.
|
|
static int
|
|
calibrate_pmtimer(void)
|
|
{
|
|
uint32 mask = acpi_pmtmr_32 ? 0xFFFFFFFFu : 0x00FFFFFFu;
|
|
uint32 want = PMTMR_HZ / 20, t0, t;
|
|
|
|
lapicw(TDCR, X1);
|
|
lapicw(TIMER, MASKED);
|
|
lapicw(TICR, 0xFFFFFFFF);
|
|
|
|
t0 = inl(acpi_pmtmr_port) & mask;
|
|
uint64 tsc0 = rdtsc();
|
|
uint lap0 = lapic[TCCR];
|
|
do {
|
|
t = inl(acpi_pmtmr_port) & mask;
|
|
if (rdtsc() - tsc0 > 4000000000ULL) {
|
|
lapicw(TICR, 0);
|
|
return 0;
|
|
}
|
|
} while (((t - t0) & mask) < want);
|
|
uint lap1 = lapic[TCCR];
|
|
uint64 tsc1 = rdtsc();
|
|
lapicw(TICR, 0);
|
|
|
|
uint32 ticks = (t - t0) & mask;
|
|
tsc_hz = (tsc1 - tsc0) * PMTMR_HZ / ticks;
|
|
lapic_timer_hz = (uint)((uint64)(lap0 - lap1) * PMTMR_HZ / ticks);
|
|
boot_tsc = tsc1;
|
|
return tsc_hz >= 20000000ULL;
|
|
}
|
|
|
|
static void
|
|
calibrate_without_pit(void)
|
|
{
|
|
if (acpi_pmtmr_port && calibrate_pmtimer()) {
|
|
kinfo("cpu0: the PIT does not count; timed against the ACPI PM timer\n");
|
|
} else {
|
|
tsc_hz = tsc_hz_from_cpuid();
|
|
if (tsc_hz == 0) {
|
|
// Nothing left to measure against. Wrong-but-running, as below: time
|
|
// will run at the wrong speed and the line says that it may.
|
|
tsc_hz = 2000000000ULL;
|
|
cprintf("cpu0: no PIT, no PM timer and no TSC frequency from CPUID; "
|
|
"assuming 2 GHz\n");
|
|
} else {
|
|
kinfo("cpu0: the PIT does not count; TSC frequency from CPUID\n");
|
|
}
|
|
|
|
lapicw(TDCR, X1);
|
|
lapicw(TIMER, MASKED);
|
|
lapicw(TICR, 0xFFFFFFFF);
|
|
uint64 tsc0 = rdtsc();
|
|
uint lap0 = lapic[TCCR];
|
|
uint64 until = tsc0 + tsc_hz / 20; // 50 ms
|
|
while (rdtsc() < until)
|
|
asm volatile("pause");
|
|
uint lap1 = lapic[TCCR];
|
|
uint64 tsc1 = rdtsc();
|
|
lapicw(TICR, 0);
|
|
|
|
lapic_timer_hz = (uint)((uint64)(lap0 - lap1) * tsc_hz / (tsc1 - tsc0));
|
|
boot_tsc = tsc1;
|
|
}
|
|
|
|
if (lapic_timer_hz < HZ * 1000u) {
|
|
cprintf("cpu0: lapic calibration implausible (%u Hz), assuming 1 GHz bus\n",
|
|
lapic_timer_hz);
|
|
lapic_timer_hz = 1000000000u;
|
|
}
|
|
}
|
|
|
|
static void
|
|
calibrate_timers(void)
|
|
{
|
|
// 50 ms: long enough that the port I/O around the measurement is noise,
|
|
// short enough that nobody notices it in boot time. The count fits in the
|
|
// 16 bits the PIT has (59659), which is the real upper bound here.
|
|
const uint us = 50000;
|
|
uint count = (uint)(((uint64)PIT_HZ * us) / 1000000);
|
|
|
|
// Channel 2, mode 0 (interrupt on terminal count), lobyte then hibyte.
|
|
// Gate high so it counts; the speaker bit stays clear so the box is silent.
|
|
outb(PIT_GATE, (inb(PIT_GATE) & ~0x02) | 0x01);
|
|
outb(PIT_CMD, 0xB0);
|
|
outb(PIT_CH2, count & 0xFF);
|
|
outb(PIT_CH2, (count >> 8) & 0xFF);
|
|
|
|
// A free-running LAPIC counter for the duration: one-shot, masked, started
|
|
// at the largest count there is so it cannot wrap inside 50 ms.
|
|
lapicw(TDCR, X1);
|
|
lapicw(TIMER, MASKED);
|
|
lapicw(TICR, 0xFFFFFFFF);
|
|
|
|
uint64 tsc0 = rdtsc();
|
|
uint lap0 = lapic[TCCR];
|
|
|
|
// Bounded. "Always present" stopped being true: recent Intel boards
|
|
// clock-gate the 8254 to save power, and then the output never rises and
|
|
// this loop was the whole of the boot -- a machine stopped at the line
|
|
// before "lapic timer", with nothing to say why. Four billion TSC cycles is
|
|
// over a second on anything that exists and twenty times the window on a
|
|
// 4 GHz part, so a working PIT never comes near it.
|
|
//
|
|
// And checked at the start: in mode 0 the output goes low the moment the
|
|
// count is written, so a bit that is already high is no PIT at all. An
|
|
// absent one floats -- port 0x61 reads 0xFF -- and then the loop ended at
|
|
// once, the TSC was "measured" at 0 MHz, and every clock in the system ran
|
|
// thousands of times fast. Under QEMU with pit=off uptime read four days
|
|
// thirty seconds after boot.
|
|
int pit_ok = !(inb(PIT_GATE) & 0x20);
|
|
while (pit_ok && !(inb(PIT_GATE) & 0x20)) { // ch2 output rises at terminal count
|
|
if (rdtsc() - tsc0 > 4000000000ULL) {
|
|
pit_ok = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint lap1 = lapic[TCCR];
|
|
uint64 tsc1 = rdtsc();
|
|
|
|
// 50 ms of a TSC slower than 20 MHz is no processor this runs on: the
|
|
// window was not 50 ms, so the PIT was not counting it.
|
|
if (pit_ok && tsc1 - tsc0 < 1000000)
|
|
pit_ok = 0;
|
|
|
|
if (!pit_ok) {
|
|
lapicw(TICR, 0);
|
|
calibrate_without_pit();
|
|
return;
|
|
}
|
|
|
|
lapicw(TICR, 0); // stop counting
|
|
|
|
// TCCR counts down, so elapsed counts are start minus end.
|
|
lapic_timer_hz = (uint)(((uint64)(lap0 - lap1) * 1000000) / us);
|
|
tsc_hz = ((tsc1 - tsc0) * 1000000) / us;
|
|
|
|
// Where the clock starts. Everything the system reports as elapsed time is
|
|
// measured from here, against the TSC, rather than by counting timer
|
|
// interrupts -- see kernel/core/time.c.
|
|
boot_tsc = tsc1;
|
|
|
|
// If the measurement came back implausible -- no LAPIC, a hypervisor that
|
|
// stops the counter, a PIT that never raises its output -- fall back to the
|
|
// old assumption rather than dividing by zero and taking the machine down.
|
|
// Wrong-but-running beats a triple fault, and the printed value says which
|
|
// case this is.
|
|
if (lapic_timer_hz < HZ * 1000u) {
|
|
cprintf("cpu0: lapic calibration implausible (%u Hz), assuming 1 GHz bus\n",
|
|
lapic_timer_hz);
|
|
lapic_timer_hz = 1000000000u; // the 1 GHz that TICR=10000000 implied
|
|
}
|
|
}
|
|
|
|
#define MSR_APIC_BASE 0x1B
|
|
#define APIC_BASE_EN (1ULL << 11) // globally enabled
|
|
#define APIC_BASE_EXTD (1ULL << 10) // x2APIC mode
|
|
|
|
// Put this processor's local APIC in xAPIC mode, which is the mode everything
|
|
// here speaks: registers at an MMIO address, read and written as memory.
|
|
//
|
|
// Firmware is allowed to hand over in x2APIC mode, and on recent Intel
|
|
// laptops it usually does. In that mode the MMIO page is not the APIC any
|
|
// more -- the registers are MSRs -- so every lapic[] access read nothing
|
|
// useful: the ID came back wrong, cpunum() panicked "unknown apicid", and the
|
|
// timer was never started. A hang or a panic within the first second, on the
|
|
// newest machines and on no emulator, because none of them hand over that way.
|
|
//
|
|
// The architecture allows exactly one way back: x2APIC to disabled, disabled
|
|
// to xAPIC. Disabling discards the APIC's state, which costs nothing here
|
|
// because lapicinit() is about to set all of it.
|
|
//
|
|
// It cannot be done when the processor has locked xAPIC out (some server
|
|
// parts, which report it in IA32_XAPIC_DISABLE_STATUS), or when its APIC ID
|
|
// does not fit in eight bits. Both get said plainly rather than left to fail
|
|
// at the first register access.
|
|
void
|
|
lapic_xapic_mode(void)
|
|
{
|
|
uint32 d;
|
|
uint64 base = rdmsr(MSR_APIC_BASE);
|
|
|
|
if ((base & (APIC_BASE_EN | APIC_BASE_EXTD)) == APIC_BASE_EN)
|
|
return; // xAPIC already: the usual case
|
|
|
|
if (base & APIC_BASE_EXTD) {
|
|
cpuid_leaf(7, 0, 0, 0, &d);
|
|
if (d & (1u << 29)) { // IA32_ARCH_CAPABILITIES exists
|
|
uint64 caps = rdmsr(0x10A);
|
|
if ((caps & (1ULL << 21)) && (rdmsr(0xBD) & 1))
|
|
panic("lapic: firmware has locked the APIC in x2APIC mode");
|
|
}
|
|
if (rdmsr(0x802) > 254) // x2APIC ID register
|
|
panic("lapic: APIC id too large for xAPIC mode");
|
|
wrmsr(MSR_APIC_BASE, base & ~(APIC_BASE_EN | APIC_BASE_EXTD));
|
|
}
|
|
// From disabled (whether firmware left it so or the line above did) to
|
|
// xAPIC. The base address bits are kept: they are where lapic[] points.
|
|
wrmsr(MSR_APIC_BASE, (base & ~APIC_BASE_EXTD) | APIC_BASE_EN);
|
|
}
|
|
|
|
void
|
|
lapicinit(void)
|
|
{
|
|
if(!lapic)
|
|
return;
|
|
|
|
lapic_xapic_mode();
|
|
|
|
// Enable local APIC; set spurious interrupt vector.
|
|
lapicw(SVR, ENABLE | (T_IRQ0 + IRQ_SPURIOUS));
|
|
|
|
// The timer counts down at the APIC bus frequency and interrupts at zero.
|
|
// What that frequency *is*, calibrate_timers() had to go and measure --
|
|
// see the comment above it for why loading a constant here does not work.
|
|
if (lapic_timer_hz == 0) {
|
|
calibrate_timers();
|
|
kinfo("cpu0: lapic timer %u Hz, tsc %u MHz, tick = %u counts at %d Hz\n",
|
|
lapic_timer_hz, (uint)(tsc_hz / 1000000),
|
|
lapic_timer_hz / HZ, HZ);
|
|
}
|
|
|
|
lapicw(TDCR, X1);
|
|
lapicw(TIMER, PERIODIC | (T_IRQ0 + IRQ_TIMER));
|
|
lapicw(TICR, lapic_timer_hz / HZ);
|
|
|
|
// Disable logical interrupt lines.
|
|
lapicw(LINT0, MASKED);
|
|
lapicw(LINT1, MASKED);
|
|
|
|
// Disable performance counter overflow interrupts
|
|
// on machines that provide that interrupt entry.
|
|
if(((lapic[VER]>>16) & 0xFF) >= 4)
|
|
lapicw(PCINT, MASKED);
|
|
|
|
// Map error interrupt to IRQ_ERROR.
|
|
lapicw(ERROR, T_IRQ0 + IRQ_ERROR);
|
|
|
|
// Clear error status register (requires back-to-back writes).
|
|
lapicw(ESR, 0);
|
|
lapicw(ESR, 0);
|
|
|
|
// Ack any outstanding interrupts.
|
|
lapicw(EOI, 0);
|
|
|
|
// Send an Init Level De-Assert to synchronise arbitration ID's.
|
|
lapicw(ICRHI, 0);
|
|
lapicw(ICRLO, BCAST | SINIT | LEVEL);
|
|
while(lapic[ICRLO] & DELIVS)
|
|
;
|
|
|
|
// Enable interrupts on the APIC (but not on the processor).
|
|
lapicw(TPR, 0);
|
|
}
|
|
|
|
int
|
|
cpunum(void)
|
|
{
|
|
int apicid, i;
|
|
|
|
// Cannot call cpu when interrupts are enabled:
|
|
// result not guaranteed to last long enough to be used!
|
|
// Would prefer to panic but even printing is chancy here:
|
|
// almost everything, including cprintf and panic, calls cpu,
|
|
// often indirectly through acquire and release.
|
|
if(readeflags()&FL_IF){
|
|
static int n;
|
|
if(n++ == 0)
|
|
cprintf("cpu called from %x with interrupts enabled\n",
|
|
__builtin_return_address(0));
|
|
}
|
|
|
|
if (!lapic)
|
|
return 0;
|
|
|
|
apicid = lapic[ID] >> 24;
|
|
for (i = 0; i < ncpu; ++i) {
|
|
if (cpus[i].apicid == apicid)
|
|
return i;
|
|
}
|
|
panic("unknown apicid\n");
|
|
}
|
|
|
|
// An NMI to every processor but this one: how a panic stops the others. An
|
|
// NMI is delivered with interrupts off too, so it reaches a processor spinning
|
|
// on a lock with cli as surely as one running a program; trap() halts any that
|
|
// takes one while panic_stop is set. Bounded, because the caller is dying.
|
|
void
|
|
lapic_nmi_others(void)
|
|
{
|
|
if (!lapic)
|
|
return;
|
|
lapicw(ICRHI, 0);
|
|
lapicw(ICRLO, 0x000C0000 | 0x00000400 | ASSERT); // all but self, NMI
|
|
for (int i = 0; i < 100000 && (lapic[ICRLO] & DELIVS); i++)
|
|
microdelay(1);
|
|
}
|
|
|
|
// Acknowledge interrupt.
|
|
void
|
|
lapiceoi(void)
|
|
{
|
|
if(lapic)
|
|
lapicw(EOI, 0);
|
|
}
|
|
|
|
// Spin for a given number of microseconds.
|
|
//
|
|
// This was an empty function body -- xv6's stub, carrying a comment saying
|
|
// real hardware would want to tune it dynamically. Nothing ever did, so every
|
|
// microdelay() in the tree returned immediately: the xHCI reset sequence, the
|
|
// USB hub power-on wait, the UART drain. Those delays are written into the
|
|
// specifications because the hardware genuinely needs the time, and a port
|
|
// that "works anyway" is one that is racing and winning.
|
|
//
|
|
// The TSC is the right clock for this: calibrate_timers() measured it in the
|
|
// same pass as the LAPIC, and unlike the LAPIC timer it is not already busy
|
|
// driving the tick.
|
|
void
|
|
microdelay(int us)
|
|
{
|
|
if (us <= 0)
|
|
return;
|
|
|
|
// Before calibration (early boot, and the AP trampoline) there is no
|
|
// measured frequency to work from. Spin on a rough count instead: the
|
|
// figure is wrong, but being approximately late is the entire point of the
|
|
// call, and being instantly early is what broke it.
|
|
if (tsc_hz == 0) {
|
|
for (volatile int i = 0; i < us * 100; i++)
|
|
;
|
|
return;
|
|
}
|
|
|
|
uint64 end = rdtsc() + ((uint64)us * tsc_hz) / 1000000;
|
|
while (rdtsc() < end)
|
|
asm volatile("pause");
|
|
}
|
|
|
|
#define CMOS_PORT 0x70
|
|
#define CMOS_RETURN 0x71
|
|
|
|
// Start additional processor running entry code at addr.
|
|
// See Appendix B of MultiProcessor Specification.
|
|
void
|
|
lapicstartap(uchar apicid, uint addr)
|
|
{
|
|
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);
|
|
wrv = (ushort*)P2V((0x40<<4 | 0x67)); // Warm reset vector
|
|
wrv[0] = 0;
|
|
wrv[1] = addr >> 4;
|
|
|
|
// "Universal startup algorithm."
|
|
// Send INIT (level-triggered) interrupt to reset other CPU.
|
|
lapicw(ICRHI, apicid<<24);
|
|
lapicw(ICRLO, SINIT | LEVEL | ASSERT);
|
|
microdelay(200);
|
|
lapicw(ICRLO, SINIT | LEVEL);
|
|
// The MP specification's 10 ms. This was 100 us, "because Bochs is slow",
|
|
// which is a figure for an emulator: older real processors want the full
|
|
// wait between INIT and STARTUP, and starting one too early is an AP that
|
|
// does not come up.
|
|
microdelay(10000);
|
|
|
|
// 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
|
|
// should be ignored, but it is part of the official Intel algorithm.
|
|
// Bochs complains about the second one. Too bad for Bochs.
|
|
for(i = 0; i < 2; i++){
|
|
lapicw(ICRHI, apicid<<24);
|
|
lapicw(ICRLO, STARTUP | (addr>>12));
|
|
microdelay(200);
|
|
}
|
|
}
|
|
|
|
// Hold a processor in wait-for-SIPI. For an AP that did not report in on
|
|
// time: if it is merely slow, this stops it from waking later and reading the
|
|
// trampoline parameters startothers() has since written for the next one --
|
|
// which would be two processors on one stack.
|
|
void
|
|
lapic_park(uchar apicid)
|
|
{
|
|
lapicw(ICRHI, apicid<<24);
|
|
lapicw(ICRLO, SINIT | LEVEL | ASSERT);
|
|
microdelay(200);
|
|
lapicw(ICRLO, SINIT | LEVEL);
|
|
microdelay(100);
|
|
}
|
|
|
|
#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);
|
|
}
|
|
//PAGEBREAK!
|
|
|
|
// 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;
|
|
}
|
|
|
|
// A fixed interrupt to one processor, by APIC id. Waits for the local APIC
|
|
// to say it has sent the last one before writing the next: two ICR writes
|
|
// back to back lose the first.
|
|
void
|
|
lapic_send_ipi(int apicid, int vector)
|
|
{
|
|
if (!lapic)
|
|
return;
|
|
while (lapic[ICRLO] & DELIVS)
|
|
;
|
|
lapicw(ICRHI, apicid << 24);
|
|
lapicw(ICRLO, vector); // fixed, physical, edge, no shorthand
|
|
while (lapic[ICRLO] & DELIVS)
|
|
;
|
|
}
|