diff options
author | pbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162> | 2009-03-31 14:34:24 +0000 |
---|---|---|
committer | pbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162> | 2009-03-31 14:34:24 +0000 |
commit | d0a981b2d50919bea53986f28234ee7402597f7c (patch) | |
tree | c45ed1969e03cf3e468459de5c9e9e931b8c239e /hw/ptimer.c | |
parent | bbeea539aa7bb7c31c5dceccdfd0d6493134ae67 (diff) |
Avoid rounding problems in ptimer_get_count
Signed-off-by: Paul Brook <paul@codesourcery.com>
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@6961 c046a42c-6fe2-441c-8c8c-71466251a162
Diffstat (limited to 'hw/ptimer.c')
-rw-r--r-- | hw/ptimer.c | 32 |
1 files changed, 31 insertions, 1 deletions
diff --git a/hw/ptimer.c b/hw/ptimer.c index 9d3862724d..b36f9a6f07 100644 --- a/hw/ptimer.c +++ b/hw/ptimer.c @@ -7,7 +7,7 @@ */ #include "hw.h" #include "qemu-timer.h" - +#include "host-utils.h" struct ptimer_state { @@ -78,9 +78,39 @@ uint64_t ptimer_get_count(ptimer_state *s) } else { uint64_t rem; uint64_t div; + uint32_t frac; + int clz1, clz2; + int shift; + + /* We need to divide time by period, where time is stored in + rem (64-bit integer) and period is stored in period/period_frac + (64.32 fixed point). + + Doing full precision division is hard, so scale values and + do a 64-bit division. The result should be rounded down, + so that the rounding error never causes the timer to go + backwards. + */ rem = s->next_event - now; div = s->period; + + clz1 = clz64(rem); + clz2 = clz64(div); + shift = clz1 < clz2 ? clz1 : clz2; + + rem <<= shift; + div <<= shift; + if (shift >= 32) { + div |= ((uint64_t)s->period_frac << (shift - 32)); + } else { + if (shift != 0) + div |= (s->period_frac >> (32 - shift)); + /* Look at remaining bits of period_frac and round div up if + necessary. */ + if ((uint32_t)(s->period_frac << shift)) + div += 1; + } counter = rem / div; } } else { |