x86/mm: Expand the exception table logic to allow new handling options
[firefly-linux-kernel-4.4.55.git] / arch / x86 / mm / extable.c
1 #include <linux/module.h>
2 #include <linux/spinlock.h>
3 #include <linux/sort.h>
4 #include <asm/uaccess.h>
5
6 typedef bool (*ex_handler_t)(const struct exception_table_entry *,
7                             struct pt_regs *, int);
8
9 static inline unsigned long
10 ex_insn_addr(const struct exception_table_entry *x)
11 {
12         return (unsigned long)&x->insn + x->insn;
13 }
14 static inline unsigned long
15 ex_fixup_addr(const struct exception_table_entry *x)
16 {
17         return (unsigned long)&x->fixup + x->fixup;
18 }
19 static inline ex_handler_t
20 ex_fixup_handler(const struct exception_table_entry *x)
21 {
22         return (ex_handler_t)((unsigned long)&x->handler + x->handler);
23 }
24
25 bool ex_handler_default(const struct exception_table_entry *fixup,
26                        struct pt_regs *regs, int trapnr)
27 {
28         regs->ip = ex_fixup_addr(fixup);
29         return true;
30 }
31 EXPORT_SYMBOL(ex_handler_default);
32
33 bool ex_handler_fault(const struct exception_table_entry *fixup,
34                      struct pt_regs *regs, int trapnr)
35 {
36         regs->ip = ex_fixup_addr(fixup);
37         regs->ax = trapnr;
38         return true;
39 }
40 EXPORT_SYMBOL_GPL(ex_handler_fault);
41
42 bool ex_handler_ext(const struct exception_table_entry *fixup,
43                    struct pt_regs *regs, int trapnr)
44 {
45         /* Special hack for uaccess_err */
46         current_thread_info()->uaccess_err = 1;
47         regs->ip = ex_fixup_addr(fixup);
48         return true;
49 }
50 EXPORT_SYMBOL(ex_handler_ext);
51
52 bool ex_has_fault_handler(unsigned long ip)
53 {
54         const struct exception_table_entry *e;
55         ex_handler_t handler;
56
57         e = search_exception_tables(ip);
58         if (!e)
59                 return false;
60         handler = ex_fixup_handler(e);
61
62         return handler == ex_handler_fault;
63 }
64
65 int fixup_exception(struct pt_regs *regs, int trapnr)
66 {
67         const struct exception_table_entry *e;
68         ex_handler_t handler;
69
70 #ifdef CONFIG_PNPBIOS
71         if (unlikely(SEGMENT_IS_PNP_CODE(regs->cs))) {
72                 extern u32 pnp_bios_fault_eip, pnp_bios_fault_esp;
73                 extern u32 pnp_bios_is_utter_crap;
74                 pnp_bios_is_utter_crap = 1;
75                 printk(KERN_CRIT "PNPBIOS fault.. attempting recovery.\n");
76                 __asm__ volatile(
77                         "movl %0, %%esp\n\t"
78                         "jmp *%1\n\t"
79                         : : "g" (pnp_bios_fault_esp), "g" (pnp_bios_fault_eip));
80                 panic("do_trap: can't hit this");
81         }
82 #endif
83
84         e = search_exception_tables(regs->ip);
85         if (!e)
86                 return 0;
87
88         handler = ex_fixup_handler(e);
89         return handler(e, regs, trapnr);
90 }
91
92 /* Restricted version used during very early boot */
93 int __init early_fixup_exception(unsigned long *ip)
94 {
95         const struct exception_table_entry *e;
96         unsigned long new_ip;
97         ex_handler_t handler;
98
99         e = search_exception_tables(*ip);
100         if (!e)
101                 return 0;
102
103         new_ip  = ex_fixup_addr(e);
104         handler = ex_fixup_handler(e);
105
106         /* special handling not supported during early boot */
107         if (handler != ex_handler_default)
108                 return 0;
109
110         *ip = new_ip;
111         return 1;
112 }
113
114 /*
115  * Search one exception table for an entry corresponding to the
116  * given instruction address, and return the address of the entry,
117  * or NULL if none is found.
118  * We use a binary search, and thus we assume that the table is
119  * already sorted.
120  */
121 const struct exception_table_entry *
122 search_extable(const struct exception_table_entry *first,
123                const struct exception_table_entry *last,
124                unsigned long value)
125 {
126         while (first <= last) {
127                 const struct exception_table_entry *mid;
128                 unsigned long addr;
129
130                 mid = ((last - first) >> 1) + first;
131                 addr = ex_insn_addr(mid);
132                 if (addr < value)
133                         first = mid + 1;
134                 else if (addr > value)
135                         last = mid - 1;
136                 else
137                         return mid;
138         }
139         return NULL;
140 }
141
142 /*
143  * The exception table needs to be sorted so that the binary
144  * search that we use to find entries in it works properly.
145  * This is used both for the kernel exception table and for
146  * the exception tables of modules that get loaded.
147  *
148  */
149 static int cmp_ex(const void *a, const void *b)
150 {
151         const struct exception_table_entry *x = a, *y = b;
152
153         /*
154          * This value will always end up fittin in an int, because on
155          * both i386 and x86-64 the kernel symbol-reachable address
156          * space is < 2 GiB.
157          *
158          * This compare is only valid after normalization.
159          */
160         return x->insn - y->insn;
161 }
162
163 void sort_extable(struct exception_table_entry *start,
164                   struct exception_table_entry *finish)
165 {
166         struct exception_table_entry *p;
167         int i;
168
169         /* Convert all entries to being relative to the start of the section */
170         i = 0;
171         for (p = start; p < finish; p++) {
172                 p->insn += i;
173                 i += 4;
174                 p->fixup += i;
175                 i += 4;
176                 p->handler += i;
177                 i += 4;
178         }
179
180         sort(start, finish - start, sizeof(struct exception_table_entry),
181              cmp_ex, NULL);
182
183         /* Denormalize all entries */
184         i = 0;
185         for (p = start; p < finish; p++) {
186                 p->insn -= i;
187                 i += 4;
188                 p->fixup -= i;
189                 i += 4;
190                 p->handler -= i;
191                 i += 4;
192         }
193 }
194
195 #ifdef CONFIG_MODULES
196 /*
197  * If the exception table is sorted, any referring to the module init
198  * will be at the beginning or the end.
199  */
200 void trim_init_extable(struct module *m)
201 {
202         /*trim the beginning*/
203         while (m->num_exentries &&
204                within_module_init(ex_insn_addr(&m->extable[0]), m)) {
205                 m->extable++;
206                 m->num_exentries--;
207         }
208         /*trim the end*/
209         while (m->num_exentries &&
210                within_module_init(ex_insn_addr(&m->extable[m->num_exentries-1]), m))
211                 m->num_exentries--;
212 }
213 #endif /* CONFIG_MODULES */