From ffffb6a0c52011e9e0bb425f0e6225d63c452abc Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Sun, 15 Mar 2026 18:46:54 +0100 Subject: [PATCH] feat: S3 sleep, PDF viewer graphics rendering, live user environment --- kernel/src/ACPI/AML/AmlInterpreter.cpp | 132 +++++++-- kernel/src/ACPI/AML/AmlInterpreter.hpp | 58 +++- kernel/src/ACPI/AML/AmlNamespace.hpp | 36 ++- kernel/src/ACPI/AML/AmlParser.cpp | 18 +- kernel/src/ACPI/AML/AmlResource.cpp | 20 +- kernel/src/ACPI/AML/AmlResource.hpp | 18 +- kernel/src/ACPI/AcpiDevices.cpp | 60 ++++- kernel/src/ACPI/AcpiDevices.hpp | 29 +- kernel/src/ACPI/AcpiEvents.cpp | 142 ++++++++++ kernel/src/ACPI/AcpiEvents.hpp | 22 ++ kernel/src/ACPI/AcpiShutdown.cpp | 17 ++ kernel/src/ACPI/AcpiSleep.cpp | 353 ++++++++++++++++++------- kernel/src/ACPI/AcpiSleep.hpp | 35 ++- kernel/src/ACPI/S3Trampoline.asm | 129 ++++++++- kernel/src/ACPI/S3Wake.asm | 59 ++++- kernel/src/Common/Panic.cpp | 136 +++++----- kernel/src/Hal/IDT.cpp | 34 ++- programs/include/Api/Syscall.hpp | 2 +- programs/src/desktop/panel.cpp | 2 +- programs/src/installer/actions.cpp | 3 + programs/src/login/main.cpp | 32 +++ programs/src/pdfviewer/helpers.cpp | 40 +++ programs/src/pdfviewer/pdf_page.cpp | 352 +++++++++++++++++++++++- programs/src/pdfviewer/pdf_parser.cpp | 4 + programs/src/pdfviewer/pdfviewer.h | 13 + programs/src/pdfviewer/render.cpp | 43 ++- 26 files changed, 1541 insertions(+), 248 deletions(-) create mode 100644 kernel/src/ACPI/AcpiEvents.cpp create mode 100644 kernel/src/ACPI/AcpiEvents.hpp diff --git a/kernel/src/ACPI/AML/AmlInterpreter.cpp b/kernel/src/ACPI/AML/AmlInterpreter.cpp index a8cb2c0..320a49c 100644 --- a/kernel/src/ACPI/AML/AmlInterpreter.cpp +++ b/kernel/src/ACPI/AML/AmlInterpreter.cpp @@ -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; diff --git a/kernel/src/ACPI/AML/AmlInterpreter.hpp b/kernel/src/ACPI/AML/AmlInterpreter.hpp index a6b9cbc..b52966f 100644 --- a/kernel/src/ACPI/AML/AmlInterpreter.hpp +++ b/kernel/src/ACPI/AML/AmlInterpreter.hpp @@ -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(); }; diff --git a/kernel/src/ACPI/AML/AmlNamespace.hpp b/kernel/src/ACPI/AML/AmlNamespace.hpp index 4b4c476..0b55699 100644 --- a/kernel/src/ACPI/AML/AmlNamespace.hpp +++ b/kernel/src/ACPI/AML/AmlNamespace.hpp @@ -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: diff --git a/kernel/src/ACPI/AML/AmlParser.cpp b/kernel/src/ACPI/AML/AmlParser.cpp index 768bdb4..11b2318 100644 --- a/kernel/src/ACPI/AML/AmlParser.cpp +++ b/kernel/src/ACPI/AML/AmlParser.cpp @@ -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)) { diff --git a/kernel/src/ACPI/AML/AmlResource.cpp b/kernel/src/ACPI/AML/AmlResource.cpp index 5da9d20..49cdcbb 100644 --- a/kernel/src/ACPI/AML/AmlResource.cpp +++ b/kernel/src/ACPI/AML/AmlResource.cpp @@ -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; diff --git a/kernel/src/ACPI/AML/AmlResource.hpp b/kernel/src/ACPI/AML/AmlResource.hpp index 4152df8..ac027dd 100644 --- a/kernel/src/ACPI/AML/AmlResource.hpp +++ b/kernel/src/ACPI/AML/AmlResource.hpp @@ -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 { diff --git a/kernel/src/ACPI/AcpiDevices.cpp b/kernel/src/ACPI/AcpiDevices.cpp index bec11de..797c97b 100644 --- a/kernel/src/ACPI/AcpiDevices.cpp +++ b/kernel/src/ACPI/AcpiDevices.cpp @@ -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; diff --git a/kernel/src/ACPI/AcpiDevices.hpp b/kernel/src/ACPI/AcpiDevices.hpp index bd5ccf2..184fe53 100644 --- a/kernel/src/ACPI/AcpiDevices.hpp +++ b/kernel/src/ACPI/AcpiDevices.hpp @@ -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; diff --git a/kernel/src/ACPI/AcpiEvents.cpp b/kernel/src/ACPI/AcpiEvents.cpp new file mode 100644 index 0000000..65a55de --- /dev/null +++ b/kernel/src/ACPI/AcpiEvents.cpp @@ -0,0 +1,142 @@ +/* + * AcpiEvents.cpp + * ACPI fixed event handling (SCI interrupt, power button, etc.) + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "AcpiEvents.hpp" +#include +#include +#include +#include +#include +#include +#include + +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); + } + + }; +}; diff --git a/kernel/src/ACPI/AcpiEvents.hpp b/kernel/src/ACPI/AcpiEvents.hpp new file mode 100644 index 0000000..b3287d4 --- /dev/null +++ b/kernel/src/ACPI/AcpiEvents.hpp @@ -0,0 +1,22 @@ +/* + * AcpiEvents.hpp + * ACPI fixed event handling (SCI interrupt, power button, etc.) + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include +#include + +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(); + }; +}; diff --git a/kernel/src/ACPI/AcpiShutdown.cpp b/kernel/src/ACPI/AcpiShutdown.cpp index 90f06f9..26706e8 100644 --- a/kernel/src/ACPI/AcpiShutdown.cpp +++ b/kernel/src/ACPI/AcpiShutdown.cpp @@ -6,6 +6,7 @@ #include "AcpiShutdown.hpp" #include "AcpiSleep.hpp" +#include "AcpiEvents.hpp" #include #include #include @@ -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, diff --git a/kernel/src/ACPI/AcpiSleep.cpp b/kernel/src/ACPI/AcpiSleep.cpp index 8f0d295..bc1ec53 100644 --- a/kernel/src/ACPI/AcpiSleep.cpp +++ b/kernel/src/ACPI/AcpiSleep.cpp @@ -5,6 +5,7 @@ */ #include "AcpiSleep.hpp" +#include "AcpiEvents.hpp" #include #include #include @@ -22,6 +23,8 @@ #include #include #include +#include +#include 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; - g_facs->X_FirmwareWakingVector = 0; + + // 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"); diff --git a/kernel/src/ACPI/AcpiSleep.hpp b/kernel/src/ACPI/AcpiSleep.hpp index 7b41f8f..386cfd2 100644 --- a/kernel/src/ACPI/AcpiSleep.hpp +++ b/kernel/src/ACPI/AcpiSleep.hpp @@ -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); diff --git a/kernel/src/ACPI/S3Trampoline.asm b/kernel/src/ACPI/S3Trampoline.asm index 59974c5..3c4840e 100644 --- a/kernel/src/ACPI/S3Trampoline.asm +++ b/kernel/src/ACPI/S3Trampoline.asm @@ -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: diff --git a/kernel/src/ACPI/S3Wake.asm b/kernel/src/ACPI/S3Wake.asm index 8551876..1917800 100644 --- a/kernel/src/ACPI/S3Wake.asm +++ b/kernel/src/ACPI/S3Wake.asm @@ -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 diff --git a/kernel/src/Common/Panic.cpp b/kernel/src/Common/Panic.cpp index 3c625a3..2d80852 100644 --- a/kernel/src/Common/Panic.cpp +++ b/kernel/src/Common/Panic.cpp @@ -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; + uint64_t cr2; + asm volatile("mov %%cr2, %0" : "=r"(cr2)); + PrintBoxedHex(kerr, "Faulting Address (CR2)", cr2, BoxWidth); - // 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); + 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); +} - 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); - } +static void PrintGPFInfo(System::PanicFrame*& frame) { + auto* gpf = (System::GPFPanicFrame*)frame; + frame = (System::PanicFrame*)&gpf->IP; - 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); - } -#endif + PrintBoxedLine(kerr, "General Protection Fault:", BoxWidth, true); + PrintBoxedDec(kerr, "Error Code", gpf->GeneralProtectionFaultError, BoxWidth); +} - 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"); @@ -82,4 +62,42 @@ void Panic(const char *meditationString, System::PanicFrame* frame) { asm ("idle 0"); #endif } -} \ No newline at end of file +} + +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(); +} diff --git a/kernel/src/Hal/IDT.cpp b/kernel/src/Hal/IDT.cpp index dca179c..d45c333 100644 --- a/kernel/src/Hal/IDT.cpp +++ b/kernel/src/Hal/IDT.cpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace Hal { constexpr auto InterruptGate = 0x8E; @@ -53,11 +54,40 @@ 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 __attribute__((interrupt)) void ExceptionHandler(System::PanicFrame* frame) { - frame->InterruptVector = i; - Panic(ExceptionStrings[i], 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) { diff --git a/programs/include/Api/Syscall.hpp b/programs/include/Api/Syscall.hpp index 585557a..3ea82c0 100644 --- a/programs/include/Api/Syscall.hpp +++ b/programs/include/Api/Syscall.hpp @@ -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]; diff --git a/programs/src/desktop/panel.cpp b/programs/src/desktop/panel.cpp index e6ec131..0c48258 100644 --- a/programs/src/desktop/panel.cpp +++ b/programs/src/desktop/panel.cpp @@ -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); diff --git a/programs/src/installer/actions.cpp b/programs/src/installer/actions.cpp index 7a662a1..04feaf0 100644 --- a/programs/src/installer/actions.cpp +++ b/programs/src/installer/actions.cpp @@ -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]; diff --git a/programs/src/login/main.cpp b/programs/src/login/main.cpp index 627cb2d..693912c 100644 --- a/programs/src/login/main.cpp +++ b/programs/src/login/main.cpp @@ -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) { diff --git a/programs/src/pdfviewer/helpers.cpp b/programs/src/pdfviewer/helpers.cpp index d287a53..b9182fd 100644 --- a/programs/src/pdfviewer/helpers.cpp +++ b/programs/src/pdfviewer/helpers.cpp @@ -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++; diff --git a/programs/src/pdfviewer/pdf_page.cpp b/programs/src/pdfviewer/pdf_page.cpp index edfe9d3..4cc466b 100644 --- a/programs/src/pdfviewer/pdf_page.cpp +++ b/programs/src/pdfviewer/pdf_page.cpp @@ -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); } diff --git a/programs/src/pdfviewer/pdf_parser.cpp b/programs/src/pdfviewer/pdf_parser.cpp index 404b4d2..4230725 100644 --- a/programs/src/pdfviewer/pdf_parser.cpp +++ b/programs/src/pdfviewer/pdf_parser.cpp @@ -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; diff --git a/programs/src/pdfviewer/pdfviewer.h b/programs/src/pdfviewer/pdfviewer.h index c3984b0..43cc7c8 100644 --- a/programs/src/pdfviewer/pdfviewer.h +++ b/programs/src/pdfviewer/pdfviewer.h @@ -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); diff --git a/programs/src/pdfviewer/render.cpp b/programs/src/pdfviewer/render.cpp index 8983ef1..d382dbf 100644 --- a/programs/src/pdfviewer/render.cpp +++ b/programs/src/pdfviewer/render.cpp @@ -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); } } }