162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci#include <linux/module.h>
362306a36Sopenharmony_ci#include <linux/kernel.h>
462306a36Sopenharmony_ci#include <linux/slab.h>
562306a36Sopenharmony_ci#include <linux/mm_types.h>
662306a36Sopenharmony_ci#include <linux/pgtable.h>
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <asm/cputype.h>
962306a36Sopenharmony_ci#include <asm/idmap.h>
1062306a36Sopenharmony_ci#include <asm/hwcap.h>
1162306a36Sopenharmony_ci#include <asm/pgalloc.h>
1262306a36Sopenharmony_ci#include <asm/sections.h>
1362306a36Sopenharmony_ci#include <asm/system_info.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci/*
1662306a36Sopenharmony_ci * Note: accesses outside of the kernel image and the identity map area
1762306a36Sopenharmony_ci * are not supported on any CPU using the idmap tables as its current
1862306a36Sopenharmony_ci * page tables.
1962306a36Sopenharmony_ci */
2062306a36Sopenharmony_cipgd_t *idmap_pgd __ro_after_init;
2162306a36Sopenharmony_cilong long arch_phys_to_idmap_offset __ro_after_init;
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#ifdef CONFIG_ARM_LPAE
2462306a36Sopenharmony_cistatic void idmap_add_pmd(pud_t *pud, unsigned long addr, unsigned long end,
2562306a36Sopenharmony_ci	unsigned long prot)
2662306a36Sopenharmony_ci{
2762306a36Sopenharmony_ci	pmd_t *pmd;
2862306a36Sopenharmony_ci	unsigned long next;
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	if (pud_none_or_clear_bad(pud) || (pud_val(*pud) & L_PGD_SWAPPER)) {
3162306a36Sopenharmony_ci		pmd = pmd_alloc_one(&init_mm, addr);
3262306a36Sopenharmony_ci		if (!pmd) {
3362306a36Sopenharmony_ci			pr_warn("Failed to allocate identity pmd.\n");
3462306a36Sopenharmony_ci			return;
3562306a36Sopenharmony_ci		}
3662306a36Sopenharmony_ci		/*
3762306a36Sopenharmony_ci		 * Copy the original PMD to ensure that the PMD entries for
3862306a36Sopenharmony_ci		 * the kernel image are preserved.
3962306a36Sopenharmony_ci		 */
4062306a36Sopenharmony_ci		if (!pud_none(*pud))
4162306a36Sopenharmony_ci			memcpy(pmd, pmd_offset(pud, 0),
4262306a36Sopenharmony_ci			       PTRS_PER_PMD * sizeof(pmd_t));
4362306a36Sopenharmony_ci		pud_populate(&init_mm, pud, pmd);
4462306a36Sopenharmony_ci		pmd += pmd_index(addr);
4562306a36Sopenharmony_ci	} else
4662306a36Sopenharmony_ci		pmd = pmd_offset(pud, addr);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	do {
4962306a36Sopenharmony_ci		next = pmd_addr_end(addr, end);
5062306a36Sopenharmony_ci		*pmd = __pmd((addr & PMD_MASK) | prot);
5162306a36Sopenharmony_ci		flush_pmd_entry(pmd);
5262306a36Sopenharmony_ci	} while (pmd++, addr = next, addr != end);
5362306a36Sopenharmony_ci}
5462306a36Sopenharmony_ci#else	/* !CONFIG_ARM_LPAE */
5562306a36Sopenharmony_cistatic void idmap_add_pmd(pud_t *pud, unsigned long addr, unsigned long end,
5662306a36Sopenharmony_ci	unsigned long prot)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	pmd_t *pmd = pmd_offset(pud, addr);
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	addr = (addr & PMD_MASK) | prot;
6162306a36Sopenharmony_ci	pmd[0] = __pmd(addr);
6262306a36Sopenharmony_ci	addr += SECTION_SIZE;
6362306a36Sopenharmony_ci	pmd[1] = __pmd(addr);
6462306a36Sopenharmony_ci	flush_pmd_entry(pmd);
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci#endif	/* CONFIG_ARM_LPAE */
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic void idmap_add_pud(pgd_t *pgd, unsigned long addr, unsigned long end,
6962306a36Sopenharmony_ci	unsigned long prot)
7062306a36Sopenharmony_ci{
7162306a36Sopenharmony_ci	p4d_t *p4d = p4d_offset(pgd, addr);
7262306a36Sopenharmony_ci	pud_t *pud = pud_offset(p4d, addr);
7362306a36Sopenharmony_ci	unsigned long next;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	do {
7662306a36Sopenharmony_ci		next = pud_addr_end(addr, end);
7762306a36Sopenharmony_ci		idmap_add_pmd(pud, addr, next, prot);
7862306a36Sopenharmony_ci	} while (pud++, addr = next, addr != end);
7962306a36Sopenharmony_ci}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_cistatic void identity_mapping_add(pgd_t *pgd, const char *text_start,
8262306a36Sopenharmony_ci				 const char *text_end, unsigned long prot)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	unsigned long addr, end;
8562306a36Sopenharmony_ci	unsigned long next;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	addr = virt_to_idmap(text_start);
8862306a36Sopenharmony_ci	end = virt_to_idmap(text_end);
8962306a36Sopenharmony_ci	pr_info("Setting up static identity map for 0x%lx - 0x%lx\n", addr, end);
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	prot |= PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AF;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	if (cpu_architecture() <= CPU_ARCH_ARMv5TEJ && !cpu_is_xscale_family())
9462306a36Sopenharmony_ci		prot |= PMD_BIT4;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	pgd += pgd_index(addr);
9762306a36Sopenharmony_ci	do {
9862306a36Sopenharmony_ci		next = pgd_addr_end(addr, end);
9962306a36Sopenharmony_ci		idmap_add_pud(pgd, addr, next, prot);
10062306a36Sopenharmony_ci	} while (pgd++, addr = next, addr != end);
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ciextern char  __idmap_text_start[], __idmap_text_end[];
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistatic int __init init_static_idmap(void)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	idmap_pgd = pgd_alloc(&init_mm);
10862306a36Sopenharmony_ci	if (!idmap_pgd)
10962306a36Sopenharmony_ci		return -ENOMEM;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	identity_mapping_add(idmap_pgd, __idmap_text_start,
11262306a36Sopenharmony_ci			     __idmap_text_end, 0);
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	/* Flush L1 for the hardware to see this page table content */
11562306a36Sopenharmony_ci	if (!(elf_hwcap & HWCAP_LPAE))
11662306a36Sopenharmony_ci		flush_cache_louis();
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	return 0;
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ciearly_initcall(init_static_idmap);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci/*
12362306a36Sopenharmony_ci * In order to soft-boot, we need to switch to a 1:1 mapping for the
12462306a36Sopenharmony_ci * cpu_reset functions. This will then ensure that we have predictable
12562306a36Sopenharmony_ci * results when turning off the mmu.
12662306a36Sopenharmony_ci */
12762306a36Sopenharmony_civoid setup_mm_for_reboot(void)
12862306a36Sopenharmony_ci{
12962306a36Sopenharmony_ci	/* Switch to the identity mapping. */
13062306a36Sopenharmony_ci	cpu_switch_mm(idmap_pgd, &init_mm);
13162306a36Sopenharmony_ci	local_flush_bp_all();
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci#ifdef CONFIG_CPU_HAS_ASID
13462306a36Sopenharmony_ci	/*
13562306a36Sopenharmony_ci	 * We don't have a clean ASID for the identity mapping, which
13662306a36Sopenharmony_ci	 * may clash with virtual addresses of the previous page tables
13762306a36Sopenharmony_ci	 * and therefore potentially in the TLB.
13862306a36Sopenharmony_ci	 */
13962306a36Sopenharmony_ci	local_flush_tlb_all();
14062306a36Sopenharmony_ci#endif
14162306a36Sopenharmony_ci}
142