aboutsummaryrefslogtreecommitdiff
path: root/arch/x86_64/arch/memory/page_table.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'arch/x86_64/arch/memory/page_table.hpp')
-rw-r--r--arch/x86_64/arch/memory/page_table.hpp232
1 files changed, 232 insertions, 0 deletions
diff --git a/arch/x86_64/arch/memory/page_table.hpp b/arch/x86_64/arch/memory/page_table.hpp
new file mode 100644
index 0000000..ce3d3a1
--- /dev/null
+++ b/arch/x86_64/arch/memory/page_table.hpp
@@ -0,0 +1,232 @@
+#ifndef TEACHOS_X86_64_PAGE_TABLE_HPP
+#define TEACHOS_X86_64_PAGE_TABLE_HPP
+
+#include <kapi/memory.hpp>
+
+#include <kstd/ext/bitfield_enum>
+#include <kstd/units>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <type_traits>
+
+namespace arch::memory
+{
+
+ //! A table containing page mapping entries.
+ //!
+ //! Page tables exist in a multi-level hierarchy and are used to map pages (virtual memory) onto frames (physical
+ //! memory). Conceptually, pages represent the data found in a virtual address space, while frames represent their
+ //! storage. In most cases, only a level 1 page table maps an actual page onto a frame. All other page tables on
+ //! higher levels do not map payload pages, but rather their subordinate page tables. The only exception to that rule
+ //! is the use of huge pages.
+ struct page_table
+ {
+ //! An entry in a page table.
+ //!
+ //! A page table entry is a combination of a frame number and a set of flags that determine the properties and
+ //! access rights to a mapped page. Entries at a higher level in the page hierarchy do not map a page directly,
+ //! unless that page is marked as huge on the relevant level, but rather map the next lower page table.
+ struct entry
+ {
+ //! Flags marking the state and configuration of an entry.
+ //!
+ //! An entry in a page table may have any combination of these flags active at the same time. The final flags of a
+ //! page are determined as the strictest combination (logical AND) of all flags along the hierarchy.
+ //!
+ //! @note This is a bitfield enum as defined by kstd::ext::bitfield_enum.
+ enum struct flags : std::uint64_t
+ {
+ empty = 0,
+ present = 1uz << 0, //!< The page is mapped.
+ writable = 1uz << 1, //!< The page is writable.
+ user_accessible = 1uz << 2, //!< The page is accessible in user mode.
+ write_through = 1uz << 3, //!< Any writes to the page must immediately hit memory.
+ disable_cache = 1uz << 4, //!< Any writes to the page must never be cached.
+ accessed = 1uz << 5, //!< The page was accessed.
+ dirty = 1uz << 6, //!< The page was written to.
+ huge_page = 1uz << 7, //!< The page is huge.
+ global = 1uz << 8, //!< The TLB entry for this page must not be flushed on context switches.
+ no_execute = 1uz << 63, //!< The data in this page must not be executed.
+ };
+
+ //! Construct an empty entry.
+ entry() = default;
+
+ //! Clear this entry, ensuring all information is set to zero.
+ //!
+ //! This effectively marks the page represented by this entry as not present. In addition, it also removes any
+ //! information about the frame referenced by this entry.
+ auto clear() noexcept -> void;
+
+ //! Check if the page represented by this entry is present.
+ //!
+ //! @note This function does not attempt to walk the page table hierarchy, but only performs a local check. This
+ //! means, that if the page is not mapped at a lower level, this will not be detected.
+ //!
+ //! @return @p true iff. the page is present at this level, @p false otherwise.
+ [[nodiscard]] auto present() const noexcept -> bool;
+
+ //! Check if the page represented by this entry is a huge page.
+ //!
+ //! @note The effective size of the page depends on the level of the page table containing this entry.
+ //!
+ //! @return @p true iff. the page is marked as being huge, @p false otherwise.
+ [[nodiscard]] auto huge() const noexcept -> bool;
+
+ //! Get all flags present in this entry.
+ //!
+ //! @return The flags that are currently set on this entry.
+ [[nodiscard]] auto all_flags() const noexcept -> flags;
+
+ //! Set all flags of this entry.
+ //!
+ //! @param flags The flags to apply to this entry.
+ auto all_flags(flags flags) noexcept -> void;
+
+ //! Add the given flags to the flags of this entry.
+ //!
+ //! @param rhs The flags to add to this entry's flags.
+ //! @return A reference to this entry.
+ auto operator|=(flags rhs) noexcept -> entry &;
+
+ //! Get the frame number associated with this entry, if the referenced page is present.
+ //!
+ //! @return an engaged std::optional iff. this entry maps a page, std::nullopt otherwise.
+ [[nodiscard]] auto frame() const noexcept -> std::optional<kapi::memory::frame>;
+
+ //! Map this entry.
+ //!
+ //! @param frame The frame to map in this entry.
+ //! @param flags The flags to apply to this entry.
+ auto frame(kapi::memory::frame frame, flags flags) noexcept -> void;
+
+ private:
+ //! A mask to retrieve, or exclude, the frame number from the raw entry.
+ //!
+ //! Page table entries in x86_64 are a compacted combination of the relevant flags and the frame number. This mask
+ //! represents the bits that make up the frame number in an entry.
+ constexpr auto static frame_number_mask{0x000f'ffff'ffff'f000uz};
+
+ //! The raw entry bytes.
+ //!
+ //! @see entry::frame_number_mask
+ std::uint64_t m_raw{};
+ };
+
+ //! The maximum number of entries in this table.
+ constexpr auto static entry_count{kapi::memory::page::size / kstd::units::bytes{sizeof(entry)}};
+
+ //! Get the entry at the given index.
+ //!
+ //! @warning This function will panic if the entry index is out of bounds.
+ //!
+ //! @param index The index of the desired entry.
+ //! @return A reference to the entry at the given index.
+ [[nodiscard]] auto operator[](std::size_t index) -> entry &;
+
+ //! @copydoc page_table::operator[]
+ [[nodiscard]] auto operator[](std::size_t index) const -> entry const &;
+
+ //! Clear the entire page table.
+ //!
+ //! This function effectively marks the page table as not mapping any pages.
+ auto clear() noexcept -> void;
+
+ //! Check if the page table is empty.
+ //!
+ //! @return @p true iff. this page table has no entries marked present, @p false otherwise.
+ [[nodiscard]] auto empty() const noexcept -> bool;
+
+ private:
+ std::array<entry, entry_count> m_entries{};
+ };
+
+} // namespace arch::memory
+
+namespace kstd::ext
+{
+ template<>
+ struct is_bitfield_enum<arch::memory::page_table::entry::flags> : std::true_type
+ {
+ };
+} // namespace kstd::ext
+
+namespace arch::memory
+{
+
+ constexpr auto to_mapper_flags(page_table::entry::flags flags) -> kapi::memory::page_mapper::flags
+ {
+ using table_flags = page_table::entry::flags;
+ using mapper_flags = kapi::memory::page_mapper::flags;
+
+ auto result = mapper_flags{};
+
+ if ((flags & table_flags::no_execute) == table_flags::empty)
+ {
+ result |= mapper_flags::executable;
+ }
+
+ if ((flags & table_flags::writable) != table_flags::empty)
+ {
+ result |= mapper_flags::writable;
+ }
+
+ if ((flags & (table_flags::disable_cache | table_flags::write_through)) != table_flags::empty)
+ {
+ result |= mapper_flags::uncached;
+ }
+
+ if ((flags & table_flags::user_accessible) == table_flags::empty)
+ {
+ result |= mapper_flags::supervisor_only;
+ }
+
+ if ((flags & table_flags::global) != table_flags::empty)
+ {
+ result |= mapper_flags::global;
+ }
+
+ return result;
+ }
+
+ constexpr auto to_table_flags(kapi::memory::page_mapper::flags flags) -> page_table::entry::flags
+ {
+ using table_flags = page_table::entry::flags;
+ using mapper_flags = kapi::memory::page_mapper::flags;
+
+ auto result = table_flags{};
+
+ if ((flags & mapper_flags::executable) == mapper_flags::empty)
+ {
+ result |= table_flags::no_execute;
+ }
+
+ if ((flags & mapper_flags::writable) != mapper_flags::empty)
+ {
+ result |= table_flags::writable;
+ }
+
+ if ((flags & mapper_flags::uncached) != mapper_flags::empty)
+ {
+ result |= (table_flags::disable_cache | table_flags::write_through);
+ }
+
+ if ((flags & mapper_flags::supervisor_only) == mapper_flags::empty)
+ {
+ result |= table_flags::user_accessible;
+ }
+
+ if ((flags & mapper_flags::global) != mapper_flags::empty)
+ {
+ result |= table_flags::global;
+ }
+
+ return result;
+ }
+
+} // namespace arch::memory
+
+#endif