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
@@ -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)
new file mode 100644
@@ -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