fault.c 5.0 KB
Newer Older
G
Guo Ren 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2018 Hangzhou C-SKY Microsystems co.,ltd.

#include <linux/signal.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/ptrace.h>
#include <linux/mman.h>
#include <linux/mm.h>
#include <linux/smp.h>
#include <linux/version.h>
#include <linux/vt_kern.h>
#include <linux/extable.h>
#include <linux/uaccess.h>
20
#include <linux/perf_event.h>
G
Guo Ren 已提交
21
#include <linux/kprobes.h>
G
Guo Ren 已提交
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56

#include <asm/hardirq.h>
#include <asm/mmu_context.h>
#include <asm/traps.h>
#include <asm/page.h>

int fixup_exception(struct pt_regs *regs)
{
	const struct exception_table_entry *fixup;

	fixup = search_exception_tables(instruction_pointer(regs));
	if (fixup) {
		regs->pc = fixup->nextinsn;

		return 1;
	}

	return 0;
}

/*
 * This routine handles page faults. It determines the address,
 * and the problem, and then passes it off to one of the appropriate
 * routines.
 */
asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long write,
			      unsigned long mmu_meh)
{
	struct vm_area_struct *vma = NULL;
	struct task_struct *tsk = current;
	struct mm_struct *mm = tsk->mm;
	int si_code;
	int fault;
	unsigned long address = mmu_meh & PAGE_MASK;

G
Guo Ren 已提交
57 58 59
	if (kprobe_page_fault(regs, tsk->thread.trap_no))
		return;

G
Guo Ren 已提交
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
	si_code = SEGV_MAPERR;

#ifndef CONFIG_CPU_HAS_TLBI
	/*
	 * We fault-in kernel-space virtual memory on-demand. The
	 * 'reference' page table is init_mm.pgd.
	 *
	 * NOTE! We MUST NOT take any locks for this case. We may
	 * be in an interrupt or a critical region, and should
	 * only copy the information from the master page table,
	 * nothing more.
	 */
	if (unlikely(address >= VMALLOC_START) &&
	    unlikely(address <= VMALLOC_END)) {
		/*
		 * Synchronize this task's top level page-table
		 * with the 'reference' page table.
		 *
		 * Do _not_ use "tsk" here. We might be inside
		 * an interrupt in the middle of a task switch..
		 */
81
		int offset = pgd_index(address);
G
Guo Ren 已提交
82 83 84 85 86 87 88
		pgd_t *pgd, *pgd_k;
		pud_t *pud, *pud_k;
		pmd_t *pmd, *pmd_k;
		pte_t *pte_k;

		unsigned long pgd_base;

G
Guo Ren 已提交
89
		pgd_base = (unsigned long)__va(get_pgd());
G
Guo Ren 已提交
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
		pgd = (pgd_t *)pgd_base + offset;
		pgd_k = init_mm.pgd + offset;

		if (!pgd_present(*pgd_k))
			goto no_context;
		set_pgd(pgd, *pgd_k);

		pud = (pud_t *)pgd;
		pud_k = (pud_t *)pgd_k;
		if (!pud_present(*pud_k))
			goto no_context;

		pmd = pmd_offset(pud, address);
		pmd_k = pmd_offset(pud_k, address);
		if (!pmd_present(*pmd_k))
			goto no_context;
		set_pmd(pmd, *pmd_k);

		pte_k = pte_offset_kernel(pmd_k, address);
		if (!pte_present(*pte_k))
			goto no_context;
		return;
	}
#endif
114 115

	perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, address);
G
Guo Ren 已提交
116 117 118 119 120 121 122
	/*
	 * If we're in an interrupt or have no user
	 * context, we must not take the fault..
	 */
	if (in_atomic() || !mm)
		goto bad_area_nosemaphore;

123
	mmap_read_lock(mm);
G
Guo Ren 已提交
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
	vma = find_vma(mm, address);
	if (!vma)
		goto bad_area;
	if (vma->vm_start <= address)
		goto good_area;
	if (!(vma->vm_flags & VM_GROWSDOWN))
		goto bad_area;
	if (expand_stack(vma, address))
		goto bad_area;
	/*
	 * Ok, we have a good vm_area for this memory access, so
	 * we can handle it..
	 */
good_area:
	si_code = SEGV_ACCERR;

	if (write) {
		if (!(vma->vm_flags & VM_WRITE))
			goto bad_area;
	} else {
144
		if (unlikely(!vma_is_accessible(vma)))
G
Guo Ren 已提交
145 146 147 148 149 150 151 152
			goto bad_area;
	}

	/*
	 * If for any reason at all we couldn't handle the fault,
	 * make sure we exit gracefully rather than endlessly redo
	 * the fault.
	 */
153
	fault = handle_mm_fault(vma, address, write ? FAULT_FLAG_WRITE : 0,
154
				regs);
G
Guo Ren 已提交
155 156 157 158 159 160 161 162 163
	if (unlikely(fault & VM_FAULT_ERROR)) {
		if (fault & VM_FAULT_OOM)
			goto out_of_memory;
		else if (fault & VM_FAULT_SIGBUS)
			goto do_sigbus;
		else if (fault & VM_FAULT_SIGSEGV)
			goto bad_area;
		BUG();
	}
164
	mmap_read_unlock(mm);
G
Guo Ren 已提交
165 166 167 168 169 170 171
	return;

	/*
	 * Something tried to access memory that isn't in our memory map..
	 * Fix it, but check if it's kernel or user first..
	 */
bad_area:
172
	mmap_read_unlock(mm);
G
Guo Ren 已提交
173 174 175 176

bad_area_nosemaphore:
	/* User mode accesses just cause a SIGSEGV */
	if (user_mode(regs)) {
177
		tsk->thread.trap_no = trap_no(regs);
178
		force_sig_fault(SIGSEGV, si_code, (void __user *)address);
G
Guo Ren 已提交
179 180 181 182
		return;
	}

no_context:
183
	tsk->thread.trap_no = trap_no(regs);
184

G
Guo Ren 已提交
185 186 187 188 189 190 191 192 193
	/* Are we prepared to handle this kernel fault? */
	if (fixup_exception(regs))
		return;

	/*
	 * Oops. The kernel tried to access some bad page. We'll have to
	 * terminate things with extreme prejudice.
	 */
	bust_spinlocks(1);
G
Guo Ren 已提交
194 195
	pr_alert("Unable to handle kernel paging request at virtual "
		 "address 0x%08lx, pc: 0x%08lx\n", address, regs->pc);
196
	die(regs, "Oops");
G
Guo Ren 已提交
197 198

out_of_memory:
199
	tsk->thread.trap_no = trap_no(regs);
200

G
Guo Ren 已提交
201 202 203 204 205 206 207 208
	/*
	 * We ran out of memory, call the OOM killer, and return the userspace
	 * (which will retry the fault, or kill us if we got oom-killed).
	 */
	pagefault_out_of_memory();
	return;

do_sigbus:
209
	tsk->thread.trap_no = trap_no(regs);
210

211
	mmap_read_unlock(mm);
G
Guo Ren 已提交
212 213 214 215 216

	/* Kernel mode? Handle exceptions or die */
	if (!user_mode(regs))
		goto no_context;

217
	force_sig_fault(SIGBUS, BUS_ADRERR, (void __user *)address);
G
Guo Ren 已提交
218
}