Commit 423f0742a8483430fe031861c316b09f1967319d
1 parent
0ff596d0
Add periodic timer implementation.
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2846 c046a42c-6fe2-441c-8c8c-71466251a162
Showing
3 changed files
with
213 additions
and
116 deletions
hw/arm_timer.c
| ... | ... | @@ -22,114 +22,24 @@ |
| 22 | 22 | #define TIMER_CTRL_ENABLE (1 << 7) |
| 23 | 23 | |
| 24 | 24 | typedef struct { |
| 25 | - int64_t next_time; | |
| 26 | - int64_t expires; | |
| 27 | - int64_t loaded; | |
| 28 | - QEMUTimer *timer; | |
| 25 | + ptimer_state *timer; | |
| 29 | 26 | uint32_t control; |
| 30 | - uint32_t count; | |
| 31 | 27 | uint32_t limit; |
| 32 | - int raw_freq; | |
| 33 | 28 | int freq; |
| 34 | 29 | int int_level; |
| 35 | 30 | qemu_irq irq; |
| 36 | 31 | } arm_timer_state; |
| 37 | 32 | |
| 38 | -/* Calculate the new expiry time of the given timer. */ | |
| 39 | - | |
| 40 | -static void arm_timer_reload(arm_timer_state *s) | |
| 41 | -{ | |
| 42 | - int64_t delay; | |
| 43 | - | |
| 44 | - s->loaded = s->expires; | |
| 45 | - delay = muldiv64(s->count, ticks_per_sec, s->freq); | |
| 46 | - if (delay == 0) | |
| 47 | - delay = 1; | |
| 48 | - s->expires += delay; | |
| 49 | -} | |
| 50 | - | |
| 51 | 33 | /* Check all active timers, and schedule the next timer interrupt. */ |
| 52 | 34 | |
| 53 | -static void arm_timer_update(arm_timer_state *s, int64_t now) | |
| 35 | +static void arm_timer_update(arm_timer_state *s) | |
| 54 | 36 | { |
| 55 | - int64_t next; | |
| 56 | - | |
| 57 | - /* Ignore disabled timers. */ | |
| 58 | - if ((s->control & TIMER_CTRL_ENABLE) == 0) | |
| 59 | - return; | |
| 60 | - /* Ignore expired one-shot timers. */ | |
| 61 | - if (s->count == 0 && (s->control & TIMER_CTRL_ONESHOT)) | |
| 62 | - return; | |
| 63 | - if (s->expires - now <= 0) { | |
| 64 | - /* Timer has expired. */ | |
| 65 | - s->int_level = 1; | |
| 66 | - if (s->control & TIMER_CTRL_ONESHOT) { | |
| 67 | - /* One-shot. */ | |
| 68 | - s->count = 0; | |
| 69 | - } else { | |
| 70 | - if ((s->control & TIMER_CTRL_PERIODIC) == 0) { | |
| 71 | - /* Free running. */ | |
| 72 | - if (s->control & TIMER_CTRL_32BIT) | |
| 73 | - s->count = 0xffffffff; | |
| 74 | - else | |
| 75 | - s->count = 0xffff; | |
| 76 | - } else { | |
| 77 | - /* Periodic. */ | |
| 78 | - s->count = s->limit; | |
| 79 | - } | |
| 80 | - } | |
| 81 | - } | |
| 82 | - while (s->expires - now <= 0) { | |
| 83 | - arm_timer_reload(s); | |
| 84 | - } | |
| 85 | 37 | /* Update interrupts. */ |
| 86 | 38 | if (s->int_level && (s->control & TIMER_CTRL_IE)) { |
| 87 | 39 | qemu_irq_raise(s->irq); |
| 88 | 40 | } else { |
| 89 | 41 | qemu_irq_lower(s->irq); |
| 90 | 42 | } |
| 91 | - | |
| 92 | - next = now; | |
| 93 | - if (next - s->expires < 0) | |
| 94 | - next = s->expires; | |
| 95 | - | |
| 96 | - /* Schedule the next timer interrupt. */ | |
| 97 | - if (next == now) { | |
| 98 | - qemu_del_timer(s->timer); | |
| 99 | - s->next_time = 0; | |
| 100 | - } else if (next != s->next_time) { | |
| 101 | - qemu_mod_timer(s->timer, next); | |
| 102 | - s->next_time = next; | |
| 103 | - } | |
| 104 | -} | |
| 105 | - | |
| 106 | -/* Return the current value of the timer. */ | |
| 107 | -static uint32_t arm_timer_getcount(arm_timer_state *s, int64_t now) | |
| 108 | -{ | |
| 109 | - int64_t left; | |
| 110 | - int64_t period; | |
| 111 | - | |
| 112 | - if (s->count == 0) | |
| 113 | - return 0; | |
| 114 | - if ((s->control & TIMER_CTRL_ENABLE) == 0) | |
| 115 | - return s->count; | |
| 116 | - left = s->expires - now; | |
| 117 | - period = s->expires - s->loaded; | |
| 118 | - /* If the timer should have expired then return 0. This can happen | |
| 119 | - when the host timer signal doesnt occur immediately. It's better to | |
| 120 | - have a timer appear to sit at zero for a while than have it wrap | |
| 121 | - around before the guest interrupt is raised. */ | |
| 122 | - /* ??? Could we trigger the interrupt here? */ | |
| 123 | - if (left < 0) | |
| 124 | - return 0; | |
| 125 | - /* We need to calculate count * elapsed / period without overfowing. | |
| 126 | - Scale both elapsed and period so they fit in a 32-bit int. */ | |
| 127 | - while (period != (int32_t)period) { | |
| 128 | - period >>= 1; | |
| 129 | - left >>= 1; | |
| 130 | - } | |
| 131 | - return ((uint64_t)s->count * (uint64_t)(int32_t)left) | |
| 132 | - / (int32_t)period; | |
| 133 | 43 | } |
| 134 | 44 | |
| 135 | 45 | uint32_t arm_timer_read(void *opaque, target_phys_addr_t offset) |
| ... | ... | @@ -141,7 +51,7 @@ uint32_t arm_timer_read(void *opaque, target_phys_addr_t offset) |
| 141 | 51 | case 6: /* TimerBGLoad */ |
| 142 | 52 | return s->limit; |
| 143 | 53 | case 1: /* TimerValue */ |
| 144 | - return arm_timer_getcount(s, qemu_get_clock(vm_clock)); | |
| 54 | + return ptimer_get_count(s->timer); | |
| 145 | 55 | case 2: /* TimerControl */ |
| 146 | 56 | return s->control; |
| 147 | 57 | case 4: /* TimerRIS */ |
| ... | ... | @@ -151,24 +61,40 @@ uint32_t arm_timer_read(void *opaque, target_phys_addr_t offset) |
| 151 | 61 | return 0; |
| 152 | 62 | return s->int_level; |
| 153 | 63 | default: |
| 154 | - cpu_abort (cpu_single_env, "arm_timer_read: Bad offset %x\n", offset); | |
| 64 | + cpu_abort (cpu_single_env, "arm_timer_read: Bad offset %x\n", | |
| 65 | + (int)offset); | |
| 155 | 66 | return 0; |
| 156 | 67 | } |
| 157 | 68 | } |
| 158 | 69 | |
| 70 | +/* Reset the timer limit after settings have changed. */ | |
| 71 | +static void arm_timer_recalibrate(arm_timer_state *s, int reload) | |
| 72 | +{ | |
| 73 | + uint32_t limit; | |
| 74 | + | |
| 75 | + if ((s->control & TIMER_CTRL_PERIODIC) == 0) { | |
| 76 | + /* Free running. */ | |
| 77 | + if (s->control & TIMER_CTRL_32BIT) | |
| 78 | + limit = 0xffffffff; | |
| 79 | + else | |
| 80 | + limit = 0xffff; | |
| 81 | + } else { | |
| 82 | + /* Periodic. */ | |
| 83 | + limit = s->limit; | |
| 84 | + } | |
| 85 | + ptimer_set_limit(s->timer, limit, reload); | |
| 86 | +} | |
| 87 | + | |
| 159 | 88 | static void arm_timer_write(void *opaque, target_phys_addr_t offset, |
| 160 | 89 | uint32_t value) |
| 161 | 90 | { |
| 162 | 91 | arm_timer_state *s = (arm_timer_state *)opaque; |
| 163 | - int64_t now; | |
| 92 | + int freq; | |
| 164 | 93 | |
| 165 | - now = qemu_get_clock(vm_clock); | |
| 166 | 94 | switch (offset >> 2) { |
| 167 | 95 | case 0: /* TimerLoad */ |
| 168 | 96 | s->limit = value; |
| 169 | - s->count = value; | |
| 170 | - s->expires = now; | |
| 171 | - arm_timer_reload(s); | |
| 97 | + arm_timer_recalibrate(s, 1); | |
| 172 | 98 | break; |
| 173 | 99 | case 1: /* TimerValue */ |
| 174 | 100 | /* ??? Linux seems to want to write to this readonly register. |
| ... | ... | @@ -179,19 +105,20 @@ static void arm_timer_write(void *opaque, target_phys_addr_t offset, |
| 179 | 105 | /* Pause the timer if it is running. This may cause some |
| 180 | 106 | inaccuracy dure to rounding, but avoids a whole lot of other |
| 181 | 107 | messyness. */ |
| 182 | - s->count = arm_timer_getcount(s, now); | |
| 108 | + ptimer_stop(s->timer); | |
| 183 | 109 | } |
| 184 | 110 | s->control = value; |
| 185 | - s->freq = s->raw_freq; | |
| 111 | + freq = s->freq; | |
| 186 | 112 | /* ??? Need to recalculate expiry time after changing divisor. */ |
| 187 | 113 | switch ((value >> 2) & 3) { |
| 188 | - case 1: s->freq >>= 4; break; | |
| 189 | - case 2: s->freq >>= 8; break; | |
| 114 | + case 1: freq >>= 4; break; | |
| 115 | + case 2: freq >>= 8; break; | |
| 190 | 116 | } |
| 117 | + arm_timer_recalibrate(s, 0); | |
| 118 | + ptimer_set_freq(s->timer, freq); | |
| 191 | 119 | if (s->control & TIMER_CTRL_ENABLE) { |
| 192 | 120 | /* Restart the timer if still enabled. */ |
| 193 | - s->expires = now; | |
| 194 | - arm_timer_reload(s); | |
| 121 | + ptimer_run(s->timer, (s->control & TIMER_CTRL_ONESHOT) != 0); | |
| 195 | 122 | } |
| 196 | 123 | break; |
| 197 | 124 | case 3: /* TimerIntClr */ |
| ... | ... | @@ -199,32 +126,34 @@ static void arm_timer_write(void *opaque, target_phys_addr_t offset, |
| 199 | 126 | break; |
| 200 | 127 | case 6: /* TimerBGLoad */ |
| 201 | 128 | s->limit = value; |
| 129 | + arm_timer_recalibrate(s, 0); | |
| 202 | 130 | break; |
| 203 | 131 | default: |
| 204 | - cpu_abort (cpu_single_env, "arm_timer_write: Bad offset %x\n", offset); | |
| 132 | + cpu_abort (cpu_single_env, "arm_timer_write: Bad offset %x\n", | |
| 133 | + (int)offset); | |
| 205 | 134 | } |
| 206 | - arm_timer_update(s, now); | |
| 135 | + arm_timer_update(s); | |
| 207 | 136 | } |
| 208 | 137 | |
| 209 | 138 | static void arm_timer_tick(void *opaque) |
| 210 | 139 | { |
| 211 | - int64_t now; | |
| 212 | - | |
| 213 | - now = qemu_get_clock(vm_clock); | |
| 214 | - arm_timer_update((arm_timer_state *)opaque, now); | |
| 140 | + arm_timer_state *s = (arm_timer_state *)opaque; | |
| 141 | + s->int_level = 1; | |
| 142 | + arm_timer_update(s); | |
| 215 | 143 | } |
| 216 | 144 | |
| 217 | 145 | static void *arm_timer_init(uint32_t freq, qemu_irq irq) |
| 218 | 146 | { |
| 219 | 147 | arm_timer_state *s; |
| 148 | + QEMUBH *bh; | |
| 220 | 149 | |
| 221 | 150 | s = (arm_timer_state *)qemu_mallocz(sizeof(arm_timer_state)); |
| 222 | 151 | s->irq = irq; |
| 223 | - s->raw_freq = s->freq = 1000000; | |
| 152 | + s->freq = freq; | |
| 224 | 153 | s->control = TIMER_CTRL_IE; |
| 225 | - s->count = 0xffffffff; | |
| 226 | 154 | |
| 227 | - s->timer = qemu_new_timer(vm_clock, arm_timer_tick, s); | |
| 155 | + bh = qemu_bh_new(arm_timer_tick, s); | |
| 156 | + s->timer = ptimer_init(bh); | |
| 228 | 157 | /* ??? Save/restore. */ |
| 229 | 158 | return s; |
| 230 | 159 | } | ... | ... |
hw/ptimer.c
0 โ 100644
| 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 "vl.h" | |
| 9 | + | |
| 10 | + | |
| 11 | +struct ptimer_state | |
| 12 | +{ | |
| 13 | + int enabled; /* 0 = disabled, 1 = periodic, 2 = oneshot. */ | |
| 14 | + uint32_t limit; | |
| 15 | + uint32_t delta; | |
| 16 | + uint32_t period_frac; | |
| 17 | + int64_t period; | |
| 18 | + int64_t last_event; | |
| 19 | + int64_t next_event; | |
| 20 | + QEMUBH *bh; | |
| 21 | + QEMUTimer *timer; | |
| 22 | +}; | |
| 23 | + | |
| 24 | +/* Use a bottom-half routine to avoid reentrancy issues. */ | |
| 25 | +static void ptimer_trigger(ptimer_state *s) | |
| 26 | +{ | |
| 27 | + if (s->bh) { | |
| 28 | + qemu_bh_schedule(s->bh); | |
| 29 | + } | |
| 30 | +} | |
| 31 | + | |
| 32 | +static void ptimer_reload(ptimer_state *s) | |
| 33 | +{ | |
| 34 | + if (s->delta == 0) { | |
| 35 | + ptimer_trigger(s); | |
| 36 | + s->delta = s->limit; | |
| 37 | + } | |
| 38 | + if (s->delta == 0 || s->period == 0) { | |
| 39 | + fprintf(stderr, "Timer with period zero, disabling\n"); | |
| 40 | + s->enabled = 0; | |
| 41 | + return; | |
| 42 | + } | |
| 43 | + | |
| 44 | + s->last_event = s->next_event; | |
| 45 | + s->next_event = s->last_event + s->delta * s->period; | |
| 46 | + if (s->period_frac) { | |
| 47 | + s->next_event += ((int64_t)s->period_frac * s->delta) >> 32; | |
| 48 | + } | |
| 49 | + qemu_mod_timer(s->timer, s->next_event); | |
| 50 | +} | |
| 51 | + | |
| 52 | +static void ptimer_tick(void *opaque) | |
| 53 | +{ | |
| 54 | + ptimer_state *s = (ptimer_state *)opaque; | |
| 55 | + ptimer_trigger(s); | |
| 56 | + s->delta = 0; | |
| 57 | + if (s->enabled == 2) { | |
| 58 | + s->enabled = 0; | |
| 59 | + } else { | |
| 60 | + ptimer_reload(s); | |
| 61 | + } | |
| 62 | +} | |
| 63 | + | |
| 64 | +uint32_t ptimer_get_count(ptimer_state *s) | |
| 65 | +{ | |
| 66 | + int64_t now; | |
| 67 | + uint32_t counter; | |
| 68 | + | |
| 69 | + if (s->enabled) { | |
| 70 | + now = qemu_get_clock(vm_clock); | |
| 71 | + /* Figure out the current counter value. */ | |
| 72 | + if (now - s->next_event > 0 | |
| 73 | + || s->period == 0) { | |
| 74 | + /* Prevent timer underflowing if it should already have | |
| 75 | + triggered. */ | |
| 76 | + counter = 0; | |
| 77 | + } else { | |
| 78 | + int64_t rem; | |
| 79 | + int64_t div; | |
| 80 | + | |
| 81 | + rem = s->next_event - now; | |
| 82 | + div = s->period; | |
| 83 | + counter = rem / div; | |
| 84 | + } | |
| 85 | + } else { | |
| 86 | + counter = s->delta; | |
| 87 | + } | |
| 88 | + return counter; | |
| 89 | +} | |
| 90 | + | |
| 91 | +void ptimer_set_count(ptimer_state *s, uint32_t count) | |
| 92 | +{ | |
| 93 | + s->delta = count; | |
| 94 | + if (s->enabled) { | |
| 95 | + s->next_event = qemu_get_clock(vm_clock); | |
| 96 | + ptimer_reload(s); | |
| 97 | + } | |
| 98 | +} | |
| 99 | + | |
| 100 | +void ptimer_run(ptimer_state *s, int oneshot) | |
| 101 | +{ | |
| 102 | + if (s->period == 0) { | |
| 103 | + fprintf(stderr, "Timer with period zero, disabling\n"); | |
| 104 | + return; | |
| 105 | + } | |
| 106 | + s->enabled = oneshot ? 2 : 1; | |
| 107 | + s->next_event = qemu_get_clock(vm_clock); | |
| 108 | + ptimer_reload(s); | |
| 109 | +} | |
| 110 | + | |
| 111 | +/* Pause a timer. Note that this may cause it to "loose" time, even if it | |
| 112 | + is immediately restarted. */ | |
| 113 | +void ptimer_stop(ptimer_state *s) | |
| 114 | +{ | |
| 115 | + if (!s->enabled) | |
| 116 | + return; | |
| 117 | + | |
| 118 | + s->delta = ptimer_get_count(s); | |
| 119 | + qemu_del_timer(s->timer); | |
| 120 | + s->enabled = 0; | |
| 121 | +} | |
| 122 | + | |
| 123 | +/* Set counter increment interval in nanoseconds. */ | |
| 124 | +void ptimer_set_period(ptimer_state *s, int64_t period) | |
| 125 | +{ | |
| 126 | + if (s->enabled) { | |
| 127 | + fprintf(stderr, "FIXME: ptimer_set_period with running timer"); | |
| 128 | + } | |
| 129 | + s->period = period; | |
| 130 | + s->period_frac = 0; | |
| 131 | +} | |
| 132 | + | |
| 133 | +/* Set counter frequency in Hz. */ | |
| 134 | +void ptimer_set_freq(ptimer_state *s, uint32_t freq) | |
| 135 | +{ | |
| 136 | + if (s->enabled) { | |
| 137 | + fprintf(stderr, "FIXME: ptimer_set_freq with running timer"); | |
| 138 | + } | |
| 139 | + s->period = 1000000000ll / freq; | |
| 140 | + s->period_frac = (1000000000ll << 32) / freq; | |
| 141 | +} | |
| 142 | + | |
| 143 | +/* Set the initial countdown value. If reload is nonzero then also set | |
| 144 | + count = limit. */ | |
| 145 | +void ptimer_set_limit(ptimer_state *s, uint32_t limit, int reload) | |
| 146 | +{ | |
| 147 | + if (s->enabled) { | |
| 148 | + fprintf(stderr, "FIXME: ptimer_set_limit with running timer"); | |
| 149 | + } | |
| 150 | + s->limit = limit; | |
| 151 | + if (reload) | |
| 152 | + s->delta = limit; | |
| 153 | +} | |
| 154 | + | |
| 155 | +ptimer_state *ptimer_init(QEMUBH *bh) | |
| 156 | +{ | |
| 157 | + ptimer_state *s; | |
| 158 | + | |
| 159 | + s = (ptimer_state *)qemu_mallocz(sizeof(ptimer_state)); | |
| 160 | + s->bh = bh; | |
| 161 | + s->timer = qemu_new_timer(vm_clock, ptimer_tick, s); | |
| 162 | + return s; | |
| 163 | +} | |
| 164 | + | ... | ... |
vl.c
| ... | ... | @@ -6322,7 +6322,6 @@ void main_loop_wait(int timeout) |
| 6322 | 6322 | } |
| 6323 | 6323 | #endif |
| 6324 | 6324 | qemu_aio_poll(); |
| 6325 | - qemu_bh_poll(); | |
| 6326 | 6325 | |
| 6327 | 6326 | if (vm_running) { |
| 6328 | 6327 | qemu_run_timers(&active_timers[QEMU_TIMER_VIRTUAL], |
| ... | ... | @@ -6330,10 +6329,15 @@ void main_loop_wait(int timeout) |
| 6330 | 6329 | /* run dma transfers, if any */ |
| 6331 | 6330 | DMA_run(); |
| 6332 | 6331 | } |
| 6333 | - | |
| 6332 | + | |
| 6334 | 6333 | /* real time timers */ |
| 6335 | 6334 | qemu_run_timers(&active_timers[QEMU_TIMER_REALTIME], |
| 6336 | 6335 | qemu_get_clock(rt_clock)); |
| 6336 | + | |
| 6337 | + /* Check bottom-halves last in case any of the earlier events triggered | |
| 6338 | + them. */ | |
| 6339 | + qemu_bh_poll(); | |
| 6340 | + | |
| 6337 | 6341 | } |
| 6338 | 6342 | |
| 6339 | 6343 | static CPUState *cur_cpu; | ... | ... |