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 AML {
// ── Global instance ─────────────────────────────────────────────
// ============================================================================
// Global instance
// ============================================================================
static Interpreter g_interpreter;
Interpreter& GetInterpreter() {
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) {
return (c >= 'A' && c <= 'Z') || c == '_';
}
@@ -33,11 +41,19 @@ namespace Hal {
return IsLeadNameChar(c) || (c >= '0' && c <= '9');
}
// ── Constructor ─────────────────────────────────────────────────
// ============================================================================
// Constructor
// ============================================================================
Interpreter::Interpreter()
: m_dsdt(nullptr), m_dsdtLength(0), m_initialized(false) {}
// ── PkgLength decoding ──────────────────────────────────────────
// ============================================================================
// PkgLength decoding
// ============================================================================
uint32_t Interpreter::DecodePkgLength(const uint8_t* aml, uint32_t* pos) {
uint8_t lead = aml[*pos];
uint32_t byteCount = (lead >> 6) & 0x03;
@@ -58,7 +74,11 @@ namespace Hal {
return length;
}
// ── Integer decoding (data objects) ─────────────────────────────
// ============================================================================
// Integer decoding (data objects)
// ============================================================================
uint64_t Interpreter::DecodeInteger(const uint8_t* aml, uint32_t* 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) {
for (int i = 0; i < 4; i++) {
outSeg[i] = (char)aml[*pos + i];
@@ -116,7 +140,11 @@ namespace Hal {
*pos += 4;
}
// ── NameString reading ──────────────────────────────────────────
// ============================================================================
// NameString reading
// ============================================================================
// Reads a NameString (which may include root prefix, parent prefixes,
// dual/multi name prefix) and produces an absolute path.
int Interpreter::ReadNameString(const uint8_t* aml, uint32_t* pos, int32_t scopeNode,
@@ -195,7 +223,11 @@ namespace Hal {
return pathPos;
}
// ── LoadTable ───────────────────────────────────────────────────
// ============================================================================
// LoadTable
// ============================================================================
bool Interpreter::LoadTable(void* tableData) {
auto* header = (ACPI::CommonSDTHeader*)tableData;
@@ -231,7 +263,11 @@ namespace Hal {
return result;
}
// ── ParseBlock ──────────────────────────────────────────────────
// ============================================================================
// ParseBlock
// ============================================================================
// Parse a block of AML opcodes, creating namespace objects.
bool Interpreter::ParseBlock(const uint8_t* aml, uint32_t offset, uint32_t endOffset,
int32_t scopeNode) {
@@ -332,7 +368,11 @@ namespace Hal {
return true;
}
// ── ParseNamedObject ────────────────────────────────────────────
// ============================================================================
// ParseNamedObject
// ============================================================================
bool Interpreter::ParseNamedObject(const uint8_t* aml, uint32_t* pos, uint32_t endOffset,
int32_t scopeNode) {
uint8_t op = aml[*pos];
@@ -484,7 +524,11 @@ namespace Hal {
return true;
}
// ── ParseExtendedOp ─────────────────────────────────────────────
// ============================================================================
// ParseExtendedOp
// ============================================================================
bool Interpreter::ParseExtendedOp(const uint8_t* aml, uint32_t* pos, uint32_t endOffset,
int32_t scopeNode) {
(*pos)++; // skip ExtOpPrefix
@@ -743,7 +787,11 @@ namespace Hal {
}
}
// ── EvaluateObject ──────────────────────────────────────────────
// ============================================================================
// EvaluateObject
// ============================================================================
bool Interpreter::EvaluateObject(const char* path, Object& result) {
int32_t node = m_ns.FindNode(path);
if (node < 0) return false;
@@ -772,7 +820,11 @@ namespace Hal {
return true;
}
// ── EvaluateMethod ──────────────────────────────────────────────
// ============================================================================
// EvaluateMethod
// ============================================================================
bool Interpreter::EvaluateMethod(const char* path, const Object* args, int argCount,
Object& result) {
int32_t node = m_ns.FindNode(path);
@@ -812,7 +864,11 @@ namespace Hal {
return ok;
}
// ── ExecuteBlock ────────────────────────────────────────────────
// ============================================================================
// ExecuteBlock
// ============================================================================
bool Interpreter::ExecuteBlock(ExecContext& ctx, uint32_t offset, uint32_t endOffset) {
uint32_t pos = offset;
@@ -824,7 +880,11 @@ namespace Hal {
return true;
}
// ── ExecuteOpcode ───────────────────────────────────────────────
// ============================================================================
// ExecuteOpcode
// ============================================================================
bool Interpreter::ExecuteOpcode(ExecContext& ctx, uint32_t* pos, uint32_t endOffset) {
if (*pos >= endOffset) return true;
@@ -1015,7 +1075,11 @@ namespace Hal {
return EvalTerm(ctx, pos, endOffset, discard);
}
// ── EvalTerm ────────────────────────────────────────────────────
// ============================================================================
// EvalTerm
// ============================================================================
// Evaluate an AML term that produces a value.
bool Interpreter::EvalTerm(ExecContext& ctx, uint32_t* pos, uint32_t endOffset,
Object& result) {
@@ -1412,7 +1476,11 @@ namespace Hal {
return true;
}
// ── EvalTarget ──────────────────────────────────────────────────
// ============================================================================
// EvalTarget
// ============================================================================
bool Interpreter::EvalTarget(ExecContext& ctx, uint32_t* pos,
int32_t& nodeIndex, bool& isLocal, int& localIdx,
bool& isArg, int& argIdx) {
@@ -1464,7 +1532,11 @@ namespace Hal {
return true;
}
// ── StoreToTarget ───────────────────────────────────────────────
// ============================================================================
// StoreToTarget
// ============================================================================
void Interpreter::StoreToTarget(ExecContext& ctx, const Object& value,
int32_t nodeIndex, bool isLocal, int localIdx,
bool isArg, int argIdx) {
@@ -1484,7 +1556,11 @@ namespace Hal {
}
}
// ── ReadField ───────────────────────────────────────────────────
// ============================================================================
// ReadField
// ============================================================================
bool Interpreter::ReadField(int32_t nodeIndex, uint64_t& value) {
auto* node = m_ns.GetNode(nodeIndex);
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);
}
// ── WriteField ──────────────────────────────────────────────────
// ============================================================================
// WriteField
// ============================================================================
bool Interpreter::WriteField(int32_t nodeIndex, uint64_t value) {
auto* node = m_ns.GetNode(nodeIndex);
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);
}
// ── ReadRegion ──────────────────────────────────────────────────
// ============================================================================
// ReadRegion
// ============================================================================
bool Interpreter::ReadRegion(RegionSpace space, uint64_t address, uint32_t bitWidth,
uint64_t& value) {
value = 0;
@@ -1576,7 +1660,11 @@ namespace Hal {
return false;
}
// ── WriteRegion ─────────────────────────────────────────────────
// ============================================================================
// WriteRegion
// ============================================================================
bool Interpreter::WriteRegion(RegionSpace space, uint64_t address, uint32_t bitWidth,
uint64_t value) {
uint32_t accessBytes = (bitWidth + 7) / 8;
+48 -10
View File
@@ -12,7 +12,11 @@
namespace Hal {
namespace AML {
// ── Extended AML Opcodes ────────────────────────────────────────
// ============================================================================
// Extended AML Opcodes
// ============================================================================
// Single-byte opcodes
static constexpr uint8_t ZeroOp = 0x00;
static constexpr uint8_t OneOp = 0x01;
@@ -93,11 +97,19 @@ namespace Hal {
static constexpr uint8_t ToHexStringOp = 0x98;
static constexpr uint8_t ToDecimalStringOp = 0x97;
// ── Interpreter Configuration ───────────────────────────────────
// ============================================================================
// Interpreter Configuration
// ============================================================================
static constexpr int MaxCallDepth = 16;
static constexpr int MaxLoopIterations = 1024;
// ── Interpreter ─────────────────────────────────────────────────
// ============================================================================
// Interpreter
// ============================================================================
class Interpreter {
public:
Interpreter();
@@ -127,7 +139,9 @@ namespace Hal {
bool IsInitialized() const { return m_initialized; }
private:
// ── Parsing (table load) ────────────────────────────────────
// ============================================================================
// Parsing (table load)
// ============================================================================
bool ParseBlock(const uint8_t* aml, uint32_t offset, uint32_t endOffset,
int32_t scopeNode);
@@ -137,7 +151,11 @@ namespace Hal {
bool ParseExtendedOp(const uint8_t* aml, uint32_t* pos, uint32_t endOffset,
int32_t scopeNode);
// ── Name resolution ─────────────────────────────────────────
// ============================================================================
// Name resolution
// ============================================================================
// Read a NameString from AML and produce an absolute path.
// Advances *pos past the name.
int ReadNameString(const uint8_t* aml, uint32_t* pos, int32_t scopeNode,
@@ -146,11 +164,19 @@ namespace Hal {
// Read a single 4-char NameSeg from AML. Advances *pos.
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);
uint64_t DecodeInteger(const uint8_t* aml, uint32_t* pos);
// ── Method execution ────────────────────────────────────────
// ============================================================================
// Method execution
// ============================================================================
struct ExecContext {
const uint8_t* Aml;
uint32_t AmlBase; // start of the block within the table
@@ -182,18 +208,30 @@ namespace Hal {
int32_t nodeIndex, bool isLocal, int localIdx,
bool isArg, int argIdx);
// ── Field I/O ───────────────────────────────────────────────
// ============================================================================
// Field I/O
// ============================================================================
bool ReadRegion(RegionSpace space, uint64_t address, uint32_t bitWidth, uint64_t& value);
bool WriteRegion(RegionSpace space, uint64_t address, uint32_t bitWidth, uint64_t value);
// ── State ───────────────────────────────────────────────────
// ============================================================================
// State
// ============================================================================
Namespace m_ns;
const uint8_t* m_dsdt;
uint32_t m_dsdtLength;
bool m_initialized;
};
// ── Global interpreter instance ─────────────────────────────────
// ============================================================================
// Global interpreter instance
// ============================================================================
Interpreter& GetInterpreter();
};
+30 -6
View File
@@ -11,7 +11,11 @@
namespace Hal {
namespace AML {
// ── AML Object Types ────────────────────────────────────────────
// ============================================================================
// AML Object Types
// ============================================================================
enum class ObjectType : uint8_t {
None = 0,
Integer,
@@ -29,7 +33,11 @@ namespace Hal {
BufferField,
};
// ── Region address spaces (OperationRegion) ─────────────────────
// ============================================================================
// Region address spaces (OperationRegion)
// ============================================================================
enum class RegionSpace : uint8_t {
SystemMemory = 0x00,
SystemIO = 0x01,
@@ -40,7 +48,11 @@ namespace Hal {
PciBarTarget = 0x06,
};
// ── Constants ───────────────────────────────────────────────────
// ============================================================================
// Constants
// ============================================================================
static constexpr int MaxNameSegLen = 4;
static constexpr int MaxPathDepth = 16;
static constexpr int MaxChildren = 32;
@@ -51,7 +63,11 @@ namespace Hal {
static constexpr int MaxMethodLocals = 8;
static constexpr int MaxNamespaceNodes = 256;
// ── AML Object ──────────────────────────────────────────────────
// ============================================================================
// AML Object
// ============================================================================
// Tagged union representing any AML value. Kept small for kernel use.
struct Object {
ObjectType Type = ObjectType::None;
@@ -99,7 +115,11 @@ namespace Hal {
Object() : Type(ObjectType::None), Integer(0) {}
};
// ── Namespace Node ──────────────────────────────────────────────
// ============================================================================
// Namespace Node
// ============================================================================
// Each node has a 4-char name segment and an associated object.
struct NamespaceNode {
char Name[MaxNameSegLen + 1]; // null-terminated 4-char segment
@@ -118,7 +138,11 @@ namespace Hal {
}
};
// ── Namespace ───────────────────────────────────────────────────
// ============================================================================
// Namespace
// ============================================================================
// Flat array of nodes forming a tree via parent/child indices.
class Namespace {
public:
+15 -3
View File
@@ -15,7 +15,11 @@ using namespace Kt;
namespace Hal {
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
// interpreter is loaded.
@@ -158,7 +162,11 @@ namespace Hal {
return result;
}
// ── Generalized brute-force sleep state scanner ──────────────────
// ============================================================================
// Generalized brute-force sleep state scanner
// ============================================================================
SleepObject FindSleepState(void* dsdtData, int state) {
SleepObject result{};
result.Valid = false;
@@ -226,7 +234,11 @@ namespace Hal {
return result;
}
// ── Full interpreter initialization ─────────────────────────────
// ============================================================================
// Full interpreter initialization
// ============================================================================
void InitializeInterpreter(void* dsdtData) {
auto& interp = GetInterpreter();
if (!interp.LoadTable(dsdtData)) {
+16 -4
View File
@@ -9,14 +9,22 @@
namespace Hal {
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 SmallDmaTag = 0x05; // DMA descriptor
static constexpr uint8_t SmallIoPortTag = 0x08; // I/O port descriptor
static constexpr uint8_t SmallFixedIoTag = 0x09; // Fixed I/O port descriptor
static constexpr uint8_t SmallEndTag = 0x0F; // End tag
// ── Large Resource Tags (byte following the large tag prefix) ───
// ============================================================================
// Large Resource Tags (byte following the large tag prefix)
// ============================================================================
static constexpr uint8_t LargeMemory24Tag = 0x01;
static constexpr uint8_t LargeVendorTag = 0x04;
static constexpr uint8_t LargeMemory32Tag = 0x05;
@@ -63,7 +71,9 @@ namespace Hal {
break;
if (tag & 0x80) {
// ── Large resource descriptor ───────────────────────
// ============================================================================
// Large resource descriptor
// ============================================================================
uint8_t largeType = tag & 0x7F;
if (pos + 3 > length) break;
uint16_t resLen = Read16(&data[pos + 1]);
@@ -154,7 +164,9 @@ namespace Hal {
pos = dataEnd;
} else {
// ── Small resource descriptor ───────────────────────
// ============================================================================
// Small resource descriptor
// ============================================================================
uint8_t smallType = (tag >> 3) & 0x0F;
uint8_t resLen = tag & 0x07;
uint32_t dataStart = pos + 1;
+15 -3
View File
@@ -10,7 +10,11 @@
namespace Hal {
namespace AML {
// ── Resource Types ──────────────────────────────────────────────
// ============================================================================
// Resource Types
// ============================================================================
enum class ResourceType : uint8_t {
None = 0,
Irq,
@@ -27,7 +31,11 @@ namespace Hal {
GpioConnection,
};
// ── Single Resource Descriptor ──────────────────────────────────
// ============================================================================
// Single Resource Descriptor
// ============================================================================
struct ResourceDescriptor {
ResourceType Type;
@@ -82,7 +90,11 @@ namespace Hal {
}
};
// ── Parsed Resource List ────────────────────────────────────────
// ============================================================================
// Parsed Resource List
// ============================================================================
static constexpr int MaxResources = 16;
struct ResourceList {
+50 -10
View File
@@ -16,7 +16,11 @@ using namespace Kt;
namespace Hal {
namespace AcpiDevices {
// ── EISAID decoding ─────────────────────────────────────────────
// ============================================================================
// EISAID decoding
// ============================================================================
// ACPI encodes PNP IDs as compressed 32-bit EISAIDs.
static void DecodeEisaId(uint32_t id, char* out) {
// EISA ID encoding:
@@ -35,7 +39,11 @@ namespace Hal {
out[7] = '\0';
}
// ── String comparison ───────────────────────────────────────────
// ============================================================================
// String comparison
// ============================================================================
static bool StrEqual(const char* a, const char* b) {
while (*a && *b) {
if (*a != *b) return false;
@@ -53,7 +61,11 @@ namespace Hal {
dst[i] = '\0';
}
// ── DeviceList methods ──────────────────────────────────────────
// ============================================================================
// DeviceList methods
// ============================================================================
const DeviceInfo* DeviceList::FindByHid(const char* hid) const {
for (int i = 0; i < Count; i++) {
if (StrEqual(Devices[i].HardwareId, hid))
@@ -70,7 +82,11 @@ namespace Hal {
return nullptr;
}
// ── EvaluateSta ─────────────────────────────────────────────────
// ============================================================================
// EvaluateSta
// ============================================================================
uint32_t EvaluateSta(int32_t deviceNodeIndex) {
auto& interp = AML::GetInterpreter();
auto& ns = interp.GetNamespace();
@@ -107,7 +123,11 @@ namespace Hal {
return STA_DEFAULT;
}
// ── EvaluateAdr ─────────────────────────────────────────────────
// ============================================================================
// EvaluateAdr
// ============================================================================
uint64_t EvaluateAdr(int32_t deviceNodeIndex) {
auto& interp = AML::GetInterpreter();
auto& ns = interp.GetNamespace();
@@ -143,7 +163,11 @@ namespace Hal {
return 0;
}
// ── EvaluateHid ─────────────────────────────────────────────────
// ============================================================================
// EvaluateHid
// ============================================================================
bool EvaluateHid(int32_t deviceNodeIndex, char* outHid, int maxLen) {
auto& interp = AML::GetInterpreter();
auto& ns = interp.GetNamespace();
@@ -182,7 +206,11 @@ namespace Hal {
return false;
}
// ── EvaluateUid ─────────────────────────────────────────────────
// ============================================================================
// EvaluateUid
// ============================================================================
bool EvaluateUid(int32_t deviceNodeIndex, char* outUid, int maxLen) {
auto& interp = AML::GetInterpreter();
auto& ns = interp.GetNamespace();
@@ -237,7 +265,11 @@ namespace Hal {
return false;
}
// ── EvaluateCrs ─────────────────────────────────────────────────
// ============================================================================
// EvaluateCrs
// ============================================================================
bool EvaluateCrs(int32_t deviceNodeIndex, AML::ResourceList& result) {
auto& interp = AML::GetInterpreter();
auto& ns = interp.GetNamespace();
@@ -268,7 +300,11 @@ namespace Hal {
return AML::ParseResourceTemplate(crsResult.Buffer.Data, crsResult.Buffer.Length, result);
}
// ── EnumerateAll ────────────────────────────────────────────────
// ============================================================================
// EnumerateAll
// ============================================================================
void EnumerateAll(DeviceList& result) {
auto& interp = AML::GetInterpreter();
if (!interp.IsInitialized()) return;
@@ -323,7 +359,11 @@ namespace Hal {
}
}
// ── GetSleepState ───────────────────────────────────────────────
// ============================================================================
// GetSleepState
// ============================================================================
SleepState GetSleepState(int state) {
SleepState result{};
result.Valid = false;
+24 -5
View File
@@ -11,7 +11,11 @@
namespace Hal {
namespace AcpiDevices {
// ── Device Status Flags (_STA) ──────────────────────────────────
// ============================================================================
// Device Status Flags (_STA)
// ============================================================================
static constexpr uint32_t STA_PRESENT = (1 << 0);
static constexpr uint32_t STA_ENABLED = (1 << 1);
static constexpr uint32_t STA_VISIBLE = (1 << 2);
@@ -21,7 +25,11 @@ namespace Hal {
// Default _STA when no _STA method exists: present + enabled + visible + functional
static constexpr uint32_t STA_DEFAULT = 0x0F;
// ── Device Info ─────────────────────────────────────────────────
// ============================================================================
// Device Info
// ============================================================================
struct DeviceInfo {
char Path[128]; // full namespace path
char HardwareId[16]; // _HID value (e.g. "PNP0A03", "ACPI0001")
@@ -47,8 +55,11 @@ namespace Hal {
const DeviceInfo* FindByPath(const char* path) const;
};
// ── Enumeration API ─────────────────────────────────────────────
// ============================================================================
// Enumeration API
// ============================================================================
// Enumerate all ACPI devices in the namespace.
// The interpreter must be initialized first (LoadTable called).
void EnumerateAll(DeviceList& result);
@@ -69,7 +80,11 @@ namespace Hal {
// Evaluate _CRS (Current Resource Settings) for a device node.
bool EvaluateCrs(int32_t deviceNodeIndex, AML::ResourceList& result);
// ── Well-known HIDs ─────────────────────────────────────────────
// ============================================================================
// Well-known HIDs
// ============================================================================
// PCI Host Bridge
static constexpr const char* HID_PCI_HOST = "PNP0A03";
static constexpr const char* HID_PCIE_HOST = "PNP0A08";
@@ -98,7 +113,11 @@ namespace Hal {
// ACPI AC Adapter
static constexpr const char* HID_AC_ADAPTER = "ACPI0003";
// ── Sleep State Support ─────────────────────────────────────────
// ============================================================================
// Sleep State Support
// ============================================================================
// Extract sleep state values from namespace objects (\_S0_ through \_S5_).
struct SleepState {
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 "AcpiSleep.hpp"
#include "AcpiEvents.hpp"
#include <ACPI/FADT.hpp>
#include <ACPI/AML/AmlParser.hpp>
#include <ACPI/AML/AmlInterpreter.hpp>
@@ -67,6 +68,9 @@ namespace Hal {
// Initialize S3 suspend support (reads FACS and \_S3_ from namespace)
AcpiSleep::Initialize(xsdt);
// Initialize ACPI event handling (SCI interrupt, power button)
AcpiEvents::Initialize(fadt);
}
bool IsAvailable() {
@@ -78,6 +82,19 @@ namespace Hal {
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");
// Phase 1: Write SLP_TYP to PM1x_CNT without SLP_EN,
+258 -93
View File
@@ -5,6 +5,7 @@
*/
#include "AcpiSleep.hpp"
#include "AcpiEvents.hpp"
#include <ACPI/FADT.hpp>
#include <ACPI/AML/AmlParser.hpp>
#include <ACPI/AML/AmlInterpreter.hpp>
@@ -22,6 +23,8 @@
#include <Drivers/PS2/PS2Controller.hpp>
#include <Graphics/Cursor.hpp>
#include <Libraries/Memory.hpp>
#include <Hal/MSR.hpp>
#include <Api/Syscall.hpp>
using namespace Kt;
@@ -31,6 +34,11 @@ extern "C" void AcpiResumeLongMode(Hal::AcpiSleep::CpuState* stateArea);
extern "C" void AcpiWakeEntry();
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
extern "C" char S3TrampolineStart[];
extern "C" char S3Trampoline64[];
@@ -40,6 +48,11 @@ extern "C" char S3TrampolineEnd[];
// Physical address where the trampoline is copied (must be < 1MB, page-aligned)
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
namespace Hal {
extern void BridgeLoadGDT();
@@ -49,7 +62,11 @@ namespace Hal {
namespace Hal {
namespace AcpiSleep {
// ── State ───────────────────────────────────────────────────────
// ============================================================================
// State
// ============================================================================
static bool g_s3Available = false;
static uint16_t g_s3SlpTypA = 0;
static uint16_t g_s3SlpTypB = 0;
@@ -65,10 +82,66 @@ namespace Hal {
// CPU state save area (aligned for fxsave)
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) {
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{};
if (!FADT::Parse(xsdt, fadt) || !fadt.Valid)
return;
@@ -96,9 +169,9 @@ namespace Hal {
g_pm1bControlBlock = fadt.PM1bControlBlock;
g_pm1EventLength = fadt.PM1EventLength;
// Find \_S3_ via brute-force DSDT scan (same approach as \_S5_).
// This works on any DSDT regardless of complexity — does not
// require the AML interpreter or namespace to be loaded.
// Find \_S3_ via brute-force DSDT scan. The AML interpreter stores
// packages as raw bytecode (no structured element access yet), so
// the brute-force scanner is the reliable path for now.
auto* dsdt = (void*)Memory::HHDM(fadt.DsdtAddress);
AML::SleepObject s3 = AML::FindSleepState(dsdt, 3);
if (s3.Valid) {
@@ -111,6 +184,11 @@ namespace Hal {
<< " SLP_TYPb=" << base::hex << (uint64_t)g_s3SlpTypB << ")";
KernelLogStream(INFO, "S3") << "Kernel PML4 phys = " << base::hex
<< (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 {
KernelLogStream(INFO, "S3") << "\\_S3_ not found in DSDT - S3 unavailable";
}
@@ -120,7 +198,11 @@ namespace Hal {
return g_s3Available;
}
// ── Evaluate _PTS (Prepare To Sleep) ────────────────────────────
// ============================================================================
// Evaluate _PTS (Prepare To Sleep)
// ============================================================================
static void EvaluatePts(int sleepState) {
auto& interp = AML::GetInterpreter();
if (!interp.IsInitialized()) return;
@@ -138,7 +220,11 @@ namespace Hal {
KernelLogStream(DEBUG, "S3") << "Evaluated \\_PTS(" << base::dec << (uint64_t)sleepState << ")";
}
// ── Evaluate _WAK (System Wake) ─────────────────────────────────
// ============================================================================
// Evaluate _WAK (System Wake)
// ============================================================================
static void EvaluateWak(int sleepState) {
auto& interp = AML::GetInterpreter();
if (!interp.IsInitialized()) return;
@@ -156,7 +242,11 @@ namespace Hal {
KernelLogStream(DEBUG, "S3") << "Evaluated \\_WAK(" << base::dec << (uint64_t)sleepState << ")";
}
// ── Clear PM1 Status Registers ──────────────────────────────────
// ============================================================================
// Clear PM1 Status Registers
// ============================================================================
static void ClearPM1Status() {
// Write 1 to clear all status bits (write-1-to-clear semantics)
uint16_t clearMask = PM1_WAK_STS | PM1_PWRBTN_STS | PM1_SLPBTN_STS |
@@ -168,7 +258,11 @@ namespace Hal {
Io::Out16(clearMask, (uint16_t)g_pm1bEventBlock);
}
// ── Enable Wake Events ──────────────────────────────────────────
// ============================================================================
// Enable Wake Events
// ============================================================================
static void EnableWakeEvents() {
// Enable register is at event block + PM1EventLength/2
uint16_t enableOffset = g_pm1EventLength / 2;
@@ -182,7 +276,11 @@ namespace Hal {
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() {
if (!g_facs) return;
@@ -197,26 +295,31 @@ namespace Hal {
uint8_t* trampolineSrc = (uint8_t*)S3TrampolineStart;
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):
// +0: uint64_t CR3 (full 64-bit PML4 physical address)
// +8: uint64_t CpuState 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);
uint8_t* dataArea = trampolineDst + dataOffset;
// Use the kernel master PML4 (which has 0x8000 identity-mapped)
// rather than the saved process PML4 (which doesn't).
// AcpiResumeLongMode will restore the saved CR3 after we're
// safely back in long mode with kernel GDT/IDT.
uint64_t cr3val = (uint64_t)Memory::VMM::g_paging->PML4;
// The kernel PML4 may be above 4GB (e.g. at 0x8BF7CC000), but
// the 32-bit trampoline can only load a 32-bit CR3. Create a
// shadow copy of the PML4 at a fixed low-memory address.
// Only the PML4 page itself needs to be below 4GB — its entries
// 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.
// If the PML4 is above 4GB, the real-mode wake path would fail.
if (cr3val > 0xFFFFFFFF) {
KernelLogStream(ERROR, "S3") << "PML4 at " << base::hex << cr3val
<< " is above 4GB - real-mode wake path will fail!";
}
uint64_t cr3val = (uint64_t)SHADOW_PML4_PHYS;
KernelLogStream(DEBUG, "S3") << "Shadow PML4 at " << base::hex << cr3val
<< " (kernel PML4 at " << base::hex << kernelPml4Phys << ")";
memcpy(dataArea + 0, &cr3val, 8);
@@ -226,10 +329,24 @@ namespace Hal {
uint64_t resumeAddr = (uint64_t)&AcpiResumeLongMode;
memcpy(dataArea + 16, &resumeAddr, 8);
// Set the 32-bit waking vector only. Setting X_FirmwareWakingVector
// to non-zero causes this laptop's firmware to hang during wake.
uint16_t pm1a = (uint16_t)g_pm1aControlBlock;
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;
// 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;
KernelLogStream(DEBUG, "S3") << "Using 32-bit waking vector (0x8000)";
}
KernelLogStream(DEBUG, "S3") << "Trampoline installed at " << base::hex
<< (uint64_t)TRAMPOLINE_PHYS << " (" << base::dec
@@ -237,7 +354,11 @@ namespace Hal {
KernelLogStream(DEBUG, "S3") << "Trampoline CR3 = " << base::hex << cr3val;
}
// ── Wait for WAK_STS ────────────────────────────────────────────
// ============================================================================
// Wait for WAK_STS
// ============================================================================
static void WaitForWake() {
// After entering S3, the CPU halts. On resume, firmware runs the
// waking vector. But if we somehow didn't enter sleep (e.g. immediate
@@ -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() {
if (!g_s3Available) {
KernelLogStream(ERROR, "S3") << "S3 suspend not available";
@@ -263,77 +478,27 @@ namespace Hal {
// 1. Evaluate _PTS(3) — Prepare To Sleep
EvaluatePts(3);
// 2. Save CPU state. AcpiSaveAndSuspend returns 1 on initial call,
// and 0 when we resume from S3 (AcpiResumeLongMode sets RAX=0).
int resumed = !AcpiSaveAndSuspend(&g_cpuState);
if (resumed) {
// ── RESUME PATH ─────────────────────────────────────────
// We just woke from S3. Firmware ran our waking vector which
// called AcpiResumeLongMode, restoring all registers and
// returning here with 0.
// Debug: write a green rectangle to the top-left corner of the
// framebuffer. The framebuffer memory survives S3 (it's RAM).
// This will be visible once the GPU display plane is restored,
// confirming the CPU successfully completed the resume path.
{
uint32_t* fb = ::Graphics::Cursor::GetFramebufferBase();
uint64_t pitch = ::Graphics::Cursor::GetFramebufferPitch();
if (fb && pitch > 0) {
for (int y = 0; y < 32; y++) {
uint32_t* row = (uint32_t*)((uint8_t*)fb + y * pitch);
for (int x = 0; x < 32; x++) {
row[x] = 0xFF00FF00; // Green (ARGB)
}
}
}
}
// Reload GDT and TSS (firmware may have clobbered them)
Hal::BridgeLoadGDT();
Hal::LoadTSS();
// Re-disable legacy 8259 PIC. Firmware may have re-enabled
// it during S3 resume POST, which could cause spurious
// interrupts on conflicting vectors once we enable interrupts.
Hal::DisableLegacyPic();
// Re-enable the Local APIC (MSR global enable + SVR + TPR)
Hal::LocalApic::Reinitialize();
// Restore I/O APIC redirection entries (lost during S3).
// Must be done before enabling interrupts so IRQ routing
// is in place when devices start generating interrupts.
Hal::IoApic::Reinitialize();
// Restart the APIC timer
Timekeeping::ApicTimerReinitialize();
// Clear WAK_STS
ClearPM1Status();
// Evaluate _WAK(3)
EvaluateWak(3);
// Re-enable PS/2 controller (ports and interrupts may be
// disabled after S3; skip full self-test to avoid resetting
// attached devices)
Drivers::PS2::Reinitialize();
// Restore Intel GPU display (GTT + display plane lost during S3)
if (Drivers::Graphics::IntelGPU::IsInitialized()) {
Drivers::Graphics::IntelGPU::Reinitialize();
}
// Re-enable interrupts
asm volatile("sti");
KernelLogStream(OK, "S3") << "Resumed from S3 suspend";
// 2. Save CPU state. On resume, AcpiResumeLongMode jumps to
// AcpiResumeEntry (not back here), which does all reinit and
// sets g_resumeComplete=1, then returns here via the stack.
g_resumeComplete = 0;
AcpiSaveAndSuspend(&g_cpuState);
// If we get here after resume, AcpiResumeEntry already ran.
if (g_resumeComplete) {
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
asm volatile("cli");
+29 -6
View File
@@ -11,7 +11,11 @@
namespace Hal {
namespace AcpiSleep {
// ── FACS (Firmware ACPI Control Structure) ──────────────────────
// ============================================================================
// FACS (Firmware ACPI Control Structure)
// ============================================================================
struct FACS {
char Signature[4]; // "FACS"
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_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_BM_STS = (1 << 4);
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_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_GBL_EN = (1 << 5);
static constexpr uint16_t PM1_PWRBTN_EN = (1 << 8);
static constexpr uint16_t PM1_SLPBTN_EN = (1 << 9);
static constexpr uint16_t PM1_RTC_EN = (1 << 10);
// ── PM1 Control Register Bits ───────────────────────────────────
// ============================================================================
// PM1 Control Register Bits
// ============================================================================
static constexpr uint16_t PM1_SCI_EN = (1 << 0);
static constexpr uint16_t PM1_BM_RLD = (1 << 1);
static constexpr uint16_t PM1_SLP_TYP_MASK = 0x1C00; // bits 10-12
static constexpr uint16_t PM1_SLP_EN = (1 << 13);
// ── CPU State (saved across S3) ─────────────────────────────────
// ============================================================================
// CPU State (saved across S3)
// ============================================================================
// Layout must match S3Wake.asm offsets exactly.
// No FPU/SSE state — the kernel is compiled with -mno-sse.
struct CpuState {
@@ -71,8 +91,11 @@ namespace Hal {
uint8_t IdtPtr[10]; // 0xB2
} __attribute__((aligned(16)));
// ── Sleep API ───────────────────────────────────────────────────
// ============================================================================
// Sleep API
// ============================================================================
// Initialize sleep support. Called during boot after ACPI init.
// xsdt is used to read FADT for FACS and PM register addresses.
void Initialize(ACPI::CommonSDTHeader* xsdt);
+120 -9
View File
@@ -19,8 +19,44 @@ S3TrampolineStart:
cli
cld
; Set up DS immediately so we can access the data area
mov ax, 0x0800
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 ss, ax
mov sp, 0x00F0
@@ -35,6 +71,12 @@ S3TrampolineStart:
[bits 32]
pm32_entry:
; Progress: reached 32-bit mode (0xA2)
mov al, 0xF2
out 0x70, al
mov al, 0xA2
out 0x71, al
mov ax, 0x10
mov ds, ax
mov es, ax
@@ -50,18 +92,32 @@ pm32_entry:
mov eax, [0x8000 + data_cr3 - S3TrampolineStart]
mov cr3, eax
mov ecx, 0xC0000080
mov ecx, 0xC0000080 ; IA32_EFER
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
mov eax, cr0
or eax, (1 << 31)
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)
; ── Temporary GDT ──────────────────────────────────────────────────────
; ═════════════════════════════════════════════════════════════════════════════
; Temporary GDT
; ═════════════════════════════════════════════════════════════════════════════
align 16
gdt_start:
dq 0x0000000000000000 ; 0x00: Null
@@ -75,6 +131,11 @@ gdt_ptr:
dw gdt_end - gdt_start - 1
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)
@@ -88,14 +149,62 @@ S3Trampoline64:
cli
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 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:
mov ax, 0x20
mov ds, ax
mov es, ax
mov ss, ax
mov rdi, [0x8000 + data_state_ptr - S3TrampolineStart]
mov rax, [0x8000 + data_resume_addr - S3TrampolineStart]
jmp rax
@@ -107,9 +216,11 @@ lm64_common:
align 8
global S3TrampolineData
S3TrampolineData:
data_cr3: dq 0 ; kernel PML4 physical address (full 64-bit)
data_state_ptr: dq 0 ; virtual address of CpuState
data_resume_addr: dq 0 ; virtual address of AcpiResumeLongMode
data_cr3: dq 0 ; +0: kernel PML4 physical address (full 64-bit)
data_state_ptr: dq 0 ; +8: virtual address of CpuState
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
S3TrampolineEnd:
+52 -7
View File
@@ -7,7 +7,9 @@
[bits 64]
section .text
; ─── AcpiSaveAndSuspend ──────────────────────────────────────────────────
; ═════════════════════════════════════════════════════════════════════════════
; AcpiSaveAndSuspend
; ═════════════════════════════════════════════════════════════════════════════
; extern "C" int AcpiSaveAndSuspend(CpuState* stateArea)
; rdi = pointer to CpuState structure
;
@@ -77,7 +79,9 @@ AcpiSaveAndSuspend:
ret
; ─── AcpiWakeEntry ───────────────────────────────────────────────────────
; ═════════════════════════════════════════════════════════════════════════════
; AcpiWakeEntry
; ═════════════════════════════════════════════════════════════════════════════
; This is the actual waking vector target. Firmware jumps here after S3
; resume. It loads the CpuState pointer from a fixed location (set before
; suspend) and falls through to AcpiResumeLongMode.
@@ -103,14 +107,18 @@ AcpiWakeEntry:
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
; without a cross-section relocation warning.
; extern "C" void* g_wakeStatePtr;
global g_wakeStatePtr
g_wakeStatePtr: dq 0
; ─── AcpiResumeLongMode ──────────────────────────────────────────────────
; ═════════════════════════════════════════════════════════════════════════════
; AcpiResumeLongMode
; ═════════════════════════════════════════════════════════════════════════════
; extern "C" void AcpiResumeLongMode(CpuState* stateArea)
; rdi = pointer to CpuState structure
;
@@ -119,6 +127,12 @@ g_wakeStatePtr: dq 0
;
global 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),
; CR3 = kernel PML4 (loaded by trampoline), CS = trampoline selector.
;
@@ -140,6 +154,12 @@ AcpiResumeLongMode:
retfq
.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
mov ax, 0x10
mov ds, ax
@@ -151,11 +171,28 @@ AcpiResumeLongMode:
; Restore IDT
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
; master PML4 to get here; now switch to the actual saved context.
mov rax, [rdi + 0x90]
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)
mov rbx, [rdi + 0x08]
mov rcx, [rdi + 0x10]
@@ -176,9 +213,17 @@ AcpiResumeLongMode:
push rax
popfq
; Progress: about to call AcpiResumeEntry (0xC4)
mov al, 0xF2
out 0x70, al
mov al, 0xC4
out 0x71, al
; Restore rdi last
mov rdi, [rdi + 0x28]
; Return 0 = "resumed from S3"
xor rax, rax
ret
; Instead of returning to Suspend() via ret (which is fragile due to
; compiler stack frame assumptions), jump directly to a dedicated C
; resume function. This is how Linux and other kernels handle S3 resume.
extern AcpiResumeEntry
jmp AcpiResumeEntry
+73 -55
View File
@@ -6,72 +6,52 @@
#include "Panic.hpp"
#include "../CppLib/BoxUI.hpp"
void Panic(const char *meditationString, System::PanicFrame* frame) {
const int boxWidth = 72;
static constexpr int BoxWidth = 72;
// Header
kerr << BOXUI_ANSI_RED_BG << BOXUI_ANSI_WHITE_FG << BOXUI_ANSI_BOLD << "\n";
kerr << BOXUI_TL;
for (int i = 0; i < boxWidth - 2; ++i) kerr << BOXUI_H;
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);
static void PrintHorizontalEdge(const char* left, const char* right) {
kerr << left;
for (int i = 0; i < BoxWidth - 2; ++i) kerr << BOXUI_H;
kerr << right << "\n";
}
#if defined (__x86_64__)
if (frame != nullptr) {
PrintBoxedSeparator(kerr, boxWidth);
PrintBoxedLine(kerr, "CPU State:", boxWidth, true);
PrintBoxedHex(kerr, "Interrupt Vector", frame->InterruptVector, boxWidth);
static void PrintPageFaultInfo(System::PanicFrame*& frame) {
auto* pf = (System::PageFaultPanicFrame*)frame;
frame = (System::PanicFrame*)&pf->IP;
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;
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);
PrintBoxedDec(kerr, "Present", pf_frame->PageFaultError.Present, boxWidth);
PrintBoxedDec(kerr, "Write", pf_frame->PageFaultError.Write, boxWidth);
PrintBoxedDec(kerr, "User", pf_frame->PageFaultError.User, boxWidth);
PrintBoxedDec(kerr, "Reserved Write", pf_frame->PageFaultError.ReservedWrite, boxWidth);
PrintBoxedDec(kerr, "Instruction Fetch", pf_frame->PageFaultError.InstructionFetch, boxWidth);
PrintBoxedDec(kerr, "Protection Key", pf_frame->PageFaultError.ProtectionKey, boxWidth);
PrintBoxedDec(kerr, "Shadow Stack", pf_frame->PageFaultError.ShadowStack, boxWidth);
PrintBoxedDec(kerr, "SGX", pf_frame->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);
PrintBoxedLine(kerr, "Page Fault Error:", BoxWidth, true);
PrintBoxedDec(kerr, "Present", pf->PageFaultError.Present, BoxWidth);
PrintBoxedDec(kerr, "Write", pf->PageFaultError.Write, BoxWidth);
PrintBoxedDec(kerr, "User", pf->PageFaultError.User, BoxWidth);
PrintBoxedDec(kerr, "Reserved Write", pf->PageFaultError.ReservedWrite, BoxWidth);
PrintBoxedDec(kerr, "Instruction Fetch",pf->PageFaultError.InstructionFetch, BoxWidth);
PrintBoxedDec(kerr, "Protection Key", pf->PageFaultError.ProtectionKey, BoxWidth);
PrintBoxedDec(kerr, "Shadow Stack", pf->PageFaultError.ShadowStack, BoxWidth);
PrintBoxedDec(kerr, "SGX", pf->PageFaultError.SGX, BoxWidth);
}
PrintBoxedSeparator(kerr, boxWidth);
PrintBoxedLine(kerr, "Registers:", boxWidth, true);
PrintBoxedHex(kerr, "Instruction Pointer", frame->IP, boxWidth);
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);
static void PrintGPFInfo(System::PanicFrame*& frame) {
auto* gpf = (System::GPFPanicFrame*)frame;
frame = (System::PanicFrame*)&gpf->IP;
PrintBoxedLine(kerr, "General Protection Fault:", BoxWidth, true);
PrintBoxedDec(kerr, "Error Code", gpf->GeneralProtectionFaultError, BoxWidth);
}
#endif
PrintBoxedLine(kerr, "", boxWidth);
// Footer
kerr << BOXUI_BL;
for (int i = 0; i < boxWidth - 2; ++i) kerr << BOXUI_H;
kerr << BOXUI_BR << "\n";
kerr << BOXUI_ANSI_RESET;
static void PrintRegisters(System::PanicFrame* frame) {
PrintBoxedSeparator(kerr, BoxWidth);
PrintBoxedLine(kerr, "Registers:", BoxWidth, true);
PrintBoxedHex(kerr, "Instruction Pointer", frame->IP, BoxWidth);
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) {
#if defined (__x86_64__)
asm ("cli");
@@ -83,3 +63,41 @@ void Panic(const char *meditationString, System::PanicFrame* frame) {
#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 <CppLib/Stream.hpp>
#include <Memory/PageFrameAllocator.hpp>
#include <Sched/Scheduler.hpp>
namespace Hal {
constexpr auto InterruptGate = 0x8E;
@@ -53,12 +54,41 @@ namespace Hal {
"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>
__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;
Panic(ExceptionStrings[i], frame);
}
}
void LoadIDT(IDTRStruct& idtr) {
asm("lidt %0" : : "m"(idtr));
+1 -1
View File
@@ -206,7 +206,7 @@ namespace Montauk {
};
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];
char name[48];
char detail[48];
+1 -1
View File
@@ -320,7 +320,7 @@ void desktop_draw_net_popup(DesktopState* ds) {
rows[0].label = "IP";
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";
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 {
// Skip ramdisk and limine.conf — installed system boots from
// 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, "limine.conf") == 0) continue;
if (strcmp(basename, "setup.toml") == 0) continue;
// It's a file — copy it
char src_path[256];
+32
View File
@@ -751,6 +751,38 @@ extern "C" void _start() {
// Load wallpaper from system desktop config
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)
int fh = montauk::open("0:/config/users.toml");
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 n = 0;
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; }
// 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
if (d[p] == '(') {
p++;
@@ -249,6 +275,65 @@ static void add_text_item(PdfPage* page, float x, float y, float size,
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) {
// Multiply two 3x3 matrices represented as [a b c d e f]
// 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.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;
Token tok;
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.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);
// 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.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);
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.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);
}
}
@@ -611,15 +720,250 @@ void parse_page(int page_idx, int page_obj_num) {
if (sy < 0) sy = -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);
}
}
// ---- 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
}
montauk::mfree(ops);
if (path_segs) montauk::mfree(path_segs);
if (path_rects) montauk::mfree(path_rects);
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].item_count = 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].height = 792;
g_doc.page_count++;
@@ -1457,6 +1460,7 @@ void free_pdf() {
if (g_doc.pages) {
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].gfx_items) montauk::mfree(g_doc.pages[i].gfx_items);
}
montauk::mfree(g_doc.pages);
g_doc.pages = nullptr;
+13
View File
@@ -66,10 +66,22 @@ struct TextItem {
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 {
TextItem* items;
int item_count;
int item_cap;
GraphicsItem* gfx_items;
int gfx_count;
int gfx_cap;
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_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_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);
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;
}
// 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
for (int i = 0; i < page->item_count; 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 > 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) {
// Embedded font: pass raw character codes directly
// (subset fonts use codes 0-N that map through the font's cmap)
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 {
// System font: filter out non-printable characters
char render_text[MAX_TEXT_LEN];
@@ -96,7 +135,7 @@ void render(uint32_t* pixels) {
if (ri > 0) {
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);
}
}
}