| /* |
| * MMU enable and page table manipulation functions |
| * |
| * Copyright (C) 2014, Red Hat Inc, Andrew Jones <drjones@redhat.com> |
| * |
| * This work is licensed under the terms of the GNU LGPL, version 2. |
| */ |
| #include <cpumask.h> |
| #include <memregions.h> |
| #include <asm/setup.h> |
| #include <asm/thread_info.h> |
| #include <asm/mmu.h> |
| #include <asm/setup.h> |
| #include <asm/page.h> |
| #include <asm/io.h> |
| |
| #include "alloc_page.h" |
| #include "vmalloc.h" |
| #include <asm/pgtable-hwdef.h> |
| #include <asm/pgtable.h> |
| |
| #include <linux/compiler.h> |
| |
| pgd_t *mmu_idmap; |
| |
| /* CPU 0 starts with disabled MMU */ |
| static cpumask_t mmu_enabled_cpumask; |
| |
| bool mmu_enabled(void) |
| { |
| /* |
| * mmu_enabled is called from places that are guarding the |
| * use of exclusive ops (which require the mmu to be enabled). |
| * That means we CANNOT call anything from here that may use a |
| * spinlock, atomic bitop, etc., otherwise we'll recurse. |
| * [cpumask_]test_bit is safe though. |
| */ |
| if (is_user()) { |
| int cpu = current_thread_info()->cpu; |
| return cpumask_test_cpu(cpu, &mmu_enabled_cpumask); |
| } |
| |
| return __mmu_enabled(); |
| } |
| |
| void mmu_mark_enabled(int cpu) |
| { |
| cpumask_set_cpu(cpu, &mmu_enabled_cpumask); |
| } |
| |
| void mmu_mark_disabled(int cpu) |
| { |
| cpumask_clear_cpu(cpu, &mmu_enabled_cpumask); |
| } |
| |
| extern void asm_mmu_enable(phys_addr_t pgtable); |
| void mmu_enable(pgd_t *pgtable) |
| { |
| struct thread_info *info = current_thread_info(); |
| |
| asm_mmu_enable(__pa(pgtable)); |
| |
| info->pgtable = pgtable; |
| mmu_mark_enabled(info->cpu); |
| } |
| |
| extern void asm_mmu_disable(void); |
| void mmu_disable(void) |
| { |
| unsigned long sp = current_stack_pointer; |
| int cpu = current_thread_info()->cpu; |
| |
| assert_msg(__virt_to_phys(sp) == sp, |
| "Attempting to disable MMU with non-identity mapped stack"); |
| |
| mmu_mark_disabled(cpu); |
| |
| asm_mmu_disable(); |
| } |
| |
| static pteval_t *get_pte(pgd_t *pgtable, uintptr_t vaddr) |
| { |
| pgd_t *pgd = pgd_offset(pgtable, vaddr); |
| pud_t *pud = pud_alloc(pgd, vaddr); |
| pmd_t *pmd = pmd_alloc(pud, vaddr); |
| pte_t *pte = pte_alloc(pmd, vaddr); |
| |
| return &pte_val(*pte); |
| } |
| |
| static pteval_t *install_pte(pgd_t *pgtable, uintptr_t vaddr, pteval_t pte) |
| { |
| pteval_t *p_pte = get_pte(pgtable, vaddr); |
| |
| WRITE_ONCE(*p_pte, pte); |
| flush_tlb_page(vaddr); |
| return p_pte; |
| } |
| |
| static pteval_t *install_page_prot(pgd_t *pgtable, phys_addr_t phys, |
| uintptr_t vaddr, pgprot_t prot) |
| { |
| pteval_t pte = phys; |
| pte |= PTE_TYPE_PAGE | PTE_AF | PTE_SHARED; |
| pte |= pgprot_val(prot); |
| return install_pte(pgtable, vaddr, pte); |
| } |
| |
| pteval_t *install_page(pgd_t *pgtable, phys_addr_t phys, void *virt) |
| { |
| return install_page_prot(pgtable, phys, (uintptr_t)virt, |
| __pgprot(PTE_WBWA | PTE_USER)); |
| } |
| |
| /* |
| * NOTE: The Arm architecture might require the use of a |
| * break-before-make sequence before making changes to a PTE and |
| * certain conditions are met (see Arm ARM D5-2669 for AArch64 and |
| * B3-1378 for AArch32 for more details). |
| */ |
| pteval_t *follow_pte(pgd_t *pgtable, uintptr_t vaddr) |
| { |
| pgd_t *pgd; |
| pud_t *pud; |
| pmd_t *pmd; |
| pte_t *pte; |
| |
| pgd = pgd_offset(pgtable, vaddr); |
| if (!pgd_valid(*pgd)) |
| return NULL; |
| |
| pud = pud_offset(pgd, vaddr); |
| if (!pud_valid(*pud)) |
| return NULL; |
| |
| pmd = pmd_offset(pud, vaddr); |
| if (!pmd_valid(*pmd)) |
| return NULL; |
| if (pmd_huge(*pmd)) |
| return &pmd_val(*pmd); |
| |
| pte = pte_offset(pmd, vaddr); |
| if (!pte_valid(*pte)) |
| return NULL; |
| |
| return &pte_val(*pte); |
| } |
| |
| phys_addr_t virt_to_pte_phys(pgd_t *pgtable, void *virt) |
| { |
| phys_addr_t mask; |
| pteval_t *pteval; |
| |
| pteval = follow_pte(pgtable, (uintptr_t)virt); |
| if (!pteval) { |
| install_page(pgtable, (phys_addr_t)(unsigned long)virt, virt); |
| return (phys_addr_t)(unsigned long)virt; |
| } |
| |
| if (pmd_huge(__pmd(*pteval))) |
| mask = PMD_MASK; |
| else |
| mask = PAGE_MASK; |
| |
| return (*pteval & PHYS_MASK & mask) | |
| ((phys_addr_t)(unsigned long)virt & ~mask); |
| } |
| |
| void mmu_set_range_ptes(pgd_t *pgtable, uintptr_t virt_offset, |
| phys_addr_t phys_start, phys_addr_t phys_end, |
| pgprot_t prot) |
| { |
| phys_addr_t paddr = phys_start & PAGE_MASK; |
| uintptr_t vaddr = virt_offset & PAGE_MASK; |
| uintptr_t virt_end = phys_end - paddr + vaddr; |
| |
| for (; vaddr < virt_end; vaddr += PAGE_SIZE, paddr += PAGE_SIZE) |
| install_page_prot(pgtable, paddr, vaddr, prot); |
| } |
| |
| void mmu_set_range_sect(pgd_t *pgtable, uintptr_t virt_offset, |
| phys_addr_t phys_start, phys_addr_t phys_end, |
| pgprot_t prot) |
| { |
| phys_addr_t paddr = phys_start & PMD_MASK; |
| uintptr_t vaddr = virt_offset & PMD_MASK; |
| uintptr_t virt_end = phys_end - paddr + vaddr; |
| pgd_t *pgd; |
| pud_t *pud; |
| pmd_t *pmd; |
| pmd_t entry; |
| |
| for (; vaddr < virt_end; vaddr += PMD_SIZE, paddr += PMD_SIZE) { |
| pmd_val(entry) = paddr; |
| pmd_val(entry) |= PMD_TYPE_SECT | PMD_SECT_AF | PMD_SECT_S; |
| pmd_val(entry) |= pgprot_val(prot); |
| pgd = pgd_offset(pgtable, vaddr); |
| pud = pud_alloc(pgd, vaddr); |
| pmd = pmd_alloc(pud, vaddr); |
| WRITE_ONCE(*pmd, entry); |
| flush_tlb_page(vaddr); |
| } |
| } |
| |
| void *setup_mmu(phys_addr_t phys_end, void *unused) |
| { |
| struct mem_region *r; |
| |
| /* 3G-4G region is reserved for vmalloc, cap phys_end at 3G */ |
| if (phys_end > (3ul << 30)) |
| phys_end = 3ul << 30; |
| |
| #ifdef __aarch64__ |
| init_alloc_vpage((void*)(4ul << 30)); |
| |
| assert_msg(system_supports_granule(PAGE_SIZE), |
| "Unsupported translation granule %ld\n", PAGE_SIZE); |
| #endif |
| |
| if (!mmu_idmap) |
| mmu_idmap = alloc_page(); |
| |
| for (r = mem_regions; r->end; ++r) { |
| if (r->flags & (MR_F_IO | MR_F_RESERVED)) { |
| continue; |
| } else if (r->flags & MR_F_CODE) { |
| /* armv8 requires code shared between EL1 and EL0 to be read-only */ |
| mmu_set_range_ptes(mmu_idmap, r->start, r->start, r->end, |
| __pgprot(PTE_WBWA | PTE_USER | PTE_RDONLY)); |
| } else { |
| mmu_set_range_ptes(mmu_idmap, r->start, r->start, r->end, |
| __pgprot(PTE_WBWA | PTE_USER)); |
| } |
| } |
| |
| mmu_enable(mmu_idmap); |
| return mmu_idmap; |
| } |
| |
| void __iomem *__ioremap(phys_addr_t phys_addr, size_t size) |
| { |
| phys_addr_t paddr_aligned = phys_addr & PAGE_MASK; |
| phys_addr_t paddr_end = PAGE_ALIGN(phys_addr + size); |
| pgprot_t prot = __pgprot(PTE_UNCACHED | PTE_USER | PTE_UXN | PTE_PXN); |
| pgd_t *pgtable; |
| |
| assert(sizeof(long) == 8 || !(phys_addr >> 32)); |
| |
| if (mmu_enabled()) { |
| pgtable = current_thread_info()->pgtable; |
| } else { |
| if (!mmu_idmap) |
| mmu_idmap = alloc_page(); |
| pgtable = mmu_idmap; |
| } |
| |
| mmu_set_range_ptes(pgtable, paddr_aligned, paddr_aligned, |
| paddr_end, prot); |
| |
| return (void __iomem *)(unsigned long)phys_addr; |
| } |
| |
| phys_addr_t __virt_to_phys(unsigned long addr) |
| { |
| if (mmu_enabled()) { |
| pgd_t *pgtable = current_thread_info()->pgtable; |
| return virt_to_pte_phys(pgtable, (void *)addr); |
| } |
| return addr; |
| } |
| |
| unsigned long __phys_to_virt(phys_addr_t addr) |
| { |
| /* |
| * We don't guarantee that phys_to_virt(virt_to_phys(vaddr)) == vaddr, but |
| * the default page tables do identity map all physical addresses, which |
| * means phys_to_virt(virt_to_phys((void *)paddr)) == paddr. |
| */ |
| assert(!mmu_enabled() || __virt_to_phys(addr) == addr); |
| return addr; |
| } |
| |
| void mmu_clear_user(pgd_t *pgtable, unsigned long vaddr) |
| { |
| pteval_t *p_pte = follow_pte(pgtable, vaddr); |
| if (p_pte) { |
| pteval_t entry = *p_pte & ~PTE_USER; |
| WRITE_ONCE(*p_pte, entry); |
| flush_tlb_page(vaddr); |
| } |
| } |