Commit d0a981b2d50919bea53986f28234ee7402597f7c
1 parent
bbeea539
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
Showing
1 changed file
with
31 additions
and
1 deletions
hw/ptimer.c
| ... | ... | @@ -7,7 +7,7 @@ |
| 7 | 7 | */ |
| 8 | 8 | #include "hw.h" |
| 9 | 9 | #include "qemu-timer.h" |
| 10 | - | |
| 10 | +#include "host-utils.h" | |
| 11 | 11 | |
| 12 | 12 | struct ptimer_state |
| 13 | 13 | { |
| ... | ... | @@ -78,9 +78,39 @@ uint64_t ptimer_get_count(ptimer_state *s) |
| 78 | 78 | } else { |
| 79 | 79 | uint64_t rem; |
| 80 | 80 | uint64_t div; |
| 81 | + uint32_t frac; | |
| 82 | + int clz1, clz2; | |
| 83 | + int shift; | |
| 84 | + | |
| 85 | + /* We need to divide time by period, where time is stored in | |
| 86 | + rem (64-bit integer) and period is stored in period/period_frac | |
| 87 | + (64.32 fixed point). | |
| 88 | + | |
| 89 | + Doing full precision division is hard, so scale values and | |
| 90 | + do a 64-bit division. The result should be rounded down, | |
| 91 | + so that the rounding error never causes the timer to go | |
| 92 | + backwards. | |
| 93 | + */ | |
| 81 | 94 | |
| 82 | 95 | rem = s->next_event - now; |
| 83 | 96 | div = s->period; |
| 97 | + | |
| 98 | + clz1 = clz64(rem); | |
| 99 | + clz2 = clz64(div); | |
| 100 | + shift = clz1 < clz2 ? clz1 : clz2; | |
| 101 | + | |
| 102 | + rem <<= shift; | |
| 103 | + div <<= shift; | |
| 104 | + if (shift >= 32) { | |
| 105 | + div |= ((uint64_t)s->period_frac << (shift - 32)); | |
| 106 | + } else { | |
| 107 | + if (shift != 0) | |
| 108 | + div |= (s->period_frac >> (32 - shift)); | |
| 109 | + /* Look at remaining bits of period_frac and round div up if | |
| 110 | + necessary. */ | |
| 111 | + if ((uint32_t)(s->period_frac << shift)) | |
| 112 | + div += 1; | |
| 113 | + } | |
| 84 | 114 | counter = rem / div; |
| 85 | 115 | } |
| 86 | 116 | } else { | ... | ... |