xref: /linux/arch/riscv/mm/extable.c (revision e91c37f1)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (C) 2009 Sunplus Core Technology Co., Ltd.
4  *  Lennox Wu <lennox.wu@sunplusct.com>
5  *  Chen Liqin <liqin.chen@sunplusct.com>
6  * Copyright (C) 2013 Regents of the University of California
7  */
8 
9 
10 #include <linux/bitfield.h>
11 #include <linux/extable.h>
12 #include <linux/module.h>
13 #include <linux/uaccess.h>
14 #include <asm/asm-extable.h>
15 #include <asm/ptrace.h>
16 
17 static inline unsigned long
18 get_ex_fixup(const struct exception_table_entry *ex)
19 {
20 	return ((unsigned long)&ex->fixup + ex->fixup);
21 }
22 
23 static bool ex_handler_fixup(const struct exception_table_entry *ex,
24 			     struct pt_regs *regs)
25 {
26 	regs->epc = get_ex_fixup(ex);
27 	return true;
28 }
29 
30 static inline unsigned long regs_get_gpr(struct pt_regs *regs, unsigned int offset)
31 {
32 	if (unlikely(!offset || offset > MAX_REG_OFFSET))
33 		return 0;
34 
35 	return *(unsigned long *)((unsigned long)regs + offset);
36 }
37 
38 static inline void regs_set_gpr(struct pt_regs *regs, unsigned int offset,
39 				unsigned long val)
40 {
41 	if (unlikely(offset > MAX_REG_OFFSET))
42 		return;
43 
44 	if (offset)
45 		*(unsigned long *)((unsigned long)regs + offset) = val;
46 }
47 
48 static bool ex_handler_uaccess_err_zero(const struct exception_table_entry *ex,
49 					struct pt_regs *regs)
50 {
51 	int reg_err = FIELD_GET(EX_DATA_REG_ERR, ex->data);
52 	int reg_zero = FIELD_GET(EX_DATA_REG_ZERO, ex->data);
53 
54 	regs_set_gpr(regs, reg_err * sizeof(unsigned long), -EFAULT);
55 	regs_set_gpr(regs, reg_zero * sizeof(unsigned long), 0);
56 
57 	regs->epc = get_ex_fixup(ex);
58 	return true;
59 }
60 
61 static bool
62 ex_handler_load_unaligned_zeropad(const struct exception_table_entry *ex,
63 				  struct pt_regs *regs)
64 {
65 	int reg_data = FIELD_GET(EX_DATA_REG_DATA, ex->data);
66 	int reg_addr = FIELD_GET(EX_DATA_REG_ADDR, ex->data);
67 	unsigned long data, addr, offset;
68 
69 	addr = regs_get_gpr(regs, reg_addr * sizeof(unsigned long));
70 
71 	offset = addr & 0x7UL;
72 	addr &= ~0x7UL;
73 
74 	data = *(unsigned long *)addr >> (offset * 8);
75 
76 	regs_set_gpr(regs, reg_data * sizeof(unsigned long), data);
77 
78 	regs->epc = get_ex_fixup(ex);
79 	return true;
80 }
81 
82 bool fixup_exception(struct pt_regs *regs)
83 {
84 	const struct exception_table_entry *ex;
85 
86 	ex = search_exception_tables(regs->epc);
87 	if (!ex)
88 		return false;
89 
90 	switch (ex->type) {
91 	case EX_TYPE_FIXUP:
92 		return ex_handler_fixup(ex, regs);
93 	case EX_TYPE_BPF:
94 		return ex_handler_bpf(ex, regs);
95 	case EX_TYPE_UACCESS_ERR_ZERO:
96 		return ex_handler_uaccess_err_zero(ex, regs);
97 	case EX_TYPE_LOAD_UNALIGNED_ZEROPAD:
98 		return ex_handler_load_unaligned_zeropad(ex, regs);
99 	}
100 
101 	BUG();
102 }
103