fix: kernel concurrency, interrupt context, and user VA safety
This commit is contained in:
1 parent
f222bf1f04
commit
b1c55073c7
71 files changed
+2654
-1092
No files matched your search
@@ -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();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user