>From 3b30f1cf78f88b40360dd65816941cf2be9dd60d Mon Sep 17 00:00:00 2001 From: Brian Behlendorf Date: Thu, 5 Aug 2010 14:59:11 -0700 Subject: [PATCH] Fix div64_u64 for 32bit platforms The current implementation of div64_u64 for 32bit systems returns an approximately correct result when the divisor exceeds 32bits. Since doing 64bit division using 32bit hardware is a long since solved problem we just use one of the existing proven methods. Additionally, add a div64_s64 function to correctly handle doing signed 64bit division. --- include/linux/kernel.h | 5 +++ include/linux/math64.h | 12 +++++++++ lib/div64.c | 64 +++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 70 insertions(+), 11 deletions(-) diff --git a/include/linux/kernel.h b/include/linux/kernel.h index 5de838b..7a00dff 100644 --- a/include/linux/kernel.h +++ b/include/linux/kernel.h @@ -162,6 +162,11 @@ extern int _cond_resched(void); (__x < 0) ? -__x : __x; \ }) +#define abs64(x) ({ \ + s64 __x = (x); \ + (__x < 0) ? -__x : __x; \ + }) + #ifdef CONFIG_PROVE_LOCKING void might_fault(void); #else diff --git a/include/linux/math64.h b/include/linux/math64.h index c87f152..23fcdfc 100644 --- a/include/linux/math64.h +++ b/include/linux/math64.h @@ -35,6 +35,14 @@ static inline u64 div64_u64(u64 dividend, u64 divisor) return dividend / divisor; } +/** + * div64_s64 - signed 64bit divide with 64bit divisor + */ +static inline s64 div64_s64(s64 dividend, s64 divisor) +{ + return dividend / divisor; +} + #elif BITS_PER_LONG == 32 #ifndef div_u64_rem @@ -53,6 +61,10 @@ extern s64 div_s64_rem(s64 dividend, s32 divisor, s32 *remainder); extern u64 div64_u64(u64 dividend, u64 divisor); #endif +#ifndef div64_s64 +extern s64 div64_s64(s64 dividend, s64 divisor); +#endif + #endif /* BITS_PER_LONG */ /** diff --git a/lib/div64.c b/lib/div64.c index a111eb8..e4e7fc6 100644 --- a/lib/div64.c +++ b/lib/div64.c @@ -77,26 +77,68 @@ s64 div_s64_rem(s64 dividend, s32 divisor, s32 *remainder) EXPORT_SYMBOL(div_s64_rem); #endif -/* 64bit divisor, dividend and result. dynamic precision */ +/** + * div64_u64 - unsigned 64bit divide with 64bit divisor + * @dividend: 64bit dividend + * @divisor: 64bit divisor + * + * This implementation is a modified version of the algorithm proposed + * by the book 'Hacker's Delight'. The original source and full proof + * can be found here and is available for use without restriction. + * + * 'http://www.hackersdelight.org/HDcode/newCode/divDouble.c' + */ #ifndef div64_u64 u64 div64_u64(u64 dividend, u64 divisor) { - u32 high, d; - - high = divisor >> 32; - if (high) { - unsigned int shift = fls(high); + u64 u0, quot0, quot1; + u32 rem; + int n; + + if (divisor >> 32 == 0) { + if (dividend >> 32 < divisor) { + return div_u64_rem(dividend, divisor, &rem); + } else { + u0 = dividend & 0xFFFFFFFF; + quot1 = div_u64_rem(dividend >> 32, divisor, &rem); + u0 += ((u64)rem << 32); + quot0 = div_u64_rem(u0, divisor, &rem); + return (quot1 << 32) + quot0; + } + } else { + n = __builtin_clzll(divisor); + quot1 = div_u64_rem(dividend >> 1, (divisor << n) >> 32, &rem); + quot0 = (quot1 << n) >> 31; - d = divisor >> shift; - dividend >>= shift; - } else - d = divisor; + if (quot0 != 0) + quot0 = quot0 - 1; + if ((dividend - quot0 * divisor) >= divisor) + quot0 = quot0 + 1; - return div_u64(dividend, d); + return quot0; + } } EXPORT_SYMBOL(div64_u64); #endif +/** + * div64_s64 - signed 64bit divide with 64bit divisor + * @dividend: 64bit dividend + * @divisor: 64bit divisor + */ +#ifndef div64_s64 +s64 div64_s64(s64 dividend, s64 divisor) +{ + s64 quot, t; + + quot = div64_u64(abs64(dividend), abs64(divisor)); + t = (dividend ^ divisor) >> 63; + + return (quot ^ t) - t; +} +EXPORT_SYMBOL(div64_s64); +#endif + #endif /* BITS_PER_LONG == 32 */ /* -- 1.5.4.5