ths | 5fafdf2 | 2007-09-16 21:08:06 +0000 | [diff] [blame] | 1 | /* |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 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 | */ |
pbrook | 87ecb68 | 2007-11-17 17:14:51 +0000 | [diff] [blame] | 8 | #include "hw.h" |
| 9 | #include "qemu-timer.h" |
pbrook | d0a981b | 2009-03-31 14:34:24 +0000 | [diff] [blame] | 10 | #include "host-utils.h" |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 11 | |
| 12 | struct ptimer_state |
| 13 | { |
Juan Quintela | 852f771 | 2010-12-01 23:51:14 +0100 | [diff] [blame] | 14 | uint8_t enabled; /* 0 = disabled, 1 = periodic, 2 = oneshot. */ |
blueswir1 | 8d05ea8 | 2007-05-24 19:48:41 +0000 | [diff] [blame] | 15 | uint64_t limit; |
| 16 | uint64_t delta; |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 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. */ |
| 26 | static void ptimer_trigger(ptimer_state *s) |
| 27 | { |
| 28 | if (s->bh) { |
| 29 | qemu_bh_schedule(s->bh); |
| 30 | } |
| 31 | } |
| 32 | |
| 33 | static 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 | |
| 53 | static 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 | |
blueswir1 | 8d05ea8 | 2007-05-24 19:48:41 +0000 | [diff] [blame] | 65 | uint64_t ptimer_get_count(ptimer_state *s) |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 66 | { |
| 67 | int64_t now; |
blueswir1 | 8d05ea8 | 2007-05-24 19:48:41 +0000 | [diff] [blame] | 68 | uint64_t counter; |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 69 | |
| 70 | if (s->enabled) { |
Paolo Bonzini | 7447545 | 2011-03-11 16:47:48 +0100 | [diff] [blame] | 71 | now = qemu_get_clock_ns(vm_clock); |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 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 { |
blueswir1 | 8d05ea8 | 2007-05-24 19:48:41 +0000 | [diff] [blame] | 79 | uint64_t rem; |
| 80 | uint64_t div; |
pbrook | d0a981b | 2009-03-31 14:34:24 +0000 | [diff] [blame] | 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 | */ |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 93 | |
| 94 | rem = s->next_event - now; |
| 95 | div = s->period; |
pbrook | d0a981b | 2009-03-31 14:34:24 +0000 | [diff] [blame] | 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 | } |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 113 | counter = rem / div; |
| 114 | } |
| 115 | } else { |
| 116 | counter = s->delta; |
| 117 | } |
| 118 | return counter; |
| 119 | } |
| 120 | |
blueswir1 | 8d05ea8 | 2007-05-24 19:48:41 +0000 | [diff] [blame] | 121 | void ptimer_set_count(ptimer_state *s, uint64_t count) |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 122 | { |
| 123 | s->delta = count; |
| 124 | if (s->enabled) { |
Paolo Bonzini | 7447545 | 2011-03-11 16:47:48 +0100 | [diff] [blame] | 125 | s->next_event = qemu_get_clock_ns(vm_clock); |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 126 | ptimer_reload(s); |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | void ptimer_run(ptimer_state *s, int oneshot) |
| 131 | { |
pbrook | 98fc561 | 2008-05-25 14:05:47 +0000 | [diff] [blame] | 132 | if (s->enabled) { |
| 133 | return; |
| 134 | } |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 135 | if (s->period == 0) { |
| 136 | fprintf(stderr, "Timer with period zero, disabling\n"); |
| 137 | return; |
| 138 | } |
| 139 | s->enabled = oneshot ? 2 : 1; |
Paolo Bonzini | 7447545 | 2011-03-11 16:47:48 +0100 | [diff] [blame] | 140 | s->next_event = qemu_get_clock_ns(vm_clock); |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 141 | ptimer_reload(s); |
| 142 | } |
| 143 | |
blueswir1 | 8d05ea8 | 2007-05-24 19:48:41 +0000 | [diff] [blame] | 144 | /* Pause a timer. Note that this may cause it to "lose" time, even if it |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 145 | is immediately restarted. */ |
| 146 | void 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. */ |
| 157 | void ptimer_set_period(ptimer_state *s, int64_t period) |
| 158 | { |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 159 | s->period = period; |
| 160 | s->period_frac = 0; |
blueswir1 | 8d05ea8 | 2007-05-24 19:48:41 +0000 | [diff] [blame] | 161 | if (s->enabled) { |
Paolo Bonzini | 7447545 | 2011-03-11 16:47:48 +0100 | [diff] [blame] | 162 | s->next_event = qemu_get_clock_ns(vm_clock); |
blueswir1 | 8d05ea8 | 2007-05-24 19:48:41 +0000 | [diff] [blame] | 163 | ptimer_reload(s); |
| 164 | } |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 165 | } |
| 166 | |
| 167 | /* Set counter frequency in Hz. */ |
| 168 | void ptimer_set_freq(ptimer_state *s, uint32_t freq) |
| 169 | { |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 170 | s->period = 1000000000ll / freq; |
| 171 | s->period_frac = (1000000000ll << 32) / freq; |
blueswir1 | 8d05ea8 | 2007-05-24 19:48:41 +0000 | [diff] [blame] | 172 | if (s->enabled) { |
Paolo Bonzini | 7447545 | 2011-03-11 16:47:48 +0100 | [diff] [blame] | 173 | s->next_event = qemu_get_clock_ns(vm_clock); |
blueswir1 | 8d05ea8 | 2007-05-24 19:48:41 +0000 | [diff] [blame] | 174 | ptimer_reload(s); |
| 175 | } |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 176 | } |
| 177 | |
| 178 | /* Set the initial countdown value. If reload is nonzero then also set |
| 179 | count = limit. */ |
blueswir1 | 8d05ea8 | 2007-05-24 19:48:41 +0000 | [diff] [blame] | 180 | void ptimer_set_limit(ptimer_state *s, uint64_t limit, int reload) |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 181 | { |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 182 | s->limit = limit; |
| 183 | if (reload) |
| 184 | s->delta = limit; |
pbrook | 62ea5b0 | 2007-06-03 10:44:47 +0000 | [diff] [blame] | 185 | if (s->enabled && reload) { |
Paolo Bonzini | 7447545 | 2011-03-11 16:47:48 +0100 | [diff] [blame] | 186 | s->next_event = qemu_get_clock_ns(vm_clock); |
blueswir1 | 8d05ea8 | 2007-05-24 19:48:41 +0000 | [diff] [blame] | 187 | ptimer_reload(s); |
| 188 | } |
| 189 | } |
| 190 | |
Juan Quintela | 852f771 | 2010-12-01 23:51:14 +0100 | [diff] [blame] | 191 | const VMStateDescription vmstate_ptimer = { |
Blue Swirl | 55a6e51f | 2009-08-31 19:30:15 +0000 | [diff] [blame] | 192 | .name = "ptimer", |
Juan Quintela | 852f771 | 2010-12-01 23:51:14 +0100 | [diff] [blame] | 193 | .version_id = 1, |
| 194 | .minimum_version_id = 1, |
| 195 | .minimum_version_id_old = 1, |
| 196 | .fields = (VMStateField[]) { |
| 197 | VMSTATE_UINT8(enabled, ptimer_state), |
| 198 | VMSTATE_UINT64(limit, ptimer_state), |
| 199 | VMSTATE_UINT64(delta, ptimer_state), |
| 200 | VMSTATE_UINT32(period_frac, ptimer_state), |
| 201 | VMSTATE_INT64(period, ptimer_state), |
| 202 | VMSTATE_INT64(last_event, ptimer_state), |
| 203 | VMSTATE_INT64(next_event, ptimer_state), |
| 204 | VMSTATE_TIMER(timer, ptimer_state), |
| 205 | VMSTATE_END_OF_LIST() |
| 206 | } |
Blue Swirl | 55a6e51f | 2009-08-31 19:30:15 +0000 | [diff] [blame] | 207 | }; |
| 208 | |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 209 | ptimer_state *ptimer_init(QEMUBH *bh) |
| 210 | { |
| 211 | ptimer_state *s; |
| 212 | |
| 213 | s = (ptimer_state *)qemu_mallocz(sizeof(ptimer_state)); |
| 214 | s->bh = bh; |
Paolo Bonzini | 7447545 | 2011-03-11 16:47:48 +0100 | [diff] [blame] | 215 | s->timer = qemu_new_timer_ns(vm_clock, ptimer_tick, s); |
pbrook | 423f074 | 2007-05-23 00:06:54 +0000 | [diff] [blame] | 216 | return s; |
| 217 | } |