]> granicus.if.org Git - zfs/commitdiff
Fix ARM 64-bit division
authorJorgen Lundman <lundman@lundman.net>
Tue, 15 May 2012 05:45:09 +0000 (05:45 +0000)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Tue, 22 May 2012 16:27:11 +0000 (09:27 -0700)
Correctly implementating 64-bit division for ARM requires more than
just providing the __aeabi_uldivmod() and __aeabi_ldivmod() symbols.
They are need to be implemented is such a way that the quotient and
remainder and left in specific registers after the division operation
completes.  This change updates the wrapper functions to accomplish
this according to the official ARM Run-time ABI.

Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov>
Closes zfsonlinux/zfs#706

module/spl/spl-generic.c

index d22100ea3cbb937d6d55be2d4ac37c92cdbf722d..785f668d69ca9569d1e29614d3f987e9f2d0a433 100644 (file)
@@ -214,22 +214,57 @@ EXPORT_SYMBOL(__umoddi3);
 
 #if defined(__arm) || defined(__arm__)
 /*
- * Implementation of 64-bit unsigned division for 32-bit arm machines.
+ * Implementation of 64-bit (un)signed division for 32-bit arm machines.
+ *
+ * Run-time ABI for the ARM Architecture (page 20).  A pair of (unsigned)
+ * long longs is returned in {{r0, r1}, {r2,r3}}, the quotient in {r0, r1},
+ * and the remainder in {r2, r3}.  The return type is specifically left
+ * set to 'void' to ensure the compiler does not overwrite these registers
+ * during the return.  All results are in registers as per ABI
  */
-uint64_t
+void
 __aeabi_uldivmod(uint64_t u, uint64_t v)
 {
-       return __udivdi3(u, v);
+       uint64_t res;
+       uint64_t mod;
+
+       res = __udivdi3(u, v);
+       mod = __umoddi3(u, v);
+       {
+               register uint32_t r0 asm("r0") = (res & 0xFFFFFFFF);
+               register uint32_t r1 asm("r1") = (res >> 32);
+               register uint32_t r2 asm("r2") = (mod & 0xFFFFFFFF);
+               register uint32_t r3 asm("r3") = (mod >> 32);
+
+               asm volatile(""
+                   : "+r"(r0), "+r"(r1), "+r"(r2),"+r"(r3)  /* output */
+                   : "r"(r0), "r"(r1), "r"(r2), "r"(r3));   /* input */
+
+               return; /* r0; */
+       }
 }
 EXPORT_SYMBOL(__aeabi_uldivmod);
 
-/*
- * Implementation of 64-bit signed division for 32-bit arm machines.
- */
-int64_t
+void
 __aeabi_ldivmod(int64_t u, int64_t v)
 {
-       return __divdi3(u, v);
+       int64_t res;
+       uint64_t mod;
+
+       res =  __divdi3(u, v);
+       mod = __umoddi3(u, v);
+       {
+               register uint32_t r0 asm("r0") = (res & 0xFFFFFFFF);
+               register uint32_t r1 asm("r1") = (res >> 32);
+               register uint32_t r2 asm("r2") = (mod & 0xFFFFFFFF);
+               register uint32_t r3 asm("r3") = (mod >> 32);
+
+               asm volatile(""
+                   : "+r"(r0), "+r"(r1), "+r"(r2),"+r"(r3)  /* output */
+                   : "r"(r0), "r"(r1), "r"(r2), "r"(r3));   /* input */
+
+               return; /* r0; */
+       }
 }
 EXPORT_SYMBOL(__aeabi_ldivmod);
 #endif /* __arm || __arm__ */