feat: various power and thermal optimizations, fix Printers app regression
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user