aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@ost.ch>2026-04-16 10:17:19 +0200
committerFelix Morgner <felix.morgner@ost.ch>2026-04-16 10:17:19 +0200
commit3c210c07c60fbe9378cfb720847e8c1d3c763ead (patch)
treec83b1aa99dd7435a9fd033772e973ade637b464d
parent888471f23e7f07749b4bc9a2fa70992062b6e4d0 (diff)
parentf9dde928add359a1dff0db402dc1454e72aea633 (diff)
downloadteachos-3c210c07c60fbe9378cfb720847e8c1d3c763ead.tar.xz
teachos-3c210c07c60fbe9378cfb720847e8c1d3c763ead.zip
Merge branch 'fmorgner/develop-BA-FS26/acpi-refactor' into develop-BA-FS26
-rw-r--r--.vscode/settings.json7
-rw-r--r--arch/x86_64/kapi/cpu.cpp16
-rw-r--r--kapi/include/kapi/acpi.hpp2
-rw-r--r--kernel/include/kernel/acpi/manager.hpp6
-rw-r--r--kernel/kapi/acpi.cpp2
-rw-r--r--kernel/src/acpi/manager.cpp48
-rw-r--r--libs/acpi/CMakeLists.txt15
-rw-r--r--libs/acpi/acpi/acpi.hpp12
-rw-r--r--libs/acpi/acpi/common/basic_table.hpp35
-rw-r--r--libs/acpi/acpi/common/checksum.cpp (renamed from libs/acpi/acpi/checksum.cpp)2
-rw-r--r--libs/acpi/acpi/common/checksum.hpp (renamed from libs/acpi/acpi/checksum.hpp)0
-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.cpp58
-rw-r--r--libs/acpi/acpi/common/table_signature.hpp17
-rw-r--r--libs/acpi/acpi/common/table_type.hpp (renamed from libs/acpi/acpi/table_type.hpp)4
-rw-r--r--libs/acpi/acpi/common/vla_table.hpp115
-rw-r--r--libs/acpi/acpi/data/madt.cpp112
-rw-r--r--libs/acpi/acpi/data/madt.hpp (renamed from libs/acpi/acpi/madt.hpp)57
-rw-r--r--libs/acpi/acpi/data/madt.test.cpp (renamed from libs/acpi/acpi/madt.test.cpp)6
-rw-r--r--libs/acpi/acpi/data/rsdt.cpp38
-rw-r--r--libs/acpi/acpi/data/rsdt.hpp42
-rw-r--r--libs/acpi/acpi/data/rsdt.test.cpp46
-rw-r--r--libs/acpi/acpi/data/xsdt.cpp38
-rw-r--r--libs/acpi/acpi/data/xsdt.hpp42
-rw-r--r--libs/acpi/acpi/data/xsdt.test.cpp46
-rw-r--r--libs/acpi/acpi/madt.cpp74
-rw-r--r--libs/acpi/acpi/pointers.cpp2
-rw-r--r--libs/acpi/acpi/sdt.cpp58
-rw-r--r--libs/acpi/acpi/sdt.hpp127
-rw-r--r--libs/acpi/test_data/basic_xsdt.asl26
-rw-r--r--libs/acpi/test_data/table_header.asl9
-rw-r--r--libs/acpi/test_data/tables.S2
-rw-r--r--libs/acpi/test_data/tables.hpp2
34 files changed, 905 insertions, 347 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
index bebda51..80844ee 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -12,6 +12,10 @@
"--clang-tidy",
"--header-insertion=iwyu"
],
+ "explorer.fileNesting.enabled": true,
+ "explorer.fileNesting.patterns": {
+ "*.hpp": "${capture}.cpp, ${capture}.test.cpp"
+ },
"files.associations": {
"**/kstd/include/kstd/**": "cpp",
},
@@ -32,6 +36,7 @@
"crtc",
"crtp",
"efer",
+ "FACS",
"functors",
"hhdm",
"idtr",
@@ -75,5 +80,5 @@
"env": "${envObject}",
"environment": "${envObjArray}",
"sourceFileMap": "${sourceFileMapObj}"
- }
+ },
} \ No newline at end of file
diff --git a/arch/x86_64/kapi/cpu.cpp b/arch/x86_64/kapi/cpu.cpp
index 22936c2..965998c 100644
--- a/arch/x86_64/kapi/cpu.cpp
+++ b/arch/x86_64/kapi/cpu.cpp
@@ -23,8 +23,8 @@ namespace kapi::cpu
namespace
{
- constexpr auto candidate_flags = ::acpi::processor_local_apic::flags::processor_enabled //
- | ::acpi::processor_local_apic::flags::online_capable;
+ constexpr auto candidate_flags = ::acpi::processor_local_apic_entry::flags::processor_enabled //
+ | ::acpi::processor_local_apic_entry::flags::online_capable;
}
auto init() -> void
@@ -52,7 +52,7 @@ namespace kapi::cpu
auto static const core_major = kapi::devices::allocate_major_number();
auto static const interrupt_controller_major = kapi::devices::allocate_major_number();
- auto madt = kapi::acpi::get_table<::acpi::madt_table_signature>();
+ auto madt = kapi::acpi::get_table<::acpi::table_signature_v<::acpi::madt>>();
if (!madt)
{
kstd::println("[x86_64:PLT] Failed to find ACPI APIC table");
@@ -62,10 +62,8 @@ namespace kapi::cpu
auto lapic_entries = *madt | std::views::filter([](auto const & entry) {
return entry.type() == ::acpi::madt_entry::type::processor_local_apic;
}) | std::views::transform([](auto const & entry) {
- return static_cast<::acpi::processor_local_apic const &>(entry);
- }) | std::views::filter([](auto const & entry) {
- return static_cast<bool>(entry.active_flags() & candidate_flags);
- });
+ return static_cast<::acpi::processor_local_apic_entry const &>(entry);
+ }) | std::views::filter([](auto const & entry) { return static_cast<bool>(entry.flags() & candidate_flags); });
auto bsp_found = false;
auto core_count = 0uz;
@@ -77,8 +75,8 @@ namespace kapi::cpu
auto is_bsp = !bsp_found;
bsp_found = true;
auto core = kstd::make_unique<devices::cpu::core>(core_major, core_count, apic.processor_id(), is_bsp);
- core->add_child(kstd::make_unique<arch::devices::local_apic>(interrupt_controller_major, core_count,
- apic.apic_id(), local_apic_address, is_bsp));
+ core->add_child(kstd::make_unique<arch::devices::local_apic>(interrupt_controller_major, core_count, apic.id(),
+ local_apic_address, is_bsp));
cpu_bus->add_child(std::move(core));
++core_count;
}
diff --git a/kapi/include/kapi/acpi.hpp b/kapi/include/kapi/acpi.hpp
index 2835496..b607ee0 100644
--- a/kapi/include/kapi/acpi.hpp
+++ b/kapi/include/kapi/acpi.hpp
@@ -23,7 +23,7 @@ namespace kapi::acpi
//!
//! @param signature The signature of the table to get.
//! @return A pointer to the table if found, nullptr otherwise.
- auto get_table(std::string_view signature) -> kstd::observer_ptr<::acpi::sdt const>;
+ auto get_table(std::string_view signature) -> kstd::observer_ptr<::acpi::table_header const>;
//! Get a type-cast pointer to an ACPI table by its signature.
//!
diff --git a/kernel/include/kernel/acpi/manager.hpp b/kernel/include/kernel/acpi/manager.hpp
index 420b44a..860d609 100644
--- a/kernel/include/kernel/acpi/manager.hpp
+++ b/kernel/include/kernel/acpi/manager.hpp
@@ -18,12 +18,12 @@ namespace kernel::acpi
auto load_tables() -> bool;
- auto get_table(std::string_view signature) -> kstd::observer_ptr<::acpi::sdt const>;
+ auto get_table(std::string_view signature) -> kstd::observer_ptr<::acpi::table_header const>;
private:
::acpi::rsdp const * m_sdp{};
- ::acpi::sdt const * m_rsdt{};
- kstd::flat_map<std::string_view, ::acpi::sdt const *> m_tables{};
+ ::acpi::table_header const * m_rsdt{};
+ kstd::flat_map<std::string_view, ::acpi::table_header const *> m_tables{};
bool m_extended{};
};
diff --git a/kernel/kapi/acpi.cpp b/kernel/kapi/acpi.cpp
index df2bf05..5a2f227 100644
--- a/kernel/kapi/acpi.cpp
+++ b/kernel/kapi/acpi.cpp
@@ -32,7 +32,7 @@ namespace kapi::acpi
return manager->load_tables();
}
- auto get_table(std::string_view signature) -> kstd::observer_ptr<::acpi::sdt const>
+ auto get_table(std::string_view signature) -> kstd::observer_ptr<::acpi::table_header const>
{
return manager->get_table(signature);
}
diff --git a/kernel/src/acpi/manager.cpp b/kernel/src/acpi/manager.cpp
index 501ce92..5876799 100644
--- a/kernel/src/acpi/manager.cpp
+++ b/kernel/src/acpi/manager.cpp
@@ -8,8 +8,10 @@
#include <acpi/acpi.hpp>
+#include <algorithm>
#include <cstddef>
#include <cstdint>
+#include <ranges>
#include <string_view>
namespace kernel::acpi
@@ -32,7 +34,7 @@ namespace kernel::acpi
}
auto physical_extended_table_address = kapi::memory::physical_address{xsdp->table_address()};
auto linear_extended_table_address = kapi::memory::hhdm_to_linear(physical_extended_table_address);
- m_rsdt = static_cast<::acpi::sdt const *>(linear_extended_table_address);
+ m_rsdt = static_cast<::acpi::xsdt const *>(linear_extended_table_address);
m_extended = true;
}
else
@@ -43,7 +45,7 @@ namespace kernel::acpi
}
auto physical_root_table_address = kapi::memory::physical_address{m_sdp->table_address()};
auto linear_root_table_address = kapi::memory::hhdm_to_linear(physical_root_table_address);
- m_rsdt = static_cast<::acpi::sdt const *>(linear_root_table_address);
+ m_rsdt = static_cast<::acpi::rsdt const *>(linear_root_table_address);
}
}
@@ -54,29 +56,12 @@ namespace kernel::acpi
kapi::system::panic("[OS:ACPI] Invalid RSDT checksum!");
}
- auto entry_size = m_extended ? sizeof(std::uint64_t) : sizeof(std::uint32_t);
- auto entry_count = (m_rsdt->length().value - sizeof(::acpi::sdt)) / entry_size;
- auto entries_base = reinterpret_cast<std::byte const *>(m_rsdt) + sizeof(::acpi::sdt);
+ auto check_and_register_table = [&](auto table_address) -> void {
+ auto physical_table_address = kapi::memory::physical_address{reinterpret_cast<std::uintptr_t>(table_address)};
+ auto mapped_table = kapi::memory::hhdm_to_linear(physical_table_address);
+ auto table = static_cast<::acpi::table_header const *>(mapped_table);
- for (std::size_t i = 0; i < entry_count; ++i)
- {
- auto physical_table_address = kapi::memory::physical_address{};
-
- if (m_extended)
- {
- auto entry = reinterpret_cast<std::uint64_t const *>(entries_base + (i * entry_size));
- physical_table_address = kapi::memory::physical_address{*entry};
- }
- else
- {
- auto entry = reinterpret_cast<std::uint32_t const *>(entries_base + (i * entry_size));
- physical_table_address = kapi::memory::physical_address{*entry};
- }
-
- auto linear_table_address = kapi::memory::hhdm_to_linear(physical_table_address);
- auto table = static_cast<::acpi::sdt const *>(linear_table_address);
-
- if (!::acpi::validate_checksum({reinterpret_cast<std::byte const *>(table), table->length().value}))
+ if (!::acpi::validate_checksum({static_cast<std::byte const *>(mapped_table), table->length().value}))
{
kstd::println(kstd::print_sink::stderr, "[OS:ACPI] Invalid table checksum!");
}
@@ -85,12 +70,25 @@ namespace kernel::acpi
kstd::println("[OS:ACPI] Found '{}' ACPI table", table->signature());
m_tables.emplace(table->signature(), table);
}
+ };
+
+ if (m_extended)
+ {
+ auto xsdt = static_cast<::acpi::xsdt const *>(m_rsdt);
+ std::ranges::for_each(*xsdt | std::views::transform([](auto const & entry) { return entry.address(); }),
+ check_and_register_table);
+ }
+ else
+ {
+ auto rsdt = static_cast<::acpi::rsdt const *>(m_rsdt);
+ std::ranges::for_each(*rsdt | std::views::transform([](auto const & entry) { return entry.address(); }),
+ check_and_register_table);
}
return !m_tables.empty();
}
- auto manager::get_table(std::string_view signature) -> kstd::observer_ptr<::acpi::sdt const>
+ auto manager::get_table(std::string_view signature) -> kstd::observer_ptr<::acpi::table_header const>
{
if (m_tables.contains(signature))
{
diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt
index 30e1aca..55d5b54 100644
--- a/libs/acpi/CMakeLists.txt
+++ b/libs/acpi/CMakeLists.txt
@@ -19,10 +19,12 @@ file(GLOB_RECURSE ACPI_HEADERS
)
target_sources("acpi" PRIVATE
- "acpi/checksum.cpp"
- "acpi/madt.cpp"
+ "acpi/common/checksum.cpp"
+ "acpi/common/table_header.cpp"
+ "acpi/data/madt.cpp"
+ "acpi/data/rsdt.cpp"
+ "acpi/data/xsdt.cpp"
"acpi/pointers.cpp"
- "acpi/sdt.cpp"
)
target_sources("acpi" PUBLIC
@@ -43,6 +45,8 @@ if(NOT CMAKE_CROSSCOMPILING)
"basic_madt"
"basic_rsdt"
"basic_rsdp"
+ "basic_xsdt"
+ "table_header"
)
foreach(TABLE IN LISTS TEST_TABLES)
@@ -58,7 +62,10 @@ if(NOT CMAKE_CROSSCOMPILING)
set_source_files_properties("test_data/tables.S" PROPERTIES OBJECT_DEPENDS "${GENERATED_TABLE_BLOBS}")
add_executable("acpi_tests"
- "acpi/madt.test.cpp"
+ "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"
"test_data/tables.S"
diff --git a/libs/acpi/acpi/acpi.hpp b/libs/acpi/acpi/acpi.hpp
index fb358cc..0da44a8 100644
--- a/libs/acpi/acpi/acpi.hpp
+++ b/libs/acpi/acpi/acpi.hpp
@@ -1,10 +1,12 @@
#ifndef ACPI_ACPI_HPP
#define ACPI_ACPI_HPP
-#include <acpi/checksum.hpp> // IWYU pragma: export
-#include <acpi/madt.hpp> // IWYU pragma: export
-#include <acpi/pointers.hpp> // IWYU pragma: export
-#include <acpi/sdt.hpp> // IWYU pragma: export
-#include <acpi/table_type.hpp> // IWYU pragma: export
+#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..33f23d5
--- /dev/null
+++ b/libs/acpi/acpi/common/basic_table.hpp
@@ -0,0 +1,35 @@
+#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();
+ }
+ };
+
+} // namespace acpi
+
+#endif \ No newline at end of file
diff --git a/libs/acpi/acpi/checksum.cpp b/libs/acpi/acpi/common/checksum.cpp
index 56275c8..09425dd 100644
--- a/libs/acpi/acpi/checksum.cpp
+++ b/libs/acpi/acpi/common/checksum.cpp
@@ -1,4 +1,4 @@
-#include <acpi/checksum.hpp>
+#include <acpi/common/checksum.hpp>
#include <algorithm>
#include <cstddef>
diff --git a/libs/acpi/acpi/checksum.hpp b/libs/acpi/acpi/common/checksum.hpp
index a92c242..a92c242 100644
--- a/libs/acpi/acpi/checksum.hpp
+++ b/libs/acpi/acpi/common/checksum.hpp
diff --git a/libs/acpi/acpi/common/table_header.cpp b/libs/acpi/acpi/common/table_header.cpp
new file mode 100644
index 0000000..c69ff5d
--- /dev/null
+++ b/libs/acpi/acpi/common/table_header.cpp
@@ -0,0 +1,130 @@
+#include <kstd/cstring>
+#include <kstd/units>
+
+#include <acpi/common/table_header.hpp>
+
+#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..d6976b3
--- /dev/null
+++ b/libs/acpi/acpi/common/table_header.test.cpp
@@ -0,0 +1,58 @@
+#include <kstd/units>
+
+#include <acpi/common/table_header.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <test_data/tables.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/table_type.hpp b/libs/acpi/acpi/common/table_type.hpp
index 7fdd7e3..bc427c7 100644
--- a/libs/acpi/acpi/table_type.hpp
+++ b/libs/acpi/acpi/common/table_type.hpp
@@ -1,5 +1,5 @@
-#ifndef ACPI_TABLE_TYPE_HPP
-#define ACPI_TABLE_TYPE_HPP
+#ifndef ACPI_COMMON_TABLE_TYPE_HPP
+#define ACPI_COMMON_TABLE_TYPE_HPP
// IWYU pragma: private, include <acpi/acpi.hpp>
diff --git a/libs/acpi/acpi/common/vla_table.hpp b/libs/acpi/acpi/common/vla_table.hpp
new file mode 100644
index 0000000..a65a28e
--- /dev/null
+++ b/libs/acpi/acpi/common/vla_table.hpp
@@ -0,0 +1,115 @@
+#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);