From 0dfb3e8dbbb2328a0a855c179552fbce3226e7e9 Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Tue, 21 Apr 2026 20:51:12 +0200 Subject: [PATCH] feat: further kernel power and power optimizations --- kernel/src/ACPI/CpuIdle.cpp | 514 +++++++++++++++++++++++ kernel/src/ACPI/CpuIdle.hpp | 33 ++ kernel/src/ACPI/FADT.cpp | 3 + kernel/src/ACPI/FADT.hpp | 3 + kernel/src/Api/Info.hpp | 2 +- kernel/src/Api/Input.hpp | 16 + kernel/src/Api/Syscall.cpp | 5 +- kernel/src/Api/Syscall.hpp | 3 + kernel/src/Drivers/Input/InputEvents.cpp | 70 +++ kernel/src/Drivers/Input/InputEvents.hpp | 22 + kernel/src/Drivers/Net/E1000E.cpp | 5 + kernel/src/Drivers/Net/E1000E.hpp | 2 + kernel/src/Drivers/PS2/Keyboard.cpp | 34 +- kernel/src/Drivers/PS2/Mouse.cpp | 5 +- kernel/src/Drivers/USB/HidKeyboard.cpp | 13 + kernel/src/Drivers/USB/HidKeyboard.hpp | 4 + kernel/src/Drivers/USB/Xhci.cpp | 4 +- kernel/src/Drivers/USB/Xhci.hpp | 1 + kernel/src/Hal/Apic/Apic.cpp | 17 + kernel/src/Hal/Apic/Apic.hpp | 1 + kernel/src/Hal/Apic/Interrupts.hpp | 1 + kernel/src/Hal/Cpu.hpp | 13 + kernel/src/Hal/SmpBoot.cpp | 12 +- kernel/src/Main.cpp | 2 + kernel/src/Sched/Scheduler.cpp | 84 ++++ kernel/src/Sched/Scheduler.hpp | 5 + kernel/src/Timekeeping/ApicTimer.cpp | 54 ++- kernel/src/Timekeeping/ApicTimer.hpp | 5 + programs/include/Api/Syscall.hpp | 1 + programs/include/montauk/syscall.h | 3 + programs/src/desktop/main.cpp | 10 +- 31 files changed, 895 insertions(+), 52 deletions(-) create mode 100644 kernel/src/ACPI/CpuIdle.cpp create mode 100644 kernel/src/ACPI/CpuIdle.hpp create mode 100644 kernel/src/Api/Input.hpp create mode 100644 kernel/src/Drivers/Input/InputEvents.cpp create mode 100644 kernel/src/Drivers/Input/InputEvents.hpp diff --git a/kernel/src/ACPI/CpuIdle.cpp b/kernel/src/ACPI/CpuIdle.cpp new file mode 100644 index 0000000..af46cb7 --- /dev/null +++ b/kernel/src/ACPI/CpuIdle.cpp @@ -0,0 +1,514 @@ +/* + * CpuIdle.cpp + * ACPI-guided CPU idle-state selection + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "CpuIdle.hpp" +#include +#include +#include +#include +#include +#include + +using namespace Kt; + +namespace Hal { + namespace CpuIdle { + + enum class IdleEntryKind : uint8_t { + FixedHardware, + IoPort, + }; + + struct IdleState { + bool Valid = false; + uint8_t CType = 0; + uint32_t LatencyUs = 0; + uint32_t PowerMw = 0; + IdleEntryKind EntryKind = IdleEntryKind::FixedHardware; + uint16_t IoPort = 0; + uint8_t WidthBits = 0; + }; + + static constexpr int MaxIdleStates = 8; + static constexpr uint8_t GAS_SYSTEM_IO = 0x01; + static constexpr uint8_t GAS_FIXED_HARDWARE = 0x7F; + static constexpr uint32_t ResidencyLatencyRatio = 8; + static constexpr uint32_t IoStateMinPredictedMs = 20; + static constexpr uint16_t LegacyC2MaxLatencyUs = 100; + + static IdleState g_idleStates[MaxIdleStates]{}; + static int g_idleStateCount = 0; + static volatile uint64_t g_fallbackMonitor = 0; + + static uint64_t ReadLe64(const uint8_t* data) { + uint64_t value = 0; + for (int i = 0; i < 8; i++) { + value |= (uint64_t)data[i] << (i * 8); + } + return value; + } + + static bool IsName(const char* actual, const char* expected) { + for (int i = 0; i < 4; i++) { + if (actual[i] != expected[i]) { + return false; + } + } + return true; + } + + static uint32_t DecodePkgLength(const uint8_t* aml, uint32_t length, uint32_t& pos) { + if (pos >= length) return 0; + + uint8_t lead = aml[pos]; + uint32_t byteCount = (lead >> 6) & 0x03; + + if (byteCount == 0) { + pos++; + return lead & 0x3F; + } + + uint32_t value = lead & 0x0F; + pos++; + + for (uint32_t i = 0; i < byteCount && pos < length; i++) { + value |= (uint32_t)aml[pos++] << (4 + 8 * i); + } + + return value; + } + + static bool DecodeInteger(const uint8_t* aml, uint32_t length, uint32_t& pos, uint64_t& value) { + if (pos >= length) return false; + + uint8_t op = aml[pos]; + switch (op) { + case AML::ZeroOp: + value = 0; + pos++; + return true; + case AML::OneOp: + value = 1; + pos++; + return true; + case AML::BytePrefix: + if (pos + 1 >= length) return false; + value = aml[pos + 1]; + pos += 2; + return true; + case AML::WordPrefix: + if (pos + 2 >= length) return false; + value = (uint16_t)aml[pos + 1] | ((uint16_t)aml[pos + 2] << 8); + pos += 3; + return true; + case AML::DWordPrefix: + if (pos + 4 >= length) return false; + value = (uint32_t)aml[pos + 1] + | ((uint32_t)aml[pos + 2] << 8) + | ((uint32_t)aml[pos + 3] << 16) + | ((uint32_t)aml[pos + 4] << 24); + pos += 5; + return true; + case AML::QWordPrefix: + if (pos + 8 >= length) return false; + value = ReadLe64(&aml[pos + 1]); + pos += 9; + return true; + default: + return false; + } + } + + static bool DecodeBufferObject(const uint8_t* aml, uint32_t length, uint32_t& pos, + const uint8_t*& data, uint32_t& dataLength) { + if (pos >= length || aml[pos] != AML::BufferOp) return false; + + pos++; + uint32_t pkgStart = pos; + uint32_t pkgLen = DecodePkgLength(aml, length, pos); + uint32_t end = pkgStart + pkgLen; + if (end > length) end = length; + + uint64_t declaredLength = 0; + if (!DecodeInteger(aml, end, pos, declaredLength)) { + return false; + } + + data = &aml[pos]; + dataLength = (uint32_t)(end - pos); + if (declaredLength < dataLength) { + dataLength = (uint32_t)declaredLength; + } + + pos = end; + return true; + } + + static bool DecodePackageObject(const uint8_t* aml, uint32_t length, uint32_t& pos, + const uint8_t*& data, uint32_t& dataLength) { + if (pos >= length) return false; + if (aml[pos] != AML::PackageOp && aml[pos] != AML::VarPackageOp) return false; + + pos++; + uint32_t pkgStart = pos; + uint32_t pkgLen = DecodePkgLength(aml, length, pos); + uint32_t end = pkgStart + pkgLen; + if (end > length) end = length; + + data = &aml[pos]; + dataLength = (uint32_t)(end - pos); + pos = end; + return true; + } + + static const char* EntryKindName(const IdleState& state) { + return (state.EntryKind == IdleEntryKind::IoPort) ? "ioport" : "fixed"; + } + + static bool ParseGas(const uint8_t* buffer, uint32_t length, IdleState& state) { + if (length < 12) { + return false; + } + + uint8_t addressSpace = buffer[0]; + uint8_t bitWidth = buffer[1]; + uint8_t accessSize = buffer[3]; + uint64_t address = ReadLe64(&buffer[4]); + + if (addressSpace == GAS_FIXED_HARDWARE) { + state.EntryKind = IdleEntryKind::FixedHardware; + return true; + } + + if (addressSpace != GAS_SYSTEM_IO || address == 0 || address > 0xFFFF) { + return false; + } + + uint8_t widthBits = bitWidth; + if (widthBits == 0) { + switch (accessSize) { + case 1: widthBits = 8; break; + case 2: widthBits = 16; break; + case 3: widthBits = 32; break; + default: widthBits = 8; break; + } + } + + if (widthBits != 8 && widthBits != 16 && widthBits != 32) { + return false; + } + + state.EntryKind = IdleEntryKind::IoPort; + state.IoPort = (uint16_t)address; + state.WidthBits = widthBits; + return true; + } + + static bool ParseIdleStatePackage(const uint8_t* aml, uint32_t length, IdleState& state) { + if (length < 2) return false; + + uint32_t pos = 1; // skip package element count byte + const uint8_t* regData = nullptr; + uint32_t regLength = 0; + uint64_t type = 0; + uint64_t latency = 0; + uint64_t power = 0; + + if (!DecodeBufferObject(aml, length, pos, regData, regLength)) { + return false; + } + if (!DecodeInteger(aml, length, pos, type)) { + return false; + } + if (!DecodeInteger(aml, length, pos, latency)) { + return false; + } + if (!DecodeInteger(aml, length, pos, power)) { + return false; + } + + state = {}; + state.CType = (uint8_t)type; + state.LatencyUs = (uint32_t)latency; + state.PowerMw = (uint32_t)power; + + if (!ParseGas(regData, regLength, state)) { + return false; + } + + state.Valid = true; + return true; + } + + static bool ParseCstObject(const char* path, const AML::Object& obj) { + if (obj.Type != AML::ObjectType::Package || obj.Buffer.Length < 2) { + return false; + } + + uint32_t pos = 1; // package element count byte + uint64_t declaredStateCount = 0; + if (!DecodeInteger(obj.Buffer.Data, obj.Buffer.Length, pos, declaredStateCount)) { + return false; + } + + int parsedCount = 0; + for (uint64_t i = 0; i < declaredStateCount && parsedCount < MaxIdleStates; i++) { + const uint8_t* nestedData = nullptr; + uint32_t nestedLength = 0; + if (!DecodePackageObject(obj.Buffer.Data, obj.Buffer.Length, pos, nestedData, nestedLength)) { + break; + } + + IdleState state{}; + if (!ParseIdleStatePackage(nestedData, nestedLength, state)) { + continue; + } + + g_idleStates[parsedCount++] = state; + } + + if (parsedCount == 0) { + return false; + } + + g_idleStateCount = parsedCount; + + KernelLogStream(OK, "CpuIdle") << "Loaded " << base::dec + << (uint64_t)g_idleStateCount << " ACPI idle state(s) from " << path; + + for (int i = 0; i < g_idleStateCount; i++) { + const auto& state = g_idleStates[i]; + KernelLogStream(INFO, "CpuIdle") << "C" << base::dec << (uint64_t)state.CType + << " latency=" << (uint64_t)state.LatencyUs << "us" + << " power=" << (uint64_t)state.PowerMw << "mW" + << " entry=" << EntryKindName(state); + } + + return true; + } + + static bool LoadLegacyPblkIdleStates(const FADT::ParsedFADT& fadt) { + if (!fadt.Valid || + fadt.WorstC2Latency == 0 || + fadt.WorstC2Latency > LegacyC2MaxLatencyUs) { + return false; + } + + auto& interp = AML::GetInterpreter(); + if (!interp.IsInitialized()) { + return false; + } + + auto& ns = interp.GetNamespace(); + char path[256]; + + for (int32_t nodeIndex = 0; nodeIndex < ns.NodeCount(); nodeIndex++) { + auto* node = ns.GetNode(nodeIndex); + if (node == nullptr || node->Obj.Type != AML::ObjectType::Processor) { + continue; + } + + uint32_t pblk = node->Obj.Processor.PblkAddr; + uint8_t pblkLen = node->Obj.Processor.PblkLen; + if (pblk == 0 || pblk > 0xFFFF || pblkLen < 5) { + continue; + } + + IdleState c1{}; + c1.Valid = true; + c1.CType = 1; + c1.LatencyUs = 1; + c1.EntryKind = IdleEntryKind::FixedHardware; + + IdleState c2{}; + c2.Valid = true; + c2.CType = 2; + c2.LatencyUs = fadt.WorstC2Latency; + c2.EntryKind = IdleEntryKind::IoPort; + c2.IoPort = (uint16_t)(pblk + 4); // ACPI legacy P_LVL2 register + c2.WidthBits = 8; + + g_idleStates[0] = c1; + g_idleStates[1] = c2; + g_idleStateCount = 2; + + ns.GetNodePath(nodeIndex, path, sizeof(path)); + KernelLogStream(OK, "CpuIdle") << "Using legacy ACPI P_BLK C2 idle state from " + << path << " latency=" << base::dec << (uint64_t)c2.LatencyUs + << "us port=" << base::hex << (uint64_t)c2.IoPort; + return true; + } + + return false; + } + + static const IdleState* SelectIdleState(uint32_t predictedIdleMs, bool hasMwait) { + uint64_t predictedIdleUs = (uint64_t)predictedIdleMs * 1000; + const IdleState* best = nullptr; + + for (int i = 0; i < g_idleStateCount; i++) { + const auto& state = g_idleStates[i]; + if (!state.Valid) continue; + + // Keep the first implementation conservative: skip ACPI I/O + // states deeper than C2 until cache/bus-master coordination is + // implemented. + if (state.EntryKind == IdleEntryKind::IoPort && state.CType > 2) { + continue; + } + if (state.EntryKind == IdleEntryKind::IoPort && + predictedIdleMs < IoStateMinPredictedMs) { + continue; + } + + if (state.CType > 1 && state.LatencyUs != 0 && + predictedIdleUs < (uint64_t)state.LatencyUs * ResidencyLatencyRatio) { + continue; + } + + if (state.EntryKind == IdleEntryKind::FixedHardware && !hasMwait && + state.CType > 1) { + continue; + } + + if (best == nullptr || state.CType >= best->CType) { + best = &state; + } + } + + return best; + } + + static void FallbackWait(bool hasMwait, volatile uint64_t* monitorAddr) { + volatile uint64_t* addr = (monitorAddr != nullptr) ? monitorAddr : &g_fallbackMonitor; + if (hasMwait) { + Hal::IdleWait(addr); + } else { + asm volatile("hlt"); + } + } + + static void FallbackWaitWithInterruptsDisabled(bool hasMwait, + volatile uint64_t* monitorAddr) { + volatile uint64_t* addr = (monitorAddr != nullptr) ? monitorAddr : &g_fallbackMonitor; + if (hasMwait) { + Hal::IdleWaitWithInterruptsDisabled(addr); + } else { + Hal::HaltWithInterruptsDisabled(); + } + } + + static void EnterIoIdleWithInterruptsDisabled(const IdleState& state) { + switch (state.WidthBits) { + case 16: { + uint16_t value; + asm volatile("sti\n\tinw %w1, %w0\n\tcli" + : "=a"(value) : "Nd"(state.IoPort) : "memory"); + break; + } + case 32: { + uint32_t value; + asm volatile("sti\n\tinl %w1, %0\n\tcli" + : "=a"(value) : "Nd"(state.IoPort) : "memory"); + break; + } + default: { + uint8_t value; + asm volatile("sti\n\tinb %w1, %b0\n\tcli" + : "=a"(value) : "Nd"(state.IoPort) : "memory"); + break; + } + } + } + + void Initialize(ACPI::CommonSDTHeader* xsdt) { + g_idleStateCount = 0; + for (int i = 0; i < MaxIdleStates; i++) { + g_idleStates[i] = {}; + } + + FADT::ParsedFADT fadt{}; + bool fadtReady = xsdt != nullptr && FADT::Parse(xsdt, fadt) && fadt.Valid; + if (fadtReady && fadt.SMI_CommandPort != 0 && fadt.CStateControl != 0) { + Io::Out8(fadt.CStateControl, (uint16_t)fadt.SMI_CommandPort); + KernelLogStream(INFO, "CpuIdle") << "Advertised ACPI _CST support"; + } + + auto& interp = AML::GetInterpreter(); + if (!interp.IsInitialized()) { + KernelLogStream(INFO, "CpuIdle") << "AML interpreter not ready - using MWAIT/HLT"; + return; + } + + auto& ns = interp.GetNamespace(); + char path[256]; + + for (int32_t nodeIndex = 0; nodeIndex < ns.NodeCount(); nodeIndex++) { + auto* node = ns.GetNode(nodeIndex); + if (node == nullptr) continue; + if (!IsName(node->Name, "_CST")) continue; + + ns.GetNodePath(nodeIndex, path, sizeof(path)); + + AML::Object obj{}; + if (!interp.EvaluateObject(path, obj)) { + continue; + } + + if (ParseCstObject(path, obj)) { + return; + } + } + + if (fadtReady && LoadLegacyPblkIdleStates(fadt)) { + return; + } + + KernelLogStream(INFO, "CpuIdle") + << "ACPI _CST unavailable or unsupported - using safe MWAIT/HLT idle fallback"; + } + + void Wait(uint32_t predictedIdleMs, bool hasMwait, volatile uint64_t* monitorAddr) { + const IdleState* state = SelectIdleState(predictedIdleMs, hasMwait); + if (state == nullptr) { + FallbackWait(hasMwait, monitorAddr); + return; + } + + if (state->EntryKind == IdleEntryKind::IoPort) { + switch (state->WidthBits) { + case 16: + (void)Io::In16(state->IoPort); + return; + case 32: + (void)Io::In32(state->IoPort); + return; + default: + (void)Io::In8(state->IoPort); + return; + } + } + + FallbackWait(hasMwait, monitorAddr); + } + + void WaitWithInterruptsDisabled(uint32_t predictedIdleMs, bool hasMwait, + volatile uint64_t* monitorAddr) { + const IdleState* state = SelectIdleState(predictedIdleMs, hasMwait); + if (state != nullptr && state->EntryKind == IdleEntryKind::IoPort) { + EnterIoIdleWithInterruptsDisabled(*state); + return; + } + + FallbackWaitWithInterruptsDisabled(hasMwait, monitorAddr); + } + + void WaitShallowWithInterruptsDisabled(bool hasMwait, volatile uint64_t* monitorAddr) { + FallbackWaitWithInterruptsDisabled(hasMwait, monitorAddr); + } + + }; +}; diff --git a/kernel/src/ACPI/CpuIdle.hpp b/kernel/src/ACPI/CpuIdle.hpp new file mode 100644 index 0000000..6f18412 --- /dev/null +++ b/kernel/src/ACPI/CpuIdle.hpp @@ -0,0 +1,33 @@ +/* + * CpuIdle.hpp + * ACPI-guided CPU idle-state selection + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include +#include + +namespace Hal { + namespace CpuIdle { + + // Initialize ACPI idle-state policy from the AML namespace and FADT. + void Initialize(ACPI::CommonSDTHeader* xsdt); + + // Enter an idle state chosen for the predicted idle duration. + // Falls back to MWAIT/HLT when ACPI data is unavailable. + void Wait(uint32_t predictedIdleMs, bool hasMwait, + volatile uint64_t* monitorAddr = nullptr); + + // Same as Wait(), but called with interrupts disabled and returns with + // interrupts disabled. Used by tickless idle paths that need atomic + // STI+HLT/MWAIT semantics. + void WaitWithInterruptsDisabled(uint32_t predictedIdleMs, bool hasMwait, + volatile uint64_t* monitorAddr = nullptr); + + // Shallow atomic idle for latency-sensitive interactive bursts. + void WaitShallowWithInterruptsDisabled(bool hasMwait, + volatile uint64_t* monitorAddr = nullptr); + + }; +}; diff --git a/kernel/src/ACPI/FADT.cpp b/kernel/src/ACPI/FADT.cpp index 70699a3..dcaa3de 100644 --- a/kernel/src/ACPI/FADT.cpp +++ b/kernel/src/ACPI/FADT.cpp @@ -78,6 +78,9 @@ namespace Hal { result.PM1EventLength = fadt->PM1EventLength; result.GPE0Length = fadt->GPE0Length; result.GPE1Length = fadt->GPE1Length; + result.CStateControl = fadt->CStateControl; + result.WorstC2Latency = fadt->WorstC2Latency; + result.WorstC3Latency = fadt->WorstC3Latency; result.AcpiEnable = fadt->AcpiEnable; result.AcpiDisable = fadt->AcpiDisable; result.Flags = fadt->Flags; diff --git a/kernel/src/ACPI/FADT.hpp b/kernel/src/ACPI/FADT.hpp index 912ea2e..d7d6692 100644 --- a/kernel/src/ACPI/FADT.hpp +++ b/kernel/src/ACPI/FADT.hpp @@ -85,6 +85,9 @@ namespace Hal { uint8_t PM1EventLength; uint8_t GPE0Length; uint8_t GPE1Length; + uint8_t CStateControl; + uint16_t WorstC2Latency; + uint16_t WorstC3Latency; uint8_t AcpiEnable; uint8_t AcpiDisable; uint32_t Flags; diff --git a/kernel/src/Api/Info.hpp b/kernel/src/Api/Info.hpp index d8fc704..bf23e04 100644 --- a/kernel/src/Api/Info.hpp +++ b/kernel/src/Api/Info.hpp @@ -22,7 +22,7 @@ namespace Montauk { for (int i = 0; ver[i]; i++) outInfo->osVersion[i] = ver[i]; outInfo->osVersion[5] = '\0'; - outInfo->apiVersion = 2; + outInfo->apiVersion = 3; outInfo->maxProcesses = Sched::MaxProcesses; } }; diff --git a/kernel/src/Api/Input.hpp b/kernel/src/Api/Input.hpp new file mode 100644 index 0000000..8cca8ae --- /dev/null +++ b/kernel/src/Api/Input.hpp @@ -0,0 +1,16 @@ +/* + * Input.hpp + * SYS_INPUT_WAIT syscall + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include + +namespace Montauk { + + static uint64_t Sys_InputWait(uint64_t observedSerial, uint64_t timeoutMs) { + return Drivers::InputEvents::WaitForChange(observedSerial, timeoutMs); + } + +} diff --git a/kernel/src/Api/Syscall.cpp b/kernel/src/Api/Syscall.cpp index 097bbde..12a1b12 100644 --- a/kernel/src/Api/Syscall.cpp +++ b/kernel/src/Api/Syscall.cpp @@ -28,6 +28,7 @@ #include "Random.hpp" // SYS_GETRANDOM #include "MemInfo.hpp" // SYS_MEMSTATS #include "Device.hpp" // SYS_DEVLIST, SYS_DISKINFO +#include "Input.hpp" // SYS_INPUT_WAIT #include "Storage.hpp" // SYS_PARTLIST, SYS_DISKREAD, SYS_DISKWRITE #include "Window.hpp" // SYS_WINCREATE, SYS_WINDESTROY, SYS_WINPRESENT, SYS_WINPOLL, SYS_WINENUM, SYS_WINMAP, SYS_WINSENDEVENT, SYS_WINRESIZE, SYS_WINSETSCALE, SYS_WINGETSCALE #include "Audio.hpp" // SYS_AUDIOOPEN, SYS_AUDIOCLOSE, SYS_AUDIOWRITE, SYS_AUDIOCTL @@ -439,6 +440,8 @@ namespace Montauk { UserMemory::IsUserPtr(frame->arg4) ? (uint64_t*)frame->arg4 : nullptr); case SYS_CLIPBOARD_CLEAR: return Sys_ClipboardClear(); + case SYS_INPUT_WAIT: + return (int64_t)Sys_InputWait(frame->arg1, frame->arg2); default: return -1; } @@ -465,7 +468,7 @@ namespace Montauk { Hal::WriteMSR(Hal::IA32_FMASK, 0x200); Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR=" - << kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 64 syscalls)"; + << kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 124 syscall slots)"; } } diff --git a/kernel/src/Api/Syscall.hpp b/kernel/src/Api/Syscall.hpp index c00e776..dae0846 100644 --- a/kernel/src/Api/Syscall.hpp +++ b/kernel/src/Api/Syscall.hpp @@ -219,6 +219,9 @@ namespace Montauk { static constexpr uint64_t SYS_CLIPBOARD_GET_TEXT = 121; static constexpr uint64_t SYS_CLIPBOARD_CLEAR = 122; + /* Input.hpp */ + static constexpr uint64_t SYS_INPUT_WAIT = 123; + static constexpr uint32_t CLIPBOARD_MAX_TEXT_BYTES = 256 * 1024; static constexpr uint32_t IPC_SIGNAL_READABLE = 1u << 0; diff --git a/kernel/src/Drivers/Input/InputEvents.cpp b/kernel/src/Drivers/Input/InputEvents.cpp new file mode 100644 index 0000000..97f91d1 --- /dev/null +++ b/kernel/src/Drivers/Input/InputEvents.cpp @@ -0,0 +1,70 @@ +/* + * InputEvents.cpp + * Shared input activity event source + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "InputEvents.hpp" +#include +#include +#include + +namespace Drivers::InputEvents { + + static std::atomic g_serial{1}; + static uint8_t g_waitObject; + + struct WaitContext { + uint64_t observedSerial; + }; + + static bool SerialStillObserved(void* rawContext) { + auto* context = static_cast(rawContext); + return context != nullptr && + g_serial.load(std::memory_order_acquire) == context->observedSerial; + } + + uint64_t GetSerial() { + return g_serial.load(std::memory_order_acquire); + } + + void NotifyActivity() { + Timekeeping::NoteInteractiveActivity(); + g_serial.fetch_add(1, std::memory_order_release); + Sched::WakeObjectWaiters(&g_waitObject); + } + + uint64_t WaitForChange(uint64_t observedSerial, uint64_t timeoutMs) { + uint64_t current = GetSerial(); + if (current != observedSerial || timeoutMs == 0) { + return current; + } + + WaitContext context{observedSerial}; + uint64_t start = Timekeeping::GetMilliseconds(); + + for (;;) { + current = GetSerial(); + if (current != observedSerial) { + return current; + } + + uint64_t waitMs = 0; + if (timeoutMs == UINT64_MAX) { + waitMs = 0; + } else { + uint64_t elapsed = Timekeeping::GetMilliseconds() - start; + if (elapsed >= timeoutMs) { + return current; + } + waitMs = timeoutMs - elapsed; + if (waitMs == 0) { + waitMs = 1; + } + } + + Sched::BlockOnObjectIf(&g_waitObject, waitMs, SerialStillObserved, &context); + } + } + +} diff --git a/kernel/src/Drivers/Input/InputEvents.hpp b/kernel/src/Drivers/Input/InputEvents.hpp new file mode 100644 index 0000000..a45e7ce --- /dev/null +++ b/kernel/src/Drivers/Input/InputEvents.hpp @@ -0,0 +1,22 @@ +/* + * InputEvents.hpp + * Shared input activity event source + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include + +namespace Drivers::InputEvents { + + // Monotonic serial incremented after mouse or keyboard state changes. + uint64_t GetSerial(); + + // Notify latency-sensitive input activity and wake input waiters. + void NotifyActivity(); + + // Wait until the serial differs from observedSerial or timeoutMs elapses. + // timeoutMs == 0 is a poll; timeoutMs == UINT64_MAX waits indefinitely. + uint64_t WaitForChange(uint64_t observedSerial, uint64_t timeoutMs); + +} diff --git a/kernel/src/Drivers/Net/E1000E.cpp b/kernel/src/Drivers/Net/E1000E.cpp index 9fd66d0..9377853 100644 --- a/kernel/src/Drivers/Net/E1000E.cpp +++ b/kernel/src/Drivers/Net/E1000E.cpp @@ -124,6 +124,10 @@ namespace Drivers::Net::E1000E { return *(volatile uint32_t*)(g_mmioBase + reg); } + static void ConfigureInterruptModeration() { + WriteReg(REG_ITR, ITR_INTERVAL_512US); + } + // ------------------------------------------------------------------------- // SW/FW semaphore (prevents conflicts with Intel Management Engine) // ------------------------------------------------------------------------- @@ -586,6 +590,7 @@ namespace Drivers::Net::E1000E { SetupRx(); SetupTx(); + ConfigureInterruptModeration(); if (SetupMsi(bus, device, function)) { WriteReg(REG_IMS, ICR_RXT0 | ICR_TXDW | ICR_TXQE | ICR_LSC | ICR_RXDMT0); diff --git a/kernel/src/Drivers/Net/E1000E.hpp b/kernel/src/Drivers/Net/E1000E.hpp index 2f71ea0..32ad7cb 100644 --- a/kernel/src/Drivers/Net/E1000E.hpp +++ b/kernel/src/Drivers/Net/E1000E.hpp @@ -17,6 +17,7 @@ namespace Drivers::Net::E1000E { constexpr uint32_t REG_CTRL_EXT = 0x0018; // Extended Device Control constexpr uint32_t REG_MDIC = 0x0020; // MDI Control (PHY access) constexpr uint32_t REG_ICR = 0x00C0; // Interrupt Cause Read + constexpr uint32_t REG_ITR = 0x00C4; // Interrupt Throttling constexpr uint32_t REG_IMS = 0x00D0; // Interrupt Mask Set constexpr uint32_t REG_IMC = 0x00D8; // Interrupt Mask Clear constexpr uint32_t REG_RCTL = 0x0100; // Receive Control @@ -93,6 +94,7 @@ namespace Drivers::Net::E1000E { constexpr uint32_t ICR_RXDMT0 = (1 << 4); // RX Descriptor Minimum Threshold constexpr uint32_t ICR_RXO = (1 << 6); // Receiver Overrun constexpr uint32_t ICR_RXT0 = (1 << 7); // Receiver Timer Interrupt + constexpr uint32_t ITR_INTERVAL_512US = 2048; // 2048 * 256 ns // TX descriptor command bits constexpr uint8_t TXCMD_EOP = (1 << 0); // End Of Packet diff --git a/kernel/src/Drivers/PS2/Keyboard.cpp b/kernel/src/Drivers/PS2/Keyboard.cpp index f8cc4a3..6e4745d 100644 --- a/kernel/src/Drivers/PS2/Keyboard.cpp +++ b/kernel/src/Drivers/PS2/Keyboard.cpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace Drivers::PS2::Keyboard { @@ -86,14 +87,15 @@ namespace Drivers::PS2::Keyboard { return shift ^ caps; } - static void BufferPush(const KeyEvent& event) { + static bool BufferPush(const KeyEvent& event) { uint32_t nextHead = (g_BufferHead + 1) & (KeyBufferSize - 1); if (nextHead == g_BufferTail) { // Buffer full, drop the event - return; + return false; } g_KeyBuffer[g_BufferHead] = event; g_BufferHead = nextHead; + return true; } static bool BufferPop(KeyEvent& event) { @@ -105,6 +107,16 @@ namespace Drivers::PS2::Keyboard { return true; } + static void PushAndNotify(const KeyEvent& event) { + g_BufferLock.Acquire(); + bool pushed = BufferPush(event); + g_BufferLock.Release(); + + if (pushed) { + Drivers::InputEvents::NotifyActivity(); + } + } + void Initialize() { Kt::KernelLogStream(Kt::INFO, "PS2/KB") << "Initializing keyboard driver"; @@ -169,9 +181,7 @@ namespace Drivers::PS2::Keyboard { .Alt = g_Modifiers.LeftAlt || g_Modifiers.RightAlt, .CapsLock = g_Modifiers.CapsLock }; - g_BufferLock.Acquire(); - BufferPush(event); - g_BufferLock.Release(); + PushAndNotify(event); return; } @@ -229,9 +239,7 @@ namespace Drivers::PS2::Keyboard { .Alt = g_Modifiers.LeftAlt || g_Modifiers.RightAlt, .CapsLock = g_Modifiers.CapsLock }; - g_BufferLock.Acquire(); - BufferPush(event); - g_BufferLock.Release(); + PushAndNotify(event); return; } if (isModifier) return; @@ -256,9 +264,7 @@ namespace Drivers::PS2::Keyboard { .CapsLock = g_Modifiers.CapsLock }; - g_BufferLock.Acquire(); - BufferPush(event); - g_BufferLock.Release(); + PushAndNotify(event); } bool IsKeyAvailable() { @@ -292,9 +298,7 @@ namespace Drivers::PS2::Keyboard { } void InjectKeyEvent(const KeyEvent& event) { - g_BufferLock.Acquire(); - BufferPush(event); - g_BufferLock.Release(); + PushAndNotify(event); } -}; \ No newline at end of file +}; diff --git a/kernel/src/Drivers/PS2/Mouse.cpp b/kernel/src/Drivers/PS2/Mouse.cpp index b846aa7..6662c48 100644 --- a/kernel/src/Drivers/PS2/Mouse.cpp +++ b/kernel/src/Drivers/PS2/Mouse.cpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace Drivers::PS2::Mouse { @@ -170,6 +171,7 @@ namespace Drivers::PS2::Mouse { if (g_State.Y > g_MaxY) g_State.Y = g_MaxY; g_StateLock.Release(); + Drivers::InputEvents::NotifyActivity(); } MouseState GetMouseState() { @@ -236,6 +238,7 @@ namespace Drivers::PS2::Mouse { if (g_State.Y > g_MaxY) g_State.Y = g_MaxY; g_StateLock.Release(); + Drivers::InputEvents::NotifyActivity(); } -}; \ No newline at end of file +}; diff --git a/kernel/src/Drivers/USB/HidKeyboard.cpp b/kernel/src/Drivers/USB/HidKeyboard.cpp index 78d38be..d79ab25 100644 --- a/kernel/src/Drivers/USB/HidKeyboard.cpp +++ b/kernel/src/Drivers/USB/HidKeyboard.cpp @@ -377,4 +377,17 @@ namespace Drivers::USB::HidKeyboard { } } + uint64_t GetNextTickDeadline() { + if (g_RepeatKey == 0) { + return 0; + } + + uint64_t firstRepeat = g_RepeatStartMs + TYPEMATIC_DELAY_MS; + if (g_LastRepeatMs == 0 || g_LastRepeatMs < firstRepeat) { + return firstRepeat; + } + + return g_LastRepeatMs + TYPEMATIC_PERIOD_MS; + } + } diff --git a/kernel/src/Drivers/USB/HidKeyboard.hpp b/kernel/src/Drivers/USB/HidKeyboard.hpp index 1159b94..99137de 100644 --- a/kernel/src/Drivers/USB/HidKeyboard.hpp +++ b/kernel/src/Drivers/USB/HidKeyboard.hpp @@ -18,4 +18,8 @@ namespace Drivers::USB::HidKeyboard { // Timer tick for typematic repeat (call from periodic timer, e.g. 1ms) void Tick(); + // Earliest millisecond deadline when typematic repeat needs service again. + // Returns 0 when no repeat is pending. + uint64_t GetNextTickDeadline(); + }; diff --git a/kernel/src/Drivers/USB/Xhci.cpp b/kernel/src/Drivers/USB/Xhci.cpp index 315bcb9..1c370ed 100644 --- a/kernel/src/Drivers/USB/Xhci.cpp +++ b/kernel/src/Drivers/USB/Xhci.cpp @@ -959,7 +959,7 @@ namespace Drivers::USB::Xhci { // Enable interrupter 0 WriteRt(IR0_IMAN, IMAN_IE); - WriteRt(IR0_IMOD, 0); + WriteRt(IR0_IMOD, IMOD_INTERVAL_100US); // Start controller WriteOp(OP_USBCMD, USBCMD_RS | USBCMD_INTE | USBCMD_HSEE); @@ -1269,7 +1269,7 @@ namespace Drivers::USB::Xhci { // Step 13: Enable interrupter 0 // ----------------------------------------------------------------- WriteRt(IR0_IMAN, IMAN_IE); - WriteRt(IR0_IMOD, 0); // No moderation + WriteRt(IR0_IMOD, IMOD_INTERVAL_100US); // ----------------------------------------------------------------- // Step 14: Start controller diff --git a/kernel/src/Drivers/USB/Xhci.hpp b/kernel/src/Drivers/USB/Xhci.hpp index 7d5b4f5..c9fd05f 100644 --- a/kernel/src/Drivers/USB/Xhci.hpp +++ b/kernel/src/Drivers/USB/Xhci.hpp @@ -110,6 +110,7 @@ namespace Drivers::USB::Xhci { // IMAN bits constexpr uint32_t IMAN_IP = (1 << 0); // Interrupt Pending constexpr uint32_t IMAN_IE = (1 << 1); // Interrupt Enable + constexpr uint32_t IMOD_INTERVAL_100US = 400; // 100 us in 250 ns units // --------------------------------------------------------------------------- // TRB (Transfer Request Block) - 16 bytes diff --git a/kernel/src/Hal/Apic/Apic.cpp b/kernel/src/Hal/Apic/Apic.cpp index 2a4df27..2f1e2cc 100644 --- a/kernel/src/Hal/Apic/Apic.cpp +++ b/kernel/src/Hal/Apic/Apic.cpp @@ -14,6 +14,8 @@ using namespace Kt; namespace Hal { namespace LocalApic { static volatile uint32_t* g_apicBase = nullptr; + static constexpr uint32_t ICR_DELIVERY_STATUS = (1 << 12); + static constexpr uint32_t ICR_LEVEL_ASSERT = (1 << 14); static inline uint64_t ReadMSR(uint32_t msr) { uint32_t lo, hi; @@ -113,6 +115,21 @@ namespace Hal { WriteRegister(REG_EOI, 0); } + void SendFixedIpi(uint32_t apicId, uint8_t vector) { + if (g_apicBase == nullptr) return; + + while (ReadRegister(REG_ICR_LOW) & ICR_DELIVERY_STATUS) { + asm volatile("pause"); + } + + WriteRegister(REG_ICR_HIGH, apicId << 24); + WriteRegister(REG_ICR_LOW, (uint32_t)vector | ICR_LEVEL_ASSERT); + + while (ReadRegister(REG_ICR_LOW) & ICR_DELIVERY_STATUS) { + asm volatile("pause"); + } + } + uint32_t GetId() { return (ReadRegister(REG_ID) >> 24) & 0xFF; } diff --git a/kernel/src/Hal/Apic/Apic.hpp b/kernel/src/Hal/Apic/Apic.hpp index cef1177..438ed78 100644 --- a/kernel/src/Hal/Apic/Apic.hpp +++ b/kernel/src/Hal/Apic/Apic.hpp @@ -44,6 +44,7 @@ namespace Hal { void Reinitialize(); void SendEOI(); + void SendFixedIpi(uint32_t apicId, uint8_t vector); uint32_t GetId(); uint32_t ReadRegister(uint32_t reg); diff --git a/kernel/src/Hal/Apic/Interrupts.hpp b/kernel/src/Hal/Apic/Interrupts.hpp index 62753e7..1d39122 100644 --- a/kernel/src/Hal/Apic/Interrupts.hpp +++ b/kernel/src/Hal/Apic/Interrupts.hpp @@ -28,6 +28,7 @@ namespace Hal { constexpr uint8_t IRQ_MOUSE = 12; constexpr uint8_t IRQ_ATA1 = 14; constexpr uint8_t IRQ_ATA2 = 15; + constexpr uint8_t IRQ_RESCHEDULE = 47; // Register a handler for the given IRQ number (0-47) void RegisterIrqHandler(uint8_t irq, IrqHandler handler); diff --git a/kernel/src/Hal/Cpu.hpp b/kernel/src/Hal/Cpu.hpp index 0062b52..f93db3b 100644 --- a/kernel/src/Hal/Cpu.hpp +++ b/kernel/src/Hal/Cpu.hpp @@ -31,6 +31,19 @@ namespace Hal { asm volatile("mwait" :: "a"(0x00), "c"(0)); } + // Atomic idle entry for paths that have already disabled interrupts. + // Keeping STI immediately adjacent to HLT/MWAIT avoids the classic race + // where an IRQ is handled between enabling interrupts and entering idle, + // after which the CPU sleeps until the next timer deadline. + inline void HaltWithInterruptsDisabled() { + asm volatile("sti\n\thlt\n\tcli" ::: "memory"); + } + + inline void IdleWaitWithInterruptsDisabled(volatile uint64_t* monitorAddr) { + asm volatile("monitor" :: "a"(monitorAddr), "c"(0), "d"(0) : "memory"); + asm volatile("sti\n\tmwait\n\tcli" :: "a"(0x00), "c"(0) : "memory"); + } + inline void EnableSSE() { uint64_t cr0; asm volatile("mov %%cr0, %0" : "=r"(cr0)); diff --git a/kernel/src/Hal/SmpBoot.cpp b/kernel/src/Hal/SmpBoot.cpp index ad3625e..90278c4 100644 --- a/kernel/src/Hal/SmpBoot.cpp +++ b/kernel/src/Hal/SmpBoot.cpp @@ -5,6 +5,7 @@ */ #include "SmpBoot.hpp" +#include #include #include #include @@ -212,16 +213,9 @@ namespace Smp { // --- Enable interrupts and enter idle loop --- asm volatile("sti"); - // Use MWAIT for deeper C-states if available, otherwise HLT. static volatile uint64_t s_idleMonitor = 0; - if (cpu->hasMwait) { - for (;;) { - Hal::IdleWait(&s_idleMonitor); - } - } else { - for (;;) { - asm volatile("hlt"); - } + for (;;) { + Hal::CpuIdle::Wait(10, cpu->hasMwait, &s_idleMonitor); } } diff --git a/kernel/src/Main.cpp b/kernel/src/Main.cpp index 81f4dc6..58c2211 100644 --- a/kernel/src/Main.cpp +++ b/kernel/src/Main.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -140,6 +141,7 @@ extern "C" void kmain() { #if defined (__x86_64__) if (g_acpi.GetXSDT() != nullptr) { Hal::AcpiShutdown::Initialize(g_acpi.GetXSDT()); + Hal::CpuIdle::Initialize(g_acpi.GetXSDT()); Hal::ApicInitialize(g_acpi.GetXSDT()); diff --git a/kernel/src/Sched/Scheduler.cpp b/kernel/src/Sched/Scheduler.cpp index ed575ef..622c17b 100644 --- a/kernel/src/Sched/Scheduler.cpp +++ b/kernel/src/Sched/Scheduler.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,41 @@ namespace Sched { return (uint64_t)Memory::VMM::g_paging->PML4; } + static void RescheduleIpiHandler(uint8_t) { + auto* cpu = Smp::GetCurrentCpuData(); + if (cpu == nullptr || cpu->currentSlot >= 0 || readyCount <= 0) { + return; + } + + Schedule(); + } + + static void KickOneIdleCpu(int sourceCpuIndex) { + if (readyCount <= 0) { + return; + } + + auto tryKick = [&](Smp::CpuData* target) { + if (target == nullptr || !target->started) return false; + if (target->cpuIndex == sourceCpuIndex) return false; + if (target->currentSlot >= 0) return false; + + Hal::LocalApic::SendFixedIpi(target->lapicId, + Hal::IRQ_VECTOR_BASE + Hal::IRQ_RESCHEDULE); + return true; + }; + + if (tryKick(Smp::GetCpuData(0))) { + return; + } + + for (int i = 0; i < Smp::GetCpuCount(); i++) { + if (tryKick(Smp::GetCpuData(i))) { + return; + } + } + } + static void SwitchAwayFromBlockedCurrentLocked() { auto* cpu = Smp::GetCurrentCpuData(); int slot = cpu->currentSlot; @@ -160,6 +196,7 @@ namespace Sched { } nextPid = 0; + Hal::RegisterIrqHandler(Hal::IRQ_RESCHEDULE, RescheduleIpiHandler); Kt::KernelLogStream(Kt::OK, "Sched") << "Initialized (" << MaxProcesses << " process slots, " << (uint64_t)TimeSliceMs << " ms time slice)"; @@ -398,6 +435,7 @@ namespace Sched { int resultPid = proc.pid; schedLock.Release(); + KickOneIdleCpu(Smp::GetCurrentCpuData() ? Smp::GetCurrentCpuData()->cpuIndex : -1); return resultPid; } @@ -448,6 +486,8 @@ namespace Sched { } void RunBspMaintenance() { + bool wokeProcesses = false; + schedLock.Acquire(); uint64_t now = Timekeeping::GetTicks(); for (int i = 0; i < MaxProcesses; i++) { @@ -459,10 +499,15 @@ namespace Sched { processTable[i].waitingOnObject = nullptr; processTable[i].state = ProcessState::Ready; readyCount++; + wokeProcesses = true; } } schedLock.Release(); + if (wokeProcesses) { + KickOneIdleCpu(0); + } + ReclaimTerminated(); } @@ -704,6 +749,10 @@ namespace Sched { processTable[next].runningOnCpu = cpu->cpuIndex; processTable[next].sliceRemaining = TimeSliceMs; + if (readyCount > 0) { + KickOneIdleCpu(cpu->cpuIndex); + } + uint64_t newCR3 = processTable[next].pml4Phys; cpu->kernelRsp = processTable[next].kernelStackTop; cpu->tss->rsp0 = processTable[next].kernelStackTop; @@ -789,6 +838,7 @@ namespace Sched { } schedLock.Release(); + KickOneIdleCpu(Smp::GetCurrentCpuData() ? Smp::GetCurrentCpuData()->cpuIndex : -1); // Safe to clean up resources now -- process is not running anywhere. WinServer::CleanupProcess(killedPid); @@ -898,9 +948,38 @@ namespace Sched { SwitchAwayFromBlockedCurrentLocked(); } + bool BlockOnObjectIf(void* object, uint64_t timeoutMs, + bool (*shouldBlock)(void*), void* context) { + if (object == nullptr || shouldBlock == nullptr) return false; + + auto* cpu = Smp::GetCurrentCpuData(); + if (cpu == nullptr) return false; + + int slot = cpu->currentSlot; + if (slot < 0) return false; + + schedLock.Acquire(); + + if (!shouldBlock(context)) { + schedLock.Release(); + return false; + } + + processTable[slot].state = ProcessState::Blocked; + processTable[slot].waitingForPid = -1; + processTable[slot].waitingOnObject = object; + processTable[slot].sleepUntilTick = (timeoutMs > 0) + ? (Timekeeping::GetTicks() + timeoutMs) + : 0; + processTable[slot].runningOnCpu = -1; + SwitchAwayFromBlockedCurrentLocked(); + return true; + } + void WakeObjectWaiters(void* object) { if (object == nullptr) return; + bool wokeAny = false; schedLock.Acquire(); for (int i = 0; i < MaxProcesses; i++) { if (processTable[i].state != ProcessState::Blocked) continue; @@ -911,8 +990,13 @@ namespace Sched { processTable[i].waitingForPid = -1; processTable[i].state = ProcessState::Ready; readyCount++; + wokeAny = true; } schedLock.Release(); + + if (wokeAny) { + KickOneIdleCpu(Smp::GetCurrentCpuData() ? Smp::GetCurrentCpuData()->cpuIndex : -1); + } } bool IsAlive(int pid) { diff --git a/kernel/src/Sched/Scheduler.hpp b/kernel/src/Sched/Scheduler.hpp index 54dfe5e..3b6030b 100644 --- a/kernel/src/Sched/Scheduler.hpp +++ b/kernel/src/Sched/Scheduler.hpp @@ -116,6 +116,11 @@ namespace Sched { // timeoutMs == 0 means wait indefinitely. void BlockOnObject(void* object, uint64_t timeoutMs = 0); + // Atomically check a condition under the scheduler lock and block only if + // it still holds. This prevents lost wakeups for edge-triggered waiters. + bool BlockOnObjectIf(void* object, uint64_t timeoutMs, + bool (*shouldBlock)(void*), void* context); + // BSP-only scheduler housekeeping: wake expired sleepers and reclaim // terminated process resources. void RunBspMaintenance(); diff --git a/kernel/src/Timekeeping/ApicTimer.cpp b/kernel/src/Timekeeping/ApicTimer.cpp index 6526dfa..e0fb85e 100644 --- a/kernel/src/Timekeeping/ApicTimer.cpp +++ b/kernel/src/Timekeeping/ApicTimer.cpp @@ -6,6 +6,7 @@ #include "ApicTimer.hpp" #include +#include #include #include #include @@ -40,9 +41,10 @@ namespace Timekeeping { 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; + // Keep a bounded periodic wake while otherwise tickless so wall-clock + // accounting and typematic repeat still advance even on a fully idle BSP. + static constexpr uint32_t BSP_IDLE_MAX_INTERVAL_MS = 50; + static constexpr uint32_t BSP_INTERACTIVE_IDLE_MAX_INTERVAL_MS = 1; // Global state static std::atomic g_tickCount{0}; @@ -52,6 +54,7 @@ namespace Timekeeping { static volatile bool g_bspIdleOneShotArmed = false; static uint32_t g_bspIdleOneShotMs = 0; static uint32_t g_bspIdleInitialCount = 0; + static std::atomic g_interactiveUntilMs{0}; static uint32_t CountForIntervalMs(uint32_t intervalMs) { return g_ticksPerMs * intervalMs; @@ -84,14 +87,6 @@ namespace Timekeeping { 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) { @@ -220,6 +215,20 @@ namespace Timekeeping { return g_tickCount.load(std::memory_order_relaxed); // 1 tick = 1 ms at 1000 Hz } + void NoteInteractiveActivity(uint32_t durationMs) { + uint64_t until = GetMilliseconds() + durationMs; + uint64_t current = g_interactiveUntilMs.load(std::memory_order_relaxed); + + while (until > current && + !g_interactiveUntilMs.compare_exchange_weak( + current, until, std::memory_order_relaxed, std::memory_order_relaxed)) { + } + } + + static bool IsInteractiveActivityActive() { + return GetMilliseconds() < g_interactiveUntilMs.load(std::memory_order_relaxed); + } + void EnableSchedulerTick() { g_schedEnabled = true; } @@ -238,7 +247,7 @@ namespace Timekeeping { 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); + Hal::CpuIdle::Wait(BSP_TICK_INTERVAL_MS, hasMwait, monitorAddr); return; } @@ -249,11 +258,19 @@ namespace Timekeeping { return; } - uint32_t waitMs = BSP_TICK_INTERVAL_MS; - waitMs = BSP_IDLE_MAX_INTERVAL_MS; + bool interactive = IsInteractiveActivityActive(); + uint32_t waitMs = interactive + ? BSP_INTERACTIVE_IDLE_MAX_INTERVAL_MS + : BSP_IDLE_MAX_INTERVAL_MS; uint64_t now = GetTicks(); uint64_t nextDeadline = Sched::GetNextDeadlineTick(); + uint64_t keyboardDeadline = Drivers::USB::HidKeyboard::GetNextTickDeadline(); + if (keyboardDeadline != 0 && + (nextDeadline == 0 || keyboardDeadline < nextDeadline)) { + nextDeadline = keyboardDeadline; + } + if (nextDeadline != 0) { if (nextDeadline <= now) { waitMs = BSP_TICK_INTERVAL_MS; @@ -271,11 +288,12 @@ namespace Timekeeping { asm volatile("cli" ::: "memory"); ProgramBspIdleOneShotTimer(waitMs); - asm volatile("sti" ::: "memory"); + if (interactive) { + Hal::CpuIdle::WaitShallowWithInterruptsDisabled(hasMwait, monitorAddr); + } else { + Hal::CpuIdle::WaitWithInterruptsDisabled(waitMs, hasMwait, monitorAddr); + } - 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) diff --git a/kernel/src/Timekeeping/ApicTimer.hpp b/kernel/src/Timekeeping/ApicTimer.hpp index a46dac7..783984c 100644 --- a/kernel/src/Timekeeping/ApicTimer.hpp +++ b/kernel/src/Timekeeping/ApicTimer.hpp @@ -32,6 +32,11 @@ namespace Timekeeping { // one-shot LAPIC timer while idle; APs keep their existing simple wait. void IdleOnce(bool hasMwait, volatile uint64_t* monitorAddr = nullptr); + // Mark recent latency-sensitive input activity. Idle policy uses this to + // stay in shallow, high-responsiveness mode briefly while the user is + // moving the pointer or dragging windows. + void NoteInteractiveActivity(uint32_t durationMs = 250); + // Busy-wait sleep for the given number of milliseconds void Sleep(uint64_t ms); }; diff --git a/programs/include/Api/Syscall.hpp b/programs/include/Api/Syscall.hpp index 6817ed3..da72ea1 100644 --- a/programs/include/Api/Syscall.hpp +++ b/programs/include/Api/Syscall.hpp @@ -156,6 +156,7 @@ namespace Montauk { static constexpr uint64_t SYS_CLIPBOARD_GET_INFO = 120; static constexpr uint64_t SYS_CLIPBOARD_GET_TEXT = 121; static constexpr uint64_t SYS_CLIPBOARD_CLEAR = 122; + static constexpr uint64_t SYS_INPUT_WAIT = 123; static constexpr uint32_t CLIPBOARD_MAX_TEXT_BYTES = 256 * 1024; diff --git a/programs/include/montauk/syscall.h b/programs/include/montauk/syscall.h index 09d953e..7720276 100644 --- a/programs/include/montauk/syscall.h +++ b/programs/include/montauk/syscall.h @@ -177,6 +177,9 @@ namespace montauk { inline bool is_key_available() { return (bool)syscall0(Montauk::SYS_ISKEYAVAILABLE); } inline void getkey(Montauk::KeyEvent* out) { syscall1(Montauk::SYS_GETKEY, (uint64_t)out); } inline char getchar() { return (char)syscall0(Montauk::SYS_GETCHAR); } + inline uint64_t input_wait(uint64_t observedSerial, uint64_t timeoutMs) { + return (uint64_t)syscall2(Montauk::SYS_INPUT_WAIT, observedSerial, timeoutMs); + } // Networking inline int32_t ping(uint32_t ip, uint32_t timeoutMs = 3000) { diff --git a/programs/src/desktop/main.cpp b/programs/src/desktop/main.cpp index 41802bd..ae555a3 100644 --- a/programs/src/desktop/main.cpp +++ b/programs/src/desktop/main.cpp @@ -551,6 +551,7 @@ bool desktop_poll_external_windows(DesktopState* ds) { void gui::desktop_run(DesktopState* ds) { uint64_t lastClockToken = 0; uint64_t lastLauncherBlinkToken = ~0ull; + uint64_t inputSerial = montauk::input_wait(0, 0); bool firstFrame = true; for (;;) { @@ -628,11 +629,10 @@ void gui::desktop_run(DesktopState* ds) { firstFrame = false; } - if (desktop_has_active_interaction(ds)) { - montauk::sleep_ms(1); - } else { - montauk::sleep_ms(sceneChanged ? 4 : 16); - } + uint64_t waitMs = (desktop_has_active_interaction(ds) || mouseChanged || keyboardChanged) + ? 1 + : (sceneChanged ? 4 : 16); + inputSerial = montauk::input_wait(inputSerial, waitMs); } }