LibACPI: Add a skeleton of a AML interpreter

This a simple skeleton for a non recursive AML parser + interpreter.
For now it support very little stuff, only a couple of opcodes but that
is enough to load a very very simple AML table.

Co-authored-by: RatcheT2497 <[email protected]>
This commit is contained in:
UndefineandRatcheT2497 authored and Sönke Holz committed 2026-10-28 18:13:04 +01:00
1 parent 7af126f893
commit 95ab8a7e1a
18 files changed
+1775

No files matched your search

+4
View File
@@ -10,6 +10,10 @@
# cmakedefine01 AFLACLOADER_DEBUG
#endif
#ifndef AML_DEBUG
# cmakedefine01 AML_DEBUG
#endif
#ifndef AUDIO_DEBUG
# cmakedefine01 AUDIO_DEBUG
#endif
+3
View File
@@ -2,6 +2,7 @@ set(AC97_DEBUG ON)
set(ACPI_DEBUG ON)
set(AFLACLOADER_DEBUG ON)
set(AHCI_DEBUG ON)
set(AML_DEBUG ON)
set(APIC_DEBUG ON)
set(APIC_SMP_DEBUG ON)
set(ARP_DEBUG ON)
@@ -282,3 +283,5 @@ set(XML_PARSER_DEBUG ON)
# set(IA32_DEBUG_INTERFACE ON)
# False positive: DEBUG_STRIPPED is a PE characteristic, not a debug flag
# set(DEBUG_STRIPPED ON)
# False positive: DEBUG_LOG is a macro in the ACPI interpreter
# set(DEBUG_LOG ON)
+1
View File
@@ -1,4 +1,5 @@
add_subdirectory(LibAccelGfx)
add_subdirectory(LibACPI)
add_subdirectory(LibArchive)
add_subdirectory(LibAudio)
add_subdirectory(LibC)
@@ -1,4 +1,10 @@
set(SOURCES
Interpreter.cpp
NameString.cpp
Node.cpp
OpcodeFilters.cpp
OpcodeFrame.cpp
Table.cpp
TableStream.cpp
)
serenity_lib(LibACPI acpi)
+154
View File
@@ -7,7 +7,9 @@
#pragma once
#include <AK/EnumBits.h>
#include <AK/Format.h>
#include <AK/StdLibExtras.h>
#include <AK/Traits.h>
#include <AK/Types.h>
namespace ACPI {
@@ -336,4 +338,156 @@ struct [[gnu::packed]] DSDT {
}
namespace AML {
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#aml-byte-stream-byte-values
constexpr u8 EXT_OP_PREFIX = 0x5B;
constexpr u8 DUAL_NAME_PREFIX = 0x2E;
constexpr u8 MULTI_NAME_PREFIX = 0x2F;
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#aml-byte-stream-byte-values
enum class Opcode : u16 {
ZeroOp = 0x0000,
OneOp = 0x0001,
OnesOp = 0x00FF,
BytePrefix = 0x000A,
WordPrefix = 0x000B,
DWordPrefix = 0x000C,
QWordPrefix = 0x000E,
StringPrefix = 0x000D,
RevisionOp = 0x5B30,
DebugOp = 0x5B31,
AliasOp = 0x0006,
NameOp = 0x0008,
ScopeOp = 0x0010,
BufferOp = 0x0011,
PackageOp = 0x0012,
VarPackageOp = 0x0013,
MethodOp = 0x0014,
ExternalOp = 0x0015,
MutexOp = 0x5B01,
EventOp = 0x5B02,
TimerOp = 0x5B33,
OpRegionOp = 0x5B80,
FieldOp = 0x5B81,
DeviceOp = 0x5B82,
// NOTE: This opcode is missing in the latest spec but is still being used.
ProcessorOp = 0x5B83,
PowerResOp = 0x5B84,
ThermalZoneOp = 0x5B85,
IndexFieldOp = 0x5B86,
BankFieldOp = 0x5B87,
DataRegionOp = 0x5B88,
CondRefOfOp = 0x5B12,
CreateFieldOp = 0x5B13,
LoadTableOp = 0x5B1F,
LoadOp = 0x5B20,
StallOp = 0x5B21,
SleepOp = 0x5B22,
AcquireOp = 0x5B23,
SignalOp = 0x5B24,
WaitOp = 0x5B25,
ResetOp = 0x5B26,
ReleaseOp = 0x5B27,
FromBCDOp = 0x5B28,
ToBCDOp = 0x5B29,
FatalOp = 0x5B32,
StoreOp = 0x70,
RefOfOp = 0x71,
AddOp = 0x72,
ConcatOp = 0x73,
SubtractOp = 0x74,
IncrementOp = 0x75,
DecrementOp = 0x76,
MultiplyOp = 0x77,
DivideOp = 0x78,
ShiftLeftOp = 0x79,
ShiftRightOp = 0x7A,
AndOp = 0x7B,
NandOp = 0x7C,
OrOp = 0x7D,
NorOp = 0x7E,
XorOp = 0x7F,
NotOp = 0x80,
FindSetLeftBitOp = 0x81,
FindSetRightBitOp = 0x82,
DerefOfOp = 0x83,
ConcatResOp = 0x84,
ModOp = 0x85,
NotifyOp = 0x86,
SizeOfOp = 0x87,
IndexOp = 0x88,
MatchOp = 0x89,
ObjectTypeOp = 0x8E,
LAndOp = 0x90,
LOrOp = 0x91,
LNotOp = 0x92,
LEqualOp = 0x93,
LGreaterOp = 0x94,
LLessOp = 0x95,
ToBufferOp = 0x96,
ToDecimalStringOp = 0x97,
ToHexStringOp = 0x98,
ToIntegerOp = 0x99,
ToStringOp = 0x9C,
CreateBitFieldOp = 0x8D,
CreateByteFieldOp = 0x8C,
CreateWordFieldOp = 0x8B,
CreateDWordFieldOp = 0x8A,
CreateQWordFieldOp = 0x8F,
CopyObjectOp = 0x9D,
MidOp = 0x9E,
ContinueOp = 0x9F,
IfOp = 0xA0,
ElseOp = 0xA1,
WhileOp = 0xA2,
NoopOp = 0xA3,
ReturnOp = 0xA4,
BreakOp = 0xA5,
BreakPointOp = 0xCC,
Local0Op = 0x60,
Local1Op = 0x61,
Local2Op = 0x62,
Local3Op = 0x63,
Local4Op = 0x64,
Local5Op = 0x65,
Local6Op = 0x66,
Local7Op = 0x67,
Arg0Op = 0x68,
Arg1Op = 0x69,
Arg2Op = 0x6A,
Arg3Op = 0x6B,
Arg4Op = 0x6C,
Arg5Op = 0x6D,
Arg6Op = 0x6E,
};
}
}
template<>
class AK::Traits<::ACPI::Structures::SDTHeader> : public DefaultTraits<::ACPI::Structures::SDTHeader> {
public:
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
struct AK::Formatter<::ACPI::AML::Opcode> : AK::Formatter<FormatString> {
ErrorOr<void> format(FormatBuilder& builder, ::ACPI::AML::Opcode opcode)
{
return AK::Formatter<FormatString>::format(builder, "{:04X}"sv, static_cast<u16>(opcode));
}
};
+441
View File
@@ -0,0 +1,441 @@
/*
* Copyright (c) 2025, RatcheT2497 <[email protected]>
* Copyright (c) 2026, Undefine <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <AK/Debug.h>
#include <LibACPI/Definitions.h>
#include <LibACPI/Interpreter.h>
#include <LibACPI/Node.h>
#include <LibACPI/Object.h>
#include <LibACPI/OpcodeFilters.h>
#include <LibACPI/Table.h>
#ifdef KERNEL
# define warnln dbgln
# define outln dbgln
# define DEBUG_LOG(...)
#else
# define DEBUG_LOG(...) warnln_if(AML_DEBUG, __VA_ARGS__)
#endif
#define INTERPRETER_ERROR(simple, error, message, ...) \
do { \
warnln("ACPI: Interpreter::{}: " message, __func__ __VA_OPT__(, ) __VA_ARGS__); \
return Error::from_string_view_or_print_error_and_return_errno(simple##sv, error); \
} while (0);
namespace ACPI::AML {
ErrorOr<void> ScopeFrame::insert_node(NameString const& path, NonnullRefPtr<Node> node)
{
auto target = path.type() == NameString::Type::Relative ? scope() : root();
DEBUG_LOG("ACPI: Inserting node with name {} at path {}", node->name().to_string_view(), path.to_string());
if (path.segment_count() < 1)
return Error::from_string_view_or_print_error_and_return_errno("Path must have at least one segment!"sv, EINVAL);
for (size_t i = 0; i < path.segment_count() - 1; i++) {
auto name = TRY(path.segment(i));
target = TRY(target->find_child(name));
}
auto basename = TRY(path.basename());
TRY(target->insert_child(basename, node));
return {};
}
ErrorOr<NonnullOwnPtr<Interpreter>> Interpreter::create()
{
auto root = TRY(Interpreter::create_root_namespace());
return TRY(adopt_nonnull_own_or_enomem(new (nothrow) Interpreter(root)));
}
ErrorOr<NonnullRefPtr<Node>> Interpreter::find_node(NameString const& path, NonnullRefPtr<Node> scope) const
{
RefPtr<Node> current_scope = scope;
bool search_rules_apply = path.type() == NameString::Type::Relative && path.segment_count() == 1 && path.parent_index() == 0;
if (path.type() == NameString::Type::Relative && path.parent_index() > 0) {
for (size_t i = 0; i < path.parent_index(); i++) {
if (current_scope == m_namespace_root)
return Error::from_string_view_or_print_error_and_return_errno("Can't go higher than root!"sv, EINVAL);
if (auto strong = current_scope->parent().strong_ref())
current_scope = strong;
else
return Error::from_string_view_or_print_error_and_return_errno("Reached a dangling node!"sv, EINVAL);
}
} else if (path.type() == NameString::Type::Absolute) {
current_scope = m_namespace_root;
}
// https://uefi.org/specs/ACPI/6.6/05_ACPI_Software_Programming_Model.html#acpi-namespace
if (search_rules_apply) {
// Path is a single name segment, is relative and has no depth.
// Crawl upwards from the current scope and try finding a node with the given name.
auto node_name = TRY(path.segment(0));
while (!current_scope.is_null()) {
auto found = current_scope->find_child(node_name);
if (!found.is_error())
return found.release_value();
if (current_scope == m_namespace_root) {
warnln("ACPI: Could not find node with name {}!", node_name.to_string_view());
return Error::from_string_view_or_print_error_and_return_errno("Node not found!"sv, EINVAL);
}
if (auto strong = current_scope->parent().strong_ref())
current_scope = strong;
else
return Error::from_string_view_or_print_error_and_return_errno("Reached a dangling node!"sv, EINVAL);
}
VERIFY_NOT_REACHED();
}
// Search crawls down from root via name segments.
for (size_t i = 0; i < path.segment_count(); i++) {
auto node_name = TRY(path.segment(i));
auto found = current_scope->find_child(node_name);
if (found.is_error()) {
// FIXME: Warn without allocating any string.
warnln("ACPI: Could not find node at path {}!", path.to_string());
return found.release_error();
}
current_scope = found.release_value();
}
return current_scope.release_nonnull();
}
ErrorOr<void> Interpreter::walk_tree(NonnullRefPtr<Node> tree_root, Function<ErrorOr<void>(NonnullRefPtr<Node>, u32)> callback) const
{
struct Entry {
NonnullRefPtr<Node> node;
size_t depth;
};
Vector<Entry> stack;
TRY(stack.try_append({ move(tree_root), 0 }));
while (!stack.is_empty()) {
auto entry = stack.take_last();
TRY(callback(entry.node, entry.depth));
auto const& children = entry.node->children();
for (size_t i = children.size(); i-- > 0;)
TRY(stack.try_append({ children[i], entry.depth + 1 }));
}
return {};
}
ErrorOr<void> Interpreter::print_namespace() const
{
TRY(walk_tree(m_namespace_root, [&](NonnullRefPtr<Node> node, u32 depth) -> ErrorOr<void> {
StringBuilder sb;
for (u32 i = 0; i < depth; i++)
TRY(sb.try_appendff("| "));
auto name = node->name().to_string_view();
TRY(sb.try_appendff("{}: ", name));
TRY(node->write_description(sb));
outln("{}", sb.string_view());
return {};
}));
return {};
}
void Interpreter::debug_print_header(TableStream& stream, size_t operand_index)
{
#ifndef KERNEL
if constexpr (AML_DEBUG) {
warn("ACPI: {:08X} ", stream.offset());
for (size_t i = 0; i < (m_opcode_frames.size() - 1) - (m_state == State::FinalizingOpcode ? 1 : 0); i++) {
warn("| ");
}
switch (m_state) {
case State::ReadingOpcode:
warn("ini");
break;
case State::ReadingOperand:
warn("opr {}", operand_index);
break;
case State::FinalizingOpcode:
warn("fin");
break;
}
warn(" ");
}
#else
(void)stream;
(void)operand_index;
#endif
}
ErrorOr<Object> Interpreter::finalize_value_opcode(OpcodeFrame& frame)
{
switch (frame.opcode()) {
case Opcode::ZeroOp:
return Object(u64(0));
case Opcode::OneOp:
return Object(u64(1));
case Opcode::BytePrefix:
case Opcode::WordPrefix:
case Opcode::DWordPrefix:
case Opcode::QWordPrefix:
VERIFY(frame.operand(0).is<u64>());
return frame.operand(0);
default:
INTERPRETER_ERROR("Unimplemented!", EINVAL, "Unimplemented value opcode {}!", frame.opcode());
}
VERIFY_NOT_REACHED();
}
ErrorOr<void> Interpreter::finalize_term_opcode(OpcodeFrame& frame)
{
switch (frame.opcode()) {
case Opcode::ScopeOp: {
auto length = TRY(frame.operand(0).as<u64>());
auto path = TRY(frame.operand(1).as<NameString>());
auto scope = TRY(find_node(path, m_scope_frames.last().scope()));
auto scope_frame = ScopeFrame(m_namespace_root, scope, frame.start(), frame.start() + length + 1, m_loaded_tables.size() - 1);
TRY(m_scope_frames.try_append(scope_frame));
return {};
}
case Opcode::NameOp: {
auto path = TRY(frame.operand(0).as<NameString>());
auto data = frame.operand(1);
auto node = TRY(try_make_ref_counted<NameNode>(data));
TRY(m_scope_frames.last().insert_node(path, node));
return {};
}
default:
INTERPRETER_ERROR("Unimplemented!", EINVAL, "Unimplemented term opcode {}!", frame.opcode());
}
VERIFY_NOT_REACHED();
}
ErrorOr<IterationDecision> Interpreter::read_operand(TableStream& stream, OperandType operand)
{
auto& top_opcode_frame = m_opcode_frames.last();
switch (operand) {
case OperandType::NameString: {
auto value = TRY(stream.read_name_string());
DEBUG_LOG("Reading namestring... ({})", value.to_string());
TRY(top_opcode_frame.finish_operand(value));
return IterationDecision::Continue;
}
case OperandType::PackageLength: {
auto length = TRY(stream.read_package_length());
DEBUG_LOG("Reading package length... ({:#X})", length);
TRY(top_opcode_frame.finish_operand(static_cast<u64>(length)));
return IterationDecision::Continue;
}
case OperandType::ByteData: {
auto value = TRY(stream.read_value<u8>());
DEBUG_LOG("Reading byte... ({})", value);
TRY(top_opcode_frame.finish_operand(static_cast<u64>(value)));
return IterationDecision::Continue;
}
case OperandType::WordData: {
auto value = TRY(stream.read_value<u16>());
DEBUG_LOG("Reading word... ({})", value);
TRY(top_opcode_frame.finish_operand(static_cast<u64>(value)));
return IterationDecision::Continue;
}
case OperandType::DWordData: {
auto value = TRY(stream.read_value<u32>());
DEBUG_LOG("Reading dword... ({})", value);
TRY(top_opcode_frame.finish_operand(static_cast<u64>(value)));
return IterationDecision::Continue;
}
case OperandType::QWordData: {
auto value = TRY(stream.read_value<u64>());
DEBUG_LOG("Reading qword... ({})", value);
TRY(top_opcode_frame.finish_operand(value));
return IterationDecision::Continue;
}
case OperandType::TermArg:
case OperandType::DataRefObject:
DEBUG_LOG("Reading complex object {}...", to_underlying(operand));
m_state = State::ReadingOpcode;
return IterationDecision::Continue;
default:
DEBUG_LOG("Failure!");
INTERPRETER_ERROR("Unimplemented operand type!", EINVAL, "Operand type {} unimplemented!", to_underlying(operand));
}
VERIFY_NOT_REACHED();
}
ErrorOr<IterationDecision> Interpreter::cycle(TableStream& stream)
{
// Stacks have already been initialized.
auto& top_opcode_frame = m_opcode_frames.last();
switch (m_state) {
case Interpreter::State::ReadingOpcode: {
// Pop all scope frames that we've exited.
// Do it here in case any pending opcodes need to be finalized at EOF.
while (m_scope_frames.size() != 0) {
auto& scope_frame = m_scope_frames.last();
if (stream.offset() < scope_frame.end()) {
break;
}
m_scope_frames.take_last();
}
if (stream.is_eof())
return IterationDecision::Break;
debug_print_header(stream);
auto start = stream.offset();
if (is_namestring_start(TRY(stream.peek_byte()))) {
auto name = TRY(stream.read_name_string());
DEBUG_LOG("{}", name.to_string());
warnln("ACPI: Reading name string as value is not yet supported");
return Error::from_errno(ENOTSUP);
}
auto opcode = TRY(stream.read_opcode());
if (!top_opcode_frame.filter_opcode_for_next_operand(opcode)) {
DEBUG_LOG("Failure!");
INTERPRETER_ERROR("Filtered opcode!", EINVAL, "Argument opcode {} filtered by opcode {}!", opcode, top_opcode_frame.opcode());
}
DEBUG_LOG("Opcode: {}", opcode);
auto opcode_frame = TRY(OpcodeFrame::from_opcode(static_cast<Opcode>(opcode), start));
TRY(m_opcode_frames.try_append(move(opcode_frame)));
m_state = State::ReadingOperand;
return IterationDecision::Continue;
}
case Interpreter::State::ReadingOperand: {
auto operand = TRY(top_opcode_frame.begin_operand());
if (operand == OperandType::End) {
m_state = State::FinalizingOpcode;
return IterationDecision::Continue;
}
debug_print_header(stream, top_opcode_frame.operand_count());
return read_operand(stream, operand);
}
case Interpreter::State::FinalizingOpcode: {
debug_print_header(stream);
auto top_frame_value = m_opcode_frames.take_last();
if (m_opcode_frames.size() > 1) {
DEBUG_LOG("Finalizing argument {}", top_frame_value.opcode());
auto value = TRY(finalize_value_opcode(top_frame_value));
TRY(m_opcode_frames.last().finish_operand(value));
m_state = State::ReadingOperand;
} else {
DEBUG_LOG("Finalizing term {}", top_frame_value.opcode());
TRY(finalize_term_opcode(top_frame_value));
m_state = State::ReadingOpcode;
}
return IterationDecision::Continue;
}
}
VERIFY_NOT_REACHED();
}
ErrorOr<Object> Interpreter::evaluate(NameString const& path)
{
auto node = TRY(find_node(path, m_namespace_root));
return evaluate(node);
}
ErrorOr<Object> Interpreter::evaluate(NonnullRefPtr<Node> node)
{
if (node->type() == NodeType::Name) {
auto name_node = static_ptr_cast<NameNode>(node);
return name_node->data();
}
INTERPRETER_ERROR("Cannot evaluate node type!", EINVAL, "Node type {} not supported for evaluation!", node->kind());
}
ErrorOr<NonnullRefPtr<Node>> Interpreter::create_root_namespace()
{
auto namespace_root = TRY(try_make_ref_counted<ScopeNode>());
// https://uefi.org/specs/ACPI/6.6/05_ACPI_Software_Programming_Model.html#predefined-root-namespaces
TRY(namespace_root->insert_child("_GPE"sv, TRY(try_make_ref_counted<ScopeNode>())));
TRY(namespace_root->insert_child("_PR_"sv, TRY(try_make_ref_counted<ScopeNode>())));
TRY(namespace_root->insert_child("_SB_"sv, TRY(try_make_ref_counted<ScopeNode>())));
TRY(namespace_root->insert_child("_SI_"sv, TRY(try_make_ref_counted<ScopeNode>())));
TRY(namespace_root->insert_child("_TZ_"sv, TRY(try_make_ref_counted<ScopeNode>())));
// https://uefi.org/specs/ACPI/6.6/05_ACPI_Software_Programming_Model.html#predefined-objects
TRY(namespace_root->insert_child("_REV"sv, TRY(try_make_ref_counted<NameNode>(Object(u64(2))))));
// FIXME: ACPI spec also mendates we insert _GL, _OSI, _OS, _DLM
return namespace_root;
}
ErrorOr<void> Interpreter::load(ReadonlyBytes bytes)
{
TRY(m_loaded_tables.try_append(TableStream(bytes)));
auto& stream = m_loaded_tables.last();
if (!validate_table(*reinterpret_cast<Structures::SDTHeader const*>(bytes.data())))
INTERPRETER_ERROR("Incorrect checksum!", EINVAL, "Table checksum incorrect!");
auto block_header = TRY(stream.read_sdt_header());
outln("ACPI: Interpreter::load[{}]: Loading table '{}' by OEM '{}'", m_loaded_tables.size() - 1, StringView(block_header.oem_table_id, 8), StringView(block_header.oem_id, 6));
auto root_scope_frame = ScopeFrame(m_namespace_root, m_namespace_root, stream.offset(), TRY(stream.size()), m_loaded_tables.size() - 1);
TRY(m_scope_frames.try_append(move(root_scope_frame)));
auto root_opcode_frame = TRY(OpcodeFrame::root_frame());
TRY(m_opcode_frames.try_append(move(root_opcode_frame)));
while (m_opcode_frames.size() > 0 && m_scope_frames.size() > 0) {
if (TRY(cycle(stream)) == IterationDecision::Break) {
break;
}
}
if (m_scope_frames.size() != 0 || m_opcode_frames.size() != 1)
return Error::from_string_view_or_print_error_and_return_errno("ACPI: Interpreter::load: Unbalanced scope or opcode frames!"sv, EINVAL);
m_opcode_frames.take_last();
return {};
}
}
+93
View File
@@ -0,0 +1,93 @@
/*
* Copyright (c) 2025, RatcheT2497 <[email protected]>
* Copyright (c) 2026, Undefine <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Debug.h>
#include <AK/Error.h>
#include <AK/Function.h>
#include <AK/Vector.h>
#include <LibACPI/Node.h>
#include <LibACPI/OpcodeFrame.h>
#include <LibACPI/TableStream.h>
namespace ACPI::AML {
class ScopeFrame {
public:
ScopeFrame(NonnullRefPtr<Node> root, NonnullRefPtr<Node> scope, size_t start, size_t end, size_t table_index)
: m_root(move(root))
, m_scope(move(scope))
, m_start(start)
, m_end(end)
, m_table_index(table_index)
{
}
ErrorOr<void> insert_node(NameString const& path, NonnullRefPtr<Node> node);
NonnullRefPtr<Node> root() { return m_root; }
NonnullRefPtr<Node> scope() { return m_scope; }
size_t start() const { return m_start; }
size_t end() const { return m_end; }
size_t size() const { return m_end - m_start; }
private:
NonnullRefPtr<Node> m_root;
NonnullRefPtr<Node> m_scope;
size_t m_start { 0 };
size_t m_end { 0 };
size_t m_table_index { 0 };
};
class Interpreter {
public:
static ErrorOr<NonnullOwnPtr<Interpreter>> create();
ErrorOr<void> load(ReadonlyBytes bytes);
ErrorOr<void> print_namespace() const;
ErrorOr<Object> evaluate(NameString const& path);
private:
enum class State : u8 {
ReadingOpcode,
ReadingOperand,
FinalizingOpcode,
};
Interpreter(NonnullRefPtr<Node> namespace_root)
: m_namespace_root(move(namespace_root))
{
}
static ErrorOr<NonnullRefPtr<Node>> create_root_namespace();
ErrorOr<NonnullRefPtr<Node>> find_node(NameString const& path, NonnullRefPtr<Node> scope) const;
ErrorOr<void> walk_tree(NonnullRefPtr<Node> tree_root, Function<ErrorOr<void>(NonnullRefPtr<Node>, u32)> callback) const;
ErrorOr<Object> evaluate(NonnullRefPtr<Node> node);
ErrorOr<IterationDecision> cycle(TableStream& stream);
ErrorOr<IterationDecision> read_operand(TableStream& stream, OperandType operand);
ErrorOr<Object> finalize_value_opcode(OpcodeFrame& frame);
ErrorOr<void> finalize_term_opcode(OpcodeFrame& frame);
void debug_print_header(TableStream& stream, size_t operand_index = 0);
State m_state { State::ReadingOpcode };
AK::Vector<TableStream> m_loaded_tables;
Vector<ScopeFrame> m_scope_frames;
Vector<OpcodeFrame> m_opcode_frames;
NonnullRefPtr<Node> m_namespace_root;
};
}
+135
View File
@@ -0,0 +1,135 @@
/*
* Copyright (c) 2025, RatcheT2497 <[email protected]>
* Copyright (c) 2026, Undefine <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <AK/Format.h>
#include <AK/StringBuilder.h>
#include <Kernel/Library/KString.h>
#include <LibACPI/Definitions.h>
#include <LibACPI/NameString.h>
#include <LibACPI/OpcodeFilters.h>
#include <LibACPI/TableStream.h>
#ifdef KERNEL
# define warnln dbgln
#endif
namespace ACPI::AML {
NameSegment::NameSegment(Array<char, 4> value)
: m_data(value)
{
}
ErrorOr<NameSegment> NameSegment::from_string_view(StringView view)
{
return NameSegment::from_bytes(view.bytes());
}
ErrorOr<NameSegment> NameSegment::from_bytes(ReadonlyBytes span)
{
if (span.size() != 4)
return Error::from_string_view_or_print_error_and_return_errno("Name segment has invalid length"sv, EINVAL);
if (!is_lead_name_char(span[0]) || !is_name_char(span[1]) || !is_name_char(span[2]) || !is_name_char(span[3])) {
warnln("ACPI: Invalid character found inside name segment: '{}'", span);
return Error::from_string_view_or_print_error_and_return_errno("Invalid character found inside name segment."sv, EINVAL);
}
return NameSegment({ (char)span[0], (char)span[1], (char)span[2], (char)span[3] });
}
StringView NameSegment::to_string_view() const
{
StringView view(m_data.data(), 4);
return view;
}
// https://uefi.org/specs/ACPI/6.6/19_ASL_Reference.html#asl-name-and-pathname-terms
ErrorOr<NameString> NameString::from_string_view(StringView input)
{
Type type = Type::Relative;
size_t depth = 0;
size_t i = 0;
if (input[i] == '\\') {
type = NameString::Type::Absolute;
if (i >= input.length())
return NameString(type, depth);
i++;
} else if (input[i] == '^') {
type = NameString::Type::Relative;
while (i < input.length() && input[i] == '^') {
depth++;
i++;
}
}
if (i >= input.length())
return NameString(type, depth);
// Validate segments, alongside separators.
Vector<NameSegment, 4> segments;
for (; i < input.length(); i += 5) {
if (i + 4 > input.length())
return Error::from_string_view_or_print_error_and_return_errno("Incomplete name segment."sv, EINVAL);
TRY(segments.try_append(TRY(NameSegment::from_string_view(input.substring_view(i, 4)))));
if (i + 4 < input.length()) {
if (input[i + 4] != '.')
return Error::from_string_view_or_print_error_and_return_errno("Invalid name segment continuation."sv, EINVAL);
}
}
return NameString(type, depth, move(segments));
}
ErrorOr<NameString> NameString::clone() const
{
return NameString(m_type, m_parent_index, TRY(m_segments.clone()));
}
ErrorOr<NameSegment> NameString::basename() const
{
if (m_segments.is_empty())
return Error::from_string_view_or_print_error_and_return_errno("NullNameString has no base name!"sv, EINVAL);
return segment(m_segments.size() - 1);
}
#ifdef KERNEL
ErrorOr<NonnullOwnPtr<Kernel::KString>> NameString::to_string() const
#else
ErrorOr<String> NameString::to_string() const
#endif
{
StringBuilder builder;
if (m_type == NameString::Type::Absolute) {
TRY(builder.try_append('\\'));
} else if (m_parent_index > 0) {
TRY(builder.try_append_repeated('^', m_parent_index));
}
for (size_t i = 0; i < m_segments.size(); i++) {
TRY(builder.try_append(TRY(segment(i)).to_string_view()));
if (i != m_segments.size() - 1) {
TRY(builder.try_append('.'));
}
}
#ifdef KERNEL
return Kernel::KString::try_create(builder.string_view());
#else
return builder.to_string();
#endif
}
}
+85
View File
@@ -0,0 +1,85 @@
/*
* Copyright (c) 2025, RatcheT2497 <[email protected]>
* Copyright (c) 2026, Undefine <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/StringView.h>
#include <AK/Vector.h>
#ifdef KERNEL
# include <Kernel/Library/KString.h>
#else
# include <AK/String.h>
#endif
namespace ACPI::AML {
class TableStream;
class NameSegment {
public:
NameSegment() = default;
static ErrorOr<NameSegment> from_string_view(StringView view);
static ErrorOr<NameSegment> from_bytes(ReadonlyBytes span);
friend bool operator==(NameSegment const& l, NameSegment const& r) { return l.m_data == r.m_data; }
friend bool operator!=(NameSegment const& l, NameSegment const& r) { return l.m_data != r.m_data; }
StringView to_string_view() const;
private:
NameSegment(Array<char, 4> value);
Array<char, 4> m_data { '_', '_', '_', '_' };
};
class NameString {
public:
enum class Type : u8 {
Absolute,
Relative,
};
static ErrorOr<NameString> from_string_view(StringView input);
NameString(Type type, size_t depth)
: m_type(type)
, m_parent_index(depth)
{
}
NameString(Type type, size_t depth, Vector<NameSegment, 4> segments)
: m_type(type)
, m_parent_index(depth)
, m_segments(move(segments))
{
}
ErrorOr<NameString> clone() const;
ErrorOr<NameSegment> basename() const;
#ifdef KERNEL
ErrorOr<NonnullOwnPtr<Kernel::KString>> to_string() const;
#else
ErrorOr<String> to_string() const;
#endif
ErrorOr<NameSegment> segment(size_t index) const { return m_segments[index]; }
size_t segment_count() const { return m_segments.size(); }
Type type() const { return m_type; }
size_t parent_index() const { return m_parent_index; }
private:
Type m_type { Type::Relative };
size_t m_parent_index { 0 };
Vector<NameSegment, 4> m_segments;
};
}
+77
View File
@@ -0,0 +1,77 @@
/*
* Copyright (c) 2025, RatcheT2497 <[email protected]>
* Copyright (c) 2026, Undefine <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <AK/Format.h>
#include <AK/RefPtr.h>
#include <LibACPI/NameString.h>
#include <LibACPI/Node.h>
#ifdef KERNEL
# define warnln dbgln
#endif
namespace ACPI::AML {
ErrorOr<NonnullRefPtr<Node>> Node::find_child(NameSegment name) const
{
for (auto const& child : m_children) {
if (child->name() == name)
return child;
}
warnln("ACPI: Node::find_child: Child {} does not exist relative to node {}!", name.to_string_view(), m_name.to_string_view());
return Error::from_string_view_or_print_error_and_return_errno("Child does not exist."sv, EINVAL);
}
ErrorOr<NonnullRefPtr<Node>> Node::find_child(StringView name) const
{
if (name.length() != 4)
return Error::from_string_view_or_print_error_and_return_errno("Name must have a length of 4."sv, EINVAL);
return find_child(TRY(NameSegment::from_string_view(name)));
}
ErrorOr<void> Node::insert_child(NameSegment name, NonnullRefPtr<Node> node)
{
/// FIXME: Duplicate name detection.
node->m_name = name;
node->m_parent = this;
TRY(m_children.try_append(node));
return {};
}
ErrorOr<void> Node::insert_child(StringView name, NonnullRefPtr<Node> node)
{
if (name.length() != 4)
return Error::from_string_view_or_print_error_and_return_errno("Name must have a length of 4."sv, EINVAL);
return insert_child(TRY(NameSegment::from_string_view(name)), node);
}
ErrorOr<void> Node::write_description(StringBuilder& builder)
{
TRY(builder.try_append(kind()));
return {};
}
ErrorOr<void> NameNode::write_description(StringBuilder& builder)
{
if (m_data.is<u64>()) {
auto value = MUST(m_data.as<u64>());
TRY(builder.try_appendff("(Decimal: {}, Hexadecimal: {:X})", value, value));
} else if (m_data.is<NameString>()) {
auto value = MUST(m_data.as<NameString>());
TRY(builder.try_appendff("{}"sv, TRY(value.to_string())));
} else {
TRY(builder.try_append("Invalid"sv));
}
return {};
}
}
+91
View File
@@ -0,0 +1,91 @@
/*
* Copyright (c) 2025, RatcheT2497 <[email protected]>
* Copyright (c) 2026, Undefine <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Array.h>
#include <AK/AtomicRefCounted.h>
#include <AK/Function.h>
#include <AK/RefPtr.h>
#include <AK/StringBuilder.h>
#include <AK/Variant.h>
#include <AK/WeakPtr.h>
#include <LibACPI/Definitions.h>
#include <LibACPI/NameString.h>
#include <LibACPI/Object.h>
namespace ACPI::AML {
enum class NodeType : u8 {
Untyped,
Scope,
Name,
};
class Node : public AtomicRefCounted<Node>
, public Weakable<Node> {
public:
ErrorOr<NonnullRefPtr<Node>> find_child(NameSegment name) const;
ErrorOr<NonnullRefPtr<Node>> find_child(StringView name) const;
ErrorOr<void> insert_child(NameSegment name, NonnullRefPtr<Node> node);
ErrorOr<void> insert_child(StringView name, NonnullRefPtr<Node> node);
NameSegment const& name() const { return m_name; }
WeakPtr<Node> parent() const { return m_parent; }
Vector<NonnullRefPtr<Node>> const& children() const { return m_children; }
virtual ErrorOr<void> write_description(StringBuilder& builder);
virtual NodeType type() const { return NodeType::Untyped; }
virtual StringView kind() const { return "Node"sv; }
virtual ~Node() = default;
protected:
Node(RefPtr<Node> parent)
: m_parent(move(parent))
{
}
NameSegment m_name;
WeakPtr<Node> m_parent;
Vector<NonnullRefPtr<Node>> m_children;
};
class ScopeNode : public Node {
public:
ScopeNode()
: Node(nullptr)
{
}
virtual NodeType type() const override { return NodeType::Scope; }
virtual StringView kind() const override { return "Scope"sv; }
};
class NameNode : public Node {
public:
NameNode(Object value)
: Node(nullptr)
, m_data(move(value))
{
}
virtual ErrorOr<void> write_description(StringBuilder& builder) override;
virtual NodeType type() const override { return NodeType::Name; }
virtual StringView kind() const override { return "Name"sv; }
Object& data() { return m_data; }
protected:
Object m_data;
};
}
+56
View File
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2025, RatcheT2497 <[email protected]>
* Copyright (c) 2026, Undefine <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/StdLibExtras.h>
#include <AK/Types.h>
#include <AK/Variant.h>
#include <AK/Vector.h>
#include <LibACPI/NameString.h>
namespace ACPI::AML {
template<typename T>
concept IsNodeData = OneOf<T, u64, NameString>;
class Object {
public:
constexpr Object()
: m_data(AK::Empty {})
{
}
template<IsNodeData T>
constexpr Object(T const& value)
: m_data(value)
{
}
template<IsNodeData T>
constexpr bool is() const
{
return m_data.has<T>();
}
template<IsNodeData T>
constexpr ErrorOr<T> as() const
{
if (m_data.has<T>())
return m_data.get<T>();
// TODO: Implement casting according both to ACPI spec and whatever Windows does.
// https://uefi.org/specs/ACPI/6.6/19_ASL_Reference.html#implicit-source-operand-conversion
return Error::from_string_view_or_print_error_and_return_errno("Can not cast given type!"sv, EINVAL);
}
protected:
Variant<AK::Empty, u64, NameString> m_data;
};
}
@@ -0,0 +1,259 @@
/*
* Copyright (c) 2025, RatcheT2497 <[email protected]>
* Copyright (c) 2026, Undefine <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibACPI/OpcodeFilters.h>
namespace ACPI::AML {
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#namespacemodifierobj
bool namespace_modifier_object_filter(Opcode opcode)
{
// NameSpaceModifierObj := DefAlias | DefName | DefScope
switch (opcode) {
case Opcode::AliasOp:
case Opcode::NameOp:
case Opcode::ScopeOp:
return true;
default:
return false;
}
}
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#namedobj
bool named_object_filter(Opcode opcode)
{
// NamedObj := DefBankField | DefCreateBitField | DefCreateByteField | DefCreateDWordField | DefCreateField | DefCreateQWordField | DefCreateWordField | DefDataRegion | DefExternal | DefOpRegion | DefPowerRes | DefThermalZone
// FieldOp missing from 6.6 docs, but given the presence of the DefXYZField
// opcodes here, I'm adding it in here.
// DeviceOp is also missing. Adding it here too.
// MutexOp likewise missing. It's named and creates an object, so here it be.
// ProcessorOp found here in 6.3 docs.
switch (opcode) {
case Opcode::BankFieldOp:
case Opcode::CreateBitFieldOp:
case Opcode::CreateByteFieldOp:
case Opcode::CreateDWordFieldOp:
case Opcode::CreateFieldOp:
case Opcode::CreateQWordFieldOp:
case Opcode::CreateWordFieldOp:
case Opcode::DataRegionOp:
case Opcode::ExternalOp:
case Opcode::OpRegionOp:
case Opcode::PowerResOp:
case Opcode::ThermalZoneOp:
return true;
default:
return false;
}
}
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#object
bool object_filter(Opcode opcode)
{
// Object := NameSpaceModifierObj | NamedObj
return namespace_modifier_object_filter(opcode) || named_object_filter(opcode);
}
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#statementopcode
bool statement_filter(Opcode opcode)
{
// StatementOpcode := DefBreak | DefBreakPoint | DefContinue | DefFatal | DefIfElse | DefNoop | DefNotify | DefRelease | DefReset | DefReturn | DefSignal | DefSleep | DefStall | DefWhile
switch (opcode) {
case Opcode::BreakOp:
case Opcode::BreakPointOp:
case Opcode::ContinueOp:
case Opcode::FatalOp:
case Opcode::IfOp:
case Opcode::ElseOp:
case Opcode::NoopOp:
case Opcode::NotifyOp:
case Opcode::ReleaseOp:
case Opcode::ResetOp:
case Opcode::ReturnOp:
case Opcode::SignalOp:
case Opcode::SleepOp:
case Opcode::StallOp:
case Opcode::WhileOp:
return true;
default:
return false;
}
}
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#expressionopcode
bool expression_filter(Opcode opcode)
{
// ExpressionOpcode := DefAcquire | DefAdd | DefAnd | DefBuffer | DefConcat | DefConcatRes | DefCondRefOf | DefCopyObject | DefDecrement | DefDerefOf | DefDivide | DefFindSetLeftBit | DefFindSetRightBit | DefFromBCD | DefIncrement | DefIndex | DefLAnd | DefLEqual | DefLGreater | DefLGreaterEqual | DefLLess | DefLLessEqual | DefMid | DefLNot | DefLNotEqual | DefLoadTable | DefLOr | DefMatch | DefMod | DefMultiply | DefNAnd | DefNOr | DefNot | DefObjectType | DefOr | DefPackage | DefVarPackage | DefRefOf | DefShiftLeft | DefShiftRight | DefSizeOf | DefStore | DefSubtract | DefTimer | DefToBCD | DefToBuffer | DefToDecimalString | DefToHexString | DefToInteger | DefToString | DefWait | DefXOr | MethodInvocation
// FIXME: Add support for MethodInvocation.
switch (opcode) {
case Opcode::AcquireOp:
case Opcode::AddOp:
case Opcode::AndOp:
case Opcode::BufferOp:
case Opcode::ConcatOp:
case Opcode::ConcatResOp:
case Opcode::CondRefOfOp:
case Opcode::CopyObjectOp:
case Opcode::DecrementOp:
case Opcode::DerefOfOp:
case Opcode::DivideOp:
case Opcode::FindSetLeftBitOp:
case Opcode::FindSetRightBitOp:
case Opcode::FromBCDOp:
case Opcode::IncrementOp:
case Opcode::IndexOp:
case Opcode::LAndOp:
case Opcode::LEqualOp:
case Opcode::LGreaterOp:
case Opcode::LLessOp:
case Opcode::LNotOp:
case Opcode::LoadTableOp:
case Opcode::LOrOp:
case Opcode::MatchOp:
case Opcode::MidOp:
case Opcode::ModOp:
case Opcode::MultiplyOp:
case Opcode::NandOp:
case Opcode::NorOp:
case Opcode::NotOp:
case Opcode::ObjectTypeOp:
case Opcode::OrOp:
case Opcode::PackageOp:
case Opcode::VarPackageOp:
case Opcode::RefOfOp:
case Opcode::ShiftLeftOp:
case Opcode::ShiftRightOp:
case Opcode::SizeOfOp:
case Opcode::StoreOp:
case Opcode::SubtractOp:
case Opcode::TimerOp:
case Opcode::ToBCDOp:
case Opcode::ToBufferOp:
case Opcode::ToDecimalStringOp:
case Opcode::ToHexStringOp:
case Opcode::ToIntegerOp:
case Opcode::ToStringOp:
case Opcode::WaitOp:
case Opcode::XorOp:
return true;
default:
return false;
}
}
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#constobj
bool const_object_filter(Opcode opcode)
{
// ConstObj := ZeroOp | OneOp | OnesOp
switch (opcode) {
case Opcode::ZeroOp:
case Opcode::OneOp:
case Opcode::OnesOp:
return true;
default:
return false;
}
}
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#computationaldata
bool computational_data_filter(Opcode opcode)
{
// ComputationalData := ByteConst | WordConst | DWordConst | QWordConst | String | ConstObj | RevisionOp | DefBuffer
switch (opcode) {
case Opcode::BytePrefix:
case Opcode::WordPrefix:
case Opcode::DWordPrefix:
case Opcode::QWordPrefix:
case Opcode::StringPrefix:
case Opcode::RevisionOp:
case Opcode::BufferOp:
return true;
default:
return const_object_filter(opcode);
}
}
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#dataobject
bool data_object_filter(Opcode opcode)
{
// DataObject := ComputationalData | DefPackage | DefVarPackage
switch (opcode) {
case Opcode::PackageOp:
case Opcode::VarPackageOp:
return true;
default:
return computational_data_filter(opcode);
}
}
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#datarefobject
bool data_ref_object_filter(Opcode opcode)
{
// DataRefObject := DataObject | ObjectReference
// FIXME: Add support for ObjectReference.
return data_object_filter(opcode);
}
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#termobj
bool term_filter(Opcode opcode)
{
// TermObj := Object | StatementOpcode | ExpressionOpcode
return object_filter(opcode) || statement_filter(opcode) || expression_filter(opcode);
}
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#termarg
bool term_arg_filter(Opcode opcode)
{
// TermArg := ExpressionOpcode | DataObject | ArgObj | LocalObj
return expression_filter(opcode) || data_object_filter(opcode) || arg_obj_filter(opcode) || local_obj_filter(opcode);
}
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#argobj
bool arg_obj_filter(Opcode opcode)
{
// ArgObj := Arg0Op | Arg1Op | Arg2Op | Arg3Op | Arg4Op | Arg5Op | Arg6Op
return opcode >= Opcode::Arg0Op && opcode <= Opcode::Arg6Op;
}
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#localobj
bool local_obj_filter(Opcode opcode)
{
// LocalObj := Local0Op | Local1Op | Local2Op | Local3Op | Local4Op | Local5Op | Local6Op | Local7Op
return opcode >= Opcode::Local0Op && opcode <= Opcode::Local7Op;
}
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#leadnamechar
bool is_lead_name_char(u8 c)
{
// LeadNameChar := ‘A’-‘Z’ | ‘_’
return (c == '_') || (c >= 'A' && c <= 'Z');
}
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#namechar
bool is_name_char(u8 c)
{
// NameChar := DigitChar | LeadNameChar
return is_lead_name_char(c) || (c >= '0' && c <= '9');
}
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#namestring
bool is_namestring_start(u8 c)
{
return is_lead_name_char(c) || (c == '^') || (c == '\\') || (c == DUAL_NAME_PREFIX) || (c == MULTI_NAME_PREFIX);
}
}
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2025, RatcheT2497 <[email protected]>
* Copyright (c) 2026, Undefine <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibACPI/Definitions.h>
namespace ACPI::AML {
bool namespace_modifier_object_filter(Opcode opcode);
bool named_object_filter(Opcode opcode);
bool object_filter(Opcode opcode);
bool statement_filter(Opcode opcode);
bool expression_filter(Opcode opcode);
bool const_object_filter(Opcode opcode);
bool computational_data_filter(Opcode opcode);
bool data_object_filter(Opcode opcode);
bool data_ref_object_filter(Opcode opcode);
bool term_filter(Opcode opcode);
bool term_arg_filter(Opcode opcode);
bool arg_obj_filter(Opcode opcode);
bool local_obj_filter(Opcode opcode);
bool is_namestring_start(u8 c);
bool is_lead_name_char(u8 c);
bool is_name_char(u8 c);
}
+111
View File
@@ -0,0 +1,111 @@
/*
* Copyright (c) 2025, RatcheT2497 <[email protected]>
* Copyright (c) 2026, Undefine <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibACPI/Definitions.h>
#include <LibACPI/OpcodeFilters.h>
#include <LibACPI/OpcodeFrame.h>
#ifdef KERNEL
# define warnln dbgln
#endif
namespace ACPI::AML {
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#aml-byte-stream-byte-values
constexpr static Array<OperandType, 1> EMPTY_OPERANDS = { OperandType::End };
constexpr static Array<OperandType, 3> SCOPE_OPERANDS = { OperandType::PackageLength, OperandType::NameString, OperandType::End };
constexpr static Array<OperandType, 3> NAME_OPERANDS = { OperandType::NameString, OperandType::DataRefObject, OperandType::End };
constexpr static Array<OperandType, 2> BYTE_CONST_OPERANDS = { OperandType::ByteData, OperandType::End };
constexpr static Array<OperandType, 2> WORD_CONST_OPERANDS = { OperandType::WordData, OperandType::End };
constexpr static Array<OperandType, 2> DWORD_CONST_OPERANDS = { OperandType::DWordData, OperandType::End };
constexpr static Array<OperandType, 2> QWORD_CONST_OPERANDS = { OperandType::QWordData, OperandType::End };
static ErrorOr<ReadonlySpan<OperandType>> get_opcode_operand_template(Opcode opcode)
{
switch (opcode) {
case Opcode::BytePrefix:
return BYTE_CONST_OPERANDS.span();
case Opcode::WordPrefix:
return WORD_CONST_OPERANDS.span();
case Opcode::DWordPrefix:
return DWORD_CONST_OPERANDS.span();
case Opcode::QWordPrefix:
return QWORD_CONST_OPERANDS.span();
case Opcode::ScopeOp:
return SCOPE_OPERANDS.span();
case Opcode::NameOp:
return NAME_OPERANDS.span();
case Opcode::ZeroOp:
case Opcode::OneOp:
return EMPTY_OPERANDS.span();
default:
warnln("ACPI: get_opcode_operand_template: Unknown opcode template: {}!", opcode);
return Error::from_string_view_or_print_error_and_return_errno("Unknown opcode template!"sv, EINVAL);
}
}
ErrorOr<OpcodeFrame> OpcodeFrame::from_opcode(Opcode opcode, size_t start)
{
auto operand_template = TRY(get_opcode_operand_template(opcode));
return OpcodeFrame(opcode, TRY(Vector<OperandType>::from_span(operand_template)), start);
}
ErrorOr<OpcodeFrame> OpcodeFrame::root_frame()
{
return OpcodeFrame({}, TRY(Vector<OperandType>::from_span(EMPTY_OPERANDS)), 0);
}
OperandType OpcodeFrame::peek_next_operand()
{
return m_operand_template[m_operand_template_index];
}
bool OpcodeFrame::filter_opcode_for_next_operand(Opcode opcode)
{
auto pending_operand = peek_next_operand();
switch (pending_operand) {
case OperandType::NameString:
case OperandType::PackageLength:
case OperandType::ByteData:
case OperandType::WordData:
case OperandType::DWordData:
case OperandType::QWordData:
return false;
case OperandType::TermArg:
return term_arg_filter(opcode);
case OperandType::DataRefObject:
return data_ref_object_filter(opcode);
case OperandType::End:
return term_filter(opcode);
}
VERIFY_NOT_REACHED();
}
ErrorOr<OperandType> OpcodeFrame::begin_operand()
{
return peek_next_operand();
}
ErrorOr<void> OpcodeFrame::finish_operand(Object const& operand)
{
TRY(m_operands.try_append(operand));
m_operand_template_index++;
return {};
}
}
+67
View File
@@ -0,0 +1,67 @@
/*
* Copyright (c) 2025, RatcheT2497 <[email protected]>
* Copyright (c) 2026, Undefine <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Vector.h>
#include <LibACPI/Definitions.h>
#include <LibACPI/Object.h>
namespace ACPI::AML {
enum class OperandType : u8 {
NameString,
PackageLength,
ByteData,
WordData,
DWordData,
QWordData,
TermArg,
DataRefObject,
End,
};
class OpcodeFrame {
public:
static ErrorOr<OpcodeFrame> from_opcode(Opcode opcode, size_t start);
static ErrorOr<OpcodeFrame> root_frame();
bool filter_opcode_for_next_operand(Opcode opcode);
ErrorOr<OperandType> begin_operand();
ErrorOr<void> finish_operand(Object const& operand);
Object const& operand(size_t index) const { return m_operands[index]; }
size_t operand_count() const { return m_operands.size(); }
// NOTE: This should never get called on the root frame
Opcode opcode() const { return m_opcode.value(); }
size_t start() const { return m_start; }
private:
OpcodeFrame(Optional<Opcode> opcode, Vector<OperandType> operand_template, size_t start)
: m_opcode(opcode)
, m_operand_template(move(operand_template))
, m_start(start)
{
}
OperandType peek_next_operand();
Optional<Opcode> m_opcode;
Vector<Object> m_operands;
Vector<OperandType> m_operand_template;
size_t m_start;
size_t m_operand_template_index { 0 };
};
}
+127
View File
@@ -0,0 +1,127 @@
/*
* Copyright (c) 2025, RatcheT2497 <[email protected]>
* Copyright (c) 2026, Undefine <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibACPI/OpcodeFilters.h>
#include <LibACPI/TableStream.h>
namespace ACPI::AML {
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#defblockheader
ErrorOr<Structures::SDTHeader> TableStream::read_sdt_header()
{
return *TRY(read_in_place<Structures::SDTHeader const>());
}
ErrorOr<Opcode> TableStream::read_opcode()
{
u16 opcode = TRY(read_value<u8>());
if (opcode == EXT_OP_PREFIX)
opcode = (opcode << 8) | TRY(read_value<u8>());
return static_cast<Opcode>(opcode);
}
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#package-length-encoding
ErrorOr<u32> TableStream::read_package_length()
{
u8 initial = TRY(read_value<u8>());
if ((initial & 0b1100'0000) == 0) {
return initial;
}
u8 additional_byte_count = initial >> 6;
u32 value = (initial & 0b1111);
int bits = 4;
for (auto i = 0; i < additional_byte_count; i++) {
value |= TRY(read_value<u8>()) << bits;
bits += 8;
}
return value;
}
// https://uefi.org/specs/ACPI/6.6/20_AML_Specification.html#namestring
ErrorOr<NameString> TableStream::read_name_string()
{
NameString::Type type = NameString::Type::Relative;
size_t depth = 0;
size_t count = 0;
u8 initial = 0;
// Figure out path type.
initial = TRY(read_value<u8>());
if (initial == '\\') {
type = NameString::Type::Absolute;
} else if (initial == '^') {
while (initial == '^') {
depth++;
if (is_eof())
return Error::from_string_view_or_print_error_and_return_errno("Unexpected EOF in NameString."sv, EINVAL);
initial = TRY(read_value<u8>());
}
TRY(seek(offset() - 1));
} else {
TRY(seek(offset() - 1));
}
// Figure out how many segments are in the path.
// NamePath := NameSeg | DualNamePath | MultiNamePath | NullName
initial = TRY(read_value<u8>());
switch (initial) {
case 0:
return NameString(type, depth);
case MULTI_NAME_PREFIX:
count = TRY(read_value<u8>());
if (count == 0) {
return Error::from_string_view_or_print_error_and_return_errno("Multiname path must have at least 1 item."sv, EINVAL);
}
break;
case DUAL_NAME_PREFIX:
count = 2;
break;
default:
if (is_lead_name_char(initial)) {
TRY(seek(offset() - 1));
count = 1;
break;
}
warnln("ACPI: Invalid character: '{:X}' at offset {:X}", (char)initial, offset());
return Error::from_string_view_or_print_error_and_return_errno("Invalid character found."sv, EINVAL);
}
Vector<NameSegment, 4> segments;
for (size_t i = 0; i < count; i++) {
auto item = TRY(read_in_place<u8 const>(4));
TRY(segments.try_append(TRY(NameSegment::from_bytes(item))));
}
return NameString(type, depth, move(segments));
}
ErrorOr<u8> TableStream::peek_byte()
{
u8 value = TRY(read_value<u8>());
TRY(seek(offset() - 1));
return value;
}
ErrorOr<Opcode> TableStream::peek_opcode()
{
auto previous = offset();
Opcode value = TRY(read_opcode());
TRY(seek(previous));
return value;
}
}
+33
View File
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2025, RatcheT2497 <[email protected]>
* Copyright (c) 2026, Undefine <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/MemoryStream.h>
#include <LibACPI/Definitions.h>
#include <LibACPI/NameString.h>
namespace ACPI::AML {
class TableStream : public FixedMemoryStream {
public:
explicit TableStream(ReadonlyBytes bytes)
: FixedMemoryStream(bytes)
{
}
ErrorOr<Structures::SDTHeader> read_sdt_header();
ErrorOr<Opcode> read_opcode();
ErrorOr<u32> read_package_length();
ErrorOr<NameString> read_name_string();
ErrorOr<u8> peek_byte();
ErrorOr<Opcode> peek_opcode();
};
}