@@ -47,6 +47,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
@@ -84,6 +89,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;
@@ -207,29 +214,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)
@@ -243,15 +268,217 @@ 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 (((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0
+ || alarm_sec == cur_sec) &&
+ ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0
+ || alarm_min == cur_min) &&
+ ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0
+ || alarm_hour == cur_hour)) {
+ return 1;
+ }
+ return 0;
+
+}
+
static void rtc_update_timer2(void *opaque)
{
RTCState *s = opaque;
+ int32_t alarm_fired = 0;
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);
}
@@ -278,6 +505,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:
@@ -343,6 +571,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);
Use a timer to emulate alarm. The timer runs only when the AF is cleared. Signed-off-by: Yang Zhang <yang.z.zhang@Intel.com> --- hw/mc146818rtc.c | 276 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 files changed, 257 insertions(+), 19 deletions(-) -- 1.7.1