xref: /kernel/linux/linux-6.6/arch/xtensa/mm/fault.c (revision 62306a36)
162306a36Sopenharmony_ci// TODO VM_EXEC flag work-around, cache aliasing
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * arch/xtensa/mm/fault.c
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
662306a36Sopenharmony_ci * License.  See the file "COPYING" in the main directory of this archive
762306a36Sopenharmony_ci * for more details.
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * Copyright (C) 2001 - 2010 Tensilica Inc.
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * Chris Zankel <chris@zankel.net>
1262306a36Sopenharmony_ci * Joe Taylor	<joe@tensilica.com, joetylr@yahoo.com>
1362306a36Sopenharmony_ci */
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <linux/mm.h>
1662306a36Sopenharmony_ci#include <linux/extable.h>
1762306a36Sopenharmony_ci#include <linux/hardirq.h>
1862306a36Sopenharmony_ci#include <linux/perf_event.h>
1962306a36Sopenharmony_ci#include <linux/uaccess.h>
2062306a36Sopenharmony_ci#include <asm/mmu_context.h>
2162306a36Sopenharmony_ci#include <asm/cacheflush.h>
2262306a36Sopenharmony_ci#include <asm/hardirq.h>
2362306a36Sopenharmony_ci#include <asm/traps.h>
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_civoid bad_page_fault(struct pt_regs*, unsigned long, int);
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic void vmalloc_fault(struct pt_regs *regs, unsigned int address)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci#ifdef CONFIG_MMU
3062306a36Sopenharmony_ci	/* Synchronize this task's top level page-table
3162306a36Sopenharmony_ci	 * with the 'reference' page table.
3262306a36Sopenharmony_ci	 */
3362306a36Sopenharmony_ci	struct mm_struct *act_mm = current->active_mm;
3462306a36Sopenharmony_ci	int index = pgd_index(address);
3562306a36Sopenharmony_ci	pgd_t *pgd, *pgd_k;
3662306a36Sopenharmony_ci	p4d_t *p4d, *p4d_k;
3762306a36Sopenharmony_ci	pud_t *pud, *pud_k;
3862306a36Sopenharmony_ci	pmd_t *pmd, *pmd_k;
3962306a36Sopenharmony_ci	pte_t *pte_k;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	if (act_mm == NULL)
4262306a36Sopenharmony_ci		goto bad_page_fault;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	pgd = act_mm->pgd + index;
4562306a36Sopenharmony_ci	pgd_k = init_mm.pgd + index;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	if (!pgd_present(*pgd_k))
4862306a36Sopenharmony_ci		goto bad_page_fault;
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	pgd_val(*pgd) = pgd_val(*pgd_k);
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	p4d = p4d_offset(pgd, address);
5362306a36Sopenharmony_ci	p4d_k = p4d_offset(pgd_k, address);
5462306a36Sopenharmony_ci	if (!p4d_present(*p4d) || !p4d_present(*p4d_k))
5562306a36Sopenharmony_ci		goto bad_page_fault;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	pud = pud_offset(p4d, address);
5862306a36Sopenharmony_ci	pud_k = pud_offset(p4d_k, address);
5962306a36Sopenharmony_ci	if (!pud_present(*pud) || !pud_present(*pud_k))
6062306a36Sopenharmony_ci		goto bad_page_fault;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	pmd = pmd_offset(pud, address);
6362306a36Sopenharmony_ci	pmd_k = pmd_offset(pud_k, address);
6462306a36Sopenharmony_ci	if (!pmd_present(*pmd) || !pmd_present(*pmd_k))
6562306a36Sopenharmony_ci		goto bad_page_fault;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	pmd_val(*pmd) = pmd_val(*pmd_k);
6862306a36Sopenharmony_ci	pte_k = pte_offset_kernel(pmd_k, address);
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	if (!pte_present(*pte_k))
7162306a36Sopenharmony_ci		goto bad_page_fault;
7262306a36Sopenharmony_ci	return;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cibad_page_fault:
7562306a36Sopenharmony_ci	bad_page_fault(regs, address, SIGKILL);
7662306a36Sopenharmony_ci#else
7762306a36Sopenharmony_ci	WARN_ONCE(1, "%s in noMMU configuration\n", __func__);
7862306a36Sopenharmony_ci#endif
7962306a36Sopenharmony_ci}
8062306a36Sopenharmony_ci/*
8162306a36Sopenharmony_ci * This routine handles page faults.  It determines the address,
8262306a36Sopenharmony_ci * and the problem, and then passes it off to one of the appropriate
8362306a36Sopenharmony_ci * routines.
8462306a36Sopenharmony_ci *
8562306a36Sopenharmony_ci * Note: does not handle Miss and MultiHit.
8662306a36Sopenharmony_ci */
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_civoid do_page_fault(struct pt_regs *regs)
8962306a36Sopenharmony_ci{
9062306a36Sopenharmony_ci	struct vm_area_struct * vma;
9162306a36Sopenharmony_ci	struct mm_struct *mm = current->mm;
9262306a36Sopenharmony_ci	unsigned int exccause = regs->exccause;
9362306a36Sopenharmony_ci	unsigned int address = regs->excvaddr;
9462306a36Sopenharmony_ci	int code;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	int is_write, is_exec;
9762306a36Sopenharmony_ci	vm_fault_t fault;
9862306a36Sopenharmony_ci	unsigned int flags = FAULT_FLAG_DEFAULT;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	code = SEGV_MAPERR;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	/* We fault-in kernel-space virtual memory on-demand. The
10362306a36Sopenharmony_ci	 * 'reference' page table is init_mm.pgd.
10462306a36Sopenharmony_ci	 */
10562306a36Sopenharmony_ci	if (address >= TASK_SIZE && !user_mode(regs)) {
10662306a36Sopenharmony_ci		vmalloc_fault(regs, address);
10762306a36Sopenharmony_ci		return;
10862306a36Sopenharmony_ci	}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	/* If we're in an interrupt or have no user
11162306a36Sopenharmony_ci	 * context, we must not take the fault..
11262306a36Sopenharmony_ci	 */
11362306a36Sopenharmony_ci	if (faulthandler_disabled() || !mm) {
11462306a36Sopenharmony_ci		bad_page_fault(regs, address, SIGSEGV);
11562306a36Sopenharmony_ci		return;
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	is_write = (exccause == EXCCAUSE_STORE_CACHE_ATTRIBUTE) ? 1 : 0;
11962306a36Sopenharmony_ci	is_exec =  (exccause == EXCCAUSE_ITLB_PRIVILEGE ||
12062306a36Sopenharmony_ci		    exccause == EXCCAUSE_ITLB_MISS ||
12162306a36Sopenharmony_ci		    exccause == EXCCAUSE_FETCH_CACHE_ATTRIBUTE) ? 1 : 0;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	pr_debug("[%s:%d:%08x:%d:%08lx:%s%s]\n",
12462306a36Sopenharmony_ci		 current->comm, current->pid,
12562306a36Sopenharmony_ci		 address, exccause, regs->pc,
12662306a36Sopenharmony_ci		 is_write ? "w" : "", is_exec ? "x" : "");
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	if (user_mode(regs))
12962306a36Sopenharmony_ci		flags |= FAULT_FLAG_USER;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, address);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ciretry:
13462306a36Sopenharmony_ci	vma = lock_mm_and_find_vma(mm, address, regs);
13562306a36Sopenharmony_ci	if (!vma)
13662306a36Sopenharmony_ci		goto bad_area_nosemaphore;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	/* Ok, we have a good vm_area for this memory access, so
13962306a36Sopenharmony_ci	 * we can handle it..
14062306a36Sopenharmony_ci	 */
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	code = SEGV_ACCERR;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	if (is_write) {
14562306a36Sopenharmony_ci		if (!(vma->vm_flags & VM_WRITE))
14662306a36Sopenharmony_ci			goto bad_area;
14762306a36Sopenharmony_ci		flags |= FAULT_FLAG_WRITE;
14862306a36Sopenharmony_ci	} else if (is_exec) {
14962306a36Sopenharmony_ci		if (!(vma->vm_flags & VM_EXEC))
15062306a36Sopenharmony_ci			goto bad_area;
15162306a36Sopenharmony_ci	} else	/* Allow read even from write-only pages. */
15262306a36Sopenharmony_ci		if (!(vma->vm_flags & (VM_READ | VM_WRITE)))
15362306a36Sopenharmony_ci			goto bad_area;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	/* If for any reason at all we couldn't handle the fault,
15662306a36Sopenharmony_ci	 * make sure we exit gracefully rather than endlessly redo
15762306a36Sopenharmony_ci	 * the fault.
15862306a36Sopenharmony_ci	 */
15962306a36Sopenharmony_ci	fault = handle_mm_fault(vma, address, flags, regs);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	if (fault_signal_pending(fault, regs)) {
16262306a36Sopenharmony_ci		if (!user_mode(regs))
16362306a36Sopenharmony_ci			bad_page_fault(regs, address, SIGKILL);
16462306a36Sopenharmony_ci		return;
16562306a36Sopenharmony_ci	}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	/* The fault is fully completed (including releasing mmap lock) */
16862306a36Sopenharmony_ci	if (fault & VM_FAULT_COMPLETED)
16962306a36Sopenharmony_ci		return;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	if (unlikely(fault & VM_FAULT_ERROR)) {
17262306a36Sopenharmony_ci		if (fault & VM_FAULT_OOM)
17362306a36Sopenharmony_ci			goto out_of_memory;
17462306a36Sopenharmony_ci		else if (fault & VM_FAULT_SIGSEGV)
17562306a36Sopenharmony_ci			goto bad_area;
17662306a36Sopenharmony_ci		else if (fault & VM_FAULT_SIGBUS)
17762306a36Sopenharmony_ci			goto do_sigbus;
17862306a36Sopenharmony_ci		BUG();
17962306a36Sopenharmony_ci	}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	if (fault & VM_FAULT_RETRY) {
18262306a36Sopenharmony_ci		flags |= FAULT_FLAG_TRIED;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci		/* No need to mmap_read_unlock(mm) as we would
18562306a36Sopenharmony_ci		 * have already released it in __lock_page_or_retry
18662306a36Sopenharmony_ci		 * in mm/filemap.c.
18762306a36Sopenharmony_ci		 */
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci		goto retry;
19062306a36Sopenharmony_ci	}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	mmap_read_unlock(mm);
19362306a36Sopenharmony_ci	return;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	/* Something tried to access memory that isn't in our memory map..
19662306a36Sopenharmony_ci	 * Fix it, but check if it's kernel or user first..
19762306a36Sopenharmony_ci	 */
19862306a36Sopenharmony_cibad_area:
19962306a36Sopenharmony_ci	mmap_read_unlock(mm);
20062306a36Sopenharmony_cibad_area_nosemaphore:
20162306a36Sopenharmony_ci	if (user_mode(regs)) {
20262306a36Sopenharmony_ci		force_sig_fault(SIGSEGV, code, (void *) address);
20362306a36Sopenharmony_ci		return;
20462306a36Sopenharmony_ci	}
20562306a36Sopenharmony_ci	bad_page_fault(regs, address, SIGSEGV);
20662306a36Sopenharmony_ci	return;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	/* We ran out of memory, or some other thing happened to us that made
21062306a36Sopenharmony_ci	 * us unable to handle the page fault gracefully.
21162306a36Sopenharmony_ci	 */
21262306a36Sopenharmony_ciout_of_memory:
21362306a36Sopenharmony_ci	mmap_read_unlock(mm);
21462306a36Sopenharmony_ci	if (!user_mode(regs))
21562306a36Sopenharmony_ci		bad_page_fault(regs, address, SIGKILL);
21662306a36Sopenharmony_ci	else
21762306a36Sopenharmony_ci		pagefault_out_of_memory();
21862306a36Sopenharmony_ci	return;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_cido_sigbus:
22162306a36Sopenharmony_ci	mmap_read_unlock(mm);
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	/* Send a sigbus, regardless of whether we were in kernel
22462306a36Sopenharmony_ci	 * or user mode.
22562306a36Sopenharmony_ci	 */
22662306a36Sopenharmony_ci	force_sig_fault(SIGBUS, BUS_ADRERR, (void *) address);
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	/* Kernel mode? Handle exceptions or die */
22962306a36Sopenharmony_ci	if (!user_mode(regs))
23062306a36Sopenharmony_ci		bad_page_fault(regs, address, SIGBUS);
23162306a36Sopenharmony_ci	return;
23262306a36Sopenharmony_ci}
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_civoid
23662306a36Sopenharmony_cibad_page_fault(struct pt_regs *regs, unsigned long address, int sig)
23762306a36Sopenharmony_ci{
23862306a36Sopenharmony_ci	extern void __noreturn die(const char*, struct pt_regs*, long);
23962306a36Sopenharmony_ci	const struct exception_table_entry *entry;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	/* Are we prepared to handle this kernel fault?  */
24262306a36Sopenharmony_ci	if ((entry = search_exception_tables(regs->pc)) != NULL) {
24362306a36Sopenharmony_ci		pr_debug("%s: Exception at pc=%#010lx (%lx)\n",
24462306a36Sopenharmony_ci			 current->comm, regs->pc, entry->fixup);
24562306a36Sopenharmony_ci		regs->pc = entry->fixup;
24662306a36Sopenharmony_ci		return;
24762306a36Sopenharmony_ci	}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	/* Oops. The kernel tried to access some bad page. We'll have to
25062306a36Sopenharmony_ci	 * terminate things with extreme prejudice.
25162306a36Sopenharmony_ci	 */
25262306a36Sopenharmony_ci	pr_alert("Unable to handle kernel paging request at virtual "
25362306a36Sopenharmony_ci		 "address %08lx\n pc = %08lx, ra = %08lx\n",
25462306a36Sopenharmony_ci		 address, regs->pc, regs->areg[0]);
25562306a36Sopenharmony_ci	die("Oops", regs, sig);
25662306a36Sopenharmony_ci}
257