aboutsummaryrefslogtreecommitdiff
path: root/hw/core/ptimer.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/core/ptimer.c')
-rw-r--r--hw/core/ptimer.c58
1 files changed, 43 insertions, 15 deletions
diff --git a/hw/core/ptimer.c b/hw/core/ptimer.c
index c45c835a17..1f4122da4e 100644
--- a/hw/core/ptimer.c
+++ b/hw/core/ptimer.c
@@ -13,6 +13,8 @@
#include "sysemu/replay.h"
#include "sysemu/qtest.h"
+#define DELTA_ADJUST 1
+
struct ptimer_state
{
uint8_t enabled; /* 0 = disabled, 1 = periodic, 2 = oneshot. */
@@ -35,16 +37,17 @@ static void ptimer_trigger(ptimer_state *s)
}
}
-static void ptimer_reload(ptimer_state *s)
+static void ptimer_reload(ptimer_state *s, int delta_adjust)
{
uint32_t period_frac = s->period_frac;
uint64_t period = s->period;
+ uint64_t delta = s->delta;
- if (s->delta == 0) {
+ if (delta == 0) {
ptimer_trigger(s);
- s->delta = s->limit;
+ delta = s->delta = s->limit;
}
- if (s->delta == 0 || s->period == 0) {
+ if (delta == 0 || s->period == 0) {
if (!qtest_enabled()) {
fprintf(stderr, "Timer with period zero, disabling\n");
}
@@ -53,6 +56,10 @@ static void ptimer_reload(ptimer_state *s)
return;
}
+ if (s->policy_mask & PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD) {
+ delta += delta_adjust;
+ }
+
/*
* Artificially limit timeout rate to something
* achievable under QEMU. Otherwise, QEMU spends all
@@ -62,15 +69,15 @@ static void ptimer_reload(ptimer_state *s)
* on the current generation of host machines.
*/
- if (s->enabled == 1 && (s->delta * period < 10000) && !use_icount) {
- period = 10000 / s->delta;
+ if (s->enabled == 1 && (delta * period < 10000) && !use_icount) {
+ period = 10000 / delta;
period_frac = 0;
}
s->last_event = s->next_event;
- s->next_event = s->last_event + s->delta * period;
+ s->next_event = s->last_event + delta * period;
if (period_frac) {
- s->next_event += ((int64_t)period_frac * s->delta) >> 32;
+ s->next_event += ((int64_t)period_frac * delta) >> 32;
}
timer_mod(s->timer, s->next_event);
}
@@ -83,7 +90,7 @@ static void ptimer_tick(void *opaque)
if (s->enabled == 2) {
s->enabled = 0;
} else {
- ptimer_reload(s);
+ ptimer_reload(s, DELTA_ADJUST);
}
}
@@ -94,6 +101,7 @@ uint64_t ptimer_get_count(ptimer_state *s)
if (s->enabled) {
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
int64_t next = s->next_event;
+ int64_t last = s->last_event;
bool expired = (now - next >= 0);
bool oneshot = (s->enabled == 2);
@@ -118,7 +126,7 @@ uint64_t ptimer_get_count(ptimer_state *s)
/* 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
@@ -145,6 +153,26 @@ uint64_t ptimer_get_count(ptimer_state *s)
div += 1;
}
counter = rem / div;
+
+ if (s->policy_mask & PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD) {
+ /* Before wrapping around, timer should stay with counter = 0
+ for a one period. */
+ if (!oneshot && s->delta == s->limit) {
+ if (now == last) {
+ /* Counter == delta here, check whether it was
+ adjusted and if it was, then right now it is
+ that "one period". */
+ if (counter == s->limit + DELTA_ADJUST) {
+ return 0;
+ }
+ } else if (counter == s->limit) {
+ /* Since the counter is rounded down and now != last,
+ the counter == limit means that delta was adjusted
+ by +1 and right now it is that adjusted period. */
+ return 0;
+ }
+ }
+ }
}
} else {
counter = s->delta;
@@ -157,7 +185,7 @@ void ptimer_set_count(ptimer_state *s, uint64_t count)
s->delta = count;
if (s->enabled) {
s->next_event = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
- ptimer_reload(s);
+ ptimer_reload(s, 0);
}
}
@@ -174,7 +202,7 @@ void ptimer_run(ptimer_state *s, int oneshot)
s->enabled = oneshot ? 2 : 1;
if (was_disabled) {
s->next_event = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
- ptimer_reload(s);
+ ptimer_reload(s, 0);
}
}
@@ -198,7 +226,7 @@ void ptimer_set_period(ptimer_state *s, int64_t period)
s->period_frac = 0;
if (s->enabled) {
s->next_event = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
- ptimer_reload(s);
+ ptimer_reload(s, 0);
}
}
@@ -210,7 +238,7 @@ void ptimer_set_freq(ptimer_state *s, uint32_t freq)
s->period_frac = (1000000000ll << 32) / freq;
if (s->enabled) {
s->next_event = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
- ptimer_reload(s);
+ ptimer_reload(s, 0);
}
}
@@ -223,7 +251,7 @@ void ptimer_set_limit(ptimer_state *s, uint64_t limit, int reload)
s->delta = limit;
if (s->enabled && reload) {
s->next_event = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
- ptimer_reload(s);
+ ptimer_reload(s, 0);
}
}