feat: S3 sleep, PDF viewer graphics rendering, live user environment
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
};
|
||||
|
||||
@@ -11,7 +11,11 @@
|
||||
namespace Hal {
|
||||
namespace AML {
|
||||
|
||||
// ── AML Object Types ────────────────────────────────────────────
|
||||
// ============================================================================
|
||||
|
||||
// AML Object Types
|
||||
|
||||
// ============================================================================
|
||||
enum class ObjectType : uint8_t {
|
||||
None = 0,
|
||||
Integer,
|
||||
@@ -29,7 +33,11 @@ namespace Hal {
|
||||
BufferField,
|
||||
};
|
||||
|
||||
// ── Region address spaces (OperationRegion) ─────────────────────
|
||||
// ============================================================================
|
||||
|
||||
// Region address spaces (OperationRegion)
|
||||
|
||||
// ============================================================================
|
||||
enum class RegionSpace : uint8_t {
|
||||
SystemMemory = 0x00,
|
||||
SystemIO = 0x01,
|
||||
@@ -40,7 +48,11 @@ namespace Hal {
|
||||
PciBarTarget = 0x06,
|
||||
};
|
||||
|
||||
// ── Constants ───────────────────────────────────────────────────
|
||||
// ============================================================================
|
||||
|
||||
// Constants
|
||||
|
||||
// ============================================================================
|
||||
static constexpr int MaxNameSegLen = 4;
|
||||
static constexpr int MaxPathDepth = 16;
|
||||
static constexpr int MaxChildren = 32;
|
||||
@@ -51,7 +63,11 @@ namespace Hal {
|
||||
static constexpr int MaxMethodLocals = 8;
|
||||
static constexpr int MaxNamespaceNodes = 256;
|
||||
|
||||
// ── AML Object ──────────────────────────────────────────────────
|
||||
// ============================================================================
|
||||
|
||||
// AML Object
|
||||
|
||||
// ============================================================================
|
||||
// Tagged union representing any AML value. Kept small for kernel use.
|
||||
struct Object {
|
||||
ObjectType Type = ObjectType::None;
|
||||
@@ -99,7 +115,11 @@ namespace Hal {
|
||||
Object() : Type(ObjectType::None), Integer(0) {}
|
||||
};
|
||||
|
||||
// ── Namespace Node ──────────────────────────────────────────────
|
||||
// ============================================================================
|
||||
|
||||
// Namespace Node
|
||||
|
||||
// ============================================================================
|
||||
// Each node has a 4-char name segment and an associated object.
|
||||
struct NamespaceNode {
|
||||
char Name[MaxNameSegLen + 1]; // null-terminated 4-char segment
|
||||
@@ -118,7 +138,11 @@ namespace Hal {
|
||||
}
|
||||
};
|
||||
|
||||
// ── Namespace ───────────────────────────────────────────────────
|
||||
// ============================================================================
|
||||
|
||||
// Namespace
|
||||
|
||||
// ============================================================================
|
||||
// Flat array of nodes forming a tree via parent/child indices.
|
||||
class Namespace {
|
||||
public:
|
||||
|
||||
@@ -15,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)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* AcpiEvents.cpp
|
||||
* ACPI fixed event handling (SCI interrupt, power button, etc.)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "AcpiEvents.hpp"
|
||||
#include <ACPI/AcpiSleep.hpp>
|
||||
#include <Io/IoPort.hpp>
|
||||
#include <Hal/Apic/Apic.hpp>
|
||||
#include <Hal/Apic/IoApic.hpp>
|
||||
#include <Hal/Apic/Interrupts.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Hal {
|
||||
namespace AcpiEvents {
|
||||
|
||||
// ============================================================================
|
||||
// State
|
||||
// ============================================================================
|
||||
static uint32_t g_pm1aEventBlock = 0;
|
||||
static uint32_t g_pm1bEventBlock = 0;
|
||||
static uint8_t g_pm1EventLength = 0;
|
||||
static uint16_t g_sciIrq = 0;
|
||||
static bool g_initialized = false;
|
||||
|
||||
// ============================================================================
|
||||
// PM1 register helpers
|
||||
// ============================================================================
|
||||
static uint16_t ReadPM1Status() {
|
||||
uint16_t sts = 0;
|
||||
if (g_pm1aEventBlock != 0)
|
||||
sts |= Io::In16((uint16_t)g_pm1aEventBlock);
|
||||
if (g_pm1bEventBlock != 0)
|
||||
sts |= Io::In16((uint16_t)g_pm1bEventBlock);
|
||||
return sts;
|
||||
}
|
||||
|
||||
static void ClearPM1StatusBits(uint16_t bits) {
|
||||
// Write-1-to-clear semantics
|
||||
if (g_pm1aEventBlock != 0)
|
||||
Io::Out16(bits, (uint16_t)g_pm1aEventBlock);
|
||||
if (g_pm1bEventBlock != 0)
|
||||
Io::Out16(bits, (uint16_t)g_pm1bEventBlock);
|
||||
}
|
||||
|
||||
static void EnablePM1Events(uint16_t mask) {
|
||||
uint16_t enableOffset = g_pm1EventLength / 2;
|
||||
if (enableOffset == 0) return;
|
||||
|
||||
if (g_pm1aEventBlock != 0) {
|
||||
uint16_t en = Io::In16((uint16_t)(g_pm1aEventBlock + enableOffset));
|
||||
en |= mask;
|
||||
Io::Out16(en, (uint16_t)(g_pm1aEventBlock + enableOffset));
|
||||
}
|
||||
if (g_pm1bEventBlock != 0) {
|
||||
uint16_t en = Io::In16((uint16_t)(g_pm1bEventBlock + enableOffset));
|
||||
en |= mask;
|
||||
Io::Out16(en, (uint16_t)(g_pm1bEventBlock + enableOffset));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SCI Interrupt Handler
|
||||
// ============================================================================
|
||||
static void SciHandler(uint8_t irq) {
|
||||
uint16_t sts = ReadPM1Status();
|
||||
|
||||
if (sts & AcpiSleep::PM1_PWRBTN_STS) {
|
||||
ClearPM1StatusBits(AcpiSleep::PM1_PWRBTN_STS);
|
||||
KernelLogStream(INFO, "ACPI") << "Power button pressed";
|
||||
}
|
||||
|
||||
if (sts & AcpiSleep::PM1_SLPBTN_STS) {
|
||||
ClearPM1StatusBits(AcpiSleep::PM1_SLPBTN_STS);
|
||||
KernelLogStream(INFO, "ACPI") << "Sleep button pressed";
|
||||
}
|
||||
|
||||
// Send EOI
|
||||
LocalApic::SendEOI();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// Initialize
|
||||
|
||||
// ============================================================================
|
||||
void Initialize(const FADT::ParsedFADT& fadt) {
|
||||
g_pm1aEventBlock = fadt.PM1aEventBlock;
|
||||
g_pm1bEventBlock = fadt.PM1bEventBlock;
|
||||
g_pm1EventLength = fadt.PM1EventLength;
|
||||
g_sciIrq = fadt.SCI_Interrupt;
|
||||
|
||||
if (g_pm1aEventBlock == 0) {
|
||||
KernelLogStream(ERROR, "ACPI") << "No PM1a event block - ACPI events unavailable";
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear any pending status bits
|
||||
ClearPM1StatusBits(
|
||||
AcpiSleep::PM1_PWRBTN_STS | AcpiSleep::PM1_SLPBTN_STS |
|
||||
AcpiSleep::PM1_WAK_STS | AcpiSleep::PM1_TMR_STS |
|
||||
AcpiSleep::PM1_GBL_STS | AcpiSleep::PM1_RTC_STS |
|
||||
AcpiSleep::PM1_BM_STS);
|
||||
|
||||
// Enable power button event
|
||||
EnablePM1Events(AcpiSleep::PM1_PWRBTN_EN);
|
||||
|
||||
// Route SCI to an IRQ vector. The SCI is level-triggered,
|
||||
// active-low (ACPI spec requirement). The MADT may have an
|
||||
// Interrupt Source Override for this IRQ that already sets
|
||||
// these flags; RouteIrq applies overrides automatically.
|
||||
uint8_t sciVector = IRQ_VECTOR_BASE + (uint8_t)g_sciIrq;
|
||||
uint8_t bspApicId = (uint8_t)LocalApic::GetId();
|
||||
|
||||
// Register the handler before routing to avoid missing events
|
||||
RegisterIrqHandler((uint8_t)g_sciIrq, SciHandler);
|
||||
|
||||
// Route via IoApic (applies MADT overrides for polarity/trigger)
|
||||
IoApic::RouteIrq((uint8_t)g_sciIrq, sciVector, bspApicId);
|
||||
|
||||
g_initialized = true;
|
||||
|
||||
KernelLogStream(OK, "ACPI") << "ACPI events initialized (SCI IRQ "
|
||||
<< base::dec << (uint64_t)g_sciIrq << ", vector "
|
||||
<< (uint64_t)sciVector << ")";
|
||||
}
|
||||
|
||||
void Reinitialize() {
|
||||
if (!g_initialized) return;
|
||||
|
||||
// Clear pending status and re-enable power button event
|
||||
ClearPM1StatusBits(
|
||||
AcpiSleep::PM1_PWRBTN_STS | AcpiSleep::PM1_SLPBTN_STS);
|
||||
EnablePM1Events(AcpiSleep::PM1_PWRBTN_EN);
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* AcpiEvents.hpp
|
||||
* ACPI fixed event handling (SCI interrupt, power button, etc.)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <ACPI/ACPI.hpp>
|
||||
#include <ACPI/FADT.hpp>
|
||||
|
||||
namespace Hal {
|
||||
namespace AcpiEvents {
|
||||
|
||||
// Initialize ACPI event handling: route the SCI interrupt,
|
||||
// register the handler, and enable fixed events (power button).
|
||||
// Must be called after APIC and FADT initialization.
|
||||
void Initialize(const FADT::ParsedFADT& fadt);
|
||||
|
||||
// Re-enable ACPI events after S3 resume.
|
||||
void Reinitialize();
|
||||
};
|
||||
};
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "AcpiShutdown.hpp"
|
||||
#include "AcpiSleep.hpp"
|
||||
#include "AcpiEvents.hpp"
|
||||
#include <ACPI/FADT.hpp>
|
||||
#include <ACPI/AML/AmlParser.hpp>
|
||||
#include <ACPI/AML/AmlInterpreter.hpp>
|
||||
@@ -67,6 +68,9 @@ namespace Hal {
|
||||
|
||||
// Initialize S3 suspend support (reads FACS and \_S3_ from namespace)
|
||||
AcpiSleep::Initialize(xsdt);
|
||||
|
||||
// Initialize ACPI event handling (SCI interrupt, power button)
|
||||
AcpiEvents::Initialize(fadt);
|
||||
}
|
||||
|
||||
bool IsAvailable() {
|
||||
@@ -78,6 +82,19 @@ namespace Hal {
|
||||
|
||||
KernelLogStream(INFO, "ACPI") << "Performing ACPI S5 shutdown...";
|
||||
|
||||
// Evaluate _PTS(5) — Prepare To Sleep (S5 = soft-off)
|
||||
auto& interp = AML::GetInterpreter();
|
||||
if (interp.IsInitialized()) {
|
||||
int32_t node = interp.GetNamespace().FindNode("\\_PTS");
|
||||
if (node >= 0) {
|
||||
AML::Object arg{};
|
||||
arg.Type = AML::ObjectType::Integer;
|
||||
arg.Integer = 5;
|
||||
AML::Object result{};
|
||||
interp.EvaluateMethod("\\_PTS", &arg, 1, result);
|
||||
}
|
||||
}
|
||||
|
||||
asm volatile("cli");
|
||||
|
||||
// Phase 1: Write SLP_TYP to PM1x_CNT without SLP_EN,
|
||||
|
||||
+258
-93
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "AcpiSleep.hpp"
|
||||
#include "AcpiEvents.hpp"
|
||||
#include <ACPI/FADT.hpp>
|
||||
#include <ACPI/AML/AmlParser.hpp>
|
||||
#include <ACPI/AML/AmlInterpreter.hpp>
|
||||
@@ -22,6 +23,8 @@
|
||||
#include <Drivers/PS2/PS2Controller.hpp>
|
||||
#include <Graphics/Cursor.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Hal/MSR.hpp>
|
||||
#include <Api/Syscall.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
@@ -31,6 +34,11 @@ extern "C" void AcpiResumeLongMode(Hal::AcpiSleep::CpuState* stateArea);
|
||||
extern "C" void AcpiWakeEntry();
|
||||
extern "C" void* g_wakeStatePtr;
|
||||
|
||||
// Called directly by AcpiResumeLongMode after restoring CPU state.
|
||||
// This avoids returning to Suspend() via ret, which is fragile because
|
||||
// the compiler's stack frame expectations may not match the restored state.
|
||||
extern "C" void AcpiResumeEntry();
|
||||
|
||||
// Real-mode trampoline from S3Trampoline.asm
|
||||
extern "C" char S3TrampolineStart[];
|
||||
extern "C" char S3Trampoline64[];
|
||||
@@ -40,6 +48,11 @@ extern "C" char S3TrampolineEnd[];
|
||||
// Physical address where the trampoline is copied (must be < 1MB, page-aligned)
|
||||
static constexpr uint32_t TRAMPOLINE_PHYS = 0x8000;
|
||||
|
||||
// Physical address for the shadow PML4 used by the trampoline (must be < 4GB).
|
||||
// The kernel PML4 may be above 4GB, but the 32-bit trampoline can only load
|
||||
// a 32-bit CR3. We copy the kernel PML4 entries here before entering S3.
|
||||
static constexpr uint32_t SHADOW_PML4_PHYS = 0x9000;
|
||||
|
||||
// GDT/TSS reload helpers
|
||||
namespace Hal {
|
||||
extern void BridgeLoadGDT();
|
||||
@@ -49,7 +62,11 @@ namespace Hal {
|
||||
namespace Hal {
|
||||
namespace AcpiSleep {
|
||||
|
||||
// ── State ───────────────────────────────────────────────────────
|
||||
// ============================================================================
|
||||
|
||||
// State
|
||||
|
||||
// ============================================================================
|
||||
static bool g_s3Available = false;
|
||||
static uint16_t g_s3SlpTypA = 0;
|
||||
static uint16_t g_s3SlpTypB = 0;
|
||||
@@ -65,10 +82,66 @@ namespace Hal {
|
||||
// CPU state save area (aligned for fxsave)
|
||||
static CpuState g_cpuState __attribute__((aligned(64)));
|
||||
|
||||
// ── Initialize ──────────────────────────────────────────────────
|
||||
// Set by AcpiResumeEntry to signal Suspend() that resume completed.
|
||||
static volatile int g_resumeComplete = 0;
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// Initialize
|
||||
|
||||
// ============================================================================
|
||||
void Initialize(ACPI::CommonSDTHeader* xsdt) {
|
||||
g_s3Available = false;
|
||||
|
||||
// Check CMOS 0x72 for S3 wake progress from a previous attempt.
|
||||
// Non-zero means the last wake attempt reached a specific stage.
|
||||
Io::Out8(0xF2, 0x70); // CMOS register 0x72 | NMI disable
|
||||
uint8_t wakeProgress = Io::In8(0x71);
|
||||
// Check CMOS 0x73 — set to 0xDD if C resume code was ever reached
|
||||
// (survives trampoline overwrites of 0x72 during sleep-wake loops)
|
||||
Io::Out8(0xF3, 0x70);
|
||||
uint8_t cReached = Io::In8(0x71);
|
||||
if (wakeProgress != 0 || cReached != 0) {
|
||||
KernelLogStream(WARNING, "S3") << "Previous S3 wake progress: 0x"
|
||||
<< base::hex << (uint64_t)wakeProgress
|
||||
<< (wakeProgress == 0xA1 ? " (16-bit trampoline)" :
|
||||
wakeProgress == 0xA2 ? " (32-bit mode)" :
|
||||
wakeProgress == 0xA3 ? " (entering long mode)" :
|
||||
wakeProgress == 0xB1 ? " (64-bit UEFI entry)" :
|
||||
wakeProgress == 0xC1 ? " (AcpiResumeLongMode entry)" :
|
||||
wakeProgress == 0xC2 ? " (GDT+CS reloaded)" :
|
||||
wakeProgress == 0xC3 ? " (IDT+CR4/CR0/CR3 restored)" :
|
||||
wakeProgress == 0xC4 ? " (about to ret to Suspend)" :
|
||||
wakeProgress == 0xD1 ? " (C resume path)" : " (unknown)");
|
||||
if (cReached == 0xEE)
|
||||
KernelLogStream(WARNING, "S3") << "Full resume completed (all reinit done)";
|
||||
else if (cReached == 0xDD)
|
||||
KernelLogStream(WARNING, "S3") << "C resume entered but did NOT complete all reinit";
|
||||
else
|
||||
KernelLogStream(WARNING, "S3") << "C resume path was NOT reached";
|
||||
// Read reinit step progress from CMOS 0x74
|
||||
Io::Out8(0xF4, 0x70);
|
||||
uint8_t reinitStep = Io::In8(0x71);
|
||||
if (reinitStep != 0) {
|
||||
const char* stepNames[] = {
|
||||
"?", "GDT", "TSS", "Syscalls", "PAT", "PIC",
|
||||
"LocalAPIC", "IoAPIC", "APICTimer", "PM1Clear",
|
||||
"WAK", "PS2", "IntelGPU", "sti"
|
||||
};
|
||||
const char* stepName = reinitStep <= 0x0D ? stepNames[reinitStep] : "?";
|
||||
KernelLogStream(WARNING, "S3") << "Reinit stopped after step 0x"
|
||||
<< base::hex << (uint64_t)reinitStep << " (" << stepName
|
||||
<< ") - next step crashed/hung";
|
||||
}
|
||||
Io::Out8(0xF4, 0x70);
|
||||
Io::Out8(0x00, 0x71);
|
||||
// Clear both
|
||||
Io::Out8(0xF2, 0x70);
|
||||
Io::Out8(0x00, 0x71);
|
||||
Io::Out8(0xF3, 0x70);
|
||||
Io::Out8(0x00, 0x71);
|
||||
}
|
||||
|
||||
FADT::ParsedFADT fadt{};
|
||||
if (!FADT::Parse(xsdt, fadt) || !fadt.Valid)
|
||||
return;
|
||||
@@ -96,9 +169,9 @@ namespace Hal {
|
||||
g_pm1bControlBlock = fadt.PM1bControlBlock;
|
||||
g_pm1EventLength = fadt.PM1EventLength;
|
||||
|
||||
// Find \_S3_ via brute-force DSDT scan (same approach as \_S5_).
|
||||
// This works on any DSDT regardless of complexity — does not
|
||||
// require the AML interpreter or namespace to be loaded.
|
||||
// Find \_S3_ via brute-force DSDT scan. The AML interpreter stores
|
||||
// packages as raw bytecode (no structured element access yet), so
|
||||
// the brute-force scanner is the reliable path for now.
|
||||
auto* dsdt = (void*)Memory::HHDM(fadt.DsdtAddress);
|
||||
AML::SleepObject s3 = AML::FindSleepState(dsdt, 3);
|
||||
if (s3.Valid) {
|
||||
@@ -111,6 +184,11 @@ namespace Hal {
|
||||
<< " SLP_TYPb=" << base::hex << (uint64_t)g_s3SlpTypB << ")";
|
||||
KernelLogStream(INFO, "S3") << "Kernel PML4 phys = " << base::hex
|
||||
<< (uint64_t)Memory::VMM::g_paging->PML4;
|
||||
KernelLogStream(INFO, "S3") << "FACS at phys " << base::hex
|
||||
<< (uint64_t)fadt.FacsAddress << " version=" << base::dec
|
||||
<< (uint64_t)g_facs->Version << " flags=" << base::hex
|
||||
<< (uint64_t)g_facs->Flags
|
||||
<< (g_facs->Flags & FACS_64BIT_WAKE_F ? " (64BIT_WAKE)" : " (32BIT_WAKE only)");
|
||||
} else {
|
||||
KernelLogStream(INFO, "S3") << "\\_S3_ not found in DSDT - S3 unavailable";
|
||||
}
|
||||
@@ -120,7 +198,11 @@ namespace Hal {
|
||||
return g_s3Available;
|
||||
}
|
||||
|
||||
// ── Evaluate _PTS (Prepare To Sleep) ────────────────────────────
|
||||
// ============================================================================
|
||||
|
||||
// Evaluate _PTS (Prepare To Sleep)
|
||||
|
||||
// ============================================================================
|
||||
static void EvaluatePts(int sleepState) {
|
||||
auto& interp = AML::GetInterpreter();
|
||||
if (!interp.IsInitialized()) return;
|
||||
@@ -138,7 +220,11 @@ namespace Hal {
|
||||
KernelLogStream(DEBUG, "S3") << "Evaluated \\_PTS(" << base::dec << (uint64_t)sleepState << ")";
|
||||
}
|
||||
|
||||
// ── Evaluate _WAK (System Wake) ─────────────────────────────────
|
||||
// ============================================================================
|
||||
|
||||
// Evaluate _WAK (System Wake)
|
||||
|
||||
// ============================================================================
|
||||
static void EvaluateWak(int sleepState) {
|
||||
auto& interp = AML::GetInterpreter();
|
||||
if (!interp.IsInitialized()) return;
|
||||
@@ -156,7 +242,11 @@ namespace Hal {
|
||||
KernelLogStream(DEBUG, "S3") << "Evaluated \\_WAK(" << base::dec << (uint64_t)sleepState << ")";
|
||||
}
|
||||
|
||||
// ── Clear PM1 Status Registers ──────────────────────────────────
|
||||
// ============================================================================
|
||||
|
||||
// Clear PM1 Status Registers
|
||||
|
||||
// ============================================================================
|
||||
static void ClearPM1Status() {
|
||||
// Write 1 to clear all status bits (write-1-to-clear semantics)
|
||||
uint16_t clearMask = PM1_WAK_STS | PM1_PWRBTN_STS | PM1_SLPBTN_STS |
|
||||
@@ -168,7 +258,11 @@ namespace Hal {
|
||||
Io::Out16(clearMask, (uint16_t)g_pm1bEventBlock);
|
||||
}
|
||||
|
||||
// ── Enable Wake Events ──────────────────────────────────────────
|
||||
// ============================================================================
|
||||
|
||||
// Enable Wake Events
|
||||
|
||||
// ============================================================================
|
||||
static void EnableWakeEvents() {
|
||||
// Enable register is at event block + PM1EventLength/2
|
||||
uint16_t enableOffset = g_pm1EventLength / 2;
|
||||
@@ -182,7 +276,11 @@ namespace Hal {
|
||||
Io::Out16(enableMask, (uint16_t)(g_pm1bEventBlock + enableOffset));
|
||||
}
|
||||
|
||||
// ── Install real-mode trampoline and set waking vector ────────────
|
||||
// ============================================================================
|
||||
|
||||
// Install real-mode trampoline and set waking vector
|
||||
|
||||
// ============================================================================
|
||||
static void SetWakingVector() {
|
||||
if (!g_facs) return;
|
||||
|
||||
@@ -197,26 +295,31 @@ namespace Hal {
|
||||
uint8_t* trampolineSrc = (uint8_t*)S3TrampolineStart;
|
||||
memcpy(trampolineDst, trampolineSrc, trampolineSize);
|
||||
|
||||
// Patch the trampoline data area with CR3 and resume addresses.
|
||||
// Patch the trampoline data area with CR3, resume addresses,
|
||||
// and PM1 control port addresses.
|
||||
// Data layout (from S3Trampoline.asm):
|
||||
// +0: uint64_t CR3 (full 64-bit PML4 physical address)
|
||||
// +8: uint64_t CpuState virtual address
|
||||
// +16: uint64_t AcpiResumeLongMode virtual address
|
||||
// +24: uint16_t PM1a control block I/O port
|
||||
// +26: uint16_t PM1b control block I/O port
|
||||
uint32_t dataOffset = (uint32_t)(S3TrampolineData - S3TrampolineStart);
|
||||
uint8_t* dataArea = trampolineDst + dataOffset;
|
||||
|
||||
// Use the kernel master PML4 (which has 0x8000 identity-mapped)
|
||||
// rather than the saved process PML4 (which doesn't).
|
||||
// AcpiResumeLongMode will restore the saved CR3 after we're
|
||||
// safely back in long mode with kernel GDT/IDT.
|
||||
uint64_t cr3val = (uint64_t)Memory::VMM::g_paging->PML4;
|
||||
// The kernel PML4 may be above 4GB (e.g. at 0x8BF7CC000), but
|
||||
// the 32-bit trampoline can only load a 32-bit CR3. Create a
|
||||
// shadow copy of the PML4 at a fixed low-memory address.
|
||||
// Only the PML4 page itself needs to be below 4GB — its entries
|
||||
// contain full 52-bit physical addresses for sub-tables.
|
||||
uint64_t kernelPml4Phys = (uint64_t)Memory::VMM::g_paging->PML4;
|
||||
uint8_t* kernelPml4Virt = (uint8_t*)Memory::HHDM(kernelPml4Phys);
|
||||
uint8_t* shadowPml4Virt = (uint8_t*)Memory::HHDM((uint64_t)SHADOW_PML4_PHYS);
|
||||
memcpy(shadowPml4Virt, kernelPml4Virt, 4096);
|
||||
|
||||
// The 16-bit->32-bit trampoline path can only load 32-bit CR3.
|
||||
// If the PML4 is above 4GB, the real-mode wake path would fail.
|
||||
if (cr3val > 0xFFFFFFFF) {
|
||||
KernelLogStream(ERROR, "S3") << "PML4 at " << base::hex << cr3val
|
||||
<< " is above 4GB - real-mode wake path will fail!";
|
||||
}
|
||||
uint64_t cr3val = (uint64_t)SHADOW_PML4_PHYS;
|
||||
|
||||
KernelLogStream(DEBUG, "S3") << "Shadow PML4 at " << base::hex << cr3val
|
||||
<< " (kernel PML4 at " << base::hex << kernelPml4Phys << ")";
|
||||
|
||||
memcpy(dataArea + 0, &cr3val, 8);
|
||||
|
||||
@@ -226,10 +329,24 @@ namespace Hal {
|
||||
uint64_t resumeAddr = (uint64_t)&AcpiResumeLongMode;
|
||||
memcpy(dataArea + 16, &resumeAddr, 8);
|
||||
|
||||
// Set the 32-bit waking vector only. Setting X_FirmwareWakingVector
|
||||
// to non-zero causes this laptop's firmware to hang during wake.
|
||||
uint16_t pm1a = (uint16_t)g_pm1aControlBlock;
|
||||
uint16_t pm1b = (uint16_t)g_pm1bControlBlock;
|
||||
memcpy(dataArea + 24, &pm1a, 2);
|
||||
memcpy(dataArea + 26, &pm1b, 2);
|
||||
|
||||
// Always set the 32-bit real-mode waking vector (universal fallback).
|
||||
g_facs->FirmwareWakingVector = TRAMPOLINE_PHYS;
|
||||
|
||||
// Use the 64-bit waking vector if the firmware advertises support.
|
||||
// FACS flags bit 1 (64BIT_WAKE_F) indicates the platform can
|
||||
// transfer control in 64-bit mode.
|
||||
if (g_facs->Flags & FACS_64BIT_WAKE_F) {
|
||||
g_facs->X_FirmwareWakingVector = TRAMPOLINE_PHYS + 0x100;
|
||||
KernelLogStream(DEBUG, "S3") << "Using 64-bit waking vector (0x8100)";
|
||||
} else {
|
||||
g_facs->X_FirmwareWakingVector = 0;
|
||||
KernelLogStream(DEBUG, "S3") << "Using 32-bit waking vector (0x8000)";
|
||||
}
|
||||
|
||||
KernelLogStream(DEBUG, "S3") << "Trampoline installed at " << base::hex
|
||||
<< (uint64_t)TRAMPOLINE_PHYS << " (" << base::dec
|
||||
@@ -237,7 +354,11 @@ namespace Hal {
|
||||
KernelLogStream(DEBUG, "S3") << "Trampoline CR3 = " << base::hex << cr3val;
|
||||
}
|
||||
|
||||
// ── Wait for WAK_STS ────────────────────────────────────────────
|
||||
// ============================================================================
|
||||
|
||||
// Wait for WAK_STS
|
||||
|
||||
// ============================================================================
|
||||
static void WaitForWake() {
|
||||
// After entering S3, the CPU halts. On resume, firmware runs the
|
||||
// waking vector. But if we somehow didn't enter sleep (e.g. immediate
|
||||
@@ -251,7 +372,101 @@ namespace Hal {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Suspend ─────────────────────────────────────────────────────
|
||||
// ============================================================================
|
||||
|
||||
// Resume Entry (called directly by AcpiResumeLongMode)
|
||||
|
||||
// ============================================================================
|
||||
// This is a standalone function with its own stack frame, avoiding
|
||||
// any dependency on the compiler's layout of Suspend().
|
||||
extern "C" void AcpiResumeEntry() {
|
||||
// Progress: reached C resume path (0xD1)
|
||||
Io::Out8(0xF2, 0x70);
|
||||
Io::Out8(0xD1, 0x71);
|
||||
Io::Out8(0xF3, 0x70);
|
||||
Io::Out8(0xDD, 0x71);
|
||||
|
||||
// CRITICAL: Clear SLP_EN and SLP_TYP in PM1 control registers
|
||||
if (g_pm1aControlBlock != 0) {
|
||||
uint16_t pm1a = Io::In16((uint16_t)g_pm1aControlBlock);
|
||||
pm1a &= ~(PM1_SLP_EN | PM1_SLP_TYP_MASK);
|
||||
Io::Out16(pm1a, (uint16_t)g_pm1aControlBlock);
|
||||
}
|
||||
if (g_pm1bControlBlock != 0) {
|
||||
uint16_t pm1b = Io::In16((uint16_t)g_pm1bControlBlock);
|
||||
pm1b &= ~(PM1_SLP_EN | PM1_SLP_TYP_MASK);
|
||||
Io::Out16(pm1b, (uint16_t)g_pm1bControlBlock);
|
||||
}
|
||||
|
||||
// Debug: green rectangle on framebuffer
|
||||
{
|
||||
uint32_t* fb = ::Graphics::Cursor::GetFramebufferBase();
|
||||
uint64_t pitch = ::Graphics::Cursor::GetFramebufferPitch();
|
||||
if (fb && pitch > 0) {
|
||||
for (int y = 0; y < 32; y++) {
|
||||
uint32_t* row = (uint32_t*)((uint8_t*)fb + y * pitch);
|
||||
for (int x = 0; x < 32; x++) {
|
||||
row[x] = 0xFF00FF00;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use CMOS 0x74 as step-by-step progress through reinit
|
||||
auto reinitProgress = [](uint8_t step) {
|
||||
Io::Out8(0xF4, 0x70);
|
||||
Io::Out8(step, 0x71);
|
||||
};
|
||||
|
||||
reinitProgress(0x01); // GDT
|
||||
Hal::BridgeLoadGDT();
|
||||
reinitProgress(0x02); // TSS
|
||||
Hal::LoadTSS();
|
||||
reinitProgress(0x03); // Syscalls
|
||||
Montauk::InitializeSyscalls();
|
||||
reinitProgress(0x04); // PAT
|
||||
Hal::InitializePAT();
|
||||
reinitProgress(0x05); // PIC
|
||||
Hal::DisableLegacyPic();
|
||||
reinitProgress(0x06); // Local APIC
|
||||
Hal::LocalApic::Reinitialize();
|
||||
reinitProgress(0x07); // IO APIC
|
||||
Hal::IoApic::Reinitialize();
|
||||
reinitProgress(0x08); // APIC Timer
|
||||
Timekeeping::ApicTimerReinitialize();
|
||||
reinitProgress(0x09); // PM1 clear
|
||||
ClearPM1Status();
|
||||
reinitProgress(0x0A); // _WAK
|
||||
EvaluateWak(3);
|
||||
reinitProgress(0x0B); // PS/2
|
||||
Drivers::PS2::Reinitialize();
|
||||
// Re-enable ACPI events (power button) after S3
|
||||
AcpiEvents::Reinitialize();
|
||||
reinitProgress(0x0C); // Intel GPU
|
||||
if (Drivers::Graphics::IntelGPU::IsInitialized()) {
|
||||
Drivers::Graphics::IntelGPU::Reinitialize();
|
||||
}
|
||||
reinitProgress(0x0D); // sti
|
||||
asm volatile("sti");
|
||||
|
||||
KernelLogStream(OK, "S3") << "Resumed from S3 suspend";
|
||||
|
||||
// Progress: all reinit complete (0xEE in CMOS 0x73)
|
||||
Io::Out8(0xF3, 0x70);
|
||||
Io::Out8(0xEE, 0x71);
|
||||
|
||||
// Signal Suspend() that resume is complete. AcpiResumeEntry
|
||||
// was entered via jmp (not call), so our ret will pop the
|
||||
// original return address from call AcpiSaveAndSuspend,
|
||||
// returning to Suspend() which checks this flag.
|
||||
g_resumeComplete = 1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// Suspend
|
||||
|
||||
// ============================================================================
|
||||
int Suspend() {
|
||||
if (!g_s3Available) {
|
||||
KernelLogStream(ERROR, "S3") << "S3 suspend not available";
|
||||
@@ -263,77 +478,27 @@ namespace Hal {
|
||||
// 1. Evaluate _PTS(3) — Prepare To Sleep
|
||||
EvaluatePts(3);
|
||||
|
||||
// 2. Save CPU state. AcpiSaveAndSuspend returns 1 on initial call,
|
||||
// and 0 when we resume from S3 (AcpiResumeLongMode sets RAX=0).
|
||||
int resumed = !AcpiSaveAndSuspend(&g_cpuState);
|
||||
if (resumed) {
|
||||
// ── RESUME PATH ─────────────────────────────────────────
|
||||
// We just woke from S3. Firmware ran our waking vector which
|
||||
// called AcpiResumeLongMode, restoring all registers and
|
||||
// returning here with 0.
|
||||
|
||||
// Debug: write a green rectangle to the top-left corner of the
|
||||
// framebuffer. The framebuffer memory survives S3 (it's RAM).
|
||||
// This will be visible once the GPU display plane is restored,
|
||||
// confirming the CPU successfully completed the resume path.
|
||||
{
|
||||
uint32_t* fb = ::Graphics::Cursor::GetFramebufferBase();
|
||||
uint64_t pitch = ::Graphics::Cursor::GetFramebufferPitch();
|
||||
if (fb && pitch > 0) {
|
||||
for (int y = 0; y < 32; y++) {
|
||||
uint32_t* row = (uint32_t*)((uint8_t*)fb + y * pitch);
|
||||
for (int x = 0; x < 32; x++) {
|
||||
row[x] = 0xFF00FF00; // Green (ARGB)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reload GDT and TSS (firmware may have clobbered them)
|
||||
Hal::BridgeLoadGDT();
|
||||
Hal::LoadTSS();
|
||||
|
||||
// Re-disable legacy 8259 PIC. Firmware may have re-enabled
|
||||
// it during S3 resume POST, which could cause spurious
|
||||
// interrupts on conflicting vectors once we enable interrupts.
|
||||
Hal::DisableLegacyPic();
|
||||
|
||||
// Re-enable the Local APIC (MSR global enable + SVR + TPR)
|
||||
Hal::LocalApic::Reinitialize();
|
||||
|
||||
// Restore I/O APIC redirection entries (lost during S3).
|
||||
// Must be done before enabling interrupts so IRQ routing
|
||||
// is in place when devices start generating interrupts.
|
||||
Hal::IoApic::Reinitialize();
|
||||
|
||||
// Restart the APIC timer
|
||||
Timekeeping::ApicTimerReinitialize();
|
||||
|
||||
// Clear WAK_STS
|
||||
ClearPM1Status();
|
||||
|
||||
// Evaluate _WAK(3)
|
||||
EvaluateWak(3);
|
||||
|
||||
// Re-enable PS/2 controller (ports and interrupts may be
|
||||
// disabled after S3; skip full self-test to avoid resetting
|
||||
// attached devices)
|
||||
Drivers::PS2::Reinitialize();
|
||||
|
||||
// Restore Intel GPU display (GTT + display plane lost during S3)
|
||||
if (Drivers::Graphics::IntelGPU::IsInitialized()) {
|
||||
Drivers::Graphics::IntelGPU::Reinitialize();
|
||||
}
|
||||
|
||||
// Re-enable interrupts
|
||||
asm volatile("sti");
|
||||
|
||||
KernelLogStream(OK, "S3") << "Resumed from S3 suspend";
|
||||
// 2. Save CPU state. On resume, AcpiResumeLongMode jumps to
|
||||
// AcpiResumeEntry (not back here), which does all reinit and
|
||||
// sets g_resumeComplete=1, then returns here via the stack.
|
||||
g_resumeComplete = 0;
|
||||
AcpiSaveAndSuspend(&g_cpuState);
|
||||
|
||||
// If we get here after resume, AcpiResumeEntry already ran.
|
||||
if (g_resumeComplete) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ── SUSPEND PATH (initial save returned 1) ──────────────────
|
||||
// ============================================================================
|
||||
|
||||
// SUSPEND PATH (initial save returned 1)
|
||||
|
||||
// ============================================================================
|
||||
// Clear CMOS progress markers before entering S3.
|
||||
Io::Out8(0xF2, 0x70); // CMOS register 0x72 | NMI disable
|
||||
Io::Out8(0x00, 0x71);
|
||||
Io::Out8(0xF3, 0x70); // CMOS register 0x73
|
||||
Io::Out8(0x00, 0x71);
|
||||
|
||||
// 3. Disable interrupts
|
||||
asm volatile("cli");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
+74
-56
@@ -6,72 +6,52 @@
|
||||
#include "Panic.hpp"
|
||||
#include "../CppLib/BoxUI.hpp"
|
||||
|
||||
void Panic(const char *meditationString, System::PanicFrame* frame) {
|
||||
const int boxWidth = 72;
|
||||
static constexpr int BoxWidth = 72;
|
||||
|
||||
// Header
|
||||
kerr << BOXUI_ANSI_RED_BG << BOXUI_ANSI_WHITE_FG << BOXUI_ANSI_BOLD << "\n";
|
||||
kerr << BOXUI_TL;
|
||||
for (int i = 0; i < boxWidth - 2; ++i) kerr << BOXUI_H;
|
||||
kerr << BOXUI_TR << "\n";
|
||||
PrintBoxedLine(kerr, "!!! KERNEL PANIC !!!", boxWidth, true);
|
||||
PrintBoxedLine(kerr, "", boxWidth);
|
||||
PrintBoxedLine(kerr, "System halted. Please reboot.", boxWidth, true);
|
||||
PrintBoxedLine(kerr, "", boxWidth);
|
||||
PrintBoxedSeparator(kerr, boxWidth);
|
||||
PrintBoxedLine(kerr, "Meditation:", boxWidth, true);
|
||||
PrintBoxedLine(kerr, meditationString, boxWidth);
|
||||
PrintBoxedLine(kerr, "", boxWidth);
|
||||
static void PrintHorizontalEdge(const char* left, const char* right) {
|
||||
kerr << left;
|
||||
for (int i = 0; i < BoxWidth - 2; ++i) kerr << BOXUI_H;
|
||||
kerr << right << "\n";
|
||||
}
|
||||
|
||||
#if defined (__x86_64__)
|
||||
if (frame != nullptr) {
|
||||
PrintBoxedSeparator(kerr, boxWidth);
|
||||
PrintBoxedLine(kerr, "CPU State:", boxWidth, true);
|
||||
PrintBoxedHex(kerr, "Interrupt Vector", frame->InterruptVector, boxWidth);
|
||||
static void PrintPageFaultInfo(System::PanicFrame*& frame) {
|
||||
auto* pf = (System::PageFaultPanicFrame*)frame;
|
||||
frame = (System::PanicFrame*)&pf->IP;
|
||||
|
||||
if (frame->InterruptVector == 0xE) {
|
||||
auto pf_frame = (System::PageFaultPanicFrame*)frame;
|
||||
frame = (System::PanicFrame*)&pf_frame->IP;
|
||||
|
||||
// CR2 holds the faulting virtual address for page faults
|
||||
uint64_t cr2;
|
||||
asm volatile("mov %%cr2, %0" : "=r"(cr2));
|
||||
PrintBoxedHex(kerr, "Faulting Address (CR2)", cr2, boxWidth);
|
||||
PrintBoxedHex(kerr, "Faulting Address (CR2)", cr2, BoxWidth);
|
||||
|
||||
PrintBoxedLine(kerr, "Page Fault Error:", boxWidth, true);
|
||||
PrintBoxedDec(kerr, "Present", pf_frame->PageFaultError.Present, boxWidth);
|
||||
PrintBoxedDec(kerr, "Write", pf_frame->PageFaultError.Write, boxWidth);
|
||||
PrintBoxedDec(kerr, "User", pf_frame->PageFaultError.User, boxWidth);
|
||||
PrintBoxedDec(kerr, "Reserved Write", pf_frame->PageFaultError.ReservedWrite, boxWidth);
|
||||
PrintBoxedDec(kerr, "Instruction Fetch", pf_frame->PageFaultError.InstructionFetch, boxWidth);
|
||||
PrintBoxedDec(kerr, "Protection Key", pf_frame->PageFaultError.ProtectionKey, boxWidth);
|
||||
PrintBoxedDec(kerr, "Shadow Stack", pf_frame->PageFaultError.ShadowStack, boxWidth);
|
||||
PrintBoxedDec(kerr, "SGX", pf_frame->PageFaultError.SGX, boxWidth);
|
||||
} else if (frame->InterruptVector == 0xD) {
|
||||
auto gpf_frame = (System::GPFPanicFrame*)frame;
|
||||
frame = (System::PanicFrame*)&frame->IP;
|
||||
PrintBoxedLine(kerr, "General Protection Fault:", boxWidth, true);
|
||||
PrintBoxedDec(kerr, "Error Code", gpf_frame->GeneralProtectionFaultError, boxWidth);
|
||||
}
|
||||
PrintBoxedLine(kerr, "Page Fault Error:", BoxWidth, true);
|
||||
PrintBoxedDec(kerr, "Present", pf->PageFaultError.Present, BoxWidth);
|
||||
PrintBoxedDec(kerr, "Write", pf->PageFaultError.Write, BoxWidth);
|
||||
PrintBoxedDec(kerr, "User", pf->PageFaultError.User, BoxWidth);
|
||||
PrintBoxedDec(kerr, "Reserved Write", pf->PageFaultError.ReservedWrite, BoxWidth);
|
||||
PrintBoxedDec(kerr, "Instruction Fetch",pf->PageFaultError.InstructionFetch, BoxWidth);
|
||||
PrintBoxedDec(kerr, "Protection Key", pf->PageFaultError.ProtectionKey, BoxWidth);
|
||||
PrintBoxedDec(kerr, "Shadow Stack", pf->PageFaultError.ShadowStack, BoxWidth);
|
||||
PrintBoxedDec(kerr, "SGX", pf->PageFaultError.SGX, BoxWidth);
|
||||
}
|
||||
|
||||
PrintBoxedSeparator(kerr, boxWidth);
|
||||
PrintBoxedLine(kerr, "Registers:", boxWidth, true);
|
||||
PrintBoxedHex(kerr, "Instruction Pointer", frame->IP, boxWidth);
|
||||
PrintBoxedHex(kerr, "Code Segment", frame->CS, boxWidth);
|
||||
PrintBoxedHex(kerr, "Flags", frame->Flags, boxWidth);
|
||||
PrintBoxedHex(kerr, "Stack Pointer", frame->SP, boxWidth);
|
||||
PrintBoxedHex(kerr, "Stack Segment", frame->SS, boxWidth);
|
||||
}
|
||||
#endif
|
||||
static void PrintGPFInfo(System::PanicFrame*& frame) {
|
||||
auto* gpf = (System::GPFPanicFrame*)frame;
|
||||
frame = (System::PanicFrame*)&gpf->IP;
|
||||
|
||||
PrintBoxedLine(kerr, "", boxWidth);
|
||||
PrintBoxedLine(kerr, "General Protection Fault:", BoxWidth, true);
|
||||
PrintBoxedDec(kerr, "Error Code", gpf->GeneralProtectionFaultError, BoxWidth);
|
||||
}
|
||||
|
||||
// Footer
|
||||
kerr << BOXUI_BL;
|
||||
for (int i = 0; i < boxWidth - 2; ++i) kerr << BOXUI_H;
|
||||
kerr << BOXUI_BR << "\n";
|
||||
kerr << BOXUI_ANSI_RESET;
|
||||
static void PrintRegisters(System::PanicFrame* frame) {
|
||||
PrintBoxedSeparator(kerr, BoxWidth);
|
||||
PrintBoxedLine(kerr, "Registers:", BoxWidth, true);
|
||||
PrintBoxedHex(kerr, "Instruction Pointer", frame->IP, BoxWidth);
|
||||
PrintBoxedHex(kerr, "Code Segment", frame->CS, BoxWidth);
|
||||
PrintBoxedHex(kerr, "Flags", frame->Flags, BoxWidth);
|
||||
PrintBoxedHex(kerr, "Stack Pointer", frame->SP, BoxWidth);
|
||||
PrintBoxedHex(kerr, "Stack Segment", frame->SS, BoxWidth);
|
||||
}
|
||||
|
||||
[[noreturn]] static void Halt() {
|
||||
while (true) {
|
||||
#if defined (__x86_64__)
|
||||
asm ("cli");
|
||||
@@ -83,3 +63,41 @@ void Panic(const char *meditationString, System::PanicFrame* frame) {
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void Panic(const char *meditationString, System::PanicFrame* frame) {
|
||||
// Header
|
||||
kerr << BOXUI_ANSI_RED_BG << BOXUI_ANSI_WHITE_FG << BOXUI_ANSI_BOLD << "\n";
|
||||
PrintHorizontalEdge(BOXUI_TL, BOXUI_TR);
|
||||
PrintBoxedLine(kerr, "!!! KERNEL PANIC !!!", BoxWidth, true);
|
||||
PrintBoxedLine(kerr, "", BoxWidth);
|
||||
PrintBoxedLine(kerr, "System halted. Please reboot.", BoxWidth, true);
|
||||
PrintBoxedLine(kerr, "", BoxWidth);
|
||||
|
||||
// Meditation string
|
||||
PrintBoxedSeparator(kerr, BoxWidth);
|
||||
PrintBoxedLine(kerr, meditationString, BoxWidth);
|
||||
PrintBoxedLine(kerr, "", BoxWidth);
|
||||
|
||||
// CPU state (x86_64 only)
|
||||
#if defined (__x86_64__)
|
||||
if (frame != nullptr) {
|
||||
PrintBoxedSeparator(kerr, BoxWidth);
|
||||
PrintBoxedLine(kerr, "CPU State:", BoxWidth, true);
|
||||
PrintBoxedHex(kerr, "Interrupt Vector", frame->InterruptVector, BoxWidth);
|
||||
|
||||
if (frame->InterruptVector == 0xE)
|
||||
PrintPageFaultInfo(frame);
|
||||
else if (frame->InterruptVector == 0xD)
|
||||
PrintGPFInfo(frame);
|
||||
|
||||
PrintRegisters(frame);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Footer
|
||||
PrintBoxedLine(kerr, "", BoxWidth);
|
||||
PrintHorizontalEdge(BOXUI_BL, BOXUI_BR);
|
||||
kerr << BOXUI_ANSI_RESET;
|
||||
|
||||
Halt();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <Platform/Registers.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Sched/Scheduler.hpp>
|
||||
|
||||
namespace Hal {
|
||||
constexpr auto InterruptGate = 0x8E;
|
||||
@@ -53,12 +54,41 @@ namespace Hal {
|
||||
"Reserved"
|
||||
};
|
||||
|
||||
// Exceptions that push a hardware error code before IP/CS/FLAGS/SP/SS
|
||||
static bool ExceptionHasErrorCode(uint8_t vector) {
|
||||
return vector == 8 || (vector >= 10 && vector <= 14)
|
||||
|| vector == 17 || vector == 21 || vector == 29 || vector == 30;
|
||||
}
|
||||
|
||||
// Extract CS from the interrupt frame. For error-code exceptions, the
|
||||
// error code sits at offset 0, shifting IP to +8 and CS to +16.
|
||||
static uint64_t GetExceptionCS(uint8_t vector, System::PanicFrame* frame) {
|
||||
if (ExceptionHasErrorCode(vector)) {
|
||||
return *(uint64_t*)((uint8_t*)frame + 16);
|
||||
}
|
||||
return frame->CS;
|
||||
}
|
||||
|
||||
template<size_t i>
|
||||
__attribute__((interrupt)) void ExceptionHandler(System::PanicFrame* frame)
|
||||
{
|
||||
uint64_t cs = GetExceptionCS(i, frame);
|
||||
|
||||
// If the fault originated in user-mode (ring 3), kill the process
|
||||
// instead of panicking the entire system.
|
||||
if ((cs & 3) == 3 && Sched::GetCurrentPid() >= 0) {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
Kt::KernelLogStream(Kt::ERROR, "Exception")
|
||||
<< ExceptionStrings[i] << " in process \""
|
||||
<< proc->name << "\" (pid " << proc->pid
|
||||
<< ") - process terminated";
|
||||
Sched::ExitProcess();
|
||||
__builtin_unreachable();
|
||||
} else {
|
||||
frame->InterruptVector = i;
|
||||
Panic(ExceptionStrings[i], frame);
|
||||
}
|
||||
}
|
||||
|
||||
void LoadIDT(IDTRStruct& idtr) {
|
||||
asm("lidt %0" : : "m"(idtr));
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user