18c2ecf20Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0-only */
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Based on arch/arm/include/asm/mmu_context.h
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 1996 Russell King.
68c2ecf20Sopenharmony_ci * Copyright (C) 2012 ARM Ltd.
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci#ifndef __ASM_MMU_CONTEXT_H
98c2ecf20Sopenharmony_ci#define __ASM_MMU_CONTEXT_H
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#ifndef __ASSEMBLY__
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include <linux/compiler.h>
148c2ecf20Sopenharmony_ci#include <linux/sched.h>
158c2ecf20Sopenharmony_ci#include <linux/sched/hotplug.h>
168c2ecf20Sopenharmony_ci#include <linux/mm_types.h>
178c2ecf20Sopenharmony_ci#include <linux/pgtable.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include <asm/cacheflush.h>
208c2ecf20Sopenharmony_ci#include <asm/cpufeature.h>
218c2ecf20Sopenharmony_ci#include <asm/proc-fns.h>
228c2ecf20Sopenharmony_ci#include <asm-generic/mm_hooks.h>
238c2ecf20Sopenharmony_ci#include <asm/cputype.h>
248c2ecf20Sopenharmony_ci#include <asm/sysreg.h>
258c2ecf20Sopenharmony_ci#include <asm/tlbflush.h>
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ciextern bool rodata_full;
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistatic inline void contextidr_thread_switch(struct task_struct *next)
308c2ecf20Sopenharmony_ci{
318c2ecf20Sopenharmony_ci	if (!IS_ENABLED(CONFIG_PID_IN_CONTEXTIDR))
328c2ecf20Sopenharmony_ci		return;
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci	write_sysreg(task_pid_nr(next), contextidr_el1);
358c2ecf20Sopenharmony_ci	isb();
368c2ecf20Sopenharmony_ci}
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci/*
398c2ecf20Sopenharmony_ci * Set TTBR0 to reserved_pg_dir. No translations will be possible via TTBR0.
408c2ecf20Sopenharmony_ci */
418c2ecf20Sopenharmony_cistatic inline void cpu_set_reserved_ttbr0(void)
428c2ecf20Sopenharmony_ci{
438c2ecf20Sopenharmony_ci	unsigned long ttbr = phys_to_ttbr(__pa_symbol(reserved_pg_dir));
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci	write_sysreg(ttbr, ttbr0_el1);
468c2ecf20Sopenharmony_ci	isb();
478c2ecf20Sopenharmony_ci}
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_civoid cpu_do_switch_mm(phys_addr_t pgd_phys, struct mm_struct *mm);
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_cistatic inline void cpu_switch_mm(pgd_t *pgd, struct mm_struct *mm)
528c2ecf20Sopenharmony_ci{
538c2ecf20Sopenharmony_ci	BUG_ON(pgd == swapper_pg_dir);
548c2ecf20Sopenharmony_ci	cpu_set_reserved_ttbr0();
558c2ecf20Sopenharmony_ci	cpu_do_switch_mm(virt_to_phys(pgd),mm);
568c2ecf20Sopenharmony_ci}
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci/*
598c2ecf20Sopenharmony_ci * TCR.T0SZ value to use when the ID map is active. Usually equals
608c2ecf20Sopenharmony_ci * TCR_T0SZ(VA_BITS), unless system RAM is positioned very high in
618c2ecf20Sopenharmony_ci * physical memory, in which case it will be smaller.
628c2ecf20Sopenharmony_ci */
638c2ecf20Sopenharmony_ciextern u64 idmap_t0sz;
648c2ecf20Sopenharmony_ciextern u64 idmap_ptrs_per_pgd;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistatic inline bool __cpu_uses_extended_idmap(void)
678c2ecf20Sopenharmony_ci{
688c2ecf20Sopenharmony_ci	return unlikely(idmap_t0sz != TCR_T0SZ(vabits_actual));
698c2ecf20Sopenharmony_ci}
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci/*
728c2ecf20Sopenharmony_ci * True if the extended ID map requires an extra level of translation table
738c2ecf20Sopenharmony_ci * to be configured.
748c2ecf20Sopenharmony_ci */
758c2ecf20Sopenharmony_cistatic inline bool __cpu_uses_extended_idmap_level(void)
768c2ecf20Sopenharmony_ci{
778c2ecf20Sopenharmony_ci	return ARM64_HW_PGTABLE_LEVELS(64 - idmap_t0sz) > CONFIG_PGTABLE_LEVELS;
788c2ecf20Sopenharmony_ci}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci/*
818c2ecf20Sopenharmony_ci * Set TCR.T0SZ to its default value (based on VA_BITS)
828c2ecf20Sopenharmony_ci */
838c2ecf20Sopenharmony_cistatic inline void __cpu_set_tcr_t0sz(unsigned long t0sz)
848c2ecf20Sopenharmony_ci{
858c2ecf20Sopenharmony_ci	unsigned long tcr;
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	if (!__cpu_uses_extended_idmap())
888c2ecf20Sopenharmony_ci		return;
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	tcr = read_sysreg(tcr_el1);
918c2ecf20Sopenharmony_ci	tcr &= ~TCR_T0SZ_MASK;
928c2ecf20Sopenharmony_ci	tcr |= t0sz << TCR_T0SZ_OFFSET;
938c2ecf20Sopenharmony_ci	write_sysreg(tcr, tcr_el1);
948c2ecf20Sopenharmony_ci	isb();
958c2ecf20Sopenharmony_ci}
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci#define cpu_set_default_tcr_t0sz()	__cpu_set_tcr_t0sz(TCR_T0SZ(vabits_actual))
988c2ecf20Sopenharmony_ci#define cpu_set_idmap_tcr_t0sz()	__cpu_set_tcr_t0sz(idmap_t0sz)
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci/*
1018c2ecf20Sopenharmony_ci * Remove the idmap from TTBR0_EL1 and install the pgd of the active mm.
1028c2ecf20Sopenharmony_ci *
1038c2ecf20Sopenharmony_ci * The idmap lives in the same VA range as userspace, but uses global entries
1048c2ecf20Sopenharmony_ci * and may use a different TCR_EL1.T0SZ. To avoid issues resulting from
1058c2ecf20Sopenharmony_ci * speculative TLB fetches, we must temporarily install the reserved page
1068c2ecf20Sopenharmony_ci * tables while we invalidate the TLBs and set up the correct TCR_EL1.T0SZ.
1078c2ecf20Sopenharmony_ci *
1088c2ecf20Sopenharmony_ci * If current is a not a user task, the mm covers the TTBR1_EL1 page tables,
1098c2ecf20Sopenharmony_ci * which should not be installed in TTBR0_EL1. In this case we can leave the
1108c2ecf20Sopenharmony_ci * reserved page tables in place.
1118c2ecf20Sopenharmony_ci */
1128c2ecf20Sopenharmony_cistatic inline void cpu_uninstall_idmap(void)
1138c2ecf20Sopenharmony_ci{
1148c2ecf20Sopenharmony_ci	struct mm_struct *mm = current->active_mm;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	cpu_set_reserved_ttbr0();
1178c2ecf20Sopenharmony_ci	local_flush_tlb_all();
1188c2ecf20Sopenharmony_ci	cpu_set_default_tcr_t0sz();
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	if (mm != &init_mm && !system_uses_ttbr0_pan())
1218c2ecf20Sopenharmony_ci		cpu_switch_mm(mm->pgd, mm);
1228c2ecf20Sopenharmony_ci}
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_cistatic inline void cpu_install_idmap(void)
1258c2ecf20Sopenharmony_ci{
1268c2ecf20Sopenharmony_ci	cpu_set_reserved_ttbr0();
1278c2ecf20Sopenharmony_ci	local_flush_tlb_all();
1288c2ecf20Sopenharmony_ci	cpu_set_idmap_tcr_t0sz();
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	cpu_switch_mm(lm_alias(idmap_pg_dir), &init_mm);
1318c2ecf20Sopenharmony_ci}
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci/*
1348c2ecf20Sopenharmony_ci * Atomically replaces the active TTBR1_EL1 PGD with a new VA-compatible PGD,
1358c2ecf20Sopenharmony_ci * avoiding the possibility of conflicting TLB entries being allocated.
1368c2ecf20Sopenharmony_ci */
1378c2ecf20Sopenharmony_cistatic inline void __nocfi cpu_replace_ttbr1(pgd_t *pgdp)
1388c2ecf20Sopenharmony_ci{
1398c2ecf20Sopenharmony_ci	typedef void (ttbr_replace_func)(phys_addr_t);
1408c2ecf20Sopenharmony_ci	extern ttbr_replace_func idmap_cpu_replace_ttbr1;
1418c2ecf20Sopenharmony_ci	ttbr_replace_func *replace_phys;
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	/* phys_to_ttbr() zeros lower 2 bits of ttbr with 52-bit PA */
1448c2ecf20Sopenharmony_ci	phys_addr_t ttbr1 = phys_to_ttbr(virt_to_phys(pgdp));
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	if (system_supports_cnp() && !WARN_ON(pgdp != lm_alias(swapper_pg_dir))) {
1478c2ecf20Sopenharmony_ci		/*
1488c2ecf20Sopenharmony_ci		 * cpu_replace_ttbr1() is used when there's a boot CPU
1498c2ecf20Sopenharmony_ci		 * up (i.e. cpufeature framework is not up yet) and
1508c2ecf20Sopenharmony_ci		 * latter only when we enable CNP via cpufeature's
1518c2ecf20Sopenharmony_ci		 * enable() callback.
1528c2ecf20Sopenharmony_ci		 * Also we rely on the cpu_hwcap bit being set before
1538c2ecf20Sopenharmony_ci		 * calling the enable() function.
1548c2ecf20Sopenharmony_ci		 */
1558c2ecf20Sopenharmony_ci		ttbr1 |= TTBR_CNP_BIT;
1568c2ecf20Sopenharmony_ci	}
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	replace_phys = (void *)__pa_symbol(function_nocfi(idmap_cpu_replace_ttbr1));
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	cpu_install_idmap();
1618c2ecf20Sopenharmony_ci	replace_phys(ttbr1);
1628c2ecf20Sopenharmony_ci	cpu_uninstall_idmap();
1638c2ecf20Sopenharmony_ci}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci/*
1668c2ecf20Sopenharmony_ci * It would be nice to return ASIDs back to the allocator, but unfortunately
1678c2ecf20Sopenharmony_ci * that introduces a race with a generation rollover where we could erroneously
1688c2ecf20Sopenharmony_ci * free an ASID allocated in a future generation. We could workaround this by
1698c2ecf20Sopenharmony_ci * freeing the ASID from the context of the dying mm (e.g. in arch_exit_mmap),
1708c2ecf20Sopenharmony_ci * but we'd then need to make sure that we didn't dirty any TLBs afterwards.
1718c2ecf20Sopenharmony_ci * Setting a reserved TTBR0 or EPD0 would work, but it all gets ugly when you
1728c2ecf20Sopenharmony_ci * take CPU migration into account.
1738c2ecf20Sopenharmony_ci */
1748c2ecf20Sopenharmony_ci#define destroy_context(mm)		do { } while(0)
1758c2ecf20Sopenharmony_civoid check_and_switch_context(struct mm_struct *mm);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_cistatic inline int
1788c2ecf20Sopenharmony_ciinit_new_context(struct task_struct *tsk, struct mm_struct *mm)
1798c2ecf20Sopenharmony_ci{
1808c2ecf20Sopenharmony_ci	atomic64_set(&mm->context.id, 0);
1818c2ecf20Sopenharmony_ci	refcount_set(&mm->context.pinned, 0);
1828c2ecf20Sopenharmony_ci	return 0;
1838c2ecf20Sopenharmony_ci}
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci#ifdef CONFIG_ARM64_SW_TTBR0_PAN
1868c2ecf20Sopenharmony_cistatic inline void update_saved_ttbr0(struct task_struct *tsk,
1878c2ecf20Sopenharmony_ci				      struct mm_struct *mm)
1888c2ecf20Sopenharmony_ci{
1898c2ecf20Sopenharmony_ci	u64 ttbr;
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	if (!system_uses_ttbr0_pan())
1928c2ecf20Sopenharmony_ci		return;
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	if (mm == &init_mm)
1958c2ecf20Sopenharmony_ci		ttbr = phys_to_ttbr(__pa_symbol(reserved_pg_dir));
1968c2ecf20Sopenharmony_ci	else
1978c2ecf20Sopenharmony_ci		ttbr = phys_to_ttbr(virt_to_phys(mm->pgd)) | ASID(mm) << 48;
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	WRITE_ONCE(task_thread_info(tsk)->ttbr0, ttbr);
2008c2ecf20Sopenharmony_ci}
2018c2ecf20Sopenharmony_ci#else
2028c2ecf20Sopenharmony_cistatic inline void update_saved_ttbr0(struct task_struct *tsk,
2038c2ecf20Sopenharmony_ci				      struct mm_struct *mm)
2048c2ecf20Sopenharmony_ci{
2058c2ecf20Sopenharmony_ci}
2068c2ecf20Sopenharmony_ci#endif
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_cistatic inline void
2098c2ecf20Sopenharmony_cienter_lazy_tlb(struct mm_struct *mm, struct task_struct *tsk)
2108c2ecf20Sopenharmony_ci{
2118c2ecf20Sopenharmony_ci	/*
2128c2ecf20Sopenharmony_ci	 * We don't actually care about the ttbr0 mapping, so point it at the
2138c2ecf20Sopenharmony_ci	 * zero page.
2148c2ecf20Sopenharmony_ci	 */
2158c2ecf20Sopenharmony_ci	update_saved_ttbr0(tsk, &init_mm);
2168c2ecf20Sopenharmony_ci}
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_cistatic inline void __switch_mm(struct mm_struct *next)
2198c2ecf20Sopenharmony_ci{
2208c2ecf20Sopenharmony_ci	/*
2218c2ecf20Sopenharmony_ci	 * init_mm.pgd does not contain any user mappings and it is always
2228c2ecf20Sopenharmony_ci	 * active for kernel addresses in TTBR1. Just set the reserved TTBR0.
2238c2ecf20Sopenharmony_ci	 */
2248c2ecf20Sopenharmony_ci	if (next == &init_mm) {
2258c2ecf20Sopenharmony_ci		cpu_set_reserved_ttbr0();
2268c2ecf20Sopenharmony_ci		return;
2278c2ecf20Sopenharmony_ci	}
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	check_and_switch_context(next);
2308c2ecf20Sopenharmony_ci}
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_cistatic inline void
2338c2ecf20Sopenharmony_ciswitch_mm(struct mm_struct *prev, struct mm_struct *next,
2348c2ecf20Sopenharmony_ci	  struct task_struct *tsk)
2358c2ecf20Sopenharmony_ci{
2368c2ecf20Sopenharmony_ci	if (prev != next)
2378c2ecf20Sopenharmony_ci		__switch_mm(next);
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci	/*
2408c2ecf20Sopenharmony_ci	 * Update the saved TTBR0_EL1 of the scheduled-in task as the previous
2418c2ecf20Sopenharmony_ci	 * value may have not been initialised yet (activate_mm caller) or the
2428c2ecf20Sopenharmony_ci	 * ASID has changed since the last run (following the context switch
2438c2ecf20Sopenharmony_ci	 * of another thread of the same process).
2448c2ecf20Sopenharmony_ci	 */
2458c2ecf20Sopenharmony_ci	update_saved_ttbr0(tsk, next);
2468c2ecf20Sopenharmony_ci}
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci#define deactivate_mm(tsk,mm)	do { } while (0)
2498c2ecf20Sopenharmony_ci#define activate_mm(prev,next)	switch_mm(prev, next, current)
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_civoid verify_cpu_asid_bits(void);
2528c2ecf20Sopenharmony_civoid post_ttbr_update_workaround(void);
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_ciunsigned long arm64_mm_context_get(struct mm_struct *mm);
2558c2ecf20Sopenharmony_civoid arm64_mm_context_put(struct mm_struct *mm);
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci#endif /* !__ASSEMBLY__ */
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci#endif /* !__ASM_MMU_CONTEXT_H */
260