diff mbox

[v6,6/7] RTC:Add alarm support

Message ID A9667DDFB95DB7438FA9D7D576C3D87E13A80C@SHSMSX101.ccr.corp.intel.com
State New
Headers show

Commit Message

Zhang, Yang Z May 17, 2012, 2:29 a.m. UTC
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
diff mbox

Patch

diff --git a/hw/mc146818rtc.c b/hw/mc146818rtc.c
index 461e8f3..956f10b 100644
--- a/hw/mc146818rtc.c
+++ b/hw/mc146818rtc.c
@@ -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);