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
@@ -10,12 +10,15 @@
|
||||
#include <Hal/Apic/Apic.hpp>
|
||||
#include <Hal/Apic/Interrupts.hpp>
|
||||
#include <Hal/Cpu.hpp>
|
||||
#include <Hal/CpuPower.hpp>
|
||||
#include <Hal/SmpBoot.hpp>
|
||||
#include <Io/IoPort.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Drivers/Net/E1000.hpp>
|
||||
#include <Drivers/Net/E1000E.hpp>
|
||||
#include <Drivers/Audio/IntelHda.hpp>
|
||||
#include <Drivers/USB/Xhci.hpp>
|
||||
#include <Drivers/USB/Bluetooth/Bluetooth.hpp>
|
||||
#include <Drivers/USB/HidKeyboard.hpp>
|
||||
@@ -47,7 +50,7 @@ namespace Timekeeping {
|
||||
static std::atomic<uint64_t> g_tickCount{0};
|
||||
static uint32_t g_ticksPerMs = 0;
|
||||
|
||||
static bool g_schedEnabled = false;
|
||||
static std::atomic<bool> g_schedEnabled{false};
|
||||
|
||||
static uint32_t CountForIntervalMs(uint32_t intervalMs) {
|
||||
return g_ticksPerMs * intervalMs;
|
||||
@@ -71,7 +74,7 @@ namespace Timekeeping {
|
||||
|
||||
// Timer IRQ handler: BSP runs timekeeping + scheduler accounting; APs
|
||||
// run scheduler accounting only.
|
||||
static void TimerHandler(uint8_t) {
|
||||
static void TimerHandler(uint8_t, bool interruptedUser) {
|
||||
auto* cpu = Smp::GetCurrentCpuData();
|
||||
uint32_t schedElapsedMs = (cpu->cpuIndex == 0) ? BSP_TICK_INTERVAL_MS : AP_TICK_INTERVAL_MS;
|
||||
|
||||
@@ -82,10 +85,29 @@ namespace Timekeeping {
|
||||
Drivers::Net::E1000E::Poll();
|
||||
}
|
||||
Drivers::USB::HidKeyboard::Tick();
|
||||
|
||||
// Bottom halves normally run from idle, but a continuously busy
|
||||
// desktop may never enter idle. Force one bounded idle-context
|
||||
// pass for USB immediately, and for network RX at most every
|
||||
// 10 ms (important for E1000E's polling fallback).
|
||||
bool usbWork = Drivers::USB::Xhci::HasDeferredWork();
|
||||
bool audioWork = Drivers::Audio::IntelHda::HasDeferredWork();
|
||||
bool netWork = Drivers::Net::E1000::HasDeferredWork() ||
|
||||
Drivers::Net::E1000E::HasDeferredWork();
|
||||
// A forced trip into the idle bottom-half context is safe only when
|
||||
// this timer interrupted ring 3. If it interrupted a syscall, that
|
||||
// suspended kernel stack may own one of the process-context mutexes
|
||||
// used below; the reserved idle context would then spin on the lock
|
||||
// while refusing to schedule its owner, deadlocking the CPU.
|
||||
if (interruptedUser &&
|
||||
(usbWork || audioWork ||
|
||||
(netWork && (g_tickCount.load(std::memory_order_relaxed) % 10 == 0)))) {
|
||||
Sched::RequestKernelWork();
|
||||
}
|
||||
}
|
||||
|
||||
if (g_schedEnabled) {
|
||||
Sched::Tick(schedElapsedMs);
|
||||
Sched::Tick(schedElapsedMs, interruptedUser);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +210,7 @@ namespace Timekeeping {
|
||||
}
|
||||
|
||||
void EnableSchedulerTick() {
|
||||
g_schedEnabled = true;
|
||||
g_schedEnabled.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
void ApicTimerInitializeAP() {
|
||||
@@ -202,16 +224,36 @@ namespace Timekeeping {
|
||||
ProgramTimer(true, AP_TICK_INTERVAL_MS);
|
||||
}
|
||||
|
||||
void IdleOnce(bool hasMwait, volatile uint64_t* monitorAddr) {
|
||||
(void)hasMwait;
|
||||
(void)monitorAddr;
|
||||
void ServiceDeferredWork() {
|
||||
auto* cpu = Smp::GetCurrentCpuData();
|
||||
if (cpu == nullptr || cpu->currentSlot >= 0) return;
|
||||
cpu->kernelWorkPending = false;
|
||||
|
||||
// These bottom halves deliberately run outside a process because they
|
||||
// enter subsystems with process-context mutexes. Do not let a timer or
|
||||
// reschedule IPI switch this idle stack to a process while such a lock
|
||||
// remains held: the process could immediately wait on that same lock,
|
||||
// leaving the suspended idle owner with no opportunity to release it.
|
||||
// The scheduler and reschedule-IPI path both honor this reservation.
|
||||
bool wasReserved = cpu->reservedForKernelWork;
|
||||
cpu->reservedForKernelWork = true;
|
||||
|
||||
// Drain USB hot-plug deferred work from any idle core, not just the BSP.
|
||||
if (Drivers::USB::Xhci::HasDeferredWork()) {
|
||||
Drivers::USB::Xhci::ProcessDeferredWork();
|
||||
}
|
||||
|
||||
// NIC hard IRQs only acknowledge/mask and queue RX work. Dispatching
|
||||
// Ethernet/TCP/UDP here keeps process-context IPC mutexes out of IRQs.
|
||||
Drivers::Net::E1000::ProcessDeferredWork();
|
||||
Drivers::Net::E1000E::ProcessDeferredWork();
|
||||
|
||||
// HDA completion IRQs only acknowledge/mask. Resampling and DMA-ring
|
||||
// refill are far too expensive for hard interrupt context.
|
||||
if (cpu->cpuIndex == 0) {
|
||||
Drivers::Audio::IntelHda::ProcessDeferredWork();
|
||||
}
|
||||
|
||||
// Complete any boot-deferred Bluetooth bring-up (Intel firmware
|
||||
// download) here instead of on the boot path: the download takes
|
||||
// seconds and used to stall kmain before the first process spawned.
|
||||
@@ -228,10 +270,35 @@ namespace Timekeeping {
|
||||
// the adapter is down.
|
||||
Drivers::USB::Bluetooth::ServiceEvents();
|
||||
|
||||
// Thermal policy records transitions during BSP maintenance; print
|
||||
// them from this explicitly non-interrupt idle path.
|
||||
Hal::CpuPower::ServiceDeferredDiagnostics();
|
||||
|
||||
cpu->reservedForKernelWork = wasReserved;
|
||||
}
|
||||
|
||||
void IdleOnce(bool hasMwait, volatile uint64_t* monitorAddr) {
|
||||
(void)hasMwait;
|
||||
(void)monitorAddr;
|
||||
auto* cpu = Smp::GetCurrentCpuData();
|
||||
|
||||
// HaltWithInterruptsDisabled() deliberately returns with IF clear so
|
||||
// the next readiness check can lead into an atomic STI+HLT. Deferred
|
||||
// work cannot inherit that state: xHCI/Bluetooth service contains
|
||||
// wall-clock waits and depends on the BSP timer and device MSIs. If
|
||||
// it runs with IF=0, GetMilliseconds() never advances on the BSP and
|
||||
// the completion interrupt cannot arrive, producing a permanent idle-
|
||||
// loop freeze. Keep IRQs enabled for all active idle-context work and
|
||||
// disable them again only immediately before entering HLT below.
|
||||
asm volatile("sti" ::: "memory");
|
||||
|
||||
ServiceDeferredWork();
|
||||
|
||||
if (cpu == nullptr || cpu->cpuIndex != 0 || !g_schedEnabled || g_ticksPerMs == 0) {
|
||||
// Non-BSP or pre-scheduler fallback: plain HLT keeps the LAPIC
|
||||
// timer running in C1 on hardware where MWAIT promotes to deeper
|
||||
// states that freeze the timer (no ARAT in practice).
|
||||
asm volatile("cli" ::: "memory");
|
||||
Hal::HaltWithInterruptsDisabled();
|
||||
return;
|
||||
}
|
||||
@@ -256,6 +323,7 @@ namespace Timekeeping {
|
||||
// HLT (vs MWAIT) is the second half: MWAIT EAX=0 is a *hint* and
|
||||
// the CPU may go deeper than C1; HLT generally stays in C1, which
|
||||
// is the deepest state guaranteed to keep the LAPIC timer alive.
|
||||
asm volatile("cli" ::: "memory");
|
||||
Hal::HaltWithInterruptsDisabled();
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@ namespace Timekeeping {
|
||||
// Enable scheduler tick (called after scheduler is initialized)
|
||||
void EnableSchedulerTick();
|
||||
|
||||
// Run bounded process-context bottom halves from an idle CPU.
|
||||
void ServiceDeferredWork();
|
||||
|
||||
// Enter one idle wait cycle for the current CPU. Uses HLT and relies on
|
||||
// the boot-time periodic LAPIC timer to wake -- one-shot wake doesn't
|
||||
// survive deep C-states on some laptop CPUs.
|
||||
|
||||
Reference in new issue
Block a user