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); } } diff --git a/include/hw/ptimer.h b/include/hw/ptimer.h index 26c7fdcd75..03441cbc22 100644 --- a/include/hw/ptimer.h +++ b/include/hw/ptimer.h @@ -35,6 +35,10 @@ */ #define PTIMER_POLICY_DEFAULT 0 +/* Periodic timer counter stays with "0" for a one period before wrapping + * around. */ +#define PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD (1 << 0) + /* ptimer.c */ typedef struct ptimer_state ptimer_state; typedef void (*ptimer_cb)(void *opaque);