xref: /kernel/linux/linux-6.6/arch/xtensa/mm/tlb.c (revision 62306a36)
162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * arch/xtensa/mm/tlb.c
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Logic that manipulates the Xtensa MMU.  Derived from MIPS.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
762306a36Sopenharmony_ci * License.  See the file "COPYING" in the main directory of this archive
862306a36Sopenharmony_ci * for more details.
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * Copyright (C) 2001 - 2003 Tensilica Inc.
1162306a36Sopenharmony_ci *
1262306a36Sopenharmony_ci * Joe Taylor
1362306a36Sopenharmony_ci * Chris Zankel	<chris@zankel.net>
1462306a36Sopenharmony_ci * Marc Gauthier
1562306a36Sopenharmony_ci */
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <linux/mm.h>
1862306a36Sopenharmony_ci#include <asm/processor.h>
1962306a36Sopenharmony_ci#include <asm/mmu_context.h>
2062306a36Sopenharmony_ci#include <asm/tlb.h>
2162306a36Sopenharmony_ci#include <asm/tlbflush.h>
2262306a36Sopenharmony_ci#include <asm/cacheflush.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistatic inline void __flush_itlb_all (void)
2662306a36Sopenharmony_ci{
2762306a36Sopenharmony_ci	int w, i;
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	for (w = 0; w < ITLB_ARF_WAYS; w++) {
3062306a36Sopenharmony_ci		for (i = 0; i < (1 << XCHAL_ITLB_ARF_ENTRIES_LOG2); i++) {
3162306a36Sopenharmony_ci			int e = w + (i << PAGE_SHIFT);
3262306a36Sopenharmony_ci			invalidate_itlb_entry_no_isync(e);
3362306a36Sopenharmony_ci		}
3462306a36Sopenharmony_ci	}
3562306a36Sopenharmony_ci	asm volatile ("isync\n");
3662306a36Sopenharmony_ci}
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistatic inline void __flush_dtlb_all (void)
3962306a36Sopenharmony_ci{
4062306a36Sopenharmony_ci	int w, i;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	for (w = 0; w < DTLB_ARF_WAYS; w++) {
4362306a36Sopenharmony_ci		for (i = 0; i < (1 << XCHAL_DTLB_ARF_ENTRIES_LOG2); i++) {
4462306a36Sopenharmony_ci			int e = w + (i << PAGE_SHIFT);
4562306a36Sopenharmony_ci			invalidate_dtlb_entry_no_isync(e);
4662306a36Sopenharmony_ci		}
4762306a36Sopenharmony_ci	}
4862306a36Sopenharmony_ci	asm volatile ("isync\n");
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_civoid local_flush_tlb_all(void)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci	__flush_itlb_all();
5562306a36Sopenharmony_ci	__flush_dtlb_all();
5662306a36Sopenharmony_ci}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci/* If mm is current, we simply assign the current task a new ASID, thus,
5962306a36Sopenharmony_ci * invalidating all previous tlb entries. If mm is someone else's user mapping,
6062306a36Sopenharmony_ci * wie invalidate the context, thus, when that user mapping is swapped in,
6162306a36Sopenharmony_ci * a new context will be assigned to it.
6262306a36Sopenharmony_ci */
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_civoid local_flush_tlb_mm(struct mm_struct *mm)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	int cpu = smp_processor_id();
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	if (mm == current->active_mm) {
6962306a36Sopenharmony_ci		unsigned long flags;
7062306a36Sopenharmony_ci		local_irq_save(flags);
7162306a36Sopenharmony_ci		mm->context.asid[cpu] = NO_CONTEXT;
7262306a36Sopenharmony_ci		activate_context(mm, cpu);
7362306a36Sopenharmony_ci		local_irq_restore(flags);
7462306a36Sopenharmony_ci	} else {
7562306a36Sopenharmony_ci		mm->context.asid[cpu] = NO_CONTEXT;
7662306a36Sopenharmony_ci		mm->context.cpu = -1;
7762306a36Sopenharmony_ci	}
7862306a36Sopenharmony_ci}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci#define _ITLB_ENTRIES (ITLB_ARF_WAYS << XCHAL_ITLB_ARF_ENTRIES_LOG2)
8262306a36Sopenharmony_ci#define _DTLB_ENTRIES (DTLB_ARF_WAYS << XCHAL_DTLB_ARF_ENTRIES_LOG2)
8362306a36Sopenharmony_ci#if _ITLB_ENTRIES > _DTLB_ENTRIES
8462306a36Sopenharmony_ci# define _TLB_ENTRIES _ITLB_ENTRIES
8562306a36Sopenharmony_ci#else
8662306a36Sopenharmony_ci# define _TLB_ENTRIES _DTLB_ENTRIES
8762306a36Sopenharmony_ci#endif
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_civoid local_flush_tlb_range(struct vm_area_struct *vma,
9062306a36Sopenharmony_ci		unsigned long start, unsigned long end)
9162306a36Sopenharmony_ci{
9262306a36Sopenharmony_ci	int cpu = smp_processor_id();
9362306a36Sopenharmony_ci	struct mm_struct *mm = vma->vm_mm;
9462306a36Sopenharmony_ci	unsigned long flags;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	if (mm->context.asid[cpu] == NO_CONTEXT)
9762306a36Sopenharmony_ci		return;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	pr_debug("[tlbrange<%02lx,%08lx,%08lx>]\n",
10062306a36Sopenharmony_ci		 (unsigned long)mm->context.asid[cpu], start, end);
10162306a36Sopenharmony_ci	local_irq_save(flags);
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	if (end-start + (PAGE_SIZE-1) <= _TLB_ENTRIES << PAGE_SHIFT) {
10462306a36Sopenharmony_ci		int oldpid = get_rasid_register();
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci		set_rasid_register(ASID_INSERT(mm->context.asid[cpu]));
10762306a36Sopenharmony_ci		start &= PAGE_MASK;
10862306a36Sopenharmony_ci		if (vma->vm_flags & VM_EXEC)
10962306a36Sopenharmony_ci			while(start < end) {
11062306a36Sopenharmony_ci				invalidate_itlb_mapping(start);
11162306a36Sopenharmony_ci				invalidate_dtlb_mapping(start);
11262306a36Sopenharmony_ci				start += PAGE_SIZE;
11362306a36Sopenharmony_ci			}
11462306a36Sopenharmony_ci		else
11562306a36Sopenharmony_ci			while(start < end) {
11662306a36Sopenharmony_ci				invalidate_dtlb_mapping(start);
11762306a36Sopenharmony_ci				start += PAGE_SIZE;
11862306a36Sopenharmony_ci			}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci		set_rasid_register(oldpid);
12162306a36Sopenharmony_ci	} else {
12262306a36Sopenharmony_ci		local_flush_tlb_mm(mm);
12362306a36Sopenharmony_ci	}
12462306a36Sopenharmony_ci	local_irq_restore(flags);
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_civoid local_flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
12862306a36Sopenharmony_ci{
12962306a36Sopenharmony_ci	int cpu = smp_processor_id();
13062306a36Sopenharmony_ci	struct mm_struct* mm = vma->vm_mm;
13162306a36Sopenharmony_ci	unsigned long flags;
13262306a36Sopenharmony_ci	int oldpid;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	if (mm->context.asid[cpu] == NO_CONTEXT)
13562306a36Sopenharmony_ci		return;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	local_irq_save(flags);
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	oldpid = get_rasid_register();
14062306a36Sopenharmony_ci	set_rasid_register(ASID_INSERT(mm->context.asid[cpu]));
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	if (vma->vm_flags & VM_EXEC)
14362306a36Sopenharmony_ci		invalidate_itlb_mapping(page);
14462306a36Sopenharmony_ci	invalidate_dtlb_mapping(page);
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	set_rasid_register(oldpid);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	local_irq_restore(flags);
14962306a36Sopenharmony_ci}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_civoid local_flush_tlb_kernel_range(unsigned long start, unsigned long end)
15262306a36Sopenharmony_ci{
15362306a36Sopenharmony_ci	if (end > start && start >= TASK_SIZE && end <= PAGE_OFFSET &&
15462306a36Sopenharmony_ci	    end - start < _TLB_ENTRIES << PAGE_SHIFT) {
15562306a36Sopenharmony_ci		start &= PAGE_MASK;
15662306a36Sopenharmony_ci		while (start < end) {
15762306a36Sopenharmony_ci			invalidate_itlb_mapping(start);
15862306a36Sopenharmony_ci			invalidate_dtlb_mapping(start);
15962306a36Sopenharmony_ci			start += PAGE_SIZE;
16062306a36Sopenharmony_ci		}
16162306a36Sopenharmony_ci	} else {
16262306a36Sopenharmony_ci		local_flush_tlb_all();
16362306a36Sopenharmony_ci	}
16462306a36Sopenharmony_ci}
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_civoid update_mmu_tlb(struct vm_area_struct *vma,
16762306a36Sopenharmony_ci		    unsigned long address, pte_t *ptep)
16862306a36Sopenharmony_ci{
16962306a36Sopenharmony_ci	local_flush_tlb_page(vma, address);
17062306a36Sopenharmony_ci}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci#ifdef CONFIG_DEBUG_TLB_SANITY
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_cistatic unsigned get_pte_for_vaddr(unsigned vaddr)
17562306a36Sopenharmony_ci{
17662306a36Sopenharmony_ci	struct task_struct *task = get_current();
17762306a36Sopenharmony_ci	struct mm_struct *mm = task->mm;
17862306a36Sopenharmony_ci	pgd_t *pgd;
17962306a36Sopenharmony_ci	p4d_t *p4d;
18062306a36Sopenharmony_ci	pud_t *pud;
18162306a36Sopenharmony_ci	pmd_t *pmd;
18262306a36Sopenharmony_ci	pte_t *pte;
18362306a36Sopenharmony_ci	unsigned int pteval;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	if (!mm)
18662306a36Sopenharmony_ci		mm = task->active_mm;
18762306a36Sopenharmony_ci	pgd = pgd_offset(mm, vaddr);
18862306a36Sopenharmony_ci	if (pgd_none_or_clear_bad(pgd))
18962306a36Sopenharmony_ci		return 0;
19062306a36Sopenharmony_ci	p4d = p4d_offset(pgd, vaddr);
19162306a36Sopenharmony_ci	if (p4d_none_or_clear_bad(p4d))
19262306a36Sopenharmony_ci		return 0;
19362306a36Sopenharmony_ci	pud = pud_offset(p4d, vaddr);
19462306a36Sopenharmony_ci	if (pud_none_or_clear_bad(pud))
19562306a36Sopenharmony_ci		return 0;
19662306a36Sopenharmony_ci	pmd = pmd_offset(pud, vaddr);
19762306a36Sopenharmony_ci	if (pmd_none_or_clear_bad(pmd))
19862306a36Sopenharmony_ci		return 0;
19962306a36Sopenharmony_ci	pte = pte_offset_map(pmd, vaddr);
20062306a36Sopenharmony_ci	if (!pte)
20162306a36Sopenharmony_ci		return 0;
20262306a36Sopenharmony_ci	pteval = pte_val(*pte);
20362306a36Sopenharmony_ci	pte_unmap(pte);
20462306a36Sopenharmony_ci	return pteval;
20562306a36Sopenharmony_ci}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_cienum {
20862306a36Sopenharmony_ci	TLB_SUSPICIOUS	= 1,
20962306a36Sopenharmony_ci	TLB_INSANE	= 2,
21062306a36Sopenharmony_ci};
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_cistatic void tlb_insane(void)
21362306a36Sopenharmony_ci{
21462306a36Sopenharmony_ci	BUG_ON(1);
21562306a36Sopenharmony_ci}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_cistatic void tlb_suspicious(void)
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci	WARN_ON(1);
22062306a36Sopenharmony_ci}
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci/*
22362306a36Sopenharmony_ci * Check that TLB entries with kernel ASID (1) have kernel VMA (>= TASK_SIZE),
22462306a36Sopenharmony_ci * and TLB entries with user ASID (>=4) have VMA < TASK_SIZE.
22562306a36Sopenharmony_ci *
22662306a36Sopenharmony_ci * Check that valid TLB entries either have the same PA as the PTE, or PTE is
22762306a36Sopenharmony_ci * marked as non-present. Non-present PTE and the page with non-zero refcount
22862306a36Sopenharmony_ci * and zero mapcount is normal for batched TLB flush operation. Zero refcount
22962306a36Sopenharmony_ci * means that the page was freed prematurely. Non-zero mapcount is unusual,
23062306a36Sopenharmony_ci * but does not necessary means an error, thus marked as suspicious.
23162306a36Sopenharmony_ci */
23262306a36Sopenharmony_cistatic int check_tlb_entry(unsigned w, unsigned e, bool dtlb)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	unsigned tlbidx = w | (e << PAGE_SHIFT);
23562306a36Sopenharmony_ci	unsigned r0 = dtlb ?
23662306a36Sopenharmony_ci		read_dtlb_virtual(tlbidx) : read_itlb_virtual(tlbidx);
23762306a36Sopenharmony_ci	unsigned r1 = dtlb ?
23862306a36Sopenharmony_ci		read_dtlb_translation(tlbidx) : read_itlb_translation(tlbidx);
23962306a36Sopenharmony_ci	unsigned vpn = (r0 & PAGE_MASK) | (e << PAGE_SHIFT);
24062306a36Sopenharmony_ci	unsigned pte = get_pte_for_vaddr(vpn);
24162306a36Sopenharmony_ci	unsigned mm_asid = (get_rasid_register() >> 8) & ASID_MASK;
24262306a36Sopenharmony_ci	unsigned tlb_asid = r0 & ASID_MASK;
24362306a36Sopenharmony_ci	bool kernel = tlb_asid == 1;
24462306a36Sopenharmony_ci	int rc = 0;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	if (tlb_asid > 0 && ((vpn < TASK_SIZE) == kernel)) {
24762306a36Sopenharmony_ci		pr_err("%cTLB: way: %u, entry: %u, VPN %08x in %s PTE\n",
24862306a36Sopenharmony_ci				dtlb ? 'D' : 'I', w, e, vpn,
24962306a36Sopenharmony_ci				kernel ? "kernel" : "user");
25062306a36Sopenharmony_ci		rc |= TLB_INSANE;
25162306a36Sopenharmony_ci	}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	if (tlb_asid == mm_asid) {
25462306a36Sopenharmony_ci		if ((pte ^ r1) & PAGE_MASK) {
25562306a36Sopenharmony_ci			pr_err("%cTLB: way: %u, entry: %u, mapping: %08x->%08x, PTE: %08x\n",
25662306a36Sopenharmony_ci					dtlb ? 'D' : 'I', w, e, r0, r1, pte);
25762306a36Sopenharmony_ci			if (pte == 0 || !pte_present(__pte(pte))) {
25862306a36Sopenharmony_ci				struct page *p = pfn_to_page(r1 >> PAGE_SHIFT);
25962306a36Sopenharmony_ci				pr_err("page refcount: %d, mapcount: %d\n",
26062306a36Sopenharmony_ci						page_count(p),
26162306a36Sopenharmony_ci						page_mapcount(p));
26262306a36Sopenharmony_ci				if (!page_count(p))
26362306a36Sopenharmony_ci					rc |= TLB_INSANE;
26462306a36Sopenharmony_ci				else if (page_mapcount(p))
26562306a36Sopenharmony_ci					rc |= TLB_SUSPICIOUS;
26662306a36Sopenharmony_ci			} else {
26762306a36Sopenharmony_ci				rc |= TLB_INSANE;
26862306a36Sopenharmony_ci			}
26962306a36Sopenharmony_ci		}
27062306a36Sopenharmony_ci	}
27162306a36Sopenharmony_ci	return rc;
27262306a36Sopenharmony_ci}
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_civoid check_tlb_sanity(void)
27562306a36Sopenharmony_ci{
27662306a36Sopenharmony_ci	unsigned long flags;
27762306a36Sopenharmony_ci	unsigned w, e;
27862306a36Sopenharmony_ci	int bug = 0;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	local_irq_save(flags);
28162306a36Sopenharmony_ci	for (w = 0; w < DTLB_ARF_WAYS; ++w)
28262306a36Sopenharmony_ci		for (e = 0; e < (1 << XCHAL_DTLB_ARF_ENTRIES_LOG2); ++e)
28362306a36Sopenharmony_ci			bug |= check_tlb_entry(w, e, true);
28462306a36Sopenharmony_ci	for (w = 0; w < ITLB_ARF_WAYS; ++w)
28562306a36Sopenharmony_ci		for (e = 0; e < (1 << XCHAL_ITLB_ARF_ENTRIES_LOG2); ++e)
28662306a36Sopenharmony_ci			bug |= check_tlb_entry(w, e, false);
28762306a36Sopenharmony_ci	if (bug & TLB_INSANE)
28862306a36Sopenharmony_ci		tlb_insane();
28962306a36Sopenharmony_ci	if (bug & TLB_SUSPICIOUS)
29062306a36Sopenharmony_ci		tlb_suspicious();
29162306a36Sopenharmony_ci	local_irq_restore(flags);
29262306a36Sopenharmony_ci}
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci#endif /* CONFIG_DEBUG_TLB_SANITY */
295