/* * AcpiDevices.cpp * ACPI device enumeration via AML namespace * Copyright (c) 2026 Daniel Hammer */ #include "AcpiDevices.hpp" #include #include #include #include #include using namespace Kt; namespace Hal { namespace AcpiDevices { // ============================================================================ // EISAID decoding // ============================================================================ // ACPI encodes PNP IDs as compressed 32-bit EISAIDs. static constexpr uint32_t ByteSwap32(uint32_t value) { return ((value & 0x000000FFu) << 24) | ((value & 0x0000FF00u) << 8) | ((value & 0x00FF0000u) >> 8) | ((value & 0xFF000000u) >> 24); } static_assert(ByteSwap32(0x0301D041u) == 0x41D00103u); // PNP0103 static_assert(ByteSwap32(0x090CD041u) == 0x41D00C09u); // PNP0C09 static void DecodeEisaId(uint32_t id, char* out) { // AML exposes the EISAID integer in little-endian byte order, while // the compressed manufacturer and product fields are defined in // display order. Convert it before extracting either field. id = ByteSwap32(id); // EISA ID encoding: // Bits 31-16: 3 compressed letters (5 bits each, '@' based) // Bits 15-0: 4 hex digits (product number) out[0] = (char)(((id >> 26) & 0x1F) + '@'); out[1] = (char)(((id >> 21) & 0x1F) + '@'); out[2] = (char)(((id >> 16) & 0x1F) + '@'); // Product ID as 4 hex digits static const char hex[] = "0123456789ABCDEF"; out[3] = hex[(id >> 12) & 0xF]; out[4] = hex[(id >> 8) & 0xF]; out[5] = hex[(id >> 4) & 0xF]; out[6] = hex[(id >> 0) & 0xF]; out[7] = '\0'; } // ============================================================================ // String comparison // ============================================================================ static bool StrEqual(const char* a, const char* b) { while (*a && *b) { if (*a != *b) return false; a++; b++; } return *a == *b; } static void StrCopy(char* dst, const char* src, int maxLen) { int i = 0; while (src[i] && i < maxLen - 1) { dst[i] = src[i]; i++; } dst[i] = '\0'; } // ============================================================================ // DeviceList methods // ============================================================================ const DeviceInfo* DeviceList::FindByHid(const char* hid) const { for (int i = 0; i < Count; i++) { if (StrEqual(Devices[i].HardwareId, hid)) return &Devices[i]; } return nullptr; } const DeviceInfo* DeviceList::FindByPath(const char* path) const { for (int i = 0; i < Count; i++) { if (StrEqual(Devices[i].Path, path)) return &Devices[i]; } return nullptr; } // ============================================================================ // EvaluateSta // ============================================================================ uint32_t EvaluateSta(int32_t deviceNodeIndex) { auto& interp = AML::GetInterpreter(); auto& ns = interp.GetNamespace(); // Look for _STA child int32_t staNode = -1; ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None, [&](int32_t idx, const AML::NamespaceNode* node) -> bool { if (node->Name[0] == '_' && node->Name[1] == 'S' && node->Name[2] == 'T' && node->Name[3] == 'A') { staNode = idx; return false; // stop } return true; }); if (staNode < 0) return STA_DEFAULT; auto* staObj = ns.GetNode(staNode); if (!staObj) return STA_DEFAULT; if (staObj->Obj.Type == AML::ObjectType::Method) { AML::Object result{}; char path[256]; ns.GetNodePath(staNode, path, 256); if (interp.EvaluateObject(path, result)) return (uint32_t)result.Integer; return STA_DEFAULT; } if (staObj->Obj.Type == AML::ObjectType::Integer) return (uint32_t)staObj->Obj.Integer; return STA_DEFAULT; } // ============================================================================ // EvaluateAdr // ============================================================================ uint64_t EvaluateAdr(int32_t deviceNodeIndex) { auto& interp = AML::GetInterpreter(); auto& ns = interp.GetNamespace(); int32_t adrNode = -1; ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None, [&](int32_t idx, const AML::NamespaceNode* node) -> bool { if (node->Name[0] == '_' && node->Name[1] == 'A' && node->Name[2] == 'D' && node->Name[3] == 'R') { adrNode = idx; return false; } return true; }); if (adrNode < 0) return 0; auto* adrObj = ns.GetNode(adrNode); if (!adrObj) return 0; if (adrObj->Obj.Type == AML::ObjectType::Method) { AML::Object result{}; char path[256]; ns.GetNodePath(adrNode, path, 256); if (interp.EvaluateObject(path, result)) return result.Integer; return 0; } if (adrObj->Obj.Type == AML::ObjectType::Integer) return adrObj->Obj.Integer; return 0; } // ============================================================================ // EvaluateHid // ============================================================================ bool EvaluateHid(int32_t deviceNodeIndex, char* outHid, int maxLen) { auto& interp = AML::GetInterpreter(); auto& ns = interp.GetNamespace(); int32_t hidNode = -1; ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None, [&](int32_t idx, const AML::NamespaceNode* node) -> bool { if (node->Name[0] == '_' && node->Name[1] == 'H' && node->Name[2] == 'I' && node->Name[3] == 'D') { hidNode = idx; return false; } return true; }); if (hidNode < 0) return false; AML::Object result{}; char path[256]; ns.GetNodePath(hidNode, path, 256); if (!interp.EvaluateObject(path, result)) return false; if (result.Type == AML::ObjectType::Integer) { // EISA ID DecodeEisaId((uint32_t)result.Integer, outHid); return true; } if (result.Type == AML::ObjectType::String) { StrCopy(outHid, result.String.Data, maxLen); return true; } return false; } // ============================================================================ // EvaluateUid // ============================================================================ bool EvaluateUid(int32_t deviceNodeIndex, char* outUid, int maxLen) { auto& interp = AML::GetInterpreter(); auto& ns = interp.GetNamespace(); int32_t uidNode = -1; ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None, [&](int32_t idx, const AML::NamespaceNode* node) -> bool { if (node->Name[0] == '_' && node->Name[1] == 'U' && node->Name[2] == 'I' && node->Name[3] == 'D') { uidNode = idx; return false; } return true; }); if (uidNode < 0) return false; AML::Object result{}; char path[256]; ns.GetNodePath(uidNode, path, 256); if (!interp.EvaluateObject(path, result)) return false; if (result.Type == AML::ObjectType::Integer) { // Convert integer to string uint64_t val = result.Integer; char buf[21]; int i = 0; if (val == 0) { buf[i++] = '0'; } else { char tmp[21]; int t = 0; while (val > 0) { tmp[t++] = '0' + (val % 10); val /= 10; } for (int j = t - 1; j >= 0; j--) buf[i++] = tmp[j]; } buf[i] = '\0'; StrCopy(outUid, buf, maxLen); return true; } if (result.Type == AML::ObjectType::String) { StrCopy(outUid, result.String.Data, maxLen); return true; } return false; } // ============================================================================ // EvaluateCrs // ============================================================================ bool EvaluateCrs(int32_t deviceNodeIndex, AML::ResourceList& result) { auto& interp = AML::GetInterpreter(); auto& ns = interp.GetNamespace(); int32_t crsNode = -1; ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None, [&](int32_t idx, const AML::NamespaceNode* node) -> bool { if (node->Name[0] == '_' && node->Name[1] == 'C' && node->Name[2] == 'R' && node->Name[3] == 'S') { crsNode = idx; return false; } return true; }); if (crsNode < 0) return false; AML::Object crsResult{}; char path[256]; ns.GetNodePath(crsNode, path, 256); if (!interp.EvaluateObject(path, crsResult)) return false; if (crsResult.Type != AML::ObjectType::Buffer) return false; return AML::ParseResourceTemplate(crsResult.Buffer.Data, crsResult.Buffer.Length, result); } // ============================================================================ // EnumerateAll // ============================================================================ // Fill one DeviceInfo from a namespace node. static void DescribeDevice(AML::Namespace& ns, int32_t idx, DeviceInfo& dev) { dev.NodeIndex = idx; ns.GetNodePath(idx, dev.Path, 128); dev.Status = EvaluateSta(idx); dev.IsPresent = (dev.Status & STA_PRESENT) && (dev.Status & STA_FUNCTIONAL); if (!EvaluateHid(idx, dev.HardwareId, 16)) dev.HardwareId[0] = '\0'; if (!EvaluateUid(idx, dev.UniqueId, 16)) dev.UniqueId[0] = '\0'; dev.Address = EvaluateAdr(idx); } // ============================================================================ // FindDeviceByHid // ============================================================================ bool FindDeviceByHid(const char* hid, DeviceInfo& out) { auto& interp = AML::GetInterpreter(); if (!interp.IsInitialized()) return false; auto& ns = interp.GetNamespace(); int32_t found = -1; // Only _HID is evaluated per candidate; the full description is // built once, for the match. WalkDescendants has no early exit, so // the first match wins and later ones are ignored. ns.WalkDescendants(ns.RootIndex(), AML::ObjectType::Device, [&](int32_t idx, [[maybe_unused]] const AML::NamespaceNode* node) { if (found >= 0) return; char candidate[16]; if (!EvaluateHid(idx, candidate, 16)) return; if (StrEqual(candidate, hid)) found = idx; }); if (found < 0) return false; out = DeviceInfo{}; DescribeDevice(ns, found, out); return true; } // ============================================================================ // EnumerateAll // ============================================================================ void EnumerateAll(DeviceList& result) { auto& interp = AML::GetInterpreter(); if (!interp.IsInitialized()) return; auto& ns = interp.GetNamespace(); result.Count = 0; result.Truncated = false; // Walk all namespace nodes looking for Device objects ns.WalkDescendants(ns.RootIndex(), AML::ObjectType::Device, [&](int32_t idx, [[maybe_unused]] const AML::NamespaceNode* node) { if (result.Count >= MaxDevices) { result.Truncated = true; return; } DescribeDevice(ns, idx, result.Devices[result.Count]); result.Count++; }); KernelLogStream(OK, "ACPI") << "Enumerated " << base::dec << (uint64_t)result.Count << " ACPI devices"; // The list is a fixed-size stack object, so a large namespace runs // past it. Say so: a device missing from the log below may simply // be one that did not fit, and looking it up by HID still works. if (result.Truncated) { KernelLogStream(WARNING, "ACPI") << "Device list truncated at " << base::dec << (uint64_t)MaxDevices << "; use FindDeviceByHid to reach the rest"; } // Log discovered devices for (int i = 0; i < result.Count; i++) { auto& dev = result.Devices[i]; if (dev.HardwareId[0]) { KernelLogStream(DEBUG, "ACPI") << " " << dev.Path << " HID=" << dev.HardwareId << (dev.IsPresent ? " [present]" : " [not present]"); } else if (dev.Address != 0) { KernelLogStream(DEBUG, "ACPI") << " " << dev.Path << " ADR=" << base::hex << dev.Address << (dev.IsPresent ? " [present]" : " [not present]"); } } } // ============================================================================ // GetSleepState // ============================================================================ SleepState GetSleepState(int state) { SleepState result{}; result.Valid = false; if (state < 0 || state > 5) return result; char path[8] = "\\_Sx_"; path[2] = 'S'; path[3] = '0' + (char)state; path[4] = '_'; path[5] = '\0'; auto& interp = AML::GetInterpreter(); AML::Object obj{}; if (!interp.EvaluateObject(path, obj)) return result; // The sleep state object should be a Package with at least 2 integers. // During loading, we stored Package data as a raw buffer. // For the common case, the \_S5_ was already extracted by the old parser. // Try to get it from namespace directly. auto& ns = interp.GetNamespace(); int32_t node = ns.FindNode(path); if (node < 0) return result; auto* nsNode = ns.GetNode(node); if (!nsNode) return result; if (nsNode->Obj.Type == AML::ObjectType::Package && nsNode->Obj.Package.Data) { // Parse the raw package elements to extract SLP_TYP values const uint8_t* data = nsNode->Obj.Package.Data; uint32_t len = nsNode->Obj.Package.Length; if (len < 1) return result; uint32_t numElements = nsNode->Obj.Package.Count; uint32_t pos = 0; if (numElements >= 1 && pos < len) { // Decode first element uint8_t elem0 = data[pos]; if (elem0 == AML::ZeroOp) { result.SLP_TYPa = 0; pos++; } else if (elem0 == AML::OneOp) { result.SLP_TYPa = 1; pos++; } else if (elem0 == AML::BytePrefix && pos + 1 < len) { result.SLP_TYPa = data[pos + 1]; pos += 2; } else { pos++; } } if (numElements >= 2 && pos < len) { uint8_t elem1 = data[pos]; if (elem1 == AML::ZeroOp) { result.SLP_TYPb = 0; pos++; } else if (elem1 == AML::OneOp) { result.SLP_TYPb = 1; pos++; } else if (elem1 == AML::BytePrefix && pos + 1 < len) { result.SLP_TYPb = data[pos + 1]; pos += 2; } else { pos++; } } result.Valid = true; } else if (nsNode->Obj.Type == AML::ObjectType::Integer) { result.SLP_TYPa = (uint16_t)nsNode->Obj.Integer; result.SLP_TYPb = 0; result.Valid = true; } return result; } }; };