diff mbox

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

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

Commit Message

Zhang, Yang Z March 19, 2012, 6:14 a.m. UTC
Changing in this patch: 
	Set the timer to one second earlier before target alarm when AF bit is clear. In version 3, in order to solve the async between UF, AF and UIP, the timer will keep running when UF or AF are clear. This is a little ugly, especially when a userspace program is using the alarm and we cannot achieve any power saving. In this version, when the AF bit is cleared, we will set the timer to one second earlier before the alarm. With this changing, we can avoid the unnecessary timer and keep the sync between UF, AF and UIP. 

Set the timer to one second earlier before target alarm when AF bit is clear.
 
Signed-off-by: Yang Zhang <yang.z.zhang@Intel.com>
---
 hw/mc146818rtc.c |  274 ++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 255 insertions(+), 19 deletions(-)

--
1.7.1
diff mbox

Patch

diff --git a/hw/mc146818rtc.c b/hw/mc146818rtc.c
index fae049e..c03606f 100644
--- a/hw/mc146818rtc.c
+++ b/hw/mc146818rtc.c
@@ -46,6 +46,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

@@ -114,6 +119,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;

@@ -232,29 +239,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)
@@ -267,15 +292,215 @@  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 (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) {
         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);
 }
@@ -316,6 +541,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:
@@ -373,6 +599,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);