more changes on original files
[linux-2.4.git] / arch / sparc / kernel / unaligned.c
1 /* $Id: unaligned.c,v 1.22.2.1 2001/12/21 00:52:47 davem Exp $
2  * unaligned.c: Unaligned load/store trap handling with special
3  *              cases for the kernel to do them more quickly.
4  *
5  * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu)
6  * Copyright (C) 1996 Jakub Jelinek (jj@sunsite.mff.cuni.cz)
7  */
8
9
10 #include <linux/kernel.h>
11 #include <linux/sched.h>
12 #include <linux/mm.h>
13 #include <asm/ptrace.h>
14 #include <asm/processor.h>
15 #include <asm/system.h>
16 #include <asm/uaccess.h>
17 #include <linux/smp.h>
18 #include <linux/smp_lock.h>
19
20 /* #define DEBUG_MNA */
21
22 enum direction {
23         load,    /* ld, ldd, ldh, ldsh */
24         store,   /* st, std, sth, stsh */
25         both,    /* Swap, ldstub, etc. */
26         fpload,
27         fpstore,
28         invalid,
29 };
30
31 #ifdef DEBUG_MNA
32 static char *dirstrings[] = {
33   "load", "store", "both", "fpload", "fpstore", "invalid"
34 };
35 #endif
36
37 static inline enum direction decode_direction(unsigned int insn)
38 {
39         unsigned long tmp = (insn >> 21) & 1;
40
41         if(!tmp)
42                 return load;
43         else {
44                 if(((insn>>19)&0x3f) == 15)
45                         return both;
46                 else
47                         return store;
48         }
49 }
50
51 /* 8 = double-word, 4 = word, 2 = half-word */
52 static inline int decode_access_size(unsigned int insn)
53 {
54         insn = (insn >> 19) & 3;
55
56         if(!insn)
57                 return 4;
58         else if(insn == 3)
59                 return 8;
60         else if(insn == 2)
61                 return 2;
62         else {
63                 printk("Impossible unaligned trap. insn=%08x\n", insn);
64                 die_if_kernel("Byte sized unaligned access?!?!", current->thread.kregs);
65                 return 4; /* just to keep gcc happy. */
66         }
67 }
68
69 /* 0x400000 = signed, 0 = unsigned */
70 static inline int decode_signedness(unsigned int insn)
71 {
72         return (insn & 0x400000);
73 }
74
75 static inline void maybe_flush_windows(unsigned int rs1, unsigned int rs2,
76                                        unsigned int rd)
77 {
78         if(rs2 >= 16 || rs1 >= 16 || rd >= 16) {
79                 /* Wheee... */
80                 __asm__ __volatile__("save %sp, -0x40, %sp\n\t"
81                                      "save %sp, -0x40, %sp\n\t"
82                                      "save %sp, -0x40, %sp\n\t"
83                                      "save %sp, -0x40, %sp\n\t"
84                                      "save %sp, -0x40, %sp\n\t"
85                                      "save %sp, -0x40, %sp\n\t"
86                                      "save %sp, -0x40, %sp\n\t"
87                                      "restore; restore; restore; restore;\n\t"
88                                      "restore; restore; restore;\n\t");
89         }
90 }
91
92 static inline int sign_extend_imm13(int imm)
93 {
94         return imm << 19 >> 19;
95 }
96
97 static inline unsigned long fetch_reg(unsigned int reg, struct pt_regs *regs)
98 {
99         struct reg_window *win;
100
101         if(reg < 16)
102                 return (!reg ? 0 : regs->u_regs[reg]);
103
104         /* Ho hum, the slightly complicated case. */
105         win = (struct reg_window *) regs->u_regs[UREG_FP];
106         return win->locals[reg - 16]; /* yes, I know what this does... */
107 }
108
109 static inline unsigned long safe_fetch_reg(unsigned int reg, struct pt_regs *regs)
110 {
111         struct reg_window *win;
112         unsigned long ret;
113
114         if(reg < 16)
115                 return (!reg ? 0 : regs->u_regs[reg]);
116
117         /* Ho hum, the slightly complicated case. */
118         win = (struct reg_window *) regs->u_regs[UREG_FP];
119
120         if ((unsigned long)win & 3)
121                 return -1;
122
123         if (get_user(ret, &win->locals[reg - 16]))
124                 return -1;
125
126         return ret;
127 }
128
129 static inline unsigned long *fetch_reg_addr(unsigned int reg, struct pt_regs *regs)
130 {
131         struct reg_window *win;
132
133         if(reg < 16)
134                 return &regs->u_regs[reg];
135         win = (struct reg_window *) regs->u_regs[UREG_FP];
136         return &win->locals[reg - 16];
137 }
138
139 static unsigned long compute_effective_address(struct pt_regs *regs,
140                                                unsigned int insn)
141 {
142         unsigned int rs1 = (insn >> 14) & 0x1f;
143         unsigned int rs2 = insn & 0x1f;
144         unsigned int rd = (insn >> 25) & 0x1f;
145
146         if(insn & 0x2000) {
147                 maybe_flush_windows(rs1, 0, rd);
148                 return (fetch_reg(rs1, regs) + sign_extend_imm13(insn));
149         } else {
150                 maybe_flush_windows(rs1, rs2, rd);
151                 return (fetch_reg(rs1, regs) + fetch_reg(rs2, regs));
152         }
153 }
154
155 unsigned long safe_compute_effective_address(struct pt_regs *regs,
156                                              unsigned int insn)
157 {
158         unsigned int rs1 = (insn >> 14) & 0x1f;
159         unsigned int rs2 = insn & 0x1f;
160         unsigned int rd = (insn >> 25) & 0x1f;
161
162         if(insn & 0x2000) {
163                 maybe_flush_windows(rs1, 0, rd);
164                 return (safe_fetch_reg(rs1, regs) + sign_extend_imm13(insn));
165         } else {
166                 maybe_flush_windows(rs1, rs2, rd);
167                 return (safe_fetch_reg(rs1, regs) + safe_fetch_reg(rs2, regs));
168         }
169 }
170
171 /* This is just to make gcc think panic does return... */
172 static void unaligned_panic(char *str)
173 {
174         panic(str);
175 }
176
177 #define do_integer_load(dest_reg, size, saddr, is_signed, errh) ({              \
178 __asm__ __volatile__ (                                                          \
179         "cmp    %1, 8\n\t"                                                      \
180         "be     9f\n\t"                                                         \
181         " cmp   %1, 4\n\t"                                                      \
182         "be     6f\n"                                                           \
183 "4:\t"  " ldub  [%2], %%l1\n"                                                   \
184 "5:\t"  "ldub   [%2 + 1], %%l2\n\t"                                             \
185         "sll    %%l1, 8, %%l1\n\t"                                              \
186         "tst    %3\n\t"                                                         \
187         "be     3f\n\t"                                                         \
188         " add   %%l1, %%l2, %%l1\n\t"                                           \
189         "sll    %%l1, 16, %%l1\n\t"                                             \
190         "sra    %%l1, 16, %%l1\n"                                               \
191 "3:\t"  "b      0f\n\t"                                                         \
192         " st    %%l1, [%0]\n"                                                   \
193 "6:\t"  "ldub   [%2 + 1], %%l2\n\t"                                             \
194         "sll    %%l1, 24, %%l1\n"                                               \
195 "7:\t"  "ldub   [%2 + 2], %%g7\n\t"                                             \
196         "sll    %%l2, 16, %%l2\n"                                               \
197 "8:\t"  "ldub   [%2 + 3], %%g1\n\t"                                             \
198         "sll    %%g7, 8, %%g7\n\t"                                              \
199         "or     %%l1, %%l2, %%l1\n\t"                                           \
200         "or     %%g7, %%g1, %%g7\n\t"                                           \
201         "or     %%l1, %%g7, %%l1\n\t"                                           \
202         "b      0f\n\t"                                                         \
203         " st    %%l1, [%0]\n"                                                   \
204 "9:\t"  "ldub   [%2], %%l1\n"                                                   \
205 "10:\t" "ldub   [%2 + 1], %%l2\n\t"                                             \
206         "sll    %%l1, 24, %%l1\n"                                               \
207 "11:\t" "ldub   [%2 + 2], %%g7\n\t"                                             \
208         "sll    %%l2, 16, %%l2\n"                                               \
209 "12:\t" "ldub   [%2 + 3], %%g1\n\t"                                             \
210         "sll    %%g7, 8, %%g7\n\t"                                              \
211         "or     %%l1, %%l2, %%l1\n\t"                                           \
212         "or     %%g7, %%g1, %%g7\n\t"                                           \
213         "or     %%l1, %%g7, %%g7\n"                                             \
214 "13:\t" "ldub   [%2 + 4], %%l1\n\t"                                             \
215         "st     %%g7, [%0]\n"                                                   \
216 "14:\t" "ldub   [%2 + 5], %%l2\n\t"                                             \
217         "sll    %%l1, 24, %%l1\n"                                               \
218 "15:\t" "ldub   [%2 + 6], %%g7\n\t"                                             \
219         "sll    %%l2, 16, %%l2\n"                                               \
220 "16:\t" "ldub   [%2 + 7], %%g1\n\t"                                             \
221         "sll    %%g7, 8, %%g7\n\t"                                              \
222         "or     %%l1, %%l2, %%l1\n\t"                                           \
223         "or     %%g7, %%g1, %%g7\n\t"                                           \
224         "or     %%l1, %%g7, %%g7\n\t"                                           \
225         "st     %%g7, [%0 + 4]\n"                                               \
226 "0:\n\n\t"                                                                      \
227         ".section __ex_table,#alloc\n\t"                                        \
228         ".word  4b, " #errh "\n\t"                                              \
229         ".word  5b, " #errh "\n\t"                                              \
230         ".word  6b, " #errh "\n\t"                                              \
231         ".word  7b, " #errh "\n\t"                                              \
232         ".word  8b, " #errh "\n\t"                                              \
233         ".word  9b, " #errh "\n\t"                                              \
234         ".word  10b, " #errh "\n\t"                                             \
235         ".word  11b, " #errh "\n\t"                                             \
236         ".word  12b, " #errh "\n\t"                                             \
237         ".word  13b, " #errh "\n\t"                                             \
238         ".word  14b, " #errh "\n\t"                                             \
239         ".word  15b, " #errh "\n\t"                                             \
240         ".word  16b, " #errh "\n\n\t"                                           \
241         ".previous\n\t"                                                         \
242         : : "r" (dest_reg), "r" (size), "r" (saddr), "r" (is_signed)            \
243         : "l1", "l2", "g7", "g1", "cc");                                        \
244 })
245         
246 #define store_common(dst_addr, size, src_val, errh) ({                          \
247 __asm__ __volatile__ (                                                          \
248         "ld     [%2], %%l1\n"                                                   \
249         "cmp    %1, 2\n\t"                                                      \
250         "be     2f\n\t"                                                         \
251         " cmp   %1, 4\n\t"                                                      \
252         "be     1f\n\t"                                                         \
253         " srl   %%l1, 24, %%l2\n\t"                                             \
254         "srl    %%l1, 16, %%g7\n"                                               \
255 "4:\t"  "stb    %%l2, [%0]\n\t"                                                 \
256         "srl    %%l1, 8, %%l2\n"                                                \
257 "5:\t"  "stb    %%g7, [%0 + 1]\n\t"                                             \
258         "ld     [%2 + 4], %%g7\n"                                               \
259 "6:\t"  "stb    %%l2, [%0 + 2]\n\t"                                             \
260         "srl    %%g7, 24, %%l2\n"                                               \
261 "7:\t"  "stb    %%l1, [%0 + 3]\n\t"                                             \
262         "srl    %%g7, 16, %%l1\n"                                               \
263 "8:\t"  "stb    %%l2, [%0 + 4]\n\t"                                             \
264         "srl    %%g7, 8, %%l2\n"                                                \
265 "9:\t"  "stb    %%l1, [%0 + 5]\n"                                               \
266 "10:\t" "stb    %%l2, [%0 + 6]\n\t"                                             \
267         "b      0f\n"                                                           \
268 "11:\t" " stb   %%g7, [%0 + 7]\n"                                               \
269 "1:\t"  "srl    %%l1, 16, %%g7\n"                                               \
270 "12:\t" "stb    %%l2, [%0]\n\t"                                                 \
271         "srl    %%l1, 8, %%l2\n"                                                \
272 "13:\t" "stb    %%g7, [%0 + 1]\n"                                               \
273 "14:\t" "stb    %%l2, [%0 + 2]\n\t"                                             \
274         "b      0f\n"                                                           \
275 "15:\t" " stb   %%l1, [%0 + 3]\n"                                               \
276 "2:\t"  "srl    %%l1, 8, %%l2\n"                                                \
277 "16:\t" "stb    %%l2, [%0]\n"                                                   \
278 "17:\t" "stb    %%l1, [%0 + 1]\n"                                               \
279 "0:\n\n\t"                                                                      \
280         ".section __ex_table,#alloc\n\t"                                        \
281         ".word  4b, " #errh "\n\t"                                              \
282         ".word  5b, " #errh "\n\t"                                              \
283         ".word  6b, " #errh "\n\t"                                              \
284         ".word  7b, " #errh "\n\t"                                              \
285         ".word  8b, " #errh "\n\t"                                              \
286         ".word  9b, " #errh "\n\t"                                              \
287         ".word  10b, " #errh "\n\t"                                             \
288         ".word  11b, " #errh "\n\t"                                             \
289         ".word  12b, " #errh "\n\t"                                             \
290         ".word  13b, " #errh "\n\t"                                             \
291         ".word  14b, " #errh "\n\t"                                             \
292         ".word  15b, " #errh "\n\t"                                             \
293         ".word  16b, " #errh "\n\t"                                             \
294         ".word  17b, " #errh "\n\n\t"                                           \
295         ".previous\n\t"                                                         \
296         : : "r" (dst_addr), "r" (size), "r" (src_val)                           \
297         : "l1", "l2", "g7", "g1", "cc");                                        \
298 })
299
300 #define do_integer_store(reg_num, size, dst_addr, regs, errh) ({                \
301         unsigned long *src_val;                                                 \
302         static unsigned long zero[2] = { 0, };                                  \
303                                                                                 \
304         if (reg_num) src_val = fetch_reg_addr(reg_num, regs);                   \
305         else {                                                                  \
306                 src_val = &zero[0];                                             \
307                 if (size == 8)                                                  \
308                         zero[1] = fetch_reg(1, regs);                           \
309         }                                                                       \
310         store_common(dst_addr, size, src_val, errh);                            \
311 })
312
313 /* XXX Need to capture/release other cpu's for SMP around this. */
314 #define do_atomic(srcdest_reg, mem, errh) ({                                    \
315         unsigned long flags, tmp;                                               \
316                                                                                 \
317         save_and_cli(flags);                                                    \
318         tmp = *srcdest_reg;                                                     \
319         do_integer_load(srcdest_reg, 4, mem, 0, errh);                          \
320         store_common(mem, 4, &tmp, errh);                                       \
321         restore_flags(flags);                                                   \
322 })
323
324 static inline void advance(struct pt_regs *regs)
325 {
326         regs->pc   = regs->npc;
327         regs->npc += 4;
328 }
329
330 static inline int floating_point_load_or_store_p(unsigned int insn)
331 {
332         return (insn >> 24) & 1;
333 }
334
335 static inline int ok_for_kernel(unsigned int insn)
336 {
337         return !floating_point_load_or_store_p(insn);
338 }
339
340 void kernel_mna_trap_fault(struct pt_regs *regs, unsigned int insn) __asm__ ("kernel_mna_trap_fault");
341
342 void kernel_mna_trap_fault(struct pt_regs *regs, unsigned int insn)
343 {
344         unsigned long g2 = regs->u_regs [UREG_G2];
345         unsigned long fixup = search_exception_table (regs->pc, &g2);
346
347         if (!fixup) {
348                 unsigned long address = compute_effective_address(regs, insn);
349                 if(address < PAGE_SIZE) {
350                         printk(KERN_ALERT "Unable to handle kernel NULL pointer dereference in mna handler");
351                 } else
352                         printk(KERN_ALERT "Unable to handle kernel paging request in mna handler");
353                 printk(KERN_ALERT " at virtual address %08lx\n",address);
354                 printk(KERN_ALERT "current->{mm,active_mm}->context = %08lx\n",
355                         (current->mm ? current->mm->context :
356                         current->active_mm->context));
357                 printk(KERN_ALERT "current->{mm,active_mm}->pgd = %08lx\n",
358                         (current->mm ? (unsigned long) current->mm->pgd :
359                         (unsigned long) current->active_mm->pgd));
360                 die_if_kernel("Oops", regs);
361                 /* Not reached */
362         }
363         regs->pc = fixup;
364         regs->npc = regs->pc + 4;
365         regs->u_regs [UREG_G2] = g2;
366 }
367
368 asmlinkage void kernel_unaligned_trap(struct pt_regs *regs, unsigned int insn)
369 {
370         enum direction dir = decode_direction(insn);
371         int size = decode_access_size(insn);
372
373         if(!ok_for_kernel(insn) || dir == both) {
374                 printk("Unsupported unaligned load/store trap for kernel at <%08lx>.\n",
375                        regs->pc);
376                 unaligned_panic("Wheee. Kernel does fpu/atomic unaligned load/store.");
377
378                 __asm__ __volatile__ ("\n"
379 "kernel_unaligned_trap_fault:\n\t"
380                 "mov    %0, %%o0\n\t"
381                 "call   kernel_mna_trap_fault\n\t"
382                 " mov   %1, %%o1\n\t"
383                 :
384                 : "r" (regs), "r" (insn)
385                 : "o0", "o1", "o2", "o3", "o4", "o5", "o7",
386                   "g1", "g2", "g3", "g4", "g5", "g7", "cc");
387         } else {
388                 unsigned long addr = compute_effective_address(regs, insn);
389
390 #ifdef DEBUG_MNA
391                 printk("KMNA: pc=%08lx [dir=%s addr=%08lx size=%d] retpc[%08lx]\n",
392                        regs->pc, dirstrings[dir], addr, size, regs->u_regs[UREG_RETPC]);
393 #endif
394                 switch(dir) {
395                 case load:
396                         do_integer_load(fetch_reg_addr(((insn>>25)&0x1f), regs),
397                                         size, (unsigned long *) addr,
398                                         decode_signedness(insn),
399                                         kernel_unaligned_trap_fault);
400                         break;
401
402                 case store:
403                         do_integer_store(((insn>>25)&0x1f), size,
404                                          (unsigned long *) addr, regs,
405                                          kernel_unaligned_trap_fault);
406                         break;
407 #if 0 /* unsupported */
408                 case both:
409                         do_atomic(fetch_reg_addr(((insn>>25)&0x1f), regs),
410                                   (unsigned long *) addr,
411                                   kernel_unaligned_trap_fault);
412                         break;
413 #endif
414                 default:
415                         panic("Impossible kernel unaligned trap.");
416                         /* Not reached... */
417                 }
418                 advance(regs);
419         }
420 }
421
422 static inline int ok_for_user(struct pt_regs *regs, unsigned int insn,
423                               enum direction dir)
424 {
425         unsigned int reg;
426         int retval, check = (dir == load) ? VERIFY_READ : VERIFY_WRITE;
427         int size = ((insn >> 19) & 3) == 3 ? 8 : 4;
428
429         if((regs->pc | regs->npc) & 3)
430                 return 0;
431
432         /* Must verify_area() in all the necessary places. */
433 #define WINREG_ADDR(regnum) ((void *)(((unsigned long *)regs->u_regs[UREG_FP])+(regnum)))
434         retval = 0;
435         reg = (insn >> 25) & 0x1f;
436         if(reg >= 16) {
437                 retval = verify_area(check, WINREG_ADDR(reg - 16), size);
438                 if(retval)
439                         return retval;
440         }
441         reg = (insn >> 14) & 0x1f;
442         if(reg >= 16) {
443                 retval = verify_area(check, WINREG_ADDR(reg - 16), size);
444                 if(retval)
445                         return retval;
446         }
447         if(!(insn & 0x2000)) {
448                 reg = (insn & 0x1f);
449                 if(reg >= 16) {
450                         retval = verify_area(check, WINREG_ADDR(reg - 16), size);
451                         if(retval)
452                                 return retval;
453                 }
454         }
455         return retval;
456 #undef WINREG_ADDR
457 }
458
459 void user_mna_trap_fault(struct pt_regs *regs, unsigned int insn) __asm__ ("user_mna_trap_fault");
460
461 void user_mna_trap_fault(struct pt_regs *regs, unsigned int insn)
462 {
463         siginfo_t info;
464
465         info.si_signo = SIGBUS;
466         info.si_errno = 0;
467         info.si_code = BUS_ADRALN;
468         info.si_addr = (void *)safe_compute_effective_address(regs, insn);
469         info.si_trapno = 0;
470         send_sig_info(SIGBUS, &info, current);
471 }
472
473 asmlinkage void user_unaligned_trap(struct pt_regs *regs, unsigned int insn)
474 {
475         enum direction dir;
476
477         lock_kernel();
478         if(!(current->thread.flags & SPARC_FLAG_UNALIGNED) ||
479            (((insn >> 30) & 3) != 3))
480                 goto kill_user;
481         dir = decode_direction(insn);
482         if(!ok_for_user(regs, insn, dir)) {
483                 goto kill_user;
484         } else {
485                 int size = decode_access_size(insn);
486                 unsigned long addr;
487
488                 if(floating_point_load_or_store_p(insn)) {
489                         printk("User FPU load/store unaligned unsupported.\n");
490                         goto kill_user;
491                 }
492
493                 addr = compute_effective_address(regs, insn);
494                 switch(dir) {
495                 case load:
496                         do_integer_load(fetch_reg_addr(((insn>>25)&0x1f), regs),
497                                         size, (unsigned long *) addr,
498                                         decode_signedness(insn),
499                                         user_unaligned_trap_fault);
500                         break;
501
502                 case store:
503                         do_integer_store(((insn>>25)&0x1f), size,
504                                          (unsigned long *) addr, regs,
505                                          user_unaligned_trap_fault);
506                         break;
507
508                 case both:
509                         do_atomic(fetch_reg_addr(((insn>>25)&0x1f), regs),
510                                   (unsigned long *) addr,
511                                   user_unaligned_trap_fault);
512                         break;
513
514                 default:
515                         unaligned_panic("Impossible user unaligned trap.");
516
517                         __asm__ __volatile__ ("\n"
518 "user_unaligned_trap_fault:\n\t"
519                         "mov    %0, %%o0\n\t"
520                         "call   user_mna_trap_fault\n\t"
521                         " mov   %1, %%o1\n\t"
522                         :
523                         : "r" (regs), "r" (insn)
524                         : "o0", "o1", "o2", "o3", "o4", "o5", "o7",
525                           "g1", "g2", "g3", "g4", "g5", "g7", "cc");
526                         goto out;
527                 }
528                 advance(regs);
529                 goto out;
530         }
531
532 kill_user:
533         user_mna_trap_fault(regs, insn);
534 out:
535         unlock_kernel();
536 }