diff options
-rw-r--r-- | hw/mc146818rtc.c | 298 |
1 files changed, 264 insertions, 34 deletions
diff --git a/hw/mc146818rtc.c b/hw/mc146818rtc.c index 7d94b25..4505fc9 100644 --- a/hw/mc146818rtc.c +++ b/hw/mc146818rtc.c @@ -63,11 +63,62 @@ #define RTC_REG_C 12 #define RTC_REG_D 13 -/* PC cmos mappings */ -#define REG_IBM_CENTURY_BYTE 0x32 -#define REG_IBM_PS2_CENTURY_BYTE 0x37 +#define REG_A_UIP 0x80 -RTCState rtc_state; +#define REG_B_SET 0x80 +#define REG_B_PIE 0x40 +#define REG_B_AIE 0x20 +#define REG_B_UIE 0x10 + +struct RTCState { + uint8_t cmos_data[128]; + uint8_t cmos_index; + int current_time; /* in seconds */ + int irq; + uint8_t buf_data[10]; /* buffered data */ + /* periodic timer */ + QEMUTimer *periodic_timer; + int64_t next_periodic_time; + /* second update */ + int64_t next_second_time; + QEMUTimer *second_timer; + QEMUTimer *second_timer2; +}; + +static void rtc_set_time(RTCState *s); +static void rtc_set_date_buf(RTCState *s, const struct tm *tm); +static void rtc_copy_date(RTCState *s); + +static void rtc_timer_update(RTCState *s, int64_t current_time) +{ + int period_code, period; + int64_t cur_clock, next_irq_clock; + + period_code = s->cmos_data[RTC_REG_A] & 0x0f; + if (period_code != 0 && + (s->cmos_data[RTC_REG_B] & REG_B_PIE)) { + if (period_code <= 2) + period_code += 7; + /* period in 32 Khz cycles */ + period = 1 << (period_code - 1); + /* compute 32 khz clock */ + cur_clock = muldiv64(current_time, 32768, ticks_per_sec); + next_irq_clock = (cur_clock & ~(period - 1)) + period; + s->next_periodic_time = muldiv64(next_irq_clock, ticks_per_sec, 32768) + 1; + qemu_mod_timer(s->periodic_timer, s->next_periodic_time); + } else { + qemu_del_timer(s->periodic_timer); + } +} + +static void rtc_periodic_timer(void *opaque) +{ + RTCState *s = opaque; + + rtc_timer_update(s, s->next_periodic_time); + s->cmos_data[RTC_REG_C] |= 0xc0; + pic_set_irq(s->irq, 1); +} 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) printf("cmos: write index=0x%02x val=0x%02x\n", s->cmos_index, data); #endif - switch(addr) { + switch(s->cmos_index) { case RTC_SECONDS_ALARM: case RTC_MINUTES_ALARM: case RTC_HOURS_ALARM: @@ -95,10 +146,30 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) case RTC_MONTH: case RTC_YEAR: s->cmos_data[s->cmos_index] = data; + /* if in set mode, do not update the time */ + if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { + rtc_set_time(s); + } break; case RTC_REG_A: + /* UIP bit is read only */ + s->cmos_data[RTC_REG_A] = (data & ~REG_A_UIP) | + (s->cmos_data[RTC_REG_A] & REG_A_UIP); + rtc_timer_update(s, qemu_get_clock(vm_clock)); + break; case RTC_REG_B: - s->cmos_data[s->cmos_index] = data; + if (data & REG_B_SET) { + /* set mode: reset UIP mode */ + s->cmos_data[RTC_REG_A] &= ~REG_A_UIP; + data &= ~REG_B_UIE; + } else { + /* if disabling set mode, update the time */ + if (s->cmos_data[RTC_REG_B] & REG_B_SET) { + rtc_set_time(s); + } + } + s->cmos_data[RTC_REG_B] = data; + rtc_timer_update(s, qemu_get_clock(vm_clock)); break; case RTC_REG_C: case RTC_REG_D: @@ -111,27 +182,104 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) } } -static inline int to_bcd(int a) +static inline int to_bcd(RTCState *s, int a) { - return ((a / 10) << 4) | (a % 10); + if (s->cmos_data[RTC_REG_B] & 0x04) { + return a; + } else { + return ((a / 10) << 4) | (a % 10); + } } -static void cmos_update_time(RTCState *s) +static inline int from_bcd(RTCState *s, int a) { - struct tm *tm; + if (s->cmos_data[RTC_REG_B] & 0x04) { + return a; + } else { + return ((a >> 4) * 10) + (a & 0x0f); + } +} + +static void rtc_set_time(RTCState *s) +{ + struct tm tm1, *tm = &tm1; + + tm->tm_sec = from_bcd(s, s->cmos_data[RTC_SECONDS]); + tm->tm_min = from_bcd(s, s->cmos_data[RTC_MINUTES]); + tm->tm_hour = from_bcd(s, s->cmos_data[RTC_HOURS]); + tm->tm_wday = from_bcd(s, s->cmos_data[RTC_DAY_OF_WEEK]); + tm->tm_mday = from_bcd(s, s->cmos_data[RTC_DAY_OF_MONTH]); + tm->tm_mon = from_bcd(s, s->cmos_data[RTC_MONTH]) - 1; + tm->tm_year = from_bcd(s, s->cmos_data[RTC_YEAR]) + 100; + + /* update internal state */ + s->buf_data[RTC_SECONDS] = s->cmos_data[RTC_SECONDS]; + s->buf_data[RTC_MINUTES] = s->cmos_data[RTC_MINUTES]; + s->buf_data[RTC_HOURS] = s->cmos_data[RTC_HOURS]; + s->buf_data[RTC_DAY_OF_WEEK] = s->cmos_data[RTC_DAY_OF_WEEK]; + s->buf_data[RTC_DAY_OF_MONTH] = s->cmos_data[RTC_DAY_OF_MONTH]; + s->buf_data[RTC_MONTH] = s->cmos_data[RTC_MONTH]; + s->buf_data[RTC_YEAR] = s->cmos_data[RTC_YEAR]; + s->current_time = mktime(tm); +} + +static void rtc_update_second(void *opaque) +{ + RTCState *s = opaque; + + /* if the oscillator is not in normal operation, we do not update */ + if ((s->cmos_data[RTC_REG_A] & 0x70) != 0x20) { + s->next_second_time += ticks_per_sec; + qemu_mod_timer(s->second_timer, s->next_second_time); + } else { + s->current_time++; + + if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { + /* update in progress bit */ + s->cmos_data[RTC_REG_A] |= REG_A_UIP; + } + qemu_mod_timer(s->second_timer2, + s->next_second_time + (ticks_per_sec * 99) / 100); + } +} + +static void rtc_update_second2(void *opaque) +{ + RTCState *s = opaque; time_t ti; - ti = time(NULL); - tm = gmtime(&ti); - s->cmos_data[RTC_SECONDS] = to_bcd(tm->tm_sec); - s->cmos_data[RTC_MINUTES] = to_bcd(tm->tm_min); - s->cmos_data[RTC_HOURS] = to_bcd(tm->tm_hour); - s->cmos_data[RTC_DAY_OF_WEEK] = to_bcd(tm->tm_wday); - s->cmos_data[RTC_DAY_OF_MONTH] = to_bcd(tm->tm_mday); - s->cmos_data[RTC_MONTH] = to_bcd(tm->tm_mon + 1); - s->cmos_data[RTC_YEAR] = to_bcd(tm->tm_year % 100); - s->cmos_data[REG_IBM_CENTURY_BYTE] = to_bcd((tm->tm_year / 100) + 19); - s->cmos_data[REG_IBM_PS2_CENTURY_BYTE] = s->cmos_data[REG_IBM_CENTURY_BYTE]; + ti = s->current_time; + rtc_set_date_buf(s, gmtime(&ti)); + + if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { + rtc_copy_date(s); + } + + /* check alarm */ + if (s->cmos_data[RTC_REG_B] & REG_B_AIE) { + if (((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0 || + s->cmos_data[RTC_SECONDS_ALARM] == s->buf_data[RTC_SECONDS]) && + ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0 || + s->cmos_data[RTC_MINUTES_ALARM] == s->buf_data[RTC_MINUTES]) && + ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0 || + s->cmos_data[RTC_HOURS_ALARM] == s->buf_data[RTC_HOURS])) { + + s->cmos_data[RTC_REG_C] |= 0xa0; + pic_set_irq(s->irq, 1); + } + } + + /* update ended interrupt */ + if (s->cmos_data[RTC_REG_B] & REG_B_UIE) { + s->cmos_data[RTC_REG_C] |= 0x90; + pic_set_irq(s->irq, 1); + } + + /* clear update in progress bit */ + s->cmos_data[RTC_REG_A] &= ~REG_A_UIP; + + s->next_second_time += ticks_per_sec; + qemu_mod_timer(s->second_timer, s->next_second_time); } 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) case RTC_DAY_OF_MONTH: case RTC_MONTH: case RTC_YEAR: - case REG_IBM_CENTURY_BYTE: - case REG_IBM_PS2_CENTURY_BYTE: - cmos_update_time(s); ret = s->cmos_data[s->cmos_index]; break; case RTC_REG_A: ret = s->cmos_data[s->cmos_index]; - /* toggle update-in-progress bit for Linux (same hack as - plex86) */ - s->cmos_data[RTC_REG_A] ^= 0x80; break; case RTC_REG_C: ret = s->cmos_data[s->cmos_index]; @@ -177,19 +319,94 @@ static uint32_t cmos_ioport_read(void *opaque, uint32_t addr) } } -void rtc_timer(void) +static void rtc_set_date_buf(RTCState *s, const struct tm *tm) { - RTCState *s = &rtc_state; - if (s->cmos_data[RTC_REG_B] & 0x50) { - pic_set_irq(s->irq, 1); + s->buf_data[RTC_SECONDS] = to_bcd(s, tm->tm_sec); + s->buf_data[RTC_MINUTES] = to_bcd(s, tm->tm_min); + if (s->cmos_data[RTC_REG_B] & 0x02) { + /* 24 hour format */ + s->buf_data[RTC_HOURS] = to_bcd(s, tm->tm_hour); + } else { + /* 12 hour format */ + s->buf_data[RTC_HOURS] = to_bcd(s, tm->tm_hour % 12); + if (tm->tm_hour >= 12) + s->buf_data[RTC_HOURS] |= 0x80; } + s->buf_data[RTC_DAY_OF_WEEK] = to_bcd(s, tm->tm_wday); + s->buf_data[RTC_DAY_OF_MONTH] = to_bcd(s, tm->tm_mday); + s->buf_data[RTC_MONTH] = to_bcd(s, tm->tm_mon + 1); + s->buf_data[RTC_YEAR] = to_bcd(s, tm->tm_year % 100); +} + +static void rtc_copy_date(RTCState *s) +{ + s->cmos_data[RTC_SECONDS] = s->buf_data[RTC_SECONDS]; + s->cmos_data[RTC_MINUTES] = s->buf_data[RTC_MINUTES]; + s->cmos_data[RTC_HOURS] = s->buf_data[RTC_HOURS]; + s->cmos_data[RTC_DAY_OF_WEEK] = s->buf_data[RTC_DAY_OF_WEEK]; + s->cmos_data[RTC_DAY_OF_MONTH] = s->buf_data[RTC_DAY_OF_MONTH]; + s->cmos_data[RTC_MONTH] = s->buf_data[RTC_MONTH]; + s->cmos_data[RTC_YEAR] = s->buf_data[RTC_YEAR]; +} + +void rtc_set_memory(RTCState *s, int addr, int val) +{ + if (addr >= 0 && addr <= 127) + s->cmos_data[addr] = val; +} + +void rtc_set_date(RTCState *s, const struct tm *tm) +{ + s->current_time = mktime((struct tm *)tm); + rtc_set_date_buf(s, tm); + rtc_copy_date(s); +} + +static void rtc_save(QEMUFile *f, void *opaque) +{ + RTCState *s = opaque; + + qemu_put_buffer(f, s->cmos_data, 128); + qemu_put_8s(f, &s->cmos_index); + qemu_put_be32s(f, &s->current_time); + qemu_put_buffer(f, s->buf_data, 10); + + qemu_put_timer(f, s->periodic_timer); + qemu_put_be64s(f, &s->next_periodic_time); + + qemu_put_be64s(f, &s->next_second_time); + qemu_put_timer(f, s->second_timer); + qemu_put_timer(f, s->second_timer2); } -void rtc_init(int base, int irq) +static int rtc_load(QEMUFile *f, void *opaque, int version_id) { - RTCState *s = &rtc_state; + RTCState *s = opaque; + + if (version_id != 1) + return -EINVAL; - cmos_update_time(s); + qemu_get_buffer(f, s->cmos_data, 128); + qemu_get_8s(f, &s->cmos_index); + qemu_get_be32s(f, &s->current_time); + qemu_get_buffer(f, s->buf_data, 10); + + qemu_get_timer(f, s->periodic_timer); + qemu_get_be64s(f, &s->next_periodic_time); + + qemu_get_be64s(f, &s->next_second_time); + qemu_get_timer(f, s->second_timer); + qemu_get_timer(f, s->second_timer2); + return 0; +} + +RTCState *rtc_init(int base, int irq) +{ + RTCState *s; + + s = qemu_mallocz(sizeof(RTCState)); + if (!s) + return NULL; s->irq = irq; s->cmos_data[RTC_REG_A] = 0x26; @@ -197,7 +414,20 @@ void rtc_init(int base, int irq) s->cmos_data[RTC_REG_C] = 0x00; s->cmos_data[RTC_REG_D] = 0x80; + s->periodic_timer = qemu_new_timer(vm_clock, + rtc_periodic_timer, s); + s->second_timer = qemu_new_timer(vm_clock, + rtc_update_second, s); + s->second_timer2 = qemu_new_timer(vm_clock, + rtc_update_second2, s); + + s->next_second_time = qemu_get_clock(vm_clock) + (ticks_per_sec * 99) / 100; + qemu_mod_timer(s->second_timer2, s->next_second_time); + register_ioport_write(base, 2, 1, cmos_ioport_write, s); register_ioport_read(base, 2, 1, cmos_ioport_read, s); + + register_savevm("mc146818rtc", base, 1, rtc_save, rtc_load, s); + return s; } |