162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/* Page Fault Handling for ARC (TLB Miss / ProtV)
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (C) 2004, 2007-2010, 2011-2012 Synopsys, Inc. (www.synopsys.com)
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/signal.h>
862306a36Sopenharmony_ci#include <linux/interrupt.h>
962306a36Sopenharmony_ci#include <linux/sched/signal.h>
1062306a36Sopenharmony_ci#include <linux/errno.h>
1162306a36Sopenharmony_ci#include <linux/ptrace.h>
1262306a36Sopenharmony_ci#include <linux/uaccess.h>
1362306a36Sopenharmony_ci#include <linux/kdebug.h>
1462306a36Sopenharmony_ci#include <linux/perf_event.h>
1562306a36Sopenharmony_ci#include <linux/mm_types.h>
1662306a36Sopenharmony_ci#include <asm/entry.h>
1762306a36Sopenharmony_ci#include <asm/mmu.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci/*
2062306a36Sopenharmony_ci * kernel virtual address is required to implement vmalloc/pkmap/fixmap
2162306a36Sopenharmony_ci * Refer to asm/processor.h for System Memory Map
2262306a36Sopenharmony_ci *
2362306a36Sopenharmony_ci * It simply copies the PMD entry (pointer to 2nd level page table or hugepage)
2462306a36Sopenharmony_ci * from swapper pgdir to task pgdir. The 2nd level table/page is thus shared
2562306a36Sopenharmony_ci */
2662306a36Sopenharmony_cinoinline static int handle_kernel_vaddr_fault(unsigned long address)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	/*
2962306a36Sopenharmony_ci	 * Synchronize this task's top level page-table
3062306a36Sopenharmony_ci	 * with the 'reference' page table.
3162306a36Sopenharmony_ci	 */
3262306a36Sopenharmony_ci	pgd_t *pgd, *pgd_k;
3362306a36Sopenharmony_ci	p4d_t *p4d, *p4d_k;
3462306a36Sopenharmony_ci	pud_t *pud, *pud_k;
3562306a36Sopenharmony_ci	pmd_t *pmd, *pmd_k;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	pgd = pgd_offset(current->active_mm, address);
3862306a36Sopenharmony_ci	pgd_k = pgd_offset_k(address);
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	if (pgd_none (*pgd_k))
4162306a36Sopenharmony_ci		goto bad_area;
4262306a36Sopenharmony_ci	if (!pgd_present(*pgd))
4362306a36Sopenharmony_ci		set_pgd(pgd, *pgd_k);
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	p4d = p4d_offset(pgd, address);
4662306a36Sopenharmony_ci	p4d_k = p4d_offset(pgd_k, address);
4762306a36Sopenharmony_ci	if (p4d_none(*p4d_k))
4862306a36Sopenharmony_ci		goto bad_area;
4962306a36Sopenharmony_ci	if (!p4d_present(*p4d))
5062306a36Sopenharmony_ci		set_p4d(p4d, *p4d_k);
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	pud = pud_offset(p4d, address);
5362306a36Sopenharmony_ci	pud_k = pud_offset(p4d_k, address);
5462306a36Sopenharmony_ci	if (pud_none(*pud_k))
5562306a36Sopenharmony_ci		goto bad_area;
5662306a36Sopenharmony_ci	if (!pud_present(*pud))
5762306a36Sopenharmony_ci		set_pud(pud, *pud_k);
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	pmd = pmd_offset(pud, address);
6062306a36Sopenharmony_ci	pmd_k = pmd_offset(pud_k, address);
6162306a36Sopenharmony_ci	if (pmd_none(*pmd_k))
6262306a36Sopenharmony_ci		goto bad_area;
6362306a36Sopenharmony_ci	if (!pmd_present(*pmd))
6462306a36Sopenharmony_ci		set_pmd(pmd, *pmd_k);
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	/* XXX: create the TLB entry here */
6762306a36Sopenharmony_ci	return 0;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cibad_area:
7062306a36Sopenharmony_ci	return 1;
7162306a36Sopenharmony_ci}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_civoid do_page_fault(unsigned long address, struct pt_regs *regs)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	struct vm_area_struct *vma = NULL;
7662306a36Sopenharmony_ci	struct task_struct *tsk = current;
7762306a36Sopenharmony_ci	struct mm_struct *mm = tsk->mm;
7862306a36Sopenharmony_ci	int sig, si_code = SEGV_MAPERR;
7962306a36Sopenharmony_ci	unsigned int write = 0, exec = 0, mask;
8062306a36Sopenharmony_ci	vm_fault_t fault = VM_FAULT_SIGSEGV;	/* handle_mm_fault() output */
8162306a36Sopenharmony_ci	unsigned int flags;			/* handle_mm_fault() input */
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	/*
8462306a36Sopenharmony_ci	 * NOTE! We MUST NOT take any locks for this case. We may
8562306a36Sopenharmony_ci	 * be in an interrupt or a critical region, and should
8662306a36Sopenharmony_ci	 * only copy the information from the master page table,
8762306a36Sopenharmony_ci	 * nothing more.
8862306a36Sopenharmony_ci	 */
8962306a36Sopenharmony_ci	if (address >= VMALLOC_START && !user_mode(regs)) {
9062306a36Sopenharmony_ci		if (unlikely(handle_kernel_vaddr_fault(address)))
9162306a36Sopenharmony_ci			goto no_context;
9262306a36Sopenharmony_ci		else
9362306a36Sopenharmony_ci			return;
9462306a36Sopenharmony_ci	}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	/*
9762306a36Sopenharmony_ci	 * If we're in an interrupt or have no user
9862306a36Sopenharmony_ci	 * context, we must not take the fault..
9962306a36Sopenharmony_ci	 */
10062306a36Sopenharmony_ci	if (faulthandler_disabled() || !mm)
10162306a36Sopenharmony_ci		goto no_context;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	if (regs->ecr.cause & ECR_C_PROTV_STORE)	/* ST/EX */
10462306a36Sopenharmony_ci		write = 1;
10562306a36Sopenharmony_ci	else if ((regs->ecr.vec == ECR_V_PROTV) &&
10662306a36Sopenharmony_ci	         (regs->ecr.cause == ECR_C_PROTV_INST_FETCH))
10762306a36Sopenharmony_ci		exec = 1;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	flags = FAULT_FLAG_DEFAULT;
11062306a36Sopenharmony_ci	if (user_mode(regs))
11162306a36Sopenharmony_ci		flags |= FAULT_FLAG_USER;
11262306a36Sopenharmony_ci	if (write)
11362306a36Sopenharmony_ci		flags |= FAULT_FLAG_WRITE;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, address);
11662306a36Sopenharmony_ciretry:
11762306a36Sopenharmony_ci	vma = lock_mm_and_find_vma(mm, address, regs);
11862306a36Sopenharmony_ci	if (!vma)
11962306a36Sopenharmony_ci		goto bad_area_nosemaphore;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	/*
12262306a36Sopenharmony_ci	 * vm_area is good, now check permissions for this memory access
12362306a36Sopenharmony_ci	 */
12462306a36Sopenharmony_ci	mask = VM_READ;
12562306a36Sopenharmony_ci	if (write)
12662306a36Sopenharmony_ci		mask = VM_WRITE;
12762306a36Sopenharmony_ci	if (exec)
12862306a36Sopenharmony_ci		mask = VM_EXEC;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	if (!(vma->vm_flags & mask)) {
13162306a36Sopenharmony_ci		si_code = SEGV_ACCERR;
13262306a36Sopenharmony_ci		goto bad_area;
13362306a36Sopenharmony_ci	}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	fault = handle_mm_fault(vma, address, flags, regs);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	/* Quick path to respond to signals */
13862306a36Sopenharmony_ci	if (fault_signal_pending(fault, regs)) {
13962306a36Sopenharmony_ci		if (!user_mode(regs))
14062306a36Sopenharmony_ci			goto no_context;
14162306a36Sopenharmony_ci		return;
14262306a36Sopenharmony_ci	}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	/* The fault is fully completed (including releasing mmap lock) */
14562306a36Sopenharmony_ci	if (fault & VM_FAULT_COMPLETED)
14662306a36Sopenharmony_ci		return;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	/*
14962306a36Sopenharmony_ci	 * Fault retry nuances, mmap_lock already relinquished by core mm
15062306a36Sopenharmony_ci	 */
15162306a36Sopenharmony_ci	if (unlikely(fault & VM_FAULT_RETRY)) {
15262306a36Sopenharmony_ci		flags |= FAULT_FLAG_TRIED;
15362306a36Sopenharmony_ci		goto retry;
15462306a36Sopenharmony_ci	}
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_cibad_area:
15762306a36Sopenharmony_ci	mmap_read_unlock(mm);
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_cibad_area_nosemaphore:
16062306a36Sopenharmony_ci	/*
16162306a36Sopenharmony_ci	 * Major/minor page fault accounting
16262306a36Sopenharmony_ci	 * (in case of retry we only land here once)
16362306a36Sopenharmony_ci	 */
16462306a36Sopenharmony_ci	if (likely(!(fault & VM_FAULT_ERROR)))
16562306a36Sopenharmony_ci		/* Normal return path: fault Handled Gracefully */
16662306a36Sopenharmony_ci		return;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	if (!user_mode(regs))
16962306a36Sopenharmony_ci		goto no_context;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	if (fault & VM_FAULT_OOM) {
17262306a36Sopenharmony_ci		pagefault_out_of_memory();
17362306a36Sopenharmony_ci		return;
17462306a36Sopenharmony_ci	}
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	if (fault & VM_FAULT_SIGBUS) {
17762306a36Sopenharmony_ci		sig = SIGBUS;
17862306a36Sopenharmony_ci		si_code = BUS_ADRERR;
17962306a36Sopenharmony_ci	}
18062306a36Sopenharmony_ci	else {
18162306a36Sopenharmony_ci		sig = SIGSEGV;
18262306a36Sopenharmony_ci	}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	tsk->thread.fault_address = address;
18562306a36Sopenharmony_ci	force_sig_fault(sig, si_code, (void __user *)address);
18662306a36Sopenharmony_ci	return;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_cino_context:
18962306a36Sopenharmony_ci	if (fixup_exception(regs))
19062306a36Sopenharmony_ci		return;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	die("Oops", regs, address);
19362306a36Sopenharmony_ci}
194