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
+4
View File
@@ -735,6 +735,10 @@ namespace Drivers::Net::E1000E {
return g_initialized;
}
bool RequiresPolling() {
return g_initialized && g_pollingMode;
}
void SetRxCallback(RxCallback callback) {
g_rxCallback = callback;
}
+3
View File
@@ -147,6 +147,9 @@ namespace Drivers::Net::E1000E {
// Check if the device was found and initialized
bool IsInitialized();
// Returns true only when the driver had to fall back to timer polling.
bool RequiresPolling();
// RX callback type: called with (packet data, length)
using RxCallback = void(*)(const uint8_t* data, uint16_t length);
+8 -1
View File
@@ -49,6 +49,7 @@ namespace Drivers::USB::Xhci {
// Hot-plug deferred work
static volatile bool g_hotplugPending[MAX_PORTS] = {};
static volatile bool g_deferredWorkPending = false;
static bool g_hotplugProcessing = false;
// MMIO region pointers
@@ -302,6 +303,7 @@ namespace Drivers::USB::Xhci {
// Defer enumeration to ProcessDeferredWork (called from timer tick)
if (g_bootScanComplete && portId >= 1 && portId <= g_maxPorts) {
g_hotplugPending[portId - 1] = true;
g_deferredWorkPending = true;
}
break;
}
@@ -723,15 +725,20 @@ namespace Drivers::USB::Xhci {
return g_initialized;
}
bool HasDeferredWork() {
return g_initialized && g_deferredWorkPending;
}
// -------------------------------------------------------------------------
// ProcessDeferredWork - handle hot-plug outside interrupt context
// Called from timer tick (same pattern as E1000E::Poll)
// -------------------------------------------------------------------------
void ProcessDeferredWork() {
if (!g_initialized || !g_bootScanComplete) return;
if (!g_initialized || !g_bootScanComplete || !g_deferredWorkPending) return;
if (g_hotplugProcessing) return;
g_hotplugProcessing = true;
g_deferredWorkPending = false;
for (uint32_t port = 0; port < g_maxPorts; port++) {
if (!g_hotplugPending[port]) continue;
+1
View File
@@ -292,6 +292,7 @@ namespace Drivers::USB::Xhci {
void Initialize();
bool Probe(const Pci::PciDevice& dev);
bool IsInitialized();
bool HasDeferredWork();
// Deferred hot-plug processing (call from timer tick, not interrupt context)
void ProcessDeferredWork();
+2 -2
View File
@@ -201,11 +201,11 @@ extern "C" void kmain() {
if (bspCpu && bspCpu->hasMwait) {
static volatile uint64_t s_bspIdleMonitor = 0;
for (;;) {
Hal::IdleWait(&s_bspIdleMonitor);
Timekeeping::IdleOnce(true, &s_bspIdleMonitor);
}
} else {
for (;;) {
asm volatile("hlt");
Timekeeping::IdleOnce(false);
}
}
}
+46 -20
View File
@@ -443,6 +443,46 @@ namespace Sched {
schedLock.Release();
}
bool HasReadyProcesses() {
return readyCount > 0;
}
void RunBspMaintenance() {
schedLock.Acquire();
uint64_t now = Timekeeping::GetTicks();
for (int i = 0; i < MaxProcesses; i++) {
if (processTable[i].state == ProcessState::Blocked &&
processTable[i].sleepUntilTick != 0 &&
now >= processTable[i].sleepUntilTick) {
processTable[i].sleepUntilTick = 0;
processTable[i].waitingForPid = -1;
processTable[i].waitingOnObject = nullptr;
processTable[i].state = ProcessState::Ready;
readyCount++;
}
}
schedLock.Release();
ReclaimTerminated();
}
uint64_t GetNextDeadlineTick() {
uint64_t nextDeadline = 0;
schedLock.Acquire();
for (int i = 0; i < MaxProcesses; i++) {
if (processTable[i].state != ProcessState::Blocked) continue;
uint64_t deadline = processTable[i].sleepUntilTick;
if (deadline == 0) continue;
if (nextDeadline == 0 || deadline < nextDeadline) {
nextDeadline = deadline;
}
}
schedLock.Release();
return nextDeadline;
}
void Schedule() {
auto* cpu = Smp::GetCurrentCpuData();
@@ -532,28 +572,12 @@ namespace Sched {
schedLock.Release();
}
void Tick() {
void Tick(uint32_t elapsedMs) {
auto* cpu = Smp::GetCurrentCpuData();
// BSP: wake sleeping processes and reclaim terminated slots
if (cpu->cpuIndex == 0) {
schedLock.Acquire();
uint64_t now = Timekeeping::GetTicks();
for (int i = 0; i < MaxProcesses; i++) {
if (processTable[i].state == ProcessState::Blocked &&
processTable[i].sleepUntilTick != 0 &&
now >= processTable[i].sleepUntilTick) {
processTable[i].sleepUntilTick = 0;
processTable[i].waitingForPid = -1;
processTable[i].waitingOnObject = nullptr;
processTable[i].state = ProcessState::Ready;
readyCount++;
}
}
schedLock.Release();
// Reclaim terminated process memory (BSP only, once per tick)
ReclaimTerminated();
RunBspMaintenance();
}
int slot = cpu->currentSlot;
@@ -577,8 +601,10 @@ namespace Sched {
return;
}
if (processTable[slot].sliceRemaining > 0) {
processTable[slot].sliceRemaining--;
if (processTable[slot].sliceRemaining > elapsedMs) {
processTable[slot].sliceRemaining -= elapsedMs;
} else {
processTable[slot].sliceRemaining = 0;
}
if (processTable[slot].sliceRemaining == 0) {
+15 -3
View File
@@ -41,7 +41,7 @@ namespace Sched {
uint64_t savedRsp;
uint64_t stackBase; // Bottom of allocated kernel stack (lowest address)
uint64_t entryPoint;
uint64_t sliceRemaining; // Ticks left in current time slice
uint64_t sliceRemaining; // Milliseconds left in current time slice
uint64_t pml4Phys; // Physical address of per-process PML4
uint64_t kernelStackTop; // Top of kernel stack (for TSS RSP0 / SYSCALL)
uint64_t userStackTop; // User-space stack top
@@ -87,8 +87,12 @@ namespace Sched {
int Spawn(const char* vfsPath, const char* args = nullptr);
void Schedule();
// Called from the APIC timer handler on every tick (per-CPU).
void Tick();
// True when there is runnable work somewhere in the process table.
bool HasReadyProcesses();
// Called from the APIC timer handler with the elapsed time for that CPU's
// tick interval. The BSP runs at 1 ms; APs may use a coarser interval.
void Tick(uint32_t elapsedMs = 1);
// Get the PID of the currently running process (-1 if idle)
int GetCurrentPid();
@@ -112,6 +116,14 @@ namespace Sched {
// timeoutMs == 0 means wait indefinitely.
void BlockOnObject(void* object, uint64_t timeoutMs = 0);
// BSP-only scheduler housekeeping: wake expired sleepers and reclaim
// terminated process resources.
void RunBspMaintenance();
// Return the earliest blocked sleep/object timeout deadline in ticks,
// or 0 when no timed waits are pending.
uint64_t GetNextDeadlineTick();
// Wake any processes blocked on the given object.
void WakeObjectWaiters(void* object);
+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);
};
+31 -13
View File
@@ -267,6 +267,36 @@ static void text_fit(const char* src, char* out, int out_len, int max_w) {
montauk::strncpy(out, ell, out_len - 1);
}
static void format_resolved_transport_line(const IppUri& uri, char* out, int out_len) {
if (!out || out_len <= 0) return;
out[0] = '\0';
const char* transport = uri.use_tls ? "TLS" : "Plain TCP";
uint32_t ip = 0;
if (!parse_ipv4_literal(uri.host, &ip))
ip = g_app.probe_caps.resolved_ip;
if (ip != 0) {
char ip_text[20];
format_ipv4(ip_text, sizeof(ip_text), ip);
snprintf(out, out_len, "Resolved: %s Transport: %s", ip_text, transport);
return;
}
if (host_looks_like_mdns(uri.host)) {
snprintf(out, out_len,
"Resolved: unavailable (.local/mDNS not supported yet) Transport: %s",
transport);
return;
}
if (g_app.probe_valid)
snprintf(out, out_len, "Resolved: unavailable Transport: %s", transport);
else
snprintf(out, out_len, "Resolved: run Probe to resolve current printer Transport: %s",
transport);
}
static bool main_mouse_in_rect(const Rect& rect) {
return rect.contains(g_app.mouse_x, g_app.mouse_y);
}
@@ -807,17 +837,7 @@ static void render_details(Canvas& c, const Layout& lo, const mtk::Theme& theme)
c.text(x, y, line, dim);
y += sfh + 4;
uint32_t ip = 0;
if (resolve_host(normalized.host, &ip)) {
char ip_text[20];
format_ipv4(ip_text, sizeof(ip_text), ip);
snprintf(line, sizeof(line), "Resolved: %s Transport: %s", ip_text,
normalized.use_tls ? "TLS" : "Plain TCP");
} else if (host_looks_like_mdns(normalized.host)) {
safe_copy(line, sizeof(line), "Resolved: unavailable (.local/mDNS not supported yet)");
} else {
safe_copy(line, sizeof(line), "Resolved: unavailable");
}
format_resolved_transport_line(normalized, line, sizeof(line));
text_fit(line, line, sizeof(line), w);
c.text(x, y, line, dim);
y += sfh + 4;
@@ -943,8 +963,6 @@ static void render_details(Canvas& c, const Layout& lo, const mtk::Theme& theme)
}
static void render() {
refresh_state();
mtk::StandaloneHost host(&g_win);
Canvas c = host.canvas();
mtk::Theme theme = printers_theme();