| // Copyright 2023 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include <inttypes.h> |
| #include <lib/arch/arm64/page-table.h> |
| #include <lib/arch/paging.h> |
| #include <lib/arch/riscv64/page-table.h> |
| #include <lib/arch/x86/page-table.h> |
| #include <sys/types.h> |
| |
| #include <array> |
| #include <cstddef> |
| #include <cstdint> |
| #include <cstdlib> |
| #include <map> |
| #include <memory> |
| #include <variant> |
| |
| #include <gtest/gtest.h> |
| #include <hwreg/array.h> |
| |
| // |
| // The testing strategy for the PagingTraits API implementations and its |
| // consumers is as follows: |
| // |
| // * First, we test the API implementations directly. |
| // |
| // * Second, we test arch::Paging against the implementations, taking the |
| // latter for granted (with the confidence gained from their exercise in the |
| // first round of testing). |
| // |
| |
| namespace { |
| |
| using AccessPermissions = arch::AccessPermissions; |
| |
| using ArmPagingLevel = arch::ArmAddressTranslationLevel; |
| using ArmPagingSettings = arch::ArmPagingSettings; |
| using ArmSystemState = arch::ArmSystemPagingState; |
| |
| using RiscvMemoryType = arch::RiscvMemoryType; |
| using RiscvPagingLevel = arch::RiscvPagingLevel; |
| using RiscvPagingSettings = arch::RiscvPagingTraitsBase::PagingSettings; |
| |
| using X86PagingLevel = arch::X86PagingLevel; |
| using X86PagingSettings = arch::X86PagingTraitsBase::PagingSettings; |
| using X86SystemState = arch::X86PagingTraitsBase::SystemState; |
| |
| template <ArmPagingLevel Level> |
| using ArmTableEntry = |
| arch::ArmAddressTranslationDescriptor<Level, arch::ArmGranuleSize::k4KiB, |
| arch::ArmMaximumVirtualAddressWidth::k48Bits>; |
| |
| constexpr bool kBools[] = {false, true}; |
| |
| constexpr AccessPermissions kRWXU = { |
| .readable = true, |
| .writable = true, |
| .executable = true, |
| .user_accessible = true, |
| }; |
| |
| constexpr AccessPermissions kRX = { |
| .readable = true, |
| .executable = true, |
| }; |
| |
| template <ArmPagingLevel Level> |
| static constexpr bool kArmLevelCanBePage = Level == ArmPagingLevel::k3; |
| |
| template <ArmPagingLevel Level> |
| static constexpr bool kArmLevelCanBeTable = Level != ArmPagingLevel::k3; |
| |
| template <ArmPagingLevel Level> |
| static constexpr bool kArmLevelCanBeBlock = |
| Level == ArmPagingLevel::k1 || Level == ArmPagingLevel::k2; |
| |
| template <ArmPagingLevel Level> |
| static constexpr bool kArmLevelCanBeTerminal = |
| kArmLevelCanBePage<Level> || kArmLevelCanBeBlock<Level>; |
| |
| template <ArmPagingLevel Level> |
| static constexpr bool kArmLevelCanBeNonTerminal = kArmLevelCanBeTable<Level>; |
| |
| template <X86PagingLevel Level> |
| constexpr bool kX86LevelCanBeTerminal = |
| Level != X86PagingLevel::kPml5Table && Level != X86PagingLevel::kPml4Table; |
| |
| template <X86PagingLevel Level> |
| constexpr bool kX86LevelCanBeNonTerminal = Level != X86PagingLevel::kPageTable; |
| |
| // |
| // Tests for PagingTraits implementations. |
| // |
| |
| template <RiscvPagingLevel Level> |
| void TestRiscvGetSetPresent() { |
| // Presence is controlled by the V bit. |
| arch::RiscvPageTableEntry<Level> entry; |
| |
| entry.set_reg_value(0).set_v(false); |
| EXPECT_FALSE(entry.present()); |
| |
| entry.set_reg_value(0).set_v(true); |
| EXPECT_TRUE(entry.present()); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{.present = false}); |
| EXPECT_FALSE(entry.v()); |
| EXPECT_FALSE(entry.present()); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{.present = false}); |
| EXPECT_FALSE(entry.v()); |
| EXPECT_FALSE(entry.present()); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = kRWXU, |
| }); |
| EXPECT_TRUE(entry.v()); |
| EXPECT_TRUE(entry.present()); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = true, |
| }); |
| EXPECT_TRUE(entry.v()); |
| EXPECT_TRUE(entry.present()); |
| } |
| |
| TEST(RiscvPagingTraitTests, GetSetPresent) { |
| TestRiscvGetSetPresent<RiscvPagingLevel::k4>(); |
| TestRiscvGetSetPresent<RiscvPagingLevel::k3>(); |
| TestRiscvGetSetPresent<RiscvPagingLevel::k2>(); |
| TestRiscvGetSetPresent<RiscvPagingLevel::k1>(); |
| TestRiscvGetSetPresent<RiscvPagingLevel::k0>(); |
| } |
| |
| template <RiscvPagingLevel Level> |
| void TestRiscvGetSetReadable() { |
| // |
| // Readability is controlled by the R bit. |
| // |
| |
| arch::RiscvPageTableEntry<Level> entry; |
| |
| // If the entry is not terminal, then any page that maps through it can be |
| // readable. |
| for (bool r : kBools) { |
| for (bool x : kBools) { |
| entry.set_reg_value(0).set_r(r).set_x(x); |
| EXPECT_EQ(entry.readable(), r || (!r && !x)) << "r=" << r << ", x=" << x; |
| } |
| } |
| |
| entry.set_reg_value(0).Set( |
| {}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = |
| AccessPermissions{ |
| .readable = false, |
| .executable = true, // Must be at least readable or executable. |
| }, |
| }); |
| EXPECT_FALSE(entry.r()); |
| EXPECT_FALSE(entry.readable()); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = AccessPermissions{.readable = true}, |
| }); |
| EXPECT_TRUE(entry.r()); |
| EXPECT_TRUE(entry.readable()); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = kRWXU, |
| }); |
| EXPECT_FALSE(entry.r()); |
| EXPECT_TRUE(entry.readable()); |
| } |
| |
| TEST(RiscvPagingTraitTests, GetSetReadable) { |
| TestRiscvGetSetReadable<RiscvPagingLevel::k4>(); |
| TestRiscvGetSetReadable<RiscvPagingLevel::k3>(); |
| TestRiscvGetSetReadable<RiscvPagingLevel::k2>(); |
| TestRiscvGetSetReadable<RiscvPagingLevel::k1>(); |
| TestRiscvGetSetReadable<RiscvPagingLevel::k0>(); |
| } |
| |
| template <RiscvPagingLevel Level> |
| void TestRiscvGetSetWritable() { |
| // |
| // Writability is controlled by the W bit. |
| // |
| arch::RiscvPageTableEntry<Level> entry; |
| |
| // If the entry is not terminal, then any page that maps through it can be |
| // writable. |
| for (bool r : kBools) { |
| for (bool w : kBools) { |
| for (bool x : kBools) { |
| entry.set_reg_value(0).set_r(r).set_w(w).set_x(x); |
| EXPECT_EQ(entry.writable(), w || (!r && !x)) << "r=" << r << ", w=" << w << ", x=" << x; |
| } |
| } |
| } |
| |
| entry.set_reg_value(0).Set( |
| {}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = |
| AccessPermissions{ |
| .readable = true, // Must be at least readable or executable. |
| .writable = false, |
| }, |
| }); |
| EXPECT_FALSE(entry.w()); |
| EXPECT_FALSE(entry.writable()); |
| |
| entry.set_reg_value(0).Set( |
| {}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = |
| AccessPermissions{ |
| .readable = true, // Must be at least readable or executable. |
| .writable = true, |
| }, |
| }); |
| EXPECT_TRUE(entry.w()); |
| EXPECT_TRUE(entry.writable()); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = kRWXU, |
| }); |
| EXPECT_FALSE(entry.w()); |
| EXPECT_TRUE(entry.writable()); |
| } |
| |
| TEST(RiscvPagingTraitTests, GetSetWritable) { |
| TestRiscvGetSetWritable<RiscvPagingLevel::k4>(); |
| TestRiscvGetSetWritable<RiscvPagingLevel::k3>(); |
| TestRiscvGetSetWritable<RiscvPagingLevel::k2>(); |
| TestRiscvGetSetWritable<RiscvPagingLevel::k1>(); |
| TestRiscvGetSetWritable<RiscvPagingLevel::k0>(); |
| } |
| |
| template <RiscvPagingLevel Level> |
| void TestRiscvGetSetExecutable() { |
| // |
| // Executability is controlled by the X bit. |
| // |
| arch::RiscvPageTableEntry<Level> entry; |
| |
| // If the entry is not terminal, then any page that maps through it can be |
| // executable. |
| for (bool r : kBools) { |
| for (bool x : kBools) { |
| entry.set_reg_value(0).set_r(r).set_x(x); |
| EXPECT_EQ(entry.executable(), x || (!r && !x)) << "r=" << r << ", x=" << x; |
| } |
| } |
| |
| entry.set_reg_value(0).Set( |
| {}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = |
| AccessPermissions{ |
| .readable = true, // Must be at least readable or executable. |
| .executable = false, |
| }, |
| }); |
| EXPECT_FALSE(entry.x()); |
| EXPECT_FALSE(entry.executable()); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = AccessPermissions{.executable = true}, |
| }); |
| EXPECT_TRUE(entry.x()); |
| EXPECT_TRUE(entry.executable()); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = kRWXU, |
| }); |
| EXPECT_FALSE(entry.x()); |
| EXPECT_TRUE(entry.executable()); |
| } |
| |
| TEST(RiscvPagingTraitTests, GetSetExecutable) { |
| TestRiscvGetSetExecutable<RiscvPagingLevel::k4>(); |
| TestRiscvGetSetExecutable<RiscvPagingLevel::k3>(); |
| TestRiscvGetSetExecutable<RiscvPagingLevel::k2>(); |
| TestRiscvGetSetExecutable<RiscvPagingLevel::k1>(); |
| TestRiscvGetSetExecutable<RiscvPagingLevel::k0>(); |
| } |
| |
| template <RiscvPagingLevel Level> |
| void TestRiscvGetSetUserAccesible() { |
| // |
| // Usermode accessibility is controlled by the U bit. |
| // |
| arch::RiscvPageTableEntry<Level> entry; |
| |
| // If the entry is not terminal, then any page that maps through it can be |
| // user-accessible. |
| for (bool r : kBools) { |
| for (bool x : kBools) { |
| for (bool u : kBools) { |
| entry.set_reg_value(0).set_r(r).set_x(x).set_u(u); |
| EXPECT_EQ(entry.user_accessible(), u || (!r && !x)) |
| << "r=" << r << ", x=" << x << ", u=" << u; |
| } |
| } |
| } |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = |
| AccessPermissions{ |
| .readable = true, |
| .user_accessible = false, |
| }, |
| }); |
| EXPECT_FALSE(entry.u()); |
| EXPECT_FALSE(entry.user_accessible()); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = |
| AccessPermissions{ |
| .readable = true, |
| .user_accessible = true, |
| }, |
| }); |
| EXPECT_TRUE(entry.u()); |
| EXPECT_TRUE(entry.user_accessible()); |
| |
| // |
| // There are no intermediate knobs to disable permissions. |
| // |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = kRWXU, |
| }); |
| EXPECT_FALSE(entry.u()); |
| EXPECT_TRUE(entry.user_accessible()); |
| } |
| |
| TEST(RiscvPagingTraitTests, GetSetUserAccesible) { |
| TestRiscvGetSetUserAccesible<RiscvPagingLevel::k4>(); |
| TestRiscvGetSetUserAccesible<RiscvPagingLevel::k3>(); |
| TestRiscvGetSetUserAccesible<RiscvPagingLevel::k2>(); |
| TestRiscvGetSetUserAccesible<RiscvPagingLevel::k1>(); |
| TestRiscvGetSetUserAccesible<RiscvPagingLevel::k0>(); |
| } |
| |
| template <RiscvPagingLevel Level> |
| void TestRiscvGetSetTerminal() { |
| // Terminality is indicated by the R or X bit. |
| arch::RiscvPageTableEntry<Level> entry; |
| |
| for (bool r : kBools) { |
| for (bool x : kBools) { |
| entry.set_reg_value(0).set_r(r).set_x(x); |
| EXPECT_EQ(entry.terminal(), r || x) << "r=" << r << ", x=" << x; |
| } |
| } |
| |
| entry.set_reg_value(0).set_r(false).set_x(false); |
| EXPECT_FALSE(entry.terminal()); |
| |
| entry.set_reg_value(0).set_r(true).set_x(false); |
| EXPECT_TRUE(entry.terminal()); |
| |
| entry.set_reg_value(0).set_r(false).set_x(true); |
| EXPECT_TRUE(entry.terminal()); |
| |
| entry.set_reg_value(0).set_r(true).set_x(true); |
| EXPECT_TRUE(entry.terminal()); |
| |
| // |
| // Setting as non-terminal should always result in the R, W, and X bits being |
| // unset. |
| // |
| |
| for (unsigned int r = 0; r <= 1; ++r) { |
| for (unsigned int w = 0; w <= 1; ++w) { |
| for (unsigned int x = 0; x <= 1; ++x) { |
| entry.set_reg_value(0).set_r(r).set_w(w).set_x(x).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = kRWXU, |
| }); |
| EXPECT_FALSE(entry.terminal()); |
| EXPECT_FALSE(entry.r()); |
| EXPECT_FALSE(entry.w()); |
| EXPECT_FALSE(entry.x()); |
| } |
| } |
| } |
| |
| // |
| // Setting as terminal requires readability or executability. |
| // |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = |
| AccessPermissions{ |
| .readable = true, |
| .executable = false, |
| }, |
| }); |
| EXPECT_TRUE(entry.terminal()); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = |
| AccessPermissions{ |
| .readable = false, |
| .executable = true, |
| }, |
| }); |
| EXPECT_TRUE(entry.terminal()); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = |
| AccessPermissions{ |
| .readable = true, |
| .executable = true, |
| }, |
| }); |
| EXPECT_TRUE(entry.terminal()); |
| } |
| |
| TEST(RiscvPagingTraitTests, GetSetTerminal) { |
| TestRiscvGetSetTerminal<RiscvPagingLevel::k4>(); |
| TestRiscvGetSetTerminal<RiscvPagingLevel::k3>(); |
| TestRiscvGetSetTerminal<RiscvPagingLevel::k2>(); |
| TestRiscvGetSetTerminal<RiscvPagingLevel::k1>(); |
| TestRiscvGetSetTerminal<RiscvPagingLevel::k0>(); |
| } |
| |
| template <RiscvPagingLevel Level> |
| void TestRiscvGetSetAddress() { |
| // The next table or page address is given via the PPN field. |
| arch::RiscvPageTableEntry<Level> entry; |
| |
| entry.set_reg_value(0).set_ppn(0xaaaa'aaaa); |
| EXPECT_EQ(0x0aaa'aaaa'a000u, entry.address()); |
| |
| constexpr std::array<uint64_t, 5> kAddrs = { |
| 0x0000'0000'0000'1000u, // 4KiB-aligned |
| 0x0000'0000'0020'0000u, // 2MiB-aligned |
| 0x0000'0000'4000'0000u, // 1GiB-aligned |
| 0x0000'0080'0000'0000u, // 512GiB-aligned |
| 0x0001'0000'0000'0000u, // 256TiB-aligned |
| }; |
| |
| // Each address in kAddrs should be a valid table address. |
| for (uint64_t addr : kAddrs) { |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .address = addr, |
| .present = true, |
| .terminal = false, |
| .access = kRWXU, |
| }); |
| EXPECT_EQ(addr >> 12, entry.ppn()); |
| EXPECT_EQ(addr, entry.address()); |
| } |
| |
| // The index for the first valid page address (per alignment constraints). |
| constexpr size_t kFirstSupported = static_cast<size_t>(Level); |
| |
| for (size_t i = kFirstSupported; i < kAddrs.size(); ++i) { |
| uint64_t addr = kAddrs[i]; |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .address = addr, |
| .present = true, |
| .terminal = true, |
| .access = AccessPermissions{.readable = true}, |
| }); |
| EXPECT_EQ(addr >> 12, entry.ppn()); |
| EXPECT_EQ(addr, entry.address()); |
| } |
| } |
| |
| TEST(RiscvPagingTraitTests, GetSetAddress) { |
| TestRiscvGetSetAddress<RiscvPagingLevel::k4>(); |
| TestRiscvGetSetAddress<RiscvPagingLevel::k3>(); |
| TestRiscvGetSetAddress<RiscvPagingLevel::k2>(); |
| TestRiscvGetSetAddress<RiscvPagingLevel::k1>(); |
| TestRiscvGetSetAddress<RiscvPagingLevel::k0>(); |
| } |
| |
| template <RiscvPagingLevel Level> |
| void TestRiscvGetSetMemory() { |
| // |
| // The memory type is given via the PBMT field. |
| // |
| |
| arch::RiscvPageTableEntry<Level> entry; |
| |
| // Memory() can only be called on a terminal entry. |
| entry = entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = {.readable = true}, |
| }); |
| entry.set_pbmt(RiscvMemoryType::kPma); |
| EXPECT_EQ(RiscvMemoryType::kPma, entry.Memory({})); |
| entry.set_pbmt(RiscvMemoryType::kNc); |
| EXPECT_EQ(RiscvMemoryType::kNc, entry.Memory({})); |
| entry.set_pbmt(RiscvMemoryType::kIo); |
| EXPECT_EQ(RiscvMemoryType::kIo, entry.Memory({})); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = {.readable = true}, |
| .memory = RiscvMemoryType::kPma, |
| }); |
| EXPECT_EQ(RiscvMemoryType::kPma, entry.pbmt()); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = {.readable = true}, |
| .memory = RiscvMemoryType::kNc, |
| }); |
| EXPECT_EQ(RiscvMemoryType::kNc, entry.pbmt()); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = {.readable = true}, |
| .memory = RiscvMemoryType::kIo, |
| }); |
| EXPECT_EQ(RiscvMemoryType::kIo, entry.pbmt()); |
| |
| // |
| // If the entry is non-terminal, the setting of memory should have no effect. |
| // |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = false, |
| .memory = RiscvMemoryType::kNc, |
| }); |
| EXPECT_EQ(RiscvMemoryType::kPma, entry.pbmt()); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = false, |
| .memory = RiscvMemoryType::kIo, |
| }); |
| EXPECT_EQ(RiscvMemoryType::kPma, entry.pbmt()); |
| } |
| |
| TEST(RiscvPagingTraitTests, GetSetMemory) { |
| TestRiscvGetSetMemory<RiscvPagingLevel::k4>(); |
| TestRiscvGetSetMemory<RiscvPagingLevel::k3>(); |
| TestRiscvGetSetMemory<RiscvPagingLevel::k2>(); |
| TestRiscvGetSetMemory<RiscvPagingLevel::k1>(); |
| TestRiscvGetSetMemory<RiscvPagingLevel::k0>(); |
| } |
| |
| template <RiscvPagingLevel Level> |
| void TestRiscvSetImpliesAccessedAndDirty() { |
| // |
| // Whether the entry was 'accessed' or 'dirty' is given via the A and D |
| // fields, respectively; |
| // |
| |
| arch::RiscvPageTableEntry<Level> entry; |
| |
| // |
| // Non-terminal entries should not report as accessed or dirty. |
| // |
| |
| entry.set_reg_value(0).set_r(false).set_a(false); |
| EXPECT_FALSE(entry.accessed()); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = false, |
| }); |
| EXPECT_FALSE(entry.accessed()); |
| EXPECT_FALSE(entry.a()); |
| EXPECT_FALSE(entry.d()); |
| |
| entry.set_reg_value(0).set_r(true).set_a(true); |
| EXPECT_TRUE(entry.accessed()); |
| |
| // |
| // Terminal entries should report as accessed, and dirty when writable. |
| // |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = {.readable = true, .writable = false}, |
| }); |
| EXPECT_TRUE(entry.accessed()); |
| EXPECT_TRUE(entry.a()); |
| EXPECT_FALSE(entry.d()); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = {.readable = true, .writable = true}, |
| }); |
| EXPECT_TRUE(entry.accessed()); |
| EXPECT_TRUE(entry.a()); |
| EXPECT_TRUE(entry.d()); |
| } |
| |
| TEST(RiscvPagingTraitTests, GetSetAccessed) { |
| TestRiscvSetImpliesAccessedAndDirty<RiscvPagingLevel::k4>(); |
| TestRiscvSetImpliesAccessedAndDirty<RiscvPagingLevel::k3>(); |
| TestRiscvSetImpliesAccessedAndDirty<RiscvPagingLevel::k2>(); |
| TestRiscvSetImpliesAccessedAndDirty<RiscvPagingLevel::k1>(); |
| TestRiscvSetImpliesAccessedAndDirty<RiscvPagingLevel::k0>(); |
| } |
| |
| template <RiscvPagingLevel Level> |
| void TestRiscvGetSetGlobal() { |
| // |
| // Whether the entry was 'accessed' is given via the G field. |
| // |
| |
| arch::RiscvPageTableEntry<Level> entry; |
| |
| for (bool t : kBools) { |
| for (bool g : kBools) { |
| entry.set_reg_value(0).set_r(t).set_g(g); |
| EXPECT_EQ(t && g, entry.global()); |
| |
| entry.set_reg_value(0).Set({}, RiscvPagingSettings{ |
| .present = true, |
| .terminal = t, |
| .access = {.readable = true}, |
| .global = g, |
| }); |
| EXPECT_EQ(t && g, entry.global()); |
| } |
| } |
| } |
| |
| TEST(RiscvPagingTraitTests, GetSetGlobal) { |
| TestRiscvGetSetGlobal<RiscvPagingLevel::k4>(); |
| TestRiscvGetSetGlobal<RiscvPagingLevel::k3>(); |
| TestRiscvGetSetGlobal<RiscvPagingLevel::k2>(); |
| TestRiscvGetSetGlobal<RiscvPagingLevel::k1>(); |
| TestRiscvGetSetGlobal<RiscvPagingLevel::k0>(); |
| } |
| |
| template <X86PagingLevel Level> |
| void TestX86GetSetPresent() { |
| // Presence is controlled by the P bit. |
| arch::X86PagingStructure<Level> entry; |
| |
| entry.set_reg_value(0).set_p(false); |
| EXPECT_FALSE(entry.set_reg_value(0).present()); |
| |
| entry.set_reg_value(0).set_p(true); |
| EXPECT_TRUE(entry.present()); |
| |
| if constexpr (kX86LevelCanBeNonTerminal<Level>) { |
| entry.set_reg_value(0).Set( |
| {}, X86PagingSettings{.present = false, .terminal = false, .access = kRX}); |
| EXPECT_FALSE(entry.p()); |
| EXPECT_FALSE(entry.present()); |
| |
| entry.set_reg_value(0).Set( |
| {}, X86PagingSettings{.present = true, .terminal = false, .access = kRX}); |
| EXPECT_TRUE(entry.p()); |
| EXPECT_TRUE(entry.present()); |
| } |
| |
| if constexpr (kX86LevelCanBeTerminal<Level>) { |
| entry.set_reg_value(0).Set( |
| {}, X86PagingSettings{.present = false, .terminal = true, .access = kRX}); |
| EXPECT_FALSE(entry.p()); |
| EXPECT_FALSE(entry.present()); |
| |
| entry.set_reg_value(0).Set({}, |
| X86PagingSettings{.present = true, .terminal = true, .access = kRX}); |
| EXPECT_TRUE(entry.p()); |
| EXPECT_TRUE(entry.present()); |
| } |
| } |
| |
| TEST(X86PagingTraitTests, GetSetPresent) { |
| TestX86GetSetPresent<X86PagingLevel::kPml5Table>(); |
| TestX86GetSetPresent<X86PagingLevel::kPml4Table>(); |
| TestX86GetSetPresent<X86PagingLevel::kPageDirectoryPointerTable>(); |
| TestX86GetSetPresent<X86PagingLevel::kPageDirectory>(); |
| TestX86GetSetPresent<X86PagingLevel::kPageTable>(); |
| } |
| |
| template <X86PagingLevel Level> |
| void TestX86GetSetReadable() { |
| // |
| // Readability is a default. |
| // |
| arch::X86PagingStructure<Level> entry; |
| |
| entry.set_reg_value(0); |
| EXPECT_TRUE(entry.readable()); |
| |
| if constexpr (kX86LevelCanBeTerminal<Level>) { |
| entry.set_reg_value(0).Set({}, X86PagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = kRX, |
| }); |
| EXPECT_TRUE(entry.readable()); |
| } |
| } |
| |
| TEST(X86PagingTraitTests, GetSetReadable) { |
| TestX86GetSetReadable<X86PagingLevel::kPml5Table>(); |
| TestX86GetSetReadable<X86PagingLevel::kPml4Table>(); |
| TestX86GetSetReadable<X86PagingLevel::kPageDirectoryPointerTable>(); |
| TestX86GetSetReadable<X86PagingLevel::kPageDirectory>(); |
| TestX86GetSetReadable<X86PagingLevel::kPageTable>(); |
| } |
| |
| template <X86PagingLevel Level> |
| void TestX86GetSetWritable() { |
| // |
| // Writability is controlled by the R/W bit. |
| // |
| |
| arch::X86PagingStructure<Level> entry; |
| |
| entry.set_reg_value(0).set_r_w(false); |
| EXPECT_FALSE(entry.writable()); |
| |
| entry.set_reg_value(0).set_r_w(true); |
| EXPECT_TRUE(entry.writable()); |
| |
| if constexpr (kX86LevelCanBeNonTerminal<Level>) { |
| entry.set_reg_value(0).set_r_w(true).Set( |
| {}, |
| X86PagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = AccessPermissions{.readable = true, .writable = false, .executable = true}, |
| }); |
| EXPECT_FALSE(entry.r_w()); |
| EXPECT_FALSE(entry.writable()); |
| |
| entry.set_reg_value(0).Set( |
| {}, X86PagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = AccessPermissions{.readable = true, .writable = true, .executable = true}, |
| }); |
| EXPECT_TRUE(entry.r_w()); |
| EXPECT_TRUE(entry.writable()); |
| } |
| |
| if constexpr (kX86LevelCanBeTerminal<Level>) { |
| entry.set_reg_value(0).set_r_w(true).Set( |
| {}, |
| X86PagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = AccessPermissions{.readable = true, .writable = false, .executable = true}, |
| }); |
| EXPECT_FALSE(entry.r_w()); |
| EXPECT_FALSE(entry.writable()); |
| |
| entry.set_reg_value(0).Set( |
| {}, X86PagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = AccessPermissions{.readable = true, .writable = true, .executable = true}, |
| }); |
| EXPECT_TRUE(entry.r_w()); |
| EXPECT_TRUE(entry.writable()); |
| } |
| } |
| |
| TEST(X86PagingTraitTests, GetSetWritable) { |
| TestX86GetSetWritable<X86PagingLevel::kPml5Table>(); |
| TestX86GetSetWritable<X86PagingLevel::kPml4Table>(); |
| TestX86GetSetWritable<X86PagingLevel::kPageDirectoryPointerTable>(); |
| TestX86GetSetWritable<X86PagingLevel::kPageDirectory>(); |
| TestX86GetSetWritable<X86PagingLevel::kPageTable>(); |
| } |
| |
| template <X86PagingLevel Level> |
| void TestX86GetSetExecutable() { |
| // |
| // Executability is controlled by the XD bit. |
| // |
| |
| arch::X86PagingStructure<Level> entry; |
| |
| entry.set_reg_value(0).set_xd(true); |
| EXPECT_FALSE(entry.executable()); |
| |
| entry.set_reg_value(0).set_xd(false); |
| EXPECT_TRUE(entry.executable()); |
| |
| if constexpr (kX86LevelCanBeNonTerminal<Level>) { |
| entry.set_reg_value(0).set_xd(false).Set( |
| {}, X86PagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = AccessPermissions{.readable = true, .executable = false}, |
| }); |
| EXPECT_TRUE(entry.xd()); |
| EXPECT_FALSE(entry.executable()); |
| |
| entry.set_reg_value(0).set_xd(false).Set({}, X86PagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = kRX, |
| }); |
| EXPECT_FALSE(entry.xd()); |
| } |
| |
| if constexpr (kX86LevelCanBeTerminal<Level>) { |
| entry.set_reg_value(0).set_xd(false).Set( |
| {}, X86PagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = AccessPermissions{.readable = true, .executable = false}, |
| }); |
| EXPECT_TRUE(entry.xd()); |
| EXPECT_FALSE(entry.executable()); |
| |
| entry.set_reg_value(0).set_xd(false).Set({}, X86PagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = kRX, |
| }); |
| EXPECT_FALSE(entry.xd()); |
| EXPECT_TRUE(entry.executable()); |
| } |
| } |
| |
| TEST(X86PagingTraitTests, GetSetExecutable) { |
| TestX86GetSetExecutable<X86PagingLevel::kPml5Table>(); |
| TestX86GetSetExecutable<X86PagingLevel::kPml4Table>(); |
| TestX86GetSetExecutable<X86PagingLevel::kPageDirectoryPointerTable>(); |
| TestX86GetSetExecutable<X86PagingLevel::kPageDirectory>(); |
| TestX86GetSetExecutable<X86PagingLevel::kPageTable>(); |
| } |
| |
| template <X86PagingLevel Level> |
| void TestX86GetSetUserAccessible() { |
| // |
| // Usermode accessibility is controlled by the U/S bit. |
| // |
| |
| arch::X86PagingStructure<Level> entry; |
| |
| entry.set_reg_value(0).set_u_s(false); |
| EXPECT_FALSE(entry.user_accessible()); |
| |
| entry.set_reg_value(0).set_u_s(true); |
| EXPECT_TRUE(entry.user_accessible()); |
| |
| if constexpr (kX86LevelCanBeNonTerminal<Level>) { |
| entry.set_reg_value(0).set_u_s(true).Set( |
| {}, |
| X86PagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = |
| AccessPermissions{.readable = true, .executable = true, .user_accessible = false}, |
| }); |
| EXPECT_FALSE(entry.u_s()); |
| EXPECT_FALSE(entry.user_accessible()); |
| |
| entry.set_reg_value(0).Set({}, X86PagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = AccessPermissions{.readable = true, |
| .executable = true, |
| .user_accessible = true}, |
| }); |
| EXPECT_TRUE(entry.u_s()); |
| EXPECT_TRUE(entry.user_accessible()); |
| } |
| |
| if constexpr (kX86LevelCanBeTerminal<Level>) { |
| entry.set_reg_value(0).set_u_s(true).Set( |
| {}, |
| X86PagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = |
| AccessPermissions{.readable = true, .executable = true, .user_accessible = false}, |
| }); |
| EXPECT_FALSE(entry.u_s()); |
| EXPECT_FALSE(entry.user_accessible()); |
| |
| entry.set_reg_value(0).Set({}, X86PagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = |
| AccessPermissions{ |
| .readable = true, |
| .executable = true, |
| .user_accessible = true, |
| }, |
| }); |
| EXPECT_TRUE(entry.u_s()); |
| EXPECT_TRUE(entry.user_accessible()); |
| } |
| } |
| |
| TEST(X86PagingTraitTests, GetSetUserAccessible) { |
| TestX86GetSetUserAccessible<X86PagingLevel::kPml5Table>(); |
| TestX86GetSetUserAccessible<X86PagingLevel::kPml4Table>(); |
| TestX86GetSetUserAccessible<X86PagingLevel::kPageDirectoryPointerTable>(); |
| TestX86GetSetUserAccessible<X86PagingLevel::kPageDirectory>(); |
| TestX86GetSetUserAccessible<X86PagingLevel::kPageTable>(); |
| } |
| |
| template <X86PagingLevel Level> |
| void TestX86GetSetTerminal() { |
| // The PS bit controls terminality for PDPT or PD entries. |
| constexpr bool kPdptOrkPd = Level == X86PagingLevel::kPageDirectoryPointerTable || |
| Level == X86PagingLevel::kPageDirectory; |
| |
| arch::X86PagingStructure<Level> entry; |
| |
| // Page table entries are terminal by default. |
| if constexpr (Level == X86PagingLevel::kPageTable) { |
| entry.set_reg_value(0); |
| EXPECT_TRUE(entry.terminal()); |
| } |
| |
| if constexpr (kX86LevelCanBeNonTerminal<Level>) { |
| entry.set_reg_value(0).Set( |
| {}, X86PagingSettings{.present = true, .terminal = false, .access = kRX}); |
| EXPECT_FALSE(entry.terminal()); |
| if constexpr (kPdptOrkPd) { |
| EXPECT_FALSE(*entry.ps()); |
| } else { |
| EXPECT_EQ(std::nullopt, entry.ps()); |
| } |
| } |
| |
| if constexpr (kX86LevelCanBeTerminal<Level>) { |
| entry.set_reg_value(0).Set({}, |
| X86PagingSettings{.present = true, .terminal = true, .access = kRX}); |
| EXPECT_TRUE(entry.terminal()); |
| if constexpr (kPdptOrkPd) { |
| EXPECT_TRUE(*entry.ps()); |
| } else { |
| EXPECT_EQ(std::nullopt, entry.ps()); |
| } |
| } |
| } |
| |
| TEST(X86PagingTraitTests, GetSetTerminal) { |
| TestX86GetSetTerminal<X86PagingLevel::kPml5Table>(); |
| TestX86GetSetTerminal<X86PagingLevel::kPml4Table>(); |
| TestX86GetSetTerminal<X86PagingLevel::kPageDirectoryPointerTable>(); |
| TestX86GetSetTerminal<X86PagingLevel::kPageDirectory>(); |
| TestX86GetSetTerminal<X86PagingLevel::kPageTable>(); |
| } |
| |
| template <X86PagingLevel Level> |
| void TestX86GetSetAddress() { |
| arch::X86PagingStructure<Level> entry; |
| |
| constexpr std::array<uint64_t, 5> kAddrs = { |
| 0x0000'0000'0000'1000u, // 4KiB-aligned |
| 0x0000'0000'0020'0000u, // 2MiB-aligned |
| 0x0000'0000'4000'0000u, // 1GiB-aligned |
| 0x0000'0080'0000'0000u, // 512GiB-aligned |
| 0x0001'0000'0000'0000u, // 256TiB-aligned |
| }; |
| |
| if constexpr (kX86LevelCanBeNonTerminal<Level>) { |
| // Each address in kAddrs should be a valid table address. |
| for (size_t i = 0; i < kAddrs.size(); ++i) { |
| uint64_t addr = kAddrs[i]; |
| entry.set_reg_value(0).Set({}, X86PagingSettings{ |
| .address = addr, |
| .present = true, |
| .terminal = false, |
| .access = kRX, |
| }); |
| EXPECT_EQ(addr, entry.address()); |
| } |
| } |
| |
| if constexpr (kX86LevelCanBeTerminal<Level>) { |
| // The index for the first valid page address (per alignment constraints). |
| constexpr size_t kFirstSupported = Level == X86PagingLevel::kPageTable ? 0 |
| : Level == X86PagingLevel::kPageDirectory ? 1 |
| : 2; |
| for (size_t i = kFirstSupported; i < kAddrs.size(); ++i) { |
| uint64_t addr = kAddrs[i]; |
| entry.set_reg_value(0).Set({}, X86PagingSettings{ |
| .address = addr, |
| .present = true, |
| .terminal = true, |
| .access = kRX, |
| }); |
| EXPECT_EQ(addr, entry.address()); |
| } |
| } |
| } |
| |
| TEST(X86PagingTraitTests, GetSetAddress) { |
| TestX86GetSetAddress<X86PagingLevel::kPml5Table>(); |
| TestX86GetSetAddress<X86PagingLevel::kPml4Table>(); |
| TestX86GetSetAddress<X86PagingLevel::kPageDirectoryPointerTable>(); |
| TestX86GetSetAddress<X86PagingLevel::kPageDirectory>(); |
| TestX86GetSetAddress<X86PagingLevel::kPageTable>(); |
| } |
| |
| template <X86PagingLevel Level> |
| void TestX86SetImpliesAccessedAndDirty() { |
| arch::X86PagingStructure<Level> entry; |
| |
| entry.set_reg_value(0).set_a(false); |
| EXPECT_FALSE(entry.accessed()); |
| |
| entry.set_reg_value(0).set_a(true); |
| EXPECT_TRUE(entry.accessed()); |
| |
| // A Set() entry should always report as accessed and dirty. |
| |
| if constexpr (kX86LevelCanBeNonTerminal<Level>) { |
| entry.set_reg_value(0).Set({}, X86PagingSettings{ |
| .present = true, |
| .terminal = false, |
| }); |
| EXPECT_TRUE(entry.accessed()); |
| EXPECT_TRUE(entry.a()); |
| EXPECT_TRUE(entry.d()); |
| } |
| |
| if constexpr (kX86LevelCanBeTerminal<Level>) { |
| entry.set_reg_value(0).Set({}, X86PagingSettings{ |
| .present = true, |
| .terminal = true, |
| }); |
| EXPECT_TRUE(entry.accessed()); |
| EXPECT_TRUE(entry.a()); |
| EXPECT_TRUE(entry.d()); |
| } |
| } |
| |
| TEST(X86PagingTraitTests, SetImpliesAccessedAndDirty) { |
| TestX86SetImpliesAccessedAndDirty<X86PagingLevel::kPml5Table>(); |
| TestX86SetImpliesAccessedAndDirty<X86PagingLevel::kPml4Table>(); |
| TestX86SetImpliesAccessedAndDirty<X86PagingLevel::kPageDirectoryPointerTable>(); |
| TestX86SetImpliesAccessedAndDirty<X86PagingLevel::kPageDirectory>(); |
| TestX86SetImpliesAccessedAndDirty<X86PagingLevel::kPageTable>(); |
| } |
| |
| template <X86PagingLevel Level> |
| void TestX86GetSetGlobal() { |
| arch::X86PagingStructure<Level> entry; |
| |
| // Non-terminal entries ignore settings of `global = true`. |
| if constexpr (kX86LevelCanBeNonTerminal<Level>) { |
| entry.set_reg_value(0) |
| .Set({}, X86PagingSettings{.present = true, .terminal = false}) |
| .set_g(false); |
| EXPECT_FALSE(entry.global()); |
| |
| entry.set_reg_value(0) |
| .Set({}, X86PagingSettings{.present = true, .terminal = false}) |
| .set_g(true); |
| EXPECT_FALSE(entry.global()); |
| |
| entry.set_reg_value(0).Set({}, X86PagingSettings{ |
| .present = true, |
| .terminal = false, |
| .global = false, |
| }); |
| EXPECT_FALSE(entry.global()); |
| |
| entry.set_reg_value(0).Set({}, X86PagingSettings{ |
| .present = true, |
| .terminal = false, |
| .global = true, |
| }); |
| EXPECT_FALSE(entry.global()); |
| } |
| |
| if constexpr (kX86LevelCanBeTerminal<Level>) { |
| entry.set_reg_value(0) |
| .Set({}, X86PagingSettings{.present = true, .terminal = true}) |
| .set_g(false); |
| EXPECT_FALSE(entry.global()); |
| |
| entry.set_reg_value(0) |
| .Set({}, X86PagingSettings{.present = true, .terminal = true}) |
| .set_g(true); |
| EXPECT_TRUE(entry.global()); |
| |
| entry.set_reg_value(0).Set({}, X86PagingSettings{ |
| .present = true, |
| .terminal = true, |
| .global = false, |
| }); |
| EXPECT_FALSE(entry.global()); |
| |
| entry.set_reg_value(0).Set({}, X86PagingSettings{ |
| .present = true, |
| .terminal = true, |
| .global = true, |
| }); |
| EXPECT_TRUE(entry.global()); |
| } |
| } |
| |
| TEST(X86PagingTraitTests, GetSetGlobal) { |
| TestX86GetSetGlobal<X86PagingLevel::kPml5Table>(); |
| TestX86GetSetGlobal<X86PagingLevel::kPml4Table>(); |
| TestX86GetSetGlobal<X86PagingLevel::kPageDirectoryPointerTable>(); |
| TestX86GetSetGlobal<X86PagingLevel::kPageDirectory>(); |
| TestX86GetSetGlobal<X86PagingLevel::kPageTable>(); |
| } |
| |
| template <ArmPagingLevel Level> |
| void TestArmGetSetPresent() { |
| ArmTableEntry<Level> entry; |
| |
| entry.set_reg_value(0).set_valid(false); |
| EXPECT_FALSE(entry.present()); |
| |
| entry.set_reg_value(0).set_valid(true); |
| EXPECT_TRUE(entry.present()); |
| |
| entry.set_reg_value(0).set_valid(true).Set({}, ArmPagingSettings{.present = false}); |
| EXPECT_FALSE(entry.valid()); |
| EXPECT_FALSE(entry.present()); |
| |
| if constexpr (kArmLevelCanBeNonTerminal<Level>) { |
| entry.set_reg_value(0).set_valid(false).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = false, |
| }); |
| EXPECT_TRUE(entry.valid()); |
| EXPECT_TRUE(entry.present()); |
| } |
| |
| if constexpr (kArmLevelCanBeTerminal<Level>) { |
| entry.set_reg_value(0).set_valid(false).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = true, |
| }); |
| EXPECT_TRUE(entry.valid()); |
| EXPECT_TRUE(entry.present()); |
| } |
| } |
| |
| TEST(ArmPagingTraitTests, GetSetPresent) { |
| TestArmGetSetPresent<ArmPagingLevel::k0>(); |
| TestArmGetSetPresent<ArmPagingLevel::k1>(); |
| TestArmGetSetPresent<ArmPagingLevel::k2>(); |
| TestArmGetSetPresent<ArmPagingLevel::k3>(); |
| } |
| |
| template <ArmPagingLevel Level> |
| void TestArmDescriptorTypes() { |
| using Format = arch::ArmAddressTranslationDescriptorFormat; |
| |
| ArmTableEntry<Level> entry; |
| |
| // The table-or-page format represents a page at the last level and a table |
| // at all others. |
| entry.set_reg_value(0).set_format(Format::kTableOrPage); |
| if constexpr (Level == ArmPagingLevel::k3) { |
| EXPECT_FALSE(entry.IsTable()); |
| EXPECT_TRUE(entry.IsPage()); |
| EXPECT_FALSE(entry.IsBlock()); |
| |
| static_cast<void>(entry.AsPage()); |
| } else { |
| EXPECT_TRUE(entry.IsTable()); |
| EXPECT_FALSE(entry.IsPage()); |
| EXPECT_FALSE(entry.IsBlock()); |
| |
| static_cast<void>(entry.AsTable()); |
| } |
| |
| // The block format only represents a block for levels 1 and 2. |
| entry.set_reg_value(0).set_format(Format::kBlock); |
| if constexpr (Level == ArmPagingLevel::k1 || Level == ArmPagingLevel::k2) { |
| EXPECT_FALSE(entry.IsTable()); |
| EXPECT_FALSE(entry.IsPage()); |
| EXPECT_TRUE(entry.IsBlock()); |
| |
| static_cast<void>(entry.AsBlock()); |
| } else { |
| EXPECT_FALSE(entry.IsTable()); |
| EXPECT_FALSE(entry.IsPage()); |
| EXPECT_FALSE(entry.IsBlock()); |
| } |
| } |
| |
| TEST(ArmPagingTraitTests, ArmDescriptorTypes) { |
| TestArmDescriptorTypes<ArmPagingLevel::k0>(); |
| TestArmDescriptorTypes<ArmPagingLevel::k1>(); |
| TestArmDescriptorTypes<ArmPagingLevel::k2>(); |
| TestArmDescriptorTypes<ArmPagingLevel::k3>(); |
| } |
| |
| template <ArmPagingLevel Level> |
| void TestArmGetSetTerminal() { |
| ArmTableEntry<Level> entry; |
| |
| if constexpr (Level == ArmPagingLevel::k3) { |
| entry.set_reg_value(0).SetAsPage(); |
| EXPECT_TRUE(entry.terminal()); |
| |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{.present = true, .terminal = true}); |
| EXPECT_TRUE(entry.terminal()); |
| EXPECT_TRUE(entry.IsPage()); |
| } |
| |
| if constexpr (Level == ArmPagingLevel::k1 || Level == ArmPagingLevel::k2) { |
| entry.set_reg_value(0).SetAsBlock(); |
| EXPECT_TRUE(entry.terminal()); |
| |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{.present = true, .terminal = true}); |
| EXPECT_TRUE(entry.terminal()); |
| EXPECT_TRUE(entry.IsBlock()); |
| } |
| |
| if constexpr (Level != ArmPagingLevel::k3) { |
| entry.set_reg_value(0).SetAsTable(); |
| EXPECT_FALSE(entry.terminal()); |
| |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{.present = true, .terminal = false}); |
| EXPECT_FALSE(entry.terminal()); |
| EXPECT_TRUE(entry.IsTable()); |
| } |
| } |
| |
| TEST(ArmPagingTraitTests, GetSetTerminal) { |
| TestArmGetSetTerminal<ArmPagingLevel::k0>(); |
| TestArmGetSetTerminal<ArmPagingLevel::k1>(); |
| TestArmGetSetTerminal<ArmPagingLevel::k2>(); |
| TestArmGetSetTerminal<ArmPagingLevel::k3>(); |
| } |
| |
| template <ArmPagingLevel Level> |
| void TestArmGetSetReadable() { |
| ArmTableEntry<Level> entry; |
| |
| if constexpr (kArmLevelCanBeNonTerminal<Level>) { |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = AccessPermissions{.readable = true}, |
| }); |
| EXPECT_TRUE(entry.readable()); |
| } |
| |
| if constexpr (kArmLevelCanBeTerminal<Level>) { |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = AccessPermissions{.readable = true}, |
| }); |
| EXPECT_TRUE(entry.readable()); |
| } |
| } |
| |
| TEST(ArmPagingTraitTests, GetSetReadable) { |
| TestArmGetSetReadable<ArmPagingLevel::k0>(); |
| TestArmGetSetReadable<ArmPagingLevel::k1>(); |
| TestArmGetSetReadable<ArmPagingLevel::k2>(); |
| TestArmGetSetReadable<ArmPagingLevel::k3>(); |
| } |
| |
| template <ArmPagingLevel Level> |
| void TestArmGetSetWritableOrUserAccessible() { |
| using ArmAccessPermissions = arch::ArmAddressTranslationAccessPermissions; |
| using ArmTableAccessPermissions = arch::ArmAddressTranslationTableAccessPermissions; |
| |
| ArmTableEntry<Level> entry; |
| |
| if constexpr (kArmLevelCanBeTable<Level>) { |
| entry.set_reg_value(0).SetAsTable().set_ap_table(ArmTableAccessPermissions::kNoEffect); |
| EXPECT_TRUE(entry.writable()); |
| EXPECT_TRUE(entry.user_accessible()); |
| |
| entry.set_reg_value(0).SetAsTable().set_ap_table(ArmTableAccessPermissions::kNoEl0Access); |
| EXPECT_TRUE(entry.writable()); |
| EXPECT_FALSE(entry.user_accessible()); |
| |
| entry.set_reg_value(0).SetAsTable().set_ap_table(ArmTableAccessPermissions::kNoWriteAccess); |
| EXPECT_FALSE(entry.writable()); |
| EXPECT_TRUE(entry.user_accessible()); |
| |
| entry.set_reg_value(0).SetAsTable().set_ap_table( |
| ArmTableAccessPermissions::kNoWriteOrEl0Access); |
| EXPECT_FALSE(entry.writable()); |
| EXPECT_FALSE(entry.user_accessible()); |
| } |
| |
| if constexpr (kArmLevelCanBePage<Level>) { |
| entry.set_reg_value(0).SetAsPage().set_ap(ArmAccessPermissions::kSupervisorReadWrite); |
| EXPECT_TRUE(entry.writable()); |
| EXPECT_FALSE(entry.user_accessible()); |
| |
| entry.set_reg_value(0).SetAsPage().set_ap(ArmAccessPermissions::kReadWrite); |
| EXPECT_TRUE(entry.writable()); |
| EXPECT_TRUE(entry.user_accessible()); |
| |
| entry.set_reg_value(0).SetAsPage().set_ap(ArmAccessPermissions::kSupervisorReadOnly); |
| EXPECT_FALSE(entry.writable()); |
| EXPECT_FALSE(entry.user_accessible()); |
| |
| entry.set_reg_value(0).SetAsPage().set_ap(ArmAccessPermissions::kReadOnly); |
| EXPECT_FALSE(entry.writable()); |
| EXPECT_TRUE(entry.user_accessible()); |
| } |
| |
| if constexpr (kArmLevelCanBeBlock<Level>) { |
| entry.set_reg_value(0).SetAsBlock().set_ap(ArmAccessPermissions::kSupervisorReadWrite); |
| EXPECT_TRUE(entry.writable()); |
| EXPECT_FALSE(entry.user_accessible()); |
| |
| entry.set_reg_value(0).SetAsBlock().set_ap(ArmAccessPermissions::kReadWrite); |
| EXPECT_TRUE(entry.writable()); |
| EXPECT_TRUE(entry.user_accessible()); |
| |
| entry.set_reg_value(0).SetAsBlock().set_ap(ArmAccessPermissions::kSupervisorReadOnly); |
| EXPECT_FALSE(entry.writable()); |
| EXPECT_FALSE(entry.user_accessible()); |
| |
| entry.set_reg_value(0).SetAsBlock().set_ap(ArmAccessPermissions::kReadOnly); |
| EXPECT_FALSE(entry.writable()); |
| EXPECT_TRUE(entry.user_accessible()); |
| } |
| |
| if constexpr (kArmLevelCanBeNonTerminal<Level>) { |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = |
| AccessPermissions{ |
| .readable = true, |
| .writable = false, |
| .user_accessible = false, |
| }, |
| }); |
| EXPECT_FALSE(entry.writable()); |
| EXPECT_FALSE(entry.user_accessible()); |
| EXPECT_EQ(entry.AsTable().ap_table(), ArmTableAccessPermissions::kNoWriteOrEl0Access); |
| |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = |
| AccessPermissions{ |
| .readable = true, |
| .writable = false, |
| .user_accessible = true, |
| }, |
| }); |
| EXPECT_FALSE(entry.writable()); |
| EXPECT_TRUE(entry.user_accessible()); |
| EXPECT_EQ(entry.AsTable().ap_table(), ArmTableAccessPermissions::kNoWriteAccess); |
| |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = |
| AccessPermissions{ |
| .readable = true, |
| .writable = true, |
| .user_accessible = false, |
| }, |
| }); |
| EXPECT_TRUE(entry.writable()); |
| EXPECT_FALSE(entry.user_accessible()); |
| EXPECT_EQ(entry.AsTable().ap_table(), ArmTableAccessPermissions::kNoEl0Access); |
| |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = |
| AccessPermissions{ |
| .readable = true, |
| .writable = true, |
| .user_accessible = true, |
| }, |
| }); |
| EXPECT_TRUE(entry.writable()); |
| EXPECT_TRUE(entry.user_accessible()); |
| EXPECT_EQ(entry.AsTable().ap_table(), ArmTableAccessPermissions::kNoEffect); |
| } |
| |
| if constexpr (kArmLevelCanBeTerminal<Level>) { |
| auto get_ap = [](auto& entry) { |
| if (entry.IsPage()) { |
| return entry.AsPage().ap(); |
| } |
| return entry.AsBlock().ap(); |
| }; |
| |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = |
| AccessPermissions{ |
| .readable = true, |
| .writable = false, |
| .user_accessible = false, |
| }, |
| }); |
| EXPECT_FALSE(entry.writable()); |
| EXPECT_FALSE(entry.user_accessible()); |
| EXPECT_EQ(get_ap(entry), ArmAccessPermissions::kSupervisorReadOnly); |
| |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = |
| AccessPermissions{ |
| .readable = true, |
| .writable = false, |
| .user_accessible = true, |
| }, |
| }); |
| EXPECT_FALSE(entry.writable()); |
| EXPECT_TRUE(entry.user_accessible()); |
| EXPECT_EQ(get_ap(entry), ArmAccessPermissions::kReadOnly); |
| |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = |
| AccessPermissions{ |
| .readable = true, |
| .writable = true, |
| .user_accessible = false, |
| }, |
| }); |
| EXPECT_TRUE(entry.writable()); |
| EXPECT_FALSE(entry.user_accessible()); |
| EXPECT_EQ(get_ap(entry), ArmAccessPermissions::kSupervisorReadWrite); |
| |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = |
| AccessPermissions{ |
| .readable = true, |
| .writable = true, |
| .user_accessible = true, |
| }, |
| }); |
| EXPECT_TRUE(entry.writable()); |
| EXPECT_TRUE(entry.user_accessible()); |
| EXPECT_EQ(get_ap(entry), ArmAccessPermissions::kReadWrite); |
| } |
| } |
| |
| TEST(ArmPagingTraitTests, GetSetWritableOrUserAccessible) { |
| TestArmGetSetWritableOrUserAccessible<ArmPagingLevel::k0>(); |
| TestArmGetSetWritableOrUserAccessible<ArmPagingLevel::k1>(); |
| TestArmGetSetWritableOrUserAccessible<ArmPagingLevel::k2>(); |
| TestArmGetSetWritableOrUserAccessible<ArmPagingLevel::k3>(); |
| } |
| |
| template <ArmPagingLevel Level> |
| void TestArmGetSetExecutable() { |
| ArmTableEntry<Level> entry; |
| |
| // |
| // `executable()` gives supervisor executability and so should be |
| // independent of UXN/UXN_TABLE. |
| // |
| |
| if constexpr (kArmLevelCanBeTable<Level>) { |
| for (bool u : kBools) { |
| entry.set_reg_value(0).SetAsTable().set_pxn_table(false).set_uxn_table(u); |
| EXPECT_TRUE(entry.executable()); |
| |
| entry.set_reg_value(0).SetAsTable().set_pxn_table(true).set_uxn_table(u); |
| EXPECT_FALSE(entry.executable()); |
| } |
| } |
| |
| if constexpr (kArmLevelCanBePage<Level>) { |
| for (bool u : kBools) { |
| entry.set_reg_value(0).SetAsPage().set_pxn(false).set_uxn(u); |
| EXPECT_TRUE(entry.executable()); |
| |
| entry.set_reg_value(0).SetAsPage().set_pxn(true).set_uxn(u); |
| EXPECT_FALSE(entry.executable()); |
| } |
| } |
| |
| if constexpr (kArmLevelCanBeBlock<Level>) { |
| for (bool u : kBools) { |
| entry.set_reg_value(0).SetAsBlock().set_pxn(false).set_uxn(u); |
| EXPECT_TRUE(entry.executable()); |
| |
| entry.set_reg_value(0).SetAsBlock().set_pxn(true).set_uxn(u); |
| EXPECT_FALSE(entry.executable()); |
| } |
| } |
| |
| // |
| // Post-Set() values of UXN/UXN_TABLE should be 1, as user-executable pages |
| // are not yet supported. |
| // |
| |
| if constexpr (kArmLevelCanBeNonTerminal<Level>) { |
| for (bool u : kBools) { |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = |
| AccessPermissions{ |
| .readable = true, |
| .executable = false, |
| .user_accessible = u, |
| }, |
| }); |
| EXPECT_FALSE(entry.executable()); |
| EXPECT_TRUE(entry.AsTable().pxn_table()); |
| EXPECT_TRUE(entry.AsTable().uxn_table()); |
| |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = |
| AccessPermissions{ |
| .readable = true, |
| .executable = true, |
| .user_accessible = u, |
| }, |
| }); |
| EXPECT_TRUE(entry.executable()); |
| EXPECT_FALSE(entry.AsTable().pxn_table()); |
| EXPECT_TRUE(entry.AsTable().uxn_table()); |
| } |
| } |
| |
| if constexpr (kArmLevelCanBeTerminal<Level>) { |
| auto get_pxn = [](auto& entry) { |
| if (entry.IsPage()) { |
| return entry.AsPage().pxn(); |
| } |
| return entry.AsBlock().pxn(); |
| }; |
| auto get_uxn = [](auto& entry) { |
| if (entry.IsPage()) { |
| return entry.AsPage().uxn(); |
| } |
| return entry.AsBlock().uxn(); |
| }; |
| |
| for (bool u : kBools) { |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = |
| AccessPermissions{ |
| .readable = true, |
| .executable = false, |
| .user_accessible = u, |
| }, |
| }); |
| EXPECT_FALSE(entry.executable()); |
| EXPECT_TRUE(get_pxn(entry)); |
| EXPECT_TRUE(get_uxn(entry)); |
| |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = |
| AccessPermissions{ |
| .readable = true, |
| .executable = true, |
| .user_accessible = u, |
| }, |
| }); |
| EXPECT_TRUE(entry.executable()); |
| EXPECT_FALSE(get_pxn(entry)); |
| EXPECT_TRUE(get_uxn(entry)); |
| } |
| } |
| } |
| |
| TEST(ArmPagingTraitTests, GetSetExecutable) { |
| TestArmGetSetExecutable<ArmPagingLevel::k0>(); |
| TestArmGetSetExecutable<ArmPagingLevel::k1>(); |
| TestArmGetSetExecutable<ArmPagingLevel::k2>(); |
| TestArmGetSetExecutable<ArmPagingLevel::k3>(); |
| } |
| |
| template <ArmPagingLevel Level> |
| void TestArmGetSetAddress() { |
| ArmTableEntry<Level> entry; |
| |
| constexpr std::array<uint64_t, 4> kAddrs = { |
| 0x0000'0000'0000'1000u, // 4KiB-aligned |
| 0x0000'0000'0020'0000u, // 2MiB-aligned |
| 0x0000'0000'4000'0000u, // 1GiB-aligned |
| 0x0000'0080'0000'0000u, // 512GiB-aligned |
| }; |
| |
| if constexpr (kArmLevelCanBeNonTerminal<Level>) { |
| for (size_t i = 0; i < kAddrs.size(); ++i) { |
| uint64_t addr = kAddrs[i]; |
| |
| entry.set_reg_value(0).SetAsTable().set_table_address(addr); |
| EXPECT_EQ(addr, entry.address()); |
| |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .address = addr, |
| .present = true, |
| .terminal = false, |
| }); |
| EXPECT_EQ(addr, entry.address()); |
| EXPECT_EQ(addr, entry.AsTable().table_address()); |
| } |
| } |
| |
| if constexpr (kArmLevelCanBeTerminal<Level>) { |
| constexpr size_t kFirstValid = []() { |
| switch (Level) { |
| case ArmPagingLevel::k1: |
| return 2; |
| case ArmPagingLevel::k2: |
| return 1; |
| case ArmPagingLevel::k3: |
| return 0; |
| } |
| }(); |
| for (size_t i = kFirstValid; i < kAddrs.size(); ++i) { |
| uint64_t addr = kAddrs[i]; |
| |
| if constexpr (Level == ArmPagingLevel::k3) { |
| entry.set_reg_value(0).SetAsPage().set_output_address(addr); |
| } else { |
| entry.set_reg_value(0).SetAsBlock().set_output_address(addr); |
| } |
| EXPECT_EQ(addr, entry.address()); |
| |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .address = addr, |
| .present = true, |
| .terminal = true, |
| }); |
| EXPECT_EQ(addr, entry.address()); |
| if constexpr (Level == ArmPagingLevel::k3) { |
| EXPECT_EQ(addr, entry.AsPage().output_address()); |
| } else { |
| EXPECT_EQ(addr, entry.AsBlock().output_address()); |
| } |
| } |
| } |
| } |
| |
| TEST(ArmPagingTraitTests, GetSetAddress) { |
| TestArmGetSetAddress<ArmPagingLevel::k0>(); |
| TestArmGetSetAddress<ArmPagingLevel::k1>(); |
| TestArmGetSetAddress<ArmPagingLevel::k2>(); |
| TestArmGetSetAddress<ArmPagingLevel::k3>(); |
| } |
| |
| template <ArmPagingLevel Level> |
| void TestArmGetSetMemory() { |
| constexpr arch::ArmMairAttribute kNormalMemory = arch::ArmMairNormalAttribute{ |
| .inner = arch::ArmCacheabilityAttribute::kWriteBackReadWriteAllocate, |
| .outer = arch::ArmCacheabilityAttribute::kWriteBackReadWriteAllocate, |
| }; |
| |
| constexpr arch::ArmMairAttribute kMmioMemory = |
| arch::ArmDeviceMemory::kNonGatheringNonReorderingEarlyAck; |
| |
| const ArmSystemState kState = { |
| .mair = arch::ArmMemoryAttrIndirectionRegister::Get() |
| .FromValue(0) |
| .SetAttribute(0, kNormalMemory) |
| .SetAttribute(1, kMmioMemory), |
| .shareability = arch::ArmShareabilityAttribute::kOuter, |
| }; |
| |
| ArmTableEntry<Level> entry; |
| |
| if constexpr (kArmLevelCanBePage<Level>) { |
| entry.set_reg_value(0).SetAsPage().set_attr_index(0); |
| EXPECT_EQ(kNormalMemory, entry.Memory(kState)); |
| |
| entry.set_reg_value(0).SetAsPage().set_attr_index(1); |
| EXPECT_EQ(kMmioMemory, entry.Memory(kState)); |
| } else if constexpr (kArmLevelCanBeBlock<Level>) { |
| entry.set_reg_value(0).SetAsBlock().set_attr_index(0); |
| EXPECT_EQ(kNormalMemory, entry.Memory(kState)); |
| |
| entry.set_reg_value(0).SetAsBlock().set_attr_index(1); |
| EXPECT_EQ(kMmioMemory, entry.Memory(kState)); |
| } |
| |
| if constexpr (kArmLevelCanBeTerminal<Level>) { |
| auto attr_index = [](auto& entry) { |
| if (entry.IsPage()) { |
| return entry.AsPage().attr_index(); |
| } |
| return entry.AsBlock().attr_index(); |
| }; |
| |
| entry.set_reg_value(0).Set(kState, ArmPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = {.readable = true}, |
| .memory = kNormalMemory, |
| }); |
| EXPECT_EQ(0u, attr_index(entry)); |
| EXPECT_EQ(kNormalMemory, entry.Memory(kState)); |
| |
| entry.set_reg_value(0).Set(kState, ArmPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = {.readable = true}, |
| .memory = kMmioMemory, |
| }); |
| EXPECT_EQ(1u, attr_index(entry)); |
| EXPECT_EQ(kMmioMemory, entry.Memory(kState)); |
| } |
| } |
| |
| TEST(ArmPagingTraitTests, GetSetMemory) { |
| TestArmGetSetMemory<ArmPagingLevel::k0>(); |
| TestArmGetSetMemory<ArmPagingLevel::k1>(); |
| TestArmGetSetMemory<ArmPagingLevel::k2>(); |
| TestArmGetSetMemory<ArmPagingLevel::k3>(); |
| } |
| |
| template <ArmPagingLevel Level> |
| void TestArmSetImpliesAccessed() { |
| ArmTableEntry<Level> entry; |
| |
| if constexpr (kArmLevelCanBeTable<Level>) { |
| entry.set_reg_value(0).SetAsTable(); |
| EXPECT_FALSE(entry.accessed()); |
| } |
| if constexpr (kArmLevelCanBePage<Level>) { |
| entry.set_reg_value(0).SetAsPage().set_af(0); |
| EXPECT_FALSE(entry.accessed()); |
| |
| entry.set_reg_value(0).SetAsPage().set_af(1); |
| EXPECT_TRUE(entry.accessed()); |
| } |
| if constexpr (kArmLevelCanBeBlock<Level>) { |
| entry.set_reg_value(0).SetAsBlock().set_af(0); |
| EXPECT_FALSE(entry.accessed()); |
| |
| entry.set_reg_value(0).SetAsBlock().set_af(1); |
| EXPECT_TRUE(entry.accessed()); |
| } |
| |
| // Non-terminal entries never report as accessed. |
| if constexpr (kArmLevelCanBeNonTerminal<Level>) { |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = false, |
| }); |
| EXPECT_FALSE(entry.accessed()); |
| } |
| |
| // An entry Set() as terminal should report as accessed. |
| if constexpr (kArmLevelCanBeTerminal<Level>) { |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = true, |
| }); |
| EXPECT_TRUE(entry.accessed()); |
| } |
| } |
| |
| TEST(ArmPagingTraitTests, SetImpliesAccessed) { |
| TestArmSetImpliesAccessed<ArmPagingLevel::k0>(); |
| TestArmSetImpliesAccessed<ArmPagingLevel::k1>(); |
| TestArmSetImpliesAccessed<ArmPagingLevel::k2>(); |
| TestArmSetImpliesAccessed<ArmPagingLevel::k3>(); |
| } |
| |
| template <ArmPagingLevel Level> |
| void TestArmGetSetGlobal() { |
| ArmTableEntry<Level> entry; |
| |
| if constexpr (kArmLevelCanBeTable<Level>) { |
| entry.set_reg_value(0).SetAsTable(); |
| EXPECT_FALSE(entry.global()); |
| } |
| if constexpr (kArmLevelCanBePage<Level>) { |
| entry.set_reg_value(0).SetAsPage().set_ng(true); |
| EXPECT_FALSE(entry.global()); |
| |
| entry.set_reg_value(0).SetAsPage().set_ng(false); |
| EXPECT_TRUE(entry.global()); |
| } |
| if constexpr (kArmLevelCanBeBlock<Level>) { |
| entry.set_reg_value(0).SetAsBlock().set_ng(true); |
| EXPECT_FALSE(entry.global()); |
| |
| entry.set_reg_value(0).SetAsBlock().set_ng(false); |
| EXPECT_TRUE(entry.global()); |
| } |
| |
| // Non-terminal entries never report 'global'. |
| if constexpr (kArmLevelCanBeNonTerminal<Level>) { |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = {.readable = true}, |
| .global = false, |
| }); |
| EXPECT_FALSE(entry.global()); |
| |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = false, |
| .access = {.readable = true}, |
| .global = true, |
| }); |
| EXPECT_FALSE(entry.global()); |
| } |
| |
| if constexpr (kArmLevelCanBeTerminal<Level>) { |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = {.readable = true}, |
| .global = false, |
| }); |
| EXPECT_FALSE(entry.global()); |
| |
| entry.set_reg_value(0).Set({}, ArmPagingSettings{ |
| .present = true, |
| .terminal = true, |
| .access = {.readable = true}, |
| .global = true, |
| }); |
| EXPECT_TRUE(entry.global()); |
| } |
| } |
| |
| TEST(ArmPagingTraitTests, GetSetGlobal) { |
| TestArmGetSetGlobal<ArmPagingLevel::k0>(); |
| TestArmGetSetGlobal<ArmPagingLevel::k1>(); |
| TestArmGetSetGlobal<ArmPagingLevel::k2>(); |
| TestArmGetSetGlobal<ArmPagingLevel::k3>(); |
| } |
| |
| // Represents a 512 entry page table and owns its own storage. |
| class Table { |
| public: |
| using Io = hwreg::ArrayIo<uint64_t, 512>; |
| |
| Io io() { return table_.direct_io(); } |
| |
| uint64_t paddr() const { return reinterpret_cast<uint64_t>(&table_); } |
| |
| void Set(uint64_t entry, size_t index) { table_.table()[index] = entry; } |
| |
| private: |
| hwreg::AlignedTableStorage<uint64_t, 512> table_; |
| }; |
| |
| // A simple helper for managing table allocations and offering the |
| // paddr-to-I/O-provider abstraction required of paging API. |
| // |
| // For simplicity, this class is intended to be used by paging schemes with |
| // tables of 512 entries. |
| template <class PagingTraits> |
| class PagingHelper { |
| public: |
| using LevelType = typename PagingTraits::LevelType; |
| |
| template <LevelType Level> |
| using TableEntry = typename PagingTraits::template TableEntry<Level>; |
| |
| static constexpr auto kLevels = PagingTraits::kLevels; |
| |
| Table& NewTable() { |
| std::unique_ptr<Table> table(new Table); |
| uint64_t addr = reinterpret_cast<uint64_t>(table.get()); |
| auto [it, ok] = tables_.insert_or_assign(addr, std::move(table)); |
| ZX_ASSERT(ok); |
| return *it->second; |
| } |
| |
| auto MakePaddrToIo() { |
| return [this](uint64_t paddr) -> Table::Io { |
| auto it = tables_.find(paddr); |
| if (it == tables_.end()) { |
| bool first = true; |
| printf("unknown \"physical\" table address %#" PRIx64 "; known table addresses:", paddr); |
| for (const auto& [addr, table] : tables_) { |
| printf("%s %#" PRIx64, first ? "" : ",", addr); |
| first = false; |
| } |
| abort(); |
| } |
| return it->second->io(); |
| }; |
| } |
| |
| auto MakeAllocator() { |
| return [this](uint64_t size, uint64_t alignment) { |
| ZX_ASSERT(size == sizeof(Table)); |
| ZX_ASSERT(alignment == alignof(Table)); |
| return NewTable().paddr(); |
| }; |
| } |
| |
| template <size_t LevelIndex> |
| TableEntry<kLevels[LevelIndex]> NewTableEntry() const { |
| return TableEntry<kLevels[LevelIndex]>{}.set_reg_value(0); |
| } |
| |
| private: |
| static_assert(PagingTraits::kTableAlignmentLog2 == 12); |
| |
| template <size_t... LevelIndex> |
| static constexpr bool Has512Entries(std::index_sequence<LevelIndex...>) { |
| return ((PagingTraits::template kNumTableEntriesLog2<kLevels[LevelIndex]> == 9) && ...); |
| } |
| static_assert(Has512Entries(std::make_index_sequence<kLevels.size()>())); |
| |
| std::map<uint64_t, std::unique_ptr<Table>> tables_; |
| }; |
| |
| template <typename PagingTraits> |
| void PagingCompilationTest() { |
| // The point here is not really to check that the traits structs are empty, |
| // but rather to force the compiler to instantiate arch::Paging for each |
| // trait, which in turn checks that the traits meet the expected API. |
| static_assert(std::is_empty_v<arch::Paging<PagingTraits>>); |
| |
| // Check that the static members expected of traits that are not referenced |
| // directly by arch::Paging are indeed defined. |
| static_assert(std::is_same_v<std::decay_t<decltype(PagingTraits::kExecuteOnlyAllowed)>, bool>); |
| } |
| |
| TEST(PagingTests, Compilation) { |
| PagingCompilationTest<arch::ExamplePagingTraits>(); |
| PagingCompilationTest<arch::RiscvSv39PagingTraits>(); |
| PagingCompilationTest<arch::RiscvSv48PagingTraits>(); |
| PagingCompilationTest<arch::RiscvSv57PagingTraits>(); |
| PagingCompilationTest<arch::X86FourLevelPagingTraits>(); |
| PagingCompilationTest<arch::X86FiveLevelPagingTraits>(); |
| PagingCompilationTest<arch::ArmLowerPagingTraits>(); |
| PagingCompilationTest<arch::ArmUpperPagingTraits>(); |
| } |
| |
| // The following macros are expected to be used on test cases given as |
| // functions of the form |
| // ``` |
| // template <class PagingTraits> |
| // void CaseName(const typename PagingTraits::SystemState& state) |
| // ``` |
| // stamping out test cases for each PagingTraits implementation under test. |
| |
| #define TEST_FOR_ALL_TRAITS(name) \ |
| TEST_FOR_ARM(name) \ |
| TEST_FOR_RISCV(name) \ |
| TEST_FOR_X86(name) |
| |
| #define TEST_FOR_ARM(name) \ |
| TEST(PagingTests, ArmLower##name) { \ |
| name<arch::ArmLowerPagingTraits, /*VaddrHighBits=*/0x0000'0000'0000'0000u>({}); \ |
| } \ |
| TEST(PagingTests, ArmUpper##name) { \ |
| name<arch::ArmUpperPagingTraits, /*VaddrHighBits=*/0xffff'0000'0000'0000u>({}); \ |
| } |
| |
| #define TEST_FOR_RISCV(name) \ |
| TEST(PagingTests, RiscvSv48##name) { name<arch::RiscvSv48PagingTraits>({}); } |
| |
| #define TEST_FOR_X86(name) \ |
| TEST(PagingTests, X86##name##NoGibPages) { \ |
| name<arch::X86FourLevelPagingTraits>({.page1gb = false}); \ |
| } \ |
| TEST(PagingTests, X86##name##GibPages) { \ |
| name<arch::X86FourLevelPagingTraits>({.page1gb = true}); \ |
| } |
| |
| template <typename SystemState> |
| bool OneGibPagesAllowed(const SystemState& state) { |
| return true; |
| } |
| |
| bool OneGibPagesAllowed(const X86SystemState& state) { return state.page1gb; } |
| |
| template <class PagingTraits, uint64_t VaddrHighBits = 0> |
| void TranslationWith4KiBPages(const typename PagingTraits::SystemState& state) { |
| using Paging = arch::Paging<PagingTraits>; |
| using PagingSettings = typename Paging::PagingSettings; |
| |
| // |---9---| |---9---| |---9---| |---9---| |----12----| |
| constexpr uint64_t kPageVaddr = |
| VaddrHighBits | 0b111111111'101010101'010101010'100100100'000000000000; |
| constexpr uint64_t kPagePaddr = 0xffff'ffff'1000; |
| |
| PagingHelper<PagingTraits> helper; |
| Table& first = helper.NewTable(); |
| Table& second = helper.NewTable(); |
| Table& third = helper.NewTable(); |
| Table& fourth = helper.NewTable(); |
| |
| { |
| auto entry = helper.template NewTableEntry<0>().Set(state, PagingSettings{ |
| .address = second.paddr(), |
| .present = true, |
| .terminal = false, |
| .access = kRWXU, |
| }); |
| first.Set(entry.reg_value(), 0b111111111); |
| } |
| { |
| auto entry = helper.template NewTableEntry<1>().Set(state, PagingSettings{ |
| .address = third.paddr(), |
| .present = true, |
| .terminal = false, |
| .access = kRWXU, |
| }); |
| second.Set(entry.reg_value(), 0b101010101); |
| } |
| { |
| auto entry = helper.template NewTableEntry<2>().Set(state, PagingSettings{ |
| .address = fourth.paddr(), |
| .present = true, |
| .terminal = false, |
| .access = kRWXU, |
| }); |
| third.Set(entry.reg_value(), 0b010101010); |
| } |
| { |
| auto entry = helper.template NewTableEntry<3>().Set(state, PagingSettings{ |
| .address = kPagePaddr, |
| .present = true, |
| .terminal = true, |
| .access = kRWXU, |
| }); |
| fourth.Set(entry.reg_value(), 0b100100100); |
| } |
| |
| { |
| auto result = Paging::template Query(first.paddr(), helper.MakePaddrToIo(), kPageVaddr); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto [paddr, page, access] = std::move(result).value(); |
| EXPECT_EQ(kPagePaddr, paddr); |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x1000u, page.size); |
| EXPECT_TRUE(access.readable); |
| EXPECT_TRUE(access.writable); |
| EXPECT_TRUE(access.executable); |
| EXPECT_TRUE(access.user_accessible); |
| } |
| { |
| auto result = Paging::template Query(first.paddr(), helper.MakePaddrToIo(), kPageVaddr | 0xabc); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto [paddr, page, access] = std::move(result).value(); |
| EXPECT_EQ(kPagePaddr | 0xabc, paddr); |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x1000u, page.size); |
| EXPECT_TRUE(access.readable); |
| EXPECT_TRUE(access.writable); |
| EXPECT_TRUE(access.executable); |
| EXPECT_TRUE(access.user_accessible); |
| } |
| { |
| auto result = Paging::template Query(first.paddr(), helper.MakePaddrToIo(), kPageVaddr | 0xfff); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto [paddr, page, access] = std::move(result).value(); |
| EXPECT_EQ(kPagePaddr | 0xfff, paddr); |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x1000u, page.size); |
| EXPECT_TRUE(access.readable); |
| EXPECT_TRUE(access.writable); |
| EXPECT_TRUE(access.executable); |
| EXPECT_TRUE(access.user_accessible); |
| } |
| } |
| TEST_FOR_ALL_TRAITS(TranslationWith4KiBPages) |
| |
| template <class PagingTraits, uint64_t VaddrHighBits = 0> |
| void TranslationWith2MiBPages(const typename PagingTraits::SystemState& state) { |
| using Paging = arch::Paging<PagingTraits>; |
| using PagingSettings = typename Paging::PagingSettings; |
| |
| // |---9---| |---9---| |---9---| |------12 + 9-------| |
| constexpr uint64_t kPageVaddr = |
| VaddrHighBits | 0b111111111'101010101'010101010'000000000000000000000; |
| constexpr uint64_t kPagePaddr = 0xffff'ff20'0000; |
| |
| PagingHelper<PagingTraits> helper; |
| Table& first = helper.NewTable(); |
| Table& second = helper.NewTable(); |
| Table& third = helper.NewTable(); |
| |
| { |
| auto entry = helper.template NewTableEntry<0>().Set(state, PagingSettings{ |
| .address = second.paddr(), |
| .present = true, |
| .terminal = false, |
| .access = kRWXU, |
| }); |
| first.Set(entry.reg_value(), 0b111111111); |
| } |
| { |
| auto entry = helper.template NewTableEntry<1>().Set(state, PagingSettings{ |
| .address = third.paddr(), |
| .present = true, |
| .terminal = false, |
| .access = kRWXU, |
| }); |
| second.Set(entry.reg_value(), 0b101010101); |
| } |
| { |
| auto entry = helper.template NewTableEntry<2>().Set(state, PagingSettings{ |
| .address = kPagePaddr, |
| .present = true, |
| .terminal = true, |
| .access = kRWXU, |
| }); |
| third.Set(entry.reg_value(), 0b010101010); |
| } |
| |
| { |
| auto result = Paging::template Query(first.paddr(), helper.MakePaddrToIo(), kPageVaddr); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto [paddr, page, access] = std::move(result).value(); |
| EXPECT_EQ(kPagePaddr, paddr); |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x20'0000u, page.size); |
| EXPECT_TRUE(access.readable); |
| EXPECT_TRUE(access.writable); |
| EXPECT_TRUE(access.executable); |
| EXPECT_TRUE(access.user_accessible); |
| } |
| { |
| auto result = |
| Paging::template Query(first.paddr(), helper.MakePaddrToIo(), kPageVaddr | 0xabcde); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto [paddr, page, access] = std::move(result).value(); |
| EXPECT_EQ(kPagePaddr | 0xabcde, paddr); |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x20'0000u, page.size); |
| EXPECT_TRUE(access.readable); |
| EXPECT_TRUE(access.writable); |
| EXPECT_TRUE(access.executable); |
| EXPECT_TRUE(access.user_accessible); |
| } |
| { |
| auto result = |
| Paging::template Query(first.paddr(), helper.MakePaddrToIo(), kPageVaddr | 0x1f'ffff); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto [paddr, page, access] = std::move(result).value(); |
| EXPECT_EQ(kPagePaddr | 0x1f'ffff, paddr); |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x20'0000u, page.size); |
| EXPECT_TRUE(access.readable); |
| EXPECT_TRUE(access.writable); |
| EXPECT_TRUE(access.executable); |
| EXPECT_TRUE(access.user_accessible); |
| } |
| } |
| TEST_FOR_ALL_TRAITS(TranslationWith2MiBPages) |
| |
| template <class PagingTraits, uint64_t VaddrHighBits = 0> |
| void TranslationWith1GiBPages(const typename PagingTraits::SystemState& state) { |
| using Paging = arch::Paging<PagingTraits>; |
| using PagingSettings = typename Paging::PagingSettings; |
| |
| // |---9---| |---9---| |---------12 + 9 + 9---------| |
| constexpr uint64_t kPageVaddr = |
| VaddrHighBits | 0b111111111'101010101'000000000000000000000000000000; |
| constexpr uint64_t kPagePaddr = 0xffff'4000'0000; |
| |
| PagingHelper<PagingTraits> helper; |
| Table& first = helper.NewTable(); |
| Table& second = helper.NewTable(); |
| |
| { |
| auto entry = helper.template NewTableEntry<0>().Set(state, PagingSettings{ |
| .address = second.paddr(), |
| .present = true, |
| .terminal = false, |
| .access = kRWXU, |
| }); |
| first.Set(entry.reg_value(), 0b111111111); |
| } |
| { |
| auto entry = helper.template NewTableEntry<1>().Set(state, PagingSettings{ |
| .address = kPagePaddr, |
| .present = true, |
| .terminal = true, |
| .access = kRWXU, |
| }); |
| second.Set(entry.reg_value(), 0b101010101); |
| } |
| |
| { |
| auto result = Paging::template Query(first.paddr(), helper.MakePaddrToIo(), kPageVaddr); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto [paddr, page, access] = std::move(result).value(); |
| EXPECT_EQ(kPagePaddr, paddr); |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x4000'0000u, page.size); |
| EXPECT_TRUE(access.readable); |
| EXPECT_TRUE(access.writable); |
| EXPECT_TRUE(access.executable); |
| EXPECT_TRUE(access.user_accessible); |
| } |
| { |
| auto result = |
| Paging::template Query(first.paddr(), helper.MakePaddrToIo(), kPageVaddr | 0xabc'def0); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto [paddr, page, access] = std::move(result).value(); |
| EXPECT_EQ(kPagePaddr | 0xabc'def0, paddr); |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x4000'0000u, page.size); |
| EXPECT_TRUE(access.readable); |
| EXPECT_TRUE(access.writable); |
| EXPECT_TRUE(access.executable); |
| EXPECT_TRUE(access.user_accessible); |
| } |
| { |
| auto result = |
| Paging::template Query(first.paddr(), helper.MakePaddrToIo(), kPageVaddr | 0x3fff'ffff); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto [paddr, page, access] = std::move(result).value(); |
| EXPECT_EQ(kPagePaddr | 0x3fff'ffff, paddr); |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x4000'0000u, page.size); |
| EXPECT_TRUE(access.readable); |
| EXPECT_TRUE(access.writable); |
| EXPECT_TRUE(access.executable); |
| EXPECT_TRUE(access.user_accessible); |
| } |
| } |
| TEST_FOR_ALL_TRAITS(TranslationWith1GiBPages) |
| |
| template <class PagingTraits, uint64_t VaddrHighBits = 0> |
| void TranslationFault(const typename PagingTraits::SystemState& state) { |
| using Paging = arch::Paging<PagingTraits>; |
| using PagingSettings = typename Paging::PagingSettings; |
| |
| // |---9---| |---9---| |---9---| |---9---| |----12----| |
| constexpr uint64_t kPageVaddr = |
| VaddrHighBits | 0b111111111'101010101'010101010'100100100'000000000000; |
| constexpr uint64_t kPagePaddr = 0xffff'ffff'1000; |
| |
| PagingHelper<PagingTraits> helper; |
| Table& first = helper.NewTable(); |
| Table& second = helper.NewTable(); |
| Table& third = helper.NewTable(); |
| Table& fourth = helper.NewTable(); |
| |
| // Zero out the entries we expect a successful translation of kPageVaddr to |
| // walk. |
| first.Set(0, 0b111111111); |
| second.Set(0, 0b101010101); |
| third.Set(0, 0b010101010); |
| fourth.Set(0, 0b100100100); |
| |
| // |
| // We should fault... and continue faulting till we properly fill out these |
| // entries to point to kPagePaddr. |
| // |
| |
| { |
| auto result = Paging::template Query(first.paddr(), helper.MakePaddrToIo(), kPageVaddr); |
| EXPECT_TRUE(result.is_error()); |
| } |
| |
| { |
| auto entry = helper.template NewTableEntry<0>().Set(state, PagingSettings{ |
| .address = second.paddr(), |
| .present = true, |
| .terminal = false, |
| .access = kRWXU, |
| }); |
| first.Set(entry.reg_value(), 0b111111111); |
| } |
| |
| { |
| auto result = Paging::template Query(first.paddr(), helper.MakePaddrToIo(), kPageVaddr); |
| EXPECT_TRUE(result.is_error()); |
| } |
| |
| { |
| auto entry = helper.template NewTableEntry<1>().Set(state, PagingSettings{ |
| .address = third.paddr(), |
| .present = true, |
| .terminal = false, |
| .access = kRWXU, |
| }); |
| second.Set(entry.reg_value(), 0b101010101); |
| } |
| |
| { |
| auto result = Paging::template Query(first.paddr(), helper.MakePaddrToIo(), kPageVaddr); |
| EXPECT_TRUE(result.is_error()); |
| } |
| |
| { |
| auto entry = helper.template NewTableEntry<2>().Set(state, PagingSettings{ |
| .address = fourth.paddr(), |
| .present = true, |
| .terminal = false, |
| .access = kRWXU, |
| }); |
| third.Set(entry.reg_value(), 0b010101010); |
| } |
| |
| { |
| auto result = Paging::template Query(first.paddr(), helper.MakePaddrToIo(), kPageVaddr); |
| EXPECT_TRUE(result.is_error()); |
| } |
| |
| { |
| auto entry = helper.template NewTableEntry<3>().Set(state, PagingSettings{ |
| .address = kPagePaddr, |
| .present = true, |
| .terminal = true, |
| .access = kRWXU, |
| }); |
| fourth.Set(entry.reg_value(), 0b100100100); |
| } |
| |
| // All entries are filled in and point to kPagePaddr, so we should no longer |
| // see a fault. |
| { |
| auto result = Paging::template Query(first.paddr(), helper.MakePaddrToIo(), kPageVaddr); |
| ASSERT_TRUE(result.is_ok()); |
| EXPECT_EQ(kPagePaddr, result->paddr); |
| } |
| } |
| |
| TEST_FOR_ALL_TRAITS(TranslationFault) |
| |
| template <class PagingTraits, uint64_t VaddrHighBits = 0> |
| void Mapped4KiBPage(typename PagingTraits::SystemState state) { |
| using Paging = arch::Paging<PagingTraits>; |
| using MapSettings = typename Paging::MapSettings; |
| |
| // |---9---| |---9---| |---9---| |---9---| |----12----| |
| constexpr uint64_t kPageVaddr = |
| VaddrHighBits | 0b111111111'101010101'010101010'100100100'000000000000; |
| constexpr uint64_t kPagePaddr = 0xffff'ffff'1000; |
| |
| constexpr MapSettings kSettings = { |
| .access = |
| AccessPermissions{ |
| .readable = true, |
| .writable = true, |
| .executable = true, |
| .user_accessible = true, |
| }, |
| }; |
| |
| PagingHelper<PagingTraits> helper; |
| Table& root = helper.NewTable(); |
| |
| { |
| auto result = Paging::template Map(root.paddr(), helper.MakePaddrToIo(), helper.MakeAllocator(), |
| state, kPageVaddr, 0x1000, kPagePaddr, kSettings); |
| ASSERT_TRUE(result.is_ok()); |
| } |
| |
| // We can test Map() against the previously-tested Query() |
| { |
| auto result = Paging::template Query(root.paddr(), helper.MakePaddrToIo(), kPageVaddr); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto [paddr, page, access] = std::move(result).value(); |
| |
| EXPECT_EQ(kPagePaddr, paddr); |
| |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x1000u, page.size); |
| |
| EXPECT_TRUE(access.readable); |
| EXPECT_TRUE(access.writable); |
| EXPECT_TRUE(access.executable); |
| EXPECT_TRUE(access.user_accessible); |
| } |
| { |
| auto result = Paging::template Query(root.paddr(), helper.MakePaddrToIo(), kPageVaddr | 0xabc); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto [paddr, page, access] = std::move(result).value(); |
| |
| EXPECT_EQ(kPagePaddr | 0xabc, paddr); |
| |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x1000u, page.size); |
| |
| EXPECT_TRUE(access.readable); |
| EXPECT_TRUE(access.writable); |
| EXPECT_TRUE(access.executable); |
| EXPECT_TRUE(access.user_accessible); |
| } |
| { |
| auto result = Paging::template Query(root.paddr(), helper.MakePaddrToIo(), kPageVaddr | 0xfff); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto [paddr, page, access] = std::move(result).value(); |
| |
| EXPECT_EQ(kPagePaddr | 0xfff, paddr); |
| |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x1000u, page.size); |
| |
| EXPECT_TRUE(access.readable); |
| EXPECT_TRUE(access.writable); |
| EXPECT_TRUE(access.executable); |
| EXPECT_TRUE(access.user_accessible); |
| } |
| } |
| TEST_FOR_ALL_TRAITS(Mapped4KiBPage) |
| |
| template <class PagingTraits, uint64_t VaddrHighBits = 0> |
| void Mapped2MiBPage(typename PagingTraits::SystemState state) { |
| using Paging = arch::Paging<PagingTraits>; |
| using MapSettings = typename Paging::MapSettings; |
| |
| // |---9---| |---9---| |---9---| |------12 + 9-------| |
| constexpr uint64_t kPageVaddr = |
| VaddrHighBits | 0b111111111'101010101'010101010'000000000000000000000; |
| constexpr uint64_t kPagePaddr = 0xffff'ff20'0000; |
| |
| constexpr MapSettings kSettings = { |
| .access = |
| { |
| .readable = true, |
| .writable = true, |
| .executable = true, |
| .user_accessible = true, |
| }, |
| }; |
| |
| PagingHelper<PagingTraits> helper; |
| Table& root = helper.NewTable(); |
| |
| { |
| auto result = Paging::template Map(root.paddr(), helper.MakePaddrToIo(), helper.MakeAllocator(), |
| state, kPageVaddr, 0x20'0000, kPagePaddr, kSettings); |
| ASSERT_TRUE(result.is_ok()); |
| } |
| |
| // We can test Map() against the previously-tested Query() |
| { |
| auto result = Paging::template Query(root.paddr(), helper.MakePaddrToIo(), kPageVaddr); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto [paddr, page, access] = std::move(result).value(); |
| |
| EXPECT_EQ(kPagePaddr, paddr); |
| |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x20'0000u, page.size); |
| |
| EXPECT_TRUE(access.readable); |
| EXPECT_TRUE(access.writable); |
| EXPECT_TRUE(access.executable); |
| EXPECT_TRUE(access.user_accessible); |
| } |
| { |
| auto result = |
| Paging::template Query(root.paddr(), helper.MakePaddrToIo(), kPageVaddr | 0xa'bcde); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto [paddr, page, access] = std::move(result).value(); |
| |
| EXPECT_EQ(kPagePaddr | 0xa'bcde, paddr); |
| |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x20'0000u, page.size); |
| |
| EXPECT_TRUE(access.readable); |
| EXPECT_TRUE(access.writable); |
| EXPECT_TRUE(access.executable); |
| EXPECT_TRUE(access.user_accessible); |
| } |
| { |
| auto result = |
| Paging::template Query(root.paddr(), helper.MakePaddrToIo(), kPageVaddr | 0x1f'ffff); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto [paddr, page, access] = std::move(result).value(); |
| |
| EXPECT_EQ(kPagePaddr | 0x1f'ffff, paddr); |
| |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x20'0000u, page.size); |
| |
| EXPECT_TRUE(access.readable); |
| EXPECT_TRUE(access.writable); |
| EXPECT_TRUE(access.executable); |
| EXPECT_TRUE(access.user_accessible); |
| } |
| } |
| TEST_FOR_ALL_TRAITS(Mapped2MiBPage) |
| |
| template <class PagingTraits, uint64_t VaddrHighBits = 0> |
| void Mapped1GiBPage(typename PagingTraits::SystemState state) { |
| using Paging = arch::Paging<PagingTraits>; |
| using MapSettings = typename Paging::MapSettings; |
| |
| // |---9---| |---9---| |---------12 + 9 + 9---------| |
| constexpr uint64_t kPageVaddr = |
| VaddrHighBits | 0b111111111'101010101'000000000000000000000000000000; |
| constexpr uint64_t kPagePaddr = 0xffff'4000'0000; |
| |
| constexpr MapSettings kSettings = { |
| .access = |
| { |
| .readable = true, |
| .writable = true, |
| .executable = true, |
| .user_accessible = true, |
| }, |
| }; |
| |
| PagingHelper<PagingTraits> helper; |
| Table& root = helper.NewTable(); |
| |
| { |
| auto result = Paging::template Map(root.paddr(), helper.MakePaddrToIo(), helper.MakeAllocator(), |
| state, kPageVaddr, 0x4000'0000, kPagePaddr, kSettings); |
| ASSERT_TRUE(result.is_ok()); |
| } |
| |
| // We can test Map() against the previously-tested Query() |
| { |
| auto result = Paging::template Query(root.paddr(), helper.MakePaddrToIo(), kPageVaddr); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto [paddr, page, access] = std::move(result).value(); |
| |
| EXPECT_EQ(kPagePaddr, paddr); |
| |
| if (OneGibPagesAllowed(state)) { |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x4000'0000u, page.size); |
| } else { |
| // 512 2MiB pages otherwise. |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x20'0000u, page.size); |
| } |
| |
| EXPECT_TRUE(access.readable); |
| EXPECT_TRUE(access.writable); |
| EXPECT_TRUE(access.executable); |
| EXPECT_TRUE(access.user_accessible); |
| } |
| { |
| auto result = |
| Paging::template Query(root.paddr(), helper.MakePaddrToIo(), kPageVaddr | 0xabc'def0); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto [paddr, page, access] = std::move(result).value(); |
| |
| EXPECT_EQ(kPagePaddr | 0xabc'def0, paddr); |
| |
| if (OneGibPagesAllowed(state)) { |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x4000'0000u, page.size); |
| } else { |
| // 512 2MiB pages otherwise. |
| EXPECT_EQ(kPagePaddr | 0xaa0'0000, page.paddr); |
| EXPECT_EQ(0x20'0000u, page.size); |
| } |
| |
| EXPECT_TRUE(access.readable); |
| EXPECT_TRUE(access.writable); |
| EXPECT_TRUE(access.executable); |
| EXPECT_TRUE(access.user_accessible); |
| } |
| { |
| auto result = |
| Paging::template Query(root.paddr(), helper.MakePaddrToIo(), kPageVaddr | 0x3fff'ffff); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto [paddr, page, access] = std::move(result).value(); |
| |
| EXPECT_EQ(kPagePaddr | 0x3fff'ffff, paddr); |
| |
| if (OneGibPagesAllowed(state)) { |
| EXPECT_EQ(kPagePaddr, page.paddr); |
| EXPECT_EQ(0x4000'0000u, page.size); |
| } else { |
| // 512 2MiB pages otherwise. |
| EXPECT_EQ(kPagePaddr | 0x3fe0'0000, page.paddr); |
| EXPECT_EQ(0x20'0000u, page.size); |
| } |
| |
| EXPECT_TRUE(access.readable); |
| EXPECT_TRUE(access.writable); |
| EXPECT_TRUE(access.executable); |
| EXPECT_TRUE(access.user_accessible); |
| } |
| } |
| TEST_FOR_ALL_TRAITS(Mapped1GiBPage) |
| |
| template <class PagingTraits, uint64_t VaddrHighBits = 0> |
| void DoubleMapping(typename PagingTraits::SystemState state) { |
| // |
| // Performing the same mapping twice should yield kAlreadyMapped. |
| // |
| |
| using Paging = arch::Paging<PagingTraits>; |
| using MapSettings = typename Paging::MapSettings; |
| |
| constexpr MapSettings kSettings = { |
| .access = |
| { |
| .readable = true, |
| .writable = true, |
| .executable = true, |
| .user_accessible = true, |
| }, |
| }; |
| |
| { |
| constexpr uint64_t kPagePaddr = 0xffff'ffff'1000; |
| constexpr uint64_t kPageVaddr = VaddrHighBits | kPagePaddr; |
| |
| PagingHelper<PagingTraits> helper; |
| Table& root = helper.NewTable(); |
| |
| auto result1 = |
| Paging::template Map(root.paddr(), helper.MakePaddrToIo(), helper.MakeAllocator(), state, |
| kPageVaddr, 0x1000, kPagePaddr, kSettings); |
| ASSERT_TRUE(result1.is_ok()); |
| |
| auto result2 = |
| Paging::template Map(root.paddr(), helper.MakePaddrToIo(), helper.MakeAllocator(), state, |
| kPageVaddr, 0x1000, kPagePaddr, kSettings); |
| ASSERT_TRUE(result2.is_error()); |
| auto error = std::move(result2).error_value(); |
| EXPECT_EQ(arch::MapError::Type::kAlreadyMapped, error.type); |
| EXPECT_EQ(kPageVaddr, error.vaddr); |
| } |
| { |
| constexpr uint64_t kPagePaddr = 0xffff'ff20'0000; |
| constexpr uint64_t kPageVaddr = VaddrHighBits | kPagePaddr; |
| |
| PagingHelper<PagingTraits> helper; |
| Table& root = helper.NewTable(); |
| |
| auto result1 = |
| Paging::template Map(root.paddr(), helper.MakePaddrToIo(), helper.MakeAllocator(), state, |
| kPageVaddr, 0x20'0000, kPagePaddr, kSettings); |
| ASSERT_TRUE(result1.is_ok()); |
| |
| auto result2 = |
| Paging::template Map(root.paddr(), helper.MakePaddrToIo(), helper.MakeAllocator(), state, |
| kPageVaddr, 0x20'0000, kPagePaddr, kSettings); |
| ASSERT_TRUE(result2.is_error()); |
| auto error = std::move(result2).error_value(); |
| EXPECT_EQ(arch::MapError::Type::kAlreadyMapped, error.type); |
| EXPECT_EQ(kPageVaddr, error.vaddr); |
| } |
| { |
| constexpr uint64_t kPagePaddr = 0xffff'4000'0000; |
| constexpr uint64_t kPageVaddr = VaddrHighBits | kPagePaddr; |
| |
| PagingHelper<PagingTraits> helper; |
| Table& root = helper.NewTable(); |
| |
| auto result1 = |
| Paging::template Map(root.paddr(), helper.MakePaddrToIo(), helper.MakeAllocator(), state, |
| kPageVaddr, 0x4000'0000, kPagePaddr, kSettings); |
| ASSERT_TRUE(result1.is_ok()); |
| |
| auto result2 = |
| Paging::template Map(root.paddr(), helper.MakePaddrToIo(), helper.MakeAllocator(), state, |
| kPageVaddr, 0x4000'0000, kPagePaddr, kSettings); |
| ASSERT_TRUE(result2.is_error()); |
| auto error = std::move(result2).error_value(); |
| EXPECT_EQ(arch::MapError::Type::kAlreadyMapped, error.type); |
| EXPECT_EQ(kPageVaddr, error.vaddr); |
| } |
| } |
| TEST_FOR_ALL_TRAITS(DoubleMapping) |
| |
| template <class PagingTraits, uint64_t VaddrHighBits = 0> |
| void MapAllocationFailure(typename PagingTraits::SystemState state) { |
| using Paging = arch::Paging<PagingTraits>; |
| using MapSettings = typename Paging::MapSettings; |
| |
| constexpr uint64_t kVaddr = 0xffff'ffff'1000; |
| constexpr uint64_t kPaddr = 0x0000'0000'1000; |
| |
| constexpr MapSettings kSettings = { |
| .access = |
| { |
| .readable = true, |
| .writable = true, |
| .executable = true, |
| .user_accessible = true, |
| }, |
| }; |
| |
| // If we fail on the i'th allocation for i=0,1,2, then the whole map attempt |
| // should fail (as we were unable to allocate any of the three levels of |
| // tables past the root). |
| for (size_t i = 0; i < 3; ++i) { |
| PagingHelper<PagingTraits> helper; |
| Table& root = helper.NewTable(); |
| |
| auto allocator = [&helper, j = size_t{0}, i]( |
| uint64_t size, uint64_t alignment) mutable -> std::optional<uint64_t> { |
| if (j++ < i) { |
| return helper.MakeAllocator()(size, alignment); |
| } |
| return {}; |
| }; |
| auto result = Paging::template Map(root.paddr(), helper.MakePaddrToIo(), allocator, state, |
| kVaddr, 0x1000, kPaddr, kSettings); |
| ASSERT_TRUE(result.is_error()); |
| auto error = std::move(result).error_value(); |
| EXPECT_EQ(arch::MapError::Type::kAllocationFailure, error.type); |
| EXPECT_EQ(error.vaddr, kVaddr); |
| } |
| } |
| TEST_FOR_ALL_TRAITS(MapAllocationFailure) |
| |
| template <class PagingTraits, uint64_t VaddrHighBits = 0> |
| void MappedRegionWithMultipleMappings(typename PagingTraits::SystemState state) { |
| using Paging = arch::Paging<PagingTraits>; |
| using MapSettings = typename Paging::MapSettings; |
| |
| constexpr uint64_t k1GiB = 0x4000'0000; |
| constexpr uint64_t k2MiB = 0x0020'0000; |
| constexpr uint64_t k4KiB = 0x0000'1000; |
| |
| // Consider an mapped region of size 2GiB + 6MiB + 36KiB that begins at an |
| // address (2MiB + 4KiB) shy of a 1GiB alignment. We'd expect the following |
| // mappings in order: |
| // * 1 4KiB page |
| // * 1 2MiB page |
| // * If 1GiB pages are supported, two 1GiB pages; else 512 2MiB ones |
| // * 2 2MiB pages |
| // * 8 4KiB pages. |
| constexpr uint64_t kPaddr = 0xffff'0000'0000 - k2MiB - k4KiB; |
| constexpr uint64_t kVaddr = VaddrHighBits | kPaddr; |
| constexpr uint64_t kSize = 2 * k1GiB + 3 * k2MiB + 9 * k4KiB; |
| |
| constexpr MapSettings kSettings = { |
| .access = |
| { |
| .readable = true, |
| .writable = true, |
| .executable = true, |
| .user_accessible = true, |
| }, |
| }; |
| |
| PagingHelper<PagingTraits> helper; |
| Table& root = helper.NewTable(); |
| |
| { |
| auto result = Paging::template Map(root.paddr(), helper.MakePaddrToIo(), helper.MakeAllocator(), |
| state, kVaddr, kSize, kPaddr, kSettings); |
| ASSERT_TRUE(result.is_ok()); |
| } |
| |
| { |
| auto result = Paging::template Query(root.paddr(), helper.MakePaddrToIo(), kVaddr); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto& page = result->page; |
| EXPECT_EQ(0x1000u, page.size); |
| EXPECT_EQ(kPaddr, page.paddr); |
| } |
| { |
| constexpr uint64_t kOffset = 0x1000u; |
| auto result = Paging::template Query(root.paddr(), helper.MakePaddrToIo(), kVaddr + kOffset); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto& page = result->page; |
| EXPECT_EQ(0x20'0000u, page.size); |
| EXPECT_EQ(kPaddr + kOffset, page.paddr); |
| } |
| if (OneGibPagesAllowed(state)) { |
| for (size_t j = 0; j < 2; ++j) { |
| uint64_t offset = 0x1000u + 0x20'0000u + j * 0x4000'0000u; |
| auto result = Paging::template Query(root.paddr(), helper.MakePaddrToIo(), kVaddr + offset); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto& page = result->page; |
| EXPECT_EQ(0x4000'0000u, page.size); |
| EXPECT_EQ(kPaddr + offset, page.paddr); |
| } |
| } else { |
| for (size_t j = 0; j < 1024; ++j) { |
| uint64_t offset = 0x1000u + 0x20'0000u + j * 0x20'0000u; |
| |
| auto result = Paging::template Query(root.paddr(), helper.MakePaddrToIo(), kVaddr + offset); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto& page = result->page; |
| EXPECT_EQ(0x20'0000u, page.size); |
| EXPECT_EQ(kPaddr + offset, page.paddr); |
| } |
| } |
| for (size_t j = 0; j < 2; ++j) { |
| uint64_t offset = 0x1000u + 0x20'0000u + 2 * 0x4000'0000u + j * 0x20'0000u; |
| |
| auto result = Paging::template Query(root.paddr(), helper.MakePaddrToIo(), kVaddr + offset); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto& page = result->page; |
| EXPECT_EQ(0x20'0000u, page.size); |
| EXPECT_EQ(kPaddr + offset, page.paddr); |
| } |
| for (size_t j = 0; j < 8; ++j) { |
| uint64_t offset = 0x1000u + 0x20'0000u + 2 * 0x4000'0000u + 2 * 0x20'0000u + j * 0x1000u; |
| |
| auto result = Paging::template Query(root.paddr(), helper.MakePaddrToIo(), kVaddr + offset); |
| ASSERT_TRUE(result.is_ok()); |
| |
| auto& page = result->page; |
| EXPECT_EQ(0x1000u, page.size); |
| EXPECT_EQ(kPaddr + offset, page.paddr); |
| } |
| } |
| TEST_FOR_ALL_TRAITS(MappedRegionWithMultipleMappings) |
| |
| } // namespace |