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, offset: reg_err * sizeof(unsigned long), val: -EFAULT); |
55 | regs_set_gpr(regs, offset: reg_zero * sizeof(unsigned long), val: 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, offset: 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, offset: reg_data * sizeof(unsigned long), val: 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(add: 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(x: 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 | |