patch-2_6_7-vs1_9_1_12
[linux-2.6.git] / arch / ppc64 / kernel / align.c
1 /* align.c - handle alignment exceptions for the Power PC.
2  *
3  * Copyright (c) 1996 Paul Mackerras <paulus@cs.anu.edu.au>
4  * Copyright (c) 1998-1999 TiVo, Inc.
5  *   PowerPC 403GCX modifications.
6  * Copyright (c) 1999 Grant Erickson <grant@lcse.umn.edu>
7  *   PowerPC 403GCX/405GP modifications.
8  * Copyright (c) 2001-2002 PPC64 team, IBM Corp
9  *   64-bit and Power4 support
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version
14  * 2 of the License, or (at your option) any later version.
15  */
16
17 #include <linux/kernel.h>
18 #include <linux/mm.h>
19 #include <asm/processor.h>
20 #include <asm/uaccess.h>
21 #include <asm/system.h>
22 #include <asm/cache.h>
23 #include <asm/cputable.h>
24
25 void disable_kernel_fp(void); /* asm function from head.S */
26
27 struct aligninfo {
28         unsigned char len;
29         unsigned char flags;
30 };
31
32 #define IS_XFORM(inst)  (((inst) >> 26) == 31)
33 #define IS_DSFORM(inst) (((inst) >> 26) >= 56)
34
35 #define INVALID { 0, 0 }
36
37 #define LD      1       /* load */
38 #define ST      2       /* store */
39 #define SE      4       /* sign-extend value */
40 #define F       8       /* to/from fp regs */
41 #define U       0x10    /* update index register */
42 #define M       0x20    /* multiple load/store */
43 #define SW      0x40    /* byte swap */
44
45 #define DCBZ    0x5f    /* 8xx/82xx dcbz faults when cache not enabled */
46
47 /*
48  * The PowerPC stores certain bits of the instruction that caused the
49  * alignment exception in the DSISR register.  This array maps those
50  * bits to information about the operand length and what the
51  * instruction would do.
52  */
53 static struct aligninfo aligninfo[128] = {
54         { 4, LD },              /* 00 0 0000: lwz / lwarx */
55         INVALID,                /* 00 0 0001 */
56         { 4, ST },              /* 00 0 0010: stw */
57         INVALID,                /* 00 0 0011 */
58         { 2, LD },              /* 00 0 0100: lhz */
59         { 2, LD+SE },           /* 00 0 0101: lha */
60         { 2, ST },              /* 00 0 0110: sth */
61         { 4, LD+M },            /* 00 0 0111: lmw */
62         { 4, LD+F },            /* 00 0 1000: lfs */
63         { 8, LD+F },            /* 00 0 1001: lfd */
64         { 4, ST+F },            /* 00 0 1010: stfs */
65         { 8, ST+F },            /* 00 0 1011: stfd */
66         INVALID,                /* 00 0 1100 */
67         { 8, LD },              /* 00 0 1101: ld */
68         INVALID,                /* 00 0 1110 */
69         { 8, ST },              /* 00 0 1111: std */
70         { 4, LD+U },            /* 00 1 0000: lwzu */
71         INVALID,                /* 00 1 0001 */
72         { 4, ST+U },            /* 00 1 0010: stwu */
73         INVALID,                /* 00 1 0011 */
74         { 2, LD+U },            /* 00 1 0100: lhzu */
75         { 2, LD+SE+U },         /* 00 1 0101: lhau */
76         { 2, ST+U },            /* 00 1 0110: sthu */
77         { 4, ST+M },            /* 00 1 0111: stmw */
78         { 4, LD+F+U },          /* 00 1 1000: lfsu */
79         { 8, LD+F+U },          /* 00 1 1001: lfdu */
80         { 4, ST+F+U },          /* 00 1 1010: stfsu */
81         { 8, ST+F+U },          /* 00 1 1011: stfdu */
82         INVALID,                /* 00 1 1100 */
83         INVALID,                /* 00 1 1101 */
84         INVALID,                /* 00 1 1110 */
85         INVALID,                /* 00 1 1111 */
86         { 8, LD },              /* 01 0 0000: ldx */
87         INVALID,                /* 01 0 0001 */
88         { 8, ST },              /* 01 0 0010: stdx */
89         INVALID,                /* 01 0 0011 */
90         INVALID,                /* 01 0 0100 */
91         { 4, LD+SE },           /* 01 0 0101: lwax */
92         INVALID,                /* 01 0 0110 */
93         INVALID,                /* 01 0 0111 */
94         { 0, LD },              /* 01 0 1000: lswx */
95         { 0, LD },              /* 01 0 1001: lswi */
96         { 0, ST },              /* 01 0 1010: stswx */
97         { 0, ST },              /* 01 0 1011: stswi */
98         INVALID,                /* 01 0 1100 */
99         { 8, LD+U },            /* 01 0 1101: ldu */
100         INVALID,                /* 01 0 1110 */
101         { 8, ST+U },            /* 01 0 1111: stdu */
102         { 8, LD+U },            /* 01 1 0000: ldux */
103         INVALID,                /* 01 1 0001 */
104         { 8, ST+U },            /* 01 1 0010: stdux */
105         INVALID,                /* 01 1 0011 */
106         INVALID,                /* 01 1 0100 */
107         { 4, LD+SE+U },         /* 01 1 0101: lwaux */
108         INVALID,                /* 01 1 0110 */
109         INVALID,                /* 01 1 0111 */
110         INVALID,                /* 01 1 1000 */
111         INVALID,                /* 01 1 1001 */
112         INVALID,                /* 01 1 1010 */
113         INVALID,                /* 01 1 1011 */
114         INVALID,                /* 01 1 1100 */
115         INVALID,                /* 01 1 1101 */
116         INVALID,                /* 01 1 1110 */
117         INVALID,                /* 01 1 1111 */
118         INVALID,                /* 10 0 0000 */
119         INVALID,                /* 10 0 0001 */
120         { 0, ST },              /* 10 0 0010: stwcx. */
121         INVALID,                /* 10 0 0011 */
122         INVALID,                /* 10 0 0100 */
123         INVALID,                /* 10 0 0101 */
124         INVALID,                /* 10 0 0110 */
125         INVALID,                /* 10 0 0111 */
126         { 4, LD+SW },           /* 10 0 1000: lwbrx */
127         INVALID,                /* 10 0 1001 */
128         { 4, ST+SW },           /* 10 0 1010: stwbrx */
129         INVALID,                /* 10 0 1011 */
130         { 2, LD+SW },           /* 10 0 1100: lhbrx */
131         { 4, LD+SE },           /* 10 0 1101  lwa */
132         { 2, ST+SW },           /* 10 0 1110: sthbrx */
133         INVALID,                /* 10 0 1111 */
134         INVALID,                /* 10 1 0000 */
135         INVALID,                /* 10 1 0001 */
136         INVALID,                /* 10 1 0010 */
137         INVALID,                /* 10 1 0011 */
138         INVALID,                /* 10 1 0100 */
139         INVALID,                /* 10 1 0101 */
140         INVALID,                /* 10 1 0110 */
141         INVALID,                /* 10 1 0111 */
142         INVALID,                /* 10 1 1000 */
143         INVALID,                /* 10 1 1001 */
144         INVALID,                /* 10 1 1010 */
145         INVALID,                /* 10 1 1011 */
146         INVALID,                /* 10 1 1100 */
147         INVALID,                /* 10 1 1101 */
148         INVALID,                /* 10 1 1110 */
149         { L1_CACHE_BYTES, ST }, /* 10 1 1111: dcbz */
150         { 4, LD },              /* 11 0 0000: lwzx */
151         INVALID,                /* 11 0 0001 */
152         { 4, ST },              /* 11 0 0010: stwx */
153         INVALID,                /* 11 0 0011 */
154         { 2, LD },              /* 11 0 0100: lhzx */
155         { 2, LD+SE },           /* 11 0 0101: lhax */
156         { 2, ST },              /* 11 0 0110: sthx */
157         INVALID,                /* 11 0 0111 */
158         { 4, LD+F },            /* 11 0 1000: lfsx */
159         { 8, LD+F },            /* 11 0 1001: lfdx */
160         { 4, ST+F },            /* 11 0 1010: stfsx */
161         { 8, ST+F },            /* 11 0 1011: stfdx */
162         INVALID,                /* 11 0 1100 */
163         { 8, LD+M },            /* 11 0 1101: lmd */
164         INVALID,                /* 11 0 1110 */
165         { 8, ST+M },            /* 11 0 1111: stmd */
166         { 4, LD+U },            /* 11 1 0000: lwzux */
167         INVALID,                /* 11 1 0001 */
168         { 4, ST+U },            /* 11 1 0010: stwux */
169         INVALID,                /* 11 1 0011 */
170         { 2, LD+U },            /* 11 1 0100: lhzux */
171         { 2, LD+SE+U },         /* 11 1 0101: lhaux */
172         { 2, ST+U },            /* 11 1 0110: sthux */
173         INVALID,                /* 11 1 0111 */
174         { 4, LD+F+U },          /* 11 1 1000: lfsux */
175         { 8, LD+F+U },          /* 11 1 1001: lfdux */
176         { 4, ST+F+U },          /* 11 1 1010: stfsux */
177         { 8, ST+F+U },          /* 11 1 1011: stfdux */
178         INVALID,                /* 11 1 1100 */
179         INVALID,                /* 11 1 1101 */
180         INVALID,                /* 11 1 1110 */
181         INVALID,                /* 11 1 1111 */
182 };
183
184 #define SWAP(a, b)      (t = (a), (a) = (b), (b) = t)
185
186 static inline unsigned make_dsisr(unsigned instr)
187 {
188         unsigned dsisr;
189         
190         /* create a DSISR value from the instruction */
191         dsisr = (instr & 0x03ff0000) >> 16;                     /* bits  6:15 --> 22:31 */
192         
193         if ( IS_XFORM(instr) ) {
194                 dsisr |= (instr & 0x00000006) << 14;            /* bits 29:30 --> 15:16 */
195                 dsisr |= (instr & 0x00000040) << 8;             /* bit     25 -->    17 */
196                 dsisr |= (instr & 0x00000780) << 3;             /* bits 21:24 --> 18:21 */
197         }
198         else {
199                 dsisr |= (instr & 0x04000000) >> 12;            /* bit      5 -->    17 */
200                 dsisr |= (instr & 0x78000000) >> 17;            /* bits  1: 4 --> 18:21 */
201                 if ( IS_DSFORM(instr) ) {
202                         dsisr |= (instr & 0x00000003) << 18;    /* bits 30:31 --> 12:13 */
203                 }
204         }
205         
206         return dsisr;
207 }
208
209 int
210 fix_alignment(struct pt_regs *regs)
211 {
212         unsigned int instr, nb, flags;
213         int t;
214         unsigned long reg, areg;
215         unsigned long i;
216         int ret;
217         unsigned dsisr;
218         unsigned char __user *addr;
219         unsigned char __user *p;
220         unsigned long __user *lp;
221         union {
222                 long ll;
223                 double dd;
224                 unsigned char v[8];
225                 struct {
226                         unsigned hi32;
227                         int      low32;
228                 } x32;
229                 struct {
230                         unsigned char hi48[6];
231                         short         low16;
232                 } x16;
233         } data;
234
235         /*
236          * Return 1 on success
237          * Return 0 if unable to handle the interrupt
238          * Return -EFAULT if data address is bad
239          */
240
241         dsisr = regs->dsisr;
242
243         if (cur_cpu_spec->cpu_features & CPU_FTR_NODSISRALIGN) {
244             unsigned int real_instr;
245             if (__get_user(real_instr, (unsigned int __user *)regs->nip))
246                 return 0;
247             dsisr = make_dsisr(real_instr);
248         }
249
250         /* extract the operation and registers from the dsisr */
251         reg = (dsisr >> 5) & 0x1f;      /* source/dest register */
252         areg = dsisr & 0x1f;            /* register to update */
253         instr = (dsisr >> 10) & 0x7f;
254         instr |= (dsisr >> 13) & 0x60;
255
256         /* Lookup the operation in our table */
257         nb = aligninfo[instr].len;
258         flags = aligninfo[instr].flags;
259
260         /* DAR has the operand effective address */
261         addr = (unsigned char __user *)regs->dar;
262
263         /* A size of 0 indicates an instruction we don't support */
264         /* we also don't support the multiples (lmw, stmw, lmd, stmd) */
265         if ((nb == 0) || (flags & M))
266                 return 0;               /* too hard or invalid instruction */
267
268         /*
269          * Special handling for dcbz
270          * dcbz may give an alignment exception for accesses to caching inhibited
271          * storage
272          */
273         if (instr == DCBZ)
274                 addr = (unsigned char __user *) ((unsigned long)addr & -L1_CACHE_BYTES);
275
276         /* Verify the address of the operand */
277         if (user_mode(regs)) {
278                 if (verify_area((flags & ST? VERIFY_WRITE: VERIFY_READ), addr, nb))
279                         return -EFAULT; /* bad address */
280         }
281
282         /* Force the fprs into the save area so we can reference them */
283         if ((flags & F) && (regs->msr & MSR_FP))
284                 giveup_fpu(current);
285         
286         /* If we are loading, get the data from user space */
287         if (flags & LD) {
288                 data.ll = 0;
289                 ret = 0;
290                 p = addr;
291                 switch (nb) {
292                 case 8:
293                         ret |= __get_user(data.v[0], p++);
294                         ret |= __get_user(data.v[1], p++);
295                         ret |= __get_user(data.v[2], p++);
296                         ret |= __get_user(data.v[3], p++);
297                 case 4:
298                         ret |= __get_user(data.v[4], p++);
299                         ret |= __get_user(data.v[5], p++);
300                 case 2:
301                         ret |= __get_user(data.v[6], p++);
302                         ret |= __get_user(data.v[7], p++);
303                         if (ret)
304                                 return -EFAULT;
305                 }
306         }
307         
308         /* If we are storing, get the data from the saved gpr or fpr */
309         if (flags & ST) {
310                 if (flags & F) {
311                         if (nb == 4) {
312                                 /* Doing stfs, have to convert to single */
313                                 enable_kernel_fp();
314                                 cvt_df(&current->thread.fpr[reg], (float *)&data.v[4], &current->thread.fpscr);
315                                 disable_kernel_fp();
316                         }
317                         else
318                                 data.dd = current->thread.fpr[reg];
319                 }
320                 else 
321                         data.ll = regs->gpr[reg];
322         }
323         
324         /* Swap bytes as needed */
325         if (flags & SW) {
326                 if (nb == 2)
327                         SWAP(data.v[6], data.v[7]);
328                 else {  /* nb must be 4 */
329                         SWAP(data.v[4], data.v[7]);
330                         SWAP(data.v[5], data.v[6]);
331                 }
332         }
333         
334         /* Sign extend as needed */
335         if (flags & SE) {
336                 if ( nb == 2 )
337                         data.ll = data.x16.low16;
338                 else    /* nb must be 4 */
339                         data.ll = data.x32.low32;
340         }
341         
342         /* If we are loading, move the data to the gpr or fpr */
343         if (flags & LD) {
344                 if (flags & F) {
345                         if (nb == 4) {
346                                 /* Doing lfs, have to convert to double */
347                                 enable_kernel_fp();
348                                 cvt_fd((float *)&data.v[4], &current->thread.fpr[reg], &current->thread.fpscr);
349                                 disable_kernel_fp();
350                         }
351                         else
352                                 current->thread.fpr[reg] = data.dd;
353                 }
354                 else
355                         regs->gpr[reg] = data.ll;
356         }
357         
358         /* If we are storing, copy the data to the user */
359         if (flags & ST) {
360                 ret = 0;
361                 p = addr;
362                 switch (nb) {
363                 case 128:       /* Special case - must be dcbz */
364                         lp = (unsigned long __user *)p;
365                         for (i = 0; i < L1_CACHE_BYTES / sizeof(long); ++i)
366                                 ret |= __put_user(0, lp++);
367                         break;
368                 case 8:
369                         ret |= __put_user(data.v[0], p++);
370                         ret |= __put_user(data.v[1], p++);
371                         ret |= __put_user(data.v[2], p++);
372                         ret |= __put_user(data.v[3], p++);
373                 case 4:
374                         ret |= __put_user(data.v[4], p++);
375                         ret |= __put_user(data.v[5], p++);
376                 case 2:
377                         ret |= __put_user(data.v[6], p++);
378                         ret |= __put_user(data.v[7], p++);
379                 }
380                 if (ret)
381                         return -EFAULT;
382         }
383         
384         /* Update RA as needed */
385         if (flags & U) {
386                 regs->gpr[areg] = regs->dar;
387         }
388
389         return 1;
390 }
391