hw/ptimer: Add "wraparound after one period" policy
Currently, periodic counter wraps around immediately once counter reaches "0", this is wrong behaviour for some of the timers, resulting in one period being lost. Add new ptimer policy that provides correct behaviour for such timers, so that counter stays with "0" for a one period before wrapping around. Signed-off-by: Dmitry Osipenko <digetx@gmail.com> Message-id: f22a670cf1f4be298b31640cb5f4be1df0f20ab6.1475421224.git.digetx@gmail.com Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
parent
a2519ad182
commit
2b5c0322b7
@ -13,6 +13,8 @@
|
|||||||
#include "sysemu/replay.h"
|
#include "sysemu/replay.h"
|
||||||
#include "sysemu/qtest.h"
|
#include "sysemu/qtest.h"
|
||||||
|
|
||||||
|
#define DELTA_ADJUST 1
|
||||||
|
|
||||||
struct ptimer_state
|
struct ptimer_state
|
||||||
{
|
{
|
||||||
uint8_t enabled; /* 0 = disabled, 1 = periodic, 2 = oneshot. */
|
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;
|
uint32_t period_frac = s->period_frac;
|
||||||
uint64_t period = s->period;
|
uint64_t period = s->period;
|
||||||
|
uint64_t delta = s->delta;
|
||||||
|
|
||||||
if (s->delta == 0) {
|
if (delta == 0) {
|
||||||
ptimer_trigger(s);
|
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()) {
|
if (!qtest_enabled()) {
|
||||||
fprintf(stderr, "Timer with period zero, disabling\n");
|
fprintf(stderr, "Timer with period zero, disabling\n");
|
||||||
}
|
}
|
||||||
@ -53,6 +56,10 @@ static void ptimer_reload(ptimer_state *s)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (s->policy_mask & PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD) {
|
||||||
|
delta += delta_adjust;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Artificially limit timeout rate to something
|
* Artificially limit timeout rate to something
|
||||||
* achievable under QEMU. Otherwise, QEMU spends all
|
* 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.
|
* on the current generation of host machines.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (s->enabled == 1 && (s->delta * period < 10000) && !use_icount) {
|
if (s->enabled == 1 && (delta * period < 10000) && !use_icount) {
|
||||||
period = 10000 / s->delta;
|
period = 10000 / delta;
|
||||||
period_frac = 0;
|
period_frac = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
s->last_event = s->next_event;
|
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) {
|
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);
|
timer_mod(s->timer, s->next_event);
|
||||||
}
|
}
|
||||||
@ -83,7 +90,7 @@ static void ptimer_tick(void *opaque)
|
|||||||
if (s->enabled == 2) {
|
if (s->enabled == 2) {
|
||||||
s->enabled = 0;
|
s->enabled = 0;
|
||||||
} else {
|
} else {
|
||||||
ptimer_reload(s);
|
ptimer_reload(s, DELTA_ADJUST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,6 +101,7 @@ uint64_t ptimer_get_count(ptimer_state *s)
|
|||||||
if (s->enabled) {
|
if (s->enabled) {
|
||||||
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||||
int64_t next = s->next_event;
|
int64_t next = s->next_event;
|
||||||
|
int64_t last = s->last_event;
|
||||||
bool expired = (now - next >= 0);
|
bool expired = (now - next >= 0);
|
||||||
bool oneshot = (s->enabled == 2);
|
bool oneshot = (s->enabled == 2);
|
||||||
|
|
||||||
@ -145,6 +153,26 @@ uint64_t ptimer_get_count(ptimer_state *s)
|
|||||||
div += 1;
|
div += 1;
|
||||||
}
|
}
|
||||||
counter = rem / div;
|
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 {
|
} else {
|
||||||
counter = s->delta;
|
counter = s->delta;
|
||||||
@ -157,7 +185,7 @@ void ptimer_set_count(ptimer_state *s, uint64_t count)
|
|||||||
s->delta = count;
|
s->delta = count;
|
||||||
if (s->enabled) {
|
if (s->enabled) {
|
||||||
s->next_event = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
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;
|
s->enabled = oneshot ? 2 : 1;
|
||||||
if (was_disabled) {
|
if (was_disabled) {
|
||||||
s->next_event = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
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;
|
s->period_frac = 0;
|
||||||
if (s->enabled) {
|
if (s->enabled) {
|
||||||
s->next_event = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
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;
|
s->period_frac = (1000000000ll << 32) / freq;
|
||||||
if (s->enabled) {
|
if (s->enabled) {
|
||||||
s->next_event = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
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;
|
s->delta = limit;
|
||||||
if (s->enabled && reload) {
|
if (s->enabled && reload) {
|
||||||
s->next_event = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
s->next_event = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||||
ptimer_reload(s);
|
ptimer_reload(s, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,10 @@
|
|||||||
*/
|
*/
|
||||||
#define PTIMER_POLICY_DEFAULT 0
|
#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 */
|
/* ptimer.c */
|
||||||
typedef struct ptimer_state ptimer_state;
|
typedef struct ptimer_state ptimer_state;
|
||||||
typedef void (*ptimer_cb)(void *opaque);
|
typedef void (*ptimer_cb)(void *opaque);
|
||||||
|
Loading…
Reference in New Issue
Block a user