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; | ... | ... |