feat: expanded ACPI support, initial support for S3 sleep
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
};
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
@@ -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();
|
||||
|
||||
};
|
||||
};
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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:
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -113,6 +113,9 @@ namespace Montauk {
|
||||
static constexpr uint64_t SYS_BTLIST = 87;
|
||||
static constexpr uint64_t SYS_BTINFO = 88;
|
||||
|
||||
// Power management
|
||||
static constexpr uint64_t SYS_SUSPEND = 89;
|
||||
|
||||
// Audio control commands (for SYS_AUDIOCTL)
|
||||
static constexpr int AUDIO_CTL_SET_VOLUME = 0;
|
||||
static constexpr int AUDIO_CTL_GET_VOLUME = 1;
|
||||
|
||||
@@ -99,6 +99,7 @@ struct DesktopState {
|
||||
SvgIcon icon_settings;
|
||||
SvgIcon icon_reboot;
|
||||
SvgIcon icon_shutdown;
|
||||
SvgIcon icon_sleep;
|
||||
SvgIcon icon_logout;
|
||||
|
||||
SvgIcon icon_procmgr;
|
||||
|
||||
@@ -265,6 +265,10 @@ namespace montauk {
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
inline int suspend() {
|
||||
return (int)syscall0(Montauk::SYS_SUSPEND);
|
||||
}
|
||||
|
||||
// Mouse
|
||||
inline void mouse_state(Montauk::MouseState* out) { syscall1(Montauk::SYS_MOUSESTATE, (uint64_t)out); }
|
||||
inline void set_mouse_bounds(int32_t maxX, int32_t maxY) {
|
||||
|
||||
@@ -172,4 +172,5 @@ void open_settings(DesktopState* ds);
|
||||
void open_reboot_dialog(DesktopState* ds);
|
||||
void open_wordprocessor(DesktopState* ds);
|
||||
void open_shutdown_dialog(DesktopState* ds);
|
||||
void open_sleep_dialog(DesktopState* ds);
|
||||
void desktop_poll_external_windows(DesktopState* ds);
|
||||
|
||||
@@ -224,7 +224,7 @@ void gui::desktop_compose(DesktopState* ds) {
|
||||
if (ds->ctx_menu_open) {
|
||||
static constexpr int CTX_MENU_W = 180;
|
||||
static constexpr int CTX_ITEM_H = 36;
|
||||
static constexpr int CTX_ITEM_COUNT = 5;
|
||||
static constexpr int CTX_ITEM_COUNT = 6;
|
||||
int cmx = ds->ctx_menu_x;
|
||||
int cmy = ds->ctx_menu_y;
|
||||
int cmh = CTX_ITEM_H * CTX_ITEM_COUNT + 8;
|
||||
@@ -242,6 +242,7 @@ void gui::desktop_compose(DesktopState* ds) {
|
||||
{ "Terminal", &ds->icon_terminal },
|
||||
{ "Files", &ds->icon_filemanager },
|
||||
{ "About", &ds->icon_settings },
|
||||
{ "Sleep", &ds->icon_sleep },
|
||||
{ "Reboot", &ds->icon_reboot },
|
||||
{ "Shutdown", &ds->icon_shutdown },
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* dialogs.cpp
|
||||
* Reboot and shutdown confirmation dialogs
|
||||
* Reboot, shutdown, and sleep confirmation dialogs
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
@@ -234,3 +234,131 @@ void open_shutdown_dialog(DesktopState* ds) {
|
||||
win->on_key = shutdown_dialog_on_key;
|
||||
win->on_close = shutdown_dialog_on_close;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Sleep (S3 Suspend) Dialog
|
||||
// ============================================================================
|
||||
|
||||
struct SleepDialogState {
|
||||
DesktopState* ds;
|
||||
int btn_w, btn_h, btn_y, sleep_x, cancel_x;
|
||||
bool hover_sleep, hover_cancel;
|
||||
};
|
||||
|
||||
static void sleep_dialog_on_draw(Window* win, Framebuffer& fb) {
|
||||
SleepDialogState* ss = (SleepDialogState*)win->app_data;
|
||||
if (!ss) return;
|
||||
|
||||
Canvas c(win);
|
||||
c.fill(colors::WINDOW_BG);
|
||||
|
||||
const char* msg = "Suspend the system?";
|
||||
int tw = text_width(msg);
|
||||
c.text((c.w - tw) / 2, 30, msg, colors::TEXT_COLOR);
|
||||
|
||||
int btn_w = 100;
|
||||
int btn_h = 32;
|
||||
int btn_y = c.h - btn_h - 20;
|
||||
int gap = 20;
|
||||
int total_w = btn_w * 2 + gap;
|
||||
int bx = (c.w - total_w) / 2;
|
||||
ss->btn_w = btn_w;
|
||||
ss->btn_h = btn_h;
|
||||
ss->btn_y = btn_y;
|
||||
ss->sleep_x = bx;
|
||||
ss->cancel_x = bx + btn_w + gap;
|
||||
|
||||
Color sleep_bg = ss->hover_sleep
|
||||
? Color::from_rgb(0x44, 0x77, 0xDD)
|
||||
: Color::from_rgb(0x33, 0x66, 0xCC);
|
||||
c.button(ss->sleep_x, btn_y, btn_w, btn_h, "Sleep", sleep_bg, colors::WHITE, 4);
|
||||
|
||||
Color cancel_bg = ss->hover_cancel
|
||||
? Color::from_rgb(0x99, 0x99, 0x99)
|
||||
: Color::from_rgb(0x88, 0x88, 0x88);
|
||||
c.button(ss->cancel_x, btn_y, btn_w, btn_h, "Cancel", cancel_bg, colors::WHITE, 4);
|
||||
}
|
||||
|
||||
static void sleep_dialog_on_mouse(Window* win, MouseEvent& ev) {
|
||||
SleepDialogState* ss = (SleepDialogState*)win->app_data;
|
||||
if (!ss) return;
|
||||
|
||||
Rect cr = win->content_rect();
|
||||
int lx = ev.x - cr.x;
|
||||
int ly = ev.y - cr.y;
|
||||
|
||||
Rect sb = {ss->sleep_x, ss->btn_y, ss->btn_w, ss->btn_h};
|
||||
Rect cb = {ss->cancel_x, ss->btn_y, ss->btn_w, ss->btn_h};
|
||||
ss->hover_sleep = sb.contains(lx, ly);
|
||||
ss->hover_cancel = cb.contains(lx, ly);
|
||||
|
||||
if (ev.left_pressed()) {
|
||||
if (ss->hover_sleep) {
|
||||
montauk::suspend();
|
||||
// If suspend returns (resume from S3), close the dialog
|
||||
for (int i = 0; i < ss->ds->window_count; i++) {
|
||||
if (ss->ds->windows[i].app_data == ss) {
|
||||
desktop_close_window(ss->ds, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ss->hover_cancel) {
|
||||
for (int i = 0; i < ss->ds->window_count; i++) {
|
||||
if (ss->ds->windows[i].app_data == ss) {
|
||||
desktop_close_window(ss->ds, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void sleep_dialog_on_key(Window* win, const Montauk::KeyEvent& key) {
|
||||
SleepDialogState* ss = (SleepDialogState*)win->app_data;
|
||||
if (!ss || !key.pressed) return;
|
||||
|
||||
if (key.ascii == '\n' || key.ascii == '\r') {
|
||||
montauk::suspend();
|
||||
// If suspend returns, close the dialog
|
||||
for (int i = 0; i < ss->ds->window_count; i++) {
|
||||
if (ss->ds->windows[i].app_data == ss) {
|
||||
desktop_close_window(ss->ds, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key.scancode == 0x01) { // Escape
|
||||
for (int i = 0; i < ss->ds->window_count; i++) {
|
||||
if (ss->ds->windows[i].app_data == ss) {
|
||||
desktop_close_window(ss->ds, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void sleep_dialog_on_close(Window* win) {
|
||||
if (win->app_data) {
|
||||
montauk::mfree(win->app_data);
|
||||
win->app_data = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void open_sleep_dialog(DesktopState* ds) {
|
||||
int wx = (ds->screen_w - 300) / 2;
|
||||
int wy = (ds->screen_h - 150) / 2;
|
||||
int idx = desktop_create_window(ds, "Sleep", wx, wy, 300, 150);
|
||||
if (idx < 0) return;
|
||||
|
||||
Window* win = &ds->windows[idx];
|
||||
SleepDialogState* ss = (SleepDialogState*)montauk::malloc(sizeof(SleepDialogState));
|
||||
montauk::memset(ss, 0, sizeof(SleepDialogState));
|
||||
ss->ds = ds;
|
||||
|
||||
win->app_data = ss;
|
||||
win->on_draw = sleep_dialog_on_draw;
|
||||
win->on_mouse = sleep_dialog_on_mouse;
|
||||
win->on_key = sleep_dialog_on_key;
|
||||
win->on_close = sleep_dialog_on_close;
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
if (left_pressed) {
|
||||
static constexpr int CTX_MENU_W = 180;
|
||||
static constexpr int CTX_ITEM_H = 36;
|
||||
static constexpr int CTX_ITEM_COUNT = 5;
|
||||
static constexpr int CTX_ITEM_COUNT = 6;
|
||||
int cmx = ds->ctx_menu_x;
|
||||
int cmy = ds->ctx_menu_y;
|
||||
int cmh = CTX_ITEM_H * CTX_ITEM_COUNT + 8;
|
||||
@@ -170,8 +170,9 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
case 0: open_terminal(ds); break;
|
||||
case 1: open_filemanager(ds); break;
|
||||
case 2: open_settings(ds); break;
|
||||
case 3: open_reboot_dialog(ds); break;
|
||||
case 4: open_shutdown_dialog(ds); break;
|
||||
case 3: open_sleep_dialog(ds); break;
|
||||
case 4: open_reboot_dialog(ds); break;
|
||||
case 5: open_shutdown_dialog(ds); break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -357,6 +358,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
case 15: open_wordprocessor(ds); break;
|
||||
case 16: montauk::exit(0); break; // Log Out
|
||||
case 17: lock_screen(ds); break; // Lock Screen
|
||||
case 18: open_sleep_dialog(ds); break; // Sleep
|
||||
}
|
||||
}
|
||||
ds->app_menu_open = false;
|
||||
|
||||
@@ -173,6 +173,7 @@ void desktop_build_menu(DesktopState* ds) {
|
||||
menu_add_embedded("Settings", 11, &ds->icon_settings);
|
||||
menu_add_embedded("Lock Screen", 17, &ds->icon_lock);
|
||||
menu_add_embedded("Log Out", 16, &ds->icon_logout);
|
||||
menu_add_embedded("Sleep", 18, &ds->icon_sleep);
|
||||
menu_add_embedded("Reboot", 12, &ds->icon_reboot);
|
||||
menu_add_embedded("Shutdown", 14, &ds->icon_shutdown);
|
||||
}
|
||||
@@ -228,6 +229,7 @@ void gui::desktop_init(DesktopState* ds) {
|
||||
ds->icon_settings = svg_load("0:/icons/help-about.svg", 20, 20, defColor);
|
||||
ds->icon_reboot = svg_load("0:/icons/system-reboot.svg", 20, 20, defColor);
|
||||
ds->icon_shutdown = svg_load("0:/icons/system-shutdown.svg", 20, 20, defColor);
|
||||
ds->icon_sleep = svg_load("0:/icons/sleep.svg", 20, 20, defColor);
|
||||
ds->icon_logout = svg_load("0:/icons/gnome-logout.svg", 20, 20, defColor);
|
||||
|
||||
ds->icon_procmgr = svg_load("0:/icons/system-monitor.svg", 20, 20, defColor);
|
||||
@@ -508,8 +510,8 @@ void gui::desktop_run(DesktopState* ds) {
|
||||
desktop_compose(ds);
|
||||
ds->fb.flip();
|
||||
|
||||
// Target ~60fps
|
||||
montauk::sleep_ms(16);
|
||||
// Yield to scheduler without artificial frame cap
|
||||
montauk::sleep_ms(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ ICONS=(
|
||||
"status/symbolic/audio-volume-high-symbolic.svg"
|
||||
"apps/scalable/gnome-logout.svg"
|
||||
"apps/scalable/lock.svg"
|
||||
"apps/scalable/sleep.svg"
|
||||
)
|
||||
|
||||
copied=0
|
||||
|
||||
Reference in New Issue
Block a user