Commit dff38e7b40b398dd713643d57d89f280c6d09ff1
1 parent
1f1af9fd
more precise RTC emulation (periodic timers + time updates)
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@688 c046a42c-6fe2-441c-8c8c-71466251a162
Showing
1 changed file
with
264 additions
and
34 deletions
hw/mc146818rtc.c
... | ... | @@ -63,11 +63,62 @@ |
63 | 63 | #define RTC_REG_C 12 |
64 | 64 | #define RTC_REG_D 13 |
65 | 65 | |
66 | -/* PC cmos mappings */ | |
67 | -#define REG_IBM_CENTURY_BYTE 0x32 | |
68 | -#define REG_IBM_PS2_CENTURY_BYTE 0x37 | |
66 | +#define REG_A_UIP 0x80 | |
69 | 67 | |
70 | -RTCState rtc_state; | |
68 | +#define REG_B_SET 0x80 | |
69 | +#define REG_B_PIE 0x40 | |
70 | +#define REG_B_AIE 0x20 | |
71 | +#define REG_B_UIE 0x10 | |
72 | + | |
73 | +struct RTCState { | |
74 | + uint8_t cmos_data[128]; | |
75 | + uint8_t cmos_index; | |
76 | + int current_time; /* in seconds */ | |
77 | + int irq; | |
78 | + uint8_t buf_data[10]; /* buffered data */ | |
79 | + /* periodic timer */ | |
80 | + QEMUTimer *periodic_timer; | |
81 | + int64_t next_periodic_time; | |
82 | + /* second update */ | |
83 | + int64_t next_second_time; | |
84 | + QEMUTimer *second_timer; | |
85 | + QEMUTimer *second_timer2; | |
86 | +}; | |
87 | + | |
88 | +static void rtc_set_time(RTCState *s); | |
89 | +static void rtc_set_date_buf(RTCState *s, const struct tm *tm); | |
90 | +static void rtc_copy_date(RTCState *s); | |
91 | + | |
92 | +static void rtc_timer_update(RTCState *s, int64_t current_time) | |
93 | +{ | |
94 | + int period_code, period; | |
95 | + int64_t cur_clock, next_irq_clock; | |
96 | + | |
97 | + period_code = s->cmos_data[RTC_REG_A] & 0x0f; | |
98 | + if (period_code != 0 && | |
99 | + (s->cmos_data[RTC_REG_B] & REG_B_PIE)) { | |
100 | + if (period_code <= 2) | |
101 | + period_code += 7; | |
102 | + /* period in 32 Khz cycles */ | |
103 | + period = 1 << (period_code - 1); | |
104 | + /* compute 32 khz clock */ | |
105 | + cur_clock = muldiv64(current_time, 32768, ticks_per_sec); | |
106 | + next_irq_clock = (cur_clock & ~(period - 1)) + period; | |
107 | + s->next_periodic_time = muldiv64(next_irq_clock, ticks_per_sec, 32768) + 1; | |
108 | + qemu_mod_timer(s->periodic_timer, s->next_periodic_time); | |
109 | + } else { | |
110 | + qemu_del_timer(s->periodic_timer); | |
111 | + } | |
112 | +} | |
113 | + | |
114 | +static void rtc_periodic_timer(void *opaque) | |
115 | +{ | |
116 | + RTCState *s = opaque; | |
117 | + | |
118 | + rtc_timer_update(s, s->next_periodic_time); | |
119 | + s->cmos_data[RTC_REG_C] |= 0xc0; | |
120 | + pic_set_irq(s->irq, 1); | |
121 | +} | |
71 | 122 | |
72 | 123 | static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) |
73 | 124 | { |
... | ... | @@ -80,7 +131,7 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) |
80 | 131 | printf("cmos: write index=0x%02x val=0x%02x\n", |
81 | 132 | s->cmos_index, data); |
82 | 133 | #endif |
83 | - switch(addr) { | |
134 | + switch(s->cmos_index) { | |
84 | 135 | case RTC_SECONDS_ALARM: |
85 | 136 | case RTC_MINUTES_ALARM: |
86 | 137 | case RTC_HOURS_ALARM: |
... | ... | @@ -95,10 +146,30 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) |
95 | 146 | case RTC_MONTH: |
96 | 147 | case RTC_YEAR: |
97 | 148 | s->cmos_data[s->cmos_index] = data; |
149 | + /* if in set mode, do not update the time */ | |
150 | + if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { | |
151 | + rtc_set_time(s); | |
152 | + } | |
98 | 153 | break; |
99 | 154 | case RTC_REG_A: |
155 | + /* UIP bit is read only */ | |
156 | + s->cmos_data[RTC_REG_A] = (data & ~REG_A_UIP) | | |
157 | + (s->cmos_data[RTC_REG_A] & REG_A_UIP); | |
158 | + rtc_timer_update(s, qemu_get_clock(vm_clock)); | |
159 | + break; | |
100 | 160 | case RTC_REG_B: |
101 | - s->cmos_data[s->cmos_index] = data; | |
161 | + if (data & REG_B_SET) { | |
162 | + /* set mode: reset UIP mode */ | |
163 | + s->cmos_data[RTC_REG_A] &= ~REG_A_UIP; | |
164 | + data &= ~REG_B_UIE; | |
165 | + } else { | |
166 | + /* if disabling set mode, update the time */ | |
167 | + if (s->cmos_data[RTC_REG_B] & REG_B_SET) { | |
168 | + rtc_set_time(s); | |
169 | + } | |
170 | + } | |
171 | + s->cmos_data[RTC_REG_B] = data; | |
172 | + rtc_timer_update(s, qemu_get_clock(vm_clock)); | |
102 | 173 | break; |
103 | 174 | case RTC_REG_C: |
104 | 175 | case RTC_REG_D: |
... | ... | @@ -111,27 +182,104 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) |
111 | 182 | } |
112 | 183 | } |
113 | 184 | |
114 | -static inline int to_bcd(int a) | |
185 | +static inline int to_bcd(RTCState *s, int a) | |
115 | 186 | { |
116 | - return ((a / 10) << 4) | (a % 10); | |
187 | + if (s->cmos_data[RTC_REG_B] & 0x04) { | |
188 | + return a; | |
189 | + } else { | |
190 | + return ((a / 10) << 4) | (a % 10); | |
191 | + } | |
117 | 192 | } |
118 | 193 | |
119 | -static void cmos_update_time(RTCState *s) | |
194 | +static inline int from_bcd(RTCState *s, int a) | |
120 | 195 | { |
121 | - struct tm *tm; | |
196 | + if (s->cmos_data[RTC_REG_B] & 0x04) { | |
197 | + return a; | |
198 | + } else { | |
199 | + return ((a >> 4) * 10) + (a & 0x0f); | |
200 | + } | |
201 | +} | |
202 | + | |
203 | +static void rtc_set_time(RTCState *s) | |
204 | +{ | |
205 | + struct tm tm1, *tm = &tm1; | |
206 | + | |
207 | + tm->tm_sec = from_bcd(s, s->cmos_data[RTC_SECONDS]); | |
208 | + tm->tm_min = from_bcd(s, s->cmos_data[RTC_MINUTES]); | |
209 | + tm->tm_hour = from_bcd(s, s->cmos_data[RTC_HOURS]); | |
210 | + tm->tm_wday = from_bcd(s, s->cmos_data[RTC_DAY_OF_WEEK]); | |
211 | + tm->tm_mday = from_bcd(s, s->cmos_data[RTC_DAY_OF_MONTH]); | |
212 | + tm->tm_mon = from_bcd(s, s->cmos_data[RTC_MONTH]) - 1; | |
213 | + tm->tm_year = from_bcd(s, s->cmos_data[RTC_YEAR]) + 100; | |
214 | + | |
215 | + /* update internal state */ | |
216 | + s->buf_data[RTC_SECONDS] = s->cmos_data[RTC_SECONDS]; | |
217 | + s->buf_data[RTC_MINUTES] = s->cmos_data[RTC_MINUTES]; | |
218 | + s->buf_data[RTC_HOURS] = s->cmos_data[RTC_HOURS]; | |
219 | + s->buf_data[RTC_DAY_OF_WEEK] = s->cmos_data[RTC_DAY_OF_WEEK]; | |
220 | + s->buf_data[RTC_DAY_OF_MONTH] = s->cmos_data[RTC_DAY_OF_MONTH]; | |
221 | + s->buf_data[RTC_MONTH] = s->cmos_data[RTC_MONTH]; | |
222 | + s->buf_data[RTC_YEAR] = s->cmos_data[RTC_YEAR]; | |
223 | + s->current_time = mktime(tm); | |
224 | +} | |
225 | + | |
226 | +static void rtc_update_second(void *opaque) | |
227 | +{ | |
228 | + RTCState *s = opaque; | |
229 | + | |
230 | + /* if the oscillator is not in normal operation, we do not update */ | |
231 | + if ((s->cmos_data[RTC_REG_A] & 0x70) != 0x20) { | |
232 | + s->next_second_time += ticks_per_sec; | |
233 | + qemu_mod_timer(s->second_timer, s->next_second_time); | |
234 | + } else { | |
235 | + s->current_time++; | |
236 | + | |
237 | + if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { | |
238 | + /* update in progress bit */ | |
239 | + s->cmos_data[RTC_REG_A] |= REG_A_UIP; | |
240 | + } | |
241 | + qemu_mod_timer(s->second_timer2, | |
242 | + s->next_second_time + (ticks_per_sec * 99) / 100); | |
243 | + } | |
244 | +} | |
245 | + | |
246 | +static void rtc_update_second2(void *opaque) | |
247 | +{ | |
248 | + RTCState *s = opaque; | |
122 | 249 | time_t ti; |
123 | 250 | |
124 | - ti = time(NULL); | |
125 | - tm = gmtime(&ti); | |
126 | - s->cmos_data[RTC_SECONDS] = to_bcd(tm->tm_sec); | |
127 | - s->cmos_data[RTC_MINUTES] = to_bcd(tm->tm_min); | |
128 | - s->cmos_data[RTC_HOURS] = to_bcd(tm->tm_hour); | |
129 | - s->cmos_data[RTC_DAY_OF_WEEK] = to_bcd(tm->tm_wday); | |
130 | - s->cmos_data[RTC_DAY_OF_MONTH] = to_bcd(tm->tm_mday); | |
131 | - s->cmos_data[RTC_MONTH] = to_bcd(tm->tm_mon + 1); | |
132 | - s->cmos_data[RTC_YEAR] = to_bcd(tm->tm_year % 100); | |
133 | - s->cmos_data[REG_IBM_CENTURY_BYTE] = to_bcd((tm->tm_year / 100) + 19); | |
134 | - s->cmos_data[REG_IBM_PS2_CENTURY_BYTE] = s->cmos_data[REG_IBM_CENTURY_BYTE]; | |
251 | + ti = s->current_time; | |
252 | + rtc_set_date_buf(s, gmtime(&ti)); | |
253 | + | |
254 | + if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { | |
255 | + rtc_copy_date(s); | |
256 | + } | |
257 | + | |
258 | + /* check alarm */ | |
259 | + if (s->cmos_data[RTC_REG_B] & REG_B_AIE) { | |
260 | + if (((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0 || | |
261 | + s->cmos_data[RTC_SECONDS_ALARM] == s->buf_data[RTC_SECONDS]) && | |
262 | + ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0 || | |
263 | + s->cmos_data[RTC_MINUTES_ALARM] == s->buf_data[RTC_MINUTES]) && | |
264 | + ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0 || | |
265 | + s->cmos_data[RTC_HOURS_ALARM] == s->buf_data[RTC_HOURS])) { | |
266 | + | |
267 | + s->cmos_data[RTC_REG_C] |= 0xa0; | |
268 | + pic_set_irq(s->irq, 1); | |
269 | + } | |
270 | + } | |
271 | + | |
272 | + /* update ended interrupt */ | |
273 | + if (s->cmos_data[RTC_REG_B] & REG_B_UIE) { | |
274 | + s->cmos_data[RTC_REG_C] |= 0x90; | |
275 | + pic_set_irq(s->irq, 1); | |
276 | + } | |
277 | + | |
278 | + /* clear update in progress bit */ | |
279 | + s->cmos_data[RTC_REG_A] &= ~REG_A_UIP; | |
280 | + | |
281 | + s->next_second_time += ticks_per_sec; | |
282 | + qemu_mod_timer(s->second_timer, s->next_second_time); | |
135 | 283 | } |
136 | 284 | |
137 | 285 | static uint32_t cmos_ioport_read(void *opaque, uint32_t addr) |
... | ... | @@ -149,16 +297,10 @@ static uint32_t cmos_ioport_read(void *opaque, uint32_t addr) |
149 | 297 | case RTC_DAY_OF_MONTH: |
150 | 298 | case RTC_MONTH: |
151 | 299 | case RTC_YEAR: |
152 | - case REG_IBM_CENTURY_BYTE: | |
153 | - case REG_IBM_PS2_CENTURY_BYTE: | |
154 | - cmos_update_time(s); | |
155 | 300 | ret = s->cmos_data[s->cmos_index]; |
156 | 301 | break; |
157 | 302 | case RTC_REG_A: |
158 | 303 | ret = s->cmos_data[s->cmos_index]; |
159 | - /* toggle update-in-progress bit for Linux (same hack as | |
160 | - plex86) */ | |
161 | - s->cmos_data[RTC_REG_A] ^= 0x80; | |
162 | 304 | break; |
163 | 305 | case RTC_REG_C: |
164 | 306 | ret = s->cmos_data[s->cmos_index]; |
... | ... | @@ -177,19 +319,94 @@ static uint32_t cmos_ioport_read(void *opaque, uint32_t addr) |
177 | 319 | } |
178 | 320 | } |
179 | 321 | |
180 | -void rtc_timer(void) | |
322 | +static void rtc_set_date_buf(RTCState *s, const struct tm *tm) | |
181 | 323 | { |
182 | - RTCState *s = &rtc_state; | |
183 | - if (s->cmos_data[RTC_REG_B] & 0x50) { | |
184 | - pic_set_irq(s->irq, 1); | |
324 | + s->buf_data[RTC_SECONDS] = to_bcd(s, tm->tm_sec); | |
325 | + s->buf_data[RTC_MINUTES] = to_bcd(s, tm->tm_min); | |
326 | + if (s->cmos_data[RTC_REG_B] & 0x02) { | |
327 | + /* 24 hour format */ | |
328 | + s->buf_data[RTC_HOURS] = to_bcd(s, tm->tm_hour); | |
329 | + } else { | |
330 | + /* 12 hour format */ | |
331 | + s->buf_data[RTC_HOURS] = to_bcd(s, tm->tm_hour % 12); | |
332 | + if (tm->tm_hour >= 12) | |
333 | + s->buf_data[RTC_HOURS] |= 0x80; | |
185 | 334 | } |
335 | + s->buf_data[RTC_DAY_OF_WEEK] = to_bcd(s, tm->tm_wday); | |
336 | + s->buf_data[RTC_DAY_OF_MONTH] = to_bcd(s, tm->tm_mday); | |
337 | + s->buf_data[RTC_MONTH] = to_bcd(s, tm->tm_mon + 1); | |
338 | + s->buf_data[RTC_YEAR] = to_bcd(s, tm->tm_year % 100); | |
339 | +} | |
340 | + | |
341 | +static void rtc_copy_date(RTCState *s) | |
342 | +{ | |
343 | + s->cmos_data[RTC_SECONDS] = s->buf_data[RTC_SECONDS]; | |
344 | + s->cmos_data[RTC_MINUTES] = s->buf_data[RTC_MINUTES]; | |
345 | + s->cmos_data[RTC_HOURS] = s->buf_data[RTC_HOURS]; | |
346 | + s->cmos_data[RTC_DAY_OF_WEEK] = s->buf_data[RTC_DAY_OF_WEEK]; | |
347 | + s->cmos_data[RTC_DAY_OF_MONTH] = s->buf_data[RTC_DAY_OF_MONTH]; | |
348 | + s->cmos_data[RTC_MONTH] = s->buf_data[RTC_MONTH]; | |
349 | + s->cmos_data[RTC_YEAR] = s->buf_data[RTC_YEAR]; | |
350 | +} | |
351 | + | |
352 | +void rtc_set_memory(RTCState *s, int addr, int val) | |
353 | +{ | |
354 | + if (addr >= 0 && addr <= 127) | |
355 | + s->cmos_data[addr] = val; | |
356 | +} | |
357 | + | |
358 | +void rtc_set_date(RTCState *s, const struct tm *tm) | |
359 | +{ | |
360 | + s->current_time = mktime((struct tm *)tm); | |
361 | + rtc_set_date_buf(s, tm); | |
362 | + rtc_copy_date(s); | |
363 | +} | |
364 | + | |
365 | +static void rtc_save(QEMUFile *f, void *opaque) | |
366 | +{ | |
367 | + RTCState *s = opaque; | |
368 | + | |
369 | + qemu_put_buffer(f, s->cmos_data, 128); | |
370 | + qemu_put_8s(f, &s->cmos_index); | |
371 | + qemu_put_be32s(f, &s->current_time); | |
372 | + qemu_put_buffer(f, s->buf_data, 10); | |
373 | + | |
374 | + qemu_put_timer(f, s->periodic_timer); | |
375 | + qemu_put_be64s(f, &s->next_periodic_time); | |
376 | + | |
377 | + qemu_put_be64s(f, &s->next_second_time); | |
378 | + qemu_put_timer(f, s->second_timer); | |
379 | + qemu_put_timer(f, s->second_timer2); | |
186 | 380 | } |
187 | 381 | |
188 | -void rtc_init(int base, int irq) | |
382 | +static int rtc_load(QEMUFile *f, void *opaque, int version_id) | |
189 | 383 | { |
190 | - RTCState *s = &rtc_state; | |
384 | + RTCState *s = opaque; | |
385 | + | |
386 | + if (version_id != 1) | |
387 | + return -EINVAL; | |
191 | 388 | |
192 | - cmos_update_time(s); | |
389 | + qemu_get_buffer(f, s->cmos_data, 128); | |
390 | + qemu_get_8s(f, &s->cmos_index); | |
391 | + qemu_get_be32s(f, &s->current_time); | |
392 | + qemu_get_buffer(f, s->buf_data, 10); | |
393 | + | |
394 | + qemu_get_timer(f, s->periodic_timer); | |
395 | + qemu_get_be64s(f, &s->next_periodic_time); | |
396 | + | |
397 | + qemu_get_be64s(f, &s->next_second_time); | |
398 | + qemu_get_timer(f, s->second_timer); | |
399 | + qemu_get_timer(f, s->second_timer2); | |
400 | + return 0; | |
401 | +} | |
402 | + | |
403 | +RTCState *rtc_init(int base, int irq) | |
404 | +{ | |
405 | + RTCState *s; | |
406 | + | |
407 | + s = qemu_mallocz(sizeof(RTCState)); | |
408 | + if (!s) | |
409 | + return NULL; | |
193 | 410 | |
194 | 411 | s->irq = irq; |
195 | 412 | s->cmos_data[RTC_REG_A] = 0x26; |
... | ... | @@ -197,7 +414,20 @@ void rtc_init(int base, int irq) |
197 | 414 | s->cmos_data[RTC_REG_C] = 0x00; |
198 | 415 | s->cmos_data[RTC_REG_D] = 0x80; |
199 | 416 | |
417 | + s->periodic_timer = qemu_new_timer(vm_clock, | |
418 | + rtc_periodic_timer, s); | |
419 | + s->second_timer = qemu_new_timer(vm_clock, | |
420 | + rtc_update_second, s); | |
421 | + s->second_timer2 = qemu_new_timer(vm_clock, | |
422 | + rtc_update_second2, s); | |
423 | + | |
424 | + s->next_second_time = qemu_get_clock(vm_clock) + (ticks_per_sec * 99) / 100; | |
425 | + qemu_mod_timer(s->second_timer2, s->next_second_time); | |
426 | + | |
200 | 427 | register_ioport_write(base, 2, 1, cmos_ioport_write, s); |
201 | 428 | register_ioport_read(base, 2, 1, cmos_ioport_read, s); |
429 | + | |
430 | + register_savevm("mc146818rtc", base, 1, rtc_save, rtc_load, s); | |
431 | + return s; | |
202 | 432 | } |
203 | 433 | ... | ... |