diff options
author | Daniel Baumann <mail@daniel-baumann.ch> | 2025-06-06 10:05:23 +0000 |
---|---|---|
committer | Daniel Baumann <mail@daniel-baumann.ch> | 2025-06-06 10:05:23 +0000 |
commit | 755cc582a2473d06f3a2131d506d0311cc70e9f9 (patch) | |
tree | 3efb1ddb8d57bbb4539ac0d229b384871c57820f /hw/core/ptimer.c | |
parent | Initial commit. (diff) | |
download | qemu-upstream.tar.xz qemu-upstream.zip |
Adding upstream version 1:7.2+dfsg.upstream/1%7.2+dfsgupstream
Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
Diffstat (limited to 'hw/core/ptimer.c')
-rw-r--r-- | hw/core/ptimer.c | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/hw/core/ptimer.c b/hw/core/ptimer.c new file mode 100644 index 00000000..eb5ba1af --- /dev/null +++ b/hw/core/ptimer.c @@ -0,0 +1,485 @@ +/* + * General purpose implementation of a simple periodic countdown timer. + * + * Copyright (c) 2007 CodeSourcery. + * + * This code is licensed under the GNU LGPL. + */ + +#include "qemu/osdep.h" +#include "hw/ptimer.h" +#include "migration/vmstate.h" +#include "qemu/host-utils.h" +#include "sysemu/replay.h" +#include "sysemu/cpu-timers.h" +#include "sysemu/qtest.h" +#include "block/aio.h" +#include "hw/clock.h" + +#define DELTA_ADJUST 1 +#define DELTA_NO_ADJUST -1 + +struct ptimer_state +{ + uint8_t enabled; /* 0 = disabled, 1 = periodic, 2 = oneshot. */ + uint64_t limit; + uint64_t delta; + uint32_t period_frac; + int64_t period; + int64_t last_event; + int64_t next_event; + uint8_t policy_mask; + QEMUTimer *timer; + ptimer_cb callback; + void *callback_opaque; + /* + * These track whether we're in a transaction block, and if we + * need to do a timer reload when the block finishes. They don't + * need to be migrated because migration can never happen in the + * middle of a transaction block. + */ + bool in_transaction; + bool need_reload; +}; + +/* Use a bottom-half routine to avoid reentrancy issues. */ +static void ptimer_trigger(ptimer_state *s) +{ + s->callback(s->callback_opaque); +} + +static void ptimer_reload(ptimer_state *s, int delta_adjust) +{ + uint32_t period_frac; + uint64_t period; + uint64_t delta; + bool suppress_trigger = false; + + /* + * Note that if delta_adjust is 0 then we must be here because of + * a count register write or timer start, not because of timer expiry. + * In that case the policy might require us to suppress the timer trigger + * that we would otherwise generate for a zero delta. + */ + if (delta_adjust == 0 && + (s->policy_mask & PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT)) { + suppress_trigger = true; + } + if (s->delta == 0 && !(s->policy_mask & PTIMER_POLICY_NO_IMMEDIATE_TRIGGER) + && !suppress_trigger) { + ptimer_trigger(s); + } + + /* + * Note that ptimer_trigger() might call the device callback function, + * which can then modify timer state, so we must not cache any fields + * from ptimer_state until after we have called it. + */ + delta = s->delta; + period = s->period; + period_frac = s->period_frac; + + if (delta == 0 && !(s->policy_mask & PTIMER_POLICY_NO_IMMEDIATE_RELOAD)) { + delta = s->delta = s->limit; + } + + if (s->period == 0) { + if (!qtest_enabled()) { + fprintf(stderr, "Timer with period zero, disabling\n"); + } + timer_del(s->timer); + s->enabled = 0; + return; + } + + if (s->policy_mask & PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD) { + if (delta_adjust != DELTA_NO_ADJUST) { + delta += delta_adjust; + } + } + + if (delta == 0 && (s->policy_mask & PTIMER_POLICY_CONTINUOUS_TRIGGER)) { + if (s->enabled == 1 && s->limit == 0) { + delta = 1; + } + } + + if (delta == 0 && (s->policy_mask & PTIMER_POLICY_NO_IMMEDIATE_TRIGGER)) { + if (delta_adjust != DELTA_NO_ADJUST) { + delta = 1; + } + } + + if (delta == 0 && (s->policy_mask & PTIMER_POLICY_NO_IMMEDIATE_RELOAD)) { + if (s->enabled == 1 && s->limit != 0) { + delta = 1; + } + } + + if (delta == 0) { + if (s->enabled == 0) { + /* trigger callback disabled the timer already */ + return; + } + if (!qtest_enabled()) { + fprintf(stderr, "Timer with delta zero, disabling\n"); + } + timer_del(s->timer); + s->enabled = 0; + return; + } + + /* + * Artificially limit timeout rate to something + * achievable under QEMU. Otherwise, QEMU spends all + * its time generating timer interrupts, and there + * is no forward progress. + * About ten microseconds is the fastest that really works + * on the current generation of host machines. + */ + + if (s->enabled == 1 && (delta * period < 10000) && + !icount_enabled() && !qtest_enabled()) { + period = 10000 / delta; + period_frac = 0; + } + + s->last_event = s->next_event; + s->next_event = s->last_event + delta * period; + if (period_frac) { + s->next_event += ((int64_t)period_frac * delta) >> 32; + } + timer_mod(s->timer, s->next_event); +} + +static void ptimer_tick(void *opaque) +{ + ptimer_state *s = (ptimer_state *)opaque; + bool trigger = true; + + /* + * We perform all the tick actions within a begin/commit block + * because the callback function that ptimer_trigger() calls + * might make calls into the ptimer APIs that provoke another + * trigger, and we want that to cause the callback function + * to be called iteratively, not recursively. + */ + ptimer_transaction_begin(s); + + if (s->enabled == 2) { + s->delta = 0; + s->enabled = 0; + } else { + int delta_adjust = DELTA_ADJUST; + + if (s->delta == 0 || s->limit == 0) { + /* If a "continuous trigger" policy is not used and limit == 0, + we should error out. delta == 0 means that this tick is + caused by a "no immediate reload" policy, so it shouldn't + be adjusted. */ + delta_adjust = DELTA_NO_ADJUST; + } + + if (!(s->policy_mask & PTIMER_POLICY_NO_IMMEDIATE_TRIGGER)) { + /* Avoid re-trigger on deferred reload if "no immediate trigger" + policy isn't used. */ + trigger = (delta_adjust == DELTA_ADJUST); + } + + s->delta = s->limit; + + ptimer_reload(s, delta_adjust); + } + + if (trigger) { + ptimer_trigger(s); + } + + ptimer_transaction_commit(s); +} + +uint64_t ptimer_get_count(ptimer_state *s) +{ + uint64_t counter; + + if (s->enabled && s->delta != 0) { + 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); + + /* Figure out the current counter value. */ + if (expired) { + /* Prevent timer underflowing if it should already have + triggered. */ + counter = 0; + } else { + uint64_t rem; + uint64_t div; + int clz1, clz2; + int shift; + uint32_t period_frac = s->period_frac; + uint64_t period = s->period; + + if (!oneshot && (s->delta * period < 10000) && + !icount_enabled() && !qtest_enabled()) { + period = 10000 / s->delta; + period_frac = 0; + } + + /* 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 + backwards. + */ + + rem = next - now; + div = period; + + clz1 = clz64(rem); + clz2 = clz64(div); + shift = clz1 < clz2 ? clz1 : clz2; + + rem <<= shift; + div <<= shift; + if (shift >= 32) { + div |= ((uint64_t)period_frac << (shift - 32)); + } else { + if (shift != 0) + div |= (period_frac >> (32 - shift)); + /* Look at remaining bits of period_frac and round div up if + necessary. */ + if ((uint32_t)(period_frac << shift)) + 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; + } + } + } + } + + if (s->policy_mask & PTIMER_POLICY_NO_COUNTER_ROUND_DOWN) { + /* If now == last then delta == limit, i.e. the counter already + represents the correct value. It would be rounded down a 1ns + later. */ + if (now != last) { + counter += 1; + } + } + } else { + counter = s->delta; + } + return counter; +} + +void ptimer_set_count(ptimer_state *s, uint64_t count) +{ + assert(s->in_transaction); + s->delta = count; + if (s->enabled) { + s->need_reload = true; + } +} + +void ptimer_run(ptimer_state *s, int oneshot) +{ + bool was_disabled = !s->enabled; + + assert(s->in_transaction); + + if (was_disabled && s->period == 0) { + if (!qtest_enabled()) { + fprintf(stderr, "Timer with period zero, disabling\n"); + } + return; + } + s->enabled = oneshot ? 2 : 1; + if (was_disabled) { + s->need_reload = true; + } +} + +/* Pause a timer. Note that this may cause it to "lose" time, even if it + is immediately restarted. */ +void ptimer_stop(ptimer_state *s) +{ + assert(s->in_transaction); + + if (!s->enabled) + return; + + s->delta = ptimer_get_count(s); + timer_del(s->timer); + s->enabled = 0; + s->need_reload = false; +} + +/* Set counter increment interval in nanoseconds. */ +void ptimer_set_period(ptimer_state *s, int64_t period) +{ + assert(s->in_transaction); + s->delta = ptimer_get_count(s); + s->period = period; + s->period_frac = 0; + if (s->enabled) { + s->need_reload = true; + } +} + +/* Set counter increment interval from a Clock */ +void ptimer_set_period_from_clock(ptimer_state *s, const Clock *clk, + unsigned int divisor) +{ + /* + * The raw clock period is a 64-bit value in units of 2^-32 ns; + * put another way it's a 32.32 fixed-point ns value. Our internal + * representation of the period is 64.32 fixed point ns, so + * the conversion is simple. + */ + uint64_t raw_period = clock_get(clk); + uint64_t period_frac; + + assert(s->in_transaction); + s->delta = ptimer_get_count(s); + s->period = extract64(raw_period, 32, 32); + period_frac = extract64(raw_period, 0, 32); + /* + * divisor specifies a possible frequency divisor between the + * clock and the timer, so it is a multiplier on the period. + * We do the multiply after splitting the raw period out into + * period and frac to avoid having to do a 32*64->96 multiply. + */ + s->period *= divisor; + period_frac *= divisor; + s->period += extract64(period_frac, 32, 32); + s->period_frac = (uint32_t)period_frac; + + if (s->enabled) { + s->need_reload = true; + } +} + +/* Set counter frequency in Hz. */ +void ptimer_set_freq(ptimer_state *s, uint32_t freq) +{ + assert(s->in_transaction); + s->delta = ptimer_get_count(s); + s->period = 1000000000ll / freq; + s->period_frac = (1000000000ll << 32) / freq; + if (s->enabled) { + s->need_reload = true; + } +} + +/* Set the initial countdown value. If reload is nonzero then also set + count = limit. */ +void ptimer_set_limit(ptimer_state *s, uint64_t limit, int reload) +{ + assert(s->in_transaction); + s->limit = limit; + if (reload) + s->delta = limit; + if (s->enabled && reload) { + s->need_reload = true; + } +} + +uint64_t ptimer_get_limit(ptimer_state *s) +{ + return s->limit; +} + +void ptimer_transaction_begin(ptimer_state *s) +{ + assert(!s->in_transaction); + s->in_transaction = true; + s->need_reload = false; +} + +void ptimer_transaction_commit(ptimer_state *s) +{ + assert(s->in_transaction); + /* + * We must loop here because ptimer_reload() can call the callback + * function, which might then update ptimer state in a way that + * means we need to do another reload and possibly another callback. + * A disabled timer never needs reloading (and if we don't check + * this then we loop forever if ptimer_reload() disables the timer). + */ + while (s->need_reload && s->enabled) { + s->need_reload = false; + s->next_event = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + ptimer_reload(s, 0); + } + /* Now we've finished reload we can leave the transaction block. */ + s->in_transaction = false; +} + +const VMStateDescription vmstate_ptimer = { + .name = "ptimer", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(enabled, ptimer_state), + VMSTATE_UINT64(limit, ptimer_state), + VMSTATE_UINT64(delta, ptimer_state), + VMSTATE_UINT32(period_frac, ptimer_state), + VMSTATE_INT64(period, ptimer_state), + VMSTATE_INT64(last_event, ptimer_state), + VMSTATE_INT64(next_event, ptimer_state), + VMSTATE_TIMER_PTR(timer, ptimer_state), + VMSTATE_END_OF_LIST() + } +}; + +ptimer_state *ptimer_init(ptimer_cb callback, void *callback_opaque, + uint8_t policy_mask) +{ + ptimer_state *s; + + /* The callback function is mandatory. */ + assert(callback); + + s = g_new0(ptimer_state, 1); + s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, ptimer_tick, s); + s->policy_mask = policy_mask; + s->callback = callback; + s->callback_opaque = callback_opaque; + + /* + * These two policies are incompatible -- trigger-on-decrement implies + * a timer trigger when the count becomes 0, but no-immediate-trigger + * implies a trigger when the count stops being 0. + */ + assert(!((policy_mask & PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT) && + (policy_mask & PTIMER_POLICY_NO_IMMEDIATE_TRIGGER))); + return s; +} + +void ptimer_free(ptimer_state *s) +{ + timer_free(s->timer); + g_free(s); +} |