18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * This file contains the routines for initializing the MMU
48c2ecf20Sopenharmony_ci * on the 8xx series of chips.
58c2ecf20Sopenharmony_ci *  -- christophe
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci *  Derived from arch/powerpc/mm/40x_mmu.c:
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/memblock.h>
118c2ecf20Sopenharmony_ci#include <linux/mmu_context.h>
128c2ecf20Sopenharmony_ci#include <linux/hugetlb.h>
138c2ecf20Sopenharmony_ci#include <asm/fixmap.h>
148c2ecf20Sopenharmony_ci#include <asm/code-patching.h>
158c2ecf20Sopenharmony_ci#include <asm/inst.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#include <mm/mmu_decl.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#define IMMR_SIZE (FIX_IMMR_SIZE << PAGE_SHIFT)
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ciextern int __map_without_ltlbs;
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_cistatic unsigned long block_mapped_ram;
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci/*
268c2ecf20Sopenharmony_ci * Return PA for this VA if it is in an area mapped with LTLBs or fixmap.
278c2ecf20Sopenharmony_ci * Otherwise, returns 0
288c2ecf20Sopenharmony_ci */
298c2ecf20Sopenharmony_ciphys_addr_t v_block_mapped(unsigned long va)
308c2ecf20Sopenharmony_ci{
318c2ecf20Sopenharmony_ci	unsigned long p = PHYS_IMMR_BASE;
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	if (va >= VIRT_IMMR_BASE && va < VIRT_IMMR_BASE + IMMR_SIZE)
348c2ecf20Sopenharmony_ci		return p + va - VIRT_IMMR_BASE;
358c2ecf20Sopenharmony_ci	if (__map_without_ltlbs)
368c2ecf20Sopenharmony_ci		return 0;
378c2ecf20Sopenharmony_ci	if (va >= PAGE_OFFSET && va < PAGE_OFFSET + block_mapped_ram)
388c2ecf20Sopenharmony_ci		return __pa(va);
398c2ecf20Sopenharmony_ci	return 0;
408c2ecf20Sopenharmony_ci}
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci/*
438c2ecf20Sopenharmony_ci * Return VA for a given PA mapped with LTLBs or fixmap
448c2ecf20Sopenharmony_ci * Return 0 if not mapped
458c2ecf20Sopenharmony_ci */
468c2ecf20Sopenharmony_ciunsigned long p_block_mapped(phys_addr_t pa)
478c2ecf20Sopenharmony_ci{
488c2ecf20Sopenharmony_ci	unsigned long p = PHYS_IMMR_BASE;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	if (pa >= p && pa < p + IMMR_SIZE)
518c2ecf20Sopenharmony_ci		return VIRT_IMMR_BASE + pa - p;
528c2ecf20Sopenharmony_ci	if (__map_without_ltlbs)
538c2ecf20Sopenharmony_ci		return 0;
548c2ecf20Sopenharmony_ci	if (pa < block_mapped_ram)
558c2ecf20Sopenharmony_ci		return (unsigned long)__va(pa);
568c2ecf20Sopenharmony_ci	return 0;
578c2ecf20Sopenharmony_ci}
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_cistatic pte_t __init *early_hugepd_alloc_kernel(hugepd_t *pmdp, unsigned long va)
608c2ecf20Sopenharmony_ci{
618c2ecf20Sopenharmony_ci	if (hpd_val(*pmdp) == 0) {
628c2ecf20Sopenharmony_ci		pte_t *ptep = memblock_alloc(sizeof(pte_basic_t), SZ_4K);
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci		if (!ptep)
658c2ecf20Sopenharmony_ci			return NULL;
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci		hugepd_populate_kernel((hugepd_t *)pmdp, ptep, PAGE_SHIFT_8M);
688c2ecf20Sopenharmony_ci		hugepd_populate_kernel((hugepd_t *)pmdp + 1, ptep, PAGE_SHIFT_8M);
698c2ecf20Sopenharmony_ci	}
708c2ecf20Sopenharmony_ci	return hugepte_offset(*(hugepd_t *)pmdp, va, PGDIR_SHIFT);
718c2ecf20Sopenharmony_ci}
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_cistatic int __ref __early_map_kernel_hugepage(unsigned long va, phys_addr_t pa,
748c2ecf20Sopenharmony_ci					     pgprot_t prot, int psize, bool new)
758c2ecf20Sopenharmony_ci{
768c2ecf20Sopenharmony_ci	pmd_t *pmdp = pmd_off_k(va);
778c2ecf20Sopenharmony_ci	pte_t *ptep;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	if (WARN_ON(psize != MMU_PAGE_512K && psize != MMU_PAGE_8M))
808c2ecf20Sopenharmony_ci		return -EINVAL;
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	if (new) {
838c2ecf20Sopenharmony_ci		if (WARN_ON(slab_is_available()))
848c2ecf20Sopenharmony_ci			return -EINVAL;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci		if (psize == MMU_PAGE_512K)
878c2ecf20Sopenharmony_ci			ptep = early_pte_alloc_kernel(pmdp, va);
888c2ecf20Sopenharmony_ci		else
898c2ecf20Sopenharmony_ci			ptep = early_hugepd_alloc_kernel((hugepd_t *)pmdp, va);
908c2ecf20Sopenharmony_ci	} else {
918c2ecf20Sopenharmony_ci		if (psize == MMU_PAGE_512K)
928c2ecf20Sopenharmony_ci			ptep = pte_offset_kernel(pmdp, va);
938c2ecf20Sopenharmony_ci		else
948c2ecf20Sopenharmony_ci			ptep = hugepte_offset(*(hugepd_t *)pmdp, va, PGDIR_SHIFT);
958c2ecf20Sopenharmony_ci	}
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	if (WARN_ON(!ptep))
988c2ecf20Sopenharmony_ci		return -ENOMEM;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	/* The PTE should never be already present */
1018c2ecf20Sopenharmony_ci	if (new && WARN_ON(pte_present(*ptep) && pgprot_val(prot)))
1028c2ecf20Sopenharmony_ci		return -EINVAL;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	set_huge_pte_at(&init_mm, va, ptep, pte_mkhuge(pfn_pte(pa >> PAGE_SHIFT, prot)));
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	return 0;
1078c2ecf20Sopenharmony_ci}
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci/*
1108c2ecf20Sopenharmony_ci * MMU_init_hw does the chip-specific initialization of the MMU hardware.
1118c2ecf20Sopenharmony_ci */
1128c2ecf20Sopenharmony_civoid __init MMU_init_hw(void)
1138c2ecf20Sopenharmony_ci{
1148c2ecf20Sopenharmony_ci}
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_cistatic bool immr_is_mapped __initdata;
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_civoid __init mmu_mapin_immr(void)
1198c2ecf20Sopenharmony_ci{
1208c2ecf20Sopenharmony_ci	if (immr_is_mapped)
1218c2ecf20Sopenharmony_ci		return;
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	immr_is_mapped = true;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	__early_map_kernel_hugepage(VIRT_IMMR_BASE, PHYS_IMMR_BASE,
1268c2ecf20Sopenharmony_ci				    PAGE_KERNEL_NCG, MMU_PAGE_512K, true);
1278c2ecf20Sopenharmony_ci}
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_cistatic void mmu_mapin_ram_chunk(unsigned long offset, unsigned long top,
1308c2ecf20Sopenharmony_ci				pgprot_t prot, bool new)
1318c2ecf20Sopenharmony_ci{
1328c2ecf20Sopenharmony_ci	unsigned long v = PAGE_OFFSET + offset;
1338c2ecf20Sopenharmony_ci	unsigned long p = offset;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	WARN_ON(!IS_ALIGNED(offset, SZ_512K) || !IS_ALIGNED(top, SZ_512K));
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	for (; p < ALIGN(p, SZ_8M) && p < top; p += SZ_512K, v += SZ_512K)
1388c2ecf20Sopenharmony_ci		__early_map_kernel_hugepage(v, p, prot, MMU_PAGE_512K, new);
1398c2ecf20Sopenharmony_ci	for (; p < ALIGN_DOWN(top, SZ_8M) && p < top; p += SZ_8M, v += SZ_8M)
1408c2ecf20Sopenharmony_ci		__early_map_kernel_hugepage(v, p, prot, MMU_PAGE_8M, new);
1418c2ecf20Sopenharmony_ci	for (; p < ALIGN_DOWN(top, SZ_512K) && p < top; p += SZ_512K, v += SZ_512K)
1428c2ecf20Sopenharmony_ci		__early_map_kernel_hugepage(v, p, prot, MMU_PAGE_512K, new);
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	if (!new)
1458c2ecf20Sopenharmony_ci		flush_tlb_kernel_range(PAGE_OFFSET + v, PAGE_OFFSET + top);
1468c2ecf20Sopenharmony_ci}
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ciunsigned long __init mmu_mapin_ram(unsigned long base, unsigned long top)
1498c2ecf20Sopenharmony_ci{
1508c2ecf20Sopenharmony_ci	unsigned long etext8 = ALIGN(__pa(_etext), SZ_8M);
1518c2ecf20Sopenharmony_ci	unsigned long sinittext = __pa(_sinittext);
1528c2ecf20Sopenharmony_ci	bool strict_boundary = strict_kernel_rwx_enabled() || debug_pagealloc_enabled();
1538c2ecf20Sopenharmony_ci	unsigned long boundary = strict_boundary ? sinittext : etext8;
1548c2ecf20Sopenharmony_ci	unsigned long einittext8 = ALIGN(__pa(_einittext), SZ_8M);
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	WARN_ON(top < einittext8);
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	mmu_mapin_immr();
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	if (__map_without_ltlbs)
1618c2ecf20Sopenharmony_ci		return 0;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	mmu_mapin_ram_chunk(0, boundary, PAGE_KERNEL_TEXT, true);
1648c2ecf20Sopenharmony_ci	if (debug_pagealloc_enabled()) {
1658c2ecf20Sopenharmony_ci		top = boundary;
1668c2ecf20Sopenharmony_ci	} else {
1678c2ecf20Sopenharmony_ci		mmu_mapin_ram_chunk(boundary, einittext8, PAGE_KERNEL_TEXT, true);
1688c2ecf20Sopenharmony_ci		mmu_mapin_ram_chunk(einittext8, top, PAGE_KERNEL, true);
1698c2ecf20Sopenharmony_ci	}
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	if (top > SZ_32M)
1728c2ecf20Sopenharmony_ci		memblock_set_current_limit(top);
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	block_mapped_ram = top;
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	return top;
1778c2ecf20Sopenharmony_ci}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_civoid mmu_mark_initmem_nx(void)
1808c2ecf20Sopenharmony_ci{
1818c2ecf20Sopenharmony_ci	unsigned long etext8 = ALIGN(__pa(_etext), SZ_8M);
1828c2ecf20Sopenharmony_ci	unsigned long sinittext = __pa(_sinittext);
1838c2ecf20Sopenharmony_ci	unsigned long boundary = strict_kernel_rwx_enabled() ? sinittext : etext8;
1848c2ecf20Sopenharmony_ci	unsigned long einittext8 = ALIGN(__pa(_einittext), SZ_8M);
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	mmu_mapin_ram_chunk(0, boundary, PAGE_KERNEL_TEXT, false);
1878c2ecf20Sopenharmony_ci	mmu_mapin_ram_chunk(boundary, einittext8, PAGE_KERNEL, false);
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	if (IS_ENABLED(CONFIG_PIN_TLB_TEXT))
1908c2ecf20Sopenharmony_ci		mmu_pin_tlb(block_mapped_ram, false);
1918c2ecf20Sopenharmony_ci}
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci#ifdef CONFIG_STRICT_KERNEL_RWX
1948c2ecf20Sopenharmony_civoid mmu_mark_rodata_ro(void)
1958c2ecf20Sopenharmony_ci{
1968c2ecf20Sopenharmony_ci	unsigned long sinittext = __pa(_sinittext);
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	mmu_mapin_ram_chunk(0, sinittext, PAGE_KERNEL_ROX, false);
1998c2ecf20Sopenharmony_ci	if (IS_ENABLED(CONFIG_PIN_TLB_DATA))
2008c2ecf20Sopenharmony_ci		mmu_pin_tlb(block_mapped_ram, true);
2018c2ecf20Sopenharmony_ci}
2028c2ecf20Sopenharmony_ci#endif
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_civoid __init setup_initial_memory_limit(phys_addr_t first_memblock_base,
2058c2ecf20Sopenharmony_ci				       phys_addr_t first_memblock_size)
2068c2ecf20Sopenharmony_ci{
2078c2ecf20Sopenharmony_ci	/* We don't currently support the first MEMBLOCK not mapping 0
2088c2ecf20Sopenharmony_ci	 * physical on those processors
2098c2ecf20Sopenharmony_ci	 */
2108c2ecf20Sopenharmony_ci	BUG_ON(first_memblock_base != 0);
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	/* 8xx can only access 32MB at the moment */
2138c2ecf20Sopenharmony_ci	memblock_set_current_limit(min_t(u64, first_memblock_size, SZ_32M));
2148c2ecf20Sopenharmony_ci}
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci/*
2178c2ecf20Sopenharmony_ci * Set up to use a given MMU context.
2188c2ecf20Sopenharmony_ci * id is context number, pgd is PGD pointer.
2198c2ecf20Sopenharmony_ci *
2208c2ecf20Sopenharmony_ci * We place the physical address of the new task page directory loaded
2218c2ecf20Sopenharmony_ci * into the MMU base register, and set the ASID compare register with
2228c2ecf20Sopenharmony_ci * the new "context."
2238c2ecf20Sopenharmony_ci */
2248c2ecf20Sopenharmony_civoid set_context(unsigned long id, pgd_t *pgd)
2258c2ecf20Sopenharmony_ci{
2268c2ecf20Sopenharmony_ci	s16 offset = (s16)(__pa(swapper_pg_dir));
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci	/* Context switch the PTE pointer for the Abatron BDI2000.
2298c2ecf20Sopenharmony_ci	 * The PGDIR is passed as second argument.
2308c2ecf20Sopenharmony_ci	 */
2318c2ecf20Sopenharmony_ci	if (IS_ENABLED(CONFIG_BDI_SWITCH))
2328c2ecf20Sopenharmony_ci		abatron_pteptrs[1] = pgd;
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	/* Register M_TWB will contain base address of level 1 table minus the
2358c2ecf20Sopenharmony_ci	 * lower part of the kernel PGDIR base address, so that all accesses to
2368c2ecf20Sopenharmony_ci	 * level 1 table are done relative to lower part of kernel PGDIR base
2378c2ecf20Sopenharmony_ci	 * address.
2388c2ecf20Sopenharmony_ci	 */
2398c2ecf20Sopenharmony_ci	mtspr(SPRN_M_TWB, __pa(pgd) - offset);
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci	/* Update context */
2428c2ecf20Sopenharmony_ci	mtspr(SPRN_M_CASID, id - 1);
2438c2ecf20Sopenharmony_ci	/* sync */
2448c2ecf20Sopenharmony_ci	mb();
2458c2ecf20Sopenharmony_ci}
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ci#ifdef CONFIG_PPC_KUEP
2488c2ecf20Sopenharmony_civoid __init setup_kuep(bool disabled)
2498c2ecf20Sopenharmony_ci{
2508c2ecf20Sopenharmony_ci	if (disabled)
2518c2ecf20Sopenharmony_ci		return;
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci	pr_info("Activating Kernel Userspace Execution Prevention\n");
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_ci	mtspr(SPRN_MI_AP, MI_APG_KUEP);
2568c2ecf20Sopenharmony_ci}
2578c2ecf20Sopenharmony_ci#endif
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci#ifdef CONFIG_PPC_KUAP
2608c2ecf20Sopenharmony_civoid __init setup_kuap(bool disabled)
2618c2ecf20Sopenharmony_ci{
2628c2ecf20Sopenharmony_ci	pr_info("Activating Kernel Userspace Access Protection\n");
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	if (disabled)
2658c2ecf20Sopenharmony_ci		pr_warn("KUAP cannot be disabled yet on 8xx when compiled in\n");
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci	mtspr(SPRN_MD_AP, MD_APG_KUAP);
2688c2ecf20Sopenharmony_ci}
2698c2ecf20Sopenharmony_ci#endif
270