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

+75 -7
View File
@@ -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();
}
+3
View File
@@ -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.