diff options
-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 { |