/* * Copyright (c) 2018 naehrwert * Copyright (c) 2018 balika011 * Copyright (c) 2018-2024 CTCaer * * 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 . */ #include #include #include #include #include #include #include #include /*! SMMU register defines */ #define SMMU_ASID(asid) (((asid) << 24u) | ((asid) << 16u) | ((asid) << 8u) | (asid)) #define SMMU_ENABLE BIT(31) #define SMMU_TLB_ACTIVE_LINES(l) ((l) << 0u) #define SMMU_TLB_RR_ARBITRATION BIT(28) #define SMMU_TLB_HIT_UNDER_MISS BIT(29) #define SMMU_TLB_STATS_ENABLE BIT(31) #define SMUU_PTC_INDEX_MAP(m) ((m) << 0u) #define SMUU_PTC_LINE_MASK(m) ((m) << 8u) #define SMUU_PTC_REQ_LIMIT(l) ((l) << 24u) #define SMUU_PTC_CACHE_ENABLE BIT(29) #define SMUU_PTC_STATS_ENABLE BIT(31) /*! Page table defines */ #define SMMU_4MB_REGION 0 #define SMMU_PAGE_TABLE 1 #define SMMU_PDIR_COUNT 1024 #define SMMU_PTBL_COUNT 1024 #define SMMU_PAGE_SHIFT 12u #define SMMU_PTN_SHIFT SMMU_PAGE_SHIFT #define SMMU_PDN_SHIFT 22u #define SMMU_ADDR_TO_PFN(addr) ((addr) >> SMMU_PAGE_SHIFT) #define SMMU_ADDR_TO_PTN(addr) ((addr) >> SMMU_PTN_SHIFT) #define SMMU_ADDR_TO_PDN(addr) ((addr) >> SMMU_PDN_SHIFT) #define SMMU_PTN_TO_ADDR(ptn) ((ptn) << SMMU_PTN_SHIFT) #define SMMU_PDN_TO_ADDR(pdn) ((pdn) << SMMU_PDN_SHIFT) #define SMMU_PTB(page, attr) (((attr) << 29u) | ((page) >> SMMU_PAGE_SHIFT)) static void *smmu_heap = (void *)SMMU_HEAP_ADDR; // Enabling SMMU requires a TZ (EL3) secure write. MC(MC_SMMU_CONFIG) = 1; static const u8 smmu_enable_payload[] = { 0xC1, 0x00, 0x00, 0x18, // 0x00: LDR W1, =0x70019010 0x20, 0x00, 0x80, 0xD2, // 0x04: MOV X0, #0x1 0x20, 0x00, 0x00, 0xB9, // 0x08: STR W0, [X1] 0x1F, 0x71, 0x08, 0xD5, // 0x0C: IC IALLUIS 0x9F, 0x3B, 0x03, 0xD5, // 0x10: DSB ISH 0xFE, 0xFF, 0xFF, 0x17, // 0x14: B loop 0x10, 0x90, 0x01, 0x70, // 0x18: MC_SMMU_CONFIG }; void *smmu_page_zalloc(u32 num) { void *page = smmu_heap; memset(page, 0, SZ_PAGE * num); smmu_heap += SZ_PAGE * num; return page; } static pde_t *_smmu_pdir_alloc() { pde_t *pdir = (pde_t *)smmu_page_zalloc(1); // Initialize pdes with no permissions. for (u32 pdn = 0; pdn < SMMU_PDIR_COUNT; pdn++) pdir[pdn].huge.page = pdn; return pdir; } static void _smmu_flush_regs() { (void)MC(MC_SMMU_PTB_DATA); } void smmu_flush_all() { // Flush the entire page table cache. MC(MC_SMMU_PTC_FLUSH) = 0; _smmu_flush_regs(); // Flush the entire table. MC(MC_SMMU_TLB_FLUSH) = 0; _smmu_flush_regs(); } void smmu_init() { MC(MC_SMMU_PTB_ASID) = 0; MC(MC_SMMU_PTB_DATA) = 0; MC(MC_SMMU_TLB_CONFIG) = SMMU_TLB_HIT_UNDER_MISS | SMMU_TLB_RR_ARBITRATION | SMMU_TLB_ACTIVE_LINES(48); MC(MC_SMMU_PTC_CONFIG) = SMUU_PTC_CACHE_ENABLE | SMUU_PTC_REQ_LIMIT(8) | SMUU_PTC_LINE_MASK(0xF) | SMUU_PTC_INDEX_MAP(0x3F); MC(MC_SMMU_PTC_FLUSH) = 0; MC(MC_SMMU_TLB_FLUSH) = 0; } void smmu_enable() { static bool enabled = false; if (enabled) return; // Launch payload on CCPLEX in order to set SMMU enable bit. ccplex_boot_cpu0((u32)smmu_enable_payload, false); msleep(100); ccplex_powergate_cpu0(); smmu_flush_all(); enabled = true; } void smmu_reset_heap() { smmu_heap = (void *)SMMU_HEAP_ADDR; } void *smmu_init_domain(u32 dev_base, u32 asid) { void *ptb = _smmu_pdir_alloc(); MC(MC_SMMU_PTB_ASID) = asid; MC(MC_SMMU_PTB_DATA) = SMMU_PTB((u32)ptb, SMMU_ATTR_ALL); _smmu_flush_regs(); // Use the same macro for both quad and single domains. Reserved bits are not set anyway. MC(dev_base) = SMMU_ENABLE | SMMU_ASID(asid); _smmu_flush_regs(); return ptb; } void smmu_deinit_domain(u32 dev_base, u32 asid) { MC(MC_SMMU_PTB_ASID) = asid; MC(MC_SMMU_PTB_DATA) = 0; MC(dev_base) = 0; _smmu_flush_regs(); } void smmu_domain_bypass(u32 dev_base, bool bypass) { if (bypass) { smmu_flush_all(); bpmp_mmu_maintenance(BPMP_MMU_MAINT_CLN_INV_WAY, false); MC(dev_base) &= ~SMMU_ENABLE; } else { bpmp_mmu_maintenance(BPMP_MMU_MAINT_CLN_INV_WAY, false); MC(dev_base) |= SMMU_ENABLE; smmu_flush_all(); } _smmu_flush_regs(); } static pte_t *_smmu_get_pte(pde_t *pdir, u32 iova) { u32 pdn = SMMU_ADDR_TO_PDN(iova); pte_t *ptbl; // Get 4MB page table or initialize one. if (pdir[pdn].tbl.attr) ptbl = (pte_t *)(SMMU_PTN_TO_ADDR(pdir[pdn].tbl.table)); else { // Allocate page table. ptbl = (pte_t *)smmu_page_zalloc(1); // Get address. u32 addr = SMMU_PDN_TO_ADDR(pdn); // Initialize page table with no permissions. for (u32 pn = 0; pn < SMMU_PTBL_COUNT; pn++, addr += SZ_PAGE) ptbl[pn].page = SMMU_ADDR_TO_PFN(addr); // Set page table to the page directory. pdir[pdn].tbl.table = SMMU_ADDR_TO_PTN((u32)ptbl); pdir[pdn].tbl.next = SMMU_PAGE_TABLE; pdir[pdn].tbl.attr = SMMU_ATTR_ALL; smmu_flush_all(); } return &ptbl[SMMU_ADDR_TO_PTN(iova) % SMMU_PTBL_COUNT]; } void smmu_map(void *ptb, u32 iova, u64 iopa, u32 pages, u32 attr) { // Map pages to page table entries. VA/PA should be aligned to 4KB. for (u32 i = 0; i < pages; i++) { pte_t *pte = _smmu_get_pte((pde_t *)ptb, iova); pte->page = SMMU_ADDR_TO_PFN(iopa); pte->attr = attr; iova += SZ_PAGE; iopa += SZ_PAGE; } smmu_flush_all(); } void smmu_map_huge(void *ptb, u32 iova, u64 iopa, u32 regions, u32 attr) { pde_t *pdir = (pde_t *)ptb; // Map 4MB regions to page directory entries. VA/PA should be aligned to 4MB. for (u32 i = 0; i < regions; i++) { u32 pdn = SMMU_ADDR_TO_PDN(iova); pdir[pdn].huge.page = SMMU_ADDR_TO_PDN(iopa); pdir[pdn].huge.next = SMMU_4MB_REGION; pdir[pdn].huge.attr = attr; iova += SZ_4M; iopa += SZ_4M; } smmu_flush_all(); }