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 { | ... | ... |