162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Memory fault handling for Hexagon
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci/*
962306a36Sopenharmony_ci * Page fault handling for the Hexagon Virtual Machine.
1062306a36Sopenharmony_ci * Can also be called by a native port emulating the HVM
1162306a36Sopenharmony_ci * execptions.
1262306a36Sopenharmony_ci */
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <asm/traps.h>
1562306a36Sopenharmony_ci#include <linux/uaccess.h>
1662306a36Sopenharmony_ci#include <linux/mm.h>
1762306a36Sopenharmony_ci#include <linux/sched/signal.h>
1862306a36Sopenharmony_ci#include <linux/signal.h>
1962306a36Sopenharmony_ci#include <linux/extable.h>
2062306a36Sopenharmony_ci#include <linux/hardirq.h>
2162306a36Sopenharmony_ci#include <linux/perf_event.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci/*
2462306a36Sopenharmony_ci * Decode of hardware exception sends us to one of several
2562306a36Sopenharmony_ci * entry points.  At each, we generate canonical arguments
2662306a36Sopenharmony_ci * for handling by the abstract memory management code.
2762306a36Sopenharmony_ci */
2862306a36Sopenharmony_ci#define FLT_IFETCH     -1
2962306a36Sopenharmony_ci#define FLT_LOAD        0
3062306a36Sopenharmony_ci#define FLT_STORE       1
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci/*
3462306a36Sopenharmony_ci * Canonical page fault handler
3562306a36Sopenharmony_ci */
3662306a36Sopenharmony_civoid do_page_fault(unsigned long address, long cause, struct pt_regs *regs)
3762306a36Sopenharmony_ci{
3862306a36Sopenharmony_ci	struct vm_area_struct *vma;
3962306a36Sopenharmony_ci	struct mm_struct *mm = current->mm;
4062306a36Sopenharmony_ci	int si_signo;
4162306a36Sopenharmony_ci	int si_code = SEGV_MAPERR;
4262306a36Sopenharmony_ci	vm_fault_t fault;
4362306a36Sopenharmony_ci	const struct exception_table_entry *fixup;
4462306a36Sopenharmony_ci	unsigned int flags = FAULT_FLAG_DEFAULT;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	/*
4762306a36Sopenharmony_ci	 * If we're in an interrupt or have no user context,
4862306a36Sopenharmony_ci	 * then must not take the fault.
4962306a36Sopenharmony_ci	 */
5062306a36Sopenharmony_ci	if (unlikely(in_interrupt() || !mm))
5162306a36Sopenharmony_ci		goto no_context;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	local_irq_enable();
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	if (user_mode(regs))
5662306a36Sopenharmony_ci		flags |= FAULT_FLAG_USER;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, address);
5962306a36Sopenharmony_ciretry:
6062306a36Sopenharmony_ci	vma = lock_mm_and_find_vma(mm, address, regs);
6162306a36Sopenharmony_ci	if (unlikely(!vma))
6262306a36Sopenharmony_ci		goto bad_area_nosemaphore;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	/* Address space is OK.  Now check access rights. */
6562306a36Sopenharmony_ci	si_code = SEGV_ACCERR;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	switch (cause) {
6862306a36Sopenharmony_ci	case FLT_IFETCH:
6962306a36Sopenharmony_ci		if (!(vma->vm_flags & VM_EXEC))
7062306a36Sopenharmony_ci			goto bad_area;
7162306a36Sopenharmony_ci		break;
7262306a36Sopenharmony_ci	case FLT_LOAD:
7362306a36Sopenharmony_ci		if (!(vma->vm_flags & VM_READ))
7462306a36Sopenharmony_ci			goto bad_area;
7562306a36Sopenharmony_ci		break;
7662306a36Sopenharmony_ci	case FLT_STORE:
7762306a36Sopenharmony_ci		if (!(vma->vm_flags & VM_WRITE))
7862306a36Sopenharmony_ci			goto bad_area;
7962306a36Sopenharmony_ci		flags |= FAULT_FLAG_WRITE;
8062306a36Sopenharmony_ci		break;
8162306a36Sopenharmony_ci	}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	fault = handle_mm_fault(vma, address, flags, regs);
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	if (fault_signal_pending(fault, regs)) {
8662306a36Sopenharmony_ci		if (!user_mode(regs))
8762306a36Sopenharmony_ci			goto no_context;
8862306a36Sopenharmony_ci		return;
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	/* The fault is fully completed (including releasing mmap lock) */
9262306a36Sopenharmony_ci	if (fault & VM_FAULT_COMPLETED)
9362306a36Sopenharmony_ci		return;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	/* The most common case -- we are done. */
9662306a36Sopenharmony_ci	if (likely(!(fault & VM_FAULT_ERROR))) {
9762306a36Sopenharmony_ci		if (fault & VM_FAULT_RETRY) {
9862306a36Sopenharmony_ci			flags |= FAULT_FLAG_TRIED;
9962306a36Sopenharmony_ci			goto retry;
10062306a36Sopenharmony_ci		}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci		mmap_read_unlock(mm);
10362306a36Sopenharmony_ci		return;
10462306a36Sopenharmony_ci	}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	mmap_read_unlock(mm);
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	/* Handle copyin/out exception cases */
10962306a36Sopenharmony_ci	if (!user_mode(regs))
11062306a36Sopenharmony_ci		goto no_context;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	if (fault & VM_FAULT_OOM) {
11362306a36Sopenharmony_ci		pagefault_out_of_memory();
11462306a36Sopenharmony_ci		return;
11562306a36Sopenharmony_ci	}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	/* User-mode address is in the memory map, but we are
11862306a36Sopenharmony_ci	 * unable to fix up the page fault.
11962306a36Sopenharmony_ci	 */
12062306a36Sopenharmony_ci	if (fault & VM_FAULT_SIGBUS) {
12162306a36Sopenharmony_ci		si_signo = SIGBUS;
12262306a36Sopenharmony_ci		si_code = BUS_ADRERR;
12362306a36Sopenharmony_ci	}
12462306a36Sopenharmony_ci	/* Address is not in the memory map */
12562306a36Sopenharmony_ci	else {
12662306a36Sopenharmony_ci		si_signo = SIGSEGV;
12762306a36Sopenharmony_ci		si_code  = SEGV_ACCERR;
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci	force_sig_fault(si_signo, si_code, (void __user *)address);
13062306a36Sopenharmony_ci	return;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_cibad_area:
13362306a36Sopenharmony_ci	mmap_read_unlock(mm);
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cibad_area_nosemaphore:
13662306a36Sopenharmony_ci	if (user_mode(regs)) {
13762306a36Sopenharmony_ci		force_sig_fault(SIGSEGV, si_code, (void __user *)address);
13862306a36Sopenharmony_ci		return;
13962306a36Sopenharmony_ci	}
14062306a36Sopenharmony_ci	/* Kernel-mode fault falls through */
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_cino_context:
14362306a36Sopenharmony_ci	fixup = search_exception_tables(pt_elr(regs));
14462306a36Sopenharmony_ci	if (fixup) {
14562306a36Sopenharmony_ci		pt_set_elr(regs, fixup->fixup);
14662306a36Sopenharmony_ci		return;
14762306a36Sopenharmony_ci	}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	/* Things are looking very, very bad now */
15062306a36Sopenharmony_ci	bust_spinlocks(1);
15162306a36Sopenharmony_ci	printk(KERN_EMERG "Unable to handle kernel paging request at "
15262306a36Sopenharmony_ci		"virtual address 0x%08lx, regs %p\n", address, regs);
15362306a36Sopenharmony_ci	die("Bad Kernel VA", regs, SIGKILL);
15462306a36Sopenharmony_ci}
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_civoid read_protection_fault(struct pt_regs *regs)
15862306a36Sopenharmony_ci{
15962306a36Sopenharmony_ci	unsigned long badvadr = pt_badva(regs);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	do_page_fault(badvadr, FLT_LOAD, regs);
16262306a36Sopenharmony_ci}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_civoid write_protection_fault(struct pt_regs *regs)
16562306a36Sopenharmony_ci{
16662306a36Sopenharmony_ci	unsigned long badvadr = pt_badva(regs);
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	do_page_fault(badvadr, FLT_STORE, regs);
16962306a36Sopenharmony_ci}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_civoid execute_protection_fault(struct pt_regs *regs)
17262306a36Sopenharmony_ci{
17362306a36Sopenharmony_ci	unsigned long badvadr = pt_badva(regs);
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	do_page_fault(badvadr, FLT_IFETCH, regs);
17662306a36Sopenharmony_ci}
177