aboutsummaryrefslogtreecommitdiff
path: root/libs/acpi
diff options
context:
space:
mode:
Diffstat (limited to 'libs/acpi')
-rw-r--r--libs/acpi/CMakeLists.txt118
-rw-r--r--libs/acpi/acpi/acpi.hpp12
-rw-r--r--libs/acpi/acpi/common/basic_table.hpp40
-rw-r--r--libs/acpi/acpi/common/checksum.cpp19
-rw-r--r--libs/acpi/acpi/common/checksum.hpp20
-rw-r--r--libs/acpi/acpi/common/table_header.cpp130
-rw-r--r--libs/acpi/acpi/common/table_header.hpp56
-rw-r--r--libs/acpi/acpi/common/table_header.test.cpp60
-rw-r--r--libs/acpi/acpi/common/table_signature.hpp17
-rw-r--r--libs/acpi/acpi/common/table_type.hpp17
-rw-r--r--libs/acpi/acpi/common/vla_table.hpp120
-rw-r--r--libs/acpi/acpi/data/madt.cpp112
-rw-r--r--libs/acpi/acpi/data/madt.hpp117
-rw-r--r--libs/acpi/acpi/data/madt.test.cpp57
-rw-r--r--libs/acpi/acpi/data/rsdt.cpp39
-rw-r--r--libs/acpi/acpi/data/rsdt.hpp42
-rw-r--r--libs/acpi/acpi/data/rsdt.test.cpp48
-rw-r--r--libs/acpi/acpi/data/xsdt.cpp39
-rw-r--r--libs/acpi/acpi/data/xsdt.hpp42
-rw-r--r--libs/acpi/acpi/data/xsdt.test.cpp48
-rw-r--r--libs/acpi/acpi/pointers.cpp56
-rw-r--r--libs/acpi/acpi/pointers.hpp72
-rw-r--r--libs/acpi/acpi/pointers.test.cpp30
-rw-r--r--libs/acpi/acpi/test_data/tables.S17
-rw-r--r--libs/acpi/acpi/test_data/tables.hpp28
-rw-r--r--libs/acpi/cmake/Scripts/IaslCompile.cmake16
-rw-r--r--libs/acpi/test_data/basic_madt.asl29
-rw-r--r--libs/acpi/test_data/basic_rsdp.asl16
-rw-r--r--libs/acpi/test_data/basic_rsdt.asl26
-rw-r--r--libs/acpi/test_data/basic_xsdt.asl26
-rw-r--r--libs/acpi/test_data/table_header.asl9
31 files changed, 1478 insertions, 0 deletions
diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt
new file mode 100644
index 0000000..1d03bb5
--- /dev/null
+++ b/libs/acpi/CMakeLists.txt
@@ -0,0 +1,118 @@
+cmake_minimum_required(VERSION "3.27.0")
+
+project("acpi"
+ DESCRIPTION "An ACPI parsing library"
+ VERSION "0.0.1"
+ LANGUAGES ASM CXX
+)
+
+include("CTest")
+
+#[============================================================================[
+# Library
+#]============================================================================]
+
+add_library("acpi" STATIC)
+add_library("acpi::lib" ALIAS "acpi")
+
+target_sources("acpi" PRIVATE
+ "acpi/common/checksum.cpp"
+ "acpi/common/table_header.cpp"
+ "acpi/data/madt.cpp"
+ "acpi/data/rsdt.cpp"
+ "acpi/data/xsdt.cpp"
+ "acpi/pointers.cpp"
+)
+
+file(GLOB_RECURSE ACPI_HEADERS
+ CONFIGURE_DEPENDS
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ "acpi/*.hpp"
+)
+
+target_sources("acpi" PUBLIC
+ FILE_SET HEADERS
+ BASE_DIRS "acpi"
+ FILES
+ ${ACPI_HEADERS}
+)
+
+target_include_directories("acpi" PUBLIC
+ "${CMAKE_CURRENT_SOURCE_DIR}"
+)
+
+target_link_libraries("acpi" PUBLIC
+ "kstd::lib"
+)
+
+set_target_properties("acpi" PROPERTIES
+ VERIFY_INTERFACE_HEADER_SETS YES
+)
+
+#[============================================================================[
+# Tests
+#]============================================================================]
+
+if(BUILD_TESTING)
+ find_package("Catch2")
+ include("Catch")
+
+ find_program(IASL_EXE NAMES "iasl" REQUIRED)
+
+ set(TEST_TABLES
+ "basic_madt"
+ "basic_rsdt"
+ "basic_rsdp"
+ "basic_xsdt"
+ "table_header"
+ )
+
+ foreach(TABLE IN LISTS TEST_TABLES)
+ add_custom_command(OUTPUT "test_data/${TABLE}.aml"
+ COMMAND ${CMAKE_COMMAND}
+ "-DIASL_EXE=${IASL_EXE}"
+ "-DIASL_OUTPUT=test_data/${TABLE}.aml"
+ "-DIASL_INPUT=${CMAKE_CURRENT_SOURCE_DIR}/test_data/${TABLE}.asl"
+ "-P"
+ "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Scripts/IaslCompile.cmake"
+ DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/test_data/${TABLE}.asl"
+ COMMENT "Compiling test_data/${TABLE}.asl"
+ VERBATIM
+ )
+ list(APPEND GENERATED_TABLE_BLOBS "${CMAKE_CURRENT_BINARY_DIR}/test_data/${TABLE}.aml")
+ endforeach()
+
+ set_source_files_properties("acpi/test_data/tables.S" PROPERTIES OBJECT_DEPENDS "${GENERATED_TABLE_BLOBS}")
+
+ if(COMMAND "enable_coverage")
+ enable_coverage("acpi")
+ endif()
+
+ add_executable("acpi_tests")
+ add_executable("acpi::tests" ALIAS "acpi_tests")
+
+ target_sources("acpi_tests" PRIVATE
+ "acpi/common/table_header.test.cpp"
+ "acpi/data/madt.test.cpp"
+ "acpi/data/rsdt.test.cpp"
+ "acpi/data/xsdt.test.cpp"
+ "acpi/pointers.test.cpp"
+
+ "acpi/test_data/tables.S"
+ )
+
+ target_include_directories("acpi_tests" PRIVATE
+ "${CMAKE_CURRENT_BINARY_DIR}/test_data"
+ )
+
+ target_link_libraries("acpi_tests" PRIVATE
+ "Catch2::Catch2WithMain"
+ "acpi::lib"
+ )
+
+ set_target_properties("acpi_tests" PROPERTIES
+ EXCLUDE_FROM_ALL NO
+ )
+
+ catch_discover_tests("acpi::tests" ${CATCH_TEST_ARGS})
+endif()
diff --git a/libs/acpi/acpi/acpi.hpp b/libs/acpi/acpi/acpi.hpp
new file mode 100644
index 0000000..0da44a8
--- /dev/null
+++ b/libs/acpi/acpi/acpi.hpp
@@ -0,0 +1,12 @@
+#ifndef ACPI_ACPI_HPP
+#define ACPI_ACPI_HPP
+
+#include <acpi/common/checksum.hpp> // IWYU pragma: export
+#include <acpi/common/table_signature.hpp> // IWYU pragma: export
+#include <acpi/common/table_type.hpp> // IWYU pragma: export
+#include <acpi/data/madt.hpp> // IWYU pragma: export
+#include <acpi/data/rsdt.hpp> // IWYU pragma: export
+#include <acpi/data/xsdt.hpp> // IWYU pragma: export
+#include <acpi/pointers.hpp> // IWYU pragma: export
+
+#endif
diff --git a/libs/acpi/acpi/common/basic_table.hpp b/libs/acpi/acpi/common/basic_table.hpp
new file mode 100644
index 0000000..f5b5b27
--- /dev/null
+++ b/libs/acpi/acpi/common/basic_table.hpp
@@ -0,0 +1,40 @@
+#ifndef ACPI_COMMON_BASIC_TABLE_HPP
+#define ACPI_COMMON_BASIC_TABLE_HPP
+
+#include <acpi/common/checksum.hpp>
+#include <acpi/common/table_header.hpp>
+#include <acpi/common/table_signature.hpp>
+
+#include <cstddef>
+#include <span>
+
+namespace acpi
+{
+
+ template<typename TableType>
+ struct basic_table : table_header
+ {
+ [[nodiscard]] auto validate_checksum() const noexcept -> bool
+ {
+ return acpi::validate_checksum(as_span());
+ }
+
+ [[nodiscard]] auto as_span() const noexcept -> std::span<std::byte const>
+ {
+ return {reinterpret_cast<std::byte const *>(this), length().value};
+ }
+
+ [[nodiscard]] auto validate() const noexcept -> bool
+ {
+ return signature() == table_signature_v<TableType> && validate_checksum();
+ }
+
+ private:
+ friend TableType;
+
+ constexpr basic_table() noexcept = default;
+ };
+
+} // namespace acpi
+
+#endif \ No newline at end of file
diff --git a/libs/acpi/acpi/common/checksum.cpp b/libs/acpi/acpi/common/checksum.cpp
new file mode 100644
index 0000000..09425dd
--- /dev/null
+++ b/libs/acpi/acpi/common/checksum.cpp
@@ -0,0 +1,19 @@
+#include <acpi/common/checksum.hpp>
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <span>
+
+namespace acpi
+{
+
+ auto validate_checksum(std::span<std::byte const> data) -> bool
+ {
+ auto sum = std::ranges::fold_left(data, std::uint8_t{}, [](auto acc, auto byte) {
+ return static_cast<std::uint8_t>(acc + static_cast<std::uint8_t>(byte));
+ });
+ return sum == 0;
+ }
+
+} // namespace acpi
diff --git a/libs/acpi/acpi/common/checksum.hpp b/libs/acpi/acpi/common/checksum.hpp
new file mode 100644
index 0000000..a92c242
--- /dev/null
+++ b/libs/acpi/acpi/common/checksum.hpp
@@ -0,0 +1,20 @@
+#ifndef ACPI_CHECKSUM_HPP
+#define ACPI_CHECKSUM_HPP
+
+// IWYU pragma: private, include <acpi/acpi.hpp>
+
+#include <cstddef>
+#include <span>
+
+namespace acpi
+{
+
+ //! Validate and ACPI entity checksum.
+ //!
+ //! @param data The data to validate the checksum of.
+ //! @return true iff. the checksum is valid, false otherwise.
+ auto validate_checksum(std::span<std::byte const> data) -> bool;
+
+} // namespace acpi
+
+#endif
diff --git a/libs/acpi/acpi/common/table_header.cpp b/libs/acpi/acpi/common/table_header.cpp
new file mode 100644
index 0000000..6a7a4dc
--- /dev/null
+++ b/libs/acpi/acpi/common/table_header.cpp
@@ -0,0 +1,130 @@
+#include <acpi/common/table_header.hpp>
+
+#include <kstd/cstring>
+#include <kstd/units>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <span>
+#include <string_view>
+
+namespace acpi
+{
+
+ struct common_table_header_data
+ {
+ std::array<char, 4> signature;
+ std::uint32_t length;
+ std::uint8_t revision;
+ std::uint8_t checksum;
+ std::array<char, 6> oem_id;
+ std::array<char, 8> oem_table_id;
+ std::uint32_t oem_revision;
+ std::array<char, 4> creator_id;
+ std::uint32_t creator_revision;
+ };
+
+ static_assert(sizeof(common_table_header_data) == common_table_header_size);
+
+ auto table_header::creator_revision() const noexcept -> decltype(common_table_header_data::creator_revision)
+ {
+ using type = decltype(common_table_header_data::creator_revision);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(common_table_header_data, creator_revision);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return value;
+ }
+
+ auto table_header::creator_id() const noexcept -> std::string_view
+ {
+ constexpr auto size = sizeof(common_table_header_data::creator_id);
+ constexpr auto offset = offsetof(common_table_header_data, creator_id);
+
+ auto const base = reinterpret_cast<char const *>(m_data.data());
+
+ return std::string_view{base + offset, size};
+ }
+
+ auto table_header::length() const noexcept -> kstd::units::bytes
+ {
+ using type = decltype(common_table_header_data::length);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(common_table_header_data, length);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto raw_value = type{};
+
+ kstd::libc::memcpy(&raw_value, data.data(), size);
+
+ return kstd::units::bytes{raw_value};
+ }
+
+ auto table_header::oem_id() const noexcept -> std::string_view
+ {
+ constexpr auto size = sizeof(common_table_header_data::oem_id);
+ constexpr auto offset = offsetof(common_table_header_data, oem_id);
+
+ auto const base = reinterpret_cast<char const *>(m_data.data());
+
+ return std::string_view{base + offset, size};
+ }
+
+ auto table_header::oem_table_id() const noexcept -> std::string_view
+ {
+ constexpr auto size = sizeof(common_table_header_data::oem_table_id);
+ constexpr auto offset = offsetof(common_table_header_data, oem_table_id);
+
+ auto const base = reinterpret_cast<char const *>(m_data.data());
+
+ return std::string_view{base + offset, size};
+ }
+
+ auto table_header::oem_revision() const noexcept -> decltype(common_table_header_data::oem_revision)
+ {
+ using type = decltype(common_table_header_data::oem_revision);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(common_table_header_data, oem_revision);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return value;
+ }
+
+ auto table_header::revision() const noexcept -> decltype(common_table_header_data::revision)
+ {
+ using type = decltype(common_table_header_data::revision);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(common_table_header_data, revision);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return value;
+ }
+
+ auto table_header::signature() const noexcept -> std::string_view
+ {
+ constexpr auto size = sizeof(common_table_header_data::signature);
+ constexpr auto offset = offsetof(common_table_header_data, signature);
+
+ auto const base = reinterpret_cast<char const *>(m_data.data());
+
+ return std::string_view{base + offset, size};
+ }
+
+} // namespace acpi \ No newline at end of file
diff --git a/libs/acpi/acpi/common/table_header.hpp b/libs/acpi/acpi/common/table_header.hpp
new file mode 100644
index 0000000..471fed8
--- /dev/null
+++ b/libs/acpi/acpi/common/table_header.hpp
@@ -0,0 +1,56 @@
+#ifndef ACPI_COMMON_TABLE_HEADER_HPP
+#define ACPI_COMMON_TABLE_HEADER_HPP
+
+// IWYU pragma: private, include <acpi/acpi.hpp>
+
+#include <kstd/units>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+
+namespace acpi
+{
+
+ //! The size of the common table header as defined in the ACPI specification.
+ constexpr auto common_table_header_size = 36;
+
+ //! The common header for all ACPI tables, except the FACS.
+ //!
+ //! Multibyte fields in the header are not guaranteed to be naturally aligned by the firmware. Therefore, no direct
+ //! access to multibyte fields is provided. Instead, the provided member functions handle alignment of the multibyte
+ //! values and thus provide a safe interface to the header fields.
+ struct table_header
+ {
+ //! Get the revision of the utility used to create this table.
+ [[nodiscard]] auto creator_revision() const noexcept -> std::uint32_t;
+
+ //! Get the vendor ID of the utility used to create this table.
+ [[nodiscard]] auto creator_id() const noexcept -> std::string_view;
+
+ //! Get the length of the entire table, including this header.
+ [[nodiscard]] auto length() const noexcept -> kstd::units::bytes;
+
+ //! Get the ID of the OEM.
+ [[nodiscard]] auto oem_id() const noexcept -> std::string_view;
+
+ //! Get the OEMs revision number of this table.
+ [[nodiscard]] auto oem_revision() const noexcept -> std::uint32_t;
+
+ //! Get the OEMs ID of this table.
+ [[nodiscard]] auto oem_table_id() const noexcept -> std::string_view;
+
+ //! Get the revision number of the structure of this table.
+ [[nodiscard]] auto revision() const noexcept -> std::uint8_t;
+
+ //! Get the signature of this table.
+ [[nodiscard]] auto signature() const noexcept -> std::string_view;
+
+ private:
+ std::array<std::byte, common_table_header_size> m_data;
+ };
+
+} // namespace acpi
+
+#endif \ No newline at end of file
diff --git a/libs/acpi/acpi/common/table_header.test.cpp b/libs/acpi/acpi/common/table_header.test.cpp
new file mode 100644
index 0000000..ddc879e
--- /dev/null
+++ b/libs/acpi/acpi/common/table_header.test.cpp
@@ -0,0 +1,60 @@
+#include <acpi/common/table_header.hpp>
+
+#include <acpi/test_data/tables.hpp>
+
+#include <kstd/units>
+
+#include <catch2/catch_test_macros.hpp>
+
+SCENARIO("Common table header parsing", "[common_table_header]")
+{
+ GIVEN("A valid compiled table header")
+ {
+ auto data = acpi::test_data::tables::table_header();
+
+ WHEN("parsing the header")
+ {
+ auto header = reinterpret_cast<acpi::table_header const *>(data.data());
+
+ THEN("the signature is correct")
+ {
+ REQUIRE(header->signature() == "TEST");
+ }
+
+ THEN("the revision is correct")
+ {
+ REQUIRE(header->revision() == 1);
+ }
+
+ THEN("the length is correct")
+ {
+ REQUIRE(header->length() == kstd::type_size<acpi::table_header>);
+ }
+
+ THEN("the oem id is correct")
+ {
+ REQUIRE(header->oem_id() == "FEMO ");
+ }
+
+ THEN("the oem table id is correct")
+ {
+ REQUIRE(header->oem_table_id() == "HDRTEST ");
+ }
+
+ THEN("the oem revision is correct")
+ {
+ REQUIRE(header->oem_revision() == 1);
+ }
+
+ THEN("the creator id is correct")
+ {
+ REQUIRE(header->creator_id() == "INTL");
+ }
+
+ THEN("the creator revision is non-zero")
+ {
+ REQUIRE(header->creator_revision() != 0);
+ }
+ }
+ }
+}
diff --git a/libs/acpi/acpi/common/table_signature.hpp b/libs/acpi/acpi/common/table_signature.hpp
new file mode 100644
index 0000000..f0c2d7a
--- /dev/null
+++ b/libs/acpi/acpi/common/table_signature.hpp
@@ -0,0 +1,17 @@
+#ifndef ACPI_COMMON_TABLE_SIGNATURE_HPP
+#define ACPI_COMMON_TABLE_SIGNATURE_HPP
+
+// IWYU pragma: private, include <acpi/acpi.hpp>
+
+namespace acpi
+{
+
+ template<typename TableType>
+ struct table_signature;
+
+ template<typename TableType>
+ constexpr auto table_signature_v = table_signature<TableType>::value;
+
+} // namespace acpi
+
+#endif
diff --git a/libs/acpi/acpi/common/table_type.hpp b/libs/acpi/acpi/common/table_type.hpp
new file mode 100644
index 0000000..bc427c7
--- /dev/null
+++ b/libs/acpi/acpi/common/table_type.hpp
@@ -0,0 +1,17 @@
+#ifndef ACPI_COMMON_TABLE_TYPE_HPP
+#define ACPI_COMMON_TABLE_TYPE_HPP
+
+// IWYU pragma: private, include <acpi/acpi.hpp>
+
+namespace acpi
+{
+
+ template<char const *>
+ struct table_type;
+
+ template<char const * Signature>
+ using table_type_t = typename table_type<Signature>::type;
+
+} // namespace acpi
+
+#endif
diff --git a/libs/acpi/acpi/common/vla_table.hpp b/libs/acpi/acpi/common/vla_table.hpp
new file mode 100644
index 0000000..d3f33a7
--- /dev/null
+++ b/libs/acpi/acpi/common/vla_table.hpp
@@ -0,0 +1,120 @@
+#ifndef ACPI_COMMON_VLA_TABLE_HPP
+#define ACPI_COMMON_VLA_TABLE_HPP
+
+#include <acpi/common/basic_table.hpp>
+
+#include <cstddef>
+#include <iterator>
+
+namespace acpi
+{
+
+ template<typename EntryType>
+ struct vla_table_iterator
+ {
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = EntryType;
+ using pointer = value_type *;
+ using reference = value_type &;
+ using difference_type = std::ptrdiff_t;
+
+ constexpr vla_table_iterator() noexcept = default;
+
+ constexpr vla_table_iterator(pointer entry, pointer limit) noexcept
+ : m_entry{entry}
+ , m_limit{limit}
+ {}
+
+ constexpr auto operator++() noexcept -> vla_table_iterator &
+ {
+ auto decayed = std::bit_cast<std::byte *>(m_entry);
+ decayed += m_entry->length();
+ m_entry = std::bit_cast<pointer>(decayed);
+ return *this;
+ }
+
+ constexpr auto operator++(int) noexcept -> vla_table_iterator
+ {
+ auto copy = *this;
+ ++*this;
+ return copy;
+ }
+
+ constexpr auto operator*() const noexcept -> reference
+ {
+ return *m_entry;
+ }
+
+ constexpr auto operator->() const noexcept -> pointer
+ {
+ return m_entry;
+ }
+
+ constexpr auto operator==(vla_table_iterator const & other) const noexcept -> bool
+ {
+ return m_entry == other.m_entry || (is_end() && other.is_end());
+ }
+
+ private:
+ [[nodiscard]] constexpr auto is_end() const noexcept -> bool
+ {
+ return m_entry == m_limit;
+ }
+
+ pointer m_entry{};
+ pointer m_limit{};
+ };
+
+ template<typename EntryType, typename TableType>
+ struct vla_table : basic_table<TableType>
+ {
+ using entry_type = EntryType;
+ using iterator = vla_table_iterator<EntryType>;
+ using const_iterator = vla_table_iterator<EntryType const>;
+
+ [[nodiscard]] auto begin() noexcept -> iterator
+ {
+ auto base = std::bit_cast<std::byte *>(this);
+ base += sizeof(TableType);
+ auto limit = std::bit_cast<std::byte *>(this);
+ limit += this->length().value;
+ return iterator{std::bit_cast<entry_type *>(base), std::bit_cast<entry_type *>(limit)};
+ }
+
+ [[nodiscard]] auto end() noexcept -> iterator
+ {
+ return iterator{};
+ }
+
+ [[nodiscard]] auto begin() const noexcept -> const_iterator
+ {
+ auto base = std::bit_cast<std::byte *>(this);
+ base += sizeof(TableType);
+ auto limit = std::bit_cast<std::byte *>(this);
+ limit += this->length().value;
+ return const_iterator{std::bit_cast<entry_type const *>(base), std::bit_cast<entry_type const *>(limit)};
+ }
+
+ [[nodiscard]] auto end() const noexcept -> const_iterator
+ {
+ return const_iterator{};
+ }
+
+ [[nodiscard]] auto cbegin() const noexcept -> const_iterator
+ {
+ return begin();
+ }
+
+ [[nodiscard]] auto cend() const noexcept -> const_iterator
+ {
+ return end();
+ }
+
+ private:
+ friend TableType;
+
+ constexpr vla_table() noexcept = default;
+ };
+} // namespace acpi
+
+#endif \ No newline at end of file
diff --git a/libs/acpi/acpi/data/madt.cpp b/libs/acpi/acpi/data/madt.cpp
new file mode 100644
index 0000000..1a8b6d3
--- /dev/null
+++ b/libs/acpi/acpi/data/madt.cpp
@@ -0,0 +1,112 @@
+#include <acpi/data/madt.hpp>
+
+#include <kstd/cstring>
+
+#include <bit>
+#include <cstddef>
+#include <cstdint>
+#include <span>
+
+namespace acpi
+{
+
+ struct madt_data
+ {
+ std::uint32_t local_interrupt_controller_address;
+ std::uint32_t flags;
+ };
+
+ static_assert(sizeof(madt_data) == 8);
+
+ auto madt::local_interrupt_controller_address() const noexcept -> std::uintptr_t
+ {
+ using type = decltype(madt_data::local_interrupt_controller_address);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(madt_data, local_interrupt_controller_address);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return value;
+ }
+
+ auto madt::flags() const noexcept -> std::uint32_t
+ {
+ using type = decltype(madt_data::flags);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(madt_data, flags);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return value;
+ }
+
+ struct madt_entry_data
+ {
+ std::uint8_t type;
+ std::uint8_t length;
+ };
+
+ static_assert(sizeof(madt_entry_data) == 2);
+
+ auto madt_entry::type() const noexcept -> enum type
+ {
+ constexpr auto field_offset = offsetof(madt_entry_data, type);
+
+ return std::bit_cast<enum type>(*(m_data.data() + field_offset));
+ }
+
+ auto madt_entry::length() const noexcept -> std::size_t
+ {
+ constexpr auto field_offset = offsetof(madt_entry_data, length);
+
+ return static_cast<std::size_t>(*(m_data.data() + field_offset));
+ }
+
+ struct [[gnu::packed]] processor_local_apic_entry_data
+ {
+ std::uint8_t apic_id;
+ std::uint8_t processor_id;
+ std::uint32_t flags;
+ };
+
+ static_assert(sizeof(processor_local_apic_entry_data) == 6);
+
+ auto processor_local_apic_entry::id() const noexcept -> std::uint8_t
+ {
+ constexpr auto field_offset = offsetof(processor_local_apic_entry_data, apic_id);
+
+ return static_cast<std::uint8_t>(*(m_data.data() + field_offset));
+ }
+
+ auto processor_local_apic_entry::flags() const noexcept -> enum flags
+ {
+ using type = decltype(processor_local_apic_entry_data::flags);
+ static_assert(sizeof(type) == sizeof(enum flags));
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(processor_local_apic_entry_data, flags);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return static_cast<enum flags>(value);
+ }
+
+ auto processor_local_apic_entry::processor_id() const noexcept -> std::uint32_t
+ {
+ constexpr auto field_offset = offsetof(processor_local_apic_entry_data, processor_id);
+
+ return static_cast<std::uint32_t>(*(m_data.data() + field_offset));
+ }
+
+} // namespace acpi
diff --git a/libs/acpi/acpi/data/madt.hpp b/libs/acpi/acpi/data/madt.hpp
new file mode 100644
index 0000000..b76daa4
--- /dev/null
+++ b/libs/acpi/acpi/data/madt.hpp
@@ -0,0 +1,117 @@
+#ifndef ACPI_ACPI_MADT_HPP
+#define ACPI_ACPI_MADT_HPP
+
+// IWYU pragma: private, include <acpi/acpi.hpp>
+
+#include <acpi/common/table_signature.hpp>
+#include <acpi/common/table_type.hpp>
+#include <acpi/common/vla_table.hpp>
+
+#include <kstd/ext/bitfield_enum>
+#include <kstd/os/error.hpp>
+#include <kstd/units>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <ranges>
+#include <type_traits>
+
+namespace acpi
+{
+
+ template<>
+ struct table_signature<struct madt>
+ {
+ constexpr char static const value[] = "APIC"; // NOLINT
+ };
+
+ template<>
+ struct table_type<table_signature_v<struct madt>>
+ {
+ using type = struct madt;
+ };
+
+ struct madt_entry
+ {
+ //! The type of an MADT entry.
+ enum struct type : std::uint8_t
+ {
+ processor_local_apic,
+ io_apic,
+ io_apic_interrupt_source_override,
+ io_apic_nmi_source,
+ local_apic_nmi_interrupts,
+ local_apic_address_override,
+ processor_local_x2_apic,
+ };
+
+ //! Get the type of this entry.
+ [[nodiscard]] auto type() const noexcept -> enum type;
+
+ //! Get the length of this entry.
+ [[nodiscard]] auto length() const noexcept -> std::size_t;
+
+ //! Cast this entry to the given concrete entry type.
+ //!
+ //! @warning This function will terminate execution if the desired target type does not match up with this entry's
+ //! actual type.
+ template<typename EntryType>
+ [[nodiscard]] auto as() const -> EntryType const &
+ {
+ if (type() != EntryType::this_type)
+ {
+ kstd::os::panic("Invalid cast");
+ }
+ return reinterpret_cast<EntryType const &>(*this);
+ }
+
+ private:
+ std::array<std::byte, 2> m_data{};
+ };
+
+ //! The Multiple APIC Description Table (MADT)
+ struct madt : vla_table<madt_entry, madt>
+ {
+ //! Get the physical address of the local interrupt controllers described by this entry.
+ [[nodiscard]] auto local_interrupt_controller_address() const noexcept -> std::uintptr_t;
+
+ [[nodiscard]] auto flags() const noexcept -> std::uint32_t;
+
+ template<typename EntryType>
+ [[nodiscard]] auto only() const noexcept
+ {
+ return *this | std::views::filter([](auto const & e) { return e.type() == EntryType::this_type; }) |
+ std::views::transform([](auto const & e) { return e.template as<EntryType>(); });
+ }
+
+ private:
+ std::array<std::byte, 8> m_data{};
+ };
+
+ struct processor_local_apic_entry : madt_entry
+ {
+ constexpr auto static this_type = type::processor_local_apic;
+
+ enum struct flags : std::uint32_t
+ {
+ processor_enabled = 1,
+ online_capable = 2,
+ };
+
+ [[nodiscard]] auto id() const noexcept -> std::uint8_t;
+ [[nodiscard]] auto flags() const noexcept -> flags;
+ [[nodiscard]] auto processor_id() const noexcept -> std::uint32_t;
+
+ private:
+ std::array<std::byte, 6> m_data{};
+ };
+
+} // namespace acpi
+
+template<>
+struct kstd::ext::is_bitfield_enum<enum acpi::processor_local_apic_entry::flags> : std::true_type
+{
+};
+
+#endif
diff --git a/libs/acpi/acpi/data/madt.test.cpp b/libs/acpi/acpi/data/madt.test.cpp
new file mode 100644
index 0000000..1b95a74
--- /dev/null
+++ b/libs/acpi/acpi/data/madt.test.cpp
@@ -0,0 +1,57 @@
+#include <acpi/data/madt.hpp>
+
+#include <acpi/test_data/tables.hpp>
+
+#include <kstd/units>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <iterator>
+
+SCENARIO("MADT parsing", "[madt]")
+{
+ GIVEN("The basic compiled MADT containing a single LAPIC entry and the default x86 LAPIC address")
+ {
+ auto data = acpi::test_data::tables::basic_madt();
+
+ WHEN("parsing the table")
+ {
+ auto madt = reinterpret_cast<acpi::madt const *>(data.data());
+
+ THEN("the signature is correct")
+ {
+ REQUIRE(madt->signature() == "APIC");
+ }
+
+ THEN("validate returns true")
+ {
+ REQUIRE(madt->validate());
+ }
+
+ THEN("there is a single entry in the table")
+ {
+ REQUIRE(std::distance(madt->begin(), madt->end()) == 1);
+ }
+
+ THEN("the LAPIC address is 0xfee00000")
+ {
+ REQUIRE(madt->local_interrupt_controller_address() == 0xfee0'0000);
+ }
+
+ THEN("the length is sizeof(madt) + sizeof(processor_local_apic)")
+ {
+ REQUIRE(madt->length().value == sizeof(acpi::madt) + sizeof(acpi::processor_local_apic_entry));
+ }
+
+ THEN("the first entry has type processor_local_apic")
+ {
+ REQUIRE(madt->cbegin()->type() == acpi::madt_entry::type::processor_local_apic);
+ }
+
+ THEN("`only` can be used to get a view of all processor_local_apic entries")
+ {
+ REQUIRE(std::ranges::distance(madt->only<acpi::processor_local_apic_entry>()) == 1);
+ }
+ }
+ }
+}
diff --git a/libs/acpi/acpi/data/rsdt.cpp b/libs/acpi/acpi/data/rsdt.cpp
new file mode 100644
index 0000000..80e209d
--- /dev/null
+++ b/libs/acpi/acpi/data/rsdt.cpp
@@ -0,0 +1,39 @@
+#include <acpi/data/rsdt.hpp>
+
+#include <acpi/common/table_header.hpp>
+
+#include <kstd/cstring>
+
+#include <cstddef>
+#include <cstdint>
+#include <span>
+
+namespace acpi
+{
+
+ struct rsdt_entry_data
+ {
+ std::uint32_t address;
+ };
+
+ [[nodiscard]] auto rsdt_entry::address() const noexcept -> table_header *
+ {
+ using type = decltype(rsdt_entry_data::address);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(rsdt_entry_data, address);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return reinterpret_cast<table_header *>(static_cast<uintptr_t>(value));
+ }
+
+ [[nodiscard]] auto rsdt_entry::length() const noexcept -> std::size_t
+ {
+ return sizeof(m_data);
+ }
+
+} // namespace acpi \ No newline at end of file
diff --git a/libs/acpi/acpi/data/rsdt.hpp b/libs/acpi/acpi/data/rsdt.hpp
new file mode 100644
index 0000000..a661187
--- /dev/null
+++ b/libs/acpi/acpi/data/rsdt.hpp
@@ -0,0 +1,42 @@
+#ifndef ACPI_DATA_RSDT_HPP
+#define ACPI_DATA_RSDT_HPP
+
+#include <acpi/common/table_header.hpp>
+#include <acpi/common/table_signature.hpp>
+#include <acpi/common/table_type.hpp>
+#include <acpi/common/vla_table.hpp>
+
+#include <array>
+#include <cstddef>
+
+namespace acpi
+{
+
+ template<>
+ struct table_signature<struct rsdt>
+ {
+ constexpr char static const value[] = "RSDT"; // NOLINT
+ };
+
+ template<>
+ struct table_type<table_signature_v<struct rsdt>>
+ {
+ using type = struct rsdt;
+ };
+
+ struct rsdt_entry
+ {
+ [[nodiscard]] auto address() const noexcept -> table_header *;
+ [[nodiscard]] auto length() const noexcept -> std::size_t;
+
+ private:
+ std::array<std::byte, 4> m_data{};
+ };
+
+ struct rsdt : vla_table<rsdt_entry, rsdt>
+ {
+ };
+
+} // namespace acpi
+
+#endif \ No newline at end of file
diff --git a/libs/acpi/acpi/data/rsdt.test.cpp b/libs/acpi/acpi/data/rsdt.test.cpp
new file mode 100644
index 0000000..47992ce
--- /dev/null
+++ b/libs/acpi/acpi/data/rsdt.test.cpp
@@ -0,0 +1,48 @@
+#include <acpi/data/rsdt.hpp>
+
+#include <acpi/acpi.hpp>
+#include <acpi/test_data/tables.hpp>
+
+#include <kstd/units>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <iterator>
+
+SCENARIO("RSDT parsing", "[rsdt]")
+{
+ GIVEN("The basic compiled RSDT containing 8 table pointers")
+ {
+ auto data = acpi::test_data::tables::basic_rsdt();
+
+ WHEN("parsing the table")
+ {
+ auto rsdt = reinterpret_cast<acpi::rsdt const *>(data.data());
+
+ THEN("the signature is correct")
+ {
+ REQUIRE(rsdt->signature() == "RSDT");
+ }
+
+ THEN("validate returns true")
+ {
+ REQUIRE(rsdt->validate());
+ }
+
+ THEN("there are 8 entries in the table")
+ {
+ REQUIRE(std::distance(rsdt->begin(), rsdt->end()) == 8);
+ }
+
+ THEN("the first entry has address 0x10")
+ {
+ REQUIRE(rsdt->cbegin()->address() == reinterpret_cast<acpi::table_header *>(0x10));
+ }
+
+ THEN("the length is sizeof(rsdt) + 8 * sizeof(rsdt_entry)")
+ {
+ REQUIRE(rsdt->length().value == sizeof(acpi::rsdt) + 8 * sizeof(acpi::rsdt_entry));
+ }
+ }
+ }
+}
diff --git a/libs/acpi/acpi/data/xsdt.cpp b/libs/acpi/acpi/data/xsdt.cpp
new file mode 100644
index 0000000..b77aeab
--- /dev/null
+++ b/libs/acpi/acpi/data/xsdt.cpp
@@ -0,0 +1,39 @@
+#include <acpi/data/xsdt.hpp>
+
+#include <acpi/common/table_header.hpp>
+
+#include <kstd/cstring>
+
+#include <cstddef>
+#include <cstdint>
+#include <span>
+
+namespace acpi
+{
+
+ struct xsdt_entry_data
+ {
+ std::uint64_t address;
+ };
+
+ [[nodiscard]] auto xsdt_entry::address() const noexcept -> table_header *
+ {
+ using type = decltype(xsdt_entry_data::address);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(xsdt_entry_data, address);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return reinterpret_cast<table_header *>(static_cast<uintptr_t>(value));
+ }
+
+ [[nodiscard]] auto xsdt_entry::length() const noexcept -> std::size_t
+ {
+ return sizeof(m_data);
+ }
+
+} // namespace acpi \ No newline at end of file
diff --git a/libs/acpi/acpi/data/xsdt.hpp b/libs/acpi/acpi/data/xsdt.hpp
new file mode 100644
index 0000000..965f23c
--- /dev/null
+++ b/libs/acpi/acpi/data/xsdt.hpp
@@ -0,0 +1,42 @@
+#ifndef ACPI_DATA_XSDT_HPP
+#define ACPI_DATA_XSDT_HPP
+
+#include <acpi/common/table_header.hpp>
+#include <acpi/common/table_signature.hpp>
+#include <acpi/common/table_type.hpp>
+#include <acpi/common/vla_table.hpp>
+
+#include <array>
+#include <cstddef>
+
+namespace acpi
+{
+
+ template<>
+ struct table_signature<struct xsdt>
+ {
+ constexpr char static const value[] = "XSDT"; // NOLINT
+ };
+
+ template<>
+ struct table_type<table_signature_v<struct xsdt>>
+ {
+ using type = struct xsdt;
+ };
+
+ struct xsdt_entry
+ {
+ [[nodiscard]] auto address() const noexcept -> table_header *;
+ [[nodiscard]] auto length() const noexcept -> std::size_t;
+
+ private:
+ std::array<std::byte, 8> m_data{};
+ };
+
+ struct xsdt : vla_table<xsdt_entry, xsdt>
+ {
+ };
+
+} // namespace acpi
+
+#endif \ No newline at end of file
diff --git a/libs/acpi/acpi/data/xsdt.test.cpp b/libs/acpi/acpi/data/xsdt.test.cpp
new file mode 100644
index 0000000..77a5340
--- /dev/null
+++ b/libs/acpi/acpi/data/xsdt.test.cpp
@@ -0,0 +1,48 @@
+#include <acpi/data/xsdt.hpp>
+
+#include <acpi/acpi.hpp>
+#include <acpi/test_data/tables.hpp>
+
+#include <kstd/units>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <iterator>
+
+SCENARIO("XSDT parsing", "[xsdt]")
+{
+ GIVEN("The basic compiled XSDT containing 8 table pointers")
+ {
+ auto data = acpi::test_data::tables::basic_xsdt();
+
+ WHEN("parsing the table")
+ {
+ auto xsdt = reinterpret_cast<acpi::xsdt const *>(data.data());
+
+ THEN("the signature is correct")
+ {
+ REQUIRE(xsdt->signature() == "XSDT");
+ }
+
+ THEN("validate returns true")
+ {
+ REQUIRE(xsdt->validate());
+ }
+
+ THEN("there are 8 entries in the table")
+ {
+ REQUIRE(std::distance(xsdt->begin(), xsdt->end()) == 8);
+ }
+
+ THEN("the first entry has address 0x10")
+ {
+ REQUIRE(xsdt->cbegin()->address() == reinterpret_cast<acpi::table_header *>(0x10));
+ }
+
+ THEN("the length is sizeof(xsdt) + 8 * sizeof(xsdt_entry)")
+ {
+ REQUIRE(xsdt->length().value == sizeof(acpi::xsdt) + 8 * sizeof(acpi::xsdt_entry));
+ }
+ }
+ }
+}
diff --git a/libs/acpi/acpi/pointers.cpp b/libs/acpi/acpi/pointers.cpp
new file mode 100644
index 0000000..2ac8d31
--- /dev/null
+++ b/libs/acpi/acpi/pointers.cpp
@@ -0,0 +1,56 @@
+#include <acpi/pointers.hpp>
+
+#include <acpi/common/checksum.hpp>
+
+#include <kstd/units>
+
+#include <bit>
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+
+namespace acpi
+{
+
+ auto rsdp::oem_id() const noexcept -> std::string_view
+ {
+ return {m_oem_id.data(), m_oem_id.size()};
+ }
+
+ auto rsdp::revision() const noexcept -> std::uint8_t
+ {
+ return m_revision;
+ }
+
+ auto rsdp::signature() const noexcept -> std::string_view
+ {
+ return {m_signature.data(), m_signature.size()};
+ }
+
+ auto rsdp::table_address() const noexcept -> std::uintptr_t
+ {
+ auto raw = std::bit_cast<std::uint32_t>(m_rsdt_address);
+ return static_cast<std::uintptr_t>(raw);
+ }
+
+ auto rsdp::validate() const noexcept -> bool
+ {
+ return signature() == "RSD PTR " && validate_checksum({reinterpret_cast<std::byte const *>(this), sizeof(rsdp)});
+ }
+
+ auto xsdp::length() const noexcept -> kstd::units::bytes
+ {
+ return kstd::units::bytes{m_length};
+ }
+
+ auto xsdp::table_address() const noexcept -> std::uintptr_t
+ {
+ return std::bit_cast<std::uintptr_t>(m_xsdt_address);
+ }
+
+ auto xsdp::validate() const noexcept -> bool
+ {
+ return signature() == "RSD PTR " && validate_checksum({reinterpret_cast<std::byte const *>(this), m_length});
+ }
+
+} // namespace acpi
diff --git a/libs/acpi/acpi/pointers.hpp b/libs/acpi/acpi/pointers.hpp
new file mode 100644
index 0000000..5771e7d
--- /dev/null
+++ b/libs/acpi/acpi/pointers.hpp
@@ -0,0 +1,72 @@
+#ifndef ACPI_POINTERS_HPP
+#define ACPI_POINTERS_HPP
+
+// IWYU pragma: private, include <acpi/acpi.hpp>
+
+#include <kstd/units>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+
+namespace acpi
+{
+
+ //! A pointer to the Root System Description Table.
+ struct [[gnu::packed]] rsdp
+ {
+ //! Get the ID of the OEM, usually a vendor name.
+ [[nodiscard]] auto oem_id() const noexcept -> std::string_view;
+
+ //! Get the revision of the ACPI Root System Description Table.
+ //!
+ //! @note Revisions greater or equal to two indicate that the system uses the Extended System Description Table, and
+ //! as such that table should be use to query information.
+ [[nodiscard]] auto revision() const noexcept -> std::uint8_t;
+
+ //! Get the signature of this pointer.
+ //!
+ //! A valid RSDP must always carry the signature "RSD PTR ".
+ [[nodiscard]] auto signature() const noexcept -> std::string_view;
+
+ //! Get the physical address of the pointed-to Root System Description Table.
+ [[nodiscard]] auto table_address() const noexcept -> std::uintptr_t;
+
+ //! Validate the checksum of this RSDP.
+ //!
+ //! @return @p true iff. the checksum of this RSDP is valid, @p false otherwise.
+ [[nodiscard]] auto validate() const noexcept -> bool;
+
+ private:
+ std::array<char, 8> m_signature;
+ [[maybe_unused]] std::uint8_t m_checksum;
+ std::array<char, 6> m_oem_id;
+ std::uint8_t m_revision;
+ std::array<std::byte, 4> m_rsdt_address;
+ };
+
+ //! A pointer to the Extended System Description Table.
+ struct [[gnu::packed]] xsdp : rsdp
+ {
+ //! Get the length of the data contained in this pointer.
+ [[nodiscard]] auto length() const noexcept -> kstd::units::bytes;
+
+ //! Get the physical address of the pointed-to Extended System Description Table.
+ [[nodiscard]] auto table_address() const noexcept -> std::uintptr_t;
+
+ //! Validate the checksum of this XSDP.
+ //!
+ //! @return @p true iff. the checksum of this RSDP is valid, @p false otherwise.
+ [[nodiscard]] auto validate() const noexcept -> bool;
+
+ private:
+ std::uint32_t m_length;
+ std::array<std::byte, 8> m_xsdt_address;
+ [[maybe_unused]] std::uint8_t m_extended_checksum;
+ [[maybe_unused]] std::array<std::byte, 3> m_reserved;
+ };
+
+} // namespace acpi
+
+#endif
diff --git a/libs/acpi/acpi/pointers.test.cpp b/libs/acpi/acpi/pointers.test.cpp
new file mode 100644
index 0000000..d7b700d
--- /dev/null
+++ b/libs/acpi/acpi/pointers.test.cpp
@@ -0,0 +1,30 @@
+#include <acpi/pointers.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <array>
+#include <bit>
+#include <cstddef>
+
+SCENARIO("ACPI root pointer parsing", "[acpi]")
+{
+ GIVEN("A null-filled pointer")
+ {
+ auto data = std::array<std::byte, sizeof(acpi::rsdp)>{};
+
+ WHEN("parsing the data")
+ {
+ auto rsdp = std::bit_cast<acpi::rsdp>(data);
+
+ THEN("the signature is invalid")
+ {
+ REQUIRE(rsdp.signature() != "RSD PTR ");
+ }
+
+ THEN("validate returns false")
+ {
+ REQUIRE_FALSE(rsdp.validate());
+ }
+ }
+ }
+}
diff --git a/libs/acpi/acpi/test_data/tables.S b/libs/acpi/acpi/test_data/tables.S
new file mode 100644
index 0000000..641db6a
--- /dev/null
+++ b/libs/acpi/acpi/test_data/tables.S
@@ -0,0 +1,17 @@
+.section .rodata
+
+.balign 16
+
+#define TABLE(name, file) \
+ .global name##_start; \
+ .global name##_end; \
+ name##_start: .incbin file; \
+ name##_end:
+
+TABLE(basic_madt, "basic_madt.aml")
+TABLE(basic_rsdt, "basic_rsdt.aml")
+TABLE(basic_rsdp, "basic_rsdp.aml")
+TABLE(basic_xsdt, "basic_xsdt.aml")
+TABLE(table_header, "table_header.aml")
+
+#undef TABLE
diff --git a/libs/acpi/acpi/test_data/tables.hpp b/libs/acpi/acpi/test_data/tables.hpp
new file mode 100644
index 0000000..e91f1a5
--- /dev/null
+++ b/libs/acpi/acpi/test_data/tables.hpp
@@ -0,0 +1,28 @@
+#ifndef ACPI_TEST_DATA_TABLES_HPP
+#define ACPI_TEST_DATA_TABLES_HPP
+
+#include <cstddef>
+#include <span>
+
+#define TABLE(name) \
+ extern "C" std::byte const name##_start; \
+ extern "C" std::byte const name##_end; \
+ auto inline name()->std::span<std::byte const> \
+ { \
+ return {&name##_start, &name##_end}; \
+ }
+
+namespace acpi::test_data::tables
+{
+
+ TABLE(basic_madt);
+ TABLE(basic_rsdt);
+ TABLE(basic_rsdp);
+ TABLE(basic_xsdt);
+ TABLE(table_header);
+
+} // namespace acpi::test_data::tables
+
+#undef TABLE
+
+#endif
diff --git a/libs/acpi/cmake/Scripts/IaslCompile.cmake b/libs/acpi/cmake/Scripts/IaslCompile.cmake
new file mode 100644
index 0000000..ff73b34
--- /dev/null
+++ b/libs/acpi/cmake/Scripts/IaslCompile.cmake
@@ -0,0 +1,16 @@
+execute_process(
+ COMMAND
+ "${IASL_EXE}"
+ "-vs"
+ "-p"
+ "${IASL_OUTPUT}"
+ "${IASL_INPUT}"
+ OUTPUT_VARIABLE IASL_OUT
+ ERROR_VARIABLE IASL_ERR
+ RESULT_VARIABLE IASL_RES
+)
+
+if(NOT IASL_RES EQUAL 0)
+ message(STATUS "${IASL_OUT}")
+ message(FATAL_ERROR "${IASL_ERR}")
+endif()
diff --git a/libs/acpi/test_data/basic_madt.asl b/libs/acpi/test_data/basic_madt.asl
new file mode 100644
index 0000000..cd6958a
--- /dev/null
+++ b/libs/acpi/test_data/basic_madt.asl
@@ -0,0 +1,29 @@
+/*
+ * Intel ACPI Component Architecture
+ * iASL Compiler/Disassembler version 20251212 (64-bit version)
+ * Copyright (c) 2000 - 2025 Intel Corporation
+ *
+ * Template for [APIC] ACPI Table (static data table)
+ * Format: [ByteLength] FieldName : HexFieldValue
+ */
+[0004] Signature : "APIC" [Multiple APIC Description Table (MADT)]
+[0004] Table Length : 00000000
+[0001] Revision : 07
+[0001] Checksum : 00
+[0006] Oem ID : "INTEL "
+[0008] Oem Table ID : "Template"
+[0004] Oem Revision : 00000001
+[0004] Asl Compiler ID : "INTL"
+[0004] Asl Compiler Revision : 20230628
+
+[0004] Local Apic Address : FEE00000
+[0004] Flags (decoded below) : 00000001
+ PC-AT Compatibility : 1
+
+[0001] Subtable Type : 00 [Processor Local APIC]
+[0001] Length : 08
+[0001] Processor ID : 00
+[0001] Local Apic ID : 00
+[0004] Flags (decoded below) : 00000001
+ Processor Enabled : 1
+ Runtime Online Capable : 0
diff --git a/libs/acpi/test_data/basic_rsdp.asl b/libs/acpi/test_data/basic_rsdp.asl
new file mode 100644
index 0000000..8274c0f
--- /dev/null
+++ b/libs/acpi/test_data/basic_rsdp.asl
@@ -0,0 +1,16 @@
+/*
+ * Intel ACPI Component Architecture
+ * iASL Compiler/Disassembler version 20251212 (64-bit version)
+ * Copyright (c) 2000 - 2025 Intel Corporation
+ *
+ * Template for [RSDP] ACPI Table (AML byte code table)
+ */
+[0008] Signature : "RSD PTR "
+[0001] Checksum : 43
+[0006] Oem ID : "INTEL "
+[0001] Revision : 02
+[0004] RSDT Address : 00000000
+[0004] Length : 00000024
+[0008] XSDT Address : 0000000000000000
+[0001] Extended Checksum : DC
+[0003] Reserved : 000000
diff --git a/libs/acpi/test_data/basic_rsdt.asl b/libs/acpi/test_data/basic_rsdt.asl
new file mode 100644
index 0000000..6cf4c7a
--- /dev/null
+++ b/libs/acpi/test_data/basic_rsdt.asl
@@ -0,0 +1,26 @@
+/*
+ * Intel ACPI Component Architecture
+ * iASL Compiler/Disassembler version 20251212 (64-bit version)
+ * Copyright (c) 2000 - 2025 Intel Corporation
+ *
+ * Template for [RSDT] ACPI Table (static data table)
+ * Format: [ByteLength] FieldName : HexFieldValue
+ */
+[0004] Signature : "RSDT" [Root System Description Table]
+[0004] Table Length : 00000000
+[0001] Revision : 01
+[0001] Checksum : 00
+[0006] Oem ID : "INTEL "
+[0008] Oem Table ID : "TEMPLATE"
+[0004] Oem Revision : 00000001
+[0004] Asl Compiler ID : "INTL"
+[0004] Asl Compiler Revision : 20100528
+
+[0004] ACPI Table Address 0 : 00000010
+[0004] ACPI Table Address 1 : 00000020
+[0004] ACPI Table Address 2 : 00000030
+[0004] ACPI Table Address 3 : 00000040
+[0004] ACPI Table Address 4 : 00000050
+[0004] ACPI Table Address 5 : 00000060
+[0004] ACPI Table Address 6 : 00000070
+[0004] ACPI Table Address 7 : 00000080
diff --git a/libs/acpi/test_data/basic_xsdt.asl b/libs/acpi/test_data/basic_xsdt.asl
new file mode 100644
index 0000000..d8589f9
--- /dev/null
+++ b/libs/acpi/test_data/basic_xsdt.asl
@@ -0,0 +1,26 @@
+/*
+ * Intel ACPI Component Architecture
+ * iASL Compiler/Disassembler version 20251212 (64-bit version)
+ * Copyright (c) 2000 - 2025 Intel Corporation
+ *
+ * Template for [XSDT] ACPI Table (static data table)
+ * Format: [ByteLength] FieldName : HexFieldValue
+ */
+[0004] Signature : "XSDT" [Extended System Description Table]
+[0004] Table Length : 00000064
+[0001] Revision : 01
+[0001] Checksum : 8B
+[0006] Oem ID : "INTEL "
+[0008] Oem Table ID : "TEMPLATE"
+[0004] Oem Revision : 00000001
+[0004] Asl Compiler ID : "INTL"
+[0004] Asl Compiler Revision : 20100528
+
+[0008] ACPI Table Address 0 : 0000000000000010
+[0008] ACPI Table Address 1 : 0000000000000020
+[0008] ACPI Table Address 2 : 0000000000000030
+[0008] ACPI Table Address 3 : 0000000000000040
+[0008] ACPI Table Address 4 : 0000000000000050
+[0008] ACPI Table Address 5 : 0000000000000060
+[0008] ACPI Table Address 6 : 0000000000000070
+[0008] ACPI Table Address 7 : 0000000000000080
diff --git a/libs/acpi/test_data/table_header.asl b/libs/acpi/test_data/table_header.asl
new file mode 100644
index 0000000..8cddc03
--- /dev/null
+++ b/libs/acpi/test_data/table_header.asl
@@ -0,0 +1,9 @@
+[0004] Signature : "TEST"
+[0004] Table Length : 00000000
+[0001] Revision : 01
+[0001] Checksum : 00
+[0006] Oem ID : "FEMO "
+[0008] Oem Table ID : "HDRTEST "
+[0004] Oem Revision : 00000001
+[0004] Asl Compiler ID : "INTL"
+[0004] Asl Compiler Revision : 00000000