Changing in this patch: Set the timer to one second earlier before target alarm when AF bit is clear. In version 3, in order to solve the async between UF, AF and UIP, the timer will keep running when UF or AF are clear. This is a little ugly, especially when a userspace program is using the alarm and we cannot achieve any power saving. In this version, when the AF bit is cleared, we will set the timer to one second earlier before the alarm. With this changing, we can avoid the unnecessary timer and keep the sync between UF, AF and UIP. Set the timer to one second earlier before target alarm when AF bit is clear. Signed-off-by: Yang Zhang <yang.z.zhang@xxxxxxxxx> --- hw/mc146818rtc.c | 274 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 files changed, 255 insertions(+), 19 deletions(-) diff --git a/hw/mc146818rtc.c b/hw/mc146818rtc.c index fae049e..c03606f 100644 --- a/hw/mc146818rtc.c +++ b/hw/mc146818rtc.c @@ -46,6 +46,11 @@ #define USEC_PER_SEC 1000000L #define NS_PER_USEC 1000L +#define NS_PER_SEC 1000000000ULL +#define SEC_PER_MIN 60 +#define SEC_PER_HOUR 3600 +#define MIN_PER_HOUR 60 +#define HOUR_PER_DAY 24 #define RTC_REINJECT_ON_ACK_COUNT 20 @@ -114,6 +119,8 @@ typedef struct RTCState { static void rtc_set_time(RTCState *s); static void rtc_calibrate_time(RTCState *s); static void rtc_set_cmos(RTCState *s); +static inline int rtc_from_bcd(RTCState *s, int a); +static uint64_t get_next_alarm(RTCState *s); static int32_t divider_reset; @@ -232,29 +239,47 @@ static void rtc_periodic_timer(void *opaque) static void check_update_timer(RTCState *s) { uint64_t next_update_time, expire_time; - uint64_t guest_usec; + uint64_t guest_usec, next_alarm_sec; + qemu_del_timer(s->update_timer); qemu_del_timer(s->update_timer2); - if (!((s->cmos_data[RTC_REG_C] & (REG_C_UF | REG_C_AF)) == - (REG_C_UF | REG_C_AF)) && !(s->cmos_data[RTC_REG_B] & REG_B_SET)) { - s->use_timer = 1; + if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { guest_usec = get_guest_rtc_us(s) % USEC_PER_SEC; - if (guest_usec >= (USEC_PER_SEC - 244)) { - /* RTC is in update cycle when enabling UIE */ - s->cmos_data[RTC_REG_A] |= REG_A_UIP; - next_update_time = (USEC_PER_SEC - guest_usec) * NS_PER_USEC; - expire_time = qemu_get_clock_ns(rtc_clock) + next_update_time; - qemu_mod_timer(s->update_timer2, expire_time); - } else { - next_update_time = (USEC_PER_SEC - guest_usec - 244) * NS_PER_USEC; - expire_time = qemu_get_clock_ns(rtc_clock) + next_update_time; - s->next_update_time = expire_time; - qemu_mod_timer(s->update_timer, expire_time); + /* if UF is clear, reprogram to next second */ + if (!(s->cmos_data[RTC_REG_C] & REG_C_UF)) { +program_next_second: + s->use_timer = 1; + if (guest_usec >= (USEC_PER_SEC - 244)) { + /* RTC is in update cycle when enabling UIE */ + s->cmos_data[RTC_REG_A] |= REG_A_UIP; + next_update_time = (USEC_PER_SEC - guest_usec) * NS_PER_USEC; + expire_time = qemu_get_clock_ns(rtc_clock) + next_update_time; + qemu_mod_timer(s->update_timer2, expire_time); + } else { + next_update_time = (USEC_PER_SEC - guest_usec - 244) + * NS_PER_USEC; + expire_time = qemu_get_clock_ns(rtc_clock) + next_update_time; + s->next_update_time = expire_time; + qemu_mod_timer(s->update_timer, expire_time); + } + return ; + } else if (!(s->cmos_data[RTC_REG_C] & REG_C_AF)) { + /* UF is set, but AF is clear. Program to one second + * earlier before target alarm*/ + next_alarm_sec = get_next_alarm(s); + if (next_alarm_sec == 1) { + goto program_next_second; + } else { + next_update_time = (USEC_PER_SEC - guest_usec) * NS_PER_USEC; + next_update_time += (next_alarm_sec - 1) * NS_PER_SEC; + expire_time = qemu_get_clock_ns(rtc_clock) + next_update_time; + s->next_update_time = expire_time; + qemu_mod_timer(s->update_timer2, expire_time); + } } - } else { - s->use_timer = 0; } + s->use_timer = 0; } static void rtc_update_timer(void *opaque) @@ -267,15 +292,215 @@ static void rtc_update_timer(void *opaque) } } +static inline uint8_t convert_hour(RTCState *s, uint8_t hour) +{ + if (!(s->cmos_data[RTC_REG_B] & REG_B_24H)) { + hour %= 12; + if (s->cmos_data[RTC_HOURS] & 0x80) { + hour += 12; + } + } + return hour; +} + +static uint64_t get_next_alarm(RTCState *s) +{ + int32_t alarm_sec, alarm_min, alarm_hour, cur_hour, cur_min, cur_sec; + int32_t hour, min; + uint64_t next_alarm_sec; + + rtc_calibrate_time(s); + rtc_set_cmos(s); + + alarm_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS_ALARM]); + alarm_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES_ALARM]); + alarm_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS_ALARM]); + alarm_hour = convert_hour(s, alarm_hour); + + cur_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS]); + cur_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES]); + cur_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS]); + cur_hour = convert_hour(s, cur_hour); + + if ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0) { + if ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0) { + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + /* All of the three alarm are in "don't care" mode */ + next_alarm_sec = 1; + } else if (cur_sec < alarm_sec) { + /* Hour and minute alarm are in "don't care" mode and + * second alarm > current second*/ + next_alarm_sec = alarm_sec - cur_sec; + } else { + /* Hour and minute alarm are in "don't care" mode and + * second alarm < current second*/ + next_alarm_sec = alarm_sec + SEC_PER_MIN - cur_sec; + } + } else { + /* Houre alarm is in "don't care mode', but minute alarm + * is in normal mode*/ + if (cur_min < alarm_min) { + /* minute alarm > current minute */ + min = alarm_min - cur_min; + next_alarm_sec = min * SEC_PER_MIN - cur_sec; + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec += 0; + } else { + next_alarm_sec += alarm_sec; + } + } else if (cur_min == alarm_min) { + /* minute alarm == current minute */ + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec = 1; + } else if (cur_sec < alarm_sec) { + next_alarm_sec = alarm_sec - cur_sec; + } else { + min = alarm_min + MIN_PER_HOUR - cur_min; + next_alarm_sec = + alarm_sec + min * SEC_PER_MIN - cur_sec; + } + } else { + /* minute alarm < current minute */ + min = alarm_min + MIN_PER_HOUR - cur_min; + next_alarm_sec = min * SEC_PER_MIN - cur_sec; + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec += 0; + } else { + next_alarm_sec += alarm_sec; + } + } + } + } else { + /* Hour alarm is not in "don't care mode' */ + if (cur_hour < alarm_hour) { + /* hour alarm > current hour */ + hour = alarm_hour - cur_hour; + next_alarm_sec = hour * SEC_PER_HOUR - + cur_min * SEC_PER_MIN - cur_sec; + if ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0) { + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec += 0; + } else { + next_alarm_sec += alarm_sec; + } + } else { + next_alarm_sec += alarm_min * SEC_PER_MIN; + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec += 0; + } else { + next_alarm_sec += alarm_sec; + } + } + } else if (cur_hour == alarm_hour) { + /* hour alarm == current hour */ + if ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0) { + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec = 1; + } else if (cur_sec < alarm_sec) { + next_alarm_sec = alarm_sec - cur_sec; + } else { + next_alarm_sec = alarm_sec + SEC_PER_MIN - cur_sec; + } + } else if (cur_min < alarm_min) { + min = alarm_min - cur_min; + next_alarm_sec = min * SEC_PER_MIN - cur_sec; + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec += 0; + } else { + next_alarm_sec += alarm_sec; + } + } else if (cur_min == alarm_min) { + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec = 1; + } else if (cur_sec < alarm_sec) { + next_alarm_sec = alarm_sec - cur_sec; + } else { + hour = alarm_hour + HOUR_PER_DAY - cur_hour; + next_alarm_sec = hour * SEC_PER_HOUR - + cur_min * SEC_PER_MIN - cur_sec; + next_alarm_sec += alarm_min * SEC_PER_MIN + alarm_sec; + } + } else { + hour = alarm_hour + HOUR_PER_DAY - cur_hour; + next_alarm_sec = hour * SEC_PER_HOUR - + cur_min * SEC_PER_MIN - cur_sec; + next_alarm_sec += alarm_min * SEC_PER_MIN; + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec += 0; + } else { + next_alarm_sec += alarm_sec; + } + } + } else { + /* hour alarm < current hour */ + hour = alarm_hour + HOUR_PER_DAY - cur_hour; + next_alarm_sec = hour * SEC_PER_HOUR - + cur_min * SEC_PER_MIN - cur_sec; + if ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0) { + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec += 0; + } else { + next_alarm_sec += alarm_sec; + } + } else { + next_alarm_sec += alarm_min * SEC_PER_MIN; + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec += 0; + } else { + next_alarm_sec += alarm_sec; + } + } + } + } + + return next_alarm_sec; +} + +static uint32_t check_alarm(RTCState *s) +{ + uint8_t alarm_hour, alarm_min, alarm_sec; + uint8_t cur_hour, cur_min, cur_sec; + + rtc_calibrate_time(s); + rtc_set_cmos(s); + + alarm_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS_ALARM]); + alarm_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES_ALARM]); + alarm_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS_ALARM]); + alarm_hour = convert_hour(s, alarm_hour); + + cur_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS]); + cur_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES]); + cur_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS]); + cur_hour = convert_hour(s, cur_hour); + + if (((alarm_sec & 0xc0) == 0xc0 || alarm_sec == cur_sec) && + ((alarm_min & 0xc0) == 0xc0 || alarm_min == cur_min) && + ((alarm_hour & 0xc0) == 0xc0 || alarm_hour == cur_hour)) { + return 1; + } + return 0; + +} + static void rtc_update_timer2(void *opaque) { RTCState *s = opaque; + int32_t alarm_fired; if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { s->cmos_data[RTC_REG_C] |= REG_C_UF; + if (check_alarm(s)) { + s->cmos_data[RTC_REG_C] |= REG_C_AF; + alarm_fired = 1; + } + s->cmos_data[RTC_REG_A] &= ~REG_A_UIP; - s->cmos_data[RTC_REG_C] |= REG_C_IRQF; - qemu_irq_raise(s->irq); + if ((s->cmos_data[RTC_REG_B] & REG_B_UIE) || + ((alarm_fired == 1) && (s->cmos_data[RTC_REG_B] & REG_B_AIE))) { + s->cmos_data[RTC_REG_C] |= REG_C_IRQF; + qemu_irq_raise(s->irq); + } } check_update_timer(s); } @@ -316,6 +541,7 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) case RTC_MINUTES_ALARM: case RTC_HOURS_ALARM: s->cmos_data[s->cmos_index] = data; + check_update_timer(s); break; case RTC_SECONDS: case RTC_MINUTES: @@ -373,6 +599,16 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) } } } + /* if an interrupt flag is already set when the interrupt + * becomes enabled, raise an interrupt imemediately*/ + if (!(s->cmos_data[RTC_REG_B] & REG_B_UIE) && (data & REG_B_UIE) + && (s->cmos_data[RTC_REG_C] & REG_C_UF)) { + qemu_irq_raise(s->irq); + } + if (!(s->cmos_data[RTC_REG_B] & REG_B_AIE) && (data & REG_B_AIE) + && (s->cmos_data[RTC_REG_C] & REG_C_AF)) { + qemu_irq_raise(s->irq); + } s->cmos_data[RTC_REG_B] = data; periodic_timer_update(s, qemu_get_clock_ns(rtc_clock)); check_update_timer(s); -- 1.7.1 -- 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