qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH v4 6/7] RTC:Add alarm support


From: Zhang, Yang Z
Subject: [Qemu-devel] [PATCH v4 6/7] RTC:Add alarm support
Date: Mon, 19 Mar 2012 06:14:31 +0000

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 <address@hidden>
---
 hw/mc146818rtc.c |  274 ++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 255 insertions(+), 19 deletions(-)

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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]