ptimer.c 6.0 KB
Newer Older
1
/*
P
pbrook 已提交
2 3 4 5
 * General purpose implementation of a simple periodic countdown timer.
 *
 * Copyright (c) 2007 CodeSourcery.
 *
M
Matthew Fernandez 已提交
6
 * This code is licensed under the GNU LGPL.
P
pbrook 已提交
7
 */
8
#include "hw/hw.h"
9
#include "qemu/timer.h"
10
#include "hw/ptimer.h"
11
#include "qemu/host-utils.h"
P
pbrook 已提交
12 13 14

struct ptimer_state
{
J
Juan Quintela 已提交
15
    uint8_t enabled; /* 0 = disabled, 1 = periodic, 2 = oneshot.  */
16 17
    uint64_t limit;
    uint64_t delta;
P
pbrook 已提交
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
    uint32_t period_frac;
    int64_t period;
    int64_t last_event;
    int64_t next_event;
    QEMUBH *bh;
    QEMUTimer *timer;
};

/* Use a bottom-half routine to avoid reentrancy issues.  */
static void ptimer_trigger(ptimer_state *s)
{
    if (s->bh) {
        qemu_bh_schedule(s->bh);
    }
}

static void ptimer_reload(ptimer_state *s)
{
    if (s->delta == 0) {
        ptimer_trigger(s);
        s->delta = s->limit;
    }
    if (s->delta == 0 || s->period == 0) {
        fprintf(stderr, "Timer with period zero, disabling\n");
        s->enabled = 0;
        return;
    }

    s->last_event = s->next_event;
    s->next_event = s->last_event + s->delta * s->period;
    if (s->period_frac) {
        s->next_event += ((int64_t)s->period_frac * s->delta) >> 32;
    }
    qemu_mod_timer(s->timer, s->next_event);
}

static void ptimer_tick(void *opaque)
{
    ptimer_state *s = (ptimer_state *)opaque;
    ptimer_trigger(s);
    s->delta = 0;
    if (s->enabled == 2) {
        s->enabled = 0;
    } else {
        ptimer_reload(s);
    }
}

66
uint64_t ptimer_get_count(ptimer_state *s)
P
pbrook 已提交
67 68
{
    int64_t now;
69
    uint64_t counter;
P
pbrook 已提交
70 71

    if (s->enabled) {
72
        now = qemu_get_clock_ns(vm_clock);
P
pbrook 已提交
73 74 75 76 77 78 79
        /* Figure out the current counter value.  */
        if (now - s->next_event > 0
            || s->period == 0) {
            /* Prevent timer underflowing if it should already have
               triggered.  */
            counter = 0;
        } else {
80 81
            uint64_t rem;
            uint64_t div;
82 83 84 85 86 87 88 89 90 91 92 93
            int clz1, clz2;
            int shift;

            /* 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.
            */
P
pbrook 已提交
94 95 96

            rem = s->next_event - now;
            div = s->period;
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113

            clz1 = clz64(rem);
            clz2 = clz64(div);
            shift = clz1 < clz2 ? clz1 : clz2;

            rem <<= shift;
            div <<= shift;
            if (shift >= 32) {
                div |= ((uint64_t)s->period_frac << (shift - 32));
            } else {
                if (shift != 0)
                    div |= (s->period_frac >> (32 - shift));
                /* Look at remaining bits of period_frac and round div up if 
                   necessary.  */
                if ((uint32_t)(s->period_frac << shift))
                    div += 1;
            }
P
pbrook 已提交
114 115 116 117 118 119 120 121
            counter = rem / div;
        }
    } else {
        counter = s->delta;
    }
    return counter;
}

122
void ptimer_set_count(ptimer_state *s, uint64_t count)
P
pbrook 已提交
123 124 125
{
    s->delta = count;
    if (s->enabled) {
126
        s->next_event = qemu_get_clock_ns(vm_clock);
P
pbrook 已提交
127 128 129 130 131 132
        ptimer_reload(s);
    }
}

void ptimer_run(ptimer_state *s, int oneshot)
{
P
pbrook 已提交
133 134 135
    if (s->enabled) {
        return;
    }
P
pbrook 已提交
136 137 138 139 140
    if (s->period == 0) {
        fprintf(stderr, "Timer with period zero, disabling\n");
        return;
    }
    s->enabled = oneshot ? 2 : 1;
141
    s->next_event = qemu_get_clock_ns(vm_clock);
P
pbrook 已提交
142 143 144
    ptimer_reload(s);
}

145
/* Pause a timer.  Note that this may cause it to "lose" time, even if it
P
pbrook 已提交
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
   is immediately restarted.  */
void ptimer_stop(ptimer_state *s)
{
    if (!s->enabled)
        return;

    s->delta = ptimer_get_count(s);
    qemu_del_timer(s->timer);
    s->enabled = 0;
}

/* Set counter increment interval in nanoseconds.  */
void ptimer_set_period(ptimer_state *s, int64_t period)
{
    s->period = period;
    s->period_frac = 0;
162
    if (s->enabled) {
163
        s->next_event = qemu_get_clock_ns(vm_clock);
164 165
        ptimer_reload(s);
    }
P
pbrook 已提交
166 167 168 169 170 171 172
}

/* Set counter frequency in Hz.  */
void ptimer_set_freq(ptimer_state *s, uint32_t freq)
{
    s->period = 1000000000ll / freq;
    s->period_frac = (1000000000ll << 32) / freq;
173
    if (s->enabled) {
174
        s->next_event = qemu_get_clock_ns(vm_clock);
175 176
        ptimer_reload(s);
    }
P
pbrook 已提交
177 178 179 180
}

/* Set the initial countdown value.  If reload is nonzero then also set
   count = limit.  */
181
void ptimer_set_limit(ptimer_state *s, uint64_t limit, int reload)
P
pbrook 已提交
182
{
183 184 185 186 187 188 189 190 191 192 193 194 195
    /*
     * 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 (limit * s->period < 10000 && s->period) {
        limit = 10000 / s->period;
    }

P
pbrook 已提交
196 197 198
    s->limit = limit;
    if (reload)
        s->delta = limit;
199
    if (s->enabled && reload) {
200
        s->next_event = qemu_get_clock_ns(vm_clock);
201 202 203 204
        ptimer_reload(s);
    }
}

J
Juan Quintela 已提交
205
const VMStateDescription vmstate_ptimer = {
B
Blue Swirl 已提交
206
    .name = "ptimer",
J
Juan Quintela 已提交
207 208 209 210 211 212 213 214 215 216 217 218 219 220
    .version_id = 1,
    .minimum_version_id = 1,
    .minimum_version_id_old = 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(timer, ptimer_state),
        VMSTATE_END_OF_LIST()
    }
B
Blue Swirl 已提交
221 222
};

P
pbrook 已提交
223 224 225 226
ptimer_state *ptimer_init(QEMUBH *bh)
{
    ptimer_state *s;

227
    s = (ptimer_state *)g_malloc0(sizeof(ptimer_state));
P
pbrook 已提交
228
    s->bh = bh;
229
    s->timer = qemu_new_timer_ns(vm_clock, ptimer_tick, s);
P
pbrook 已提交
230 231
    return s;
}