Add alarm support. Set the timer to one second earlier before target alarm when AF bit is clear.
Signed-off-by: Yang Zhang <yang.z.zh...@intel.com> --- hw/mc146818rtc.c | 273 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 files changed, 254 insertions(+), 19 deletions(-) diff --git a/hw/mc146818rtc.c b/hw/mc146818rtc.c index be74399..e6f221c 100644 --- a/hw/mc146818rtc.c +++ b/hw/mc146818rtc.c @@ -49,6 +49,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 @@ -117,6 +122,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; @@ -241,29 +248,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) @@ -276,15 +301,214 @@ 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 (rtc_running(s)) { 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); } @@ -325,6 +549,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: @@ -388,6 +613,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