[SPARC64]: Handle little-endian unaligned loads/stores correctly.
authorDavid S. Miller <davem@davemloft.net>
Tue, 20 Sep 2005 02:56:06 +0000 (19:56 -0700)
committerDavid S. Miller <davem@davemloft.net>
Tue, 20 Sep 2005 02:56:06 +0000 (19:56 -0700)
Because we use byte loads/stores to cons up the value
in and out of registers, we can't expect the ASI endianness
setting to take care of this for us.  So do it by hand.

This case is triggered by drivers/block/aoe/aoecmd.c in the
ataid_complete() function where it goes:

/* word 100: number lba48 sectors */
ssize = le64_to_cpup((__le64 *) &id[100<<1]);

This &id[100<<1] address is 4 byte, rather than 8 byte aligned,
thus triggering the unaligned exception.

Signed-off-by: David S. Miller <davem@davemloft.net>
arch/sparc64/kernel/una_asm.S
arch/sparc64/kernel/unaligned.c

index cbb40585253c279f0cdc83eaf337c3170b5e2089..da48400bcc95a8b43a7dcef27f63e62998b0489f 100644 (file)
@@ -17,7 +17,7 @@ kernel_unaligned_trap_fault:
 __do_int_store:
        rd      %asi, %o4
        wr      %o3, 0, %asi
-       ldx     [%o2], %g3
+       mov     %o2, %g3
        cmp     %o1, 2
        be,pn   %icc, 2f
         cmp    %o1, 4
index da9739f0d43723cbee80f87cf9f491bff1ea11f3..42718f6a7d3671ea349c3918f2355c11809c272c 100644 (file)
@@ -184,13 +184,14 @@ extern void do_int_load(unsigned long *dest_reg, int size,
                        unsigned long *saddr, int is_signed, int asi);
        
 extern void __do_int_store(unsigned long *dst_addr, int size,
-                          unsigned long *src_val, int asi);
+                          unsigned long src_val, int asi);
 
 static inline void do_int_store(int reg_num, int size, unsigned long *dst_addr,
-                               struct pt_regs *regs, int asi)
+                               struct pt_regs *regs, int asi, int orig_asi)
 {
        unsigned long zero = 0;
-       unsigned long *src_val = &zero;
+       unsigned long *src_val_p = &zero;
+       unsigned long src_val;
 
        if (size == 16) {
                size = 8;
@@ -198,7 +199,25 @@ static inline void do_int_store(int reg_num, int size, unsigned long *dst_addr,
                        (unsigned)fetch_reg(reg_num, regs) : 0)) << 32) |
                        (unsigned)fetch_reg(reg_num + 1, regs);
        } else if (reg_num) {
-               src_val = fetch_reg_addr(reg_num, regs);
+               src_val_p = fetch_reg_addr(reg_num, regs);
+       }
+       src_val = *src_val_p;
+       if (unlikely(asi != orig_asi)) {
+               switch (size) {
+               case 2:
+                       src_val = swab16(src_val);
+                       break;
+               case 4:
+                       src_val = swab32(src_val);
+                       break;
+               case 8:
+                       src_val = swab64(src_val);
+                       break;
+               case 16:
+               default:
+                       BUG();
+                       break;
+               };
        }
        __do_int_store(dst_addr, size, src_val, asi);
 }
@@ -276,6 +295,7 @@ asmlinkage void kernel_unaligned_trap(struct pt_regs *regs, unsigned int insn, u
                kernel_mna_trap_fault();
        } else {
                unsigned long addr;
+               int orig_asi, asi;
 
                addr = compute_effective_address(regs, insn,
                                                 ((insn >> 25) & 0x1f));
@@ -285,18 +305,48 @@ asmlinkage void kernel_unaligned_trap(struct pt_regs *regs, unsigned int insn, u
                       regs->tpc, dirstrings[dir], addr, size,
                       regs->u_regs[UREG_RETPC]);
 #endif
+               orig_asi = asi = decode_asi(insn, regs);
+               switch (asi) {
+               case ASI_NL:
+               case ASI_AIUPL:
+               case ASI_AIUSL:
+               case ASI_PL:
+               case ASI_SL:
+               case ASI_PNFL:
+               case ASI_SNFL:
+                       asi &= ~0x08;
+                       break;
+               };
                switch (dir) {
                case load:
                        do_int_load(fetch_reg_addr(((insn>>25)&0x1f), regs),
                                    size, (unsigned long *) addr,
-                                   decode_signedness(insn),
-                                   decode_asi(insn, regs));
+                                   decode_signedness(insn), asi);
+                       if (unlikely(asi != orig_asi)) {
+                               unsigned long val_in = *(unsigned long *) addr;
+                               switch (size) {
+                               case 2:
+                                       val_in = swab16(val_in);
+                                       break;
+                               case 4:
+                                       val_in = swab32(val_in);
+                                       break;
+                               case 8:
+                                       val_in = swab64(val_in);
+                                       break;
+                               case 16:
+                               default:
+                                       BUG();
+                                       break;
+                               };
+                               *(unsigned long *) addr = val_in;
+                       }
                        break;
 
                case store:
                        do_int_store(((insn>>25)&0x1f), size,
                                     (unsigned long *) addr, regs,
-                                    decode_asi(insn, regs));
+                                    asi, orig_asi);
                        break;
 
                default: