@@ -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);
Use timer to emulate alarm. The timer is enabled when AIE is setting Signed-off-by: Yang Zhang <yang.z.zhang@intel.com> --- hw/mc146818rtc.c | 187 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 187 insertions(+), 0 deletions(-) -- 1.7.1