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;