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,11 +63,62 @@ | ||
| 63 | #define RTC_REG_C 12 | 63 | #define RTC_REG_C 12 |
| 64 | #define RTC_REG_D 13 | 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 | static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) | 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,7 +131,7 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) | ||
| 80 | printf("cmos: write index=0x%02x val=0x%02x\n", | 131 | printf("cmos: write index=0x%02x val=0x%02x\n", |
| 81 | s->cmos_index, data); | 132 | s->cmos_index, data); |
| 82 | #endif | 133 | #endif |
| 83 | - switch(addr) { | 134 | + switch(s->cmos_index) { |
| 84 | case RTC_SECONDS_ALARM: | 135 | case RTC_SECONDS_ALARM: |
| 85 | case RTC_MINUTES_ALARM: | 136 | case RTC_MINUTES_ALARM: |
| 86 | case RTC_HOURS_ALARM: | 137 | case RTC_HOURS_ALARM: |
| @@ -95,10 +146,30 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) | @@ -95,10 +146,30 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) | ||
| 95 | case RTC_MONTH: | 146 | case RTC_MONTH: |
| 96 | case RTC_YEAR: | 147 | case RTC_YEAR: |
| 97 | s->cmos_data[s->cmos_index] = data; | 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 | break; | 153 | break; |
| 99 | case RTC_REG_A: | 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 | case RTC_REG_B: | 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 | break; | 173 | break; |
| 103 | case RTC_REG_C: | 174 | case RTC_REG_C: |
| 104 | case RTC_REG_D: | 175 | case RTC_REG_D: |
| @@ -111,27 +182,104 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) | @@ -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 | time_t ti; | 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 | static uint32_t cmos_ioport_read(void *opaque, uint32_t addr) | 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,16 +297,10 @@ static uint32_t cmos_ioport_read(void *opaque, uint32_t addr) | ||
| 149 | case RTC_DAY_OF_MONTH: | 297 | case RTC_DAY_OF_MONTH: |
| 150 | case RTC_MONTH: | 298 | case RTC_MONTH: |
| 151 | case RTC_YEAR: | 299 | case RTC_YEAR: |
| 152 | - case REG_IBM_CENTURY_BYTE: | ||
| 153 | - case REG_IBM_PS2_CENTURY_BYTE: | ||
| 154 | - cmos_update_time(s); | ||
| 155 | ret = s->cmos_data[s->cmos_index]; | 300 | ret = s->cmos_data[s->cmos_index]; |
| 156 | break; | 301 | break; |
| 157 | case RTC_REG_A: | 302 | case RTC_REG_A: |
| 158 | ret = s->cmos_data[s->cmos_index]; | 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 | break; | 304 | break; |
| 163 | case RTC_REG_C: | 305 | case RTC_REG_C: |
| 164 | ret = s->cmos_data[s->cmos_index]; | 306 | ret = s->cmos_data[s->cmos_index]; |
| @@ -177,19 +319,94 @@ static uint32_t cmos_ioport_read(void *opaque, uint32_t addr) | @@ -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 | s->irq = irq; | 411 | s->irq = irq; |
| 195 | s->cmos_data[RTC_REG_A] = 0x26; | 412 | s->cmos_data[RTC_REG_A] = 0x26; |
| @@ -197,7 +414,20 @@ void rtc_init(int base, int irq) | @@ -197,7 +414,20 @@ void rtc_init(int base, int irq) | ||
| 197 | s->cmos_data[RTC_REG_C] = 0x00; | 414 | s->cmos_data[RTC_REG_C] = 0x00; |
| 198 | s->cmos_data[RTC_REG_D] = 0x80; | 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 | register_ioport_write(base, 2, 1, cmos_ioport_write, s); | 427 | register_ioport_write(base, 2, 1, cmos_ioport_write, s); |
| 201 | register_ioport_read(base, 2, 1, cmos_ioport_read, s); | 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 |