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