feat: various power and thermal optimizations, fix Printers app regression

This commit is contained in:
2026-04-20 16:00:52 +02:00
parent ad68f99b74
commit d1fffecff6
10 changed files with 250 additions and 65 deletions
+136 -26
View File
@@ -8,6 +8,7 @@
#include <atomic>
#include <Hal/Apic/Apic.hpp>
#include <Hal/Apic/Interrupts.hpp>
#include <Hal/Cpu.hpp>
#include <Hal/SmpBoot.hpp>
#include <Io/IoPort.hpp>
#include <Terminal/Terminal.hpp>
@@ -33,30 +34,90 @@ namespace Timekeeping {
// APIC timer divide configuration values
static constexpr uint32_t DIVIDE_BY_16 = 0x03;
// Timer tick rate: 1000 Hz (1 ms per tick)
static constexpr uint32_t TIMER_HZ = 1000;
// The BSP keeps a 1 ms tick for timekeeping and sleep deadlines.
// APs use a coarser 10 ms scheduler tick to avoid waking idle cores
// 1000 times per second with no useful work to do.
static constexpr uint32_t BSP_TICK_INTERVAL_MS = 1;
static constexpr uint32_t BSP_TIMER_HZ = 1000 / BSP_TICK_INTERVAL_MS;
static constexpr uint32_t AP_TICK_INTERVAL_MS = 10;
// Without a reschedule IPI, the BSP still needs a short periodic safety net
// while idle so input- and IPC-driven wakeups do not feel laggy.
static constexpr uint32_t BSP_IDLE_MAX_INTERVAL_MS = 2;
// Global state
static std::atomic<uint64_t> g_tickCount{0};
static uint32_t g_ticksPerMs = 0;
static bool g_schedEnabled = false;
static volatile bool g_bspIdleOneShotArmed = false;
static uint32_t g_bspIdleOneShotMs = 0;
static uint32_t g_bspIdleInitialCount = 0;
// Timer IRQ handler: BSP handles timekeeping+polling, all CPUs run scheduler
static uint32_t CountForIntervalMs(uint32_t intervalMs) {
return g_ticksPerMs * intervalMs;
}
static void ProgramTimer(bool periodic, uint32_t intervalMs) {
uint32_t lvt = (Hal::IRQ_VECTOR_BASE + Hal::IRQ_TIMER);
if (periodic) {
lvt |= LVT_PERIODIC;
}
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16);
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, lvt);
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL,
CountForIntervalMs(intervalMs));
}
static void ProgramBspPeriodicTimer() {
ProgramTimer(true, BSP_TICK_INTERVAL_MS);
}
static void ProgramBspIdleOneShotTimer(uint32_t intervalMs) {
g_bspIdleOneShotMs = intervalMs;
g_bspIdleInitialCount = CountForIntervalMs(intervalMs);
g_bspIdleOneShotArmed = true;
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16);
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT,
(Hal::IRQ_VECTOR_BASE + Hal::IRQ_TIMER));
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, g_bspIdleInitialCount);
}
static void WaitForInterrupt(bool hasMwait, volatile uint64_t* monitorAddr) {
if (hasMwait && monitorAddr != nullptr) {
Hal::IdleWait(monitorAddr);
} else {
asm volatile("hlt");
}
}
// Timer IRQ handler: BSP handles timekeeping and the few timer-driven
// fallbacks that are still required; APs only run scheduler accounting.
static void TimerHandler(uint8_t) {
auto* cpu = Smp::GetCurrentCpuData();
uint32_t schedElapsedMs = (cpu->cpuIndex == 0) ? BSP_TICK_INTERVAL_MS : AP_TICK_INTERVAL_MS;
if (cpu->cpuIndex == 0) {
// BSP: increment global tick count and poll devices
g_tickCount.fetch_add(1, std::memory_order_relaxed);
if (g_bspIdleOneShotArmed) {
schedElapsedMs = g_bspIdleOneShotMs;
g_bspIdleOneShotArmed = false;
ProgramBspPeriodicTimer();
}
Drivers::Net::E1000E::Poll();
Drivers::USB::Xhci::ProcessDeferredWork();
g_tickCount.fetch_add(schedElapsedMs, std::memory_order_relaxed);
if (Drivers::Net::E1000E::RequiresPolling()) {
Drivers::Net::E1000E::Poll();
}
if (Drivers::USB::Xhci::HasDeferredWork()) {
Drivers::USB::Xhci::ProcessDeferredWork();
}
Drivers::USB::HidKeyboard::Tick();
}
if (g_schedEnabled) {
Sched::Tick();
Sched::Tick(schedElapsedMs);
}
}
@@ -129,16 +190,11 @@ namespace Timekeeping {
Hal::RegisterIrqHandler(Hal::IRQ_TIMER, TimerHandler);
// Configure APIC timer: periodic mode, vector 32
uint32_t lvt = (Hal::IRQ_VECTOR_BASE + Hal::IRQ_TIMER) | LVT_PERIODIC;
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16);
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, lvt);
ProgramBspPeriodicTimer();
// Set initial count for 1ms intervals (1000 Hz tick rate)
uint32_t initialCount = g_ticksPerMs;
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, initialCount);
KernelLogStream(OK, "Timer") << "APIC timer started: " << base::dec << (uint64_t)TIMER_HZ
<< " Hz periodic, initial count=" << (uint64_t)initialCount;
KernelLogStream(OK, "Timer") << "APIC timer started: BSP " << base::dec
<< (uint64_t)BSP_TIMER_HZ << " Hz periodic, initial count="
<< (uint64_t)CountForIntervalMs(BSP_TICK_INTERVAL_MS);
}
void ApicTimerReinitialize() {
@@ -150,10 +206,8 @@ namespace Timekeeping {
// Reprogram the APIC timer registers (they were lost during S3).
// The calibrated g_ticksPerMs value is still valid (it's in RAM).
// The IRQ handler registration also survives (it's a function pointer array in RAM).
uint32_t lvt = (Hal::IRQ_VECTOR_BASE + Hal::IRQ_TIMER) | LVT_PERIODIC;
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16);
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, lvt);
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, g_ticksPerMs);
g_bspIdleOneShotArmed = false;
ProgramBspPeriodicTimer();
KernelLogStream(OK, "Timer") << "APIC timer restarted after S3 resume";
}
@@ -176,11 +230,67 @@ namespace Timekeeping {
// identical. This avoids PIT contention during AP boot.
if (g_ticksPerMs == 0) return;
// Configure periodic timer at 1000 Hz (same vector as BSP)
uint32_t lvt = (Hal::IRQ_VECTOR_BASE + Hal::IRQ_TIMER) | LVT_PERIODIC;
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16);
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, lvt);
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, g_ticksPerMs);
// Configure a coarser periodic timer on APs. The scheduler still gets
// a 10 ms time slice, but idle APs stop taking 1000 timer interrupts/sec.
ProgramTimer(true, AP_TICK_INTERVAL_MS);
}
void IdleOnce(bool hasMwait, volatile uint64_t* monitorAddr) {
auto* cpu = Smp::GetCurrentCpuData();
if (cpu == nullptr || cpu->cpuIndex != 0 || !g_schedEnabled || g_ticksPerMs == 0) {
WaitForInterrupt(hasMwait, monitorAddr);
return;
}
Sched::RunBspMaintenance();
if (Sched::HasReadyProcesses()) {
Sched::Schedule();
return;
}
uint32_t waitMs = BSP_TICK_INTERVAL_MS;
waitMs = BSP_IDLE_MAX_INTERVAL_MS;
uint64_t now = GetTicks();
uint64_t nextDeadline = Sched::GetNextDeadlineTick();
if (nextDeadline != 0) {
if (nextDeadline <= now) {
waitMs = BSP_TICK_INTERVAL_MS;
} else {
uint64_t untilDeadline = nextDeadline - now;
if (untilDeadline < waitMs) {
waitMs = (uint32_t)untilDeadline;
}
}
}
if (waitMs == 0) {
waitMs = BSP_TICK_INTERVAL_MS;
}
asm volatile("cli" ::: "memory");
ProgramBspIdleOneShotTimer(waitMs);
asm volatile("sti" ::: "memory");
WaitForInterrupt(hasMwait, monitorAddr);
asm volatile("cli" ::: "memory");
if (g_bspIdleOneShotArmed) {
uint32_t currentCount = Hal::LocalApic::ReadRegister(Hal::LocalApic::REG_TIMER_CURRENT);
uint32_t elapsedTicks = (g_bspIdleInitialCount > currentCount)
? (g_bspIdleInitialCount - currentCount)
: 0;
uint32_t elapsedMs = (g_ticksPerMs > 0) ? (elapsedTicks / g_ticksPerMs) : 0;
if (elapsedMs > 0) {
g_tickCount.fetch_add(elapsedMs, std::memory_order_relaxed);
}
g_bspIdleOneShotArmed = false;
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, 0);
ProgramBspPeriodicTimer();
}
asm volatile("sti" ::: "memory");
}
void Sleep(uint64_t ms) {
+4
View File
@@ -28,6 +28,10 @@ namespace Timekeeping {
// Enable scheduler tick (called after scheduler is initialized)
void EnableSchedulerTick();
// Enter one idle wait cycle for the current CPU. The BSP uses a
// one-shot LAPIC timer while idle; APs keep their existing simple wait.
void IdleOnce(bool hasMwait, volatile uint64_t* monitorAddr = nullptr);
// Busy-wait sleep for the given number of milliseconds
void Sleep(uint64_t ms);
};