feat: further kernel power and power optimizations

This commit is contained in:
2026-04-21 20:51:12 +02:00
parent d1fffecff6
commit 0dfb3e8dbb
31 changed files with 895 additions and 52 deletions
+514
View File
@@ -0,0 +1,514 @@
/*
* CpuIdle.cpp
* ACPI-guided CPU idle-state selection
* Copyright (c) 2026 Daniel Hammer
*/
#include "CpuIdle.hpp"
#include <ACPI/FADT.hpp>
#include <ACPI/AML/AmlInterpreter.hpp>
#include <Hal/Cpu.hpp>
#include <Io/IoPort.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
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);
}
};
};
+33
View File
@@ -0,0 +1,33 @@
/*
* CpuIdle.hpp
* ACPI-guided CPU idle-state selection
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <ACPI/ACPI.hpp>
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);
};
};
+3
View File
@@ -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;
+3
View File
@@ -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;
+1 -1
View File
@@ -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;
}
};
+16
View File
@@ -0,0 +1,16 @@
/*
* Input.hpp
* SYS_INPUT_WAIT syscall
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <Drivers/Input/InputEvents.hpp>
namespace Montauk {
static uint64_t Sys_InputWait(uint64_t observedSerial, uint64_t timeoutMs) {
return Drivers::InputEvents::WaitForChange(observedSerial, timeoutMs);
}
}
+4 -1
View File
@@ -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)";
}
}
+3
View File
@@ -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;
+70
View File
@@ -0,0 +1,70 @@
/*
* InputEvents.cpp
* Shared input activity event source
* Copyright (c) 2026 Daniel Hammer
*/
#include "InputEvents.hpp"
#include <atomic>
#include <Sched/Scheduler.hpp>
#include <Timekeeping/ApicTimer.hpp>
namespace Drivers::InputEvents {
static std::atomic<uint64_t> g_serial{1};
static uint8_t g_waitObject;
struct WaitContext {
uint64_t observedSerial;
};
static bool SerialStillObserved(void* rawContext) {
auto* context = static_cast<WaitContext*>(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);
}
}
}
+22
View File
@@ -0,0 +1,22 @@
/*
* InputEvents.hpp
* Shared input activity event source
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
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);
}
+5
View File
@@ -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);
+2
View File
@@ -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
+18 -14
View File
@@ -13,6 +13,7 @@
#include <Terminal/Terminal.hpp>
#include <Hal/Apic/Interrupts.hpp>
#include <Hal/Apic/IoApic.hpp>
#include <Drivers/Input/InputEvents.hpp>
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);
}
};
+3
View File
@@ -13,6 +13,7 @@
#include <Terminal/Terminal.hpp>
#include <Hal/Apic/Interrupts.hpp>
#include <Hal/Apic/IoApic.hpp>
#include <Drivers/Input/InputEvents.hpp>
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();
}
};
+13
View File
@@ -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;
}
}
+4
View File
@@ -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();
};
+2 -2
View File
@@ -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
+1
View File
@@ -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
+17
View File
@@ -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;
}
+1
View File
@@ -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);
+1
View File
@@ -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);
+13
View File
@@ -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));
+2 -8
View File
@@ -5,6 +5,7 @@
*/
#include "SmpBoot.hpp"
#include <ACPI/CpuIdle.hpp>
#include <Hal/Apic/Apic.hpp>
#include <Hal/Apic/Interrupts.hpp>
#include <Hal/IDT.hpp>
@@ -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");
}
Hal::CpuIdle::Wait(10, cpu->hasMwait, &s_idleMonitor);
}
}
+2
View File
@@ -20,6 +20,7 @@
#include <Memory/PageFrameAllocator.hpp>
#include <Memory/Paging.hpp>
#include <ACPI/ACPI.hpp>
#include <ACPI/CpuIdle.hpp>
#include <ACPI/AcpiShutdown.hpp>
#include <ACPI/AcpiEvents.hpp>
#include <Hal/Apic/ApicInit.hpp>
@@ -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());
+84
View File
@@ -14,6 +14,7 @@
#include <CppLib/Stream.hpp>
#include <CppLib/Spinlock.hpp>
#include <Hal/Apic/Apic.hpp>
#include <Hal/Apic/Interrupts.hpp>
#include <Hal/GDT.hpp>
#include <Hal/SmpBoot.hpp>
#include <Timekeeping/ApicTimer.hpp>
@@ -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) {
+5
View File
@@ -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();
+36 -18
View File
@@ -6,6 +6,7 @@
#include "ApicTimer.hpp"
#include <atomic>
#include <ACPI/CpuIdle.hpp>
#include <Hal/Apic/Apic.hpp>
#include <Hal/Apic/Interrupts.hpp>
#include <Hal/Cpu.hpp>
@@ -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<uint64_t> 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<uint64_t> 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)
+5
View File
@@ -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);
};
+1
View File
@@ -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;
+3
View File
@@ -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) {
+5 -5
View File
@@ -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);
}
}