From 4165241b6f1e92a2b60f78774f47c41eb390b031 Mon Sep 17 00:00:00 2001 From: Chloe M Date: Mon, 13 Jul 2026 22:25:23 -0400 Subject: [PATCH] stos/amd64: cpu: Add LAPIC driver + MCB in KPCR Signed-off-by: Chloe M --- service/ptos/arch/amd64/cpu/init.c | 5 + service/ptos/arch/amd64/cpu/lapic.c | 352 +++++++++++++++++++++++++++ service/ptos/head/arch/amd64/lapic.h | 100 ++++++++ service/ptos/head/arch/amd64/mcb.h | 27 ++ service/ptos/head/hal/kpcr.h | 3 + service/ptos/ke/init.c | 6 +- 6 files changed, 490 insertions(+), 3 deletions(-) create mode 100644 service/ptos/arch/amd64/cpu/lapic.c create mode 100644 service/ptos/head/arch/amd64/lapic.h create mode 100644 service/ptos/head/arch/amd64/mcb.h diff --git a/service/ptos/arch/amd64/cpu/init.c b/service/ptos/arch/amd64/cpu/init.c index f1c91f9..8dbe59a 100644 --- a/service/ptos/arch/amd64/cpu/init.c +++ b/service/ptos/arch/amd64/cpu/init.c @@ -11,6 +11,7 @@ #include #include #include +#include /* * Initialize interrupts for the current processor @@ -65,5 +66,9 @@ HalKpcrP1Init(KPCR *Kpcr) VOID HalKpcrP2Init(KPCR *Kpcr) { + /* Initialize per-processor pools */ ExPoolRegionInit(&Kpcr->PoolRegion); + + /* Initialize the Local APIC unit */ + MdLapicInit(Kpcr); } diff --git a/service/ptos/arch/amd64/cpu/lapic.c b/service/ptos/arch/amd64/cpu/lapic.c new file mode 100644 index 0000000..7b45f40 --- /dev/null +++ b/service/ptos/arch/amd64/cpu/lapic.c @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2026, Chloe M. + * Provided under the BSD-3 clause. + * + * Description: Local APIC driver + * Author: Chloe M. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Local APIC timer vector */ +#define LAPIC_TMR_VECTOR 0x81 + +/* + * Trace only on the bootstrap processor but not on any others + * to avoid log spam... + */ +#define DTRACE_BSP(Fmt, ...) do { \ + ULONG ApicBase; \ + \ + ApicBase = MdRdmsr(IA32_APIC_BASE_MSR); \ + if (ISSET(ApicBase, BIT(8))) { \ + TRACE("[ LAPIC ]: " Fmt, ##__VA_ARGS__); \ + } \ + } while (0); + +/* Globals */ +extern VOID LapicTmrIsr(VOID); + +/* + * Returns true if the Local APIC unit is supported on the + * current processor. + */ +static BOOLEAN +LapicIsSupported(VOID) +{ + ULONG Edx, Unused; + + CPUID(1, Unused, Unused, Unused, Edx); + return ISSET(Edx, BIT(9)) != 0; +} + +/* + * Returns true if the CPU can operate its Local APIC unit + * in the newer x2APIC mode. + */ +static BOOLEAN +LapicHasX2Apic(VOID) +{ + ULONG Ecx, Unused; + + CPUID(1, Unused, Unused, Ecx, Unused); + return ISSET(Ecx, BIT(21)) != 0; +} + +/* + * Read a Local APIC register + * + * @Mcb: Machine core block + * @Register: Register to read + */ +static UQUAD +LapicRegRead(MCB *Mcb, ULONG Register) +{ + ULONG *Base; + UQUAD Value; + + if (!Mcb->HasX2Apic) { + Base = PTR_OFFSET(Mcb->LapicBase, Register); + Value = MMIORead32(Base); + } else { + Register >>= 4; + Value = MdRdmsr(x2APIC_MSR_BASE + Register); + } + + return Value; +} + +/* + * Write a value to a Local APIC register space + * + * @Mcb: Machine core block + * @Register: Register to read + * @Value: Value to write + */ +static VOID +LapicRegWrite(MCB *Mcb, ULONG Register, UQUAD Value) +{ + ULONG *Base; + + if (!Mcb->HasX2Apic) { + Base = PTR_OFFSET(Mcb->LapicBase, Register); + MMIOWrite32(Base, (ULONG)Value); + } else { + Register >>= 4; + MdWrmsr(x2APIC_MSR_BASE + Register, Value); + } +} + +/* + * Enable the Local APIC unit + * + * @Mcb: Machine core block + */ +static VOID +LapicEnable(MCB *Mcb) +{ + UQUAD ApicBase; + ULONG Svr, Version; + const CHAR *mode; + const CHAR *type = "discrete 82489DX"; + + /* Hardware enable the Local APIC unit */ + ApicBase = MdRdmsr(IA32_APIC_BASE_MSR); + ApicBase |= LAPIC_HW_ENABLE; + ApicBase |= Mcb->HasX2Apic << x2APIC_ENABLE_SHIFT; + MdWrmsr(IA32_APIC_BASE_MSR, ApicBase); + + /* Software enable the Local APIC unit */ + Svr = LapicRegRead(Mcb, LAPIC_SVR); + Svr |= LAPIC_SW_ENABLE; + LapicRegWrite(Mcb, LAPIC_SVR, Svr); + + /* Check the type */ + Version = LapicRegRead(Mcb, LAPIC_VERSION); + if ((Version & 0xFF) > 0) { + type = "integrated"; + } + + mode = Mcb->HasX2Apic ? "x2apic" : "xapic"; + DTRACE_BSP("enabled %s %s\n", type, mode); +} + +/* + * Stop the Local APIC timer + * + * @Mcb: Machine core block + */ +static VOID +LapicTimerStop(MCB *Mcb) +{ + if (Mcb == NULL) { + return; + } + + LapicRegWrite(Mcb, LAPIC_LVT_TMR, LAPIC_LVT_MASK); + LapicRegWrite(Mcb, LAPIC_INIT_CNT, 0); +} + +/* + * Start the Local APIC timer + * + * @Mcb: Machine core block + * @Mask: True to mask the timer + * @Mode: Timer mode + * @Count: Timer count + */ +static VOID +LapicTimerStart(MCB *Mcb, BOOLEAN Mask, UCHAR Mode, ULONG Count) +{ + ULONG Tmp; + + Tmp = (Mode << 17) | (Mask << 16) | LAPIC_TMR_VECTOR; + LapicRegWrite(Mcb, LAPIC_LVT_TMR, Tmp); + LapicRegWrite(Mcb, LAPIC_DCR, 0); + LapicRegWrite(Mcb, LAPIC_INIT_CNT, Count); +} + +/* + * Start the Local APIC timer in oneshot mode + * + * @Mcb: Machine core block + * @Mask: True to mask the timer + * @Count: Initial count + */ +static void +LapicTimerOneShot(MCB *Mcb, BOOLEAN Mask, ULONG Count) +{ + LapicTimerStart(Mcb, Mask, LVT_TMR_ONESHOT, Count); +} + +/* + * Calibrate the Local APIC timer + * + * @Mcb: Machine core block + */ +static VOID +LapicTimerCalibrate(MCB *Mcb) +{ + const USHORT MAX_SAMPLES = 0xFFFF; + USHORT TicksStart, TicksEnd; + USIZE Freq, TicksTotal; + + if (Mcb == NULL) { + return; + } + + LapicTimerStop(Mcb); + MdPitSetCount(MAX_SAMPLES); + TicksStart = MdPitGetCount(); + + LapicRegWrite(Mcb, LAPIC_INIT_CNT, MAX_SAMPLES); + while (LapicRegRead(Mcb, LAPIC_CUR_CNT) != 0); + + TicksEnd = MdPitGetCount(); + TicksTotal = TicksStart - TicksEnd; + + Freq = (MAX_SAMPLES / TicksTotal) * I8254_DIVIDEND; + LapicTimerStop(Mcb); + Mcb->LapicTmrFreq = Freq; +} + +VOID +MdLapicSendIpi(UCHAR Vector, UCHAR DestId, BOOLEAN LogicalDest, + IPI_SHORTHAND Xnd, IPI_DELMOD DelMod) +{ + KPCR *ThisCore; + MCB *Mcb; + ULONG IcrLow, IcrHigh; + + /* + * If the shorthand is refering to ourselves and we are x2APIC we + * can use a better path via its specialized SELF IPI MSR. + */ + ThisCore = HalKpcrCurrent(); + if (Xnd == IPI_XND_SELF && Mcb->HasX2Apic) { + MdWrmsr(LAPIC_SELF_IPI, Vector); + return; + } + + Mcb = &ThisCore->Mcb; + IcrLow = 0; + IcrHigh = 0; + + /* Clamp these fields */ + DelMod &= 7; + Xnd &= 3; + + /* Encode the ICR */ + IcrLow |= Vector & 0xFF; + IcrLow |= Xnd << 18; + IcrLow |= DelMod << 8; + IcrLow |= LogicalDest << 11; + + /* For xAPIC only */ + if (!Mcb->HasX2Apic) { + IcrHigh |= (UQUAD)DestId << 24; + LapicRegWrite(Mcb, LAPIC_ICRHI, IcrHigh); + LapicRegWrite(Mcb, LAPIC_ICRLO, IcrLow); + + /* Only on legacy xAPICs do we poll */ + for (;;) { + IcrLow = LapicRegRead(Mcb, LAPIC_ICRLO); + if (ISSET(IcrLow, IPI_DELSTAT_PENDING)) + HalCpuSpinWait(); + } + + return; + } + + /* On x2APICs only as it queues */ + LapicRegWrite(Mcb, LAPIC_ICRLO, ((UQUAD)DestId << 32) | IcrLow); +} + +ULONG +MdLapicId(VOID) +{ + KPCR *ThisCore; + MCB *Mcb; + + ThisCore = HalKpcrCurrent(); + Mcb = &ThisCore->Mcb; + + /* 32-bit when in x2APIC mode */ + if (Mcb->HasX2Apic) { + return LapicRegRead(Mcb, LAPIC_ID) & 0xFFFFFFFF; + } + + return (LapicRegRead(Mcb, LAPIC_ID) >> 24) & 0xF; +} + +VOID +MdLapicTimerOneshotUs(USIZE Usec) +{ + UQUAD Ticks; + KPCR *Kpcr; + MCB *Mcb; + + Kpcr = HalKpcrCurrent(); + Mcb = &Kpcr->Mcb; + + Ticks = Usec * (Mcb->LapicTmrFreq / 1000000); + LapicTimerOneShot(Mcb, false, Ticks); +} + +VOID +MdLapicSendEoi(VOID) +{ + KPCR *Kpcr; + + Kpcr = HalKpcrCurrent(); + LapicRegWrite(&Kpcr->Mcb, LAPIC_EOI, 0); +} + +VOID +MdLapicInit(KPCR *Kpcr) +{ + ACPI_MADT *Madt; + MCB *Mcb; + + if (Kpcr == NULL) { + KeBugCheck( + BUGCHECK_MISC, + "lapic: failed to initialize lapic driver\n" + ); + } + + if (!LapicIsSupported()) { + KeBugCheck( + BUGCHECK_MISSING_HARDWARE, + "lapic: local apic not supported\n" + ); + } + + /* Obtain the ACPI MADT table */ + Madt = AcpiQuery("APIC"); + if (Madt == NULL) { + KeBugCheck(BUGCHECK_MISC, "lapic: could not obtain MADT\n"); + } + + Mcb = &Kpcr->Mcb; + Mcb->LapicBase = PMA_TO_VMA((UPTR)Madt->LapicAddr); + Mcb->HasX2Apic = LapicHasX2Apic(); + + /* Enable the Local APIC */ + LapicEnable(Mcb); + + /* Calibrate the timer */ + LapicTimerCalibrate(Mcb); +} diff --git a/service/ptos/head/arch/amd64/lapic.h b/service/ptos/head/arch/amd64/lapic.h new file mode 100644 index 0000000..df2d3ef --- /dev/null +++ b/service/ptos/head/arch/amd64/lapic.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2026, Chloe M. + * Provided under the BSD-3 clause. + * + * Description: Local APIC driver + * Author: Chloe M. + */ + +#ifndef _MACHINE_LAPIC_H_ +#define _MACHINE_LAPIC_H_ 1 + +#include +#include + +/* + * Destination shorthand values for inter-processor + * interrupts. + * + * @IPI_XND_NONE: No shorthand + * @IPI_XND_SELF: Self shorthand + * @IPI_XND_AIS: All including self shorthand + * @IPI_XND_AES: All exclduing self shorthand + */ +typedef enum { + IPI_XND_NONE, + IPI_XND_SELF, + IPI_XND_AIS, + IPI_XND_AES +} IPI_SHORTHAND; + +/* + * Delivery mode values for inter-processor + * interrupts. + * + * @IPI_DELMOD_FIXED: Deliver a specific interrupt vector to a slutty core~ + * @IPI_LOWPRI: Equivalent to a FIXED IPI but lowest priority + * @IPI_DELMOD_SMI: Sends an SMI, we don't use this + * @IPI_DELMOD_RESERVED: Reserved + * @IPI_DELMOD_NMI: Deliver a non-maskable interrupt + * @IPI_DELMOD_INIT: Deliver an INIT IPI to a processor + * @IPI_DELMOD_STARTUP: Deliver a STARTUP IPI to a processor + */ +typedef enum { + IPI_DELMOD_FIXED, + IPI_DELMOD_LOWPRI, + IPI_DELMOD_SMI, + IPI_DELMOD_RESERVED, + IPI_DELMOD_NMI, + IPI_DELMOD_INIT, + IPI_DELMOD_STARTUP +} IPI_DELMOD; + +/* IPI Delivery status bits */ +#define IPI_DELSTAT_PENDING BIT(12) + +/* IPI Destination mode */ +#define IPI_DELMOD_LOGICAL BIT(0) + +/* + * Initialize the Local APIC unit for the current + * processor. + * + * @Kpcr: KPCR of current processor + */ +VOID MdLapicInit(KPCR *Kpcr); + +/* + * Obtain the APIC ID of the current processor + */ +ULONG MdLapicId(VOID); + +/* + * Send an inter-processor interrupt + * + * @Vector: Interrupt vector to assign to ICR + * @DestId: Target APIC ID [depends on @LogicalDest] + * @LogicalDest: If true, IPI destination is logical + * @Xnd: Destination shorthand + * @Delmod: Delivery mode + */ +VOID MdLapicSendIpi( + UCHAR Vector, UCHAR DestId, + BOOLEAN LogicalDest, IPI_SHORTHAND Xnd, + IPI_DELMOD DelMod +); + +/* + * Start the Local APIC timer in oneshot mode and fire after + * a number of microseconds + * + * @Usec: Number of microseconds to fire after + */ +VOID MdLapicTimerOneshotUs(USIZE Usec); + +/* + * Send an end-of-interrupt signal to the Local APIC unit + */ +VOID MdLapicSendEoi(VOID); + +#endif /* !_MACHINE_LAPIC_H_ */ diff --git a/service/ptos/head/arch/amd64/mcb.h b/service/ptos/head/arch/amd64/mcb.h new file mode 100644 index 0000000..a778c9c --- /dev/null +++ b/service/ptos/head/arch/amd64/mcb.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2026, Chloe M. + * Provided under the BSD-3 clause. + * + * Description: Machine core block + * Author: Chloe M. + */ + +#ifndef _MACHINE_MCB_H_ +#define _MACHINE_MCB_H_ 1 + +#include + +/* + * The machine core block contains MD processor information + * + * @LapicBase: Local APIC MMIO base + * @HasX2Apic: Has an x2APIC unit + * @LapicTmrFreq: Local APIC timer frequency + */ +typedef struct { + VOID *LapicBase; + UCHAR HasX2Apic : 1; + USIZE LapicTmrFreq; +} MCB; + +#endif /* !_MACHINE_MCB_H_ */ diff --git a/service/ptos/head/hal/kpcr.h b/service/ptos/head/hal/kpcr.h index b5807b9..a5700e9 100644 --- a/service/ptos/head/hal/kpcr.h +++ b/service/ptos/head/hal/kpcr.h @@ -11,6 +11,7 @@ #include #include +#include /* * The kernel processor control region contains MI @@ -18,10 +19,12 @@ * * @Id: Logical processor ID assigned by us * @PoolRegion: Allocator pool region + * @Mcb: Machine core block */ typedef struct { USHORT Id; POOL_REGION PoolRegion; + MCB Mcb; } KPCR; /* diff --git a/service/ptos/ke/init.c b/service/ptos/ke/init.c index 4adb66e..7b31a0d 100644 --- a/service/ptos/ke/init.c +++ b/service/ptos/ke/init.c @@ -87,12 +87,12 @@ KiKernelInit(VOID) /* Initialize virtual memory */ MmVmInit(); - /* Phase 2 init of bootstrap core */ - HalKpcrP2Init(&BootstrapCore); - /* Initialize ACPI */ AcpiInit(); + /* Phase 2 init of bootstrap core */ + HalKpcrP2Init(&BootstrapCore); + /* Initialize the root process */ InitializeRootProcess(); }