diff options
| author | Marcel Braun <marcel.braun@ost.ch> | 2026-04-02 08:48:00 +0200 |
|---|---|---|
| committer | Marcel Braun <marcel.braun@ost.ch> | 2026-04-02 08:48:00 +0200 |
| commit | 0c01a95325b26151ff3c9a70142f5dc83ff7d53f (patch) | |
| tree | 9bf034f544ae773b653554a54edfce232f835754 /kernel/src/test_support | |
| parent | 022d3e872de9c5a6a52c67f74af13706552330c0 (diff) | |
| parent | 3eb680cf5bcef626505cac82820996d8db4170d7 (diff) | |
| download | teachos-0c01a95325b26151ff3c9a70142f5dc83ff7d53f.tar.xz teachos-0c01a95325b26151ff3c9a70142f5dc83ff7d53f.zip | |
Merge branch 'fmorgner/develop-SA-FS26/kernel-bht' into 'develop-BA-FS26'
Add experimental support for kernel tests
See merge request teachos/kernel!20
Diffstat (limited to 'kernel/src/test_support')
| -rw-r--r-- | kernel/src/test_support/kapi/cio.cpp | 43 | ||||
| -rw-r--r-- | kernel/src/test_support/kapi/cpu.cpp | 41 | ||||
| -rw-r--r-- | kernel/src/test_support/kapi/interrupts.cpp | 11 | ||||
| -rw-r--r-- | kernel/src/test_support/kapi/memory.cpp | 71 | ||||
| -rw-r--r-- | kernel/src/test_support/log_buffer.cpp | 39 | ||||
| -rw-r--r-- | kernel/src/test_support/page_mapper.cpp | 78 | ||||
| -rw-r--r-- | kernel/src/test_support/simulated_memory.cpp | 93 | ||||
| -rw-r--r-- | kernel/src/test_support/state_reset_listener.cpp | 50 |
8 files changed, 426 insertions, 0 deletions
diff --git a/kernel/src/test_support/kapi/cio.cpp b/kernel/src/test_support/kapi/cio.cpp new file mode 100644 index 0000000..35452d4 --- /dev/null +++ b/kernel/src/test_support/kapi/cio.cpp @@ -0,0 +1,43 @@ +#include <kapi/cio.hpp> + +#include "kernel/test_support/log_buffer.hpp" + +#include <iostream> +#include <string> +#include <string_view> + +namespace kapi::cio +{ + + namespace + { + + class test_output_device : public output_device + { + public: + test_output_device() = default; + + auto write(output_stream stream, std::string_view text) -> void override + { + auto & standard_stream = stream == output_stream::stdout ? std::cout : std::cerr; + standard_stream << text; + if (text != "\n") + { + kernel::tests::log_buffer::append(std::string{text}); + } + } + } device{}; + + } // namespace + + auto init() -> void + { + set_output_device(device); + } + + auto reset() -> void + { + kernel::tests::log_buffer::clear(); + } + +} // namespace kapi::cio
\ No newline at end of file diff --git a/kernel/src/test_support/kapi/cpu.cpp b/kernel/src/test_support/kapi/cpu.cpp new file mode 100644 index 0000000..6592d15 --- /dev/null +++ b/kernel/src/test_support/kapi/cpu.cpp @@ -0,0 +1,41 @@ +#include "kernel/test_support/cpu.hpp" + +#include <kapi/cpu.hpp> + +#include <atomic> +#include <stdexcept> + +namespace kapi::cpu +{ + + namespace + { + auto static initialized = std::atomic_flag{}; + } + + auto reset() -> void + { + if (!initialized.test()) + { + throw std::logic_error{"kapi::cpu::reset() called before kapi::cpu::init()"}; + } + + initialized.clear(); + } + + auto init() -> void + { + if (initialized.test_and_set()) + { + throw std::logic_error("kapi::cpu::init() called more than once"); + } + + // TODO: make sure that simulated interrupt can run. + } + + auto halt() -> void + { + throw kernel::tests::cpu::halt{}; + } + +} // namespace kapi::cpu
\ No newline at end of file diff --git a/kernel/src/test_support/kapi/interrupts.cpp b/kernel/src/test_support/kapi/interrupts.cpp new file mode 100644 index 0000000..0077266 --- /dev/null +++ b/kernel/src/test_support/kapi/interrupts.cpp @@ -0,0 +1,11 @@ +#include <kapi/interrupts.hpp> + +namespace kapi::interrupts +{ + + auto enable() -> void + { + // TODO: enable simulated interrupts. + } + +} // namespace kapi::interrupts
\ No newline at end of file diff --git a/kernel/src/test_support/kapi/memory.cpp b/kernel/src/test_support/kapi/memory.cpp new file mode 100644 index 0000000..f244b7f --- /dev/null +++ b/kernel/src/test_support/kapi/memory.cpp @@ -0,0 +1,71 @@ +#include "kapi/memory.hpp" + +#include <kapi/memory.hpp> + +#include "kernel/test_support/bump_frame_allocator.hpp" +#include "kernel/test_support/page_mapper.hpp" + +#include <kstd/units> + +#include <optional> + +namespace +{ + //! The size of the simulated RAM. + constexpr auto memory_size = kstd::units::MiB(32); + constexpr auto number_of_frames = memory_size / kapi::memory::frame::size; + + auto constinit bump_allocator = std::optional<kernel::tests::bump_frame_allocator>{}; + auto constinit test_mapper = std::optional<kernel::tests::page_mapper>{}; + + auto constinit old_allocator = std::optional<kapi::memory::frame_allocator *>{}; + auto constinit old_mapper = std::optional<kapi::memory::page_mapper *>{}; + + auto handoff_to_kernel_pmm(kapi::memory::frame_allocator & new_allocator) -> void + { + auto first_free_frame = bump_allocator->next_free_frame; + auto number_of_free_frames = number_of_frames - first_free_frame; + new_allocator.release_many({kapi::memory::frame{first_free_frame}, number_of_free_frames}); + } + +} // namespace + +namespace kapi::memory +{ + + auto init() -> void + { + bump_allocator.emplace(); + test_mapper.emplace(memory_size); + + old_allocator = set_frame_allocator(*bump_allocator); + old_mapper = set_page_mapper(*test_mapper); + + init_pmm(memory_size / frame::size, handoff_to_kernel_pmm); + } + + auto reset() -> void + { + if (old_allocator && *old_allocator) + { + set_frame_allocator(**old_allocator); + } + + if (old_mapper && *old_mapper) + { + set_page_mapper(**old_mapper); + } + + bump_allocator.reset(); + test_mapper.reset(); + } + +} // namespace kapi::memory + +namespace kernel::tests::memory +{ + auto heap_base() -> kapi::memory::linear_address + { + return test_mapper->memory.heap_base(); + } +} // namespace kernel::tests::memory
\ No newline at end of file diff --git a/kernel/src/test_support/log_buffer.cpp b/kernel/src/test_support/log_buffer.cpp new file mode 100644 index 0000000..36ed15e --- /dev/null +++ b/kernel/src/test_support/log_buffer.cpp @@ -0,0 +1,39 @@ +#include "kernel/test_support/log_buffer.hpp" + +#include <algorithm> +#include <string> +#include <vector> + +namespace kernel::tests::log_buffer +{ + + namespace + { + std::vector<std::string> recorded_messages{}; + } + + auto append(std::string const & message) -> void + { + recorded_messages.push_back(message); + } + + auto clear() -> void + { + recorded_messages.clear(); + } + + auto flat_messages() -> std::string + { + return std::ranges::fold_left(recorded_messages, std::string{}, + [](std::string accumulator, std::string const & message) { + accumulator += message; + return accumulator; + }); + } + + auto messages() -> std::vector<std::string> const & + { + return recorded_messages; + } + +} // namespace kernel::tests::log_buffer diff --git a/kernel/src/test_support/page_mapper.cpp b/kernel/src/test_support/page_mapper.cpp new file mode 100644 index 0000000..abdcae5 --- /dev/null +++ b/kernel/src/test_support/page_mapper.cpp @@ -0,0 +1,78 @@ +#include "kernel/test_support/page_mapper.hpp" + +#include "kapi/memory.hpp" + +#include <kstd/units> + +#include <cstddef> +#include <format> +#include <stdexcept> +#include <sys/mman.h> + +namespace kernel::tests +{ + + page_mapper::page_mapper(kstd::units::bytes memory_size) + : memory{memory_size} + {} + + auto page_mapper::map(kapi::memory::page page, kapi::memory::frame frame, flags) -> std::byte * + { + auto result = page_mappings.insert({page.number(), frame}); + if (!result.second) + { + auto error = std::format("Page {} was already mapped!", page.number()); + throw std::invalid_argument{error}; + } + + auto page_address = page.start_address(); + auto sandbox_start = memory.heap_base(); + auto sandbox_end = sandbox_start + memory.heap_size(); + + if (page_address >= sandbox_start && page_address < sandbox_end) + { + auto virtual_target = static_cast<std::byte *>(page_address); + auto physical_offset = frame.number() * kapi::memory::frame::size; + auto mapped_ptr = mmap(virtual_target, kapi::memory::page::size.value, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_FIXED, memory.memory_descriptor(), physical_offset.value); + if (mapped_ptr == MAP_FAILED) + { + throw std::runtime_error("Failed to map page"); + } + + return static_cast<std::byte *>(mapped_ptr); + } + else if (page_address >= kapi::memory::mmio_base) + { + throw std::runtime_error("MMIO mapping not yet supported in testing!"); + } + else if (page_address >= kapi::memory::higher_half_direct_map_base) + { + auto offset = frame.number() * kapi::memory::frame::size; + return memory.ram_base() + offset; + } + + return nullptr; + } + + auto page_mapper::unmap(kapi::memory::page page) -> void + { + if (!try_unmap(page)) + { + auto error = std::format("Page {} was never mapped!", page.number()); + throw std::invalid_argument{error}; + } + } + + auto page_mapper::try_unmap(kapi::memory::page page) noexcept -> bool + { + if (page_mappings.contains(page.number())) + { + page_mappings.erase(page.number()); + return true; + } + + return false; + } + +} // namespace kernel::tests
\ No newline at end of file diff --git a/kernel/src/test_support/simulated_memory.cpp b/kernel/src/test_support/simulated_memory.cpp new file mode 100644 index 0000000..fa3d36c --- /dev/null +++ b/kernel/src/test_support/simulated_memory.cpp @@ -0,0 +1,93 @@ +#include "kernel/test_support/simulated_memory.hpp" + +#include "kapi/memory.hpp" + +#include <kstd/units> + +#include <cstddef> +#include <cstring> +#include <stdexcept> +#include <sys/mman.h> +#include <sys/types.h> +#include <unistd.h> + +using namespace kstd::units_literals; + +namespace kernel::tests +{ + namespace + { + constexpr auto virtual_size = 1_GiB; + } + + simulated_memory::simulated_memory(kstd::units::bytes size) + : m_memory_descriptor{memfd_create("teachos_simulated_memory", 0)} + , m_size{size} + { + if (m_memory_descriptor < 0) + { + throw std::runtime_error("Failed to create simulated memory"); + } + + if (ftruncate(m_memory_descriptor, static_cast<off_t>(m_size.value)) < 0) + { + throw std::runtime_error("Failed to resize simulated memory"); + } + + auto mapped_pointer = mmap(nullptr, m_size.value, PROT_READ | PROT_WRITE, MAP_SHARED, m_memory_descriptor, 0); + if (mapped_pointer == MAP_FAILED) + { + throw std::runtime_error("Failed to map simulated memory"); + } + + m_physical_base = static_cast<std::byte *>(mapped_pointer); + + auto virtual_pointer = mmap(nullptr, virtual_size.value, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (virtual_pointer == MAP_FAILED) + { + throw std::runtime_error("Failed to map simulated memory"); + } + + m_virtual_base = static_cast<std::byte *>(virtual_pointer); + + clear(); + } + + simulated_memory::~simulated_memory() + { + munmap(m_physical_base, m_size.value); + munmap(m_virtual_base, virtual_size.value); + close(m_memory_descriptor); + } + + auto simulated_memory::clear() -> void + { + std::memset(m_physical_base, 0, m_size.value); + } + + auto simulated_memory::ram_base() noexcept -> std::byte * + { + return m_physical_base; + } + + auto simulated_memory::ram_base() const noexcept -> std::byte const * + { + return m_physical_base; + } + + auto simulated_memory::heap_base() const noexcept -> kapi::memory::linear_address + { + return kapi::memory::linear_address{m_virtual_base}; + } + + auto simulated_memory::heap_size() const noexcept -> kstd::units::bytes + { + return virtual_size; + } + + auto simulated_memory::memory_descriptor() const noexcept -> int + { + return m_memory_descriptor; + } + +} // namespace kernel::tests
\ No newline at end of file diff --git a/kernel/src/test_support/state_reset_listener.cpp b/kernel/src/test_support/state_reset_listener.cpp new file mode 100644 index 0000000..e201a10 --- /dev/null +++ b/kernel/src/test_support/state_reset_listener.cpp @@ -0,0 +1,50 @@ +#include "kapi/cio.hpp" +#include "kapi/cpu.hpp" +#include "kapi/memory.hpp" + +#include <catch2/catch_test_case_info.hpp> +#include <catch2/interfaces/catch_interfaces_reporter.hpp> +#include <catch2/reporters/catch_reporter_event_listener.hpp> +#include <catch2/reporters/catch_reporter_registrars.hpp> + +namespace kapi +{ + namespace cio + { + auto reset() -> void; + } + + namespace cpu + { + auto reset() -> void; + } + + namespace memory + { + auto reset() -> void; + } +} // namespace kapi + +struct state_reset_listener : Catch::EventListenerBase +{ + using EventListenerBase::EventListenerBase; + + void testCaseStarting(Catch::TestCaseInfo const &) override + { + kapi::cio::init(); + kapi::cpu::init(); + kapi::memory::init(); + } + + void testCaseEnded(Catch::TestCaseStats const &) override + { + kapi::memory::reset(); + kapi::cpu::reset(); + kapi::cio::reset(); + } +}; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" +CATCH_REGISTER_LISTENER(state_reset_listener); +#pragma GCC diagnostic pop
\ No newline at end of file |
