diff options
| -rw-r--r-- | src/common/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/common/multi_level_page_table.cpp | 7 | ||||
| -rw-r--r-- | src/common/multi_level_page_table.h | 79 | ||||
| -rw-r--r-- | src/common/multi_level_page_table.inc | 83 | 
4 files changed, 171 insertions, 0 deletions
| diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 3447fabd8..2db414819 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -81,6 +81,8 @@ add_library(common STATIC      microprofile.cpp      microprofile.h      microprofileui.h +    multi_level_page_table.cpp +    multi_level_page_table.h      nvidia_flags.cpp      nvidia_flags.h      page_table.cpp diff --git a/src/common/multi_level_page_table.cpp b/src/common/multi_level_page_table.cpp new file mode 100644 index 000000000..561785ca7 --- /dev/null +++ b/src/common/multi_level_page_table.cpp @@ -0,0 +1,7 @@ +#include "common/multi_level_page_table.inc" + +namespace Common { +template class Common::MultiLevelPageTable<GPUVAddr>; +template class Common::MultiLevelPageTable<VAddr>; +template class Common::MultiLevelPageTable<PAddr>; +} // namespace Common diff --git a/src/common/multi_level_page_table.h b/src/common/multi_level_page_table.h new file mode 100644 index 000000000..dde1cc962 --- /dev/null +++ b/src/common/multi_level_page_table.h @@ -0,0 +1,79 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <type_traits> +#include <utility> +#include <vector> + +#include "common/common_types.h" + +namespace Common { + +template <typename BaseAddr> +class MultiLevelPageTable final { +public: +    constexpr MultiLevelPageTable() = default; +    explicit MultiLevelPageTable(std::size_t address_space_bits, std::size_t first_level_bits, +                                 std::size_t page_bits); + +    ~MultiLevelPageTable() noexcept; + +    MultiLevelPageTable(const MultiLevelPageTable&) = delete; +    MultiLevelPageTable& operator=(const MultiLevelPageTable&) = delete; + +    MultiLevelPageTable(MultiLevelPageTable&& other) noexcept +        : address_space_bits{std::exchange(other.address_space_bits, 0)}, +          first_level_bits{std::exchange(other.first_level_bits, 0)}, page_bits{std::exchange( +                                                                          other.page_bits, 0)}, +          first_level_shift{std::exchange(other.first_level_shift, 0)}, +          first_level_chunk_size{std::exchange(other.first_level_chunk_size, 0)}, +          first_level_map{std::move(other.first_level_map)}, base_ptr{std::exchange(other.base_ptr, +                                                                                    nullptr)} {} + +    MultiLevelPageTable& operator=(MultiLevelPageTable&& other) noexcept { +        address_space_bits = std::exchange(other.address_space_bits, 0); +        first_level_bits = std::exchange(other.first_level_bits, 0); +        page_bits = std::exchange(other.page_bits, 0); +        first_level_shift = std::exchange(other.first_level_shift, 0); +        first_level_chunk_size = std::exchange(other.first_level_chunk_size, 0); +        alloc_size = std::exchange(other.alloc_size, 0); +        first_level_map = std::move(other.first_level_map); +        base_ptr = std::exchange(other.base_ptr, nullptr); +        return *this; +    } + +    void ReserveRange(u64 start, std::size_t size); + +    [[nodiscard]] constexpr const BaseAddr& operator[](std::size_t index) const { +        return base_ptr[index]; +    } + +    [[nodiscard]] constexpr BaseAddr& operator[](std::size_t index) { +        return base_ptr[index]; +    } + +    [[nodiscard]] constexpr BaseAddr* data() { +        return base_ptr; +    } + +    [[nodiscard]] constexpr const BaseAddr* data() const { +        return base_ptr; +    } + +private: +    void AllocateLevel(u64 level); + +    std::size_t address_space_bits{}; +    std::size_t first_level_bits{}; +    std::size_t page_bits{}; +    std::size_t first_level_shift{}; +    std::size_t first_level_chunk_size{}; +    std::size_t alloc_size{}; +    std::vector<void*> first_level_map{}; +    BaseAddr* base_ptr{}; +}; + +} // namespace Common diff --git a/src/common/multi_level_page_table.inc b/src/common/multi_level_page_table.inc new file mode 100644 index 000000000..a75e61f9d --- /dev/null +++ b/src/common/multi_level_page_table.inc @@ -0,0 +1,83 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#ifdef _WIN32 +#include <windows.h> +#else +#include <sys/mman.h> +#endif + +#include "common/assert.h" +#include "common/multi_level_page_table.h" + +namespace Common { + +template <typename BaseAddr> +MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bits_, +                                                   std::size_t first_level_bits_, +                                                   std::size_t page_bits_) +    : address_space_bits{address_space_bits_}, +      first_level_bits{first_level_bits_}, page_bits{page_bits_} { +    first_level_shift = address_space_bits - first_level_bits; +    first_level_chunk_size = 1ULL << (first_level_shift - page_bits); +    alloc_size = (1ULL << (address_space_bits - page_bits)) * sizeof(BaseAddr); +    std::size_t first_level_size = 1ULL << first_level_bits; +    first_level_map.resize(first_level_size, nullptr); +#ifdef _WIN32 +    void* base{VirtualAlloc(nullptr, alloc_size, MEM_RESERVE, PAGE_READWRITE)}; +#else +    void* base{mmap(nullptr, alloc_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)}; + +    if (base == MAP_FAILED) { +        base = nullptr; +    } +#endif + +    ASSERT(base); +    base_ptr = reinterpret_cast<BaseAddr*>(base); +} + +template <typename BaseAddr> +MultiLevelPageTable<BaseAddr>::~MultiLevelPageTable() noexcept { +    if (!base_ptr) { +        return; +    } +#ifdef _WIN32 +    ASSERT(VirtualFree(base_ptr, 0, MEM_RELEASE)); +#else +    ASSERT(munmap(base_ptr, alloc_size) == 0); +#endif +} + +template <typename BaseAddr> +void MultiLevelPageTable<BaseAddr>::ReserveRange(u64 start, std::size_t size) { +    const u64 new_start = start >> first_level_shift; +    const u64 new_end = +        (start + size + (first_level_chunk_size << page_bits) - 1) >> first_level_shift; +    for (u64 i = new_start; i <= new_end; i++) { +        if (!first_level_map[i]) { +            AllocateLevel(i); +        } +    } +} + +template <typename BaseAddr> +void MultiLevelPageTable<BaseAddr>::AllocateLevel(u64 level) { +    void* ptr = reinterpret_cast<char*>(base_ptr) + level * first_level_chunk_size; +#ifdef _WIN32 +    void* base{VirtualAlloc(ptr, first_level_chunk_size, MEM_COMMIT, PAGE_READWRITE)}; +#else +    void* base{mmap(ptr, first_level_chunk_size, PROT_READ | PROT_WRITE, +                    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)}; + +    if (base == MAP_FAILED) { +        base = nullptr; +    } +#endif +    ASSERT(base); + +    first_level_map[level] = base; +} + +} // namespace Common | 
