From 1fa31688a0e237dec1170dcea9e8f0a0571a25e5 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Wed, 15 Apr 2026 08:55:03 +0200 Subject: acpi: add basic MADT tests --- arch/x86_64/kapi/cpu.cpp | 2 +- libs/acpi/CMakeLists.txt | 34 +++++++++++++++++++++++ libs/acpi/acpi/madt.cpp | 10 +++++-- libs/acpi/acpi/madt.hpp | 38 +++++++++++++++++++++++--- libs/acpi/acpi/madt.test.cpp | 55 ++++++++++++++++++++++++++++++++++++++ libs/acpi/acpi/sdt.cpp | 7 +++++ libs/acpi/acpi/sdt.hpp | 8 ++++++ libs/acpi/test_data/basic_madt.asl | 29 ++++++++++++++++++++ libs/acpi/test_data/basic_rsdt.asl | 26 ++++++++++++++++++ libs/acpi/test_data/tables.S | 14 ++++++++++ libs/acpi/test_data/tables.hpp | 25 +++++++++++++++++ 11 files changed, 242 insertions(+), 6 deletions(-) create mode 100644 libs/acpi/acpi/madt.test.cpp create mode 100644 libs/acpi/test_data/basic_madt.asl create mode 100644 libs/acpi/test_data/basic_rsdt.asl create mode 100644 libs/acpi/test_data/tables.S create mode 100644 libs/acpi/test_data/tables.hpp diff --git a/arch/x86_64/kapi/cpu.cpp b/arch/x86_64/kapi/cpu.cpp index a836b20..22936c2 100644 --- a/arch/x86_64/kapi/cpu.cpp +++ b/arch/x86_64/kapi/cpu.cpp @@ -60,7 +60,7 @@ namespace kapi::cpu } auto lapic_entries = *madt | std::views::filter([](auto const & entry) { - return entry.type() == ::acpi::madt_entry::types::processor_local_apic; + 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) { diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index b8face4..8ace42d 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -1,3 +1,11 @@ +cmake_minimum_required(VERSION "3.27.0") + +project("acpi" + DESCRIPTION "An ACPI parsing library" + VERSION "0.0.1" + LANGUAGES ASM CXX +) + add_library("acpi" STATIC) add_library("libs::acpi" ALIAS "acpi") @@ -29,8 +37,34 @@ target_link_libraries("acpi" PUBLIC ) if(NOT CMAKE_CROSSCOMPILING) + find_program(IASL_EXE NAMES "iasl" REQUIRED) + + set(TEST_TABLES + "basic_madt" + "basic_rsdt" + ) + + foreach(TABLE IN LISTS TEST_TABLES) + add_custom_command(OUTPUT "test_data/${TABLE}.aml" + COMMAND "${IASL_EXE}" -p "test_data/${TABLE}.aml" "${CMAKE_CURRENT_SOURCE_DIR}/test_data/${TABLE}.asl" + 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("test_data/tables.S" PROPERTIES OBJECT_DEPENDS "${GENERATED_TABLE_BLOBS}") + add_executable("acpi_tests" + "acpi/madt.test.cpp" "acpi/pointers.test.cpp" + + "test_data/tables.S" + ) + + target_include_directories("acpi_tests" PRIVATE + "${CMAKE_CURRENT_BINARY_DIR}/test_data" ) target_link_libraries("acpi_tests" PRIVATE diff --git a/libs/acpi/acpi/madt.cpp b/libs/acpi/acpi/madt.cpp index 40289cf..6a62f07 100644 --- a/libs/acpi/acpi/madt.cpp +++ b/libs/acpi/acpi/madt.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -16,9 +17,14 @@ namespace acpi return m_flags; } - auto madt_entry::type() const noexcept -> types + auto madt::validate() const noexcept -> bool { - return static_cast(m_type); + return signature() == madt_table_signature && sdt::validate(); + } + + auto madt_entry::type() const noexcept -> enum type + { + return static_cast(m_type); } auto madt_entry::length() const noexcept -> std::size_t diff --git a/libs/acpi/acpi/madt.hpp b/libs/acpi/acpi/madt.hpp index f680430..8b75f58 100644 --- a/libs/acpi/acpi/madt.hpp +++ b/libs/acpi/acpi/madt.hpp @@ -4,6 +4,7 @@ // IWYU pragma: private, include #include +#include #include #include @@ -11,6 +12,7 @@ #include #include +#include #include namespace acpi @@ -18,7 +20,8 @@ namespace acpi struct [[gnu::packed]] madt_entry { - enum struct types : std::uint8_t + //! The type of an MADT entry. + enum struct type : std::uint8_t { processor_local_apic, io_apic, @@ -29,28 +32,55 @@ namespace acpi processor_local_x2_apic, }; - [[nodiscard]] auto type() const noexcept -> types; + //! 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 + [[nodiscard]] auto as() const -> EntryType const & + { + if (type() != EntryType::this_type) + { + kstd::os::panic("Invalid cast"); + } + return reinterpret_cast(*this); + } + private: std::uint8_t m_type; std::uint8_t m_length; }; - //! The common header for all + //! The Multiple APIC Description Table (MADT) struct [[gnu::packed]] madt : sdt { using iterator = sdt_iterator; using const_iterator = iterator; + //! 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; + [[nodiscard]] auto validate() const noexcept -> bool; [[nodiscard]] auto begin() const noexcept -> iterator; [[nodiscard]] auto cbegin() const noexcept -> iterator; [[nodiscard]] auto end() const noexcept -> iterator; [[nodiscard]] auto cend() const noexcept -> iterator; + template + [[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(); }); + } + private: std::uint32_t m_local_interrupt_controller_address; std::uint32_t m_flags; @@ -58,6 +88,8 @@ namespace acpi struct [[gnu::packed]] processor_local_apic : madt_entry { + constexpr auto static this_type = type::processor_local_apic; + enum struct flags : std::uint32_t { processor_enabled = 1, diff --git a/libs/acpi/acpi/madt.test.cpp b/libs/acpi/acpi/madt.test.cpp new file mode 100644 index 0000000..8926192 --- /dev/null +++ b/libs/acpi/acpi/madt.test.cpp @@ -0,0 +1,55 @@ +#include + +#include +#include +#include + +#include + +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(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() == kstd::type_size + kstd::type_size); + } + + 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()) == 1); + } + } + } +} diff --git a/libs/acpi/acpi/sdt.cpp b/libs/acpi/acpi/sdt.cpp index c2b9d68..6c6cb47 100644 --- a/libs/acpi/acpi/sdt.cpp +++ b/libs/acpi/acpi/sdt.cpp @@ -1,7 +1,9 @@ #include +#include #include +#include #include #include @@ -48,4 +50,9 @@ namespace acpi return {m_signature.data(), m_signature.size()}; } + auto sdt::validate() const noexcept -> bool + { + return acpi::validate_checksum({reinterpret_cast(this), length().value}); + } + } // namespace acpi diff --git a/libs/acpi/acpi/sdt.hpp b/libs/acpi/acpi/sdt.hpp index 9e5ec89..72b3896 100644 --- a/libs/acpi/acpi/sdt.hpp +++ b/libs/acpi/acpi/sdt.hpp @@ -52,6 +52,11 @@ namespace acpi return *m_entry; } + constexpr auto operator->() const noexcept -> pointer + { + return m_entry; + } + constexpr auto operator==(sdt_iterator const & other) const noexcept -> bool { return m_entry == other.m_entry || (is_end() && other.is_end()); @@ -94,6 +99,9 @@ namespace acpi //! Get the signature of this table. [[nodiscard]] auto signature() const noexcept -> std::string_view; + //! Valide this table's checksum + [[nodiscard]] auto validate() const noexcept -> bool; + private: std::array m_signature; std::uint32_t m_length; 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_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/tables.S b/libs/acpi/test_data/tables.S new file mode 100644 index 0000000..af58109 --- /dev/null +++ b/libs/acpi/test_data/tables.S @@ -0,0 +1,14 @@ +.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") + +#undef TABLE diff --git a/libs/acpi/test_data/tables.hpp b/libs/acpi/test_data/tables.hpp new file mode 100644 index 0000000..2b3874f --- /dev/null +++ b/libs/acpi/test_data/tables.hpp @@ -0,0 +1,25 @@ +#ifndef ACPI_TEST_DATA_TABLES_HPP +#define ACPI_TEST_DATA_TABLES_HPP + +#include +#include + +#define TABLE(name) \ + extern "C" std::byte const name##_start; \ + extern "C" std::byte const name##_end; \ + auto inline name()->std::span \ + { \ + return {&name##_start, &name##_end}; \ + } + +namespace acpi::test_data::tables +{ + + TABLE(basic_madt); + TABLE(basic_rsdt); + +} // namespace acpi::test_data::tables + +#undef TABLE + +#endif -- cgit v1.2.3