dm_jiffies_to_msec64() converts a 22-bit chunk to milliseconds, rounds
it up, then multiplies that rounded value by the number of chunks. At
HZ=300, the chunk length is not an integral number of milliseconds, so
the rounding error grows with the accumulated time. For 4,194,305
jiffies, a stats print reports 13,981,018 ms instead of 13,981,017 ms.

Divide the full jiffies value by HZ and convert the remainder to
milliseconds once. This gives a single rounding of the total while
avoiding an overflowing multiplication of the full input. Preserve the
precise timestamp path, which already returns nanoseconds unchanged.

Signed-off-by: Baek Kyu, Kim <[email protected]>
---
Tested under ARM64 QEMU/KVM with HZ=300 through the DM stats ioctl
path on a zero target, using injected time values. At 4,194,305
jiffies, the baseline printed 13,981,018 ms in all six time fields and
the patched kernel printed 13,981,017 ms. Precise timestamps and
stats_print_clear also passed.

 drivers/md/dm-stats.c | 19 ++++++-------------
 1 file changed, 6 insertions(+), 13 deletions(-)

diff --git a/drivers/md/dm-stats.c b/drivers/md/dm-stats.c
index beabbe3b39f3..9ec568d120a4 100644
--- a/drivers/md/dm-stats.c
+++ b/drivers/md/dm-stats.c
@@ -9,6 +9,7 @@
 #include <linux/vmalloc.h>
 #include <linux/mm.h>
 #include <linux/module.h>
+#include <linux/math64.h>
 #include <linux/device-mapper.h>
 
 #include "dm-core.h"
@@ -831,23 +832,15 @@ static int dm_stats_clear(struct dm_stats *stats, int id)
  */
 static unsigned long long dm_jiffies_to_msec64(struct dm_stat *s, unsigned 
long long j)
 {
-       unsigned long long result;
-       unsigned int mult;
+       u64 result;
+       u32 remainder;
 
        if (s->stat_flags & STAT_PRECISE_TIMESTAMPS)
                return j;
 
-       result = 0;
-       if (j)
-               result = jiffies_to_msecs(j & 0x3fffff);
-       if (j >= 1 << 22) {
-               mult = jiffies_to_msecs(1 << 22);
-               result += (unsigned long long)mult * ((j >> 22) & 0x3fffff);
-       }
-       if (j >= 1ULL << 44)
-               result += (unsigned long long)mult * (unsigned long long)(1 << 
22) * (j >> 44);
-
-       return result;
+       result = div_u64_rem(j, HZ, &remainder);
+       return result * MSEC_PER_SEC +
+               DIV_ROUND_UP_ULL((u64)remainder * MSEC_PER_SEC, HZ);
 }
 
 static int dm_stats_print(struct dm_stats *stats, int id,

Reply via email to