feat: expanded ACPI support, initial support for S3 sleep

This commit is contained in:
2026-03-15 00:55:19 +01:00
parent 261b536041
commit 64c26f4288
39 changed files with 4403 additions and 59 deletions
File diff suppressed because it is too large Load Diff
+200
View File
@@ -0,0 +1,200 @@
/*
* AmlInterpreter.hpp
* AML bytecode interpreter — parses DSDT/SSDT into the ACPI namespace
* and evaluates methods, fields, and device status
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include "AmlNamespace.hpp"
#include <cstdint>
namespace Hal {
namespace AML {
// ── Extended AML Opcodes ────────────────────────────────────────
// Single-byte opcodes
static constexpr uint8_t ZeroOp = 0x00;
static constexpr uint8_t OneOp = 0x01;
static constexpr uint8_t AliasOp = 0x06;
static constexpr uint8_t NameOp = 0x08;
static constexpr uint8_t BytePrefix = 0x0A;
static constexpr uint8_t WordPrefix = 0x0B;
static constexpr uint8_t DWordPrefix = 0x0C;
static constexpr uint8_t StringPrefix = 0x0D;
static constexpr uint8_t QWordPrefix = 0x0E;
static constexpr uint8_t ScopeOp = 0x10;
static constexpr uint8_t BufferOp = 0x11;
static constexpr uint8_t PackageOp = 0x12;
static constexpr uint8_t VarPackageOp = 0x13;
static constexpr uint8_t MethodOp = 0x14;
static constexpr uint8_t DualNamePrefix = 0x2E;
static constexpr uint8_t MultiNamePrefix = 0x2F;
static constexpr uint8_t LocalPrefix = 0x60; // Local0..Local7 = 0x60..0x67
static constexpr uint8_t ArgPrefix = 0x68; // Arg0..Arg6 = 0x68..0x6E
static constexpr uint8_t StoreOp = 0x70;
static constexpr uint8_t AddOp = 0x72;
static constexpr uint8_t SubtractOp = 0x74;
static constexpr uint8_t MultiplyOp = 0x77;
static constexpr uint8_t ShiftLeftOp = 0x79;
static constexpr uint8_t ShiftRightOp = 0x7A;
static constexpr uint8_t AndOp = 0x7B;
static constexpr uint8_t NandOp = 0x7C;
static constexpr uint8_t OrOp = 0x7D;
static constexpr uint8_t NorOp = 0x7E;
static constexpr uint8_t XorOp = 0x7F;
static constexpr uint8_t NotOp = 0x80;
static constexpr uint8_t DerefOfOp = 0x83;
static constexpr uint8_t SizeOfOp = 0x87;
static constexpr uint8_t IndexOp = 0x88;
static constexpr uint8_t CreateDWordFieldOp = 0x8A;
static constexpr uint8_t CreateWordFieldOp = 0x8B;
static constexpr uint8_t CreateByteFieldOp = 0x8C;
static constexpr uint8_t CreateBitFieldOp = 0x8D;
static constexpr uint8_t OnesOp = 0xFF;
static constexpr uint8_t ReturnOp = 0xA4;
static constexpr uint8_t BreakOp = 0xA5;
static constexpr uint8_t IfOp = 0xA0;
static constexpr uint8_t ElseOp = 0xA1;
static constexpr uint8_t WhileOp = 0xA2;
static constexpr uint8_t NoopOp = 0xA3;
static constexpr uint8_t ConcatOp = 0x73;
static constexpr uint8_t ToIntegerOp = 0x99;
static constexpr uint8_t ToBufferOp = 0x96;
static constexpr uint8_t RevisionOp = 0x30; // not a real AML op, used internally
// ExtOp prefix (0x5B) followed by second byte
static constexpr uint8_t ExtOpPrefix = 0x5B;
static constexpr uint8_t MutexOp = 0x01; // after ExtOpPrefix
static constexpr uint8_t EventOp = 0x02;
static constexpr uint8_t OpRegionOp = 0x80;
static constexpr uint8_t FieldOp = 0x81;
static constexpr uint8_t DeviceOp = 0x82;
static constexpr uint8_t ProcessorOp = 0x83;
static constexpr uint8_t PowerResOp = 0x84;
static constexpr uint8_t ThermalZoneOp = 0x85;
static constexpr uint8_t IndexFieldOp = 0x86;
static constexpr uint8_t BankFieldOp = 0x87;
static constexpr uint8_t AcquireOp = 0x23;
static constexpr uint8_t ReleaseOp = 0x27;
static constexpr uint8_t SleepOp = 0x22;
static constexpr uint8_t StallOp = 0x21;
static constexpr uint8_t LNotOp = 0x92; // single-byte, actually
static constexpr uint8_t LEqualOp = 0x93; // single-byte
static constexpr uint8_t LGreaterOp = 0x94; // single-byte
static constexpr uint8_t LLessOp = 0x95; // single-byte
static constexpr uint8_t LAndOp = 0x90; // single-byte
static constexpr uint8_t LOrOp = 0x91; // single-byte
static constexpr uint8_t IncrementOp = 0x75;
static constexpr uint8_t DecrementOp = 0x76;
static constexpr uint8_t DivideOp = 0x78;
static constexpr uint8_t ModOp = 0x85;
static constexpr uint8_t ConcatResOp = 0x84;
static constexpr uint8_t ToHexStringOp = 0x98;
static constexpr uint8_t ToDecimalStringOp = 0x97;
// ── Interpreter Configuration ───────────────────────────────────
static constexpr int MaxCallDepth = 16;
static constexpr int MaxLoopIterations = 1024;
// ── Interpreter ─────────────────────────────────────────────────
class Interpreter {
public:
Interpreter();
// Parse a DSDT or SSDT table into the namespace.
// tableData points to the CommonSDTHeader (HHDM-mapped).
bool LoadTable(void* tableData);
// Evaluate a named object, returning its value.
// For methods, executes them with no arguments.
bool EvaluateObject(const char* path, Object& result);
// Evaluate a method with arguments.
bool EvaluateMethod(const char* path, const Object* args, int argCount, Object& result);
// Read a field value. Returns integer.
bool ReadField(int32_t nodeIndex, uint64_t& value);
// Write a field value.
bool WriteField(int32_t nodeIndex, uint64_t value);
// Get the namespace for direct queries.
Namespace& GetNamespace() { return m_ns; }
const Namespace& GetNamespace() const { return m_ns; }
// Check if the interpreter has been initialized
bool IsInitialized() const { return m_initialized; }
private:
// ── Parsing (table load) ────────────────────────────────────
bool ParseBlock(const uint8_t* aml, uint32_t offset, uint32_t endOffset,
int32_t scopeNode);
bool ParseNamedObject(const uint8_t* aml, uint32_t* pos, uint32_t endOffset,
int32_t scopeNode);
bool ParseExtendedOp(const uint8_t* aml, uint32_t* pos, uint32_t endOffset,
int32_t scopeNode);
// ── Name resolution ─────────────────────────────────────────
// Read a NameString from AML and produce an absolute path.
// Advances *pos past the name.
int ReadNameString(const uint8_t* aml, uint32_t* pos, int32_t scopeNode,
char* outPath, int maxLen);
// Read a single 4-char NameSeg from AML. Advances *pos.
void ReadNameSeg(const uint8_t* aml, uint32_t* pos, char* outSeg);
// ── Value decoding ──────────────────────────────────────────
uint32_t DecodePkgLength(const uint8_t* aml, uint32_t* pos);
uint64_t DecodeInteger(const uint8_t* aml, uint32_t* pos);
// ── Method execution ────────────────────────────────────────
struct ExecContext {
const uint8_t* Aml;
uint32_t AmlBase; // start of the block within the table
uint32_t AmlLength;
int32_t ScopeNode;
Object Locals[MaxMethodLocals];
Object Args[MaxMethodArgs];
Object ReturnValue;
bool Returned;
bool Broken;
int Depth;
};
bool ExecuteBlock(ExecContext& ctx, uint32_t offset, uint32_t endOffset);
bool ExecuteOpcode(ExecContext& ctx, uint32_t* pos, uint32_t endOffset);
// Evaluate a term (expression that produces a value) within an execution context.
bool EvalTerm(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
// Evaluate a "SuperName" target for Store operations.
// Returns the node index for named targets, or handles locals/args.
// If isLocal/isArg is set, localIdx/argIdx contains the index.
bool EvalTarget(ExecContext& ctx, uint32_t* pos,
int32_t& nodeIndex, bool& isLocal, int& localIdx,
bool& isArg, int& argIdx);
// Store a value to a target (node, local, or arg).
void StoreToTarget(ExecContext& ctx, const Object& value,
int32_t nodeIndex, bool isLocal, int localIdx,
bool isArg, int argIdx);
// ── Field I/O ───────────────────────────────────────────────
bool ReadRegion(RegionSpace space, uint64_t address, uint32_t bitWidth, uint64_t& value);
bool WriteRegion(RegionSpace space, uint64_t address, uint32_t bitWidth, uint64_t value);
// ── State ───────────────────────────────────────────────────
Namespace m_ns;
const uint8_t* m_dsdt;
uint32_t m_dsdtLength;
bool m_initialized;
};
// ── Global interpreter instance ─────────────────────────────────
Interpreter& GetInterpreter();
};
};
+204
View File
@@ -0,0 +1,204 @@
/*
* AmlNamespace.cpp
* ACPI namespace tree implementation
* Copyright (c) 2026 Daniel Hammer
*/
#include "AmlNamespace.hpp"
#include <Libraries/Memory.hpp>
namespace Hal {
namespace AML {
Namespace::Namespace() : m_nodeCount(0) {
for (int i = 0; i < MaxNamespaceNodes; i++)
m_nodes[i].Clear();
// Create root node "\"
int32_t root = AllocNode();
m_nodes[root].Name[0] = '\\';
m_nodes[root].Name[1] = '\0';
m_nodes[root].ParentIndex = -1;
}
int32_t Namespace::AllocNode() {
if (m_nodeCount >= MaxNamespaceNodes)
return -1;
int32_t idx = m_nodeCount++;
m_nodes[idx].Clear();
return idx;
}
bool Namespace::SegmentEqual(const char* a, const char* b) {
for (int i = 0; i < MaxNameSegLen; i++) {
char ca = a[i] ? a[i] : '_';
char cb = b[i] ? b[i] : '_';
if (ca != cb) return false;
}
return true;
}
void Namespace::PadSegment(const char* src, char* dst) {
int i = 0;
while (i < MaxNameSegLen && src[i] != '\0') {
dst[i] = src[i];
i++;
}
while (i < MaxNameSegLen) {
dst[i] = '_';
i++;
}
dst[MaxNameSegLen] = '\0';
}
int Namespace::ParsePath(const char* path, char segments[][MaxNameSegLen + 1], int maxSegments) {
if (!path || !*path) return 0;
const char* p = path;
// Skip leading backslash (root prefix)
if (*p == '\\') p++;
int count = 0;
while (*p && count < maxSegments) {
// Skip dots (parent prefix / dual/multi name prefix separator)
if (*p == '.') { p++; continue; }
// Skip caret (parent prefix) — we don't handle relative paths here
if (*p == '^') { p++; continue; }
// Read up to 4 characters for a name segment
int i = 0;
while (i < MaxNameSegLen && *p && *p != '.' && *p != '\\') {
segments[count][i] = *p;
i++;
p++;
}
// Pad with underscores
while (i < MaxNameSegLen) {
segments[count][i] = '_';
i++;
}
segments[count][MaxNameSegLen] = '\0';
count++;
}
return count;
}
int32_t Namespace::FindChildByName(int32_t parentIndex, const char* seg) const {
auto* parent = GetNode(parentIndex);
if (!parent) return -1;
for (int32_t i = 0; i < parent->ChildCount; i++) {
int32_t ci = parent->ChildIndices[i];
if (ci < 0 || ci >= m_nodeCount) continue;
if (SegmentEqual(m_nodes[ci].Name, seg))
return ci;
}
return -1;
}
int32_t Namespace::CreateNode(const char* absolutePath) {
char segments[MaxPathDepth][MaxNameSegLen + 1];
int segCount = ParsePath(absolutePath, segments, MaxPathDepth);
int32_t current = 0; // root
for (int i = 0; i < segCount; i++) {
int32_t child = FindChildByName(current, segments[i]);
if (child < 0) {
// Create the node
child = AllocNode();
if (child < 0) return -1;
memcpy(m_nodes[child].Name, segments[i], MaxNameSegLen + 1);
m_nodes[child].ParentIndex = current;
// Add to parent's children
auto* parent = &m_nodes[current];
if (parent->ChildCount < MaxChildren) {
parent->ChildIndices[parent->ChildCount++] = child;
} else {
return -1; // too many children
}
}
current = child;
}
return current;
}
int32_t Namespace::FindNode(const char* absolutePath) const {
char segments[MaxPathDepth][MaxNameSegLen + 1];
int segCount = ParsePath(absolutePath, segments, MaxPathDepth);
int32_t current = 0; // root
for (int i = 0; i < segCount; i++) {
current = FindChildByName(current, segments[i]);
if (current < 0) return -1;
}
return current;
}
int32_t Namespace::ResolveName(const char* name, int32_t scopeNodeIndex) const {
// If it starts with '\', it's absolute
if (name[0] == '\\')
return FindNode(name);
// Try to find relative to the current scope, walking up
char padded[MaxNameSegLen + 1];
PadSegment(name, padded);
int32_t scope = scopeNodeIndex;
while (scope >= 0) {
int32_t found = FindChildByName(scope, padded);
if (found >= 0) return found;
scope = m_nodes[scope].ParentIndex;
}
return -1;
}
NamespaceNode* Namespace::GetNode(int32_t index) {
if (index < 0 || index >= m_nodeCount) return nullptr;
return &m_nodes[index];
}
const NamespaceNode* Namespace::GetNode(int32_t index) const {
if (index < 0 || index >= m_nodeCount) return nullptr;
return &m_nodes[index];
}
char* Namespace::GetNodePath(int32_t index, char* outBuf, int maxLen) const {
if (!outBuf || maxLen < 2) return outBuf;
// Build path by walking up to root
// Collect segments in reverse
char segments[MaxPathDepth][MaxNameSegLen + 1];
int segCount = 0;
int32_t cur = index;
while (cur > 0 && segCount < MaxPathDepth) { // stop at root (index 0)
auto* node = GetNode(cur);
if (!node) break;
memcpy(segments[segCount], node->Name, MaxNameSegLen + 1);
segCount++;
cur = node->ParentIndex;
}
// Write root prefix
int pos = 0;
outBuf[pos++] = '\\';
// Write segments in reverse order
for (int i = segCount - 1; i >= 0 && pos < maxLen - 1; i--) {
if (i < segCount - 1 && pos < maxLen - 1)
outBuf[pos++] = '.';
for (int j = 0; j < MaxNameSegLen && pos < maxLen - 1; j++)
outBuf[pos++] = segments[i][j];
}
outBuf[pos] = '\0';
return outBuf;
}
};
};
+199
View File
@@ -0,0 +1,199 @@
/*
* AmlNamespace.hpp
* AML object types and ACPI namespace tree
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <Libraries/Memory.hpp>
namespace Hal {
namespace AML {
// ── AML Object Types ────────────────────────────────────────────
enum class ObjectType : uint8_t {
None = 0,
Integer,
String,
Buffer,
Package,
Device,
Method,
OperationRegion,
Field,
Mutex,
Processor,
ThermalZone,
PowerResource,
BufferField,
};
// ── Region address spaces (OperationRegion) ─────────────────────
enum class RegionSpace : uint8_t {
SystemMemory = 0x00,
SystemIO = 0x01,
PciConfig = 0x02,
EmbeddedControl = 0x03,
SMBus = 0x04,
CMOS = 0x05,
PciBarTarget = 0x06,
};
// ── Constants ───────────────────────────────────────────────────
static constexpr int MaxNameSegLen = 4;
static constexpr int MaxPathDepth = 16;
static constexpr int MaxChildren = 32;
static constexpr int MaxStringLen = 64;
static constexpr int MaxBufferLen = 256;
static constexpr int MaxPackageElements = 16;
static constexpr int MaxMethodArgs = 7;
static constexpr int MaxMethodLocals = 8;
static constexpr int MaxNamespaceNodes = 256;
// ── AML Object ──────────────────────────────────────────────────
// Tagged union representing any AML value. Kept small for kernel use.
struct Object {
ObjectType Type = ObjectType::None;
union {
uint64_t Integer;
struct {
char Data[MaxStringLen];
uint16_t Length;
} String;
struct {
uint8_t Data[MaxBufferLen];
uint32_t Length;
} Buffer;
struct {
uint8_t ArgCount; // bits 0-2 of method flags
bool Serialized; // bit 3
uint32_t AmlOffset; // offset into DSDT AML where the method body starts
uint32_t AmlLength; // length of the method body
} Method;
struct {
RegionSpace Space;
uint64_t Offset;
uint64_t Length;
} Region;
struct {
uint32_t RegionNodeIndex; // index of the parent OperationRegion node
uint32_t BitOffset;
uint32_t BitLength;
uint8_t AccessType; // 0=Any, 1=Byte, 2=Word, 3=DWord, 4=QWord, 5=Buffer
} Field;
struct {
uint8_t ProcId;
uint32_t PblkAddr;
uint8_t PblkLen;
} Processor;
};
Object() : Type(ObjectType::None), Integer(0) {}
};
// ── Namespace Node ──────────────────────────────────────────────
// Each node has a 4-char name segment and an associated object.
struct NamespaceNode {
char Name[MaxNameSegLen + 1]; // null-terminated 4-char segment
Object Obj;
int32_t ParentIndex; // -1 for root
int32_t ChildIndices[MaxChildren];
int32_t ChildCount;
void Clear() {
Name[0] = 0;
Obj = Object{};
ParentIndex = -1;
ChildCount = 0;
for (int i = 0; i < MaxChildren; i++)
ChildIndices[i] = -1;
}
};
// ── Namespace ───────────────────────────────────────────────────
// Flat array of nodes forming a tree via parent/child indices.
class Namespace {
public:
Namespace();
// Create or find a node at the given absolute path (e.g. "\\_SB_.PCI0").
// Returns the node index, or -1 on failure.
int32_t CreateNode(const char* absolutePath);
// Find a node by absolute path. Returns index or -1.
int32_t FindNode(const char* absolutePath) const;
// Find a node relative to a scope. Tries:
// 1. scopePath + name
// 2. Walk up parent scopes
// 3. Root scope
int32_t ResolveName(const char* name, int32_t scopeNodeIndex) const;
// Get a node by index.
NamespaceNode* GetNode(int32_t index);
const NamespaceNode* GetNode(int32_t index) const;
// Get the root node index (always 0).
int32_t RootIndex() const { return 0; }
// Build the absolute path of a node into outBuf. Returns outBuf.
char* GetNodePath(int32_t index, char* outBuf, int maxLen) const;
// Get the number of nodes in the namespace.
int32_t NodeCount() const { return m_nodeCount; }
// Iterate children of a node matching a given object type.
// callback returns true to continue, false to stop.
// Returns the index of the node that stopped iteration, or -1.
template<typename Fn>
int32_t ForEachChild(int32_t parentIndex, ObjectType type, Fn callback) const {
auto* parent = GetNode(parentIndex);
if (!parent) return -1;
for (int32_t i = 0; i < parent->ChildCount; i++) {
int32_t ci = parent->ChildIndices[i];
auto* child = GetNode(ci);
if (!child) continue;
if (type != ObjectType::None && child->Obj.Type != type) continue;
if (!callback(ci, child)) return ci;
}
return -1;
}
// Recursively find all descendants of a given type.
template<typename Fn>
void WalkDescendants(int32_t nodeIndex, ObjectType type, Fn callback) const {
auto* node = GetNode(nodeIndex);
if (!node) return;
for (int32_t i = 0; i < node->ChildCount; i++) {
int32_t ci = node->ChildIndices[i];
auto* child = GetNode(ci);
if (!child) continue;
if (type == ObjectType::None || child->Obj.Type == type)
callback(ci, child);
WalkDescendants(ci, type, callback);
}
}
private:
int32_t AllocNode();
int32_t FindChildByName(int32_t parentIndex, const char* seg) const;
// Parse an absolute path into segments. Returns number of segments.
static int ParsePath(const char* path, char segments[][MaxNameSegLen + 1], int maxSegments);
static bool SegmentEqual(const char* a, const char* b);
static void PadSegment(const char* src, char* dst); // pad to 4 chars with '_'
NamespaceNode m_nodes[MaxNamespaceNodes];
int32_t m_nodeCount;
};
};
};
+103 -34
View File
@@ -1,10 +1,11 @@
/*
* AmlParser.cpp
* Primitive AML bytecode parser for extracting ACPI sleep state values
* AML bytecode parser — S5 extraction (brute-force) and interpreter init
* Copyright (c) 2026 Daniel Hammer
*/
#include "AmlParser.hpp"
#include "AmlInterpreter.hpp"
#include <ACPI/ACPI.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
@@ -14,19 +15,28 @@ using namespace Kt;
namespace Hal {
namespace AML {
// Decode a PkgLength field and return its value.
// Advances *pos past the PkgLength bytes.
// ── Legacy S5 extraction (brute-force scan) ─────────────────────
// Kept for fast S5 extraction during early boot before the full
// interpreter is loaded.
static constexpr uint8_t NameOp_ = 0x08;
static constexpr uint8_t PackageOp_ = 0x12;
static constexpr uint8_t ZeroOp_ = 0x00;
static constexpr uint8_t OneOp_ = 0x01;
static constexpr uint8_t OnesOp_ = 0xFF;
static constexpr uint8_t BytePrefix_ = 0x0A;
static constexpr uint8_t WordPrefix_ = 0x0B;
static constexpr uint8_t DWordPrefix_= 0x0C;
static uint32_t DecodePkgLength(const uint8_t* aml, uint32_t* pos) {
uint8_t lead = aml[*pos];
uint32_t byteCount = (lead >> 6) & 0x03;
if (byteCount == 0) {
// Single byte encoding: bits 0-5 are the length
(*pos)++;
return lead & 0x3F;
}
// Multi-byte: lead bits 0-3 are low nibble, followed by byteCount bytes
uint32_t length = lead & 0x0F;
(*pos)++;
@@ -38,35 +48,32 @@ namespace Hal {
return length;
}
// Decode an AML integer at position *pos.
// Handles ZeroOp, OneOp, OnesOp, BytePrefix, WordPrefix, DWordPrefix.
// Returns the decoded value and advances *pos.
static uint32_t DecodeInteger(const uint8_t* aml, uint32_t* pos) {
static uint32_t DecodeIntegerLegacy(const uint8_t* aml, uint32_t* pos) {
uint8_t op = aml[*pos];
switch (op) {
case ZeroOp:
case ZeroOp_:
(*pos)++;
return 0;
case OneOp:
case OneOp_:
(*pos)++;
return 1;
case OnesOp:
case OnesOp_:
(*pos)++;
return 0xFFFFFFFF;
case BytePrefix: {
case BytePrefix_: {
(*pos)++;
uint8_t val = aml[*pos];
(*pos)++;
return val;
}
case WordPrefix: {
case WordPrefix_: {
(*pos)++;
uint16_t val = aml[*pos] | ((uint16_t)aml[*pos + 1] << 8);
*pos += 2;
return val;
}
case DWordPrefix: {
case DWordPrefix_: {
(*pos)++;
uint32_t val = aml[*pos]
| ((uint32_t)aml[*pos + 1] << 8)
@@ -76,7 +83,6 @@ namespace Hal {
return val;
}
default:
// Unknown encoding — treat as zero and skip
(*pos)++;
return 0;
}
@@ -93,23 +99,17 @@ namespace Hal {
return result;
}
// The AML bytecode starts right after the CommonSDTHeader
const uint8_t* aml = (const uint8_t*)dsdtData;
uint32_t amlLength = header->Length;
uint32_t dataStart = sizeof(ACPI::CommonSDTHeader);
// Scan for the \_S5_ name in the AML stream.
// We look for the 4-byte sequence '_S5_' preceded by a NameOp (0x08)
// or preceded by a scope path like '\' (0x5C).
for (uint32_t i = dataStart; i + 4 < amlLength; i++) {
if (aml[i] == '_' && aml[i+1] == 'S' && aml[i+2] == '5' && aml[i+3] == '_') {
// Verify a valid AML context: either NameOp before it,
// or '\' + NameOp pattern, or just the name in a scope
bool validContext = false;
if (i >= 1 && aml[i-1] == NameOp) {
if (i >= 1 && aml[i-1] == NameOp_) {
validContext = true;
} else if (i >= 2 && aml[i-2] == NameOp && aml[i-1] == '\\') {
} else if (i >= 2 && aml[i-2] == NameOp_ && aml[i-1] == '\\') {
validContext = true;
}
@@ -118,21 +118,16 @@ namespace Hal {
KernelLogStream(OK, "AML") << "Found \\_S5_ object at offset " << base::hex << (uint64_t)i;
// Move past the name
uint32_t pos = i + 4;
// Expect PackageOp
if (pos >= amlLength || aml[pos] != PackageOp) {
if (pos >= amlLength || aml[pos] != PackageOp_) {
KernelLogStream(ERROR, "AML") << "Expected PackageOp after \\_S5_, got " << base::hex << (uint64_t)aml[pos];
continue;
}
pos++;
// Decode package length (we don't actually need the value,
// but must advance past it)
DecodePkgLength(aml, &pos);
// Number of elements in the package
if (pos >= amlLength) continue;
uint8_t numElements = aml[pos];
pos++;
@@ -142,12 +137,10 @@ namespace Hal {
continue;
}
// First element: SLP_TYPa
result.SLP_TYPa = (uint16_t)DecodeInteger(aml, &pos);
result.SLP_TYPa = (uint16_t)DecodeIntegerLegacy(aml, &pos);
// Second element: SLP_TYPb (if present)
if (numElements >= 2 && pos < amlLength) {
result.SLP_TYPb = (uint16_t)DecodeInteger(aml, &pos);
result.SLP_TYPb = (uint16_t)DecodeIntegerLegacy(aml, &pos);
} else {
result.SLP_TYPb = 0;
}
@@ -165,5 +158,81 @@ namespace Hal {
return result;
}
// ── Generalized brute-force sleep state scanner ──────────────────
SleepObject FindSleepState(void* dsdtData, int state) {
SleepObject result{};
result.Valid = false;
if (state < 0 || state > 5) return result;
auto* header = (ACPI::CommonSDTHeader*)dsdtData;
if (!ACPI::TestChecksum(header)) {
KernelLogStream(ERROR, "AML") << "DSDT checksum failed";
return result;
}
// Build the 4-char name we're looking for: _S0_ through _S5_
char target[4] = { '_', 'S', (char)('0' + state), '_' };
const uint8_t* aml = (const uint8_t*)dsdtData;
uint32_t amlLength = header->Length;
uint32_t dataStart = sizeof(ACPI::CommonSDTHeader);
for (uint32_t i = dataStart; i + 4 < amlLength; i++) {
if (aml[i] == target[0] && aml[i+1] == target[1] &&
aml[i+2] == target[2] && aml[i+3] == target[3]) {
bool validContext = false;
if (i >= 1 && aml[i-1] == NameOp_)
validContext = true;
else if (i >= 2 && aml[i-2] == NameOp_ && aml[i-1] == '\\')
validContext = true;
if (!validContext) continue;
uint32_t pos = i + 4;
if (pos >= amlLength || aml[pos] != PackageOp_) continue;
pos++;
DecodePkgLength(aml, &pos);
if (pos >= amlLength) continue;
uint8_t numElements = aml[pos];
pos++;
if (numElements < 1) continue;
result.SLP_TYPa = (uint16_t)DecodeIntegerLegacy(aml, &pos);
if (numElements >= 2 && pos < amlLength)
result.SLP_TYPb = (uint16_t)DecodeIntegerLegacy(aml, &pos);
else
result.SLP_TYPb = 0;
result.Valid = true;
KernelLogStream(OK, "AML") << "\\_S" << base::dec << (uint64_t)state
<< "_ found: SLP_TYPa=" << base::hex << (uint64_t)result.SLP_TYPa
<< " SLP_TYPb=" << base::hex << (uint64_t)result.SLP_TYPb;
return result;
}
}
KernelLogStream(INFO, "AML") << "\\_S" << base::dec << (uint64_t)state
<< "_ not found in DSDT";
return result;
}
// ── Full interpreter initialization ─────────────────────────────
void InitializeInterpreter(void* dsdtData) {
auto& interp = GetInterpreter();
if (!interp.LoadTable(dsdtData)) {
KernelLogStream(ERROR, "AML") << "Failed to load DSDT into AML interpreter";
}
}
};
};
+16 -14
View File
@@ -1,6 +1,6 @@
/*
* AmlParser.hpp
* Primitive AML bytecode parser for extracting ACPI sleep state values
* AML bytecode parser — S5 extraction and interpreter initialization
* Copyright (c) 2026 Daniel Hammer
*/
@@ -10,26 +10,28 @@
namespace Hal {
namespace AML {
// AML opcodes used during \_S5_ parsing
static constexpr uint8_t NameOp = 0x08;
static constexpr uint8_t PackageOp = 0x12;
static constexpr uint8_t ZeroOp = 0x00;
static constexpr uint8_t OneOp = 0x01;
static constexpr uint8_t OnesOp = 0xFF;
static constexpr uint8_t BytePrefix = 0x0A;
static constexpr uint8_t WordPrefix = 0x0B;
static constexpr uint8_t DWordPrefix = 0x0C;
struct S5Object {
struct SleepObject {
uint16_t SLP_TYPa;
uint16_t SLP_TYPb;
bool Valid;
};
// Legacy compat alias
using S5Object = SleepObject;
// Parse a DSDT (or SSDT) AML block to find the \_S5_ object.
// dsdtData points to the CommonSDTHeader of the DSDT (HHDM-mapped).
// Returns the parsed S5 values on success.
S5Object FindS5(void* dsdtData);
// Parse a DSDT to find any \_Sx_ object (x = 0-5) via brute-force scan.
// Works on any DSDT regardless of complexity — does not require the
// interpreter or namespace.
SleepObject FindSleepState(void* dsdtData, int state);
// Initialize the AML interpreter with the DSDT.
// This loads the full table into the namespace and enables
// method evaluation, device enumeration, and field access.
// Should be called during boot after ACPI table discovery.
void InitializeInterpreter(void* dsdtData);
};
};
+222
View File
@@ -0,0 +1,222 @@
/*
* AmlResource.cpp
* ACPI resource descriptor parsing
* Copyright (c) 2026 Daniel Hammer
*/
#include "AmlResource.hpp"
namespace Hal {
namespace AML {
// ── Small Resource Tags (bits 6:3 of the tag byte) ──────────────
static constexpr uint8_t SmallIrqTag = 0x04; // IRQ descriptor
static constexpr uint8_t SmallDmaTag = 0x05; // DMA descriptor
static constexpr uint8_t SmallIoPortTag = 0x08; // I/O port descriptor
static constexpr uint8_t SmallFixedIoTag = 0x09; // Fixed I/O port descriptor
static constexpr uint8_t SmallEndTag = 0x0F; // End tag
// ── Large Resource Tags (byte following the large tag prefix) ───
static constexpr uint8_t LargeMemory24Tag = 0x01;
static constexpr uint8_t LargeVendorTag = 0x04;
static constexpr uint8_t LargeMemory32Tag = 0x05;
static constexpr uint8_t LargeMem32FixedTag = 0x06;
static constexpr uint8_t LargeDWordAddrTag = 0x07;
static constexpr uint8_t LargeWordAddrTag = 0x08;
static constexpr uint8_t LargeExtIrqTag = 0x09;
static constexpr uint8_t LargeQWordAddrTag = 0x0A;
static constexpr uint8_t LargeGpioTag = 0x0C;
static uint16_t Read16(const uint8_t* p) {
return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
}
static uint32_t Read32(const uint8_t* p) {
return (uint32_t)p[0] | ((uint32_t)p[1] << 8)
| ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
static uint64_t Read64(const uint8_t* p) {
uint64_t val = 0;
for (int i = 0; i < 8; i++)
val |= (uint64_t)p[i] << (i * 8);
return val;
}
// Find the lowest set bit in a mask. Returns the bit number, or -1.
static int FirstSetBit(uint16_t mask) {
for (int i = 0; i < 16; i++) {
if (mask & (1 << i)) return i;
}
return -1;
}
bool ParseResourceTemplate(const uint8_t* data, uint32_t length, ResourceList& result) {
result.Count = 0;
uint32_t pos = 0;
while (pos < length && result.Count < MaxResources) {
uint8_t tag = data[pos];
// End tag
if ((tag & 0x80) == 0 && ((tag >> 3) & 0x0F) == SmallEndTag)
break;
if (tag & 0x80) {
// ── Large resource descriptor ───────────────────────
uint8_t largeType = tag & 0x7F;
if (pos + 3 > length) break;
uint16_t resLen = Read16(&data[pos + 1]);
uint32_t dataStart = pos + 3;
uint32_t dataEnd = dataStart + resLen;
if (dataEnd > length) break;
auto& res = result.Resources[result.Count];
switch (largeType) {
case LargeExtIrqTag: {
if (resLen < 2) break;
res.Type = ResourceType::ExtendedIrq;
uint8_t flags = data[dataStart];
res.ExtendedIrq.Flags = flags;
res.ExtendedIrq.Shareable = (flags >> 3) & 1;
uint8_t irqCount = data[dataStart + 1];
if (irqCount > 0 && resLen >= 6) {
res.ExtendedIrq.Interrupt = Read32(&data[dataStart + 2]);
} else {
res.ExtendedIrq.Interrupt = 0;
}
result.Count++;
break;
}
case LargeMemory32Tag: {
if (resLen < 17) break;
res.Type = ResourceType::Memory32;
res.Memory32.ReadWrite = data[dataStart] & 1;
res.Memory32.Base = Read32(&data[dataStart + 1]);
// Max = data[dataStart + 5..8]
// Alignment = data[dataStart + 9..12]
res.Memory32.Length = Read32(&data[dataStart + 13]);
result.Count++;
break;
}
case LargeMem32FixedTag: {
if (resLen < 9) break;
res.Type = ResourceType::Memory32;
res.Memory32.ReadWrite = data[dataStart] & 1;
res.Memory32.Base = Read32(&data[dataStart + 1]);
res.Memory32.Length = Read32(&data[dataStart + 5]);
result.Count++;
break;
}
case LargeDWordAddrTag: {
if (resLen < 23) break;
res.Type = ResourceType::DWordAddress;
// ResourceType at dataStart+0, GenFlags at +1, TypeFlags at +2
res.AddressSpace.GranularityMin = Read32(&data[dataStart + 3]);
res.AddressSpace.GranularityMax = Read32(&data[dataStart + 7]);
// Min at +7, Max at +11, Translation at +15, Length at +19
res.AddressSpace.Base = Read32(&data[dataStart + 7]);
res.AddressSpace.Length = Read32(&data[dataStart + 19]);
result.Count++;
break;
}
case LargeQWordAddrTag: {
if (resLen < 43) break;
res.Type = ResourceType::QWordAddress;
res.AddressSpace.GranularityMin = Read64(&data[dataStart + 3]);
res.AddressSpace.GranularityMax = Read64(&data[dataStart + 11]);
res.AddressSpace.Base = Read64(&data[dataStart + 11]);
res.AddressSpace.Length = Read64(&data[dataStart + 35]);
result.Count++;
break;
}
case LargeWordAddrTag: {
if (resLen < 13) break;
res.Type = ResourceType::WordAddress;
res.AddressSpace.GranularityMin = Read16(&data[dataStart + 3]);
res.AddressSpace.GranularityMax = Read16(&data[dataStart + 5]);
res.AddressSpace.Base = Read16(&data[dataStart + 5]);
res.AddressSpace.Length = Read16(&data[dataStart + 11]);
result.Count++;
break;
}
default:
// Unknown large descriptor — skip
break;
}
pos = dataEnd;
} else {
// ── Small resource descriptor ───────────────────────
uint8_t smallType = (tag >> 3) & 0x0F;
uint8_t resLen = tag & 0x07;
uint32_t dataStart = pos + 1;
uint32_t dataEnd = dataStart + resLen;
if (dataEnd > length) break;
auto& res = result.Resources[result.Count];
switch (smallType) {
case SmallIrqTag: {
if (resLen < 2) break;
res.Type = ResourceType::Irq;
res.Irq.Mask = Read16(&data[dataStart]);
res.Irq.Flags = (resLen >= 3) ? data[dataStart + 2] : 0;
int irq = FirstSetBit(res.Irq.Mask);
res.Irq.Irq = (irq >= 0) ? (uint8_t)irq : 0;
result.Count++;
break;
}
case SmallDmaTag: {
if (resLen < 2) break;
res.Type = ResourceType::Dma;
res.Dma.Mask = data[dataStart];
res.Dma.Flags = data[dataStart + 1];
int ch = FirstSetBit(res.Dma.Mask);
res.Dma.Channel = (ch >= 0) ? (uint8_t)ch : 0;
result.Count++;
break;
}
case SmallIoPortTag: {
if (resLen < 7) break;
res.Type = ResourceType::IoPort;
res.IoPort.Decode16Bit = data[dataStart] & 1;
res.IoPort.Base = Read16(&data[dataStart + 1]);
// Max = data[dataStart + 3..4]
res.IoPort.Alignment = data[dataStart + 5];
res.IoPort.Length = data[dataStart + 6];
result.Count++;
break;
}
case SmallFixedIoTag: {
if (resLen < 3) break;
res.Type = ResourceType::FixedIoPort;
res.FixedIoPort.Base = Read16(&data[dataStart]);
res.FixedIoPort.Length = data[dataStart + 2];
result.Count++;
break;
}
default:
break;
}
pos = dataEnd;
}
}
return result.Count > 0;
}
};
};
+158
View File
@@ -0,0 +1,158 @@
/*
* AmlResource.hpp
* ACPI resource descriptor parsing (_CRS, _PRS, _SRS buffers)
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
namespace Hal {
namespace AML {
// ── Resource Types ──────────────────────────────────────────────
enum class ResourceType : uint8_t {
None = 0,
Irq,
Dma,
IoPort,
FixedIoPort,
Memory16,
Memory32,
Memory32Fixed,
QWordAddress,
DWordAddress,
WordAddress,
ExtendedIrq,
GpioConnection,
};
// ── Single Resource Descriptor ──────────────────────────────────
struct ResourceDescriptor {
ResourceType Type;
union {
struct {
uint16_t Mask; // bitmask of supported IRQs
uint8_t Flags;
uint8_t Irq; // decoded first IRQ number
} Irq;
struct {
uint32_t Interrupt; // GSI number
uint8_t Flags; // edge/level, active high/low
bool Shareable;
} ExtendedIrq;
struct {
uint8_t Mask; // bitmask of supported DMA channels
uint8_t Flags;
uint8_t Channel; // decoded first channel
} Dma;
struct {
uint16_t Base;
uint16_t Length;
uint8_t Alignment;
bool Decode16Bit; // true = 16-bit decode, false = 10-bit
} IoPort;
struct {
uint16_t Base;
uint8_t Length;
} FixedIoPort;
struct {
uint32_t Base;
uint32_t Length;
bool ReadWrite; // true = R/W, false = read-only
} Memory32;
struct {
uint64_t Base;
uint64_t Length;
uint64_t GranularityMin;
uint64_t GranularityMax;
} AddressSpace;
};
ResourceDescriptor() : Type(ResourceType::None) {
// Zero the largest union member
AddressSpace = {};
}
};
// ── Parsed Resource List ────────────────────────────────────────
static constexpr int MaxResources = 16;
struct ResourceList {
ResourceDescriptor Resources[MaxResources];
int Count;
ResourceList() : Count(0) {}
// Find the first resource of a given type. Returns nullptr if not found.
const ResourceDescriptor* FindFirst(ResourceType type) const {
for (int i = 0; i < Count; i++) {
if (Resources[i].Type == type)
return &Resources[i];
}
return nullptr;
}
// Get the IRQ number from the first IRQ or ExtendedIrq resource.
// Returns -1 if no IRQ found.
int GetIrq() const {
for (int i = 0; i < Count; i++) {
if (Resources[i].Type == ResourceType::Irq)
return Resources[i].Irq.Irq;
if (Resources[i].Type == ResourceType::ExtendedIrq)
return (int)Resources[i].ExtendedIrq.Interrupt;
}
return -1;
}
// Get the first IO port base and length.
// Returns false if no IO port found.
bool GetIoPort(uint16_t& base, uint16_t& length) const {
for (int i = 0; i < Count; i++) {
if (Resources[i].Type == ResourceType::IoPort) {
base = Resources[i].IoPort.Base;
length = Resources[i].IoPort.Length;
return true;
}
if (Resources[i].Type == ResourceType::FixedIoPort) {
base = Resources[i].FixedIoPort.Base;
length = Resources[i].FixedIoPort.Length;
return true;
}
}
return false;
}
// Get the first memory base and length.
bool GetMemory(uint64_t& base, uint64_t& length) const {
for (int i = 0; i < Count; i++) {
if (Resources[i].Type == ResourceType::Memory32) {
base = Resources[i].Memory32.Base;
length = Resources[i].Memory32.Length;
return true;
}
if (Resources[i].Type == ResourceType::QWordAddress ||
Resources[i].Type == ResourceType::DWordAddress ||
Resources[i].Type == ResourceType::WordAddress) {
base = Resources[i].AddressSpace.Base;
length = Resources[i].AddressSpace.Length;
return true;
}
}
return false;
}
};
// Parse a resource template buffer (as returned by _CRS evaluation)
// into a structured ResourceList.
bool ParseResourceTemplate(const uint8_t* data, uint32_t length, ResourceList& result);
};
};
+394
View File
@@ -0,0 +1,394 @@
/*
* AcpiDevices.cpp
* ACPI device enumeration via AML namespace
* Copyright (c) 2026 Daniel Hammer
*/
#include "AcpiDevices.hpp"
#include <ACPI/AML/AmlInterpreter.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Libraries/String.hpp>
#include <Libraries/Memory.hpp>
using namespace Kt;
namespace Hal {
namespace AcpiDevices {
// ── EISAID decoding ─────────────────────────────────────────────
// ACPI encodes PNP IDs as compressed 32-bit EISAIDs.
static void DecodeEisaId(uint32_t id, char* out) {
// EISA ID encoding:
// Bits 31-16: 3 compressed letters (5 bits each, '@' based)
// Bits 15-0: 4 hex digits (product number)
out[0] = (char)(((id >> 26) & 0x1F) + '@');
out[1] = (char)(((id >> 21) & 0x1F) + '@');
out[2] = (char)(((id >> 16) & 0x1F) + '@');
// Product ID as 4 hex digits
static const char hex[] = "0123456789ABCDEF";
out[3] = hex[(id >> 12) & 0xF];
out[4] = hex[(id >> 8) & 0xF];
out[5] = hex[(id >> 4) & 0xF];
out[6] = hex[(id >> 0) & 0xF];
out[7] = '\0';
}
// ── String comparison ───────────────────────────────────────────
static bool StrEqual(const char* a, const char* b) {
while (*a && *b) {
if (*a != *b) return false;
a++; b++;
}
return *a == *b;
}
static void StrCopy(char* dst, const char* src, int maxLen) {
int i = 0;
while (src[i] && i < maxLen - 1) {
dst[i] = src[i];
i++;
}
dst[i] = '\0';
}
// ── DeviceList methods ──────────────────────────────────────────
const DeviceInfo* DeviceList::FindByHid(const char* hid) const {
for (int i = 0; i < Count; i++) {
if (StrEqual(Devices[i].HardwareId, hid))
return &Devices[i];
}
return nullptr;
}
const DeviceInfo* DeviceList::FindByPath(const char* path) const {
for (int i = 0; i < Count; i++) {
if (StrEqual(Devices[i].Path, path))
return &Devices[i];
}
return nullptr;
}
// ── EvaluateSta ─────────────────────────────────────────────────
uint32_t EvaluateSta(int32_t deviceNodeIndex) {
auto& interp = AML::GetInterpreter();
auto& ns = interp.GetNamespace();
// Look for _STA child
int32_t staNode = -1;
ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None,
[&](int32_t idx, const AML::NamespaceNode* node) -> bool {
if (node->Name[0] == '_' && node->Name[1] == 'S' &&
node->Name[2] == 'T' && node->Name[3] == 'A') {
staNode = idx;
return false; // stop
}
return true;
});
if (staNode < 0) return STA_DEFAULT;
auto* staObj = ns.GetNode(staNode);
if (!staObj) return STA_DEFAULT;
if (staObj->Obj.Type == AML::ObjectType::Method) {
AML::Object result{};
char path[256];
ns.GetNodePath(staNode, path, 256);
if (interp.EvaluateObject(path, result))
return (uint32_t)result.Integer;
return STA_DEFAULT;
}
if (staObj->Obj.Type == AML::ObjectType::Integer)
return (uint32_t)staObj->Obj.Integer;
return STA_DEFAULT;
}
// ── EvaluateAdr ─────────────────────────────────────────────────
uint64_t EvaluateAdr(int32_t deviceNodeIndex) {
auto& interp = AML::GetInterpreter();
auto& ns = interp.GetNamespace();
int32_t adrNode = -1;
ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None,
[&](int32_t idx, const AML::NamespaceNode* node) -> bool {
if (node->Name[0] == '_' && node->Name[1] == 'A' &&
node->Name[2] == 'D' && node->Name[3] == 'R') {
adrNode = idx;
return false;
}
return true;
});
if (adrNode < 0) return 0;
auto* adrObj = ns.GetNode(adrNode);
if (!adrObj) return 0;
if (adrObj->Obj.Type == AML::ObjectType::Method) {
AML::Object result{};
char path[256];
ns.GetNodePath(adrNode, path, 256);
if (interp.EvaluateObject(path, result))
return result.Integer;
return 0;
}
if (adrObj->Obj.Type == AML::ObjectType::Integer)
return adrObj->Obj.Integer;
return 0;
}
// ── EvaluateHid ─────────────────────────────────────────────────
bool EvaluateHid(int32_t deviceNodeIndex, char* outHid, int maxLen) {
auto& interp = AML::GetInterpreter();
auto& ns = interp.GetNamespace();
int32_t hidNode = -1;
ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None,
[&](int32_t idx, const AML::NamespaceNode* node) -> bool {
if (node->Name[0] == '_' && node->Name[1] == 'H' &&
node->Name[2] == 'I' && node->Name[3] == 'D') {
hidNode = idx;
return false;
}
return true;
});
if (hidNode < 0) return false;
AML::Object result{};
char path[256];
ns.GetNodePath(hidNode, path, 256);
if (!interp.EvaluateObject(path, result))
return false;
if (result.Type == AML::ObjectType::Integer) {
// EISA ID
DecodeEisaId((uint32_t)result.Integer, outHid);
return true;
}
if (result.Type == AML::ObjectType::String) {
StrCopy(outHid, result.String.Data, maxLen);
return true;
}
return false;
}
// ── EvaluateUid ─────────────────────────────────────────────────
bool EvaluateUid(int32_t deviceNodeIndex, char* outUid, int maxLen) {
auto& interp = AML::GetInterpreter();
auto& ns = interp.GetNamespace();
int32_t uidNode = -1;
ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None,
[&](int32_t idx, const AML::NamespaceNode* node) -> bool {
if (node->Name[0] == '_' && node->Name[1] == 'U' &&
node->Name[2] == 'I' && node->Name[3] == 'D') {
uidNode = idx;
return false;
}
return true;
});
if (uidNode < 0) return false;
AML::Object result{};
char path[256];
ns.GetNodePath(uidNode, path, 256);
if (!interp.EvaluateObject(path, result))
return false;
if (result.Type == AML::ObjectType::Integer) {
// Convert integer to string
uint64_t val = result.Integer;
char buf[21];
int i = 0;
if (val == 0) {
buf[i++] = '0';
} else {
char tmp[21];
int t = 0;
while (val > 0) {
tmp[t++] = '0' + (val % 10);
val /= 10;
}
for (int j = t - 1; j >= 0; j--)
buf[i++] = tmp[j];
}
buf[i] = '\0';
StrCopy(outUid, buf, maxLen);
return true;
}
if (result.Type == AML::ObjectType::String) {
StrCopy(outUid, result.String.Data, maxLen);
return true;
}
return false;
}
// ── EvaluateCrs ─────────────────────────────────────────────────
bool EvaluateCrs(int32_t deviceNodeIndex, AML::ResourceList& result) {
auto& interp = AML::GetInterpreter();
auto& ns = interp.GetNamespace();
int32_t crsNode = -1;
ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None,
[&](int32_t idx, const AML::NamespaceNode* node) -> bool {
if (node->Name[0] == '_' && node->Name[1] == 'C' &&
node->Name[2] == 'R' && node->Name[3] == 'S') {
crsNode = idx;
return false;
}
return true;
});
if (crsNode < 0) return false;
AML::Object crsResult{};
char path[256];
ns.GetNodePath(crsNode, path, 256);
if (!interp.EvaluateObject(path, crsResult))
return false;
if (crsResult.Type != AML::ObjectType::Buffer)
return false;
return AML::ParseResourceTemplate(crsResult.Buffer.Data, crsResult.Buffer.Length, result);
}
// ── EnumerateAll ────────────────────────────────────────────────
void EnumerateAll(DeviceList& result) {
auto& interp = AML::GetInterpreter();
if (!interp.IsInitialized()) return;
auto& ns = interp.GetNamespace();
result.Count = 0;
// Walk all namespace nodes looking for Device objects
ns.WalkDescendants(ns.RootIndex(), AML::ObjectType::Device,
[&](int32_t idx, [[maybe_unused]] const AML::NamespaceNode* node) {
if (result.Count >= MaxDevices) return;
auto& dev = result.Devices[result.Count];
dev.NodeIndex = idx;
// Get the path
ns.GetNodePath(idx, dev.Path, 128);
// Evaluate _STA
dev.Status = EvaluateSta(idx);
dev.IsPresent = (dev.Status & STA_PRESENT) && (dev.Status & STA_FUNCTIONAL);
// Evaluate _HID
if (!EvaluateHid(idx, dev.HardwareId, 16))
dev.HardwareId[0] = '\0';
// Evaluate _UID
if (!EvaluateUid(idx, dev.UniqueId, 16))
dev.UniqueId[0] = '\0';
// Evaluate _ADR
dev.Address = EvaluateAdr(idx);
result.Count++;
});
KernelLogStream(OK, "ACPI") << "Enumerated " << base::dec
<< (uint64_t)result.Count << " ACPI devices";
// Log discovered devices
for (int i = 0; i < result.Count; i++) {
auto& dev = result.Devices[i];
if (dev.HardwareId[0]) {
KernelLogStream(DEBUG, "ACPI") << " " << dev.Path
<< " HID=" << dev.HardwareId
<< (dev.IsPresent ? " [present]" : " [not present]");
} else if (dev.Address != 0) {
KernelLogStream(DEBUG, "ACPI") << " " << dev.Path
<< " ADR=" << base::hex << dev.Address
<< (dev.IsPresent ? " [present]" : " [not present]");
}
}
}
// ── GetSleepState ───────────────────────────────────────────────
SleepState GetSleepState(int state) {
SleepState result{};
result.Valid = false;
if (state < 0 || state > 5) return result;
char path[8] = "\\_Sx_";
path[2] = 'S';
path[3] = '0' + (char)state;
path[4] = '_';
path[5] = '\0';
auto& interp = AML::GetInterpreter();
AML::Object obj{};
if (!interp.EvaluateObject(path, obj))
return result;
// The sleep state object should be a Package with at least 2 integers.
// During loading, we stored Package data as a raw buffer.
// For the common case, the \_S5_ was already extracted by the old parser.
// Try to get it from namespace directly.
auto& ns = interp.GetNamespace();
int32_t node = ns.FindNode(path);
if (node < 0) return result;
auto* nsNode = ns.GetNode(node);
if (!nsNode) return result;
if (nsNode->Obj.Type == AML::ObjectType::Package) {
// Parse the raw package buffer to extract SLP_TYP values
const uint8_t* data = nsNode->Obj.Buffer.Data;
uint32_t len = nsNode->Obj.Buffer.Length;
if (len < 2) return result;
// NumElements
uint8_t numElements = data[0];
uint32_t pos = 1;
if (numElements >= 1 && pos < len) {
// Decode first element
uint8_t elem0 = data[pos];
if (elem0 == AML::ZeroOp) { result.SLP_TYPa = 0; pos++; }
else if (elem0 == AML::OneOp) { result.SLP_TYPa = 1; pos++; }
else if (elem0 == AML::BytePrefix && pos + 1 < len) { result.SLP_TYPa = data[pos + 1]; pos += 2; }
else { pos++; }
}
if (numElements >= 2 && pos < len) {
uint8_t elem1 = data[pos];
if (elem1 == AML::ZeroOp) { result.SLP_TYPb = 0; pos++; }
else if (elem1 == AML::OneOp) { result.SLP_TYPb = 1; pos++; }
else if (elem1 == AML::BytePrefix && pos + 1 < len) { result.SLP_TYPb = data[pos + 1]; pos += 2; }
else { pos++; }
}
result.Valid = true;
} else if (nsNode->Obj.Type == AML::ObjectType::Integer) {
result.SLP_TYPa = (uint16_t)nsNode->Obj.Integer;
result.SLP_TYPb = 0;
result.Valid = true;
}
return result;
}
};
};
+112
View File
@@ -0,0 +1,112 @@
/*
* AcpiDevices.hpp
* ACPI device enumeration — walks namespace to discover and query devices
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <ACPI/AML/AmlResource.hpp>
namespace Hal {
namespace AcpiDevices {
// ── Device Status Flags (_STA) ──────────────────────────────────
static constexpr uint32_t STA_PRESENT = (1 << 0);
static constexpr uint32_t STA_ENABLED = (1 << 1);
static constexpr uint32_t STA_VISIBLE = (1 << 2);
static constexpr uint32_t STA_FUNCTIONAL = (1 << 3);
static constexpr uint32_t STA_BATTERY = (1 << 4); // battery present (for batteries)
// Default _STA when no _STA method exists: present + enabled + visible + functional
static constexpr uint32_t STA_DEFAULT = 0x0F;
// ── Device Info ─────────────────────────────────────────────────
struct DeviceInfo {
char Path[128]; // full namespace path
char HardwareId[16]; // _HID value (e.g. "PNP0A03", "ACPI0001")
char UniqueId[16]; // _UID value
uint64_t Address; // _ADR value (for PCI: (device << 16) | function)
uint32_t Status; // _STA flags
int32_t NodeIndex; // namespace node index
bool IsPresent; // STA_PRESENT && STA_FUNCTIONAL
};
static constexpr int MaxDevices = 64;
struct DeviceList {
DeviceInfo Devices[MaxDevices];
int Count;
DeviceList() : Count(0) {}
// Find a device by HID. Returns nullptr if not found.
const DeviceInfo* FindByHid(const char* hid) const;
// Find a device by path. Returns nullptr if not found.
const DeviceInfo* FindByPath(const char* path) const;
};
// ── Enumeration API ─────────────────────────────────────────────
// Enumerate all ACPI devices in the namespace.
// The interpreter must be initialized first (LoadTable called).
void EnumerateAll(DeviceList& result);
// Evaluate _STA for a device node. Returns STA_DEFAULT if no _STA exists.
uint32_t EvaluateSta(int32_t deviceNodeIndex);
// Evaluate _ADR for a device node. Returns 0 if no _ADR exists.
uint64_t EvaluateAdr(int32_t deviceNodeIndex);
// Evaluate _HID for a device node. Returns false if no _HID exists.
// Writes the HID string (or EISAID) into outHid.
bool EvaluateHid(int32_t deviceNodeIndex, char* outHid, int maxLen);
// Evaluate _UID for a device node.
bool EvaluateUid(int32_t deviceNodeIndex, char* outUid, int maxLen);
// Evaluate _CRS (Current Resource Settings) for a device node.
bool EvaluateCrs(int32_t deviceNodeIndex, AML::ResourceList& result);
// ── Well-known HIDs ─────────────────────────────────────────────
// PCI Host Bridge
static constexpr const char* HID_PCI_HOST = "PNP0A03";
static constexpr const char* HID_PCIE_HOST = "PNP0A08";
// System Timer (PIT)
static constexpr const char* HID_PIT = "PNP0100";
// RTC / CMOS
static constexpr const char* HID_RTC = "PNP0B00";
// Keyboard controller (i8042)
static constexpr const char* HID_KBD = "PNP0303";
// PS/2 Mouse
static constexpr const char* HID_MOUSE = "PNP0F13";
// Serial port (UART)
static constexpr const char* HID_SERIAL = "PNP0501";
// ACPI Embedded Controller
static constexpr const char* HID_EC = "PNP0C09";
// ACPI Power Button
static constexpr const char* HID_PWRBTN = "PNP0C0C";
// ACPI Sleep Button
static constexpr const char* HID_SLPBTN = "PNP0C0E";
// ACPI Lid device
static constexpr const char* HID_LID = "PNP0C0D";
// ACPI Thermal Zone
static constexpr const char* HID_THERMAL = "PNP0C0B";
// ACPI Battery
static constexpr const char* HID_BATTERY = "PNP0C0A";
// ACPI AC Adapter
static constexpr const char* HID_AC_ADAPTER = "ACPI0003";
// ── Sleep State Support ─────────────────────────────────────────
// Extract sleep state values from namespace objects (\_S0_ through \_S5_).
struct SleepState {
uint16_t SLP_TYPa;
uint16_t SLP_TYPb;
bool Valid;
};
// Get the sleep type values for a given sleep state (0-5).
SleepState GetSleepState(int state);
};
};
+19 -2
View File
@@ -5,8 +5,11 @@
*/
#include "AcpiShutdown.hpp"
#include "AcpiSleep.hpp"
#include <ACPI/FADT.hpp>
#include <ACPI/AML/AmlParser.hpp>
#include <ACPI/AML/AmlInterpreter.hpp>
#include <ACPI/AcpiDevices.hpp>
#include <Io/IoPort.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
@@ -29,7 +32,7 @@ namespace Hal {
void Initialize(ACPI::CommonSDTHeader* xsdt) {
FADT::ParsedFADT fadt{};
if (!FADT::Parse(xsdt, fadt) || !fadt.Valid) {
KernelLogStream(ERROR, "ACPI") << "Failed to parse FADT ACPI shutdown unavailable";
KernelLogStream(ERROR, "ACPI") << "Failed to parse FADT - ACPI shutdown unavailable";
return;
}
@@ -38,7 +41,7 @@ namespace Hal {
AML::S5Object s5 = AML::FindS5(dsdt);
if (!s5.Valid) {
KernelLogStream(ERROR, "ACPI") << "Could not find \\_S5_ in DSDT ACPI shutdown unavailable";
KernelLogStream(ERROR, "ACPI") << "Could not find \\_S5_ in DSDT - ACPI shutdown unavailable";
return;
}
@@ -50,6 +53,20 @@ namespace Hal {
KernelLogStream(OK, "ACPI") << "ACPI shutdown initialized (PM1a=" << base::hex
<< (uint64_t)g_pm1aControlBlock << " SLP_TYPa=" << (uint64_t)g_slpTypA << ")";
// Initialize the full AML interpreter with the DSDT.
// This loads the entire DSDT namespace, enabling device enumeration,
// method evaluation, and field I/O for the rest of the kernel.
AML::InitializeInterpreter(dsdt);
// Enumerate ACPI devices now that the namespace is loaded
if (AML::GetInterpreter().IsInitialized()) {
AcpiDevices::DeviceList devices{};
AcpiDevices::EnumerateAll(devices);
}
// Initialize S3 suspend support (reads FACS and \_S3_ from namespace)
AcpiSleep::Initialize(xsdt);
}
bool IsAvailable() {
+403
View File
@@ -0,0 +1,403 @@
/*
* AcpiSleep.cpp
* ACPI S3 suspend-to-RAM implementation
* Copyright (c) 2026 Daniel Hammer
*/
#include "AcpiSleep.hpp"
#include <ACPI/FADT.hpp>
#include <ACPI/AML/AmlParser.hpp>
#include <ACPI/AML/AmlInterpreter.hpp>
#include <Io/IoPort.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Memory/HHDM.hpp>
#include <Memory/Paging.hpp>
#include <Hal/GDT.hpp>
#include <Hal/Apic/Apic.hpp>
#include <Hal/Apic/IoApic.hpp>
#include <Hal/Apic/Pic.hpp>
#include <Timekeeping/ApicTimer.hpp>
#include <Drivers/Graphics/IntelGPU.hpp>
#include <Drivers/PS2/PS2Controller.hpp>
#include <Graphics/Cursor.hpp>
#include <Libraries/Memory.hpp>
using namespace Kt;
// Assembly routines from S3Wake.asm
extern "C" int AcpiSaveAndSuspend(Hal::AcpiSleep::CpuState* stateArea);
extern "C" void AcpiResumeLongMode(Hal::AcpiSleep::CpuState* stateArea);
extern "C" void AcpiWakeEntry();
extern "C" void* g_wakeStatePtr;
// Real-mode trampoline from S3Trampoline.asm
extern "C" char S3TrampolineStart[];
extern "C" char S3Trampoline64[];
extern "C" char S3TrampolineData[];
extern "C" char S3TrampolineEnd[];
// Physical address where the trampoline is copied (must be < 1MB, page-aligned)
static constexpr uint32_t TRAMPOLINE_PHYS = 0x8000;
// GDT/TSS reload helpers
namespace Hal {
extern void BridgeLoadGDT();
extern void LoadTSS();
}
namespace Hal {
namespace AcpiSleep {
// ── State ───────────────────────────────────────────────────────
static bool g_s3Available = false;
static uint16_t g_s3SlpTypA = 0;
static uint16_t g_s3SlpTypB = 0;
static uint32_t g_pm1aEventBlock = 0;
static uint32_t g_pm1bEventBlock = 0;
static uint32_t g_pm1aControlBlock = 0;
static uint32_t g_pm1bControlBlock = 0;
static uint8_t g_pm1EventLength = 0;
static FACS* g_facs = nullptr;
// CPU state save area (aligned for fxsave)
static CpuState g_cpuState __attribute__((aligned(64)));
// ── Initialize ──────────────────────────────────────────────────
void Initialize(ACPI::CommonSDTHeader* xsdt) {
g_s3Available = false;
FADT::ParsedFADT fadt{};
if (!FADT::Parse(xsdt, fadt) || !fadt.Valid)
return;
// Check if FACS exists
if (fadt.FacsAddress == 0) {
KernelLogStream(INFO, "S3") << "No FACS - S3 unavailable";
return;
}
g_facs = (FACS*)Memory::HHDM(fadt.FacsAddress);
// Verify FACS signature
if (g_facs->Signature[0] != 'F' || g_facs->Signature[1] != 'A' ||
g_facs->Signature[2] != 'C' || g_facs->Signature[3] != 'S') {
KernelLogStream(ERROR, "S3") << "Invalid FACS signature";
g_facs = nullptr;
return;
}
// Store PM register addresses
g_pm1aEventBlock = fadt.PM1aEventBlock;
g_pm1bEventBlock = fadt.PM1bEventBlock;
g_pm1aControlBlock = fadt.PM1aControlBlock;
g_pm1bControlBlock = fadt.PM1bControlBlock;
g_pm1EventLength = fadt.PM1EventLength;
// Find \_S3_ via brute-force DSDT scan (same approach as \_S5_).
// This works on any DSDT regardless of complexity — does not
// require the AML interpreter or namespace to be loaded.
auto* dsdt = (void*)Memory::HHDM(fadt.DsdtAddress);
AML::SleepObject s3 = AML::FindSleepState(dsdt, 3);
if (s3.Valid) {
g_s3SlpTypA = s3.SLP_TYPa;
g_s3SlpTypB = s3.SLP_TYPb;
g_s3Available = true;
KernelLogStream(OK, "S3") << "S3 suspend available (SLP_TYPa="
<< base::hex << (uint64_t)g_s3SlpTypA
<< " SLP_TYPb=" << base::hex << (uint64_t)g_s3SlpTypB << ")";
KernelLogStream(INFO, "S3") << "Kernel PML4 phys = " << base::hex
<< (uint64_t)Memory::VMM::g_paging->PML4;
} else {
KernelLogStream(INFO, "S3") << "\\_S3_ not found in DSDT - S3 unavailable";
}
}
bool IsS3Available() {
return g_s3Available;
}
// ── Evaluate _PTS (Prepare To Sleep) ────────────────────────────
static void EvaluatePts(int sleepState) {
auto& interp = AML::GetInterpreter();
if (!interp.IsInitialized()) return;
int32_t node = interp.GetNamespace().FindNode("\\_PTS");
if (node < 0) return;
AML::Object arg{};
arg.Type = AML::ObjectType::Integer;
arg.Integer = (uint64_t)sleepState;
AML::Object result{};
interp.EvaluateMethod("\\_PTS", &arg, 1, result);
KernelLogStream(DEBUG, "S3") << "Evaluated \\_PTS(" << base::dec << (uint64_t)sleepState << ")";
}
// ── Evaluate _WAK (System Wake) ─────────────────────────────────
static void EvaluateWak(int sleepState) {
auto& interp = AML::GetInterpreter();
if (!interp.IsInitialized()) return;
int32_t node = interp.GetNamespace().FindNode("\\_WAK");
if (node < 0) return;
AML::Object arg{};
arg.Type = AML::ObjectType::Integer;
arg.Integer = (uint64_t)sleepState;
AML::Object result{};
interp.EvaluateMethod("\\_WAK", &arg, 1, result);
KernelLogStream(DEBUG, "S3") << "Evaluated \\_WAK(" << base::dec << (uint64_t)sleepState << ")";
}
// ── Clear PM1 Status Registers ──────────────────────────────────
static void ClearPM1Status() {
// Write 1 to clear all status bits (write-1-to-clear semantics)
uint16_t clearMask = PM1_WAK_STS | PM1_PWRBTN_STS | PM1_SLPBTN_STS |
PM1_RTC_STS | PM1_TMR_STS | PM1_BM_STS | PM1_GBL_STS;
if (g_pm1aEventBlock != 0)
Io::Out16(clearMask, (uint16_t)g_pm1aEventBlock);
if (g_pm1bEventBlock != 0)
Io::Out16(clearMask, (uint16_t)g_pm1bEventBlock);
}
// ── Enable Wake Events ──────────────────────────────────────────
static void EnableWakeEvents() {
// Enable register is at event block + PM1EventLength/2
uint16_t enableOffset = g_pm1EventLength / 2;
// Enable power button and RTC as wake sources
uint16_t enableMask = PM1_PWRBTN_EN | PM1_RTC_EN;
if (g_pm1aEventBlock != 0 && enableOffset > 0)
Io::Out16(enableMask, (uint16_t)(g_pm1aEventBlock + enableOffset));
if (g_pm1bEventBlock != 0 && enableOffset > 0)
Io::Out16(enableMask, (uint16_t)(g_pm1bEventBlock + enableOffset));
}
// ── Install real-mode trampoline and set waking vector ────────────
static void SetWakingVector() {
if (!g_facs) return;
// Store the CpuState pointer where the 64-bit wake stub can find it.
g_wakeStatePtr = (void*)&g_cpuState;
// Copy the real-mode trampoline to low physical memory (0x8000).
// This code transitions real mode → protected mode → long mode
// and then jumps to AcpiResumeLongMode in the kernel.
uint32_t trampolineSize = (uint32_t)(S3TrampolineEnd - S3TrampolineStart);
uint8_t* trampolineDst = (uint8_t*)Memory::HHDM((uint64_t)TRAMPOLINE_PHYS);
uint8_t* trampolineSrc = (uint8_t*)S3TrampolineStart;
memcpy(trampolineDst, trampolineSrc, trampolineSize);
// Patch the trampoline data area with CR3 and resume addresses.
// Data layout (from S3Trampoline.asm):
// +0: uint64_t CR3 (full 64-bit PML4 physical address)
// +8: uint64_t CpuState virtual address
// +16: uint64_t AcpiResumeLongMode virtual address
uint32_t dataOffset = (uint32_t)(S3TrampolineData - S3TrampolineStart);
uint8_t* dataArea = trampolineDst + dataOffset;
// Use the kernel master PML4 (which has 0x8000 identity-mapped)
// rather than the saved process PML4 (which doesn't).
// AcpiResumeLongMode will restore the saved CR3 after we're
// safely back in long mode with kernel GDT/IDT.
uint64_t cr3val = (uint64_t)Memory::VMM::g_paging->PML4;
// The 16-bit->32-bit trampoline path can only load 32-bit CR3.
// If the PML4 is above 4GB, the real-mode wake path would fail.
if (cr3val > 0xFFFFFFFF) {
KernelLogStream(ERROR, "S3") << "PML4 at " << base::hex << cr3val
<< " is above 4GB - real-mode wake path will fail!";
}
memcpy(dataArea + 0, &cr3val, 8);
uint64_t statePtr = (uint64_t)&g_cpuState;
memcpy(dataArea + 8, &statePtr, 8);
uint64_t resumeAddr = (uint64_t)&AcpiResumeLongMode;
memcpy(dataArea + 16, &resumeAddr, 8);
// Set the 32-bit waking vector only. Setting X_FirmwareWakingVector
// to non-zero causes this laptop's firmware to hang during wake.
g_facs->FirmwareWakingVector = TRAMPOLINE_PHYS;
g_facs->X_FirmwareWakingVector = 0;
KernelLogStream(DEBUG, "S3") << "Trampoline installed at " << base::hex
<< (uint64_t)TRAMPOLINE_PHYS << " (" << base::dec
<< (uint64_t)trampolineSize << " bytes)";
KernelLogStream(DEBUG, "S3") << "Trampoline CR3 = " << base::hex << cr3val;
}
// ── Wait for WAK_STS ────────────────────────────────────────────
static void WaitForWake() {
// After entering S3, the CPU halts. On resume, firmware runs the
// waking vector. But if we somehow didn't enter sleep (e.g. immediate
// wake), poll WAK_STS.
for (int i = 0; i < 10000; i++) {
if (g_pm1aEventBlock != 0) {
uint16_t sts = Io::In16((uint16_t)g_pm1aEventBlock);
if (sts & PM1_WAK_STS) return;
}
Io::IoPortWait();
}
}
// ── Suspend ─────────────────────────────────────────────────────
int Suspend() {
if (!g_s3Available) {
KernelLogStream(ERROR, "S3") << "S3 suspend not available";
return -1;
}
KernelLogStream(INFO, "S3") << "Preparing for S3 suspend...";
// 1. Evaluate _PTS(3) — Prepare To Sleep
EvaluatePts(3);
// 2. Save CPU state. AcpiSaveAndSuspend returns 1 on initial call,
// and 0 when we resume from S3 (AcpiResumeLongMode sets RAX=0).
int resumed = !AcpiSaveAndSuspend(&g_cpuState);
if (resumed) {
// ── RESUME PATH ─────────────────────────────────────────
// We just woke from S3. Firmware ran our waking vector which
// called AcpiResumeLongMode, restoring all registers and
// returning here with 0.
// Debug: write a green rectangle to the top-left corner of the
// framebuffer. The framebuffer memory survives S3 (it's RAM).
// This will be visible once the GPU display plane is restored,
// confirming the CPU successfully completed the resume path.
{
uint32_t* fb = ::Graphics::Cursor::GetFramebufferBase();
uint64_t pitch = ::Graphics::Cursor::GetFramebufferPitch();
if (fb && pitch > 0) {
for (int y = 0; y < 32; y++) {
uint32_t* row = (uint32_t*)((uint8_t*)fb + y * pitch);
for (int x = 0; x < 32; x++) {
row[x] = 0xFF00FF00; // Green (ARGB)
}
}
}
}
// Reload GDT and TSS (firmware may have clobbered them)
Hal::BridgeLoadGDT();
Hal::LoadTSS();
// Re-disable legacy 8259 PIC. Firmware may have re-enabled
// it during S3 resume POST, which could cause spurious
// interrupts on conflicting vectors once we enable interrupts.
Hal::DisableLegacyPic();
// Re-enable the Local APIC (MSR global enable + SVR + TPR)
Hal::LocalApic::Reinitialize();
// Restore I/O APIC redirection entries (lost during S3).
// Must be done before enabling interrupts so IRQ routing
// is in place when devices start generating interrupts.
Hal::IoApic::Reinitialize();
// Restart the APIC timer
Timekeeping::ApicTimerReinitialize();
// Clear WAK_STS
ClearPM1Status();
// Evaluate _WAK(3)
EvaluateWak(3);
// Re-enable PS/2 controller (ports and interrupts may be
// disabled after S3; skip full self-test to avoid resetting
// attached devices)
Drivers::PS2::Reinitialize();
// Restore Intel GPU display (GTT + display plane lost during S3)
if (Drivers::Graphics::IntelGPU::IsInitialized()) {
Drivers::Graphics::IntelGPU::Reinitialize();
}
// Re-enable interrupts
asm volatile("sti");
KernelLogStream(OK, "S3") << "Resumed from S3 suspend";
return 0;
}
// ── SUSPEND PATH (initial save returned 1) ──────────────────
// 3. Disable interrupts
asm volatile("cli");
// 4. Identity-map the trampoline page so the CPU can execute it
// after paging is re-enabled with our CR3 during wake.
// Physical 0x8000 → virtual 0x8000 (flat identity mapping).
Memory::VMM::g_paging->Map(TRAMPOLINE_PHYS, TRAMPOLINE_PHYS);
// 5. Flush all caches
asm volatile("wbinvd");
// 6. Set the waking vector in FACS
SetWakingVector();
// 7. Clear any pending PM1 status bits
ClearPM1Status();
// 8. Enable wake events (power button, RTC)
EnableWakeEvents();
// 9. Write SLP_TYP to PM1 control registers (without SLP_EN)
uint16_t pm1a = Io::In16((uint16_t)g_pm1aControlBlock);
pm1a = (pm1a & ~PM1_SLP_TYP_MASK) | (g_s3SlpTypA << 10);
Io::Out16(pm1a, (uint16_t)g_pm1aControlBlock);
if (g_pm1bControlBlock != 0) {
uint16_t pm1b = Io::In16((uint16_t)g_pm1bControlBlock);
pm1b = (pm1b & ~PM1_SLP_TYP_MASK) | (g_s3SlpTypB << 10);
Io::Out16(pm1b, (uint16_t)g_pm1bControlBlock);
}
// 10. Flush caches again
asm volatile("wbinvd");
// 11. Assert SLP_EN — this triggers S3 entry
Io::Out16(pm1a | PM1_SLP_EN, (uint16_t)g_pm1aControlBlock);
if (g_pm1bControlBlock != 0) {
uint16_t pm1b = Io::In16((uint16_t)g_pm1bControlBlock);
Io::Out16(pm1b | PM1_SLP_EN, (uint16_t)g_pm1bControlBlock);
}
// The CPU must halt for the chipset/hypervisor to complete the S3
// transition. On real hardware the chipset cuts power after SLP_EN;
// on QEMU/KVM the vCPU must reach HLT before QEMU finalizes S3.
// On resume, firmware jumps to our waking vector.
for (;;) {
asm volatile("hlt");
// If we wake from HLT (spurious or real wake), check WAK_STS
if (g_pm1aEventBlock != 0) {
uint16_t sts = Io::In16((uint16_t)g_pm1aEventBlock);
if (sts & PM1_WAK_STS) break;
}
}
// If we get here, either S3 failed or we woke immediately.
// The normal wake path goes through AcpiResumeLongMode instead.
// Re-enable interrupts as a safety measure.
asm volatile("sti");
KernelLogStream(WARNING, "S3") << "S3 entry may have failed (fell through)";
return 0;
}
};
};
+90
View File
@@ -0,0 +1,90 @@
/*
* AcpiSleep.hpp
* ACPI S3 suspend-to-RAM support
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <ACPI/ACPI.hpp>
namespace Hal {
namespace AcpiSleep {
// ── FACS (Firmware ACPI Control Structure) ──────────────────────
struct FACS {
char Signature[4]; // "FACS"
uint32_t Length;
uint32_t HardwareSignature;
uint32_t FirmwareWakingVector; // 32-bit real-mode wake address
uint32_t GlobalLock;
uint32_t Flags;
uint64_t X_FirmwareWakingVector; // 64-bit wake address (ACPI 2.0+)
uint8_t Version;
uint8_t Reserved[3];
uint32_t OspmFlags;
} __attribute__((packed));
// FACS Flags
static constexpr uint32_t FACS_S4BIOS_F = (1 << 0);
static constexpr uint32_t FACS_64BIT_WAKE_F = (1 << 1);
// FADT Flags relevant to sleep
static constexpr uint32_t FADT_S4_RTC_STS_VALID = (1 << 16);
static constexpr uint32_t FADT_HW_REDUCED_ACPI = (1 << 20);
// ── PM1 Status Register Bits ────────────────────────────────────
static constexpr uint16_t PM1_TMR_STS = (1 << 0);
static constexpr uint16_t PM1_BM_STS = (1 << 4);
static constexpr uint16_t PM1_GBL_STS = (1 << 5);
static constexpr uint16_t PM1_PWRBTN_STS = (1 << 8);
static constexpr uint16_t PM1_SLPBTN_STS = (1 << 9);
static constexpr uint16_t PM1_RTC_STS = (1 << 10);
static constexpr uint16_t PM1_WAK_STS = (1 << 15);
// ── PM1 Enable Register Bits ────────────────────────────────────
static constexpr uint16_t PM1_TMR_EN = (1 << 0);
static constexpr uint16_t PM1_GBL_EN = (1 << 5);
static constexpr uint16_t PM1_PWRBTN_EN = (1 << 8);
static constexpr uint16_t PM1_SLPBTN_EN = (1 << 9);
static constexpr uint16_t PM1_RTC_EN = (1 << 10);
// ── PM1 Control Register Bits ───────────────────────────────────
static constexpr uint16_t PM1_SCI_EN = (1 << 0);
static constexpr uint16_t PM1_BM_RLD = (1 << 1);
static constexpr uint16_t PM1_SLP_TYP_MASK = 0x1C00; // bits 10-12
static constexpr uint16_t PM1_SLP_EN = (1 << 13);
// ── CPU State (saved across S3) ─────────────────────────────────
// Layout must match S3Wake.asm offsets exactly.
// No FPU/SSE state — the kernel is compiled with -mno-sse.
struct CpuState {
uint64_t Rax, Rbx, Rcx, Rdx; // 0x00 - 0x18
uint64_t Rsi, Rdi, Rbp, Rsp; // 0x20 - 0x38
uint64_t R8, R9, R10, R11; // 0x40 - 0x58
uint64_t R12, R13, R14, R15; // 0x60 - 0x78
uint64_t Rflags; // 0x80
uint64_t Rip; // 0x88
uint64_t Cr3; // 0x90
uint64_t Cr0, Cr4; // 0x98, 0xA0
uint8_t GdtPtr[10]; // 0xA8
uint8_t IdtPtr[10]; // 0xB2
} __attribute__((aligned(16)));
// ── Sleep API ───────────────────────────────────────────────────
// Initialize sleep support. Called during boot after ACPI init.
// xsdt is used to read FADT for FACS and PM register addresses.
void Initialize(ACPI::CommonSDTHeader* xsdt);
// Returns true if S3 suspend is supported by the hardware.
bool IsS3Available();
// Perform an S3 suspend-to-RAM.
// Saves CPU state, evaluates _PTS(3), programs PM registers,
// sets waking vector, and enters S3.
// Returns 0 on successful resume, or -1 if S3 is unavailable.
int Suspend();
};
};
+20
View File
@@ -58,14 +58,34 @@ namespace Hal {
result.DsdtAddress = fadt->Dsdt;
}
// FACS address (for waking vector)
if (fadt->Header.Length >= offsetof(Table, X_FirmwareControl) + 8 && fadt->X_FirmwareControl != 0) {
result.FacsAddress = fadt->X_FirmwareControl;
} else {
result.FacsAddress = fadt->FirmwareCtrl;
}
result.PM1aEventBlock = fadt->PM1aEventBlock;
result.PM1bEventBlock = fadt->PM1bEventBlock;
result.PM1aControlBlock = fadt->PM1aControlBlock;
result.PM1bControlBlock = fadt->PM1bControlBlock;
result.PM2ControlBlock = fadt->PM2ControlBlock;
result.PMTimerBlock = fadt->PMTimerBlock;
result.GPE0Block = fadt->GPE0Block;
result.GPE1Block = fadt->GPE1Block;
result.SMI_CommandPort = fadt->SMI_CommandPort;
result.SCI_Interrupt = fadt->SCI_Interrupt;
result.PM1EventLength = fadt->PM1EventLength;
result.GPE0Length = fadt->GPE0Length;
result.GPE1Length = fadt->GPE1Length;
result.AcpiEnable = fadt->AcpiEnable;
result.AcpiDisable = fadt->AcpiDisable;
result.Flags = fadt->Flags;
result.Valid = true;
KernelLogStream(INFO, "FADT") << "DSDT at " << base::hex << result.DsdtAddress;
KernelLogStream(INFO, "FADT") << "FACS at " << base::hex << result.FacsAddress;
KernelLogStream(INFO, "FADT") << "PM1a_EVT_BLK: " << base::hex << (uint64_t)result.PM1aEventBlock;
KernelLogStream(INFO, "FADT") << "PM1a_CNT_BLK: " << base::hex << (uint64_t)result.PM1aControlBlock;
if (result.PM1bControlBlock != 0) {
+12
View File
@@ -71,11 +71,23 @@ namespace Hal {
struct ParsedFADT {
uint64_t DsdtAddress;
uint64_t FacsAddress;
uint32_t PM1aEventBlock;
uint32_t PM1bEventBlock;
uint32_t PM1aControlBlock;
uint32_t PM1bControlBlock;
uint32_t PM2ControlBlock;
uint32_t PMTimerBlock;
uint32_t GPE0Block;
uint32_t GPE1Block;
uint32_t SMI_CommandPort;
uint16_t SCI_Interrupt;
uint8_t PM1EventLength;
uint8_t GPE0Length;
uint8_t GPE1Length;
uint8_t AcpiEnable;
uint8_t AcpiDisable;
uint32_t Flags;
bool Valid;
};
+115
View File
@@ -0,0 +1,115 @@
;
; S3Trampoline.asm
; S3 resume trampoline with both 16-bit and 64-bit entry points
; Copyright (c) 2026 Daniel Hammer
;
; Copied to physical 0x8000 before entering S3. Two entry points:
; 0x8000: real-mode (16-bit) for legacy BIOS wake
; 0x8100: long-mode (64-bit) for UEFI wake
;
[bits 16]
section .text
; ═════════════════════════════════════════════════════════════════════════
; 16-bit real-mode entry (FirmwareWakingVector = 0x8000)
; ═════════════════════════════════════════════════════════════════════════
global S3TrampolineStart
S3TrampolineStart:
cli
cld
mov ax, 0x0800
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x00F0
lgdt [ds:gdt_ptr - S3TrampolineStart]
mov eax, cr0
or eax, 1
mov cr0, eax
jmp dword 0x08:(0x8000 + pm32_entry - S3TrampolineStart)
[bits 32]
pm32_entry:
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr4
or eax, (1 << 5)
mov cr4, eax
; Load lower 32 bits of CR3 from data area
mov eax, [0x8000 + data_cr3 - S3TrampolineStart]
mov cr3, eax
mov ecx, 0xC0000080
rdmsr
or eax, (1 << 8)
wrmsr
mov eax, cr0
or eax, (1 << 31)
mov cr0, eax
jmp dword 0x18:(0x8000 + lm64_common - S3TrampolineStart)
; ── Temporary GDT ──────────────────────────────────────────────────────
align 16
gdt_start:
dq 0x0000000000000000 ; 0x00: Null
dq 0x00CF9A000000FFFF ; 0x08: 32-bit code
dq 0x00CF92000000FFFF ; 0x10: 32-bit data
dq 0x00209A0000000000 ; 0x18: 64-bit code
dq 0x0000920000000000 ; 0x20: 64-bit data
gdt_end:
gdt_ptr:
dw gdt_end - gdt_start - 1
dd 0x8000 + gdt_start - S3TrampolineStart
; ═════════════════════════════════════════════════════════════════════════
; 64-bit entry at offset 0x100 (X_FirmwareWakingVector = 0x8100)
; UEFI firmware jumps here already in long mode with its own page tables.
; ═════════════════════════════════════════════════════════════════════════
[bits 64]
times (0x100 - ($ - S3TrampolineStart)) db 0x90
global S3Trampoline64
S3Trampoline64:
cli
cld
; Load kernel PML4 (full 64-bit) - firmware identity-maps low memory
mov rax, [0x8000 + data_cr3 - S3TrampolineStart]
mov cr3, rax
; Fall through to common path
; ── Common 64-bit path (both entries converge here) ──────────────────
lm64_common:
mov rdi, [0x8000 + data_state_ptr - S3TrampolineStart]
mov rax, [0x8000 + data_resume_addr - S3TrampolineStart]
jmp rax
; ═════════════════════════════════════════════════════════════════════════
; Data area (patched by kernel before S3)
; ═════════════════════════════════════════════════════════════════════════
align 8
global S3TrampolineData
S3TrampolineData:
data_cr3: dq 0 ; kernel PML4 physical address (full 64-bit)
data_state_ptr: dq 0 ; virtual address of CpuState
data_resume_addr: dq 0 ; virtual address of AcpiResumeLongMode
global S3TrampolineEnd
S3TrampolineEnd:
+184
View File
@@ -0,0 +1,184 @@
;
; S3Wake.asm
; S3 suspend/resume: CPU state save and wake trampoline
; Copyright (c) 2026 Daniel Hammer
;
[bits 64]
section .text
; ─── AcpiSaveAndSuspend ──────────────────────────────────────────────────
; extern "C" int AcpiSaveAndSuspend(CpuState* stateArea)
; rdi = pointer to CpuState structure
;
; Saves all CPU registers into the CpuState structure, then returns 1.
; The caller is expected to enter S3 after this returns.
; When the system resumes, AcpiResumeLongMode jumps to the saved RIP
; with RAX=0, so Suspend() knows it's a resume.
;
; Returns: 1 on initial call (proceed to enter S3)
; 0 on resume from S3
;
; CpuState layout:
; 0x00 RAX 0x40 R8 0x80 RFLAGS
; 0x08 RBX 0x48 R9 0x88 RIP
; 0x10 RCX 0x50 R10 0x90 CR3
; 0x18 RDX 0x58 R11 0x98 CR0
; 0x20 RSI 0x60 R12 0xA0 CR4
; 0x28 RDI 0x68 R13 0xA8 GDT (10 bytes)
; 0x30 RBP 0x70 R14 0xB2 IDT (10 bytes)
; 0x38 RSP 0x78 R15
;
global AcpiSaveAndSuspend
AcpiSaveAndSuspend:
; Save all general-purpose registers
mov [rdi + 0x00], rax
mov [rdi + 0x08], rbx
mov [rdi + 0x10], rcx
mov [rdi + 0x18], rdx
mov [rdi + 0x20], rsi
mov [rdi + 0x28], rdi ; save rdi (pointer to state area)
mov [rdi + 0x30], rbp
mov [rdi + 0x38], rsp
mov [rdi + 0x40], r8
mov [rdi + 0x48], r9
mov [rdi + 0x50], r10
mov [rdi + 0x58], r11
mov [rdi + 0x60], r12
mov [rdi + 0x68], r13
mov [rdi + 0x70], r14
mov [rdi + 0x78], r15
; Save RFLAGS
pushfq
pop rax
mov [rdi + 0x80], rax
; Save return address as resume RIP
mov rax, [rsp] ; return address on stack
mov [rdi + 0x88], rax
; Save control registers
mov rax, cr3
mov [rdi + 0x90], rax
mov rax, cr0
mov [rdi + 0x98], rax
mov rax, cr4
mov [rdi + 0xA0], rax
; Save GDT pointer (offset 0xA8)
sgdt [rdi + 0xA8]
; Save IDT pointer (offset 0xB2)
sidt [rdi + 0xB2]
; Return 1 = "initial save, now enter S3"
mov rax, 1
ret
; ─── AcpiWakeEntry ───────────────────────────────────────────────────────
; This is the actual waking vector target. Firmware jumps here after S3
; resume. It loads the CpuState pointer from a fixed location (set before
; suspend) and falls through to AcpiResumeLongMode.
;
; For 64-bit waking vector (X_FirmwareWakingVector): firmware resumes in
; long mode and jumps directly here. We load rdi from the saved pointer
; and proceed to restore state.
;
; For QEMU with OVMF and similar UEFI firmware, the 64-bit path works.
; Real-mode (32-bit FirmwareWakingVector) wake is not yet supported.
;
global AcpiWakeEntry
AcpiWakeEntry:
; Clear direction flag (firmware may have left it set)
cld
; Load the CpuState pointer from the well-known location.
; g_wakeStatePtr is set by the C code before entering S3.
lea rdi, [rel g_wakeStatePtr]
mov rdi, [rdi]
; Fall through to restore all state
jmp AcpiResumeLongMode
; ─── Well-known pointer to CpuState (set before suspend) ────────────────
; Placed in .text so AcpiWakeEntry can use RIP-relative addressing
; without a cross-section relocation warning.
; extern "C" void* g_wakeStatePtr;
global g_wakeStatePtr
g_wakeStatePtr: dq 0
; ─── AcpiResumeLongMode ──────────────────────────────────────────────────
; extern "C" void AcpiResumeLongMode(CpuState* stateArea)
; rdi = pointer to CpuState structure
;
; Restores all saved registers and returns to the original Suspend() caller
; with RAX=0 to indicate "resumed from S3".
;
global AcpiResumeLongMode
AcpiResumeLongMode:
; At entry: rdi = CpuState*, RSP = trampoline stack (unmapped junk),
; CR3 = kernel PML4 (loaded by trampoline), CS = trampoline selector.
;
; We must restore the kernel stack FIRST so push/pop work, then reload
; GDT/CS/IDT before any interrupt can fire.
; Restore kernel RSP immediately so we have a valid stack
mov rsp, [rdi + 0x38]
; Restore GDT
lgdt [rdi + 0xA8]
; Reload CS to kernel code selector (0x08) via far return.
; Without this, CS still holds the trampoline's selector (0x18)
; which maps to UserData in the kernel GDT → #GP on first interrupt.
push 0x08 ; kernel code selector
lea rax, [rel .reload_cs]
push rax
retfq
.reload_cs:
; Reload data segment registers with kernel data selector
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
; Restore IDT
lidt [rdi + 0xB2]
; Restore saved CR3 (process PML4). The trampoline used the kernel
; master PML4 to get here; now switch to the actual saved context.
mov rax, [rdi + 0x90]
mov cr3, rax
; Restore general-purpose registers (skip RSP — already restored above)
mov rbx, [rdi + 0x08]
mov rcx, [rdi + 0x10]
mov rdx, [rdi + 0x18]
mov rsi, [rdi + 0x20]
mov rbp, [rdi + 0x30]
mov r8, [rdi + 0x40]
mov r9, [rdi + 0x48]
mov r10, [rdi + 0x50]
mov r11, [rdi + 0x58]
mov r12, [rdi + 0x60]
mov r13, [rdi + 0x68]
mov r14, [rdi + 0x70]
mov r15, [rdi + 0x78]
; Restore RFLAGS
mov rax, [rdi + 0x80]
push rax
popfq
; Restore rdi last
mov rdi, [rdi + 0x28]
; Return 0 = "resumed from S3"
xor rax, rax
ret
+9 -1
View File
@@ -1,6 +1,6 @@
/*
* Power.hpp
* SYS_RESET, SYS_SHUTDOWN syscalls
* SYS_RESET, SYS_SHUTDOWN, SYS_SUSPEND syscalls
* Copyright (c) 2026 Daniel Hammer
*/
@@ -9,6 +9,7 @@
#include <Efi/UEFI.hpp>
#include <Memory/Paging.hpp>
#include <ACPI/AcpiShutdown.hpp>
#include <ACPI/AcpiSleep.hpp>
namespace Montauk {
@@ -41,4 +42,11 @@ namespace Montauk {
asm volatile("cli; hlt");
__builtin_unreachable();
}
static int64_t Sys_Suspend() {
if (!Hal::AcpiSleep::IsS3Available()) {
return -1; // S3 not supported
}
return (int64_t)Hal::AcpiSleep::Suspend();
}
};
+3 -1
View File
@@ -22,7 +22,7 @@
#include "Info.hpp" // SYS_GETINFO
#include "Graphics.hpp" // SYS_FBINFO, SYS_FBMAP, SYS_TERMSIZE, SYS_TERMSCALE
#include "Net.hpp" // SYS_PING, SYS_SOCKET, SYS_CONNECT, SYS_BIND, SYS_LISTEN, SYS_ACCEPT, SYS_SEND, SYS_RECV, SYS_CLOSESOCK, SYS_SENDTO, SYS_RECVFROM, SYS_GETNETCFG, SYS_SETNETCFG, SYS_RESOLVE
#include "Power.hpp" // SYS_RESET, SYS_SHUTDOWN
#include "Power.hpp" // SYS_RESET, SYS_SHUTDOWN, SYS_SUSPEND
#include "Mouse.hpp" // SYS_MOUSESTATE, SYS_SETMOUSEBOUNDS
#include "IoRedir.hpp" // SYS_SPAWN_REDIR, SYS_CHILDIO_READ, SYS_CHILDIO_WRITE, SYS_CHILDIO_WRITEKEY, SYS_CHILDIO_SETTERMSZ
#include "Random.hpp" // SYS_GETRANDOM
@@ -331,6 +331,8 @@ namespace Montauk {
case SYS_BTINFO:
if (!ValidUserPtr(frame->arg1)) return -1;
return Sys_BtInfo((BtAdapterInfo*)frame->arg1);
case SYS_SUSPEND:
return Sys_Suspend();
default:
return -1;
}
+3
View File
@@ -171,6 +171,9 @@ namespace Montauk {
static constexpr uint64_t SYS_BTLIST = 87;
static constexpr uint64_t SYS_BTINFO = 88;
/* Power.hpp */
static constexpr uint64_t SYS_SUSPEND = 89;
static constexpr int SOCK_TCP = 1;
static constexpr int SOCK_UDP = 2;
+54
View File
@@ -640,4 +640,58 @@ namespace Drivers::Graphics::IntelGPU {
return g_fbPitch;
}
void Reinitialize() {
if (!g_initialized || !g_mmioBase) return;
KernelLogStream(INFO, "IntelGPU") << "Reinitializing display after S3 resume";
// 1. Re-enable PCI memory space and bus mastering. S3 resets the PCI
// command register, so MMIO writes to the GPU are silently dropped
// until we re-enable these bits.
Pci::EnableBusMaster(g_gpuInfo.pciBus, g_gpuInfo.pciDevice, g_gpuInfo.pciFunction);
KernelLogStream(DEBUG, "IntelGPU") << "PCI memory space and bus mastering re-enabled";
// 2. Disable VGA plane (firmware may have re-enabled it during POST)
DisableVga();
// 3. Reprogram GTT entries (hardware lost all GTT state during S3)
uint64_t pageCount = (g_fbSize + 0xFFF) / 0x1000;
if (g_gpuGen >= 8) {
volatile uint64_t* gtt64 = (volatile uint64_t*)g_gttBase;
for (uint64_t i = 0; i < pageCount; i++) {
gtt64[i] = MakeGttPte64(g_fbPhysBase + i * 0x1000);
}
// Flush GTT writes by reading back the last entry
(void)gtt64[pageCount - 1];
} else {
volatile uint32_t* gtt32 = (volatile uint32_t*)g_gttBase;
for (uint64_t i = 0; i < pageCount; i++) {
gtt32[i] = MakeGttPte32(g_fbPhysBase + i * 0x1000);
}
// Flush GTT writes by reading back the last entry
(void)gtt32[pageCount - 1];
}
// 4. Re-enable the display pipe. After S3, the pipe may be off even
// though the firmware lit the backlight. Wait for it to become
// active before programming the display plane.
uint32_t pipeConf = ReadReg(PIPEACONF);
if (!(pipeConf & PIPECONF_ENABLE)) {
WriteReg(PIPEACONF, pipeConf | PIPECONF_ENABLE);
// Wait for pipe to become active (PIPECONF_STATE bit 30)
for (int i = 0; i < 100000; i++) {
if (ReadReg(PIPEACONF) & PIPECONF_STATE)
break;
asm volatile("pause");
}
}
// 5. Reprogram display plane to point at our GTT-mapped framebuffer
ProgramDisplayPlane();
KernelLogStream(OK, "IntelGPU") << "Display restored after S3 resume";
}
};
+3
View File
@@ -401,6 +401,9 @@ namespace Drivers::Graphics::IntelGPU {
// Check if an Intel GPU was found and initialized
bool IsInitialized();
// Restore display state after S3 resume (GTT entries, pipe, display plane)
void Reinitialize();
// Get detected GPU information
const GpuInfo* GetGpuInfo();
+25
View File
@@ -63,6 +63,31 @@ namespace Drivers::PS2 {
return g_DualChannel;
}
void Reinitialize() {
// Flush any stale data from the output buffer
FlushOutputBuffer();
// Re-enable both ports
SendCommand(CmdEnablePort1);
if (g_DualChannel) {
SendCommand(CmdEnablePort2);
}
// Re-enable interrupts and translation in the config byte
SendCommand(CmdReadConfig);
uint8_t config = ReadData();
config |= ConfigPort1Interrupt | ConfigPort1Translation;
if (g_DualChannel) {
config |= ConfigPort2Interrupt;
}
SendCommand(CmdWriteConfig);
SendData(config);
Kt::KernelLogStream(Kt::OK, "PS2") << "Controller re-enabled after S3 resume";
}
void Initialize() {
Kt::KernelLogStream(Kt::INFO, "PS2") << "Initializing PS/2 controller";
+5
View File
@@ -43,6 +43,11 @@ namespace Drivers::PS2 {
void Initialize();
// Lightweight re-enable after S3 resume. Skips the full self-test
// and port-test sequence (which can reset attached devices); just
// re-enables ports and interrupts so keyboard/mouse work again.
void Reinitialize();
void SendCommand(uint8_t command);
void SendData(uint8_t data);
uint8_t ReadData();
+24
View File
@@ -64,6 +64,30 @@ namespace Hal {
<< " max LVT=" << base::dec << (uint64_t)((version >> 16) & 0xFF);
}
void Reinitialize() {
if (!g_apicBase) return;
// Re-assert the APIC Global Enable in the IA32_APIC_BASE MSR.
// Some firmware clears bit 11 during S3 resume. If the global
// enable is off, all subsequent MMIO register writes (SVR, TPR,
// timer LVT, etc.) are silently ignored - meaning no interrupts
// are delivered, no timer fires, and the system appears frozen.
uint64_t msrValue = ReadMSR(MSR_APIC_BASE);
if (!(msrValue & (1ULL << 11))) {
msrValue |= (1ULL << 11);
WriteMSR(MSR_APIC_BASE, msrValue);
}
// Re-enable APIC software enable bit in SVR + set spurious vector
uint32_t svr = ReadRegister(REG_SPURIOUS);
svr |= (1 << 8);
svr = (svr & 0xFFFFFF00) | SPURIOUS_VECTOR;
WriteRegister(REG_SPURIOUS, svr);
// Accept all interrupts
WriteRegister(REG_TPR, 0);
}
void SendEOI() {
WriteRegister(REG_EOI, 0);
}
+6
View File
@@ -32,6 +32,12 @@ namespace Hal {
constexpr uint32_t MSR_APIC_BASE = 0x1B;
void Initialize(uint64_t apicBasePhys);
// Re-enable the Local APIC after S3 resume.
// The MMIO mapping and base address survive (they're in page tables / RAM).
// Only the SVR and TPR hardware registers need reprogramming.
void Reinitialize();
void SendEOI();
uint32_t GetId();
+30
View File
@@ -21,6 +21,11 @@ namespace Hal {
static MADT::InterruptSourceOverride g_overrides[MADT::ParsedMADT::MaxOverrides];
static int g_overrideCount = 0;
// Shadow copy of redirection entries for S3 resume replay
static constexpr uint32_t MAX_REDIR_ENTRIES = 24;
static uint64_t g_savedRedirEntries[MAX_REDIR_ENTRIES];
static uint32_t g_savedEntryCount = 0;
static uint32_t ReadRegister(uint32_t reg) {
// Write the register index to IOREGSEL (offset 0x00)
g_ioApicBase[0] = reg;
@@ -39,6 +44,13 @@ namespace Hal {
WriteRegister(regHigh, (uint32_t)(entry >> 32));
WriteRegister(regLow, (uint32_t)(entry & 0xFFFFFFFF));
// Shadow the entry for S3 resume replay
if (index < MAX_REDIR_ENTRIES) {
g_savedRedirEntries[index] = entry;
if (index >= g_savedEntryCount)
g_savedEntryCount = index + 1;
}
}
uint64_t GetRedirectionEntry(uint8_t index) {
@@ -106,6 +118,24 @@ namespace Hal {
<< " -> APIC " << (uint64_t)destinationApicId;
}
void Reinitialize() {
if (!g_ioApicBase || g_savedEntryCount == 0) return;
// Replay all saved redirection entries into the I/O APIC hardware.
// The hardware registers are lost during S3 but our shadow copy in
// RAM survives.
for (uint32_t i = 0; i < g_savedEntryCount; i++) {
uint32_t regLow = IOREDTBL_BASE + (i * 2);
uint32_t regHigh = IOREDTBL_BASE + (i * 2) + 1;
WriteRegister(regHigh, (uint32_t)(g_savedRedirEntries[i] >> 32));
WriteRegister(regLow, (uint32_t)(g_savedRedirEntries[i] & 0xFFFFFFFF));
}
KernelLogStream(DEBUG, "IOAPIC") << "Restored " << base::dec
<< (uint64_t)g_savedEntryCount << " redirection entries after S3 resume";
}
void Initialize(uint64_t ioApicBasePhys, uint32_t gsiBase,
MADT::InterruptSourceOverride* overrides, int overrideCount) {
g_ioApicBase = (volatile uint32_t*)Memory::HHDM(ioApicBasePhys);
+5
View File
@@ -29,6 +29,11 @@ namespace Hal {
void Initialize(uint64_t ioApicBasePhys, uint32_t gsiBase,
MADT::InterruptSourceOverride* overrides, int overrideCount);
// Re-apply all I/O APIC redirection entries after S3 resume.
// The I/O APIC hardware state is lost during S3; the saved routing
// table (in RAM) is replayed into the redirection registers.
void Reinitialize();
void SetRedirectionEntry(uint8_t irq, uint64_t entry);
uint64_t GetRedirectionEntry(uint8_t irq);
+17
View File
@@ -136,6 +136,23 @@ namespace Timekeeping {
<< " Hz periodic, initial count=" << (uint64_t)initialCount;
}
void ApicTimerReinitialize() {
if (g_ticksPerMs == 0) {
KernelLogStream(ERROR, "Timer") << "Cannot reinit APIC timer - not calibrated";
return;
}
// Reprogram the APIC timer registers (they were lost during S3).
// The calibrated g_ticksPerMs value is still valid (it's in RAM).
// The IRQ handler registration also survives (it's a function pointer array in RAM).
uint32_t lvt = (Hal::IRQ_VECTOR_BASE + Hal::IRQ_TIMER) | LVT_PERIODIC;
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16);
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, lvt);
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, g_ticksPerMs);
KernelLogStream(OK, "Timer") << "APIC timer restarted after S3 resume";
}
uint64_t GetTicks() {
return g_tickCount;
}
+5
View File
@@ -11,6 +11,11 @@ namespace Timekeeping {
// Initialize the APIC timer: calibrate against PIT, start periodic interrupts
void ApicTimerInitialize();
// Reinitialize the APIC timer after S3 resume using the previously
// calibrated tick rate. Skips PIT calibration and IRQ registration
// (both survive in RAM). Only reprograms the timer hardware registers.
void ApicTimerReinitialize();
// Get the monotonic tick count (increments on each timer interrupt)
uint64_t GetTicks();