diff --git a/cmake/utils.cmake b/cmake/utils.cmake index 2464d58..5cf1593 100644 --- a/cmake/utils.cmake +++ b/cmake/utils.cmake @@ -6,7 +6,7 @@ macro(prot_add_utest src_file) set(TEST_NAME "${src_file}_test") add_executable(${TEST_NAME} ${src_file}) - target_link_libraries(${TEST_NAME} PRIVATE GMock::gmock_main PROT::defaults) + target_link_libraries(${TEST_NAME} PRIVATE PROT::defaults GTest::gtest GTest::gtest_main GTest::gmock GTest::gmock_main) foreach(arg IN LISTS ARGN) target_link_libraries(${TEST_NAME} PRIVATE ${arg}) endforeach() @@ -15,3 +15,18 @@ macro(prot_add_utest src_file) EXTRA_ARGS --gtest_color=yes PROPERTIES LABELS unit) endmacro() + +function(prot_add_component_utest COMPONENT_NAME) + file(GLOB TESTLIST RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.cc") + + foreach(TEST_SRC ${TESTLIST}) + set(TEST_NAME "${src_file}_test") + prot_add_utest(${TEST_SRC} ARGN) + + set_target_properties(${TEST_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/test/${COMPONENT_NAME}" + ) + + endforeach() +endfunction() diff --git a/src/memory/CMakeLists.txt b/src/memory/CMakeLists.txt index 02d640a..3446f7c 100644 --- a/src/memory/CMakeLists.txt +++ b/src/memory/CMakeLists.txt @@ -1,4 +1,5 @@ add_library(prot_mem STATIC memory.cc plain_memory.cc simple_page_mem.cc) + target_link_libraries( prot_mem PUBLIC PROT::isa @@ -6,3 +7,5 @@ target_link_libraries( target_include_directories(prot_mem PUBLIC include) add_library(PROT::memory ALIAS prot_mem) + +add_subdirectory(tests) diff --git a/src/memory/include/prot/page.hh b/src/memory/include/prot/page.hh new file mode 100644 index 0000000..4f944f6 --- /dev/null +++ b/src/memory/include/prot/page.hh @@ -0,0 +1,75 @@ +#ifndef PROT_PAGE_HH_INCLUDED +#define PROT_PAGE_HH_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include "prot/isa.hh" + +namespace prot { +template class PageConfig { +public: + using PadeId = uint32_t; + + static constexpr std::uint64_t getPageSize() { return kPageSize; } + static isa::Addr getOffset(isa::Addr addr) { return addr & kOffsetMask; } + static PadeId getId(isa::Addr addr) { return (addr & kIdMask) >> kPageBits; } + +private: + static constexpr std::uint64_t kPageBits{PageBits}; + static constexpr std::uint64_t kPageSize{1ULL << kPageBits}; + static constexpr std::uint64_t kOffsetMask{kPageSize - 1}; + static constexpr std::uint64_t kIdMask{~kOffsetMask}; +}; + +template +concept PageConfigConc = requires(T, isa::Addr addr) { + { T::getPageSize() } -> std::same_as; + { T::getId(addr) } -> std::same_as; + { T::getOffset(addr) } -> std::same_as; +}; + +class PageFault : public std::out_of_range { +public: + explicit PageFault(isa::Addr addr) + : std::out_of_range("Page fault at address: 0x" + std::to_string(addr)), + m_addr(addr) {} + + isa::Addr addr() const { return m_addr; } + +private: + isa::Addr m_addr; +}; + +template class Page final { +public: + Page() : m_data(PageConfigT::getPageSize()) {} + +public: + void writeBlock(const std::byte *src, isa::Addr offset, std::size_t size) { + if (offset >= PageConfigT::getPageSize()) + throw std::out_of_range{"Offset out of page"}; + + std::copy_n(src, size, m_data.data() + offset); + } + + void readBlock(isa::Addr offset, std::size_t size, std::byte *dst) const { + if (offset >= PageConfigT::getPageSize()) + throw std::out_of_range{"Offset out of page"}; + + std::copy_n(m_data.data() + offset, size, dst); + } + + auto at(isa::Addr addr) const { return m_data.at(addr); } + auto getSize() const { return PageConfigT::getPageSize(); } + +private: + std::vector m_data{}; +}; +} // namespace prot + +#endif // PROT_PAGE_HH_INCLUDED diff --git a/src/memory/include/prot/page_memory.hh b/src/memory/include/prot/page_memory.hh new file mode 100644 index 0000000..e36fbc4 --- /dev/null +++ b/src/memory/include/prot/page_memory.hh @@ -0,0 +1,123 @@ +#ifndef PROT_PAGE_MEMORY_HH_INCLUDED +#define PROT_PAGE_MEMORY_HH_INCLUDED + +#include "memory.hh" +#include "page.hh" + +#include +#include +#include +#include +#include + +namespace prot { + +template +bool memWalker(isa::Addr addr, std::size_t size, Func func) { + if (size == 0) + return true; + + isa::Addr currentAddr = addr; + std::size_t remainSize = size; + + while (remainSize > 0) { + auto pageId = PageConfigT::getId(currentAddr); + auto offset = PageConfigT::getOffset(currentAddr); + auto chunkSize = std::min(remainSize, PageConfigT::getPageSize() - offset); + + if (!func(pageId, chunkSize, offset)) + return false; + + currentAddr += chunkSize; + remainSize -= chunkSize; + } + + return true; +} + +template +class PageMemConfig final { +public: + using PageConfigT = PageConfig; + using PageT = Page; + + static constexpr std::uint64_t getPagesNum() { + return kAddrSpace / PageConfigT::getPageSize(); + } + + static constexpr PageConfigT getPageConfig() { return kPageConf; } + +private: + static constexpr std::uint64_t kAddrBits{AddrBits}; + static constexpr std::uint64_t kAddrSpace{1ULL << AddrBits}; + static constexpr PageConfigT kPageConf{}; + + static_assert(kAddrSpace >= PageConfigT::getPageSize(), + "Total virtual address space is smaller than single page size"); +}; + +template +concept PageMemConfigConc = requires(T) { + { T::getPagesNum() } -> std::same_as; + { T::getPageConfig() } -> std::same_as; +}; + +template class PageMemory : public Memory { +public: + using PageConfigT = typename PageMemConfigT::PageConfigT; + using PageT = typename PageMemConfigT::PageT; + using PageId = typename PageConfigT::PadeId; + + PageMemory() : m_config(PageMemConfigT::getPageConfig()) { + m_storage.reserve(PageMemConfigT::getPagesNum()); + } + + void writeBlock(std::span src, isa::Addr addr) override { + const auto *cur = src.data(); + + memWalker( + addr, src.size(), + [this, &cur](isa::Addr addr, size_t size, isa::Addr offset) { + auto &page = this->getPage(addr); + page.writeBlock(cur, offset, size); + cur += size; + return true; + }); + } + + void readBlock(isa::Addr addr, std::span dest) const override { + auto *cur = dest.data(); + + memWalker( + addr, dest.size(), + [this, &cur](isa::Addr addr, size_t size, isa::Addr offset) { + const auto &page = this->getPage(addr); + page.readBlock(offset, size, cur); + cur += size; + return true; + }); + } + +private: + PageT getPage(isa::Addr addr) const { + auto pageId = m_config.getId(addr); + auto found = m_storage.find(pageId); + + if (found == m_storage.end()) + throw PageFault{addr}; + + return found->second; + } + + PageT &getPage(isa::Addr addr) { + auto pageId = m_config.getId(addr); + auto [it, inserted] = m_storage.try_emplace(pageId); + return it->second; + } + + std::unordered_map m_storage{}; + PageConfigT m_config; +}; +} // namespace prot + +#endif // PROT_PAGE_MEMORY_HH_INCLUDED diff --git a/src/memory/tests/CMakeLists.txt b/src/memory/tests/CMakeLists.txt new file mode 100644 index 0000000..cb87d22 --- /dev/null +++ b/src/memory/tests/CMakeLists.txt @@ -0,0 +1,5 @@ +set(COMPONENT_NAME memory) + +if(BUILD_TESTS) + prot_add_component_utest(${COMPONENT_NAME} PROT::memory) +endif() diff --git a/src/memory/tests/page_memory.cc b/src/memory/tests/page_memory.cc new file mode 100644 index 0000000..e71c639 --- /dev/null +++ b/src/memory/tests/page_memory.cc @@ -0,0 +1,95 @@ +#include "prot/page_memory.hh" +#include + +class PageMemTest : public ::testing::Test { +public: + static constexpr std::uint64_t kPageBits = 12; + static constexpr std::uint64_t kAddrBits = 16; + +protected: + // RAM -- 64kb, Page -- 4KB + prot::PageMemory> m_mem{}; +}; + +TEST_F(PageMemTest, writeBlockTest) { + const std::byte test_data[] = {std::byte{0xde}, std::byte{0xad}, + std::byte{0xbe}, std::byte{0xef}}; + + std::byte read_data[sizeof(test_data)]; + + constexpr prot::isa::Addr addr = 42; + + m_mem.writeBlock(test_data, addr); + m_mem.readBlock(addr, read_data); + + EXPECT_TRUE(std::equal(std::begin(test_data), std::end(test_data), + std::begin(read_data))); +} + +TEST_F(PageMemTest, writeBlockTest2) { + const std::byte test_data[] = {std::byte{0xde}, std::byte{0xad}, + std::byte{0xbe}, std::byte{0xef}}; + + constexpr prot::isa::Addr addr = 42; + + m_mem.writeBlock(test_data, addr); + + EXPECT_EQ(m_mem.read(addr), 0xde); + EXPECT_EQ(m_mem.read(addr), 0xadde); + EXPECT_EQ(m_mem.read(addr), 0xefbeadde); +} + +TEST_F(PageMemTest, writeBlockTest3) { + constexpr prot::isa::Addr addr = 42; + + m_mem.write(addr + 0, 0xde); + m_mem.write(addr + 1, 0xad); + m_mem.write(addr + 2, 0xef); + m_mem.write(addr + 3, 0xef); + + EXPECT_EQ(m_mem.read(addr + 0), 0xde); + EXPECT_EQ(m_mem.read(addr + 1), 0xad); + EXPECT_EQ(m_mem.read(addr + 2), 0xef); + EXPECT_EQ(m_mem.read(addr + 3), 0xef); +} + +TEST_F(PageMemTest, crossPageBoundaryTest) { + constexpr prot::isa::Addr boundary_addr = 4096 - 2; + const std::byte test_data[] = {std::byte{0x12}, std::byte{0x34}, + std::byte{0x56}, std::byte{0x78}}; + + m_mem.writeBlock(test_data, boundary_addr); + + std::byte read_data[sizeof(test_data)]; + m_mem.readBlock(boundary_addr, read_data); + + EXPECT_TRUE(std::equal(std::begin(test_data), std::end(test_data), + std::begin(read_data))); +} + +TEST_F(PageMemTest, pageFaultTest) { + constexpr prot::isa::Addr unmapped_addr = 0x10000; + + std::byte buffer[4]; + + EXPECT_THROW(m_mem.readBlock(unmapped_addr, buffer), prot::PageFault); + + const std::byte test_data[] = {std::byte{0xaa}}; + m_mem.writeBlock(test_data, 0x100); + + std::byte read_byte[1]; + m_mem.readBlock(0x100, read_byte); + EXPECT_EQ(test_data[0], read_byte[0]); +} + +TEST_F(PageMemTest, edgeCasesTest) { + constexpr prot::isa::Addr end_addr = (1ULL << 16) - 4; + const std::byte end_data[] = {std::byte{0xfe}, std::byte{0xed}, + std::byte{0xfa}, std::byte{0xce}}; + m_mem.writeBlock(end_data, end_addr); + + std::byte read_end[4]; + m_mem.readBlock(end_addr, read_end); + EXPECT_TRUE(std::equal(std::begin(end_data), std::end(end_data), + std::begin(read_end))); +}