/* * This file is part of libn3ds * Copyright (C) 2024 derrek, profi200 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that 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 . */ #include "types.h" #include "mem_map.h" #include "fb_assert.h" #include "arm11/drivers/scu.h" #include "memory.h" #include "arm.h" // Mem permissions. Bit 5 (APX), 1-0 (AP[1:0]), all other padding. #define PERM_NA (0b000000u) #define PERM_PRIV_RW_USR_NA (0b000001u) #define PERM_PRIV_RW_USR_RO (0b000010u) #define PERM_PRIV_RW_USR_RW (0b000011u) // These 2 don't work for supersections because no APX bit. #define PERM_PRIV_RO_USR_NA (0b100001u) #define PERM_PRIV_RO_USR_RO (0b100010u) // Predefined mem attributes. Bit 12-10 (TEX[2:0]), 1 (C), 0 (B), all other padding. // All of these count for outer and inner. #define ATTR_STRONGLY_ORDERED (0b0000000000000u) // Always shared. #define ATTR_SHARED_DEVICE (0b0000000000001u) #define ATTR_NORM_WRITE_TROUGH_NO_ALLOC (0b0000000000010u) // Behaves as noncacheable on ARM11 MPCore. #define ATTR_NORM_WRITE_BACK_NO_ALLOC (0b0000000000011u) // Behaves as write-back write-allocate. #define ATTR_NORM_NONCACHABLE (0b0010000000000u) #define ATTR_NORM_WRITE_BACK_ALLOC (0b0010000000011u) #define ATTR_NONSHARED_DEVICE (0b0100000000000u) // Policies for custom normal memory attributes. #define POLI_NONCACHABLE_UNBUFFERED (0b00u) #define POLI_WRITE_BACK_ALLOC_BUFFERED (0b01u) #define POLI_WRITE_THROUGH_NO_ALLOC_BUFFERED (0b10u) // Behaves as noncacheable on ARM11 MPCore. #define POLI_WRITE_BACK_NO_ALLOC_BUFFERED (0b11u) // Behaves as write-back write-allocate. // Make custom normal memory attributes. #define CUSTOM_ATTR(outer, inner) (BIT(12) | (outer)<<10 | (inner)) // Converts the attribute bits from L1 format to L2 format. // Required for mmuMapPages(). #define L1_TO_L2(attr) (((attr)>>6 | (attr)) & 0x73) typedef struct { u32 l1[4096]; u32 l2PrivReg[256]; // L2 table for MPCore private region. u32 l2DspAxi[256]; // L2 table for DSP and AXI RAM. u32 l2Boot11[256]; // L2 table for boot11 (high vectors). } MmuTables; static MmuTables *const g_mmuTables = (MmuTables*)A11_MMU_TABLES_BASE; /** * @brief Maps up to 256 16 MiB sections of memory. Domain is always 0. * * @param[in] va The virtual address base. Must be aligned to 16 MiB. * @param[in] pa The physical address base. Must be aligned to 16 MiB. * @param[in] num The number of sections to map. * @param[in] access The access permission bits. * @param[in] xn If this memory should be marked as execute never. * @param[in] attr Other attribute bits like caching. */ static void mmuMapSupersections(u32 va, u32 pa, u32 num, u8 access, bool xn, u32 attr) { fb_assert(!(va & ~0xFF000000)); fb_assert(!(pa & ~0xFF000000)); fb_assert(num < 256); u32 *const l1Table = g_mmuTables->l1; for(u32 i = 0; i < 0x1000000 * num; i += 0x1000000) { const u32 l1Ss = (va + i)>>20; for(u32 n = 0; n < 16; n++) { l1Table[l1Ss + n] = (pa + i) | BIT(18) | access<<10 | xn<<4 | attr<<2 | 0b10u; } } } /** * @brief Maps up to 4096 1 MiB sections of memory. * * @param[in] va The virtual address base. Must be aligned to 1 MiB. * @param[in] pa The physical address base. Must be aligned to 1 MiB. * @param[in] num The number of sections to map. * @param[in] shared If the sections are shared memory. * @param[in] access The access permission bits. * @param[in] domain One of the 16 possible domains. * @param[in] xn If this memory should be marked as execute never. * @param[in] attr Other attribute bits like caching. */ static void mmuMapSections(u32 va, u32 pa, u32 num, bool shared, u8 access, u8 domain, bool xn, u32 attr) { fb_assert(!(va & ~0xFFF00000)); fb_assert(!(pa & ~0xFFF00000)); fb_assert(num < 4096); u32 *const l1Table = g_mmuTables->l1; for(u32 i = 0; i < 0x100000 * num; i += 0x100000) { l1Table[(va + i)>>20] = (pa + i) | shared<<16 | access<<10 | domain<<5 | xn<<4 | attr<<2 | 0b10u; } } /** * @brief Maps up to 256 4 KiB pages of memory. * @brief The mapped range must not cross the next section. * * @param[in] va The virtual address base. Must be aligned to 4 KiB. * @param[in] pa The physical address base. Must be aligned to 4 KiB. * @param[in] num The number of pages to map. * @param l2Table The L2 MMU table address base for this mapping. * @param[in] shared If the pages are shared memory. * @param[in] access The access permission bits. * @param[in] domain One of the 16 possible domains. * @param[in] xn If this memory should be marked as execute never. * @param[in] attr Other attribute bits like caching. */ static void mmuMapPages(u32 va, u32 pa, u32 num, u32 *const l2Table, bool shared, u8 access, u8 domain, bool xn, u32 attr) { fb_assert(!(va & ~0xFFFFF000)); fb_assert(!(pa & ~0xFFFFF000)); fb_assert(num < 256); fb_assert(!((u32)l2Table & ~0xFFFFFC00)); for(u32 i = 0; i < 0x1000 * num; i += 0x1000) { l2Table[(va + i)>>12 & 0xFF] = ((pa + i) & 0xFFFFF000) | shared<<10 | access<<4 | attr<<2 | 0b10u | xn; } g_mmuTables->l1[va>>20] = (u32)l2Table | domain<<5 | 0b01u; } void setupMmu(void) { __setFcsepidr(0); // FCSE PID Register (FCSE PID = 0). Note: Must be 0 before disabling the MMU otherwise UB. __setCidr(0); // Context ID Register (ASID = 0, PROCID = 0). __setTtbr0((u32)g_mmuTables->l1 | 0x12); // TTBR0 address shared page table walk and outer cachable write-through, no allocate on write. __setTtbcr(0); // Use the 16 KiB L1 table only. __setDacr(1); // Domain 0 = client, remaining domains all = no access. static volatile bool syncFlag = false; if(__getCpuId() == 0) { // Clear L1 and L2 tables. clear32((u32*)g_mmuTables, 0, sizeof(MmuTables)); // IO mem mapping. mmuMapSections(IO_COMMON_BASE, IO_COMMON_BASE, 4, true, PERM_PRIV_RW_USR_NA, 0, true, ATTR_SHARED_DEVICE); // MPCore private region mapping. mmuMapPages(MPCORE_PRIV_BASE, MPCORE_PRIV_BASE, 2, g_mmuTables->l2PrivReg, false, PERM_PRIV_RW_USR_NA, 0, true, L1_TO_L2(ATTR_SHARED_DEVICE)); // VRAM mapping. mmuMapSections(VRAM_BASE, VRAM_BASE, 6, true, PERM_PRIV_RW_USR_NA, 0, true, ATTR_NORM_WRITE_BACK_ALLOC); // DSP RAM mapping minus a guard page for the stacks. mmuMapPages(DSP_RAM_BASE, DSP_RAM_BASE, (DSP_RAM_SIZE / 4096) - 1, g_mmuTables->l2DspAxi, true, PERM_PRIV_RW_USR_NA, 0, true, L1_TO_L2(ATTR_NORM_WRITE_BACK_ALLOC)); // AXI RAM core 0/1 stack mapping. mmuMapPages(A11_C0_STACK_START, A11_C0_STACK_START, 4, g_mmuTables->l2DspAxi, true, PERM_PRIV_RW_USR_NA, 0, true, L1_TO_L2(ATTR_NORM_WRITE_BACK_ALLOC)); // AXI RAM MMU table mapping. const u32 mmuTablesPages = ((sizeof(MmuTables) + 0xFFFu) & ~0xFFFu) / 0x1000; mmuMapPages((u32)g_mmuTables, (u32)g_mmuTables, mmuTablesPages, g_mmuTables->l2DspAxi, true, PERM_PRIV_RO_USR_NA, 0, true, L1_TO_L2(ATTR_NORM_WRITE_TROUGH_NO_ALLOC)); extern const u32 __start__[]; extern const u32 __text_pages__[]; extern const u32 __rodata_start__[]; extern const u32 __rodata_pages__[]; extern const u32 __data_start__[]; const u32 dataPages = (AXI_RAM_BASE + AXI_RAM_SIZE - (u32)__data_start__) / 0x1000; // text. mmuMapPages((u32)__start__, (u32)__start__, (u32)__text_pages__, g_mmuTables->l2DspAxi, true, PERM_PRIV_RO_USR_NA, 0, false, L1_TO_L2(ATTR_NORM_WRITE_BACK_ALLOC)); // rodata. mmuMapPages((u32)__rodata_start__, (u32)__rodata_start__, (u32)__rodata_pages__, g_mmuTables->l2DspAxi, true, PERM_PRIV_RO_USR_NA, 0, true, L1_TO_L2(ATTR_NORM_WRITE_BACK_ALLOC)); // data, bss and heap. mmuMapPages((u32)__data_start__, (u32)__data_start__, dataPages, g_mmuTables->l2DspAxi, true, PERM_PRIV_RW_USR_NA, 0, true, L1_TO_L2(ATTR_NORM_WRITE_BACK_ALLOC)); // FCRAM with New 3DS extension. mmuMapSupersections(FCRAM_BASE, FCRAM_BASE, 16, PERM_PRIV_RW_USR_NA, true, ATTR_NORM_WRITE_BACK_ALLOC); // Map libn3ds executable start to boot11 mirror (exception vectors). mmuMapPages(BOOT11_HI_MIRROR, (u32)__start__, 1, g_mmuTables->l2Boot11, true, PERM_PRIV_RO_USR_NA, 0, false, L1_TO_L2(ATTR_NORM_WRITE_BACK_ALLOC)); // Invalidate tag RAMs before enabling SMP as recommended by the MPCore doc. // TODO: Disable SCU on poweroff/reboot/FIRM launch. Scu *const scu = getScuRegs(); scu->ctrl = SCU_CTRL_RST_VAL; // Disable SCU and parity checking. Access to all aliases. scu->inval_all = SCU_WAY_ALL; // Invalidate SCU tag RAMs of all CPUs. scu->ctrl = SCU_CTRL_RST_VAL | SCU_EN; // Enable SCU. syncFlag = true; __sev(); } else while(!syncFlag) __wfe(); // Invalidate TLB (Unified TLB operation). __asm__ volatile("mcr p15, 0, %0, c8, c7, 0" : : "r" (0) : "memory"); __dsb(); // Enable Return stack, Dynamic branch prediction, Static branch prediction, // Instruction folding and SMP mode: the CPU is taking part in coherency. __setAcr(__getAcr() | 0x2F); // Enable MMU, D-Cache, Program flow prediction, // I-Cache, high exception vectors, Unaligned data access, // subpage AP bits disabled. __setCr(__getCr() | 0xC03805); // Invalidate Both Caches. Also flushes the branch target cache. __asm__ volatile("mcr p15, 0, %0, c7, c7, 0" : : "r" (0) : "memory"); __dsb(); __isb(); }