Patchwork [v2,3/4] RTC:Add alarm support

login
register
mail settings
Submitter Zhang, Yang Z
Date Feb. 20, 2012, 12:25 a.m.
Message ID <A9667DDFB95DB7438FA9D7D576C3D87E071FFF@SHSMSX101.ccr.corp.intel.com>
Download mbox | patch
Permalink /patch/142091/
State New
Headers show

Comments

Zhang, Yang Z - Feb. 20, 2012, 12:25 a.m.
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

Patch

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);