SchOS/kernel/arch/x86_64/lapic.c
Nexoniarz 438e7af141 Keyboard lights, TLB shootdowns, battery in sysfs, /proc/pid/maps
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
2026-10-25 13:43:27 +01:00

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)
;
}