aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162>2009-03-31 14:34:24 +0000
committerpbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162>2009-03-31 14:34:24 +0000
commitd0a981b2d50919bea53986f28234ee7402597f7c (patch)
treec45ed1969e03cf3e468459de5c9e9e931b8c239e
parentbbeea539aa7bb7c31c5dceccdfd0d6493134ae67 (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
-rw-r--r--hw/ptimer.c32
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 {