fix: kernel concurrency, interrupt context, and user VA safety

This commit is contained in:
daniel committed 2026-08-01 15:43:38 +02:00
1 parent f222bf1f04
commit b1c55073c7
71 files changed
+2654 -1092

No files matched your search

+95 -11
View File
@@ -24,6 +24,10 @@ namespace Hal {
static uint32_t g_pm1aEventBlock = 0;
static uint32_t g_pm1bEventBlock = 0;
static uint8_t g_pm1EventLength = 0;
static uint32_t g_gpe0Block = 0;
static uint32_t g_gpe1Block = 0;
static uint8_t g_gpe0Length = 0;
static uint8_t g_gpe1Length = 0;
static uint16_t g_sciIrq = 0;
static bool g_initialized = false;
@@ -39,6 +43,18 @@ namespace Hal {
return sts;
}
static uint16_t ReadPM1Enable() {
uint16_t enableOffset = g_pm1EventLength / 2;
uint16_t enable = 0;
if (enableOffset == 0) return 0;
if (g_pm1aEventBlock != 0)
enable |= Io::In16((uint16_t)(g_pm1aEventBlock + enableOffset));
if (g_pm1bEventBlock != 0)
enable |= Io::In16((uint16_t)(g_pm1bEventBlock + enableOffset));
return enable;
}
static void ClearPM1StatusBits(uint16_t bits) {
// Write-1-to-clear semantics
if (g_pm1aEventBlock != 0)
@@ -59,23 +75,67 @@ namespace Hal {
}
}
// The kernel does not currently evaluate GPE control methods. Leaving
// firmware-enabled GPEs armed is therefore unsafe: an unacknowledged
// level-triggered SCI can continuously retrigger and starve the BSP.
static bool IsValidGpeIoBlock(uint32_t base, uint8_t length) {
return base != 0 && length >= 2 && (length & 1) == 0 &&
base <= 0xFFFF && length <= 0x10000U - base;
}
static void DisableAndClearGpeBlock(uint32_t base, uint8_t length) {
if (!IsValidGpeIoBlock(base, length)) return;
uint16_t statusBytes = length / 2;
for (uint16_t i = 0; i < statusBytes; i++)
Io::Out8(0, (uint16_t)(base + statusBytes + i));
for (uint16_t i = 0; i < statusBytes; i++)
Io::Out8(0xFF, (uint16_t)(base + i));
}
static void QuiesceActiveGpeBlock(uint32_t base, uint8_t length) {
if (!IsValidGpeIoBlock(base, length)) return;
uint16_t statusBytes = length / 2;
for (uint16_t i = 0; i < statusBytes; i++) {
uint16_t statusPort = (uint16_t)(base + i);
uint16_t enablePort = (uint16_t)(base + statusBytes + i);
uint8_t enabled = Io::In8(enablePort);
uint8_t active = Io::In8(statusPort) & enabled;
if (active == 0) continue;
// Disable the unsupported source before acknowledging it so a
// level source cannot immediately assert the SCI again.
Io::Out8(enabled & (uint8_t)~active, enablePort);
Io::Out8(active, statusPort);
}
}
static void DisableAndClearGpes() {
DisableAndClearGpeBlock(g_gpe0Block, g_gpe0Length);
DisableAndClearGpeBlock(g_gpe1Block, g_gpe1Length);
}
static void QuiesceActiveGpes() {
QuiesceActiveGpeBlock(g_gpe0Block, g_gpe0Length);
QuiesceActiveGpeBlock(g_gpe1Block, g_gpe1Length);
}
// ============================================================================
// SCI Interrupt Handler
// ============================================================================
static void SciHandler(uint8_t irq) {
static void SciHandler(uint8_t /*irq*/, bool) {
uint16_t sts = ReadPM1Status();
uint16_t activeFixedEvents = sts & ReadPM1Enable();
if (activeFixedEvents != 0)
ClearPM1StatusBits(activeFixedEvents);
if (sts & AcpiSleep::PM1_PWRBTN_STS) {
ClearPM1StatusBits(AcpiSleep::PM1_PWRBTN_STS);
KernelLogStream(INFO, "ACPI") << "Power button pressed";
}
// Firmware may re-enable a GPE after initialization. Since no GPE
// AML methods are dispatched yet, disable and acknowledge every
// active source rather than allowing an SCI interrupt storm.
QuiesceActiveGpes();
if (sts & AcpiSleep::PM1_SLPBTN_STS) {
ClearPM1StatusBits(AcpiSleep::PM1_SLPBTN_STS);
KernelLogStream(INFO, "ACPI") << "Sleep button pressed";
}
// Note: EOI is sent by HalIrqDispatch after this handler returns.
// Note: HalIrqDispatch sends the LAPIC EOI before invoking handlers.
}
// ============================================================================
@@ -93,6 +153,10 @@ namespace Hal {
g_pm1aEventBlock = fadt.PM1aEventBlock;
g_pm1bEventBlock = fadt.PM1bEventBlock;
g_pm1EventLength = fadt.PM1EventLength;
g_gpe0Block = fadt.GPE0Block;
g_gpe1Block = fadt.GPE1Block;
g_gpe0Length = fadt.GPE0Length;
g_gpe1Length = fadt.GPE1Length;
g_sciIrq = fadt.SCI_Interrupt;
if (g_pm1aEventBlock == 0) {
@@ -100,6 +164,19 @@ namespace Hal {
return;
}
if (g_gpe0Length != 0 && !IsValidGpeIoBlock(g_gpe0Block, g_gpe0Length)) {
KernelLogStream(WARNING, "ACPI")
<< "GPE0 block is not usable SystemIO (base=" << base::hex
<< (uint64_t)g_gpe0Block << ", length=" << base::dec
<< (uint64_t)g_gpe0Length << ")";
}
if (g_gpe1Length != 0 && !IsValidGpeIoBlock(g_gpe1Block, g_gpe1Length)) {
KernelLogStream(WARNING, "ACPI")
<< "GPE1 block is not usable SystemIO (base=" << base::hex
<< (uint64_t)g_gpe1Block << ", length=" << base::dec
<< (uint64_t)g_gpe1Length << ")";
}
// Clear any pending status bits
ClearPM1StatusBits(
AcpiSleep::PM1_PWRBTN_STS | AcpiSleep::PM1_SLPBTN_STS |
@@ -110,6 +187,10 @@ namespace Hal {
// Enable power button event
SetPM1Events(AcpiSleep::PM1_PWRBTN_EN);
// Disable unsupported firmware GPE sources before the level SCI is
// routed. Clear stale status only after the enables are down.
DisableAndClearGpes();
// Route SCI to an IRQ vector. The SCI is level-triggered,
// active-low (ACPI spec requirement). The MADT may have an
// Interrupt Source Override for this IRQ that already sets
@@ -145,6 +226,9 @@ namespace Hal {
// Drop the suspend-time RTC wake enable and restore only the
// fixed events we actually want during normal runtime.
SetPM1Events(AcpiSleep::PM1_PWRBTN_EN);
// Firmware and _WAK may have restored GPE enables while asleep.
DisableAndClearGpes();
}
};
+23
View File
@@ -21,6 +21,21 @@ namespace Hal {
return true;
}
static uint32_t ExtendedSystemIoAddress(const Table* fadt,
const uint8_t gas[12],
size_t fieldOffset) {
// Generic Address Structure: byte 0 is AddressSpaceId and bytes
// 4..11 are the little-endian address. This event code uses x86
// port I/O, so accept only SystemIO GAS entries that fit the
// architectural 16-bit port space.
if (fadt->Header.Length < fieldOffset + 12 || gas[0] != 1) return 0;
uint64_t address = 0;
for (int i = 0; i < 8; i++)
address |= (uint64_t)gas[4 + i] << (i * 8);
return address <= 0xFFFF ? (uint32_t)address : 0;
}
static Table* FindFADTInXSDT(ACPI::CommonSDTHeader* xsdt) {
uint32_t entryCount = (xsdt->Length - sizeof(ACPI::CommonSDTHeader)) / 8;
uint64_t* entries = (uint64_t*)((uint64_t)xsdt + sizeof(ACPI::CommonSDTHeader));
@@ -73,6 +88,14 @@ namespace Hal {
result.PMTimerBlock = fadt->PMTimerBlock;
result.GPE0Block = fadt->GPE0Block;
result.GPE1Block = fadt->GPE1Block;
if (result.GPE0Block == 0) {
result.GPE0Block = ExtendedSystemIoAddress(
fadt, fadt->X_GPE0Block, offsetof(Table, X_GPE0Block));
}
if (result.GPE1Block == 0) {
result.GPE1Block = ExtendedSystemIoAddress(
fadt, fadt->X_GPE1Block, offsetof(Table, X_GPE1Block));
}
result.SMI_CommandPort = fadt->SMI_CommandPort;
result.SCI_Interrupt = fadt->SCI_Interrupt;
result.PM1EventLength = fadt->PM1EventLength;