Use timer to emulate alarm. The timer is enabled when AIE is setting Signed-off-by: Yang Zhang <yang.z.zhang@xxxxxxxxx> --- hw/mc146818rtc.c | 187 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 187 insertions(+), 0 deletions(-) diff --git a/hw/mc146818rtc.c b/hw/mc146818rtc.c index bb1873b..2445c6b 100644 --- a/hw/mc146818rtc.c +++ b/hw/mc146818rtc.c @@ -45,6 +45,10 @@ #endif #define USEC_PER_SEC 1000000 +#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 @@ -101,6 +105,9 @@ typedef struct RTCState { /* update-ended timer */ QEMUTimer *update_timer; + /* alarm timer */ + QEMUTimer *alarm_timer; + uint16_t irq_reinject_on_ack_count; uint32_t irq_coalesced; uint32_t period; @@ -110,6 +117,9 @@ typedef struct RTCState { } RTCState; static void rtc_set_time(RTCState *s); +static void rtc_set_cmos(RTCState *s); +static void rtc_calibrate_time(RTCState *s); +static inline int rtc_from_bcd(RTCState *s, int a); #ifdef TARGET_I386 static void rtc_coalesced_timer_update(RTCState *s) @@ -245,6 +255,180 @@ static void rtc_update_timer(void *opaque) } } +/* handle alarm timer */ +static void alarm_timer_update(RTCState *s) +{ + struct timeval tv_now; + uint64_t next_update_usec, next_update_time, next_alarm_sec; + uint64_t expire_time; + int32_t alarm_sec, alarm_min, alarm_hour, cur_hour, cur_min, cur_sec; + int32_t hour, min; + + if (s->cmos_data[RTC_REG_B] & REG_B_AIE) { + 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]); + if (!(s->cmos_data[RTC_REG_B] & REG_B_24H)) { + alarm_hour %= 12; + if (s->cmos_data[RTC_HOURS] & 0x80) { + alarm_hour += 12; + } + } + 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]); + if (!(s->cmos_data[RTC_REG_B] & REG_B_24H)) { + cur_hour %= 12; + if (s->cmos_data[RTC_HOURS] & 0x80) { + cur_hour += 12; + } + } + + gettimeofday(&tv_now, NULL); + next_update_usec = + USEC_PER_SEC - (tv_now.tv_usec + s->offset_usec) % 1000000; + next_update_time = (get_ticks_per_sec() / USEC_PER_SEC) * + next_update_usec + qemu_get_clock_ns(rtc_clock); + + 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) { + 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 { + min = alarm_min + MIN_PER_HOUR - cur_min; + next_alarm_sec = + alarm_sec + min * SEC_PER_MIN - cur_sec; + } + } else { + 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 { + if (cur_hour < alarm_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) { + 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 - 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 - 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_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; + } + } + } + } + expire_time = (next_alarm_sec - 1) * get_ticks_per_sec() + + next_update_time; + qemu_mod_timer(s->alarm_timer, expire_time); + } else { + qemu_del_timer(s->alarm_timer); + } +} + +static void rtc_alarm_timer(void *opaque) +{ + RTCState *s = opaque; + + /* alarm interrupt */ + s->cmos_data[RTC_REG_C] |= REG_C_AF; + if (s->cmos_data[RTC_REG_B] & REG_B_AIE) { + s->cmos_data[RTC_REG_C] |= REG_C_IRQF; + qemu_irq_raise(s->irq); + } +} + static void rtc_set_offset(RTCState *s) { struct tm *tm = &s->current_tm; @@ -321,6 +505,7 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) } periodic_timer_update(s, qemu_get_clock_ns(rtc_clock)); update_ended_timer_update(s); + alarm_timer_update(s); break; case RTC_REG_C: case RTC_REG_D: @@ -535,6 +720,7 @@ static const VMStateDescription vmstate_rtc = { VMSTATE_TIMER(periodic_timer, RTCState), VMSTATE_INT64(next_periodic_time, RTCState), VMSTATE_TIMER(update_timer, RTCState), + VMSTATE_TIMER(alarm_timer, RTCState), VMSTATE_UINT32_V(irq_coalesced, RTCState, 2), VMSTATE_UINT32_V(period, RTCState, 2), VMSTATE_END_OF_LIST() @@ -632,6 +818,7 @@ static int rtc_initfn(ISADevice *dev) s->periodic_timer = qemu_new_timer_ns(rtc_clock, rtc_periodic_timer, s); s->update_timer = qemu_new_timer_ns(rtc_clock, rtc_update_timer, s); + s->alarm_timer = qemu_new_timer_ns(rtc_clock, rtc_alarm_timer, s); s->clock_reset_notifier.notify = rtc_notify_clock_reset; qemu_register_clock_reset_notifier(rtc_clock, &s->clock_reset_notifier); -- 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