change the RTC update logic to use host time with offset to calculate RTC clock. There have no need to use two periodic timers to maintain an internal timer for RTC clock update and alarm check. Instead, we calculate the real RTC time by the host time with an offset. For alarm and updated-end interrupt, if guest enabled it, then we setup a timer, or else, stop it. Signed-off-by: Yang Zhang <yang.z.zhang@xxxxxxxxx> diff --git a/hw/mc146818rtc.c b/hw/mc146818rtc.c index 9cbd052..ac1854e 100644 --- a/hw/mc146818rtc.c +++ b/hw/mc146818rtc.c @@ -84,7 +84,7 @@ typedef struct RTCState { MemoryRegion io; uint8_t cmos_data[128]; uint8_t cmos_index; - struct tm current_tm; + int64_t offset; int32_t base_year; qemu_irq irq; qemu_irq sqw_irq; @@ -93,19 +93,18 @@ typedef struct RTCState { QEMUTimer *periodic_timer; int64_t next_periodic_time; /* second update */ - int64_t next_second_time; + QEMUTimer *update_timer; + int64_t next_update_time; + /* alarm */ + QEMUTimer *alarm_timer; + int64_t next_alarm_time; uint16_t irq_reinject_on_ack_count; uint32_t irq_coalesced; uint32_t period; QEMUTimer *coalesced_timer; - QEMUTimer *second_timer; - QEMUTimer *second_timer2; Notifier clock_reset_notifier; } RTCState; -static void rtc_set_time(RTCState *s); -static void rtc_copy_date(RTCState *s); - #ifdef TARGET_I386 static void rtc_coalesced_timer_update(RTCState *s) { @@ -140,6 +139,72 @@ static void rtc_coalesced_timer(void *opaque) } #endif +static inline int rtc_to_bcd(RTCState *s, int a) +{ + if (s->cmos_data[RTC_REG_B] & REG_B_DM) { + return a; + } else { + return ((a / 10) << 4) | (a % 10); + } +} + +static inline int rtc_from_bcd(RTCState *s, int a) +{ + if (s->cmos_data[RTC_REG_B] & REG_B_DM) { + return a; + } else { + return ((a >> 4) * 10) + (a & 0x0f); + } +} + +static void rtc_set_time(RTCState *s) +{ + struct tm tm ; + + tm.tm_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS]); + tm.tm_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES]); + tm.tm_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS] & 0x7f); + if (!(s->cmos_data[RTC_REG_B] & REG_B_24H) && + (s->cmos_data[RTC_HOURS] & 0x80)) { + tm.tm_hour += 12; + } + tm.tm_wday = rtc_from_bcd(s, s->cmos_data[RTC_DAY_OF_WEEK]) - 1; + tm.tm_mday = rtc_from_bcd(s, s->cmos_data[RTC_DAY_OF_MONTH]); + tm.tm_mon = rtc_from_bcd(s, s->cmos_data[RTC_MONTH]) - 1; + tm.tm_year = rtc_from_bcd(s, s->cmos_data[RTC_YEAR]) + s->base_year - 1900; + + s->offset = qemu_timedate_diff(&tm); + + rtc_change_mon_event(&tm); +} + +static void rtc_update_time(RTCState *s) +{ + struct tm tm; + int year; + + qemu_get_timedate(&tm, s->offset); + + s->cmos_data[RTC_SECONDS] = rtc_to_bcd(s, tm.tm_sec); + s->cmos_data[RTC_MINUTES] = rtc_to_bcd(s, tm.tm_min); + if (s->cmos_data[RTC_REG_B] & REG_B_24H) { + /* 24 hour format */ + s->cmos_data[RTC_HOURS] = rtc_to_bcd(s, tm.tm_hour); + } else { + /* 12 hour format */ + s->cmos_data[RTC_HOURS] = rtc_to_bcd(s, tm.tm_hour % 12); + if (tm.tm_hour >= 12) + s->cmos_data[RTC_HOURS] |= 0x80; + } + s->cmos_data[RTC_DAY_OF_WEEK] = rtc_to_bcd(s, tm.tm_wday + 1); + s->cmos_data[RTC_DAY_OF_MONTH] = rtc_to_bcd(s, tm.tm_mday); + s->cmos_data[RTC_MONTH] = rtc_to_bcd(s, tm.tm_mon + 1); + year = (tm.tm_year - s->base_year) % 100; + if (year < 0) + year += 100; + s->cmos_data[RTC_YEAR] = rtc_to_bcd(s, year); +} + static void rtc_timer_update(RTCState *s, int64_t current_time) { int period_code, period; @@ -174,7 +239,7 @@ static void rtc_timer_update(RTCState *s, int64_t current_time) } } -static void rtc_periodic_timer(void *opaque) +static void rtc_periodic_interrupt(void *opaque) { RTCState *s = opaque; @@ -204,6 +269,92 @@ static void rtc_periodic_timer(void *opaque) } } +static void rtc_enable_update_interrupt(void *opaque) +{ + RTCState *s = opaque; + + s->next_update_time = qemu_get_clock_ns(rtc_clock) + get_ticks_per_sec(); + qemu_mod_timer(s->update_timer, s->next_update_time); +} + +static void rtc_disable_update_interrupt(void *opaque) +{ + RTCState *s = opaque; + + qemu_del_timer(s->update_timer); +} + +static void rtc_update_interrupt(void *opaque) +{ + RTCState *s = opaque; + + /* update ended interrupt */ + s->cmos_data[RTC_REG_C] |= REG_C_UF; + if (s->cmos_data[RTC_REG_B] & REG_B_UIE) { + s->cmos_data[RTC_REG_C] |= REG_C_IRQF; + qemu_irq_raise(s->irq); + + s->next_update_time += get_ticks_per_sec(); + qemu_mod_timer(s->update_timer, s->next_update_time); + } else + rtc_disable_update_interrupt(s); +} + +static void rtc_enable_alarm(void *opaque) +{ + RTCState *s = opaque; + + s->next_alarm_time = qemu_get_clock_ns(rtc_clock) + get_ticks_per_sec(); + s->next_alarm_time /= get_ticks_per_sec(); + s->next_alarm_time *= get_ticks_per_sec(); + + qemu_mod_timer(s->alarm_timer, s->next_alarm_time); +} + +static void rtc_disable_alarm(void *opaque) +{ + RTCState *s = opaque; + + qemu_del_timer(s->alarm_timer); +} + +static void rtc_alarm_interrupt(void *opaque) +{ + RTCState *s = opaque; + struct tm tm; + int hour = 0; + + qemu_get_timedate(&tm, s->offset); + + if ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) != 0xc0) { + hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS_ALARM] & 0x7f); + if (!(s->cmos_data[RTC_REG_B] & REG_B_24H) && + (s->cmos_data[RTC_HOURS_ALARM] & 0x80)) { + hour += 12; + } + } + + /* check alarm */ + if (s->cmos_data[RTC_REG_B] & REG_B_AIE) { + if (((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0 || + rtc_from_bcd(s, s->cmos_data[RTC_SECONDS_ALARM]) == tm.tm_sec) && + ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0 || + rtc_from_bcd(s, s->cmos_data[RTC_MINUTES_ALARM]) == tm.tm_min) && + ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0 || + hour == tm.tm_hour)) { + + printf("raise irq\n"); + s->cmos_data[RTC_REG_C] |= 0xa0; + qemu_irq_raise(s->irq); + } + + s->next_alarm_time += get_ticks_per_sec(); + qemu_mod_timer(s->alarm_timer, s->next_alarm_time); + } else + rtc_disable_alarm(s); + +} + static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) { RTCState *s = opaque; @@ -239,26 +390,37 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) rtc_timer_update(s, qemu_get_clock_ns(rtc_clock)); break; case RTC_REG_B: - 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 (data & REG_B_SET) + rtc_update_time(s); + else { /* if disabling set mode, update the time */ if (s->cmos_data[RTC_REG_B] & REG_B_SET) { rtc_set_time(s); } } + if (((s->cmos_data[RTC_REG_B] ^ data) & (REG_B_DM | REG_B_24H)) && !(data & REG_B_SET)) { /* If the time format has changed and not in set mode, update the registers immediately. */ s->cmos_data[RTC_REG_B] = data; - rtc_copy_date(s); - } else { + rtc_update_time(s); + } else s->cmos_data[RTC_REG_B] = data; - } + + /* check alarm interrupt */ + if ((s->cmos_data[RTC_REG_B] & REG_B_AIE) && + !(s->cmos_data[RTC_REG_B] & REG_B_SET) ) + rtc_enable_alarm(s); + + /* check update-ended interrupt */ + if ((s->cmos_data[RTC_REG_B] & REG_B_UIE) && + !(s->cmos_data[RTC_REG_B] & REG_B_SET)) + rtc_enable_update_interrupt(s); + + /* check periodic interrupt or square-wave */ rtc_timer_update(s, qemu_get_clock_ns(rtc_clock)); + break; case RTC_REG_C: case RTC_REG_D: @@ -271,184 +433,6 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) } } -static inline int rtc_to_bcd(RTCState *s, int a) -{ - if (s->cmos_data[RTC_REG_B] & REG_B_DM) { - return a; - } else { - return ((a / 10) << 4) | (a % 10); - } -} - -static inline int rtc_from_bcd(RTCState *s, int a) -{ - if (s->cmos_data[RTC_REG_B] & REG_B_DM) { - return a; - } else { - return ((a >> 4) * 10) + (a & 0x0f); - } -} - -static void rtc_set_time(RTCState *s) -{ - struct tm *tm = &s->current_tm; - - tm->tm_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS]); - tm->tm_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES]); - tm->tm_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS] & 0x7f); - if (!(s->cmos_data[RTC_REG_B] & REG_B_24H) && - (s->cmos_data[RTC_HOURS] & 0x80)) { - tm->tm_hour += 12; - } - tm->tm_wday = rtc_from_bcd(s, s->cmos_data[RTC_DAY_OF_WEEK]) - 1; - tm->tm_mday = rtc_from_bcd(s, s->cmos_data[RTC_DAY_OF_MONTH]); - tm->tm_mon = rtc_from_bcd(s, s->cmos_data[RTC_MONTH]) - 1; - tm->tm_year = rtc_from_bcd(s, s->cmos_data[RTC_YEAR]) + s->base_year - 1900; - - rtc_change_mon_event(tm); -} - -static void rtc_copy_date(RTCState *s) -{ - const struct tm *tm = &s->current_tm; - int year; - - s->cmos_data[RTC_SECONDS] = rtc_to_bcd(s, tm->tm_sec); - s->cmos_data[RTC_MINUTES] = rtc_to_bcd(s, tm->tm_min); - if (s->cmos_data[RTC_REG_B] & REG_B_24H) { - /* 24 hour format */ - s->cmos_data[RTC_HOURS] = rtc_to_bcd(s, tm->tm_hour); - } else { - /* 12 hour format */ - s->cmos_data[RTC_HOURS] = rtc_to_bcd(s, tm->tm_hour % 12); - if (tm->tm_hour >= 12) - s->cmos_data[RTC_HOURS] |= 0x80; - } - s->cmos_data[RTC_DAY_OF_WEEK] = rtc_to_bcd(s, tm->tm_wday + 1); - s->cmos_data[RTC_DAY_OF_MONTH] = rtc_to_bcd(s, tm->tm_mday); - s->cmos_data[RTC_MONTH] = rtc_to_bcd(s, tm->tm_mon + 1); - year = (tm->tm_year - s->base_year) % 100; - if (year < 0) - year += 100; - s->cmos_data[RTC_YEAR] = rtc_to_bcd(s, year); -} - -/* month is between 0 and 11. */ -static int get_days_in_month(int month, int year) -{ - static const int days_tab[12] = { - 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 - }; - int d; - if ((unsigned )month >= 12) - return 31; - d = days_tab[month]; - if (month == 1) { - if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) - d++; - } - return d; -} - -/* update 'tm' to the next second */ -static void rtc_next_second(struct tm *tm) -{ - int days_in_month; - - tm->tm_sec++; - if ((unsigned)tm->tm_sec >= 60) { - tm->tm_sec = 0; - tm->tm_min++; - if ((unsigned)tm->tm_min >= 60) { - tm->tm_min = 0; - tm->tm_hour++; - if ((unsigned)tm->tm_hour >= 24) { - tm->tm_hour = 0; - /* next day */ - tm->tm_wday++; - if ((unsigned)tm->tm_wday >= 7) - tm->tm_wday = 0; - days_in_month = get_days_in_month(tm->tm_mon, - tm->tm_year + 1900); - tm->tm_mday++; - if (tm->tm_mday < 1) { - tm->tm_mday = 1; - } else if (tm->tm_mday > days_in_month) { - tm->tm_mday = 1; - tm->tm_mon++; - if (tm->tm_mon >= 12) { - tm->tm_mon = 0; - tm->tm_year++; - } - } - } - } - } -} - - -static void rtc_update_second(void *opaque) -{ - RTCState *s = opaque; - int64_t delay; - - /* 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 += get_ticks_per_sec(); - qemu_mod_timer(s->second_timer, s->next_second_time); - } else { - rtc_next_second(&s->current_tm); - - if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { - /* update in progress bit */ - s->cmos_data[RTC_REG_A] |= REG_A_UIP; - } - /* should be 244 us = 8 / 32768 seconds, but currently the - timers do not have the necessary resolution. */ - delay = (get_ticks_per_sec() * 1) / 100; - if (delay < 1) - delay = 1; - qemu_mod_timer(s->second_timer2, - s->next_second_time + delay); - } -} - -static void rtc_update_second2(void *opaque) -{ - RTCState *s = opaque; - - 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 || - rtc_from_bcd(s, s->cmos_data[RTC_SECONDS_ALARM]) == s->current_tm.tm_sec) && - ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0 || - rtc_from_bcd(s, s->cmos_data[RTC_MINUTES_ALARM]) == s->current_tm.tm_min) && - ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0 || - rtc_from_bcd(s, s->cmos_data[RTC_HOURS_ALARM]) == s->current_tm.tm_hour)) { - - s->cmos_data[RTC_REG_C] |= 0xa0; - qemu_irq_raise(s->irq); - } - } - - /* update ended interrupt */ - s->cmos_data[RTC_REG_C] |= REG_C_UF; - if (s->cmos_data[RTC_REG_B] & REG_B_UIE) { - s->cmos_data[RTC_REG_C] |= REG_C_IRQF; - qemu_irq_raise(s->irq); - } - - /* clear update in progress bit */ - s->cmos_data[RTC_REG_A] &= ~REG_A_UIP; - - s->next_second_time += get_ticks_per_sec(); - qemu_mod_timer(s->second_timer, s->next_second_time); -} - static uint32_t cmos_ioport_read(void *opaque, uint32_t addr) { RTCState *s = opaque; @@ -464,9 +448,9 @@ static uint32_t cmos_ioport_read(void *opaque, uint32_t addr) case RTC_DAY_OF_MONTH: case RTC_MONTH: case RTC_YEAR: - ret = s->cmos_data[s->cmos_index]; - break; - case RTC_REG_A: + /* if not in set mode, update the time before read */ + if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) + rtc_update_time(s); ret = s->cmos_data[s->cmos_index]; break; case RTC_REG_C: @@ -507,13 +491,6 @@ void rtc_set_memory(ISADevice *dev, int addr, int val) s->cmos_data[addr] = val; } -void rtc_set_date(ISADevice *dev, const struct tm *tm) -{ - RTCState *s = DO_UPCAST(RTCState, dev, dev); - s->current_tm = *tm; - rtc_copy_date(s); -} - /* PC cmos mappings */ #define REG_IBM_CENTURY_BYTE 0x32 #define REG_IBM_PS2_CENTURY_BYTE 0x37 @@ -524,10 +501,11 @@ static void rtc_set_date_from_host(ISADevice *dev) struct tm tm; int val; + s->offset = 0; /* set the CMOS date */ - qemu_get_timedate(&tm, 0); - rtc_set_date(dev, &tm); + rtc_update_time(s); + qemu_get_timedate(&tm, s->offset); val = rtc_to_bcd(s, (tm.tm_year / 100) + 19); rtc_set_memory(dev, REG_IBM_CENTURY_BYTE, val); rtc_set_memory(dev, REG_IBM_PS2_CENTURY_BYTE, val); @@ -556,18 +534,13 @@ static const VMStateDescription vmstate_rtc = { .fields = (VMStateField []) { VMSTATE_BUFFER(cmos_data, RTCState), VMSTATE_UINT8(cmos_index, RTCState), - VMSTATE_INT32(current_tm.tm_sec, RTCState), - VMSTATE_INT32(current_tm.tm_min, RTCState), - VMSTATE_INT32(current_tm.tm_hour, RTCState), - VMSTATE_INT32(current_tm.tm_wday, RTCState), - VMSTATE_INT32(current_tm.tm_mday, RTCState), - VMSTATE_INT32(current_tm.tm_mon, RTCState), - VMSTATE_INT32(current_tm.tm_year, RTCState), + VMSTATE_INT64(offset, RTCState), VMSTATE_TIMER(periodic_timer, RTCState), VMSTATE_INT64(next_periodic_time, RTCState), - VMSTATE_INT64(next_second_time, RTCState), - VMSTATE_TIMER(second_timer, RTCState), - VMSTATE_TIMER(second_timer2, RTCState), + VMSTATE_TIMER(update_timer, RTCState), + VMSTATE_INT64(next_update_time, RTCState), + VMSTATE_TIMER(alarm_timer, RTCState), + VMSTATE_INT64(next_alarm_time, RTCState), VMSTATE_UINT32_V(irq_coalesced, RTCState, 2), VMSTATE_UINT32_V(period, RTCState, 2), VMSTATE_END_OF_LIST() @@ -580,8 +553,6 @@ static void rtc_notify_clock_reset(Notifier *notifier, void *data) int64_t now = *(int64_t *)data; rtc_set_date_from_host(&s->dev); - s->next_second_time = now + (get_ticks_per_sec() * 99) / 100; - qemu_mod_timer(s->second_timer2, s->next_second_time); rtc_timer_update(s, now); #ifdef TARGET_I386 if (rtc_td_hack) { @@ -627,13 +598,15 @@ static void rtc_get_date(DeviceState *dev, Visitor *v, void *opaque, ISADevice *isa = DO_UPCAST(ISADevice, qdev, dev); RTCState *s = DO_UPCAST(RTCState, dev, isa); + struct tm tm; + qemu_get_timedate(&tm, s->offset); visit_start_struct(v, NULL, "struct tm", name, 0, errp); - visit_type_int32(v, &s->current_tm.tm_year, "tm_year", errp); - visit_type_int32(v, &s->current_tm.tm_mon, "tm_mon", errp); - visit_type_int32(v, &s->current_tm.tm_mday, "tm_mday", errp); - visit_type_int32(v, &s->current_tm.tm_hour, "tm_hour", errp); - visit_type_int32(v, &s->current_tm.tm_min, "tm_min", errp); - visit_type_int32(v, &s->current_tm.tm_sec, "tm_sec", errp); + visit_type_int32(v, &tm.tm_year, "tm_year", errp); + visit_type_int32(v, &tm.tm_mon, "tm_mon", errp); + visit_type_int32(v, &tm.tm_mday, "tm_mday", errp); + visit_type_int32(v, &tm.tm_hour, "tm_hour", errp); + visit_type_int32(v, &tm.tm_min, "tm_min", errp); + visit_type_int32(v, &tm.tm_sec, "tm_sec", errp); visit_end_struct(v, errp); } @@ -649,22 +622,18 @@ static int rtc_initfn(ISADevice *dev) rtc_set_date_from_host(dev); - s->periodic_timer = qemu_new_timer_ns(rtc_clock, rtc_periodic_timer, s); + s->periodic_timer = qemu_new_timer_ns(rtc_clock, rtc_periodic_interrupt, s); #ifdef TARGET_I386 if (rtc_td_hack) s->coalesced_timer = qemu_new_timer_ns(rtc_clock, rtc_coalesced_timer, s); #endif - s->second_timer = qemu_new_timer_ns(rtc_clock, rtc_update_second, s); - s->second_timer2 = qemu_new_timer_ns(rtc_clock, rtc_update_second2, s); + s->update_timer = qemu_new_timer_ns(rtc_clock, rtc_update_interrupt, s); + s->alarm_timer = qemu_new_timer_ns(rtc_clock, rtc_alarm_interrupt, s); s->clock_reset_notifier.notify = rtc_notify_clock_reset; qemu_register_clock_reset_notifier(rtc_clock, &s->clock_reset_notifier); - s->next_second_time = - qemu_get_clock_ns(rtc_clock) + (get_ticks_per_sec() * 99) / 100; - qemu_mod_timer(s->second_timer2, s->next_second_time); - memory_region_init_io(&s->io, &cmos_ops, s, "rtc", 2); isa_register_ioport(dev, &s->io, base); -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html