feat: S3 sleep, PDF viewer graphics rendering, live user environment

This commit is contained in:
2026-03-15 18:46:54 +01:00
parent 64c26f4288
commit ffffb6a0c5
26 changed files with 1541 additions and 248 deletions
+110 -22
View File
@@ -17,14 +17,22 @@ using namespace Kt;
namespace Hal { namespace Hal {
namespace AML { namespace AML {
// ── Global instance ───────────────────────────────────────────── // ============================================================================
// Global instance
// ============================================================================
static Interpreter g_interpreter; static Interpreter g_interpreter;
Interpreter& GetInterpreter() { Interpreter& GetInterpreter() {
return g_interpreter; return g_interpreter;
} }
// ── Helper: is a byte a lead name character? ──────────────────── // ============================================================================
// Helper: is a byte a lead name character?
// ============================================================================
static bool IsLeadNameChar(uint8_t c) { static bool IsLeadNameChar(uint8_t c) {
return (c >= 'A' && c <= 'Z') || c == '_'; return (c >= 'A' && c <= 'Z') || c == '_';
} }
@@ -33,11 +41,19 @@ namespace Hal {
return IsLeadNameChar(c) || (c >= '0' && c <= '9'); return IsLeadNameChar(c) || (c >= '0' && c <= '9');
} }
// ── Constructor ───────────────────────────────────────────────── // ============================================================================
// Constructor
// ============================================================================
Interpreter::Interpreter() Interpreter::Interpreter()
: m_dsdt(nullptr), m_dsdtLength(0), m_initialized(false) {} : m_dsdt(nullptr), m_dsdtLength(0), m_initialized(false) {}
// ── PkgLength decoding ────────────────────────────────────────── // ============================================================================
// PkgLength decoding
// ============================================================================
uint32_t Interpreter::DecodePkgLength(const uint8_t* aml, uint32_t* pos) { uint32_t Interpreter::DecodePkgLength(const uint8_t* aml, uint32_t* pos) {
uint8_t lead = aml[*pos]; uint8_t lead = aml[*pos];
uint32_t byteCount = (lead >> 6) & 0x03; uint32_t byteCount = (lead >> 6) & 0x03;
@@ -58,7 +74,11 @@ namespace Hal {
return length; return length;
} }
// ── Integer decoding (data objects) ───────────────────────────── // ============================================================================
// Integer decoding (data objects)
// ============================================================================
uint64_t Interpreter::DecodeInteger(const uint8_t* aml, uint32_t* pos) { uint64_t Interpreter::DecodeInteger(const uint8_t* aml, uint32_t* pos) {
uint8_t op = aml[*pos]; uint8_t op = aml[*pos];
@@ -107,7 +127,11 @@ namespace Hal {
} }
} }
// ── NameSeg reading ───────────────────────────────────────────── // ============================================================================
// NameSeg reading
// ============================================================================
void Interpreter::ReadNameSeg(const uint8_t* aml, uint32_t* pos, char* outSeg) { void Interpreter::ReadNameSeg(const uint8_t* aml, uint32_t* pos, char* outSeg) {
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
outSeg[i] = (char)aml[*pos + i]; outSeg[i] = (char)aml[*pos + i];
@@ -116,7 +140,11 @@ namespace Hal {
*pos += 4; *pos += 4;
} }
// ── NameString reading ────────────────────────────────────────── // ============================================================================
// NameString reading
// ============================================================================
// Reads a NameString (which may include root prefix, parent prefixes, // Reads a NameString (which may include root prefix, parent prefixes,
// dual/multi name prefix) and produces an absolute path. // dual/multi name prefix) and produces an absolute path.
int Interpreter::ReadNameString(const uint8_t* aml, uint32_t* pos, int32_t scopeNode, int Interpreter::ReadNameString(const uint8_t* aml, uint32_t* pos, int32_t scopeNode,
@@ -195,7 +223,11 @@ namespace Hal {
return pathPos; return pathPos;
} }
// ── LoadTable ─────────────────────────────────────────────────── // ============================================================================
// LoadTable
// ============================================================================
bool Interpreter::LoadTable(void* tableData) { bool Interpreter::LoadTable(void* tableData) {
auto* header = (ACPI::CommonSDTHeader*)tableData; auto* header = (ACPI::CommonSDTHeader*)tableData;
@@ -231,7 +263,11 @@ namespace Hal {
return result; return result;
} }
// ── ParseBlock ────────────────────────────────────────────────── // ============================================================================
// ParseBlock
// ============================================================================
// Parse a block of AML opcodes, creating namespace objects. // Parse a block of AML opcodes, creating namespace objects.
bool Interpreter::ParseBlock(const uint8_t* aml, uint32_t offset, uint32_t endOffset, bool Interpreter::ParseBlock(const uint8_t* aml, uint32_t offset, uint32_t endOffset,
int32_t scopeNode) { int32_t scopeNode) {
@@ -332,7 +368,11 @@ namespace Hal {
return true; return true;
} }
// ── ParseNamedObject ──────────────────────────────────────────── // ============================================================================
// ParseNamedObject
// ============================================================================
bool Interpreter::ParseNamedObject(const uint8_t* aml, uint32_t* pos, uint32_t endOffset, bool Interpreter::ParseNamedObject(const uint8_t* aml, uint32_t* pos, uint32_t endOffset,
int32_t scopeNode) { int32_t scopeNode) {
uint8_t op = aml[*pos]; uint8_t op = aml[*pos];
@@ -484,7 +524,11 @@ namespace Hal {
return true; return true;
} }
// ── ParseExtendedOp ───────────────────────────────────────────── // ============================================================================
// ParseExtendedOp
// ============================================================================
bool Interpreter::ParseExtendedOp(const uint8_t* aml, uint32_t* pos, uint32_t endOffset, bool Interpreter::ParseExtendedOp(const uint8_t* aml, uint32_t* pos, uint32_t endOffset,
int32_t scopeNode) { int32_t scopeNode) {
(*pos)++; // skip ExtOpPrefix (*pos)++; // skip ExtOpPrefix
@@ -743,7 +787,11 @@ namespace Hal {
} }
} }
// ── EvaluateObject ────────────────────────────────────────────── // ============================================================================
// EvaluateObject
// ============================================================================
bool Interpreter::EvaluateObject(const char* path, Object& result) { bool Interpreter::EvaluateObject(const char* path, Object& result) {
int32_t node = m_ns.FindNode(path); int32_t node = m_ns.FindNode(path);
if (node < 0) return false; if (node < 0) return false;
@@ -772,7 +820,11 @@ namespace Hal {
return true; return true;
} }
// ── EvaluateMethod ────────────────────────────────────────────── // ============================================================================
// EvaluateMethod
// ============================================================================
bool Interpreter::EvaluateMethod(const char* path, const Object* args, int argCount, bool Interpreter::EvaluateMethod(const char* path, const Object* args, int argCount,
Object& result) { Object& result) {
int32_t node = m_ns.FindNode(path); int32_t node = m_ns.FindNode(path);
@@ -812,7 +864,11 @@ namespace Hal {
return ok; return ok;
} }
// ── ExecuteBlock ──────────────────────────────────────────────── // ============================================================================
// ExecuteBlock
// ============================================================================
bool Interpreter::ExecuteBlock(ExecContext& ctx, uint32_t offset, uint32_t endOffset) { bool Interpreter::ExecuteBlock(ExecContext& ctx, uint32_t offset, uint32_t endOffset) {
uint32_t pos = offset; uint32_t pos = offset;
@@ -824,7 +880,11 @@ namespace Hal {
return true; return true;
} }
// ── ExecuteOpcode ─────────────────────────────────────────────── // ============================================================================
// ExecuteOpcode
// ============================================================================
bool Interpreter::ExecuteOpcode(ExecContext& ctx, uint32_t* pos, uint32_t endOffset) { bool Interpreter::ExecuteOpcode(ExecContext& ctx, uint32_t* pos, uint32_t endOffset) {
if (*pos >= endOffset) return true; if (*pos >= endOffset) return true;
@@ -1015,7 +1075,11 @@ namespace Hal {
return EvalTerm(ctx, pos, endOffset, discard); return EvalTerm(ctx, pos, endOffset, discard);
} }
// ── EvalTerm ──────────────────────────────────────────────────── // ============================================================================
// EvalTerm
// ============================================================================
// Evaluate an AML term that produces a value. // Evaluate an AML term that produces a value.
bool Interpreter::EvalTerm(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, bool Interpreter::EvalTerm(ExecContext& ctx, uint32_t* pos, uint32_t endOffset,
Object& result) { Object& result) {
@@ -1412,7 +1476,11 @@ namespace Hal {
return true; return true;
} }
// ── EvalTarget ────────────────────────────────────────────────── // ============================================================================
// EvalTarget
// ============================================================================
bool Interpreter::EvalTarget(ExecContext& ctx, uint32_t* pos, bool Interpreter::EvalTarget(ExecContext& ctx, uint32_t* pos,
int32_t& nodeIndex, bool& isLocal, int& localIdx, int32_t& nodeIndex, bool& isLocal, int& localIdx,
bool& isArg, int& argIdx) { bool& isArg, int& argIdx) {
@@ -1464,7 +1532,11 @@ namespace Hal {
return true; return true;
} }
// ── StoreToTarget ─────────────────────────────────────────────── // ============================================================================
// StoreToTarget
// ============================================================================
void Interpreter::StoreToTarget(ExecContext& ctx, const Object& value, void Interpreter::StoreToTarget(ExecContext& ctx, const Object& value,
int32_t nodeIndex, bool isLocal, int localIdx, int32_t nodeIndex, bool isLocal, int localIdx,
bool isArg, int argIdx) { bool isArg, int argIdx) {
@@ -1484,7 +1556,11 @@ namespace Hal {
} }
} }
// ── ReadField ─────────────────────────────────────────────────── // ============================================================================
// ReadField
// ============================================================================
bool Interpreter::ReadField(int32_t nodeIndex, uint64_t& value) { bool Interpreter::ReadField(int32_t nodeIndex, uint64_t& value) {
auto* node = m_ns.GetNode(nodeIndex); auto* node = m_ns.GetNode(nodeIndex);
if (!node || node->Obj.Type != ObjectType::Field) return false; if (!node || node->Obj.Type != ObjectType::Field) return false;
@@ -1505,7 +1581,11 @@ namespace Hal {
return ReadRegion(regionNode->Obj.Region.Space, byteAddr, bitLen + bitShift, value); return ReadRegion(regionNode->Obj.Region.Space, byteAddr, bitLen + bitShift, value);
} }
// ── WriteField ────────────────────────────────────────────────── // ============================================================================
// WriteField
// ============================================================================
bool Interpreter::WriteField(int32_t nodeIndex, uint64_t value) { bool Interpreter::WriteField(int32_t nodeIndex, uint64_t value) {
auto* node = m_ns.GetNode(nodeIndex); auto* node = m_ns.GetNode(nodeIndex);
if (!node || node->Obj.Type != ObjectType::Field) return false; if (!node || node->Obj.Type != ObjectType::Field) return false;
@@ -1534,7 +1614,11 @@ namespace Hal {
return WriteRegion(regionNode->Obj.Region.Space, byteAddr, bitLen + bitShift, value); return WriteRegion(regionNode->Obj.Region.Space, byteAddr, bitLen + bitShift, value);
} }
// ── ReadRegion ────────────────────────────────────────────────── // ============================================================================
// ReadRegion
// ============================================================================
bool Interpreter::ReadRegion(RegionSpace space, uint64_t address, uint32_t bitWidth, bool Interpreter::ReadRegion(RegionSpace space, uint64_t address, uint32_t bitWidth,
uint64_t& value) { uint64_t& value) {
value = 0; value = 0;
@@ -1576,7 +1660,11 @@ namespace Hal {
return false; return false;
} }
// ── WriteRegion ───────────────────────────────────────────────── // ============================================================================
// WriteRegion
// ============================================================================
bool Interpreter::WriteRegion(RegionSpace space, uint64_t address, uint32_t bitWidth, bool Interpreter::WriteRegion(RegionSpace space, uint64_t address, uint32_t bitWidth,
uint64_t value) { uint64_t value) {
uint32_t accessBytes = (bitWidth + 7) / 8; uint32_t accessBytes = (bitWidth + 7) / 8;
+48 -10
View File
@@ -12,7 +12,11 @@
namespace Hal { namespace Hal {
namespace AML { namespace AML {
// ── Extended AML Opcodes ──────────────────────────────────────── // ============================================================================
// Extended AML Opcodes
// ============================================================================
// Single-byte opcodes // Single-byte opcodes
static constexpr uint8_t ZeroOp = 0x00; static constexpr uint8_t ZeroOp = 0x00;
static constexpr uint8_t OneOp = 0x01; static constexpr uint8_t OneOp = 0x01;
@@ -93,11 +97,19 @@ namespace Hal {
static constexpr uint8_t ToHexStringOp = 0x98; static constexpr uint8_t ToHexStringOp = 0x98;
static constexpr uint8_t ToDecimalStringOp = 0x97; static constexpr uint8_t ToDecimalStringOp = 0x97;
// ── Interpreter Configuration ─────────────────────────────────── // ============================================================================
// Interpreter Configuration
// ============================================================================
static constexpr int MaxCallDepth = 16; static constexpr int MaxCallDepth = 16;
static constexpr int MaxLoopIterations = 1024; static constexpr int MaxLoopIterations = 1024;
// ── Interpreter ───────────────────────────────────────────────── // ============================================================================
// Interpreter
// ============================================================================
class Interpreter { class Interpreter {
public: public:
Interpreter(); Interpreter();
@@ -127,7 +139,9 @@ namespace Hal {
bool IsInitialized() const { return m_initialized; } bool IsInitialized() const { return m_initialized; }
private: private:
// ── Parsing (table load) ──────────────────────────────────── // ============================================================================
// Parsing (table load)
// ============================================================================
bool ParseBlock(const uint8_t* aml, uint32_t offset, uint32_t endOffset, bool ParseBlock(const uint8_t* aml, uint32_t offset, uint32_t endOffset,
int32_t scopeNode); int32_t scopeNode);
@@ -137,7 +151,11 @@ namespace Hal {
bool ParseExtendedOp(const uint8_t* aml, uint32_t* pos, uint32_t endOffset, bool ParseExtendedOp(const uint8_t* aml, uint32_t* pos, uint32_t endOffset,
int32_t scopeNode); int32_t scopeNode);
// ── Name resolution ───────────────────────────────────────── // ============================================================================
// Name resolution
// ============================================================================
// Read a NameString from AML and produce an absolute path. // Read a NameString from AML and produce an absolute path.
// Advances *pos past the name. // Advances *pos past the name.
int ReadNameString(const uint8_t* aml, uint32_t* pos, int32_t scopeNode, int ReadNameString(const uint8_t* aml, uint32_t* pos, int32_t scopeNode,
@@ -146,11 +164,19 @@ namespace Hal {
// Read a single 4-char NameSeg from AML. Advances *pos. // Read a single 4-char NameSeg from AML. Advances *pos.
void ReadNameSeg(const uint8_t* aml, uint32_t* pos, char* outSeg); void ReadNameSeg(const uint8_t* aml, uint32_t* pos, char* outSeg);
// ── Value decoding ────────────────────────────────────────── // ============================================================================
// Value decoding
// ============================================================================
uint32_t DecodePkgLength(const uint8_t* aml, uint32_t* pos); uint32_t DecodePkgLength(const uint8_t* aml, uint32_t* pos);
uint64_t DecodeInteger(const uint8_t* aml, uint32_t* pos); uint64_t DecodeInteger(const uint8_t* aml, uint32_t* pos);
// ── Method execution ──────────────────────────────────────── // ============================================================================
// Method execution
// ============================================================================
struct ExecContext { struct ExecContext {
const uint8_t* Aml; const uint8_t* Aml;
uint32_t AmlBase; // start of the block within the table uint32_t AmlBase; // start of the block within the table
@@ -182,18 +208,30 @@ namespace Hal {
int32_t nodeIndex, bool isLocal, int localIdx, int32_t nodeIndex, bool isLocal, int localIdx,
bool isArg, int argIdx); bool isArg, int argIdx);
// ── Field I/O ─────────────────────────────────────────────── // ============================================================================
// Field I/O
// ============================================================================
bool ReadRegion(RegionSpace space, uint64_t address, uint32_t bitWidth, uint64_t& value); 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); bool WriteRegion(RegionSpace space, uint64_t address, uint32_t bitWidth, uint64_t value);
// ── State ─────────────────────────────────────────────────── // ============================================================================
// State
// ============================================================================
Namespace m_ns; Namespace m_ns;
const uint8_t* m_dsdt; const uint8_t* m_dsdt;
uint32_t m_dsdtLength; uint32_t m_dsdtLength;
bool m_initialized; bool m_initialized;
}; };
// ── Global interpreter instance ───────────────────────────────── // ============================================================================
// Global interpreter instance
// ============================================================================
Interpreter& GetInterpreter(); Interpreter& GetInterpreter();
}; };
+30 -6
View File
@@ -11,7 +11,11 @@
namespace Hal { namespace Hal {
namespace AML { namespace AML {
// ── AML Object Types ──────────────────────────────────────────── // ============================================================================
// AML Object Types
// ============================================================================
enum class ObjectType : uint8_t { enum class ObjectType : uint8_t {
None = 0, None = 0,
Integer, Integer,
@@ -29,7 +33,11 @@ namespace Hal {
BufferField, BufferField,
}; };
// ── Region address spaces (OperationRegion) ───────────────────── // ============================================================================
// Region address spaces (OperationRegion)
// ============================================================================
enum class RegionSpace : uint8_t { enum class RegionSpace : uint8_t {
SystemMemory = 0x00, SystemMemory = 0x00,
SystemIO = 0x01, SystemIO = 0x01,
@@ -40,7 +48,11 @@ namespace Hal {
PciBarTarget = 0x06, PciBarTarget = 0x06,
}; };
// ── Constants ─────────────────────────────────────────────────── // ============================================================================
// Constants
// ============================================================================
static constexpr int MaxNameSegLen = 4; static constexpr int MaxNameSegLen = 4;
static constexpr int MaxPathDepth = 16; static constexpr int MaxPathDepth = 16;
static constexpr int MaxChildren = 32; static constexpr int MaxChildren = 32;
@@ -51,7 +63,11 @@ namespace Hal {
static constexpr int MaxMethodLocals = 8; static constexpr int MaxMethodLocals = 8;
static constexpr int MaxNamespaceNodes = 256; static constexpr int MaxNamespaceNodes = 256;
// ── AML Object ────────────────────────────────────────────────── // ============================================================================
// AML Object
// ============================================================================
// Tagged union representing any AML value. Kept small for kernel use. // Tagged union representing any AML value. Kept small for kernel use.
struct Object { struct Object {
ObjectType Type = ObjectType::None; ObjectType Type = ObjectType::None;
@@ -99,7 +115,11 @@ namespace Hal {
Object() : Type(ObjectType::None), Integer(0) {} Object() : Type(ObjectType::None), Integer(0) {}
}; };
// ── Namespace Node ────────────────────────────────────────────── // ============================================================================
// Namespace Node
// ============================================================================
// Each node has a 4-char name segment and an associated object. // Each node has a 4-char name segment and an associated object.
struct NamespaceNode { struct NamespaceNode {
char Name[MaxNameSegLen + 1]; // null-terminated 4-char segment char Name[MaxNameSegLen + 1]; // null-terminated 4-char segment
@@ -118,7 +138,11 @@ namespace Hal {
} }
}; };
// ── Namespace ─────────────────────────────────────────────────── // ============================================================================
// Namespace
// ============================================================================
// Flat array of nodes forming a tree via parent/child indices. // Flat array of nodes forming a tree via parent/child indices.
class Namespace { class Namespace {
public: public:
+15 -3
View File
@@ -15,7 +15,11 @@ using namespace Kt;
namespace Hal { namespace Hal {
namespace AML { namespace AML {
// ── Legacy S5 extraction (brute-force scan) ───────────────────── // ============================================================================
// Legacy S5 extraction (brute-force scan)
// ============================================================================
// Kept for fast S5 extraction during early boot before the full // Kept for fast S5 extraction during early boot before the full
// interpreter is loaded. // interpreter is loaded.
@@ -158,7 +162,11 @@ namespace Hal {
return result; return result;
} }
// ── Generalized brute-force sleep state scanner ────────────────── // ============================================================================
// Generalized brute-force sleep state scanner
// ============================================================================
SleepObject FindSleepState(void* dsdtData, int state) { SleepObject FindSleepState(void* dsdtData, int state) {
SleepObject result{}; SleepObject result{};
result.Valid = false; result.Valid = false;
@@ -226,7 +234,11 @@ namespace Hal {
return result; return result;
} }
// ── Full interpreter initialization ───────────────────────────── // ============================================================================
// Full interpreter initialization
// ============================================================================
void InitializeInterpreter(void* dsdtData) { void InitializeInterpreter(void* dsdtData) {
auto& interp = GetInterpreter(); auto& interp = GetInterpreter();
if (!interp.LoadTable(dsdtData)) { if (!interp.LoadTable(dsdtData)) {
+16 -4
View File
@@ -9,14 +9,22 @@
namespace Hal { namespace Hal {
namespace AML { namespace AML {
// ── Small Resource Tags (bits 6:3 of the tag byte) ────────────── // ============================================================================
// Small Resource Tags (bits 6:3 of the tag byte)
// ============================================================================
static constexpr uint8_t SmallIrqTag = 0x04; // IRQ descriptor static constexpr uint8_t SmallIrqTag = 0x04; // IRQ descriptor
static constexpr uint8_t SmallDmaTag = 0x05; // DMA descriptor static constexpr uint8_t SmallDmaTag = 0x05; // DMA descriptor
static constexpr uint8_t SmallIoPortTag = 0x08; // I/O port 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 SmallFixedIoTag = 0x09; // Fixed I/O port descriptor
static constexpr uint8_t SmallEndTag = 0x0F; // End tag static constexpr uint8_t SmallEndTag = 0x0F; // End tag
// ── Large Resource Tags (byte following the large tag prefix) ─── // ============================================================================
// Large Resource Tags (byte following the large tag prefix)
// ============================================================================
static constexpr uint8_t LargeMemory24Tag = 0x01; static constexpr uint8_t LargeMemory24Tag = 0x01;
static constexpr uint8_t LargeVendorTag = 0x04; static constexpr uint8_t LargeVendorTag = 0x04;
static constexpr uint8_t LargeMemory32Tag = 0x05; static constexpr uint8_t LargeMemory32Tag = 0x05;
@@ -63,7 +71,9 @@ namespace Hal {
break; break;
if (tag & 0x80) { if (tag & 0x80) {
// ── Large resource descriptor ─────────────────────── // ============================================================================
// Large resource descriptor
// ============================================================================
uint8_t largeType = tag & 0x7F; uint8_t largeType = tag & 0x7F;
if (pos + 3 > length) break; if (pos + 3 > length) break;
uint16_t resLen = Read16(&data[pos + 1]); uint16_t resLen = Read16(&data[pos + 1]);
@@ -154,7 +164,9 @@ namespace Hal {
pos = dataEnd; pos = dataEnd;
} else { } else {
// ── Small resource descriptor ─────────────────────── // ============================================================================
// Small resource descriptor
// ============================================================================
uint8_t smallType = (tag >> 3) & 0x0F; uint8_t smallType = (tag >> 3) & 0x0F;
uint8_t resLen = tag & 0x07; uint8_t resLen = tag & 0x07;
uint32_t dataStart = pos + 1; uint32_t dataStart = pos + 1;
+15 -3
View File
@@ -10,7 +10,11 @@
namespace Hal { namespace Hal {
namespace AML { namespace AML {
// ── Resource Types ────────────────────────────────────────────── // ============================================================================
// Resource Types
// ============================================================================
enum class ResourceType : uint8_t { enum class ResourceType : uint8_t {
None = 0, None = 0,
Irq, Irq,
@@ -27,7 +31,11 @@ namespace Hal {
GpioConnection, GpioConnection,
}; };
// ── Single Resource Descriptor ────────────────────────────────── // ============================================================================
// Single Resource Descriptor
// ============================================================================
struct ResourceDescriptor { struct ResourceDescriptor {
ResourceType Type; ResourceType Type;
@@ -82,7 +90,11 @@ namespace Hal {
} }
}; };
// ── Parsed Resource List ──────────────────────────────────────── // ============================================================================
// Parsed Resource List
// ============================================================================
static constexpr int MaxResources = 16; static constexpr int MaxResources = 16;
struct ResourceList { struct ResourceList {
+50 -10
View File
@@ -16,7 +16,11 @@ using namespace Kt;
namespace Hal { namespace Hal {
namespace AcpiDevices { namespace AcpiDevices {
// ── EISAID decoding ───────────────────────────────────────────── // ============================================================================
// EISAID decoding
// ============================================================================
// ACPI encodes PNP IDs as compressed 32-bit EISAIDs. // ACPI encodes PNP IDs as compressed 32-bit EISAIDs.
static void DecodeEisaId(uint32_t id, char* out) { static void DecodeEisaId(uint32_t id, char* out) {
// EISA ID encoding: // EISA ID encoding:
@@ -35,7 +39,11 @@ namespace Hal {
out[7] = '\0'; out[7] = '\0';
} }
// ── String comparison ─────────────────────────────────────────── // ============================================================================
// String comparison
// ============================================================================
static bool StrEqual(const char* a, const char* b) { static bool StrEqual(const char* a, const char* b) {
while (*a && *b) { while (*a && *b) {
if (*a != *b) return false; if (*a != *b) return false;
@@ -53,7 +61,11 @@ namespace Hal {
dst[i] = '\0'; dst[i] = '\0';
} }
// ── DeviceList methods ────────────────────────────────────────── // ============================================================================
// DeviceList methods
// ============================================================================
const DeviceInfo* DeviceList::FindByHid(const char* hid) const { const DeviceInfo* DeviceList::FindByHid(const char* hid) const {
for (int i = 0; i < Count; i++) { for (int i = 0; i < Count; i++) {
if (StrEqual(Devices[i].HardwareId, hid)) if (StrEqual(Devices[i].HardwareId, hid))
@@ -70,7 +82,11 @@ namespace Hal {
return nullptr; return nullptr;
} }
// ── EvaluateSta ───────────────────────────────────────────────── // ============================================================================
// EvaluateSta
// ============================================================================
uint32_t EvaluateSta(int32_t deviceNodeIndex) { uint32_t EvaluateSta(int32_t deviceNodeIndex) {
auto& interp = AML::GetInterpreter(); auto& interp = AML::GetInterpreter();
auto& ns = interp.GetNamespace(); auto& ns = interp.GetNamespace();
@@ -107,7 +123,11 @@ namespace Hal {
return STA_DEFAULT; return STA_DEFAULT;
} }
// ── EvaluateAdr ───────────────────────────────────────────────── // ============================================================================
// EvaluateAdr
// ============================================================================
uint64_t EvaluateAdr(int32_t deviceNodeIndex) { uint64_t EvaluateAdr(int32_t deviceNodeIndex) {
auto& interp = AML::GetInterpreter(); auto& interp = AML::GetInterpreter();
auto& ns = interp.GetNamespace(); auto& ns = interp.GetNamespace();
@@ -143,7 +163,11 @@ namespace Hal {
return 0; return 0;
} }
// ── EvaluateHid ───────────────────────────────────────────────── // ============================================================================
// EvaluateHid
// ============================================================================
bool EvaluateHid(int32_t deviceNodeIndex, char* outHid, int maxLen) { bool EvaluateHid(int32_t deviceNodeIndex, char* outHid, int maxLen) {
auto& interp = AML::GetInterpreter(); auto& interp = AML::GetInterpreter();
auto& ns = interp.GetNamespace(); auto& ns = interp.GetNamespace();
@@ -182,7 +206,11 @@ namespace Hal {
return false; return false;
} }
// ── EvaluateUid ───────────────────────────────────────────────── // ============================================================================
// EvaluateUid
// ============================================================================
bool EvaluateUid(int32_t deviceNodeIndex, char* outUid, int maxLen) { bool EvaluateUid(int32_t deviceNodeIndex, char* outUid, int maxLen) {
auto& interp = AML::GetInterpreter(); auto& interp = AML::GetInterpreter();
auto& ns = interp.GetNamespace(); auto& ns = interp.GetNamespace();
@@ -237,7 +265,11 @@ namespace Hal {
return false; return false;
} }
// ── EvaluateCrs ───────────────────────────────────────────────── // ============================================================================
// EvaluateCrs
// ============================================================================
bool EvaluateCrs(int32_t deviceNodeIndex, AML::ResourceList& result) { bool EvaluateCrs(int32_t deviceNodeIndex, AML::ResourceList& result) {
auto& interp = AML::GetInterpreter(); auto& interp = AML::GetInterpreter();
auto& ns = interp.GetNamespace(); auto& ns = interp.GetNamespace();
@@ -268,7 +300,11 @@ namespace Hal {
return AML::ParseResourceTemplate(crsResult.Buffer.Data, crsResult.Buffer.Length, result); return AML::ParseResourceTemplate(crsResult.Buffer.Data, crsResult.Buffer.Length, result);
} }
// ── EnumerateAll ──────────────────────────────────────────────── // ============================================================================
// EnumerateAll
// ============================================================================
void EnumerateAll(DeviceList& result) { void EnumerateAll(DeviceList& result) {
auto& interp = AML::GetInterpreter(); auto& interp = AML::GetInterpreter();
if (!interp.IsInitialized()) return; if (!interp.IsInitialized()) return;
@@ -323,7 +359,11 @@ namespace Hal {
} }
} }
// ── GetSleepState ─────────────────────────────────────────────── // ============================================================================
// GetSleepState
// ============================================================================
SleepState GetSleepState(int state) { SleepState GetSleepState(int state) {
SleepState result{}; SleepState result{};
result.Valid = false; result.Valid = false;
+24 -5
View File
@@ -11,7 +11,11 @@
namespace Hal { namespace Hal {
namespace AcpiDevices { namespace AcpiDevices {
// ── Device Status Flags (_STA) ────────────────────────────────── // ============================================================================
// Device Status Flags (_STA)
// ============================================================================
static constexpr uint32_t STA_PRESENT = (1 << 0); static constexpr uint32_t STA_PRESENT = (1 << 0);
static constexpr uint32_t STA_ENABLED = (1 << 1); static constexpr uint32_t STA_ENABLED = (1 << 1);
static constexpr uint32_t STA_VISIBLE = (1 << 2); static constexpr uint32_t STA_VISIBLE = (1 << 2);
@@ -21,7 +25,11 @@ namespace Hal {
// Default _STA when no _STA method exists: present + enabled + visible + functional // Default _STA when no _STA method exists: present + enabled + visible + functional
static constexpr uint32_t STA_DEFAULT = 0x0F; static constexpr uint32_t STA_DEFAULT = 0x0F;
// ── Device Info ───────────────────────────────────────────────── // ============================================================================
// Device Info
// ============================================================================
struct DeviceInfo { struct DeviceInfo {
char Path[128]; // full namespace path char Path[128]; // full namespace path
char HardwareId[16]; // _HID value (e.g. "PNP0A03", "ACPI0001") char HardwareId[16]; // _HID value (e.g. "PNP0A03", "ACPI0001")
@@ -47,8 +55,11 @@ namespace Hal {
const DeviceInfo* FindByPath(const char* path) const; const DeviceInfo* FindByPath(const char* path) const;
}; };
// ── Enumeration API ───────────────────────────────────────────── // ============================================================================
// Enumeration API
// ============================================================================
// Enumerate all ACPI devices in the namespace. // Enumerate all ACPI devices in the namespace.
// The interpreter must be initialized first (LoadTable called). // The interpreter must be initialized first (LoadTable called).
void EnumerateAll(DeviceList& result); void EnumerateAll(DeviceList& result);
@@ -69,7 +80,11 @@ namespace Hal {
// Evaluate _CRS (Current Resource Settings) for a device node. // Evaluate _CRS (Current Resource Settings) for a device node.
bool EvaluateCrs(int32_t deviceNodeIndex, AML::ResourceList& result); bool EvaluateCrs(int32_t deviceNodeIndex, AML::ResourceList& result);
// ── Well-known HIDs ───────────────────────────────────────────── // ============================================================================
// Well-known HIDs
// ============================================================================
// PCI Host Bridge // PCI Host Bridge
static constexpr const char* HID_PCI_HOST = "PNP0A03"; static constexpr const char* HID_PCI_HOST = "PNP0A03";
static constexpr const char* HID_PCIE_HOST = "PNP0A08"; static constexpr const char* HID_PCIE_HOST = "PNP0A08";
@@ -98,7 +113,11 @@ namespace Hal {
// ACPI AC Adapter // ACPI AC Adapter
static constexpr const char* HID_AC_ADAPTER = "ACPI0003"; static constexpr const char* HID_AC_ADAPTER = "ACPI0003";
// ── Sleep State Support ───────────────────────────────────────── // ============================================================================
// Sleep State Support
// ============================================================================
// Extract sleep state values from namespace objects (\_S0_ through \_S5_). // Extract sleep state values from namespace objects (\_S0_ through \_S5_).
struct SleepState { struct SleepState {
uint16_t SLP_TYPa; uint16_t SLP_TYPa;
+142
View File
@@ -0,0 +1,142 @@
/*
* AcpiEvents.cpp
* ACPI fixed event handling (SCI interrupt, power button, etc.)
* Copyright (c) 2026 Daniel Hammer
*/
#include "AcpiEvents.hpp"
#include <ACPI/AcpiSleep.hpp>
#include <Io/IoPort.hpp>
#include <Hal/Apic/Apic.hpp>
#include <Hal/Apic/IoApic.hpp>
#include <Hal/Apic/Interrupts.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
using namespace Kt;
namespace Hal {
namespace AcpiEvents {
// ============================================================================
// State
// ============================================================================
static uint32_t g_pm1aEventBlock = 0;
static uint32_t g_pm1bEventBlock = 0;
static uint8_t g_pm1EventLength = 0;
static uint16_t g_sciIrq = 0;
static bool g_initialized = false;
// ============================================================================
// PM1 register helpers
// ============================================================================
static uint16_t ReadPM1Status() {
uint16_t sts = 0;
if (g_pm1aEventBlock != 0)
sts |= Io::In16((uint16_t)g_pm1aEventBlock);
if (g_pm1bEventBlock != 0)
sts |= Io::In16((uint16_t)g_pm1bEventBlock);
return sts;
}
static void ClearPM1StatusBits(uint16_t bits) {
// Write-1-to-clear semantics
if (g_pm1aEventBlock != 0)
Io::Out16(bits, (uint16_t)g_pm1aEventBlock);
if (g_pm1bEventBlock != 0)
Io::Out16(bits, (uint16_t)g_pm1bEventBlock);
}
static void EnablePM1Events(uint16_t mask) {
uint16_t enableOffset = g_pm1EventLength / 2;
if (enableOffset == 0) return;
if (g_pm1aEventBlock != 0) {
uint16_t en = Io::In16((uint16_t)(g_pm1aEventBlock + enableOffset));
en |= mask;
Io::Out16(en, (uint16_t)(g_pm1aEventBlock + enableOffset));
}
if (g_pm1bEventBlock != 0) {
uint16_t en = Io::In16((uint16_t)(g_pm1bEventBlock + enableOffset));
en |= mask;
Io::Out16(en, (uint16_t)(g_pm1bEventBlock + enableOffset));
}
}
// ============================================================================
// SCI Interrupt Handler
// ============================================================================
static void SciHandler(uint8_t irq) {
uint16_t sts = ReadPM1Status();
if (sts & AcpiSleep::PM1_PWRBTN_STS) {
ClearPM1StatusBits(AcpiSleep::PM1_PWRBTN_STS);
KernelLogStream(INFO, "ACPI") << "Power button pressed";
}
if (sts & AcpiSleep::PM1_SLPBTN_STS) {
ClearPM1StatusBits(AcpiSleep::PM1_SLPBTN_STS);
KernelLogStream(INFO, "ACPI") << "Sleep button pressed";
}
// Send EOI
LocalApic::SendEOI();
}
// ============================================================================
// Initialize
// ============================================================================
void Initialize(const FADT::ParsedFADT& fadt) {
g_pm1aEventBlock = fadt.PM1aEventBlock;
g_pm1bEventBlock = fadt.PM1bEventBlock;
g_pm1EventLength = fadt.PM1EventLength;
g_sciIrq = fadt.SCI_Interrupt;
if (g_pm1aEventBlock == 0) {
KernelLogStream(ERROR, "ACPI") << "No PM1a event block - ACPI events unavailable";
return;
}
// Clear any pending status bits
ClearPM1StatusBits(
AcpiSleep::PM1_PWRBTN_STS | AcpiSleep::PM1_SLPBTN_STS |
AcpiSleep::PM1_WAK_STS | AcpiSleep::PM1_TMR_STS |
AcpiSleep::PM1_GBL_STS | AcpiSleep::PM1_RTC_STS |
AcpiSleep::PM1_BM_STS);
// Enable power button event
EnablePM1Events(AcpiSleep::PM1_PWRBTN_EN);
// Route SCI to an IRQ vector. The SCI is level-triggered,
// active-low (ACPI spec requirement). The MADT may have an
// Interrupt Source Override for this IRQ that already sets
// these flags; RouteIrq applies overrides automatically.
uint8_t sciVector = IRQ_VECTOR_BASE + (uint8_t)g_sciIrq;
uint8_t bspApicId = (uint8_t)LocalApic::GetId();
// Register the handler before routing to avoid missing events
RegisterIrqHandler((uint8_t)g_sciIrq, SciHandler);
// Route via IoApic (applies MADT overrides for polarity/trigger)
IoApic::RouteIrq((uint8_t)g_sciIrq, sciVector, bspApicId);
g_initialized = true;
KernelLogStream(OK, "ACPI") << "ACPI events initialized (SCI IRQ "
<< base::dec << (uint64_t)g_sciIrq << ", vector "
<< (uint64_t)sciVector << ")";
}
void Reinitialize() {
if (!g_initialized) return;
// Clear pending status and re-enable power button event
ClearPM1StatusBits(
AcpiSleep::PM1_PWRBTN_STS | AcpiSleep::PM1_SLPBTN_STS);
EnablePM1Events(AcpiSleep::PM1_PWRBTN_EN);
}
};
};
+22
View File
@@ -0,0 +1,22 @@
/*
* AcpiEvents.hpp
* ACPI fixed event handling (SCI interrupt, power button, etc.)
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <ACPI/ACPI.hpp>
#include <ACPI/FADT.hpp>
namespace Hal {
namespace AcpiEvents {
// Initialize ACPI event handling: route the SCI interrupt,
// register the handler, and enable fixed events (power button).
// Must be called after APIC and FADT initialization.
void Initialize(const FADT::ParsedFADT& fadt);
// Re-enable ACPI events after S3 resume.
void Reinitialize();
};
};
+17
View File
@@ -6,6 +6,7 @@
#include "AcpiShutdown.hpp" #include "AcpiShutdown.hpp"
#include "AcpiSleep.hpp" #include "AcpiSleep.hpp"
#include "AcpiEvents.hpp"
#include <ACPI/FADT.hpp> #include <ACPI/FADT.hpp>
#include <ACPI/AML/AmlParser.hpp> #include <ACPI/AML/AmlParser.hpp>
#include <ACPI/AML/AmlInterpreter.hpp> #include <ACPI/AML/AmlInterpreter.hpp>
@@ -67,6 +68,9 @@ namespace Hal {
// Initialize S3 suspend support (reads FACS and \_S3_ from namespace) // Initialize S3 suspend support (reads FACS and \_S3_ from namespace)
AcpiSleep::Initialize(xsdt); AcpiSleep::Initialize(xsdt);
// Initialize ACPI event handling (SCI interrupt, power button)
AcpiEvents::Initialize(fadt);
} }
bool IsAvailable() { bool IsAvailable() {
@@ -78,6 +82,19 @@ namespace Hal {
KernelLogStream(INFO, "ACPI") << "Performing ACPI S5 shutdown..."; KernelLogStream(INFO, "ACPI") << "Performing ACPI S5 shutdown...";
// Evaluate _PTS(5) — Prepare To Sleep (S5 = soft-off)
auto& interp = AML::GetInterpreter();
if (interp.IsInitialized()) {
int32_t node = interp.GetNamespace().FindNode("\\_PTS");
if (node >= 0) {
AML::Object arg{};
arg.Type = AML::ObjectType::Integer;
arg.Integer = 5;
AML::Object result{};
interp.EvaluateMethod("\\_PTS", &arg, 1, result);
}
}
asm volatile("cli"); asm volatile("cli");
// Phase 1: Write SLP_TYP to PM1x_CNT without SLP_EN, // Phase 1: Write SLP_TYP to PM1x_CNT without SLP_EN,
+258 -93
View File
@@ -5,6 +5,7 @@
*/ */
#include "AcpiSleep.hpp" #include "AcpiSleep.hpp"
#include "AcpiEvents.hpp"
#include <ACPI/FADT.hpp> #include <ACPI/FADT.hpp>
#include <ACPI/AML/AmlParser.hpp> #include <ACPI/AML/AmlParser.hpp>
#include <ACPI/AML/AmlInterpreter.hpp> #include <ACPI/AML/AmlInterpreter.hpp>
@@ -22,6 +23,8 @@
#include <Drivers/PS2/PS2Controller.hpp> #include <Drivers/PS2/PS2Controller.hpp>
#include <Graphics/Cursor.hpp> #include <Graphics/Cursor.hpp>
#include <Libraries/Memory.hpp> #include <Libraries/Memory.hpp>
#include <Hal/MSR.hpp>
#include <Api/Syscall.hpp>
using namespace Kt; using namespace Kt;
@@ -31,6 +34,11 @@ extern "C" void AcpiResumeLongMode(Hal::AcpiSleep::CpuState* stateArea);
extern "C" void AcpiWakeEntry(); extern "C" void AcpiWakeEntry();
extern "C" void* g_wakeStatePtr; extern "C" void* g_wakeStatePtr;
// Called directly by AcpiResumeLongMode after restoring CPU state.
// This avoids returning to Suspend() via ret, which is fragile because
// the compiler's stack frame expectations may not match the restored state.
extern "C" void AcpiResumeEntry();
// Real-mode trampoline from S3Trampoline.asm // Real-mode trampoline from S3Trampoline.asm
extern "C" char S3TrampolineStart[]; extern "C" char S3TrampolineStart[];
extern "C" char S3Trampoline64[]; extern "C" char S3Trampoline64[];
@@ -40,6 +48,11 @@ extern "C" char S3TrampolineEnd[];
// Physical address where the trampoline is copied (must be < 1MB, page-aligned) // Physical address where the trampoline is copied (must be < 1MB, page-aligned)
static constexpr uint32_t TRAMPOLINE_PHYS = 0x8000; static constexpr uint32_t TRAMPOLINE_PHYS = 0x8000;
// Physical address for the shadow PML4 used by the trampoline (must be < 4GB).
// The kernel PML4 may be above 4GB, but the 32-bit trampoline can only load
// a 32-bit CR3. We copy the kernel PML4 entries here before entering S3.
static constexpr uint32_t SHADOW_PML4_PHYS = 0x9000;
// GDT/TSS reload helpers // GDT/TSS reload helpers
namespace Hal { namespace Hal {
extern void BridgeLoadGDT(); extern void BridgeLoadGDT();
@@ -49,7 +62,11 @@ namespace Hal {
namespace Hal { namespace Hal {
namespace AcpiSleep { namespace AcpiSleep {
// ── State ─────────────────────────────────────────────────────── // ============================================================================
// State
// ============================================================================
static bool g_s3Available = false; static bool g_s3Available = false;
static uint16_t g_s3SlpTypA = 0; static uint16_t g_s3SlpTypA = 0;
static uint16_t g_s3SlpTypB = 0; static uint16_t g_s3SlpTypB = 0;
@@ -65,10 +82,66 @@ namespace Hal {
// CPU state save area (aligned for fxsave) // CPU state save area (aligned for fxsave)
static CpuState g_cpuState __attribute__((aligned(64))); static CpuState g_cpuState __attribute__((aligned(64)));
// ── Initialize ────────────────────────────────────────────────── // Set by AcpiResumeEntry to signal Suspend() that resume completed.
static volatile int g_resumeComplete = 0;
// ============================================================================
// Initialize
// ============================================================================
void Initialize(ACPI::CommonSDTHeader* xsdt) { void Initialize(ACPI::CommonSDTHeader* xsdt) {
g_s3Available = false; g_s3Available = false;
// Check CMOS 0x72 for S3 wake progress from a previous attempt.
// Non-zero means the last wake attempt reached a specific stage.
Io::Out8(0xF2, 0x70); // CMOS register 0x72 | NMI disable
uint8_t wakeProgress = Io::In8(0x71);
// Check CMOS 0x73 — set to 0xDD if C resume code was ever reached
// (survives trampoline overwrites of 0x72 during sleep-wake loops)
Io::Out8(0xF3, 0x70);
uint8_t cReached = Io::In8(0x71);
if (wakeProgress != 0 || cReached != 0) {
KernelLogStream(WARNING, "S3") << "Previous S3 wake progress: 0x"
<< base::hex << (uint64_t)wakeProgress
<< (wakeProgress == 0xA1 ? " (16-bit trampoline)" :
wakeProgress == 0xA2 ? " (32-bit mode)" :
wakeProgress == 0xA3 ? " (entering long mode)" :
wakeProgress == 0xB1 ? " (64-bit UEFI entry)" :
wakeProgress == 0xC1 ? " (AcpiResumeLongMode entry)" :
wakeProgress == 0xC2 ? " (GDT+CS reloaded)" :
wakeProgress == 0xC3 ? " (IDT+CR4/CR0/CR3 restored)" :
wakeProgress == 0xC4 ? " (about to ret to Suspend)" :
wakeProgress == 0xD1 ? " (C resume path)" : " (unknown)");
if (cReached == 0xEE)
KernelLogStream(WARNING, "S3") << "Full resume completed (all reinit done)";
else if (cReached == 0xDD)
KernelLogStream(WARNING, "S3") << "C resume entered but did NOT complete all reinit";
else
KernelLogStream(WARNING, "S3") << "C resume path was NOT reached";
// Read reinit step progress from CMOS 0x74
Io::Out8(0xF4, 0x70);
uint8_t reinitStep = Io::In8(0x71);
if (reinitStep != 0) {
const char* stepNames[] = {
"?", "GDT", "TSS", "Syscalls", "PAT", "PIC",
"LocalAPIC", "IoAPIC", "APICTimer", "PM1Clear",
"WAK", "PS2", "IntelGPU", "sti"
};
const char* stepName = reinitStep <= 0x0D ? stepNames[reinitStep] : "?";
KernelLogStream(WARNING, "S3") << "Reinit stopped after step 0x"
<< base::hex << (uint64_t)reinitStep << " (" << stepName
<< ") - next step crashed/hung";
}
Io::Out8(0xF4, 0x70);
Io::Out8(0x00, 0x71);
// Clear both
Io::Out8(0xF2, 0x70);
Io::Out8(0x00, 0x71);
Io::Out8(0xF3, 0x70);
Io::Out8(0x00, 0x71);
}
FADT::ParsedFADT fadt{}; FADT::ParsedFADT fadt{};
if (!FADT::Parse(xsdt, fadt) || !fadt.Valid) if (!FADT::Parse(xsdt, fadt) || !fadt.Valid)
return; return;
@@ -96,9 +169,9 @@ namespace Hal {
g_pm1bControlBlock = fadt.PM1bControlBlock; g_pm1bControlBlock = fadt.PM1bControlBlock;
g_pm1EventLength = fadt.PM1EventLength; g_pm1EventLength = fadt.PM1EventLength;
// Find \_S3_ via brute-force DSDT scan (same approach as \_S5_). // Find \_S3_ via brute-force DSDT scan. The AML interpreter stores
// This works on any DSDT regardless of complexity — does not // packages as raw bytecode (no structured element access yet), so
// require the AML interpreter or namespace to be loaded. // the brute-force scanner is the reliable path for now.
auto* dsdt = (void*)Memory::HHDM(fadt.DsdtAddress); auto* dsdt = (void*)Memory::HHDM(fadt.DsdtAddress);
AML::SleepObject s3 = AML::FindSleepState(dsdt, 3); AML::SleepObject s3 = AML::FindSleepState(dsdt, 3);
if (s3.Valid) { if (s3.Valid) {
@@ -111,6 +184,11 @@ namespace Hal {
<< " SLP_TYPb=" << base::hex << (uint64_t)g_s3SlpTypB << ")"; << " SLP_TYPb=" << base::hex << (uint64_t)g_s3SlpTypB << ")";
KernelLogStream(INFO, "S3") << "Kernel PML4 phys = " << base::hex KernelLogStream(INFO, "S3") << "Kernel PML4 phys = " << base::hex
<< (uint64_t)Memory::VMM::g_paging->PML4; << (uint64_t)Memory::VMM::g_paging->PML4;
KernelLogStream(INFO, "S3") << "FACS at phys " << base::hex
<< (uint64_t)fadt.FacsAddress << " version=" << base::dec
<< (uint64_t)g_facs->Version << " flags=" << base::hex
<< (uint64_t)g_facs->Flags
<< (g_facs->Flags & FACS_64BIT_WAKE_F ? " (64BIT_WAKE)" : " (32BIT_WAKE only)");
} else { } else {
KernelLogStream(INFO, "S3") << "\\_S3_ not found in DSDT - S3 unavailable"; KernelLogStream(INFO, "S3") << "\\_S3_ not found in DSDT - S3 unavailable";
} }
@@ -120,7 +198,11 @@ namespace Hal {
return g_s3Available; return g_s3Available;
} }
// ── Evaluate _PTS (Prepare To Sleep) ──────────────────────────── // ============================================================================
// Evaluate _PTS (Prepare To Sleep)
// ============================================================================
static void EvaluatePts(int sleepState) { static void EvaluatePts(int sleepState) {
auto& interp = AML::GetInterpreter(); auto& interp = AML::GetInterpreter();
if (!interp.IsInitialized()) return; if (!interp.IsInitialized()) return;
@@ -138,7 +220,11 @@ namespace Hal {
KernelLogStream(DEBUG, "S3") << "Evaluated \\_PTS(" << base::dec << (uint64_t)sleepState << ")"; KernelLogStream(DEBUG, "S3") << "Evaluated \\_PTS(" << base::dec << (uint64_t)sleepState << ")";
} }
// ── Evaluate _WAK (System Wake) ───────────────────────────────── // ============================================================================
// Evaluate _WAK (System Wake)
// ============================================================================
static void EvaluateWak(int sleepState) { static void EvaluateWak(int sleepState) {
auto& interp = AML::GetInterpreter(); auto& interp = AML::GetInterpreter();
if (!interp.IsInitialized()) return; if (!interp.IsInitialized()) return;
@@ -156,7 +242,11 @@ namespace Hal {
KernelLogStream(DEBUG, "S3") << "Evaluated \\_WAK(" << base::dec << (uint64_t)sleepState << ")"; KernelLogStream(DEBUG, "S3") << "Evaluated \\_WAK(" << base::dec << (uint64_t)sleepState << ")";
} }
// ── Clear PM1 Status Registers ────────────────────────────────── // ============================================================================
// Clear PM1 Status Registers
// ============================================================================
static void ClearPM1Status() { static void ClearPM1Status() {
// Write 1 to clear all status bits (write-1-to-clear semantics) // Write 1 to clear all status bits (write-1-to-clear semantics)
uint16_t clearMask = PM1_WAK_STS | PM1_PWRBTN_STS | PM1_SLPBTN_STS | uint16_t clearMask = PM1_WAK_STS | PM1_PWRBTN_STS | PM1_SLPBTN_STS |
@@ -168,7 +258,11 @@ namespace Hal {
Io::Out16(clearMask, (uint16_t)g_pm1bEventBlock); Io::Out16(clearMask, (uint16_t)g_pm1bEventBlock);
} }
// ── Enable Wake Events ────────────────────────────────────────── // ============================================================================
// Enable Wake Events
// ============================================================================
static void EnableWakeEvents() { static void EnableWakeEvents() {
// Enable register is at event block + PM1EventLength/2 // Enable register is at event block + PM1EventLength/2
uint16_t enableOffset = g_pm1EventLength / 2; uint16_t enableOffset = g_pm1EventLength / 2;
@@ -182,7 +276,11 @@ namespace Hal {
Io::Out16(enableMask, (uint16_t)(g_pm1bEventBlock + enableOffset)); Io::Out16(enableMask, (uint16_t)(g_pm1bEventBlock + enableOffset));
} }
// ── Install real-mode trampoline and set waking vector ──────────── // ============================================================================
// Install real-mode trampoline and set waking vector
// ============================================================================
static void SetWakingVector() { static void SetWakingVector() {
if (!g_facs) return; if (!g_facs) return;
@@ -197,26 +295,31 @@ namespace Hal {
uint8_t* trampolineSrc = (uint8_t*)S3TrampolineStart; uint8_t* trampolineSrc = (uint8_t*)S3TrampolineStart;
memcpy(trampolineDst, trampolineSrc, trampolineSize); memcpy(trampolineDst, trampolineSrc, trampolineSize);
// Patch the trampoline data area with CR3 and resume addresses. // Patch the trampoline data area with CR3, resume addresses,
// and PM1 control port addresses.
// Data layout (from S3Trampoline.asm): // Data layout (from S3Trampoline.asm):
// +0: uint64_t CR3 (full 64-bit PML4 physical address) // +0: uint64_t CR3 (full 64-bit PML4 physical address)
// +8: uint64_t CpuState virtual address // +8: uint64_t CpuState virtual address
// +16: uint64_t AcpiResumeLongMode virtual address // +16: uint64_t AcpiResumeLongMode virtual address
// +24: uint16_t PM1a control block I/O port
// +26: uint16_t PM1b control block I/O port
uint32_t dataOffset = (uint32_t)(S3TrampolineData - S3TrampolineStart); uint32_t dataOffset = (uint32_t)(S3TrampolineData - S3TrampolineStart);
uint8_t* dataArea = trampolineDst + dataOffset; uint8_t* dataArea = trampolineDst + dataOffset;
// Use the kernel master PML4 (which has 0x8000 identity-mapped) // The kernel PML4 may be above 4GB (e.g. at 0x8BF7CC000), but
// rather than the saved process PML4 (which doesn't). // the 32-bit trampoline can only load a 32-bit CR3. Create a
// AcpiResumeLongMode will restore the saved CR3 after we're // shadow copy of the PML4 at a fixed low-memory address.
// safely back in long mode with kernel GDT/IDT. // Only the PML4 page itself needs to be below 4GB — its entries
uint64_t cr3val = (uint64_t)Memory::VMM::g_paging->PML4; // contain full 52-bit physical addresses for sub-tables.
uint64_t kernelPml4Phys = (uint64_t)Memory::VMM::g_paging->PML4;
uint8_t* kernelPml4Virt = (uint8_t*)Memory::HHDM(kernelPml4Phys);
uint8_t* shadowPml4Virt = (uint8_t*)Memory::HHDM((uint64_t)SHADOW_PML4_PHYS);
memcpy(shadowPml4Virt, kernelPml4Virt, 4096);
// The 16-bit->32-bit trampoline path can only load 32-bit CR3. uint64_t cr3val = (uint64_t)SHADOW_PML4_PHYS;
// If the PML4 is above 4GB, the real-mode wake path would fail.
if (cr3val > 0xFFFFFFFF) { KernelLogStream(DEBUG, "S3") << "Shadow PML4 at " << base::hex << cr3val
KernelLogStream(ERROR, "S3") << "PML4 at " << base::hex << cr3val << " (kernel PML4 at " << base::hex << kernelPml4Phys << ")";
<< " is above 4GB - real-mode wake path will fail!";
}
memcpy(dataArea + 0, &cr3val, 8); memcpy(dataArea + 0, &cr3val, 8);
@@ -226,10 +329,24 @@ namespace Hal {
uint64_t resumeAddr = (uint64_t)&AcpiResumeLongMode; uint64_t resumeAddr = (uint64_t)&AcpiResumeLongMode;
memcpy(dataArea + 16, &resumeAddr, 8); memcpy(dataArea + 16, &resumeAddr, 8);
// Set the 32-bit waking vector only. Setting X_FirmwareWakingVector uint16_t pm1a = (uint16_t)g_pm1aControlBlock;
// to non-zero causes this laptop's firmware to hang during wake. uint16_t pm1b = (uint16_t)g_pm1bControlBlock;
memcpy(dataArea + 24, &pm1a, 2);
memcpy(dataArea + 26, &pm1b, 2);
// Always set the 32-bit real-mode waking vector (universal fallback).
g_facs->FirmwareWakingVector = TRAMPOLINE_PHYS; g_facs->FirmwareWakingVector = TRAMPOLINE_PHYS;
// Use the 64-bit waking vector if the firmware advertises support.
// FACS flags bit 1 (64BIT_WAKE_F) indicates the platform can
// transfer control in 64-bit mode.
if (g_facs->Flags & FACS_64BIT_WAKE_F) {
g_facs->X_FirmwareWakingVector = TRAMPOLINE_PHYS + 0x100;
KernelLogStream(DEBUG, "S3") << "Using 64-bit waking vector (0x8100)";
} else {
g_facs->X_FirmwareWakingVector = 0; g_facs->X_FirmwareWakingVector = 0;
KernelLogStream(DEBUG, "S3") << "Using 32-bit waking vector (0x8000)";
}
KernelLogStream(DEBUG, "S3") << "Trampoline installed at " << base::hex KernelLogStream(DEBUG, "S3") << "Trampoline installed at " << base::hex
<< (uint64_t)TRAMPOLINE_PHYS << " (" << base::dec << (uint64_t)TRAMPOLINE_PHYS << " (" << base::dec
@@ -237,7 +354,11 @@ namespace Hal {
KernelLogStream(DEBUG, "S3") << "Trampoline CR3 = " << base::hex << cr3val; KernelLogStream(DEBUG, "S3") << "Trampoline CR3 = " << base::hex << cr3val;
} }
// ── Wait for WAK_STS ──────────────────────────────────────────── // ============================================================================
// Wait for WAK_STS
// ============================================================================
static void WaitForWake() { static void WaitForWake() {
// After entering S3, the CPU halts. On resume, firmware runs the // After entering S3, the CPU halts. On resume, firmware runs the
// waking vector. But if we somehow didn't enter sleep (e.g. immediate // waking vector. But if we somehow didn't enter sleep (e.g. immediate
@@ -251,7 +372,101 @@ namespace Hal {
} }
} }
// ── Suspend ───────────────────────────────────────────────────── // ============================================================================
// Resume Entry (called directly by AcpiResumeLongMode)
// ============================================================================
// This is a standalone function with its own stack frame, avoiding
// any dependency on the compiler's layout of Suspend().
extern "C" void AcpiResumeEntry() {
// Progress: reached C resume path (0xD1)
Io::Out8(0xF2, 0x70);
Io::Out8(0xD1, 0x71);
Io::Out8(0xF3, 0x70);
Io::Out8(0xDD, 0x71);
// CRITICAL: Clear SLP_EN and SLP_TYP in PM1 control registers
if (g_pm1aControlBlock != 0) {
uint16_t pm1a = Io::In16((uint16_t)g_pm1aControlBlock);
pm1a &= ~(PM1_SLP_EN | PM1_SLP_TYP_MASK);
Io::Out16(pm1a, (uint16_t)g_pm1aControlBlock);
}
if (g_pm1bControlBlock != 0) {
uint16_t pm1b = Io::In16((uint16_t)g_pm1bControlBlock);
pm1b &= ~(PM1_SLP_EN | PM1_SLP_TYP_MASK);
Io::Out16(pm1b, (uint16_t)g_pm1bControlBlock);
}
// Debug: green rectangle on framebuffer
{
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;
}
}
}
}
// Use CMOS 0x74 as step-by-step progress through reinit
auto reinitProgress = [](uint8_t step) {
Io::Out8(0xF4, 0x70);
Io::Out8(step, 0x71);
};
reinitProgress(0x01); // GDT
Hal::BridgeLoadGDT();
reinitProgress(0x02); // TSS
Hal::LoadTSS();
reinitProgress(0x03); // Syscalls
Montauk::InitializeSyscalls();
reinitProgress(0x04); // PAT
Hal::InitializePAT();
reinitProgress(0x05); // PIC
Hal::DisableLegacyPic();
reinitProgress(0x06); // Local APIC
Hal::LocalApic::Reinitialize();
reinitProgress(0x07); // IO APIC
Hal::IoApic::Reinitialize();
reinitProgress(0x08); // APIC Timer
Timekeeping::ApicTimerReinitialize();
reinitProgress(0x09); // PM1 clear
ClearPM1Status();
reinitProgress(0x0A); // _WAK
EvaluateWak(3);
reinitProgress(0x0B); // PS/2
Drivers::PS2::Reinitialize();
// Re-enable ACPI events (power button) after S3
AcpiEvents::Reinitialize();
reinitProgress(0x0C); // Intel GPU
if (Drivers::Graphics::IntelGPU::IsInitialized()) {
Drivers::Graphics::IntelGPU::Reinitialize();
}
reinitProgress(0x0D); // sti
asm volatile("sti");
KernelLogStream(OK, "S3") << "Resumed from S3 suspend";
// Progress: all reinit complete (0xEE in CMOS 0x73)
Io::Out8(0xF3, 0x70);
Io::Out8(0xEE, 0x71);
// Signal Suspend() that resume is complete. AcpiResumeEntry
// was entered via jmp (not call), so our ret will pop the
// original return address from call AcpiSaveAndSuspend,
// returning to Suspend() which checks this flag.
g_resumeComplete = 1;
}
// ============================================================================
// Suspend
// ============================================================================
int Suspend() { int Suspend() {
if (!g_s3Available) { if (!g_s3Available) {
KernelLogStream(ERROR, "S3") << "S3 suspend not available"; KernelLogStream(ERROR, "S3") << "S3 suspend not available";
@@ -263,77 +478,27 @@ namespace Hal {
// 1. Evaluate _PTS(3) — Prepare To Sleep // 1. Evaluate _PTS(3) — Prepare To Sleep
EvaluatePts(3); EvaluatePts(3);
// 2. Save CPU state. AcpiSaveAndSuspend returns 1 on initial call, // 2. Save CPU state. On resume, AcpiResumeLongMode jumps to
// and 0 when we resume from S3 (AcpiResumeLongMode sets RAX=0). // AcpiResumeEntry (not back here), which does all reinit and
int resumed = !AcpiSaveAndSuspend(&g_cpuState); // sets g_resumeComplete=1, then returns here via the stack.
if (resumed) { g_resumeComplete = 0;
// ── RESUME PATH ───────────────────────────────────────── AcpiSaveAndSuspend(&g_cpuState);
// 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";
// If we get here after resume, AcpiResumeEntry already ran.
if (g_resumeComplete) {
return 0; return 0;
} }
// ── SUSPEND PATH (initial save returned 1) ────────────────── // ============================================================================
// SUSPEND PATH (initial save returned 1)
// ============================================================================
// Clear CMOS progress markers before entering S3.
Io::Out8(0xF2, 0x70); // CMOS register 0x72 | NMI disable
Io::Out8(0x00, 0x71);
Io::Out8(0xF3, 0x70); // CMOS register 0x73
Io::Out8(0x00, 0x71);
// 3. Disable interrupts // 3. Disable interrupts
asm volatile("cli"); asm volatile("cli");
+29 -6
View File
@@ -11,7 +11,11 @@
namespace Hal { namespace Hal {
namespace AcpiSleep { namespace AcpiSleep {
// ── FACS (Firmware ACPI Control Structure) ────────────────────── // ============================================================================
// FACS (Firmware ACPI Control Structure)
// ============================================================================
struct FACS { struct FACS {
char Signature[4]; // "FACS" char Signature[4]; // "FACS"
uint32_t Length; uint32_t Length;
@@ -33,7 +37,11 @@ namespace Hal {
static constexpr uint32_t FADT_S4_RTC_STS_VALID = (1 << 16); static constexpr uint32_t FADT_S4_RTC_STS_VALID = (1 << 16);
static constexpr uint32_t FADT_HW_REDUCED_ACPI = (1 << 20); static constexpr uint32_t FADT_HW_REDUCED_ACPI = (1 << 20);
// ── PM1 Status Register Bits ──────────────────────────────────── // ============================================================================
// PM1 Status Register Bits
// ============================================================================
static constexpr uint16_t PM1_TMR_STS = (1 << 0); static constexpr uint16_t PM1_TMR_STS = (1 << 0);
static constexpr uint16_t PM1_BM_STS = (1 << 4); static constexpr uint16_t PM1_BM_STS = (1 << 4);
static constexpr uint16_t PM1_GBL_STS = (1 << 5); static constexpr uint16_t PM1_GBL_STS = (1 << 5);
@@ -42,20 +50,32 @@ namespace Hal {
static constexpr uint16_t PM1_RTC_STS = (1 << 10); static constexpr uint16_t PM1_RTC_STS = (1 << 10);
static constexpr uint16_t PM1_WAK_STS = (1 << 15); static constexpr uint16_t PM1_WAK_STS = (1 << 15);
// ── PM1 Enable Register Bits ──────────────────────────────────── // ============================================================================
// PM1 Enable Register Bits
// ============================================================================
static constexpr uint16_t PM1_TMR_EN = (1 << 0); static constexpr uint16_t PM1_TMR_EN = (1 << 0);
static constexpr uint16_t PM1_GBL_EN = (1 << 5); static constexpr uint16_t PM1_GBL_EN = (1 << 5);
static constexpr uint16_t PM1_PWRBTN_EN = (1 << 8); static constexpr uint16_t PM1_PWRBTN_EN = (1 << 8);
static constexpr uint16_t PM1_SLPBTN_EN = (1 << 9); static constexpr uint16_t PM1_SLPBTN_EN = (1 << 9);
static constexpr uint16_t PM1_RTC_EN = (1 << 10); static constexpr uint16_t PM1_RTC_EN = (1 << 10);
// ── PM1 Control Register Bits ─────────────────────────────────── // ============================================================================
// PM1 Control Register Bits
// ============================================================================
static constexpr uint16_t PM1_SCI_EN = (1 << 0); static constexpr uint16_t PM1_SCI_EN = (1 << 0);
static constexpr uint16_t PM1_BM_RLD = (1 << 1); 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_TYP_MASK = 0x1C00; // bits 10-12
static constexpr uint16_t PM1_SLP_EN = (1 << 13); static constexpr uint16_t PM1_SLP_EN = (1 << 13);
// ── CPU State (saved across S3) ───────────────────────────────── // ============================================================================
// CPU State (saved across S3)
// ============================================================================
// Layout must match S3Wake.asm offsets exactly. // Layout must match S3Wake.asm offsets exactly.
// No FPU/SSE state — the kernel is compiled with -mno-sse. // No FPU/SSE state — the kernel is compiled with -mno-sse.
struct CpuState { struct CpuState {
@@ -71,8 +91,11 @@ namespace Hal {
uint8_t IdtPtr[10]; // 0xB2 uint8_t IdtPtr[10]; // 0xB2
} __attribute__((aligned(16))); } __attribute__((aligned(16)));
// ── Sleep API ─────────────────────────────────────────────────── // ============================================================================
// Sleep API
// ============================================================================
// Initialize sleep support. Called during boot after ACPI init. // Initialize sleep support. Called during boot after ACPI init.
// xsdt is used to read FADT for FACS and PM register addresses. // xsdt is used to read FADT for FACS and PM register addresses.
void Initialize(ACPI::CommonSDTHeader* xsdt); void Initialize(ACPI::CommonSDTHeader* xsdt);
+120 -9
View File
@@ -19,8 +19,44 @@ S3TrampolineStart:
cli cli
cld cld
; Set up DS immediately so we can access the data area
mov ax, 0x0800 mov ax, 0x0800
mov ds, ax mov ds, ax
; Clear SLP_EN + SLP_TYP in PM1 control registers ASAP.
; The chipset may still have these set from S3 entry and will
; re-enter sleep if we don't clear them immediately.
mov dx, [ds:data_pm1a_ctrl - S3TrampolineStart]
or dx, dx
jz .no_pm1a_16
in ax, dx
and ax, 0xC3FF ; clear SLP_EN (bit 13) + SLP_TYP (bits 12:10)
out dx, ax
.no_pm1a_16:
mov dx, [ds:data_pm1b_ctrl - S3TrampolineStart]
or dx, dx
jz .no_pm1b_16
in ax, dx
and ax, 0xC3FF
out dx, ax
.no_pm1b_16:
; Progress: reached 16-bit trampoline (write 0xA1 to CMOS 0x72)
mov al, 0xF2 ; register 0x72 | 0x80 (NMI disable)
out 0x70, al
mov al, 0xA1
out 0x71, al
; Enable A20 gate via fast A20 (port 0x92). On real hardware, firmware
; may leave A20 disabled after S3 wake, causing odd-megabyte addresses
; to wrap. QEMU always has A20 enabled, masking this bug.
in al, 0x92
or al, 2 ; set A20 enable bit
and al, 0xFE ; clear bit 0 (system reset)
out 0x92, al
; Set up remaining segments and stack (DS already set above for PM1 clear)
mov ax, 0x0800
mov es, ax mov es, ax
mov ss, ax mov ss, ax
mov sp, 0x00F0 mov sp, 0x00F0
@@ -35,6 +71,12 @@ S3TrampolineStart:
[bits 32] [bits 32]
pm32_entry: pm32_entry:
; Progress: reached 32-bit mode (0xA2)
mov al, 0xF2
out 0x70, al
mov al, 0xA2
out 0x71, al
mov ax, 0x10 mov ax, 0x10
mov ds, ax mov ds, ax
mov es, ax mov es, ax
@@ -50,18 +92,32 @@ pm32_entry:
mov eax, [0x8000 + data_cr3 - S3TrampolineStart] mov eax, [0x8000 + data_cr3 - S3TrampolineStart]
mov cr3, eax mov cr3, eax
mov ecx, 0xC0000080 mov ecx, 0xC0000080 ; IA32_EFER
rdmsr rdmsr
or eax, (1 << 8) or eax, (1 << 8) ; LME (Long Mode Enable)
or eax, (1 << 11) ; NXE (No-Execute Enable) — kernel page tables
; use NX bits; without NXE, bit 63 in PTEs is
; reserved and any page walk through an NX entry
; triggers a reserved-bit #PF → triple fault.
or eax, (1 << 0) ; SCE (Syscall Enable) — must be set before
; returning to code that may handle syscalls.
wrmsr wrmsr
mov eax, cr0 mov eax, cr0
or eax, (1 << 31) or eax, (1 << 31)
mov cr0, eax mov cr0, eax
; Progress: entering long mode (0xA3)
mov al, 0xF2
out 0x70, al
mov al, 0xA3
out 0x71, al
jmp dword 0x18:(0x8000 + lm64_common - S3TrampolineStart) jmp dword 0x18:(0x8000 + lm64_common - S3TrampolineStart)
; ── Temporary GDT ────────────────────────────────────────────────────── ; ═════════════════════════════════════════════════════════════════════════════
; Temporary GDT
; ═════════════════════════════════════════════════════════════════════════════
align 16 align 16
gdt_start: gdt_start:
dq 0x0000000000000000 ; 0x00: Null dq 0x0000000000000000 ; 0x00: Null
@@ -75,6 +131,11 @@ gdt_ptr:
dw gdt_end - gdt_start - 1 dw gdt_end - gdt_start - 1
dd 0x8000 + gdt_start - S3TrampolineStart dd 0x8000 + gdt_start - S3TrampolineStart
; 64-bit GDT pointer (lgdt in long mode reads 10 bytes: 2-byte limit + 8-byte base)
gdt_ptr64:
dw gdt_end - gdt_start - 1
dq 0x8000 + gdt_start - S3TrampolineStart
; ═════════════════════════════════════════════════════════════════════════ ; ═════════════════════════════════════════════════════════════════════════
; 64-bit entry at offset 0x100 (X_FirmwareWakingVector = 0x8100) ; 64-bit entry at offset 0x100 (X_FirmwareWakingVector = 0x8100)
@@ -88,14 +149,62 @@ S3Trampoline64:
cli cli
cld cld
; Load kernel PML4 (full 64-bit) - firmware identity-maps low memory ; CRITICAL: Clear SLP_EN + SLP_TYP immediately (same as 16-bit path)
mov dx, [0x8000 + data_pm1a_ctrl - S3TrampolineStart]
or dx, dx
jz .no_pm1a_64
in ax, dx
and ax, 0xC3FF
out dx, ax
.no_pm1a_64:
mov dx, [0x8000 + data_pm1b_ctrl - S3TrampolineStart]
or dx, dx
jz .no_pm1b_64
in ax, dx
and ax, 0xC3FF
out dx, ax
.no_pm1b_64:
; Progress: reached 64-bit trampoline (0xB1)
mov al, 0xF2
out 0x70, al
mov al, 0xB1
out 0x71, al
; Enable NXE and SCE in EFER before loading kernel CR3.
; Kernel page tables use NX bits — without NXE, loading CR3
; would cause reserved-bit #PF on first page walk.
mov ecx, 0xC0000080 ; IA32_EFER
rdmsr
or eax, (1 << 11) ; NXE
or eax, (1 << 0) ; SCE
wrmsr
; Load kernel PML4 (full 64-bit) - firmware identity-maps low memory.
; Do NOT load the trampoline GDT here — UEFI's CS selector (e.g. 0x38)
; would be out of bounds in our small GDT, causing #GP on the next
; instruction fetch. UEFI's segments are fine for these few instructions;
; AcpiResumeLongMode will load the kernel GDT and reload all segments.
mov rax, [0x8000 + data_cr3 - S3TrampolineStart] mov rax, [0x8000 + data_cr3 - S3TrampolineStart]
mov cr3, rax mov cr3, rax
; Fall through to common path ; Load state pointer and jump to resume (same as lm64_common below,
; but we skip the segment reload which needs the trampoline GDT).
mov rdi, [0x8000 + data_state_ptr - S3TrampolineStart]
mov rax, [0x8000 + data_resume_addr - S3TrampolineStart]
jmp rax
; ── Common 64-bit path (both entries converge here) ────────────────── ; ═════════════════════════════════════════════════════════════════════════════
; Common 64-bit path for 16-bit → 32-bit → 64-bit transition
; ═════════════════════════════════════════════════════════════════════════════
; The real-mode path arrives here with the trampoline GDT active,
; so selector 0x20 (64-bit data) is valid.
lm64_common: lm64_common:
mov ax, 0x20
mov ds, ax
mov es, ax
mov ss, ax
mov rdi, [0x8000 + data_state_ptr - S3TrampolineStart] mov rdi, [0x8000 + data_state_ptr - S3TrampolineStart]
mov rax, [0x8000 + data_resume_addr - S3TrampolineStart] mov rax, [0x8000 + data_resume_addr - S3TrampolineStart]
jmp rax jmp rax
@@ -107,9 +216,11 @@ lm64_common:
align 8 align 8
global S3TrampolineData global S3TrampolineData
S3TrampolineData: S3TrampolineData:
data_cr3: dq 0 ; kernel PML4 physical address (full 64-bit) data_cr3: dq 0 ; +0: kernel PML4 physical address (full 64-bit)
data_state_ptr: dq 0 ; virtual address of CpuState data_state_ptr: dq 0 ; +8: virtual address of CpuState
data_resume_addr: dq 0 ; virtual address of AcpiResumeLongMode data_resume_addr: dq 0 ; +16: virtual address of AcpiResumeLongMode
data_pm1a_ctrl: dw 0 ; +24: PM1a control block I/O port
data_pm1b_ctrl: dw 0 ; +26: PM1b control block I/O port
global S3TrampolineEnd global S3TrampolineEnd
S3TrampolineEnd: S3TrampolineEnd:
+52 -7
View File
@@ -7,7 +7,9 @@
[bits 64] [bits 64]
section .text section .text
; ─── AcpiSaveAndSuspend ────────────────────────────────────────────────── ; ═════════════════════════════════════════════════════════════════════════════
; AcpiSaveAndSuspend
; ═════════════════════════════════════════════════════════════════════════════
; extern "C" int AcpiSaveAndSuspend(CpuState* stateArea) ; extern "C" int AcpiSaveAndSuspend(CpuState* stateArea)
; rdi = pointer to CpuState structure ; rdi = pointer to CpuState structure
; ;
@@ -77,7 +79,9 @@ AcpiSaveAndSuspend:
ret ret
; ─── AcpiWakeEntry ─────────────────────────────────────────────────────── ; ═════════════════════════════════════════════════════════════════════════════
; AcpiWakeEntry
; ═════════════════════════════════════════════════════════════════════════════
; This is the actual waking vector target. Firmware jumps here after S3 ; This is the actual waking vector target. Firmware jumps here after S3
; resume. It loads the CpuState pointer from a fixed location (set before ; resume. It loads the CpuState pointer from a fixed location (set before
; suspend) and falls through to AcpiResumeLongMode. ; suspend) and falls through to AcpiResumeLongMode.
@@ -103,14 +107,18 @@ AcpiWakeEntry:
jmp AcpiResumeLongMode jmp AcpiResumeLongMode
; ─── Well-known pointer to CpuState (set before suspend) ──────────────── ; ═════════════════════════════════════════════════════════════════════════════
; Well-known pointer to CpuState (set before suspend)
; ═════════════════════════════════════════════════════════════════════════════
; Placed in .text so AcpiWakeEntry can use RIP-relative addressing ; Placed in .text so AcpiWakeEntry can use RIP-relative addressing
; without a cross-section relocation warning. ; without a cross-section relocation warning.
; extern "C" void* g_wakeStatePtr; ; extern "C" void* g_wakeStatePtr;
global g_wakeStatePtr global g_wakeStatePtr
g_wakeStatePtr: dq 0 g_wakeStatePtr: dq 0
; ─── AcpiResumeLongMode ────────────────────────────────────────────────── ; ═════════════════════════════════════════════════════════════════════════════
; AcpiResumeLongMode
; ═════════════════════════════════════════════════════════════════════════════
; extern "C" void AcpiResumeLongMode(CpuState* stateArea) ; extern "C" void AcpiResumeLongMode(CpuState* stateArea)
; rdi = pointer to CpuState structure ; rdi = pointer to CpuState structure
; ;
@@ -119,6 +127,12 @@ g_wakeStatePtr: dq 0
; ;
global AcpiResumeLongMode global AcpiResumeLongMode
AcpiResumeLongMode: AcpiResumeLongMode:
; Progress: reached AcpiResumeLongMode (0xC1)
mov al, 0xF2
out 0x70, al
mov al, 0xC1
out 0x71, al
; At entry: rdi = CpuState*, RSP = trampoline stack (unmapped junk), ; At entry: rdi = CpuState*, RSP = trampoline stack (unmapped junk),
; CR3 = kernel PML4 (loaded by trampoline), CS = trampoline selector. ; CR3 = kernel PML4 (loaded by trampoline), CS = trampoline selector.
; ;
@@ -140,6 +154,12 @@ AcpiResumeLongMode:
retfq retfq
.reload_cs: .reload_cs:
; Progress: GDT + CS reloaded (0xC2)
mov al, 0xF2
out 0x70, al
mov al, 0xC2
out 0x71, al
; Reload data segment registers with kernel data selector ; Reload data segment registers with kernel data selector
mov ax, 0x10 mov ax, 0x10
mov ds, ax mov ds, ax
@@ -151,11 +171,28 @@ AcpiResumeLongMode:
; Restore IDT ; Restore IDT
lidt [rdi + 0xB2] lidt [rdi + 0xB2]
; Restore CR4 before CR3 — PGE, PAE, OSFXSR etc. must be set before
; the page table switch. The trampoline only set minimal bits (PAE).
; Real hardware needs the full saved CR4 (especially PGE for TLB
; correctness and WP in CR0 for write-protect enforcement).
mov rax, [rdi + 0xA0]
mov cr4, rax
; Restore CR0 (WP, NE, etc.)
mov rax, [rdi + 0x98]
mov cr0, rax
; Restore saved CR3 (process PML4). The trampoline used the kernel ; Restore saved CR3 (process PML4). The trampoline used the kernel
; master PML4 to get here; now switch to the actual saved context. ; master PML4 to get here; now switch to the actual saved context.
mov rax, [rdi + 0x90] mov rax, [rdi + 0x90]
mov cr3, rax mov cr3, rax
; Progress: IDT + CR4/CR0/CR3 restored (0xC3)
mov al, 0xF2
out 0x70, al
mov al, 0xC3
out 0x71, al
; Restore general-purpose registers (skip RSP — already restored above) ; Restore general-purpose registers (skip RSP — already restored above)
mov rbx, [rdi + 0x08] mov rbx, [rdi + 0x08]
mov rcx, [rdi + 0x10] mov rcx, [rdi + 0x10]
@@ -176,9 +213,17 @@ AcpiResumeLongMode:
push rax push rax
popfq popfq
; Progress: about to call AcpiResumeEntry (0xC4)
mov al, 0xF2
out 0x70, al
mov al, 0xC4
out 0x71, al
; Restore rdi last ; Restore rdi last
mov rdi, [rdi + 0x28] mov rdi, [rdi + 0x28]
; Return 0 = "resumed from S3" ; Instead of returning to Suspend() via ret (which is fragile due to
xor rax, rax ; compiler stack frame assumptions), jump directly to a dedicated C
ret ; resume function. This is how Linux and other kernels handle S3 resume.
extern AcpiResumeEntry
jmp AcpiResumeEntry
+74 -56
View File
@@ -6,72 +6,52 @@
#include "Panic.hpp" #include "Panic.hpp"
#include "../CppLib/BoxUI.hpp" #include "../CppLib/BoxUI.hpp"
void Panic(const char *meditationString, System::PanicFrame* frame) { static constexpr int BoxWidth = 72;
const int boxWidth = 72;
// Header static void PrintHorizontalEdge(const char* left, const char* right) {
kerr << BOXUI_ANSI_RED_BG << BOXUI_ANSI_WHITE_FG << BOXUI_ANSI_BOLD << "\n"; kerr << left;
kerr << BOXUI_TL; for (int i = 0; i < BoxWidth - 2; ++i) kerr << BOXUI_H;
for (int i = 0; i < boxWidth - 2; ++i) kerr << BOXUI_H; kerr << right << "\n";
kerr << BOXUI_TR << "\n"; }
PrintBoxedLine(kerr, "!!! KERNEL PANIC !!!", boxWidth, true);
PrintBoxedLine(kerr, "", boxWidth);
PrintBoxedLine(kerr, "System halted. Please reboot.", boxWidth, true);
PrintBoxedLine(kerr, "", boxWidth);
PrintBoxedSeparator(kerr, boxWidth);
PrintBoxedLine(kerr, "Meditation:", boxWidth, true);
PrintBoxedLine(kerr, meditationString, boxWidth);
PrintBoxedLine(kerr, "", boxWidth);
#if defined (__x86_64__) static void PrintPageFaultInfo(System::PanicFrame*& frame) {
if (frame != nullptr) { auto* pf = (System::PageFaultPanicFrame*)frame;
PrintBoxedSeparator(kerr, boxWidth); frame = (System::PanicFrame*)&pf->IP;
PrintBoxedLine(kerr, "CPU State:", boxWidth, true);
PrintBoxedHex(kerr, "Interrupt Vector", frame->InterruptVector, boxWidth);
if (frame->InterruptVector == 0xE) {
auto pf_frame = (System::PageFaultPanicFrame*)frame;
frame = (System::PanicFrame*)&pf_frame->IP;
// CR2 holds the faulting virtual address for page faults
uint64_t cr2; uint64_t cr2;
asm volatile("mov %%cr2, %0" : "=r"(cr2)); asm volatile("mov %%cr2, %0" : "=r"(cr2));
PrintBoxedHex(kerr, "Faulting Address (CR2)", cr2, boxWidth); PrintBoxedHex(kerr, "Faulting Address (CR2)", cr2, BoxWidth);
PrintBoxedLine(kerr, "Page Fault Error:", boxWidth, true); PrintBoxedLine(kerr, "Page Fault Error:", BoxWidth, true);
PrintBoxedDec(kerr, "Present", pf_frame->PageFaultError.Present, boxWidth); PrintBoxedDec(kerr, "Present", pf->PageFaultError.Present, BoxWidth);
PrintBoxedDec(kerr, "Write", pf_frame->PageFaultError.Write, boxWidth); PrintBoxedDec(kerr, "Write", pf->PageFaultError.Write, BoxWidth);
PrintBoxedDec(kerr, "User", pf_frame->PageFaultError.User, boxWidth); PrintBoxedDec(kerr, "User", pf->PageFaultError.User, BoxWidth);
PrintBoxedDec(kerr, "Reserved Write", pf_frame->PageFaultError.ReservedWrite, boxWidth); PrintBoxedDec(kerr, "Reserved Write", pf->PageFaultError.ReservedWrite, BoxWidth);
PrintBoxedDec(kerr, "Instruction Fetch", pf_frame->PageFaultError.InstructionFetch, boxWidth); PrintBoxedDec(kerr, "Instruction Fetch",pf->PageFaultError.InstructionFetch, BoxWidth);
PrintBoxedDec(kerr, "Protection Key", pf_frame->PageFaultError.ProtectionKey, boxWidth); PrintBoxedDec(kerr, "Protection Key", pf->PageFaultError.ProtectionKey, BoxWidth);
PrintBoxedDec(kerr, "Shadow Stack", pf_frame->PageFaultError.ShadowStack, boxWidth); PrintBoxedDec(kerr, "Shadow Stack", pf->PageFaultError.ShadowStack, BoxWidth);
PrintBoxedDec(kerr, "SGX", pf_frame->PageFaultError.SGX, boxWidth); PrintBoxedDec(kerr, "SGX", pf->PageFaultError.SGX, BoxWidth);
} else if (frame->InterruptVector == 0xD) { }
auto gpf_frame = (System::GPFPanicFrame*)frame;
frame = (System::PanicFrame*)&frame->IP;
PrintBoxedLine(kerr, "General Protection Fault:", boxWidth, true);
PrintBoxedDec(kerr, "Error Code", gpf_frame->GeneralProtectionFaultError, boxWidth);
}
PrintBoxedSeparator(kerr, boxWidth); static void PrintGPFInfo(System::PanicFrame*& frame) {
PrintBoxedLine(kerr, "Registers:", boxWidth, true); auto* gpf = (System::GPFPanicFrame*)frame;
PrintBoxedHex(kerr, "Instruction Pointer", frame->IP, boxWidth); frame = (System::PanicFrame*)&gpf->IP;
PrintBoxedHex(kerr, "Code Segment", frame->CS, boxWidth);
PrintBoxedHex(kerr, "Flags", frame->Flags, boxWidth);
PrintBoxedHex(kerr, "Stack Pointer", frame->SP, boxWidth);
PrintBoxedHex(kerr, "Stack Segment", frame->SS, boxWidth);
}
#endif
PrintBoxedLine(kerr, "", boxWidth); PrintBoxedLine(kerr, "General Protection Fault:", BoxWidth, true);
PrintBoxedDec(kerr, "Error Code", gpf->GeneralProtectionFaultError, BoxWidth);
}
// Footer static void PrintRegisters(System::PanicFrame* frame) {
kerr << BOXUI_BL; PrintBoxedSeparator(kerr, BoxWidth);
for (int i = 0; i < boxWidth - 2; ++i) kerr << BOXUI_H; PrintBoxedLine(kerr, "Registers:", BoxWidth, true);
kerr << BOXUI_BR << "\n"; PrintBoxedHex(kerr, "Instruction Pointer", frame->IP, BoxWidth);
kerr << BOXUI_ANSI_RESET; PrintBoxedHex(kerr, "Code Segment", frame->CS, BoxWidth);
PrintBoxedHex(kerr, "Flags", frame->Flags, BoxWidth);
PrintBoxedHex(kerr, "Stack Pointer", frame->SP, BoxWidth);
PrintBoxedHex(kerr, "Stack Segment", frame->SS, BoxWidth);
}
[[noreturn]] static void Halt() {
while (true) { while (true) {
#if defined (__x86_64__) #if defined (__x86_64__)
asm ("cli"); asm ("cli");
@@ -83,3 +63,41 @@ void Panic(const char *meditationString, System::PanicFrame* frame) {
#endif #endif
} }
} }
void Panic(const char *meditationString, System::PanicFrame* frame) {
// Header
kerr << BOXUI_ANSI_RED_BG << BOXUI_ANSI_WHITE_FG << BOXUI_ANSI_BOLD << "\n";
PrintHorizontalEdge(BOXUI_TL, BOXUI_TR);
PrintBoxedLine(kerr, "!!! KERNEL PANIC !!!", BoxWidth, true);
PrintBoxedLine(kerr, "", BoxWidth);
PrintBoxedLine(kerr, "System halted. Please reboot.", BoxWidth, true);
PrintBoxedLine(kerr, "", BoxWidth);
// Meditation string
PrintBoxedSeparator(kerr, BoxWidth);
PrintBoxedLine(kerr, meditationString, BoxWidth);
PrintBoxedLine(kerr, "", BoxWidth);
// CPU state (x86_64 only)
#if defined (__x86_64__)
if (frame != nullptr) {
PrintBoxedSeparator(kerr, BoxWidth);
PrintBoxedLine(kerr, "CPU State:", BoxWidth, true);
PrintBoxedHex(kerr, "Interrupt Vector", frame->InterruptVector, BoxWidth);
if (frame->InterruptVector == 0xE)
PrintPageFaultInfo(frame);
else if (frame->InterruptVector == 0xD)
PrintGPFInfo(frame);
PrintRegisters(frame);
}
#endif
// Footer
PrintBoxedLine(kerr, "", BoxWidth);
PrintHorizontalEdge(BOXUI_BL, BOXUI_BR);
kerr << BOXUI_ANSI_RESET;
Halt();
}
+30
View File
@@ -11,6 +11,7 @@
#include <Platform/Registers.hpp> #include <Platform/Registers.hpp>
#include <CppLib/Stream.hpp> #include <CppLib/Stream.hpp>
#include <Memory/PageFrameAllocator.hpp> #include <Memory/PageFrameAllocator.hpp>
#include <Sched/Scheduler.hpp>
namespace Hal { namespace Hal {
constexpr auto InterruptGate = 0x8E; constexpr auto InterruptGate = 0x8E;
@@ -53,12 +54,41 @@ namespace Hal {
"Reserved" "Reserved"
}; };
// Exceptions that push a hardware error code before IP/CS/FLAGS/SP/SS
static bool ExceptionHasErrorCode(uint8_t vector) {
return vector == 8 || (vector >= 10 && vector <= 14)
|| vector == 17 || vector == 21 || vector == 29 || vector == 30;
}
// Extract CS from the interrupt frame. For error-code exceptions, the
// error code sits at offset 0, shifting IP to +8 and CS to +16.
static uint64_t GetExceptionCS(uint8_t vector, System::PanicFrame* frame) {
if (ExceptionHasErrorCode(vector)) {
return *(uint64_t*)((uint8_t*)frame + 16);
}
return frame->CS;
}
template<size_t i> template<size_t i>
__attribute__((interrupt)) void ExceptionHandler(System::PanicFrame* frame) __attribute__((interrupt)) void ExceptionHandler(System::PanicFrame* frame)
{ {
uint64_t cs = GetExceptionCS(i, frame);
// If the fault originated in user-mode (ring 3), kill the process
// instead of panicking the entire system.
if ((cs & 3) == 3 && Sched::GetCurrentPid() >= 0) {
auto* proc = Sched::GetCurrentProcessPtr();
Kt::KernelLogStream(Kt::ERROR, "Exception")
<< ExceptionStrings[i] << " in process \""
<< proc->name << "\" (pid " << proc->pid
<< ") - process terminated";
Sched::ExitProcess();
__builtin_unreachable();
} else {
frame->InterruptVector = i; frame->InterruptVector = i;
Panic(ExceptionStrings[i], frame); Panic(ExceptionStrings[i], frame);
} }
}
void LoadIDT(IDTRStruct& idtr) { void LoadIDT(IDTRStruct& idtr) {
asm("lidt %0" : : "m"(idtr)); asm("lidt %0" : : "m"(idtr));
+1 -1
View File
@@ -206,7 +206,7 @@ namespace Montauk {
}; };
struct DevInfo { struct DevInfo {
uint8_t category; // 0=CPU, 1=Interrupt, 2=Timer, 3=Input, 4=USB, 5=Network, 6=Display, 7=Storage, 8=PCI uint8_t category; // 0=CPU, 1=Interrupt, 2=Timer, 3=Input, 4=USB, 5=Network, 6=Display, 7=Storage, 8=PCI, 9=Audio, 10=ACPI
uint8_t _pad[3]; uint8_t _pad[3];
char name[48]; char name[48];
char detail[48]; char detail[48];
+1 -1
View File
@@ -320,7 +320,7 @@ void desktop_draw_net_popup(DesktopState* ds) {
rows[0].label = "IP"; rows[0].label = "IP";
if (connected) format_ip(rows[0].value, nc.ipAddress); if (connected) format_ip(rows[0].value, nc.ipAddress);
else montauk::strcpy(rows[0].value, "\xE2\x80\x94"); // em dash else montauk::strcpy(rows[0].value, "0.0.0.0");
rows[1].label = "Subnet"; rows[1].label = "Subnet";
format_ip(rows[1].value, nc.subnetMask); format_ip(rows[1].value, nc.subnetMask);
+3
View File
@@ -238,8 +238,11 @@ static bool copy_recursive(const char* src_dir, const char* dst_dir,
} else { } else {
// Skip ramdisk and limine.conf — installed system boots from // Skip ramdisk and limine.conf — installed system boots from
// disk and gets a fresh config without the ramdisk module. // disk and gets a fresh config without the ramdisk module.
// Skip setup.toml — live/setup environment config that should
// not be present on the installed system.
if (strcmp(basename, "ramdisk.tar") == 0) continue; if (strcmp(basename, "ramdisk.tar") == 0) continue;
if (strcmp(basename, "limine.conf") == 0) continue; if (strcmp(basename, "limine.conf") == 0) continue;
if (strcmp(basename, "setup.toml") == 0) continue;
// It's a file — copy it // It's a file — copy it
char src_path[256]; char src_path[256];
+32
View File
@@ -751,6 +751,38 @@ extern "C" void _start() {
// Load wallpaper from system desktop config // Load wallpaper from system desktop config
load_login_wallpaper(ls); load_login_wallpaper(ls);
// Check for setup environment (installation medium / ramdisk boot).
// If setup.toml exists with environment.mode = "setup", skip login
// entirely and launch the desktop in a passwordless live session.
{
auto doc = montauk::config::load("setup");
const char* mode = doc.get_string("environment.mode", "");
if (montauk::streq(mode, "setup")) {
const char* user = doc.get_string("session.username", "liveuser");
const char* display = doc.get_string("session.display_name", "Live User");
const char* role = doc.get_string("session.role", "admin");
// Create a temporary user entry so the desktop and apps can
// query session info normally.
montauk::fmkdir("0:/users");
montauk::user::create_user(user, display, "", role);
montauk::user::set_session(user);
doc.destroy();
// Launch desktop directly -- no login required
int pid = montauk::spawn("0:/os/desktop.elf", user);
if (pid >= 0) {
montauk::waitpid(pid);
}
// Desktop exited (reboot/shutdown expected in setup mode).
// Clear session and fall through to normal login in case
// setup.toml was removed during installation.
montauk::user::clear_session();
} else {
doc.destroy();
}
}
// Check if users.toml exists (first boot detection) // Check if users.toml exists (first boot detection)
int fh = montauk::open("0:/config/users.toml"); int fh = montauk::open("0:/config/users.toml");
if (fh < 0) { if (fh < 0) {
+40
View File
@@ -84,6 +84,46 @@ void px_fill_rounded(uint32_t* px, int bw, int bh,
} }
} }
void px_line(uint32_t* px, int bw, int bh,
int x0, int y0, int x1, int y1, int thick, Color c) {
if (thick < 1) thick = 1;
int half = thick / 2;
// Horizontal line
if (y0 == y1) {
int lx = x0 < x1 ? x0 : x1;
int rx = x0 > x1 ? x0 : x1;
px_fill(px, bw, bh, lx, y0 - half, rx - lx + 1, thick, c);
return;
}
// Vertical line
if (x0 == x1) {
int ty = y0 < y1 ? y0 : y1;
int by = y0 > y1 ? y0 : y1;
px_fill(px, bw, bh, x0 - half, ty, thick, by - ty + 1, c);
return;
}
// General case: Bresenham with thickness
uint32_t v = c.to_pixel();
int dx = x1 - x0; int sx = dx > 0 ? 1 : -1; if (dx < 0) dx = -dx;
int dy = y1 - y0; int sy = dy > 0 ? 1 : -1; if (dy < 0) dy = -dy;
int err = dx - dy;
int cx = x0, cy = y0;
while (true) {
for (int oy = -half; oy <= half; oy++) {
for (int ox = -half; ox <= half; ox++) {
int px_x = cx + ox, py_y = cy + oy;
if (px_x >= 0 && px_x < bw && py_y >= 0 && py_y < bh)
px[py_y * bw + px_x] = v;
}
}
if (cx == x1 && cy == y1) break;
int e2 = 2 * err;
if (e2 > -dy) { err -= dy; cx += sx; }
if (e2 < dx) { err += dx; cy += sy; }
}
}
int str_len(const char* s) { int str_len(const char* s) {
int n = 0; int n = 0;
while (s[n]) n++; while (s[n]) n++;
+348 -4
View File
@@ -36,6 +36,32 @@ static int next_token(const uint8_t* d, int len, int p, Token* tok) {
if (p >= len) { tok->type = TOK_EOF; return p; } if (p >= len) { tok->type = TOK_EOF; return p; }
// Dictionary << ... >> (e.g., marked-content properties in tagged PDFs)
if (d[p] == '<' && p + 1 < len && d[p + 1] == '<') {
p += 2;
int depth = 1;
while (p + 1 < len && depth > 0) {
if (d[p] == '<' && d[p + 1] == '<') { depth++; p += 2; }
else if (d[p] == '>' && d[p + 1] == '>') { depth--; p += 2; }
else p++;
}
if (p < len && depth > 0) p++; // skip stray last char on malformed input
tok->type = TOK_NAME;
tok->str[0] = '\0';
tok->str_len = 0;
return p;
}
// Stray > or >> (skip gracefully)
if (d[p] == '>') {
p++;
if (p < len && d[p] == '>') p++;
tok->type = TOK_NAME;
tok->str[0] = '\0';
tok->str_len = 0;
return p;
}
// Literal string // Literal string
if (d[p] == '(') { if (d[p] == '(') {
p++; p++;
@@ -249,6 +275,65 @@ static void add_text_item(PdfPage* page, float x, float y, float size,
item->text[copy] = '\0'; item->text[copy] = '\0';
} }
// ============================================================================
// Graphics State
// ============================================================================
static constexpr int MAX_PATH_SEGS = 512;
static constexpr int MAX_PATH_RECTS = 128;
static constexpr int MAX_GFX_STACK = 16;
struct PathSeg { float x1, y1, x2, y2; };
struct PathRect { float x, y, w, h; };
struct GfxState {
float ctm[6]; // current transformation matrix
float line_width;
uint8_t stroke_r, stroke_g, stroke_b;
uint8_t fill_r, fill_g, fill_b;
};
static void ctm_transform(const float* ctm, float x, float y, float* ox, float* oy) {
*ox = ctm[0] * x + ctm[2] * y + ctm[4];
*oy = ctm[1] * x + ctm[3] * y + ctm[5];
}
static float ctm_scale(const float* ctm) {
// Approximate scale factor (geometric mean of axis scales)
float sx = ctm[0] * ctm[0] + ctm[1] * ctm[1];
float sy = ctm[2] * ctm[2] + ctm[3] * ctm[3];
// sqrt approximation: just use max for simplicity
if (sx < sy) sx = sy;
// Manual sqrt via Newton's method (2 iterations)
if (sx <= 0) return 1.0f;
float g = sx;
g = 0.5f * (g + sx / g);
g = 0.5f * (g + sx / g);
return g;
}
static void add_gfx_item(PdfPage* page, GfxType type,
float x1, float y1, float x2, float y2,
float lw, uint8_t r, uint8_t g, uint8_t b) {
if (page->gfx_count >= page->gfx_cap) {
int new_cap = page->gfx_cap ? page->gfx_cap * 2 : 64;
GraphicsItem* ni = (GraphicsItem*)montauk::malloc(new_cap * sizeof(GraphicsItem));
if (!ni) return;
if (page->gfx_items) {
montauk::memcpy(ni, page->gfx_items, page->gfx_count * sizeof(GraphicsItem));
montauk::mfree(page->gfx_items);
}
page->gfx_items = ni;
page->gfx_cap = new_cap;
}
GraphicsItem* item = &page->gfx_items[page->gfx_count++];
item->type = type;
item->x1 = x1; item->y1 = y1;
item->x2 = x2; item->y2 = y2;
item->line_width = lw;
item->r = r; item->g = g; item->b = b;
}
static void matrix_multiply(float* result, const float* a, const float* b) { static void matrix_multiply(float* result, const float* a, const float* b) {
// Multiply two 3x3 matrices represented as [a b c d e f] // Multiply two 3x3 matrices represented as [a b c d e f]
// where the matrix is: [a b 0] // where the matrix is: [a b 0]
@@ -368,6 +453,24 @@ void parse_page(int page_idx, int page_obj_num) {
ts.tounicode = nullptr; ts.tounicode = nullptr;
ts.embedded_font = nullptr; ts.embedded_font = nullptr;
// Graphics state
GfxState gs;
gs.ctm[0] = 1; gs.ctm[1] = 0; gs.ctm[2] = 0; gs.ctm[3] = 1;
gs.ctm[4] = 0; gs.ctm[5] = 0;
gs.line_width = 1.0f;
gs.stroke_r = 0; gs.stroke_g = 0; gs.stroke_b = 0;
gs.fill_r = 0; gs.fill_g = 0; gs.fill_b = 0;
GfxState gs_stack[MAX_GFX_STACK];
int gs_depth = 0;
// Path accumulation
PathSeg* path_segs = (PathSeg*)montauk::malloc(MAX_PATH_SEGS * sizeof(PathSeg));
PathRect* path_rects = (PathRect*)montauk::malloc(MAX_PATH_RECTS * sizeof(PathRect));
int seg_count = 0, rect_count = 0;
float path_cx = 0, path_cy = 0; // current point
float path_sx = 0, path_sy = 0; // subpath start
bool in_text = false; bool in_text = false;
Token tok; Token tok;
int pos = 0; int pos = 0;
@@ -523,7 +626,9 @@ void parse_page(int page_idx, int page_obj_num) {
if (sy < 0) sy = -sy; if (sy < 0) sy = -sy;
if (sy > 0.01f) eff_size *= sy; if (sy > 0.01f) eff_size *= sy;
add_text_item(page, ts.tm[4], ts.tm[5], eff_size, float gx, gy;
ctm_transform(gs.ctm, ts.tm[4], ts.tm[5], &gx, &gy);
add_text_item(page, gx, gy, eff_size,
ops[0].str, ops[0].str_len, ts.font_flags, ts.tounicode, ts.embedded_font); ops[0].str, ops[0].str_len, ts.font_flags, ts.tounicode, ts.embedded_font);
// Advance text position using font metrics when available // Advance text position using font metrics when available
@@ -555,7 +660,9 @@ void parse_page(int page_idx, int page_obj_num) {
if (sy < 0) sy = -sy; if (sy < 0) sy = -sy;
if (sy > 0.01f) eff_size *= sy; if (sy > 0.01f) eff_size *= sy;
add_text_item(page, ts.tm[4], ts.tm[5], eff_size, float gx, gy;
ctm_transform(gs.ctm, ts.tm[4], ts.tm[5], &gx, &gy);
add_text_item(page, gx, gy, eff_size,
sop->str, sop->str_len, ts.font_flags, ts.tounicode, ts.embedded_font); sop->str, sop->str_len, ts.font_flags, ts.tounicode, ts.embedded_font);
TrueTypeFont* adv_font = ts.embedded_font; TrueTypeFont* adv_font = ts.embedded_font;
@@ -592,7 +699,9 @@ void parse_page(int page_idx, int page_obj_num) {
if (sy < 0) sy = -sy; if (sy < 0) sy = -sy;
if (sy > 0.01f) eff_size *= sy; if (sy > 0.01f) eff_size *= sy;
add_text_item(page, ts.tm[4], ts.tm[5], eff_size, float gx, gy;
ctm_transform(gs.ctm, ts.tm[4], ts.tm[5], &gx, &gy);
add_text_item(page, gx, gy, eff_size,
ops[0].str, ops[0].str_len, ts.font_flags, ts.tounicode, ts.embedded_font); ops[0].str, ops[0].str_len, ts.font_flags, ts.tounicode, ts.embedded_font);
} }
} }
@@ -611,15 +720,250 @@ void parse_page(int page_idx, int page_obj_num) {
if (sy < 0) sy = -sy; if (sy < 0) sy = -sy;
if (sy > 0.01f) eff_size *= sy; if (sy > 0.01f) eff_size *= sy;
add_text_item(page, ts.tm[4], ts.tm[5], eff_size, float gx, gy;
ctm_transform(gs.ctm, ts.tm[4], ts.tm[5], &gx, &gy);
add_text_item(page, gx, gy, eff_size,
ops[2].str, ops[2].str_len, ts.font_flags, ts.tounicode, ts.embedded_font); ops[2].str, ops[2].str_len, ts.font_flags, ts.tounicode, ts.embedded_font);
} }
} }
// ---- Graphics state operators ----
// q - save graphics state
else if (op[0] == 'q' && op[1] == '\0') {
if (gs_depth < MAX_GFX_STACK)
gs_stack[gs_depth++] = gs;
}
// Q - restore graphics state
else if (op[0] == 'Q' && op[1] == '\0') {
if (gs_depth > 0)
gs = gs_stack[--gs_depth];
}
// cm - concat matrix
else if (op[0] == 'c' && op[1] == 'm' && op[2] == '\0') {
if (op_count >= 6) {
float m[6] = { ops[0].num, ops[1].num, ops[2].num,
ops[3].num, ops[4].num, ops[5].num };
float r[6];
// new_ctm = m * old_ctm
r[0] = m[0]*gs.ctm[0] + m[1]*gs.ctm[2];
r[1] = m[0]*gs.ctm[1] + m[1]*gs.ctm[3];
r[2] = m[2]*gs.ctm[0] + m[3]*gs.ctm[2];
r[3] = m[2]*gs.ctm[1] + m[3]*gs.ctm[3];
r[4] = m[4]*gs.ctm[0] + m[5]*gs.ctm[2] + gs.ctm[4];
r[5] = m[4]*gs.ctm[1] + m[5]*gs.ctm[3] + gs.ctm[5];
for (int i = 0; i < 6; i++) gs.ctm[i] = r[i];
}
}
// w - set line width
else if (op[0] == 'w' && op[1] == '\0' && !in_text) {
if (op_count >= 1) gs.line_width = ops[0].num;
}
// ---- Color operators ----
// g - set fill gray
else if (op[0] == 'g' && op[1] == '\0') {
if (op_count >= 1) {
uint8_t v = (uint8_t)(ops[0].num * 255);
gs.fill_r = v; gs.fill_g = v; gs.fill_b = v;
}
}
// G - set stroke gray
else if (op[0] == 'G' && op[1] == '\0') {
if (op_count >= 1) {
uint8_t v = (uint8_t)(ops[0].num * 255);
gs.stroke_r = v; gs.stroke_g = v; gs.stroke_b = v;
}
}
// rg - set fill RGB
else if (op[0] == 'r' && op[1] == 'g' && op[2] == '\0') {
if (op_count >= 3) {
gs.fill_r = (uint8_t)(ops[0].num * 255);
gs.fill_g = (uint8_t)(ops[1].num * 255);
gs.fill_b = (uint8_t)(ops[2].num * 255);
}
}
// RG - set stroke RGB
else if (op[0] == 'R' && op[1] == 'G' && op[2] == '\0') {
if (op_count >= 3) {
gs.stroke_r = (uint8_t)(ops[0].num * 255);
gs.stroke_g = (uint8_t)(ops[1].num * 255);
gs.stroke_b = (uint8_t)(ops[2].num * 255);
}
}
// k - set fill CMYK (approximate as RGB)
else if (op[0] == 'k' && op[1] == '\0') {
if (op_count >= 4) {
float c_ = ops[0].num, m_ = ops[1].num, y_ = ops[2].num, k_ = ops[3].num;
gs.fill_r = (uint8_t)((1 - c_) * (1 - k_) * 255);
gs.fill_g = (uint8_t)((1 - m_) * (1 - k_) * 255);
gs.fill_b = (uint8_t)((1 - y_) * (1 - k_) * 255);
}
}
// K - set stroke CMYK
else if (op[0] == 'K' && op[1] == '\0') {
if (op_count >= 4) {
float c_ = ops[0].num, m_ = ops[1].num, y_ = ops[2].num, k_ = ops[3].num;
gs.stroke_r = (uint8_t)((1 - c_) * (1 - k_) * 255);
gs.stroke_g = (uint8_t)((1 - m_) * (1 - k_) * 255);
gs.stroke_b = (uint8_t)((1 - y_) * (1 - k_) * 255);
}
}
// ---- Path construction operators ----
// m - moveto
else if (op[0] == 'm' && op[1] == '\0' && !in_text) {
if (op_count >= 2) {
ctm_transform(gs.ctm, ops[0].num, ops[1].num, &path_cx, &path_cy);
path_sx = path_cx;
path_sy = path_cy;
}
}
// l - lineto
else if (op[0] == 'l' && op[1] == '\0' && !in_text) {
if (op_count >= 2 && seg_count < MAX_PATH_SEGS) {
float nx, ny;
ctm_transform(gs.ctm, ops[0].num, ops[1].num, &nx, &ny);
path_segs[seg_count].x1 = path_cx;
path_segs[seg_count].y1 = path_cy;
path_segs[seg_count].x2 = nx;
path_segs[seg_count].y2 = ny;
seg_count++;
path_cx = nx;
path_cy = ny;
}
}
// re - rectangle (x y w h)
else if (op[0] == 'r' && op[1] == 'e' && op[2] == '\0') {
if (op_count >= 4 && rect_count < MAX_PATH_RECTS) {
float rx = ops[0].num, ry = ops[1].num;
float rw = ops[2].num, rh = ops[3].num;
// Transform corners through CTM
float tx, ty;
ctm_transform(gs.ctm, rx, ry, &tx, &ty);
float tw = rw * ctm_scale(gs.ctm);
float th = rh * ctm_scale(gs.ctm);
path_rects[rect_count].x = tx;
path_rects[rect_count].y = ty;
path_rects[rect_count].w = tw;
path_rects[rect_count].h = th;
rect_count++;
// re also adds 4 line segments and sets current point
if (seg_count + 4 <= MAX_PATH_SEGS) {
float x0, y0, x1, y1, x2, y2, x3, y3;
ctm_transform(gs.ctm, rx, ry, &x0, &y0);
ctm_transform(gs.ctm, rx + rw, ry, &x1, &y1);
ctm_transform(gs.ctm, rx + rw, ry + rh, &x2, &y2);
ctm_transform(gs.ctm, rx, ry + rh, &x3, &y3);
path_segs[seg_count++] = {x0, y0, x1, y1};
path_segs[seg_count++] = {x1, y1, x2, y2};
path_segs[seg_count++] = {x2, y2, x3, y3};
path_segs[seg_count++] = {x3, y3, x0, y0};
}
path_cx = tx;
path_cy = ty;
path_sx = tx;
path_sy = ty;
}
}
// h - closepath
else if (op[0] == 'h' && op[1] == '\0') {
if (seg_count < MAX_PATH_SEGS &&
(path_cx != path_sx || path_cy != path_sy)) {
path_segs[seg_count].x1 = path_cx;
path_segs[seg_count].y1 = path_cy;
path_segs[seg_count].x2 = path_sx;
path_segs[seg_count].y2 = path_sy;
seg_count++;
path_cx = path_sx;
path_cy = path_sy;
}
}
// ---- Path painting operators ----
// S - stroke
else if (op[0] == 'S' && op[1] == '\0' && !in_text) {
float lw = gs.line_width * ctm_scale(gs.ctm);
for (int si = 0; si < seg_count; si++)
add_gfx_item(page, GFX_LINE,
path_segs[si].x1, path_segs[si].y1,
path_segs[si].x2, path_segs[si].y2,
lw, gs.stroke_r, gs.stroke_g, gs.stroke_b);
seg_count = 0;
rect_count = 0;
}
// s - close and stroke
else if (op[0] == 's' && op[1] == '\0' && !in_text) {
// Close first
if (seg_count < MAX_PATH_SEGS &&
(path_cx != path_sx || path_cy != path_sy)) {
path_segs[seg_count++] = {path_cx, path_cy, path_sx, path_sy};
}
float lw = gs.line_width * ctm_scale(gs.ctm);
for (int si = 0; si < seg_count; si++)
add_gfx_item(page, GFX_LINE,
path_segs[si].x1, path_segs[si].y1,
path_segs[si].x2, path_segs[si].y2,
lw, gs.stroke_r, gs.stroke_g, gs.stroke_b);
seg_count = 0;
rect_count = 0;
}
// f or F - fill
else if ((op[0] == 'f' || op[0] == 'F') &&
(op[1] == '\0' || (op[1] == '*' && op[2] == '\0')) && !in_text) {
for (int ri = 0; ri < rect_count; ri++)
add_gfx_item(page, GFX_RECT_FILL,
path_rects[ri].x, path_rects[ri].y,
path_rects[ri].w, path_rects[ri].h,
0, gs.fill_r, gs.fill_g, gs.fill_b);
seg_count = 0;
rect_count = 0;
}
// B or B* - fill then stroke
else if (op[0] == 'B' && (op[1] == '\0' || (op[1] == '*' && op[2] == '\0'))
&& !in_text) {
for (int ri = 0; ri < rect_count; ri++)
add_gfx_item(page, GFX_RECT_FILL,
path_rects[ri].x, path_rects[ri].y,
path_rects[ri].w, path_rects[ri].h,
0, gs.fill_r, gs.fill_g, gs.fill_b);
float lw = gs.line_width * ctm_scale(gs.ctm);
for (int si = 0; si < seg_count; si++)
add_gfx_item(page, GFX_LINE,
path_segs[si].x1, path_segs[si].y1,
path_segs[si].x2, path_segs[si].y2,
lw, gs.stroke_r, gs.stroke_g, gs.stroke_b);
seg_count = 0;
rect_count = 0;
}
// b or b* - close, fill then stroke
else if (op[0] == 'b' && (op[1] == '\0' || (op[1] == '*' && op[2] == '\0'))
&& !in_text) {
if (seg_count < MAX_PATH_SEGS &&
(path_cx != path_sx || path_cy != path_sy))
path_segs[seg_count++] = {path_cx, path_cy, path_sx, path_sy};
for (int ri = 0; ri < rect_count; ri++)
add_gfx_item(page, GFX_RECT_FILL,
path_rects[ri].x, path_rects[ri].y,
path_rects[ri].w, path_rects[ri].h,
0, gs.fill_r, gs.fill_g, gs.fill_b);
float lw = gs.line_width * ctm_scale(gs.ctm);
for (int si = 0; si < seg_count; si++)
add_gfx_item(page, GFX_LINE,
path_segs[si].x1, path_segs[si].y1,
path_segs[si].x2, path_segs[si].y2,
lw, gs.stroke_r, gs.stroke_g, gs.stroke_b);
seg_count = 0;
rect_count = 0;
}
// n - end path without painting (clipping only)
else if (op[0] == 'n' && op[1] == '\0' && !in_text) {
seg_count = 0;
rect_count = 0;
}
op_count = 0; // reset operand stack after operator op_count = 0; // reset operand stack after operator
} }
montauk::mfree(ops); montauk::mfree(ops);
if (path_segs) montauk::mfree(path_segs);
if (path_rects) montauk::mfree(path_rects);
montauk::mfree(stream_data); montauk::mfree(stream_data);
} }
+4
View File
@@ -887,6 +887,9 @@ static void add_page(int obj_num) {
g_doc.pages[g_doc.page_count].items = nullptr; g_doc.pages[g_doc.page_count].items = nullptr;
g_doc.pages[g_doc.page_count].item_count = 0; g_doc.pages[g_doc.page_count].item_count = 0;
g_doc.pages[g_doc.page_count].item_cap = 0; g_doc.pages[g_doc.page_count].item_cap = 0;
g_doc.pages[g_doc.page_count].gfx_items = nullptr;
g_doc.pages[g_doc.page_count].gfx_count = 0;
g_doc.pages[g_doc.page_count].gfx_cap = 0;
g_doc.pages[g_doc.page_count].width = 612; g_doc.pages[g_doc.page_count].width = 612;
g_doc.pages[g_doc.page_count].height = 792; g_doc.pages[g_doc.page_count].height = 792;
g_doc.page_count++; g_doc.page_count++;
@@ -1457,6 +1460,7 @@ void free_pdf() {
if (g_doc.pages) { if (g_doc.pages) {
for (int i = 0; i < g_doc.page_count; i++) { for (int i = 0; i < g_doc.page_count; i++) {
if (g_doc.pages[i].items) montauk::mfree(g_doc.pages[i].items); if (g_doc.pages[i].items) montauk::mfree(g_doc.pages[i].items);
if (g_doc.pages[i].gfx_items) montauk::mfree(g_doc.pages[i].gfx_items);
} }
montauk::mfree(g_doc.pages); montauk::mfree(g_doc.pages);
g_doc.pages = nullptr; g_doc.pages = nullptr;
+13
View File
@@ -66,10 +66,22 @@ struct TextItem {
TrueTypeFont* font; // embedded font, or nullptr for system font TrueTypeFont* font; // embedded font, or nullptr for system font
}; };
enum GfxType { GFX_LINE, GFX_RECT_FILL, GFX_RECT_STROKE };
struct GraphicsItem {
GfxType type;
float x1, y1, x2, y2; // LINE: endpoints; RECT: x,y,w,h (PDF coords)
float line_width;
uint8_t r, g, b;
};
struct PdfPage { struct PdfPage {
TextItem* items; TextItem* items;
int item_count; int item_count;
int item_cap; int item_cap;
GraphicsItem* gfx_items;
int gfx_count;
int gfx_cap;
float width, height; // page dimensions in points (from MediaBox) float width, height; // page dimensions in points (from MediaBox)
}; };
@@ -141,6 +153,7 @@ void px_hline(uint32_t* px, int bw, int bh, int x, int y, int w, Color c);
void px_vline(uint32_t* px, int bw, int bh, int x, int y, int h, Color c); void px_vline(uint32_t* px, int bw, int bh, int x, int y, int h, Color c);
void px_rect(uint32_t* px, int bw, int bh, int x, int y, int w, int h, Color c); void px_rect(uint32_t* px, int bw, int bh, int x, int y, int w, int h, Color c);
void px_fill_rounded(uint32_t* px, int bw, int bh, int x, int y, int w, int h, int r, Color c); void px_fill_rounded(uint32_t* px, int bw, int bh, int x, int y, int w, int h, int r, Color c);
void px_line(uint32_t* px, int bw, int bh, int x0, int y0, int x1, int y1, int thick, Color c);
int str_len(const char* s); int str_len(const char* s);
void str_cpy(char* dst, const char* src, int max); void str_cpy(char* dst, const char* src, int max);
+41 -2
View File
@@ -48,6 +48,38 @@ void render(uint32_t* pixels) {
pixels[row * g_win_w + col] = white; pixels[row * g_win_w + col] = white;
} }
// Draw graphics items (lines, filled rectangles)
for (int i = 0; i < page->gfx_count; i++) {
GraphicsItem* gi = &page->gfx_items[i];
Color gfx_color = Color::from_rgb(gi->r, gi->g, gi->b);
if (gi->type == GFX_RECT_FILL) {
// gi->x1,y1 = rect origin (PDF coords), gi->x2,y2 = width,height
int rx = page_x + (int)(gi->x1 * g_zoom);
int ry = page_y + (int)((page->height - gi->y1 - gi->y2) * g_zoom);
int rw = (int)(gi->x2 * g_zoom);
int rh = (int)(gi->y2 * g_zoom);
if (rw < 0) { rx += rw; rw = -rw; }
if (rh < 0) { ry += rh; rh = -rh; }
// Clip to content area
if (ry + rh > clip_y0 && ry < clip_y1)
px_fill(pixels, g_win_w, g_win_h, rx, ry, rw, rh, gfx_color);
} else {
// GFX_LINE or GFX_RECT_STROKE
int lx0 = page_x + (int)(gi->x1 * g_zoom);
int ly0 = page_y + (int)((page->height - gi->y1) * g_zoom);
int lx1 = page_x + (int)(gi->x2 * g_zoom);
int ly1 = page_y + (int)((page->height - gi->y2) * g_zoom);
int lw = (int)(gi->line_width * g_zoom + 0.5f);
if (lw < 1) lw = 1;
// Basic clip check
int min_y = ly0 < ly1 ? ly0 : ly1;
int max_y = ly0 > ly1 ? ly0 : ly1;
if (max_y + lw >= clip_y0 && min_y - lw < clip_y1)
px_line(pixels, g_win_w, g_win_h, lx0, ly0, lx1, ly1, lw, gfx_color);
}
}
// Draw text items // Draw text items
for (int i = 0; i < page->item_count; i++) { for (int i = 0; i < page->item_count; i++) {
TextItem* item = &page->items[i]; TextItem* item = &page->items[i];
@@ -75,11 +107,18 @@ void render(uint32_t* pixels) {
if (px_size < 4) px_size = 4; if (px_size < 4) px_size = 4;
if (px_size > 120) px_size = 120; if (px_size > 120) px_size = 120;
// draw_to_buffer treats y as top of text box, but PDF
// specifies the baseline. Subtract ascent so the rendered
// baseline lands at the correct position.
GlyphCache* gc = font->get_cache(px_size);
int baseline_adj = gc ? gc->ascent : (int)(px_size * 0.8f);
int ty = sy - baseline_adj;
if (item->font) { if (item->font) {
// Embedded font: pass raw character codes directly // Embedded font: pass raw character codes directly
// (subset fonts use codes 0-N that map through the font's cmap) // (subset fonts use codes 0-N that map through the font's cmap)
font->draw_to_buffer(pixels, g_win_w, g_win_h, font->draw_to_buffer(pixels, g_win_w, g_win_h,
sx, sy, item->text, TEXT_COLOR, px_size); sx, ty, item->text, TEXT_COLOR, px_size);
} else { } else {
// System font: filter out non-printable characters // System font: filter out non-printable characters
char render_text[MAX_TEXT_LEN]; char render_text[MAX_TEXT_LEN];
@@ -96,7 +135,7 @@ void render(uint32_t* pixels) {
if (ri > 0) { if (ri > 0) {
font->draw_to_buffer(pixels, g_win_w, g_win_h, font->draw_to_buffer(pixels, g_win_w, g_win_h,
sx, sy, render_text, TEXT_COLOR, px_size); sx, ty, render_text, TEXT_COLOR, px_size);
} }
} }
} }