qemu/hw/ptimer.c
<<
>>
Prefs
   1/*
   2 * General purpose implementation of a simple periodic countdown timer.
   3 *
   4 * Copyright (c) 2007 CodeSourcery.
   5 *
   6 * This code is licenced under the GNU LGPL.
   7 */
   8#include "hw.h"
   9#include "qemu-timer.h"
  10#include "host-utils.h"
  11
  12struct ptimer_state
  13{
  14    int enabled; /* 0 = disabled, 1 = periodic, 2 = oneshot.  */
  15    uint64_t limit;
  16    uint64_t delta;
  17    uint32_t period_frac;
  18    int64_t period;
  19    int64_t last_event;
  20    int64_t next_event;
  21    QEMUBH *bh;
  22    QEMUTimer *timer;
  23};
  24
  25/* Use a bottom-half routine to avoid reentrancy issues.  */
  26static void ptimer_trigger(ptimer_state *s)
  27{
  28    if (s->bh) {
  29        qemu_bh_schedule(s->bh);
  30    }
  31}
  32
  33static void ptimer_reload(ptimer_state *s)
  34{
  35    if (s->delta == 0) {
  36        ptimer_trigger(s);
  37        s->delta = s->limit;
  38    }
  39    if (s->delta == 0 || s->period == 0) {
  40        fprintf(stderr, "Timer with period zero, disabling\n");
  41        s->enabled = 0;
  42        return;
  43    }
  44
  45    s->last_event = s->next_event;
  46    s->next_event = s->last_event + s->delta * s->period;
  47    if (s->period_frac) {
  48        s->next_event += ((int64_t)s->period_frac * s->delta) >> 32;
  49    }
  50    qemu_mod_timer(s->timer, s->next_event);
  51}
  52
  53static void ptimer_tick(void *opaque)
  54{
  55    ptimer_state *s = (ptimer_state *)opaque;
  56    ptimer_trigger(s);
  57    s->delta = 0;
  58    if (s->enabled == 2) {
  59        s->enabled = 0;
  60    } else {
  61        ptimer_reload(s);
  62    }
  63}
  64
  65uint64_t ptimer_get_count(ptimer_state *s)
  66{
  67    int64_t now;
  68    uint64_t counter;
  69
  70    if (s->enabled) {
  71        now = qemu_get_clock(vm_clock);
  72        /* Figure out the current counter value.  */
  73        if (now - s->next_event > 0
  74            || s->period == 0) {
  75            /* Prevent timer underflowing if it should already have
  76               triggered.  */
  77            counter = 0;
  78        } else {
  79            uint64_t rem;
  80            uint64_t div;
  81            int clz1, clz2;
  82            int shift;
  83
  84            /* We need to divide time by period, where time is stored in
  85               rem (64-bit integer) and period is stored in period/period_frac
  86               (64.32 fixed point).
  87              
  88               Doing full precision division is hard, so scale values and
  89               do a 64-bit division.  The result should be rounded down,
  90               so that the rounding error never causes the timer to go
  91               backwards.
  92            */
  93
  94            rem = s->next_event - now;
  95            div = s->period;
  96
  97            clz1 = clz64(rem);
  98            clz2 = clz64(div);
  99            shift = clz1 < clz2 ? clz1 : clz2;
 100
 101            rem <<= shift;
 102            div <<= shift;
 103            if (shift >= 32) {
 104                div |= ((uint64_t)s->period_frac << (shift - 32));
 105            } else {
 106                if (shift != 0)
 107                    div |= (s->period_frac >> (32 - shift));
 108                /* Look at remaining bits of period_frac and round div up if 
 109                   necessary.  */
 110                if ((uint32_t)(s->period_frac << shift))
 111                    div += 1;
 112            }
 113            counter = rem / div;
 114        }
 115    } else {
 116        counter = s->delta;
 117    }
 118    return counter;
 119}
 120
 121void ptimer_set_count(ptimer_state *s, uint64_t count)
 122{
 123    s->delta = count;
 124    if (s->enabled) {
 125        s->next_event = qemu_get_clock(vm_clock);
 126        ptimer_reload(s);
 127    }
 128}
 129
 130void ptimer_run(ptimer_state *s, int oneshot)
 131{
 132    if (s->enabled) {
 133        return;
 134    }
 135    if (s->period == 0) {
 136        fprintf(stderr, "Timer with period zero, disabling\n");
 137        return;
 138    }
 139    s->enabled = oneshot ? 2 : 1;
 140    s->next_event = qemu_get_clock(vm_clock);
 141    ptimer_reload(s);
 142}
 143
 144/* Pause a timer.  Note that this may cause it to "lose" time, even if it
 145   is immediately restarted.  */
 146void ptimer_stop(ptimer_state *s)
 147{
 148    if (!s->enabled)
 149        return;
 150
 151    s->delta = ptimer_get_count(s);
 152    qemu_del_timer(s->timer);
 153    s->enabled = 0;
 154}
 155
 156/* Set counter increment interval in nanoseconds.  */
 157void ptimer_set_period(ptimer_state *s, int64_t period)
 158{
 159    s->period = period;
 160    s->period_frac = 0;
 161    if (s->enabled) {
 162        s->next_event = qemu_get_clock(vm_clock);
 163        ptimer_reload(s);
 164    }
 165}
 166
 167/* Set counter frequency in Hz.  */
 168void ptimer_set_freq(ptimer_state *s, uint32_t freq)
 169{
 170    s->period = 1000000000ll / freq;
 171    s->period_frac = (1000000000ll << 32) / freq;
 172    if (s->enabled) {
 173        s->next_event = qemu_get_clock(vm_clock);
 174        ptimer_reload(s);
 175    }
 176}
 177
 178/* Set the initial countdown value.  If reload is nonzero then also set
 179   count = limit.  */
 180void ptimer_set_limit(ptimer_state *s, uint64_t limit, int reload)
 181{
 182    s->limit = limit;
 183    if (reload)
 184        s->delta = limit;
 185    if (s->enabled && reload) {
 186        s->next_event = qemu_get_clock(vm_clock);
 187        ptimer_reload(s);
 188    }
 189}
 190
 191void qemu_put_ptimer(QEMUFile *f, ptimer_state *s)
 192{
 193    qemu_put_byte(f, s->enabled);
 194    qemu_put_be64s(f, &s->limit);
 195    qemu_put_be64s(f, &s->delta);
 196    qemu_put_be32s(f, &s->period_frac);
 197    qemu_put_sbe64s(f, &s->period);
 198    qemu_put_sbe64s(f, &s->last_event);
 199    qemu_put_sbe64s(f, &s->next_event);
 200    qemu_put_timer(f, s->timer);
 201}
 202
 203void qemu_get_ptimer(QEMUFile *f, ptimer_state *s)
 204{
 205    s->enabled = qemu_get_byte(f);
 206    qemu_get_be64s(f, &s->limit);
 207    qemu_get_be64s(f, &s->delta);
 208    qemu_get_be32s(f, &s->period_frac);
 209    qemu_get_sbe64s(f, &s->period);
 210    qemu_get_sbe64s(f, &s->last_event);
 211    qemu_get_sbe64s(f, &s->next_event);
 212    qemu_get_timer(f, s->timer);
 213}
 214
 215static int get_ptimer(QEMUFile *f, void *pv, size_t size)
 216{
 217    ptimer_state *v = pv;
 218
 219    qemu_get_ptimer(f, v);
 220    return 0;
 221}
 222
 223static void put_ptimer(QEMUFile *f, void *pv, size_t size)
 224{
 225    ptimer_state *v = pv;
 226
 227    qemu_put_ptimer(f, v);
 228}
 229
 230const VMStateInfo vmstate_info_ptimer = {
 231    .name = "ptimer",
 232    .get  = get_ptimer,
 233    .put  = put_ptimer,
 234};
 235
 236ptimer_state *ptimer_init(QEMUBH *bh)
 237{
 238    ptimer_state *s;
 239
 240    s = (ptimer_state *)qemu_mallocz(sizeof(ptimer_state));
 241    s->bh = bh;
 242    s->timer = qemu_new_timer(vm_clock, ptimer_tick, s);
 243    return s;
 244}
 245