diff options
61 files changed, 2071 insertions, 650 deletions
@@ -11,6 +11,7 @@ Documentation: If: PathMatch: - "libs/.*/tests/.*\\.cpp" + - "libs/.*/.*\\.test\\.cpp" - "kernel/.*\\.tests.cpp" Diagnostics: ClangTidy: diff --git a/.devcontainer/x86-64/devcontainer.json b/.devcontainer/x86-64/devcontainer.json index 6bf1616..775da72 100644 --- a/.devcontainer/x86-64/devcontainer.json +++ b/.devcontainer/x86-64/devcontainer.json @@ -5,7 +5,7 @@ "ghcr.io/devcontainers/features/git:1": {}, "ghcr.io/devcontainers/features/git-lfs:1": {}, "ghcr.io/devcontainers-extra/features/apt-packages:1": { - "packages": "build-essential,clang-tidy,clangd,cmake,grub2-common,grub-pc,mtools,ninja-build,qemu-system-x86,ssh,xorriso,gdb" + "packages": "acpica-tools,build-essential,clang-tidy,clangd,cmake,grub2-common,grub-pc,mtools,ninja-build,qemu-system-x86,ssh,xorriso,gdb" } }, "customizations": { 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 a836b20..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"); @@ -60,12 +60,10 @@ 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) { - 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/include/kernel/test_support/filesystem/storage_boot_module_fixture.hpp b/kernel/include/kernel/test_support/filesystem/storage_boot_module_fixture.hpp index ee658e2..4b49684 100644 --- a/kernel/include/kernel/test_support/filesystem/storage_boot_module_fixture.hpp +++ b/kernel/include/kernel/test_support/filesystem/storage_boot_module_fixture.hpp @@ -3,11 +3,10 @@ #include "kapi/boot_module/boot_module_registry.hpp" -#include <kstd/string> -#include <kstd/vector> - #include <cstddef> #include <filesystem> +#include <string> +#include <vector> namespace kernel::tests::filesystem { @@ -16,16 +15,33 @@ namespace kernel::tests::filesystem ~storage_boot_module_fixture(); auto setup_modules(std::size_t module_count, std::size_t module_size = 4096) -> void; - auto setup_modules_from_img(kstd::vector<kstd::string> const & module_names, - kstd::vector<std::filesystem::path> const & img_paths) -> void; + auto setup_modules_from_img(std::vector<std::string> const & module_names, + std::vector<std::filesystem::path> const & img_paths) -> void; protected: + struct mapped_image + { + explicit mapped_image(std::filesystem::path path); + ~mapped_image(); + + mapped_image(mapped_image const &) = delete; + auto operator=(mapped_image const &) -> mapped_image & = delete; + + mapped_image(mapped_image &&) noexcept; + auto operator=(mapped_image &&) noexcept -> mapped_image &; + + int file_descriptor; + std::byte * mapping; + std::size_t size; + }; + kapi::boot_modules::boot_module_registry m_registry{}; - kstd::vector<kstd::string> m_module_names{}; - kstd::vector<kstd::vector<std::byte>> m_module_data{}; + std::vector<std::string> m_module_names{}; + std::vector<std::vector<std::byte>> m_module_data{}; + std::vector<mapped_image> m_mapped_images{}; private: - auto setup_module_from_img(kstd::string const & module_name, std::filesystem::path const & img_path) -> void; + auto setup_module_from_img(std::string const & module_name, std::filesystem::path const & img_path) -> void; }; } // namespace kernel::tests::filesystem diff --git a/kernel/include/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp b/kernel/include/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp index 98012b0..bd1c289 100644 --- a/kernel/include/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp +++ b/kernel/include/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp @@ -3,11 +3,10 @@ #include "kernel/test_support/filesystem/storage_boot_module_fixture.hpp" -#include <kstd/string> -#include <kstd/vector> - #include <cstddef> #include <filesystem> +#include <string> +#include <vector> namespace kernel::tests::filesystem { @@ -16,8 +15,8 @@ namespace kernel::tests::filesystem ~storage_boot_module_vfs_fixture(); auto setup_modules_and_init_vfs(std::size_t module_count, std::size_t module_size = 4096) -> void; - auto setup_modules_from_img_and_init_vfs(kstd::vector<kstd::string> const & module_names, - kstd::vector<std::filesystem::path> const & img_paths) -> void; + auto setup_modules_from_img_and_init_vfs(std::vector<std::string> const & module_names, + std::vector<std::filesystem::path> const & img_paths) -> void; }; } // namespace kernel::tests::filesystem 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/kstd/print.cpp b/kernel/kstd/print.cpp index beee2e5..7854027 100644 --- a/kernel/kstd/print.cpp +++ b/kernel/kstd/print.cpp @@ -1,12 +1,13 @@ #include "kapi/cio.hpp" +#include <kstd/bits/format/output_buffer.hpp> #include <kstd/format> #include <kstd/os/print.hpp> #include <kstd/print> +#include <algorithm> #include <array> #include <cstddef> -#include <iterator> #include <string_view> namespace kstd::os @@ -14,7 +15,7 @@ namespace kstd::os namespace { - struct write_buffer + struct write_buffer final : kstd::bits::format::output_buffer { using output_stream = kapi::cio::output_stream; @@ -29,35 +30,36 @@ namespace kstd::os : m_stream{stream} {} - ~write_buffer() noexcept + ~write_buffer() noexcept final { flush(); } - auto flush() noexcept -> void + auto push(std::string_view text) -> void final { - if (m_position > 0) + std::ranges::for_each(text, [this](auto c) { this->push(c); }); + } + + auto push(char character) -> void final + { + if (m_position >= size) { - std::string_view chunk{m_buffer.data(), m_position}; - kapi::cio::write(m_stream, chunk); - m_position = 0; + flush(); } + m_buffer.at(m_position++) = character; } - auto static callback(void * object, std::string_view text) -> void + private: + auto flush() noexcept -> void { - auto * self = static_cast<write_buffer *>(object); - for (char const character : text) + if (m_position > 0) { - if (self->m_position >= size) - { - self->flush(); - } - self->m_buffer.at(self->m_position++) = character; + std::string_view chunk{m_buffer.data(), m_position}; + kapi::cio::write(m_stream, chunk); + m_position = 0; } } - private: output_stream m_stream; std::array<char, size> m_buffer{}; std::size_t m_position{}; @@ -69,184 +71,7 @@ namespace kstd::os { auto writer = write_buffer{(sink == print_sink::stderr) ? kapi::cio::output_stream::stderr : kapi::cio::output_stream::stdout}; - auto context = kstd::format_context{.writer = write_buffer::callback, .user_data = &writer, .args = args}; - auto parse_context = kstd::format_parse_context{format, args.size()}; - - auto it = parse_context.begin(); - auto end = parse_context.end(); - - while (it != end) - { - if (*it != '{' && *it != '}') - { - auto start = it; - while (it != end && *it != '{' && *it != '}') - { - std::advance(it, 1); - } - parse_context.advance_to(it); - context.push(std::string_view(start, it - start)); - continue; - } - - if (*it == '{') - { - std::advance(it, 1); - if (it != end && *it == '{') - { - context.push('{'); - std::advance(it, 1); - parse_context.advance_to(it); - continue; - } - - parse_context.advance_to(it); - auto index = 0uz; - - if (it != end && *it >= '0' && *it <= '9') - { - while (it != end && *it >= '0' && *it <= '9') - { - index = index * 10 + static_cast<std::size_t>(*it - '0'); - std::advance(it, 1); - } - parse_context.check_arg_id(index); - } - else - { - index = parse_context.next_arg_id(); - } - - if (it != end && *it == ':') - { - std::advance(it, 1); - } - - parse_context.advance_to(it); - - if (index < args.size()) - { - auto const & arg = args[index]; - switch (arg.type) - { - case kstd::bits::format::arg_type::boolean: - { - auto fmt = kstd::formatter<bool>{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.boolean, context); - break; - } - case kstd::bits::format::arg_type::character: - { - auto fmt = kstd::formatter<char>{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.character, context); - break; - } - case kstd::bits::format::arg_type::integer: - { - auto fmt = kstd::formatter<long long>{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.integer, context); - break; - } - case kstd::bits::format::arg_type::unsigned_integer: - { - auto fmt = kstd::formatter<unsigned long long>{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.unsigned_integer, context); - break; - } - case kstd::bits::format::arg_type::string_view: - { - auto fmt = kstd::formatter<std::string_view>{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.string_view, context); - break; - } - case kstd::bits::format::arg_type::c_string: - { - auto fmt = kstd::formatter<char const *>{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.c_string, context); - break; - } - case kstd::bits::format::arg_type::pointer: - { - auto fmt = kstd::formatter<void const *>{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.pointer, context); - break; - } - case kstd::bits::format::arg_type::user_defined: - { - if (arg.value.user_defined.format) - { - arg.value.user_defined.format(arg.value.user_defined.pointer, parse_context, context); - } - else - { - context.push("{?}"); - } - break; - } - default: - { - context.push("{fmt-err: unknown-type}"); - break; - } - } - } - else - { - context.push("{fmt-err: bound}"); - } - - it = parse_context.begin(); - - if (it != end && *it == '}') - { - std::advance(it, 1); - parse_context.advance_to(it); - } - else - { - context.push("{fmt-err: unconsumed}"); - while (it != end && *it != '}') - { - std::advance(it, 1); - } - - if (it != end) - { - std::advance(it, 1); - parse_context.advance_to(it); - } - } - } - else if (*it == '}') - { - std::advance(it, 1); - if (it != end && *it == '}') - { - context.push('}'); - std::advance(it, 1); - parse_context.advance_to(it); - } - else - { - context.push("{fmt-err: unescaped}"); - parse_context.advance_to(it); - } - } - } + kstd::bits::format::vformat_to(writer, format, args); } } // namespace kstd::os 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/kernel/src/test_support/filesystem/storage_boot_module_fixture.cpp b/kernel/src/test_support/filesystem/storage_boot_module_fixture.cpp index a139f63..cd8360b 100644 --- a/kernel/src/test_support/filesystem/storage_boot_module_fixture.cpp +++ b/kernel/src/test_support/filesystem/storage_boot_module_fixture.cpp @@ -8,17 +8,72 @@ #include "kernel/test_support/boot_modules.hpp" #include "kernel/test_support/devices/storage/management.hpp" -#include <kstd/string> -#include <kstd/vector> - #include <cstddef> +#include <fcntl.h> #include <filesystem> -#include <fstream> -#include <ios> +#include <format> #include <stdexcept> +#include <string> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> +#include <utility> +#include <vector> namespace kernel::tests::filesystem { + storage_boot_module_fixture::mapped_image::mapped_image(std::filesystem::path path) + : file_descriptor(::open(path.c_str(), O_RDWR)) + { + if (file_descriptor < 0) + { + throw std::runtime_error{"Failed to open image file for test boot module: " + path.string()}; + } + + struct stat statistics{}; + if (::fstat(file_descriptor, &statistics) < 0) + { + throw std::runtime_error{"Failed to get statistics for image file: " + path.string()}; + } + + size = static_cast<std::size_t>(statistics.st_size); + + mapping = static_cast<std::byte *>(::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, file_descriptor, 0)); + if (mapping == MAP_FAILED) + { + throw std::runtime_error{"Failed to map image file for test boot module: " + path.string()}; + } + } + + storage_boot_module_fixture::mapped_image::~mapped_image() + { + if (mapping != nullptr) + { + ::munmap(mapping, size); + } + if (file_descriptor >= 0) + { + ::close(file_descriptor); + } + } + + storage_boot_module_fixture::mapped_image::mapped_image(mapped_image && other) noexcept + : file_descriptor{std::exchange(other.file_descriptor, -1)} + , mapping{std::exchange(other.mapping, nullptr)} + , size{std::exchange(other.size, 0)} + {} + + auto storage_boot_module_fixture::mapped_image::operator=(mapped_image && other) noexcept -> mapped_image & + { + if (this != &other) + { + file_descriptor = std::exchange(other.file_descriptor, -1); + mapping = std::exchange(other.mapping, nullptr); + size = std::exchange(other.size, 0); + } + return *this; + } + storage_boot_module_fixture::~storage_boot_module_fixture() { kernel::tests::devices::storage::management::deinit(); @@ -36,23 +91,22 @@ namespace kernel::tests::filesystem for (std::size_t i = 0; i < module_count; ++i) { - m_module_names.push_back("test_mod" + kstd::to_string(i)); + m_module_names.push_back(std::format("test_mod{}", i)); m_module_data.emplace_back(module_size, std::byte{static_cast<unsigned char>(0x40 + (i % 16))}); } for (std::size_t i = 0; i < module_count; ++i) { m_registry.add_boot_module(kapi::boot_modules::boot_module{ - m_module_names[i].view(), kapi::memory::linear_address{m_module_data[i].data()}, m_module_data[i].size()}); + m_module_names[i].c_str(), kapi::memory::linear_address{m_module_data[i].data()}, m_module_data[i].size()}); } kapi::boot_modules::set_boot_module_registry(m_registry); kernel::devices::storage::management::init(); } - auto storage_boot_module_fixture::setup_modules_from_img(kstd::vector<kstd::string> const & module_names, - kstd::vector<std::filesystem::path> const & img_paths) - -> void + auto storage_boot_module_fixture::setup_modules_from_img(std::vector<std::string> const & module_names, + std::vector<std::filesystem::path> const & img_paths) -> void { m_module_names.clear(); m_module_data.clear(); @@ -72,38 +126,13 @@ namespace kernel::tests::filesystem kernel::devices::storage::management::init(); } - auto storage_boot_module_fixture::setup_module_from_img(kstd::string const & module_name, + auto storage_boot_module_fixture::setup_module_from_img(std::string const & module_name, std::filesystem::path const & img_path) -> void { - auto file = std::ifstream{img_path, std::ios::binary | std::ios::ate}; - if (!file) - { - throw std::runtime_error{"Failed to open image file for test boot module: " + img_path.string()}; - } - - auto const end_pos = file.tellg(); - if (end_pos < 0) - { - throw std::runtime_error{"Failed to determine image file size for test boot module: " + img_path.string()}; - } - - auto const size = static_cast<std::size_t>(end_pos); - file.seekg(0, std::ios::beg); - m_module_names.push_back(module_name); - m_module_data.emplace_back(size); - - if (size > 0) - { - file.read(reinterpret_cast<char *>(m_module_data.back().data()), static_cast<std::streamsize>(size)); - if (!file) - { - throw std::runtime_error{"Failed to read image file content for test boot module: " + img_path.string()}; - } - } + auto & mapped_image = m_mapped_images.emplace_back(img_path); m_registry.add_boot_module(kapi::boot_modules::boot_module{ - m_module_names.back().view(), kapi::memory::linear_address{m_module_data.back().data()}, - m_module_data.back().size()}); + m_module_names.back().c_str(), kapi::memory::linear_address{mapped_image.mapping}, mapped_image.size}); } } // namespace kernel::tests::filesystem
\ No newline at end of file diff --git a/kernel/src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp b/kernel/src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp index cba7278..1a71b57 100644 --- a/kernel/src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp +++ b/kernel/src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp @@ -3,11 +3,10 @@ #include "kernel/filesystem/vfs.hpp" #include "kernel/test_support/filesystem/vfs.hpp" -#include <kstd/string> -#include <kstd/vector> - #include <cstddef> #include <filesystem> +#include <string> +#include <vector> namespace kernel::tests::filesystem { @@ -24,7 +23,7 @@ namespace kernel::tests::filesystem } auto storage_boot_module_vfs_fixture::setup_modules_from_img_and_init_vfs( - kstd::vector<kstd::string> const & module_names, kstd::vector<std::filesystem::path> const & img_paths) -> void + std::vector<std::string> const & module_names, std::vector<std::filesystem::path> const & img_paths) -> void { setup_modules_from_img(module_names, img_paths); kernel::filesystem::vfs::init(); diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index c6e63b9..55d5b54 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") @@ -11,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 @@ -27,3 +37,54 @@ target_sources("acpi" PUBLIC target_link_libraries("acpi" PUBLIC "libs::kstd" ) + +if(NOT CMAKE_CROSSCOMPILING) + 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 "${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/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" + ) + + target_include_directories("acpi_tests" PRIVATE + "${CMAKE_CURRENT_BINARY_DIR}/test_data" + ) + + target_link_libraries("acpi_tests" PRIVATE + "Catch2::Catch2WithMain" + "libs::acpi" + ) + + set_target_properties("acpi_tests" PROPERTIES + C_CLANG_TIDY "" + CXX_CLANG_TIDY "" + EXCLUDE_FROM_ALL NO + ) + + catch_discover_tests("acpi_tests") +endif() 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); + 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(); + } + }; +} // 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..a8d4741 --- /dev/null +++ b/libs/acpi/acpi/data/madt.cpp @@ -0,0 +1,112 @@ +#include <kstd/cstring> + +#include <acpi/data/madt.hpp> + +#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..8307826 --- /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 <kstd/ext/bitfield_enum> +#include <kstd/os/error.hpp> +#include <kstd/units> + +#include <acpi/common/table_signature.hpp> +#include <acpi/common/table_type.hpp> +#include <acpi/common/vla_table.hpp> + +#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..6795499 --- /dev/null +++ b/libs/acpi/acpi/data/madt.test.cpp @@ -0,0 +1,55 @@ +#include <kstd/units> + +#include <acpi/data/madt.hpp> +#include <catch2/catch_test_macros.hpp> +#include <test_data/tables.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..fe108c7 --- /dev/null +++ b/libs/acpi/acpi/data/rsdt.cpp @@ -0,0 +1,38 @@ +#include <kstd/cstring> + +#include <acpi/common/table_header.hpp> +#include <acpi/data/rsdt.hpp> + +#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..937dce0 --- /dev/null +++ b/libs/acpi/acpi/data/rsdt.test.cpp @@ -0,0 +1,46 @@ +#include <kstd/units> + +#include <acpi/acpi.hpp> +#include <acpi/data/rsdt.hpp> +#include <catch2/catch_test_macros.hpp> +#include <test_data/tables.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..b4202b8 --- /dev/null +++ b/libs/acpi/acpi/data/xsdt.cpp @@ -0,0 +1,38 @@ +#include <kstd/cstring> + +#include <acpi/common/table_header.hpp> +#include <acpi/data/xsdt.hpp> + +#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..7fb564c --- /dev/null +++ b/libs/acpi/acpi/data/xsdt.test.cpp @@ -0,0 +1,46 @@ +#include <kstd/units> + +#include <acpi/acpi.hpp> +#include <acpi/data/xsdt.hpp> +#include <catch2/catch_test_macros.hpp> +#include <test_data/tables.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/madt.cpp b/libs/acpi/acpi/madt.cpp deleted file mode 100644 index 40289cf..0000000 --- a/libs/acpi/acpi/madt.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include <acpi/madt.hpp> - -#include <cstddef> -#include <cstdint> - -namespace acpi -{ - - auto madt::local_interrupt_controller_address() const noexcept -> std::uintptr_t - { - return static_cast<std::uintptr_t>(m_local_interrupt_controller_address); - } - - auto madt::flags() const noexcept -> std::uint32_t - { - return m_flags; - } - - auto madt_entry::type() const noexcept -> types - { - return static_cast<types>(m_type); - } - - auto madt_entry::length() const noexcept -> std::size_t - { - return m_length; - } - - auto processor_local_apic::apic_id() const noexcept -> std::uint8_t - { - return m_apic_id; - } - - auto processor_local_apic::active_flags() const noexcept -> flags - { - return static_cast<flags>(m_flags); - } - - auto processor_local_apic::processor_id() const noexcept -> std::uint32_t - { - return m_processor_id; - } - - auto madt::begin() const noexcept -> iterator - { - auto base = reinterpret_cast<std::byte const *>(this); - base += sizeof(madt); - auto limit = reinterpret_cast<std::byte const *>(this); - limit += length().value; - return iterator{reinterpret_cast<madt_entry const *>(base), reinterpret_cast<madt_entry const *>(limit)}; - } - - auto madt::cbegin() const noexcept -> iterator - { - return begin(); - } - - auto madt::end() const noexcept -> iterator - { - return {}; - } - - auto madt::cend() const noexcept -> iterator - { - return end(); - } - -} // namespace acpi diff --git a/libs/acpi/acpi/madt.hpp b/libs/acpi/acpi/madt.hpp deleted file mode 100644 index f680430..0000000 --- a/libs/acpi/acpi/madt.hpp +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef ACPI_ACPI_MADT_HPP -#define ACPI_ACPI_MADT_HPP - -// IWYU pragma: private, include <acpi/acpi.hpp> - -#include <kstd/ext/bitfield_enum> -#include <kstd/units> - -#include <acpi/sdt.hpp> -#include <acpi/table_type.hpp> - -#include <cstddef> -#include <cstdint> -#include <type_traits> - -namespace acpi -{ - - struct [[gnu::packed]] madt_entry - { - enum struct types : 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, - }; - - [[nodiscard]] auto type() const noexcept -> types; - [[nodiscard]] auto length() const noexcept -> std::size_t; - - private: - std::uint8_t m_type; - std::uint8_t m_length; - }; - - //! The common header for all - struct [[gnu::packed]] madt : sdt - { - using iterator = sdt_iterator<madt_entry const>; - using const_iterator = iterator; - - [[nodiscard]] auto local_interrupt_controller_address() const noexcept -> std::uintptr_t; - [[nodiscard]] auto flags() const noexcept -> std::uint32_t; - - [[nodiscard]] auto begin() const noexcept -> iterator; - [[nodiscard]] auto cbegin() const noexcept -> iterator; - [[nodiscard]] auto end() const noexcept -> iterator; - [[nodiscard]] auto cend() const noexcept -> iterator; - - private: - std::uint32_t m_local_interrupt_controller_address; - std::uint32_t m_flags; - }; - - struct [[gnu::packed]] processor_local_apic : madt_entry - { - enum struct flags : std::uint32_t - { - processor_enabled = 1, - online_capable = 2, - }; - - [[nodiscard]] auto apic_id() const noexcept -> std::uint8_t; - [[nodiscard]] auto active_flags() const noexcept -> flags; - [[nodiscard]] auto processor_id() const noexcept -> std::uint32_t; - - private: - std::uint8_t m_processor_id; - std::uint8_t m_apic_id; - std::uint32_t m_flags; - }; - - constexpr char const madt_table_signature[] = "APIC"; // NOLINT - - template<> - struct table_type<madt_table_signature> - { - using type = madt; - }; - -} // namespace acpi - -template<> -struct kstd::ext::is_bitfield_enum<acpi::processor_local_apic::flags> : std::true_type -{ -}; - -#endif diff --git a/libs/acpi/acpi/pointers.cpp b/libs/acpi/acpi/pointers.cpp index b206cc6..45a42ce 100644 --- a/libs/acpi/acpi/pointers.cpp +++ b/libs/acpi/acpi/pointers.cpp @@ -1,6 +1,6 @@ #include <kstd/units> -#include <acpi/checksum.hpp> +#include <acpi/common/checksum.hpp> #include <acpi/pointers.hpp> #include <bit> @@ -34,7 +34,7 @@ namespace acpi auto rsdp::validate() const noexcept -> bool { - return validate_checksum({reinterpret_cast<std::byte const *>(this), sizeof(rsdp)}); + return signature() == "RSD PTR " && validate_checksum({reinterpret_cast<std::byte const *>(this), sizeof(rsdp)}); } auto xsdp::length() const noexcept -> kstd::units::bytes @@ -49,7 +49,7 @@ namespace acpi auto xsdp::validate() const noexcept -> bool { - return validate_checksum({reinterpret_cast<std::byte const *>(this), m_length}); + return signature() == "RSD PTR " && validate_checksum({reinterpret_cast<std::byte const *>(this), m_length}); } } // namespace acpi diff --git a/libs/acpi/acpi/pointers.test.cpp b/libs/acpi/acpi/pointers.test.cpp new file mode 100644 index 0000000..06ce1a4 --- /dev/null +++ b/libs/acpi/acpi/pointers.test.cpp @@ -0,0 +1,29 @@ +#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/sdt.cpp b/libs/acpi/acpi/sdt.cpp deleted file mode 100644 index c2b9d68..0000000 --- a/libs/acpi/acpi/sdt.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include <kstd/units> - -#include <acpi/sdt.hpp> - -#include <cstdint> -#include <string_view> - -namespace acpi -{ - - auto sdt::creator_revision() const noexcept -> std::uint32_t - { - return m_creator_revision; - } - - auto sdt::creator_id() const noexcept -> std::uint32_t - { - return m_creator_id; - } - - auto sdt::length() const noexcept -> kstd::units::bytes - { - return kstd::units::bytes{m_length}; - } - - auto sdt::oem_id() const noexcept -> std::string_view - { - return {m_oem_id.data(), m_oem_id.size()}; - } - - auto sdt::oem_revision() const noexcept -> std::uint32_t - { - return m_oem_revision; - } - - auto sdt::oem_table_id() const noexcept -> std::string_view - { - return {m_oem_table_id.data(), m_oem_table_id.size()}; - } - - auto sdt::revision() const noexcept -> std::uint8_t - { - return m_revision; - } - - auto sdt::signature() const noexcept -> std::string_view - { - return {m_signature.data(), m_signature.size()}; - } - -} // namespace acpi diff --git a/libs/acpi/acpi/sdt.hpp b/libs/acpi/acpi/sdt.hpp deleted file mode 100644 index 9e5ec89..0000000 --- a/libs/acpi/acpi/sdt.hpp +++ /dev/null @@ -1,119 +0,0 @@ -#ifndef ACPI_SDT_HPP -#define ACPI_SDT_HPP - -// IWYU pragma: private, include <acpi/acpi.hpp> - -#include <kstd/units> - -#include <acpi/table_type.hpp> - -#include <array> -#include <cstddef> -#include <cstdint> -#include <iterator> -#include <string_view> - -namespace acpi -{ - - template<typename EntryType> - struct sdt_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 sdt_iterator() noexcept = default; - - constexpr sdt_iterator(pointer entry, pointer limit) noexcept - : m_entry{entry} - , m_limit{limit} - {} - - constexpr auto operator++() noexcept -> sdt_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 -> sdt_iterator - { - auto copy = *this; - ++*this; - return copy; - } - - constexpr auto operator*() const noexcept -> reference - { - return *m_entry; - } - - constexpr auto operator==(sdt_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{}; - }; - - //! The common base of all System Description Tables. - struct [[gnu::packed]] sdt - { - //! 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::uint32_t; - - //! 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<char, 4> m_signature; - std::uint32_t m_length; - std::uint8_t m_revision; - [[maybe_unused]] std::uint8_t m_checksum; - std::array<char, 6> m_oem_id; - std::array<char, 8> m_oem_table_id; - std::uint32_t m_oem_revision; - std::uint32_t m_creator_id; - std::uint32_t m_creator_revision; - }; - - constexpr char const rsdt_table_signature[] = "RSDT"; // NOLINT - - template<> - struct table_type<rsdt_table_signature> - { - using type = sdt; - }; - -} // namespace acpi - -#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 diff --git a/libs/acpi/test_data/tables.S b/libs/acpi/test_data/tables.S new file mode 100644 index 0000000..641db6a --- /dev/null +++ b/libs/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/test_data/tables.hpp b/libs/acpi/test_data/tables.hpp new file mode 100644 index 0000000..e91f1a5 --- /dev/null +++ b/libs/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/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 240118e..ced3138 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -21,6 +21,7 @@ target_sources("kstd" PRIVATE "src/os/error.cpp" "src/mutex.cpp" + "src/vformat.cpp" ) file(GLOB_RECURSE KSTD_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "include/kstd/*") @@ -43,6 +44,7 @@ if(CMAKE_CROSSCOMPILING) else() add_executable("kstd_tests" "tests/src/flat_map.cpp" + "tests/src/format.cpp" "tests/src/vector.cpp" "tests/src/observer_ptr.cpp" "tests/src/os_panic.cpp" diff --git a/libs/kstd/include/kstd/bits/format/context.hpp b/libs/kstd/include/kstd/bits/format/context.hpp index 478a48f..7f392a0 100644 --- a/libs/kstd/include/kstd/bits/format/context.hpp +++ b/libs/kstd/include/kstd/bits/format/context.hpp @@ -4,6 +4,7 @@ // IWYU pragma: private, include <kstd/format> #include "arg.hpp" +#include "kstd/bits/format/output_buffer.hpp" #include <kstd/os/error.hpp> @@ -29,13 +30,14 @@ namespace kstd struct format_context { - using writer_function = void(void *, std::string_view); using format_args = std::span<format_arg const>; - - writer_function * writer{}; - void * user_data{}; format_args args{}; + format_context(bits::format::output_buffer & buffer, format_args args) + : args{args} + , m_buffer{&buffer} + {} + [[nodiscard]] auto arg(std::size_t const id) const -> format_arg const & { if (id >= args.size()) @@ -47,13 +49,16 @@ namespace kstd constexpr auto push(std::string_view string) -> void { - writer(user_data, string); + m_buffer->push(string); } constexpr auto push(char character) -> void { - writer(user_data, std::string_view(&character, 1)); + m_buffer->push(character); } + + private: + bits::format::output_buffer * m_buffer; }; } // namespace kstd diff --git a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp index 336e1b0..e371cec 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp @@ -51,11 +51,11 @@ namespace kstd auto const text = value ? std::string_view{"true"} : std::string_view{"false"}; auto final_width = 0uz; - if (specifiers.width_mode == bits::format::width_mode::static_value) + if (specifiers.mode == bits::format::width_mode::static_value) { final_width = specifiers.width_value; } - else if (specifiers.width_mode == bits::format::width_mode::dynamic_argument_id) + else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) { auto const & arg = context.arg(specifiers.width_value); final_width = bits::format::extrat_dynamic_width(arg); diff --git a/libs/kstd/include/kstd/bits/format/formatter/char.hpp b/libs/kstd/include/kstd/bits/format/formatter/char.hpp new file mode 100644 index 0000000..ddfefe5 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter/char.hpp @@ -0,0 +1,94 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP +#define KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP + +#include "../context.hpp" +#include "../error.hpp" +#include "../formatter.hpp" +#include "../parse_context.hpp" +#include "../specifiers.hpp" +#include "integral.hpp" + +#include <iterator> + +namespace kstd +{ + + template<> + struct formatter<char> : formatter<unsigned char> + { + bits::format::specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + + auto it = context.begin(); + auto const end = context.end(); + + if (specifiers.alternative_form || specifiers.zero_pad || specifiers.sign != bits::format::sign_mode::none) + { + bits::format::error("Invalid format specifiers for 'char'"); + } + + if (it != end && *it != '}') + { + if (*it == 'c' || *it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X') + { + specifiers.type = *it; + std::advance(it, 1); + } + else + { + bits::format::error("Invalid type specifier for char."); + } + } + + if (it != end && *it != '}') + { + bits::format::error("Missing terminating '}' in format string."); + } + + return it; + } + + auto format(char value, format_context & context) const -> void + { + if (specifiers.type == '\0' || specifiers.type == 'c') + { + auto final_width = 0uz; + + if (specifiers.mode == bits::format::width_mode::static_value) + { + final_width = specifiers.width_value; + } + else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) + { + auto const & arg = context.arg(specifiers.width_value); + final_width = bits::format::extrat_dynamic_width(arg); + } + + auto padding = + bits::format::calculate_format_padding(final_width, 1, specifiers.align, bits::format::alignment::left); + + for (auto i = 0uz; i < padding.left; ++i) + { + context.push(specifiers.fill); + } + + context.push(value); + + for (auto i = 0uz; i < padding.right; ++i) + { + context.push(specifiers.fill); + } + } + else + { + formatter<unsigned char>::format(static_cast<unsigned char>(value), context); + } + } + }; + +} // namespace kstd + +#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp index 4912a44..e5a234a 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp @@ -64,11 +64,11 @@ namespace kstd auto format(T value, format_context & context) const -> void { auto final_width = 0uz; - if (specifiers.width_mode == bits::format::width_mode::static_value) + if (specifiers.mode == bits::format::width_mode::static_value) { final_width = specifiers.width_value; } - else if (specifiers.width_mode == bits::format::width_mode::dynamic_argument_id) + else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) { auto const & arg = context.arg(specifiers.width_value); final_width = bits::format::extrat_dynamic_width(arg); diff --git a/libs/kstd/include/kstd/bits/format/output_buffer.hpp b/libs/kstd/include/kstd/bits/format/output_buffer.hpp new file mode 100644 index 0000000..fd7a2b4 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/output_buffer.hpp @@ -0,0 +1,32 @@ +#ifndef KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP +#define KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP + +// IWYU pragma: private, include <kstd/format> + +#include <string_view> + +namespace kstd::bits::format +{ + + //! An abstract interface for formatted output buffers. + //! + //! This interface is intended to be use for functions dealing with string formatting, like the print and the format + //! family. + struct output_buffer + { + virtual ~output_buffer() = default; + + //! Push a text segment into the buffer. + //! + //! @param text The text segment to push. + virtual auto push(std::string_view text) -> void = 0; + + //! Push a single character into the buffer. + //! + //! @param character The character to push into the buffer. + virtual auto push(char character) -> void = 0; + }; + +} // namespace kstd::bits::format + +#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/specifiers.hpp b/libs/kstd/include/kstd/bits/format/specifiers.hpp index 9bc66c7..18c6f66 100644 --- a/libs/kstd/include/kstd/bits/format/specifiers.hpp +++ b/libs/kstd/include/kstd/bits/format/specifiers.hpp @@ -43,7 +43,7 @@ namespace kstd::bits::format bool alternative_form{}; bool zero_pad{}; - width_mode width_mode{}; + width_mode mode{}; std::size_t width_value{}; char type{}; }; @@ -133,7 +133,7 @@ namespace kstd::bits::format if (it != end && *it == '{') { - specs.width_mode = width_mode::dynamic_argument_id; + specs.mode = width_mode::dynamic_argument_id; std::advance(it, 1); auto argument_id = 0uz; @@ -160,7 +160,7 @@ namespace kstd::bits::format } else if (it != end && *it >= '0' && *it <= '9') { - specs.width_mode = width_mode::static_value; + specs.mode = width_mode::static_value; while (it != end && *it >= '0' && *it <= '9') { specs.width_value = specs.width_value * 10 + static_cast<std::size_t>(*it - '0'); diff --git a/libs/kstd/include/kstd/bits/format/vformat.hpp b/libs/kstd/include/kstd/bits/format/vformat.hpp new file mode 100644 index 0000000..4fec7dd --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/vformat.hpp @@ -0,0 +1,99 @@ +#ifndef KSTD_BITS_FORMAT_VFORMAT_HPP +#define KSTD_BITS_FORMAT_VFORMAT_HPP + +// IWYU pragma: private, include <kstd/format> + +#include <kstd/bits/format/args.hpp> +#include <kstd/bits/format/output_buffer.hpp> +#include <kstd/bits/format/string.hpp> +#include <kstd/string> + +#include <algorithm> +#include <iterator> +#include <string_view> +#include <type_traits> + +namespace kstd +{ + + namespace bits::format + { + //! Format a string with the given arguments into the given buffer. + //! + //! External implementations of `vprint` may call this function. + //! + //! @param buffer The buffer to format into. + //! @param format The format string to use. + //! @param args The arguments for the format string. + auto vformat_to(output_buffer & buffer, std::string_view format, format_args args) -> void; + + struct string_writer : output_buffer + { + auto push(std::string_view text) -> void override; + auto push(char character) -> void override; + + auto release() -> string &&; + + private: + string m_result{}; + }; + + template<std::output_iterator<char> Output> + struct iterator_writer : output_buffer + { + explicit iterator_writer(Output iterator) + : m_output{iterator} + {} + + auto iterator() const -> Output + { + return m_output; + } + + auto push(std::string_view text) -> void override + { + m_output = std::ranges::copy(text, m_output).out; + } + + auto push(char character) -> void override + { + *m_output++ = character; + } + + private: + Output m_output{}; + }; + + } // namespace bits::format + + //! Format a given string with the provided arguments. + //! + //! @param format The format string. + //! @param args The arguments for the format string. + //! @return A new string containing the result of the format operation. + template<typename... ArgumentTypes> + auto format(format_string<std::type_identity_t<ArgumentTypes>...> format, ArgumentTypes &&... args) -> string + { + auto buffer = bits::format::string_writer{}; + bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward<ArgumentTypes>(args)...).args); + return buffer.release(); + } + + //! Format a given string with the provided arguments. + //! + //! @param iterator The iterator to write to. + //! @param format The format string. + //! @param args The arguments for the format string. + //! @return An iterator past the last element written. + template<std::output_iterator<char> Output, typename... ArgumentTypes> + auto format_to(Output iterator, format_string<std::type_identity_t<ArgumentTypes>...> format, + ArgumentTypes &&... args) -> Output + { + auto buffer = bits::format::iterator_writer{iterator}; + bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward<ArgumentTypes>(args)...).args); + return buffer.iterator(); + } + +} // namespace kstd + +#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/flat_map b/libs/kstd/include/kstd/flat_map index 3b13754..f12b1b5 100644 --- a/libs/kstd/include/kstd/flat_map +++ b/libs/kstd/include/kstd/flat_map @@ -256,6 +256,33 @@ namespace kstd }; } + //! Try to insert a element for the given key into this map. + //! + //! This function does nothing if the key is already present. + //! + //! @param key The key to insert a value for. + //! @param args The arguments to use to construct the mapped value. + template<typename... Args> + auto try_emplace(key_type const & key, Args &&... args) -> std::pair<iterator, bool> + { + auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); + if (found != m_containers.keys.cend() && !m_comparator(*found, key) && !m_comparator(key, *found)) + { + return {found, false}; + } + + auto offset = std::distance(m_containers.keys.cbegin(), found); + auto intersertion_point = m_containers.value.begin() + offset; + + auto inserted_key = m_containers.keys.emplace(key); + auto inserted_mapped = m_containers.values.emplace(std::forward<Args>(args)...); + + return { + iterator{inserted_key, inserted_mapped}, + true + }; + } + //! Find an element with an equivalent key. //! //! @param key The key to look up. diff --git a/libs/kstd/include/kstd/format b/libs/kstd/include/kstd/format index adee5f8..047ea5c 100644 --- a/libs/kstd/include/kstd/format +++ b/libs/kstd/include/kstd/format @@ -7,13 +7,16 @@ #include "bits/format/formatter.hpp" // IWYU pragma: export #include "bits/format/formatter/bool.hpp" // IWYU pragma: export #include "bits/format/formatter/byte.hpp" // IWYU pragma: export +#include "bits/format/formatter/char.hpp" // IWYU pragma: export #include "bits/format/formatter/cstring.hpp" // IWYU pragma: export #include "bits/format/formatter/integral.hpp" // IWYU pragma: export #include "bits/format/formatter/ordering.hpp" // IWYU pragma: export #include "bits/format/formatter/pointer.hpp" // IWYU pragma: export #include "bits/format/formatter/range.hpp" // IWYU pragma: export #include "bits/format/formatter/string_view.hpp" // IWYU pragma: export +#include "bits/format/output_buffer.hpp" // IWYU pragma: export #include "bits/format/parse_context.hpp" // IWYU pragma: export #include "bits/format/string.hpp" // IWYU pragma: export +#include "bits/format/vformat.hpp" // IWYU pragma: export #endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/string b/libs/kstd/include/kstd/string index 4ce19ce..58c4a08 100644 --- a/libs/kstd/include/kstd/string +++ b/libs/kstd/include/kstd/string @@ -347,6 +347,26 @@ namespace kstd return !(lhs == rhs); } + [[nodiscard]] constexpr auto inline operator==(string const & lhs, char const * rhs) -> bool + { + return lhs.view() == std::string_view{rhs}; + } + + [[nodiscard]] constexpr auto inline operator!=(string const & lhs, char const * rhs) -> bool + { + return !(lhs == rhs); + } + + [[nodiscard]] constexpr auto inline operator==(char const * lhs, string const & rhs) -> bool + { + return std::string_view{lhs} == rhs.view(); + } + + [[nodiscard]] constexpr auto inline operator!=(char const * lhs, string const & rhs) -> bool + { + return !(lhs == rhs); + } + template<> struct formatter<string> : formatter<std::string_view> { diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index e51cbac..e1b8b38 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -111,7 +111,7 @@ namespace kstd allocator_type const & allocator = allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v<allocator_type>) : m_allocator{allocator} - , m_size{std::ranges::distance(first, last)} + , m_size{static_cast<std::size_t>(std::ranges::distance(first, last))} , m_capacity{m_size} , m_data{allocate_n(m_capacity)} { @@ -728,6 +728,45 @@ namespace kstd return begin() + prefix_size; } + template<typename... Args> + constexpr auto emplace(const_iterator position, Args &&... args) -> iterator + { + auto prefix_size = std::ranges::distance(begin(), position); + auto suffix_size = std::ranges::distance(position, end()); + + if (position == end()) + { + emplace_back(std::forward<Args>(args)...); + return begin() + prefix_size; + } + + if (m_capacity == m_size) + { + auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2; + auto new_data = allocate_n(new_capacity); + auto old_size = size(); + std::allocator_traits<allocator_type>::construct(m_allocator, new_data + prefix_size, + std::forward<Args>(args)...); + uninitialized_move_with_allocator(begin(), new_data, prefix_size); + uninitialized_move_with_allocator(begin() + prefix_size, new_data + prefix_size + 1, suffix_size); + destroy_n(begin(), old_size); + deallocate(); + m_data = new_data; + m_capacity = new_capacity; + m_size = old_size; + } + else + { + auto insert_position = begin() + prefix_size; + std::allocator_traits<allocator_type>::construct(m_allocator, end(), std::move(*(end() - 1))); + std::ranges::move_backward(insert_position, end() - 1, end()); + *insert_position = value_type{std::forward<Args>(args)...}; + } + + ++m_size; + return begin() + prefix_size; + } + //! Erase an element at a given position. //! //! @note This function will panic if position == end() diff --git a/libs/kstd/src/vformat.cpp b/libs/kstd/src/vformat.cpp new file mode 100644 index 0000000..b7c5121 --- /dev/null +++ b/libs/kstd/src/vformat.cpp @@ -0,0 +1,209 @@ +#include <kstd/format> +#include <kstd/string> + +#include <cstddef> +#include <iterator> +#include <string_view> +#include <utility> + +namespace kstd::bits::format +{ + + auto vformat_to(output_buffer & buffer, std::string_view format, format_args args) -> void + { + auto context = kstd::format_context{buffer, args}; + auto parse_context = kstd::format_parse_context{format, args.size()}; + + auto it = parse_context.begin(); + auto end = parse_context.end(); + + while (it != end) + { + if (*it != '{' && *it != '}') + { + auto start = it; + while (it != end && *it != '{' && *it != '}') + { + std::advance(it, 1); + } + parse_context.advance_to(it); + context.push(std::string_view(start, it - start)); + continue; + } + + if (*it == '{') + { + std::advance(it, 1); + if (it != end && *it == '{') + { + context.push('{'); + std::advance(it, 1); + parse_context.advance_to(it); + continue; + } + + parse_context.advance_to(it); + auto index = 0uz; + + if (it != end && *it >= '0' && *it <= '9') + { + while (it != end && *it >= '0' && *it <= '9') + { + index = index * 10 + static_cast<std::size_t>(*it - '0'); + std::advance(it, 1); + } + parse_context.check_arg_id(index); + } + else + { + index = parse_context.next_arg_id(); + } + + if (it != end && *it == ':') + { + std::advance(it, 1); + } + + parse_context.advance_to(it); + + if (index < args.size()) + { + auto const & arg = args[index]; + switch (arg.type) + { + case kstd::bits::format::arg_type::boolean: + { + auto fmt = kstd::formatter<bool>{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.boolean, context); + break; + } + case kstd::bits::format::arg_type::character: + { + auto fmt = kstd::formatter<char>{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.character, context); + break; + } + case kstd::bits::format::arg_type::integer: + { + auto fmt = kstd::formatter<long long>{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.integer, context); + break; + } + case kstd::bits::format::arg_type::unsigned_integer: + { + auto fmt = kstd::formatter<unsigned long long>{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.unsigned_integer, context); + break; + } + case kstd::bits::format::arg_type::string_view: + { + auto fmt = kstd::formatter<std::string_view>{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.string_view, context); + break; + } + case kstd::bits::format::arg_type::c_string: + { + auto fmt = kstd::formatter<char const *>{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.c_string, context); + break; + } + case kstd::bits::format::arg_type::pointer: + { + auto fmt = kstd::formatter<void const *>{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.pointer, context); + break; + } + case kstd::bits::format::arg_type::user_defined: + { + if (arg.value.user_defined.format) + { + arg.value.user_defined.format(arg.value.user_defined.pointer, parse_context, context); + } + else + { + context.push("{?}"); + } + break; + } + default: + { + context.push("{fmt-err: unknown-type}"); + break; + } + } + } + else + { + context.push("{fmt-err: bound}"); + } + + it = parse_context.begin(); + + if (it != end && *it == '}') + { + std::advance(it, 1); + parse_context.advance_to(it); + } + else + { + context.push("{fmt-err: unconsumed}"); + while (it != end && *it != '}') + { + std::advance(it, 1); + } + + if (it != end) + { + std::advance(it, 1); + parse_context.advance_to(it); + } + } + } + else if (*it == '}') + { + std::advance(it, 1); + if (it != end && *it == '}') + { + context.push('}'); + std::advance(it, 1); + parse_context.advance_to(it); + } + else + { + context.push("{fmt-err: unescaped}"); + parse_context.advance_to(it); + } + } + } + } + + auto string_writer::push(std::string_view text) -> void + { + m_result.append(text); + } + + auto string_writer::push(char character) -> void + { + m_result.push_back(character); + } + + auto string_writer::release() -> string && + { + return std::move(m_result); + } + +} // namespace kstd::bits::format
\ No newline at end of file diff --git a/libs/kstd/tests/src/format.cpp b/libs/kstd/tests/src/format.cpp new file mode 100644 index 0000000..73c8102 --- /dev/null +++ b/libs/kstd/tests/src/format.cpp @@ -0,0 +1,116 @@ +#include <kstd/flat_map> +#include <kstd/format> +#include <kstd/tests/os_panic.hpp> + +#include <catch2/catch_test_macros.hpp> + +#include <iterator> +#include <sstream> + +SCENARIO("Formatting to a new string", "[format]") +{ + GIVEN("a format string without any placeholders") + { + auto const & fmt = "This is a test"; + + WHEN("calling format with without any arguments.") + { + auto result = kstd::format(fmt); + + THEN("the result is the unmodified string") + { + REQUIRE(result == "This is a test"); + } + } + + WHEN("calling format with additional arguments") + { + auto result = kstd::format(fmt, 1, 2, 3); + + THEN("the result is the unmodified string") + { + REQUIRE(result == "This is a test"); + } + } + } + + GIVEN("a format string with placeholders") + { + auto const & fmt = "Here are some placeholders: {} {} {}"; + + WHEN("calling format with the same number of arguments as there are placeholders") + { + auto result = kstd::format(fmt, 1, true, 'a'); + + THEN("the result is the formatted string") + { + REQUIRE(result == "Here are some placeholders: 1 true a"); + } + } + + WHEN("calling format with too many arguments") + { + auto result = kstd::format(fmt, 2, false, 'b', 4, 5, 6); + + THEN("the result is the formatted string") + { + REQUIRE(result == "Here are some placeholders: 2 false b"); + } + } + } +} + +SCENARIO("Formatting to an output iterator", "[format]") +{ + auto buffer = std::ostringstream{}; + + GIVEN("a format string without any placeholders") + { + auto const & fmt = "This is a test"; + + WHEN("calling format with without any arguments.") + { + kstd::format_to(std::ostream_iterator<char>{buffer}, fmt); + + THEN("the unmodified string is written to the iterator") + { + REQUIRE(buffer.str() == "This is a test"); + } + } + + WHEN("calling format with additional arguments") + { + kstd::format_to(std::ostream_iterator<char>{buffer}, fmt, 1, 2, 3); + + THEN("the unmodified string is written to the iterator") + { + REQUIRE(buffer.str() == "This is a test"); + } + } + } + + GIVEN("a format string with placeholders") + { + auto const & fmt = "Here are some placeholders: {} {} {}"; + + WHEN("calling format with the same number of arguments as there are placeholders") + { + kstd::format_to(std::ostream_iterator<char>{buffer}, fmt, 1, true, -100); + + THEN("the formatted string is written to the iterator") + { + REQUIRE(buffer.str() == "Here are some placeholders: 1 true -100"); + } + } + + WHEN("calling format with too many arguments") + { + kstd::format_to(std::ostream_iterator<char>{buffer}, fmt, 2, false, -200, 4, 5, 6); + + THEN("the formatted string is written to the iterator") + { + REQUIRE(buffer.str() == "Here are some placeholders: 2 false -200"); + } + } + } +}
\ No newline at end of file diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index 81bf32f..5faaaff 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -463,6 +463,33 @@ SCENARIO("Vector modifiers", "[vector]") } } + WHEN("emplace is called with the end iterator and constructor arguments") + { + v.emplace(v.end(), 20); + + THEN("the element is appended and the size and capacity increase") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() >= 1); + REQUIRE(v.back() == 20); + } + } + + WHEN("emplace is called while capacity is sufficient") + { + v.reserve(10); + auto const capacity = v.capacity(); + + v.emplace(v.end(), 20); + + THEN("the element is appended without reallocation") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() == capacity); + REQUIRE(v.back() == 20); + } + } + WHEN("inserting an element") { auto it = v.insert(v.cbegin(), 42); @@ -526,6 +553,24 @@ SCENARIO("Vector modifiers", "[vector]") } } + WHEN("emplace is called with an iterator and constructor arguments") + { + auto it = v.emplace(v.begin() + 2, 25); + + THEN("the element is inserted and the size and capacity increase") + { + REQUIRE(v.size() == 4); + REQUIRE(v.capacity() >= initial_capacity); + REQUIRE(v.at(2) == 25); + } + + THEN("the returned iterator points to the inserted element") + { + REQUIRE(it == v.cbegin() + 2); + REQUIRE(*it == 25); + } + } + WHEN("push_back is called with a reference to an internal element") { v.shrink_to_fit(); |
