From 987731ea436f8fc92c1b9e5a2a53dccdea197e5a Mon Sep 17 00:00:00 2001
From: TuxSH <1922548+TuxSH@users.noreply.github.com>
Date: Sun, 1 Mar 2020 18:45:42 +0000
Subject: [PATCH] thermosphere: mmu table builder
---
thermosphere/src/cpu/hvisor_cpu_mmu.hpp | 197 ++++++++++++++++++
.../src/cpu/hvisor_cpu_sysreg_general.hpp | 43 ++++
2 files changed, 240 insertions(+)
create mode 100644 thermosphere/src/cpu/hvisor_cpu_mmu.hpp
diff --git a/thermosphere/src/cpu/hvisor_cpu_mmu.hpp b/thermosphere/src/cpu/hvisor_cpu_mmu.hpp
new file mode 100644
index 000000000..97e2d2461
--- /dev/null
+++ b/thermosphere/src/cpu/hvisor_cpu_mmu.hpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2019-2020 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+
+#pragma once
+
+#include "hvisor_cpu_sysreg_general.hpp"
+
+namespace ams::hvisor::cpu {
+
+ // Assumes addr is valid, must be called with interrupts masked
+ inline uintptr_t Va2Pa(const void *vaddrEl2) {
+ uintptr_t va = reinterpret_cast(vaddrEl2);
+ __asm__ __volatile__("at s1e2r, %0" :: "r"(va) : "memory");
+ return (THERMOSPHERE_GET_SYSREG(par_el1) & MASK2L(47, 12)) | (va & MASKL(12));
+ }
+
+ enum MmuPteType : u64 {
+ MMU_ENTRY_FAULT = 0,
+ MMU_ENTRY_BLOCK = 1,
+ MMU_ENTRY_TABLE = 3,
+
+ // L3 (this definition allows for recursive page tables)
+ MMU_ENTRY_PAGE = 3,
+ };
+
+ // Multi-byte attributes...
+ constexpr u64 MMU_ATTRINDEX(u64 idx) { return (idx & 8) << 2; }
+ constexpr u64 MMU_MEMATTR(u64 attr) { return (attr & 0xF) << 2; }
+
+ // Attributes. They are defined in a way that allows recursive page tables (assuming PBHA isn't used)
+ enum MmuPteAttributes : u64 {
+ // Stage 1 Table only, the rest is block/page only
+ MMU_NS_TABLE = BITL(62),
+ MMU_AP_TABLE = BITL(61),
+ MMU_XN_TABLE = BITL(60),
+ MMU_PXN_TABLE = BITL(59),
+
+ MMU_UXN = BITL(54), // EL1&0 only
+ MMU_PXN = BITL(53), // EL1&0 only
+ MMU_XN = MMU_UXN,
+ MMU_XN0 = MMU_PXN, // Armv8.2, stage 2 only
+ MMU_CONTIGUOUS = BITL(52),
+ MMU_DBM = BITL(51), // stage 1 only
+ MMU_GP = BITL(50), // undocumented
+
+ // ARMv8.4-TTRem only
+ MMU_NT = BITL(16),
+
+ // EL1&0 only
+ MMU_NG = BITL(11),
+
+ MMU_AF = BITL(10),
+
+ // SH[1:0]
+ MMU_NON_SHAREABLE = 0 << 8,
+ MMU_OUTER_SHAREABLE = 2 << 8,
+ MMU_INNER_SHAREABLE = 2 << 8,
+
+ // AP[2:1], stage 1 only. AP[0] does not exist.
+ MMU_AP_PRIV_RW = 0 << 6,
+ MMU_AP_RW = 1 << 6,
+ MMU_AP_PRIV_RO = 2 << 6,
+ MMU_AP_RO = 3 << 6,
+
+ // S2AP[1:0], stage 2 only
+ MMU_S2AP_NONE = 0 << 6,
+ MMU_S2AP_RO = 1 << 6,
+ MMU_S2AP_WO = 2 << 6,
+ MMU_S2AP_RW = 3 << 6,
+
+ // NS, stage 1 only
+ MMU_NS = BITL(5),
+
+ // See above...
+
+ // MemAttr[3:0], stage 2 only (convenience defs). When combining, strongest memory type applies
+ MMU_MEMATTR_DEVICE_NGNRE = MMU_MEMATTR(2),
+ MMU_MEMATTR_UNCHANGED = MMU_MEMATTR(0xF),
+
+ // Other useful defines for stage 2:
+ MMU_SAME_SHAREABILITY = MMU_NON_SHAREABLE,
+ };
+
+ template
+ class MmuTableBuilder final {
+ private:
+ static constexpr u32 tgBitSize = GetTranslationGranuleBitSize(GranuleSize);
+
+ // tgBitSize - 3 = log2(tg / sizeof(u64))
+ static constexpr u32 levelShift = tgBitSize + (tgBitSize - 3) * (3 - Level);
+ static constexpr u32 levelBitSize = std::min(AddressSpaceSize - levelShift, tgBitSize - 3);
+ static constexpr u64 levelMask = MASKL(levelBitSize);
+ static constexpr size_t ComputeIndex(uintptr_t va)
+ {
+ return (va >> levelShift) & levelMask;
+ }
+
+ private:
+ u64 *m_pageTable = nullptr;
+
+ public:
+ using NextLevelBuilder = MmuTableBuilder;
+ static_assert(Level <= 3, "Invalid translation table level");
+ static_assert(AddressSpaceSize <= 48);
+ static_assert(AddressSpaceSize > levelShift, "Address space size mismatch with translation level");
+ static constexpr size_t blockSize = BITL(levelShift);
+ static constexpr size_t tableSize = BITL(levelBitSize);
+
+ public:
+ constexpr MmuTableBuilder(u64 *pageTable = nullptr) : m_pageTable{pageTable} {}
+
+ constexpr MmuTableBuilder &InitializeTable()
+ {
+ std::memset(m_pageTable, 0, 8 * tableSize);
+ // Fails to optimize before GCC 10: std::fill_n(m_pageTable, tableSize, MMU_ENTRY_FAULT);
+ return *this;
+ }
+
+ // Precondition: va and pa bits in range
+ constexpr NextLevelBuilder MapTable(uintptr_t va, uintptr_t pa, u64 *table, u64 attribs = 0) const
+ {
+ static_assert(Level < 3, "Level 3 is the last level of translation");
+
+ m_pageTable[ComputeIndex(va)] = pa | attribs | MMU_ENTRY_TABLE;
+ return NextLevelBuilder{table};
+ }
+
+ NextLevelBuilder MapTable(uintptr_t va, u64 *table, u64 attribs = 0) const
+ {
+ if constexpr (IsMmuEnabled) {
+ return MapTable(va, Va2Pa(table), table, attribs);
+ } else {
+ return MapTable(va, reinterpret_cast(table), table, attribs);
+ }
+ }
+
+ constexpr MmuTableBuilder &Unmap(uintptr_t va)
+ {
+ m_pageTable[ComputeIndex(va)] = MMU_ENTRY_FAULT;
+ return *this;
+ }
+
+ // Precondition: guardSize == 0 if Level == 0
+ constexpr MmuTableBuilder &UnmapRange(uintptr_t va, size_t size, size_t guardSize = 0)
+ {
+ for (size_t off = 0, offVa = 0; off < size; off += blockSize, offVa += blockSize + guardSize) {
+ Unmap(va + offVa);
+ }
+ return *this;
+ }
+
+ // Precondition: va and pa bits in range
+ constexpr MmuTableBuilder &MapBlock(uintptr_t va, uintptr_t pa, u64 attribs)
+ {
+ static_assert(Level > 0, "Can only map L1 tables at L0");
+
+ constexpr u64 entryType = Level == 3 ? MMU_ENTRY_PAGE : MMU_ENTRY_BLOCK;
+ m_pageTable[ComputeIndex(va)] = pa | attribs | MMU_AF | entryType;
+ return *this;
+ }
+
+ constexpr MmuTableBuilder &MapBlock(uintptr_t pa, u64 attribs)
+ {
+ return MapBlock(pa, pa, attribs);
+ }
+
+ // Precondition: size and guardSize are multiples of blockSize
+ constexpr MmuTableBuilder &MapBlockRange(uintptr_t va, uintptr_t pa, size_t size, u64 attribs, size_t guardSize = 0)
+ {
+ for (size_t off = 0, offVa = 0; off < size; off += blockSize, offVa += blockSize + guardSize) {
+ MapBlock(va + offVa, pa + off, attribs);
+ UnmapRange(va + offVa + blockSize, guardSize, 0);
+ }
+ return *this;
+ }
+
+ constexpr MmuTableBuilder &MapBlockRange(uintptr_t pa, size_t size, u64 attribs)
+ {
+ return MapBlockRange(pa, pa, attribs, size, 0);
+ }
+ };
+
+}
diff --git a/thermosphere/src/cpu/hvisor_cpu_sysreg_general.hpp b/thermosphere/src/cpu/hvisor_cpu_sysreg_general.hpp
index 021b79912..6f043aac1 100644
--- a/thermosphere/src/cpu/hvisor_cpu_sysreg_general.hpp
+++ b/thermosphere/src/cpu/hvisor_cpu_sysreg_general.hpp
@@ -445,4 +445,47 @@ namespace ams::hvisor::cpu {
CNTCTL_ENABLE = BITL(0),
};
+ // TCR_ELx flags
+ enum TcrFlags {
+ TCR_IRGN_NC = (0 << 8),
+ TCR_IRGN_WBWA = (1 << 8),
+ TCR_IRGN_WT = (2 << 8),
+ TCR_IRGN_WBNWA = (3 << 8),
+ TCR_IRGN_MASK = (3 << 8),
+ TCR_ORGN_NC = (0 << 10),
+ TCR_ORGN_WBWA = (1 << 10),
+ TCR_ORGN_WT = (2 << 10),
+ TCR_ORGN_WBNWA = (3 << 10),
+ TCR_ORGN_MASK = (3 << 10),
+ TCR_NOT_SHARED = (0 << 12),
+ TCR_SHARED_OUTER = (2 << 12),
+ TCR_SHARED_INNER = (3 << 12),
+ TCR_EPD1_DISABLE = BITL(23),
+
+ TCR_EL1_RSVD = BITL(31),
+ TCR_EL2_RSVD = (BITL(31) | BITL(23)),
+ VTCR_EL2_RSVD = BITL(31),
+ TCR_EL3_RSVD = (BITL(31) | BITL(23)),
+ };
+
+ // Could have used enum class here, but can't start identifiers with a digit...
+ enum TranslationGranuleSize {
+ TranslationGranule_4K = 0,
+ TranslationGranule_64K = 1,
+ TranslationGranule_16K = 2,
+ };
+
+ constexpr size_t GetTranslationGranuleBitSize(TranslationGranuleSize granuleSize)
+ {
+ switch (granuleSize) {
+ case TranslationGranule_4K: return 12;
+ case TranslationGranule_64K: return 16;
+ case TranslationGranule_16K: return 14;
+ default: return 0;
+ }
+ }
+
+ constexpr u64 TCR_T0SZ(size_t addressSpaceSize) { return (64ul - (addressSpaceSize & 0x3F)) << 0; }
+ constexpr u64 TCR_PS(u64 n) { return (n & 7) << 16; }
+ constexpr u64 VTCR_SL0(u64 n) { return (n & 3) << 6; }
}