From f8d5e45941562cd8259523bd225c81a9dd841308 Mon Sep 17 00:00:00 2001
From: Paolo Bonzini <pbonzini@redhat.com>
Date: Tue, 22 Nov 2011 18:53:42 +0100
Subject: [PATCH] add rtc emulation tests

---
 config-x86-common.mak |    4 +-
 x86/rtc.c             |  294 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 297 insertions(+), 1 deletions(-)
 create mode 100644 x86/rtc.c

diff --git a/config-x86-common.mak b/config-x86-common.mak
index a093b8d..348c02a 100644
--- a/config-x86-common.mak
+++ b/config-x86-common.mak
@@ -34,7 +34,7 @@ tests-common = $(TEST_DIR)/vmexit.flat $(TEST_DIR)/tsc.flat \
                $(TEST_DIR)/realmode.flat $(TEST_DIR)/msr.flat \
                $(TEST_DIR)/hypercall.flat $(TEST_DIR)/sieve.flat \
                $(TEST_DIR)/kvmclock_test.flat  $(TEST_DIR)/eventinj.flat \
-               $(TEST_DIR)/s3.flat
+               $(TEST_DIR)/s3.flat $(TEST_DIR)/rtc.flat
 
 ifdef API
 tests-common += api/api-sample
@@ -77,6 +77,8 @@ $(TEST_DIR)/idt_test.elf: $(cstart.o) $(TEST_DIR)/idt_test.o
 
 $(TEST_DIR)/xsave.elf: $(cstart.o) $(TEST_DIR)/xsave.o
 
+$(TEST_DIR)/rtc.elf: $(cstart.o) $(TEST_DIR)/rtc.o
+
 $(TEST_DIR)/rmap_chain.elf: $(cstart.o) $(TEST_DIR)/rmap_chain.o
 
 $(TEST_DIR)/svm.elf: $(cstart.o)
diff --git a/x86/rtc.c b/x86/rtc.c
new file mode 100644
index 0000000..03cf4dc
--- /dev/null
+++ b/x86/rtc.c
@@ -0,0 +1,294 @@
+#include "io.h"
+#include "processor.h"
+#include "libcflat.h"
+
+#define RTC_SECONDS             0
+#define RTC_SECONDS_ALARM       1
+#define RTC_MINUTES             2
+#define RTC_MINUTES_ALARM       3
+#define RTC_HOURS               4
+#define RTC_HOURS_ALARM         5
+#define RTC_ALARM_DONT_CARE    0xC0
+
+#define RTC_DAY_OF_WEEK         6
+#define RTC_DAY_OF_MONTH        7
+#define RTC_MONTH               8
+#define RTC_YEAR                9
+
+#define RTC_REG_A               10
+#define RTC_REG_B               11
+#define RTC_REG_C               12
+#define RTC_REG_D               13
+
+#define REG_A_UIP 0x80
+
+#define REG_B_SET  0x80
+#define REG_B_PIE  0x40
+#define REG_B_AIE  0x20
+#define REG_B_UIE  0x10
+#define REG_B_SQWE 0x08
+#define REG_B_DM   0x04
+#define REG_B_24H  0x02
+
+#define REG_C_UF   0x10
+#define REG_C_IRQF 0x80
+#define REG_C_PF   0x40
+#define REG_C_AF   0x20
+
+static inline int rtc_in(int reg)
+{
+    //asm volatile("cli");
+    outb(reg, 0x70);
+    unsigned char x = inb(0x71);
+    //asm volatile("sti");
+    return x;
+}
+
+static inline void rtc_out(int reg, int val)
+{
+    //asm volatile("cli");
+    outb(reg, 0x70);
+    outb(val, 0x71);
+    //asm volatile("sti");
+}
+
+static inline void rtc_en(int bit)
+{
+    rtc_in(RTC_REG_C);
+    rtc_out(RTC_REG_B, rtc_in(RTC_REG_B) | bit);
+}
+
+static inline void rtc_dis(int bit)
+{
+    rtc_out(RTC_REG_B, rtc_in(RTC_REG_B) & ~bit);
+}
+
+static inline int rtc_uip()
+{
+    return (rtc_in(RTC_REG_A) & REG_A_UIP) != 0;
+}
+
+static inline void rtc_intr_wait(int bit)
+{
+    unsigned char x;
+    do
+       x = rtc_in(RTC_REG_C);
+    while ((x & bit) != bit);
+}
+
+static inline void rtc_bin()
+{
+    rtc_en(REG_B_DM);
+}
+
+static inline void rtc_bcd()
+{
+    rtc_dis(REG_B_DM);
+}
+
+static inline void rtc_set(int h, int m, int s)
+{
+    rtc_en(REG_B_SET);
+    rtc_out(RTC_HOURS, h);
+    rtc_out(RTC_MINUTES, m);
+    rtc_out(RTC_SECONDS, s);
+    rtc_dis(REG_B_SET);
+}
+
+static inline void rtc_set_alarm(int h, int m, int s)
+{
+    rtc_out(RTC_HOURS_ALARM, h);
+    rtc_out(RTC_MINUTES_ALARM, m);
+    rtc_out(RTC_SECONDS_ALARM, s);
+}
+
+static void rtc_dump(char *s)
+{
+    printf ("%s, time %x:%x:%x | alarm %x:%x:%x | a=%x b=%x c=%x\n", s,
+            rtc_in(RTC_HOURS), rtc_in(RTC_MINUTES), rtc_in(RTC_SECONDS),
+            rtc_in(RTC_HOURS_ALARM), rtc_in(RTC_MINUTES_ALARM), rtc_in(RTC_SECONDS_ALARM),
+            rtc_in(RTC_REG_A), rtc_in(RTC_REG_B), rtc_in(RTC_REG_C));
+}
+
+static inline void rtc_check(int x)
+{
+    rtc_dump(x ? "ok" : "bad!");
+}
+
+static void rtc_reread(int b_set, int h_set, int b, int h)
+{
+    int m, s;
+    rtc_out(RTC_REG_B, b_set);
+    rtc_set(h_set, 0x20, 0x30);
+    rtc_out(RTC_REG_B, b);
+    if (((b_set ^ b) & REG_B_DM) == 0)
+        m = 0x20, s = 0x30;
+    else if (b_set & REG_B_DM)
+        m = 0x32, s = 0x48;
+    else
+        m = 20, s = 30;
+
+    int ok = (rtc_in(RTC_HOURS) == h &&
+              rtc_in(RTC_MINUTES) == m &&
+              rtc_in(RTC_SECONDS) == s);
+    if (!ok) printf("%x ", h_set), rtc_dump("bad!");
+}
+
+#define PM |0x80
+
+static unsigned char flags[4] = { REG_B_DM, 0, REG_B_DM|REG_B_24H, REG_B_24H };
+static char *flags_string[4] = { "bin/12h", "BCD/12h", "bin/24h", "BCD/24h" };
+static unsigned char hours[4][24] = {
+    { 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 
+      12 PM, 1 PM, 2 PM, 3 PM, 4 PM, 5 PM, 6 PM, 7 PM, 8 PM, 9 PM, 10 PM, 11 PM,
+    },
+    { 0x12, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 
+      0x12 PM, 0x1 PM, 0x2 PM, 0x3 PM, 0x4 PM, 0x5 PM,
+      0x6 PM, 0x7 PM, 0x8 PM, 0x9 PM, 0x10 PM, 0x11 PM,
+    },
+    { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+      12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23
+    },
+    { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11,
+      0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23
+    }
+};
+
+int main()
+{
+    rtc_dis(REG_B_UIE | REG_B_PIE | REG_B_AIE);
+
+    rtc_dump("start");
+    printf("calibrating... ");
+    rtc_intr_wait(REG_C_UF);
+    long long cycles = rdtsc();
+    rtc_intr_wait(REG_C_UF);
+    long long cycles_per_sec = rdtsc() - cycles;
+    printf("%lld cycles/sec\n", cycles_per_sec);
+
+    /* UF is set when UIP resets.  */
+    while(rtc_uip());
+    rtc_intr_wait(REG_C_UF);
+    while(!rtc_uip());
+    while(rtc_uip());
+    rtc_check(rtc_in(RTC_REG_C) & REG_C_UF);
+
+    /* Test switching the DM and 24/12 bits.  */
+    for (int i = 0; i < 4; i++) {
+        /* The 24/12 bit cannot be changed without reinitializing the hours,
+         * so limit iteration to j < 2. */
+        for (int j = 0; j < 2; j++) {
+            printf ("set %s, get %s\n", flags_string[i], flags_string[i^j]);
+            for (int k = 0; k < 24; k++)
+                rtc_reread(flags[i], hours[i][k], flags[i^j], hours[i^j][k]);
+        }
+    }
+
+    /* Set bin/24hours */
+    rtc_bin();
+
+    /* Setting the clock resets UIP/UIE.  */
+    while(rtc_uip());
+    while(!rtc_uip());
+    rtc_en(REG_B_UIE);
+    rtc_check(rtc_uip());
+    rtc_check(rtc_in(RTC_REG_B) & REG_B_UIE);
+    rtc_set(0, 0, 0);
+    rtc_check(!rtc_uip());
+    rtc_check((rtc_in(RTC_REG_B) & REG_B_UIE) == 0);
+
+    /* The UIP bit should be set for at least 244 usec.  */
+    for (int i = 0; i < 5; i++) {
+        while(rtc_uip());
+        while(!rtc_uip());
+        cycles = rdtsc();
+        while(rtc_uip());
+        long long cycles_per_upd = rdtsc() - cycles;
+        printf("%lld cycles from UIP=1 to update (%lld usec)\n", cycles_per_upd,
+               cycles_per_upd * 1000000 / cycles_per_sec);
+        rtc_check(cycles_per_upd * 1000000 / cycles_per_sec > 244);
+
+        /* Too imprecise UIP time means the client will waste more time
+         * busy waiting.  Allow up to 3 ms (hardware does 2228 us).  */
+        rtc_check(cycles_per_upd * 1000000 / cycles_per_sec < 3000);
+    }
+
+    /* After completing a divider reset, the first update cycle begins
+     * one half-second later.
+     */
+    for (int i = 0; i < 5; i++) {
+        while(!rtc_uip());
+        rtc_out(RTC_REG_A, 0x70);
+        rtc_set(i, 0, 0);
+        rtc_out(RTC_REG_A, 0x20);
+        cycles = rdtsc();
+        while(!rtc_uip());
+        while(rtc_uip());
+        long long cycles_to_upd = rdtsc() - cycles;
+        rtc_check(rtc_in(RTC_SECONDS) == 1);
+        printf("%lld cycles from set to update (%lld msec)\n", cycles_to_upd,
+               cycles_to_upd * 1000 / cycles_per_sec);
+        rtc_check(cycles_to_upd * 1000 / cycles_per_sec > 400);
+        rtc_check(cycles_to_upd * 1000 / cycles_per_sec < 600);
+    }
+
+    /* Same as above, but test UF this time.  */
+    for (int i = 0; i < 5; i++) {
+        while(!rtc_uip());
+        rtc_out(RTC_REG_A, 0x70);
+        rtc_set(i, 0, 0);
+        rtc_out(RTC_REG_A, 0x20);
+        cycles = rdtsc();
+        rtc_intr_wait(REG_C_UF);
+        long long cycles_to_upd = rdtsc() - cycles;
+        rtc_check(rtc_in(RTC_SECONDS) == 1);
+        printf("%lld cycles from set to update interrupt (%lld msec)\n",
+               cycles_to_upd, cycles_to_upd * 1000 / cycles_per_sec);
+        rtc_check(cycles_to_upd * 1000 / cycles_per_sec > 400);
+        rtc_check(cycles_to_upd * 1000 / cycles_per_sec < 600);
+    }
+
+    /* Test consecutive alarms */
+    rtc_bin();
+    rtc_set(9, 0, 0);
+    rtc_in(RTC_REG_C);
+    for (int i = 2; i <= 5; i++) {
+        if (rtc_in(RTC_SECONDS) >= i)
+            break;
+        rtc_set_alarm(9, 0, i);
+        rtc_intr_wait(REG_C_AF);
+        rtc_check(rtc_in(RTC_SECONDS) == i);
+    }
+
+    /* Test spaced alarms in BCD mode */
+    rtc_bcd();
+    cycles = rdtsc();
+    rtc_set_alarm(9, 0, 0x10);
+    rtc_intr_wait(REG_C_AF);
+    long long cycles_to_alarm = rdtsc() - cycles;
+    rtc_check(rtc_in(RTC_SECONDS) == 0x10);
+    rtc_check(cycles_to_alarm / cycles_per_sec < 8);
+
+    /* Test spaced alarms in binary mode */
+    rtc_bin();
+    cycles = rdtsc();
+    rtc_set_alarm(9, 0, 18);
+    rtc_intr_wait(REG_C_AF);
+    cycles_to_alarm = rdtsc() - cycles;
+    rtc_check(rtc_in(RTC_SECONDS) == 18);
+    rtc_check(rtc_in(RTC_SECONDS_ALARM) == 18);
+    rtc_check(cycles_to_alarm / cycles_per_sec > 6);
+
+    /* Test consecutive alarms with wildcards */
+    for (int i = 0, secs = 18; i <= 3; i++) {
+        rtc_set_alarm(9, 0, 0xc0);
+        rtc_intr_wait(REG_C_AF);
+        rtc_check(rtc_in(RTC_SECONDS) == ++secs);
+        rtc_set_alarm(9, 0xc0, 0xc0);
+        rtc_intr_wait(REG_C_AF);
+        rtc_check(rtc_in(RTC_SECONDS) == ++secs);
+        rtc_set_alarm(0xc0, 0xc0, 0xc0);
+        rtc_intr_wait(REG_C_AF);
+        rtc_check(rtc_in(RTC_SECONDS) == ++secs);
+    }
+}
-- 
1.7.7.6

