feat: further kernel power and power optimizations
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
for (;;) {
|
||||
Hal::CpuIdle::Wait(10, cpu->hasMwait, &s_idleMonitor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user