fls() counts bits starting from 1, so shifting the operands right by
1 + fls(high) discards one more bit of the divisor than intended. Both
functions estimate the quotient from the shifted operands and then fix
it up with a single decrement/increment, so an estimate that is off by
more than one cannot be repaired and the result comes out wrong.
This only affects BITS_PER_LONG == 32, where these are the out-of-line
implementations; on 64-bit the header provides plain C division.
The error is only reachable when the quotient is large, which needs a
divisor just above 2^32. For example:
dividend = 15559272575191414037
divisor = 4333540799
expected = 3590429465
actual = 3590429468 (off by 3)
A sweep over 6.4M random operand pairs, stratified by the width of the
divisor's high word, mismatches a __int128 reference 8260 times before
this change and never after it. All failures have a divisor with one or
two significant bits above bit 32; uniformly random 64-bit divisors are
closer to 2^63 and yield quotients of ~1, which hides the problem.
Port of Linux commit cdc94a374931 ("lib/div64.c: off by one in shift"),
which fixed the same code and cites [1].
In-tree users of div64_u64() that are built for 32-bit targets include
the Aspeed, Meson and Cadence TTC PWM drivers, the Versaclock and
wrpll-cln28hpc clock drivers, and the DWC3 USB core.
Link: https://bugzilla.kernel.org/show_bug.cgi?id=202391 [1]
Fixes: 0342e335ba88 ("lib: div64: sync with Linux")
Signed-off-by: Alexey Charkov <[email protected]>
---
lib/div64.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/div64.c b/lib/div64.c
index 779d7521f69f..14d402ce4c7e 100644
--- a/lib/div64.c
+++ b/lib/div64.c
@@ -109,7 +109,7 @@ u64 div64_u64_rem(u64 dividend, u64 divisor, u64 *remainder)
quot = div_u64_rem(dividend, divisor, &rem32);
*remainder = rem32;
} else {
- int n = 1 + fls(high);
+ int n = fls(high);
quot = div_u64(dividend >> n, divisor >> n);
if (quot != 0)
@@ -147,7 +147,7 @@ u64 div64_u64(u64 dividend, u64 divisor)
if (high == 0) {
quot = div_u64(dividend, divisor);
} else {
- int n = 1 + fls(high);
+ int n = fls(high);
quot = div_u64(dividend >> n, divisor >> n);
if (quot != 0)
--
2.54.0