From: Kuo-Jung Su <dant...@faraday-tech.com> The FTRTC011 QEMU model is implemented without calender functions. It acts in counter mode only, all the time & timezone conversion relies on the c runtime library. (i.e. mktime(), localtime() ...etc)
Signed-off-by: Kuo-Jung Su <dant...@faraday-tech.com> --- tests/Makefile | 3 + tests/ftrtc011-test.c | 410 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 413 insertions(+) create mode 100644 tests/ftrtc011-test.c diff --git a/tests/Makefile b/tests/Makefile index 567e36e..5d0776e 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -75,6 +75,8 @@ gcov-files-sparc-y += hw/m48t59.c gcov-files-sparc64-y += hw/m48t59.c check-qtest-arm-y = tests/tmp105-test$(EXESUF) gcov-files-arm-y += hw/tmp105.c +check-qtest-arm-y = tests/ftrtc011-test$(EXESUF) +gcov-files-arm-y += hw/arm/ftrtc011.c GENERATED_HEADERS += tests/test-qapi-types.h tests/test-qapi-visit.h tests/test-qmp-commands.h @@ -133,6 +135,7 @@ tests/m48t59-test$(EXESUF): tests/m48t59-test.o tests/fdc-test$(EXESUF): tests/fdc-test.o tests/hd-geo-test$(EXESUF): tests/hd-geo-test.o tests/tmp105-test$(EXESUF): tests/tmp105-test.o +tests/ftrtc011-test$(EXESUF): tests/ftrtc011-test.o # QTest rules diff --git a/tests/ftrtc011-test.c b/tests/ftrtc011-test.c new file mode 100644 index 0000000..e2324fa --- /dev/null +++ b/tests/ftrtc011-test.c @@ -0,0 +1,410 @@ +/* + * QTest testcase for the FTRTC011 real-time clock + * + * Copyright (c) 2013 Kuo-Jung Su + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#include "libqtest.h" +#include "hw/arm/ftrtc011.h" + +#include <glib.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#define A369_FTRTC011_BASE 0x92100000 +#define A369_FTRTC011_IRQ_ALARM 42 /* edge triggered */ +#define A369_FTRTC011_IRQ_SEC 43 /* edge triggered */ +#define A369_FTRTC011_IRQ_MIN 44 /* edge triggered */ +#define A369_FTRTC011_IRQ_HOUR 45 /* edge triggered */ + +#define CFG_BASEYEAR 2010 + +static time_t ts_base; + +static uint32_t rtc_read(uint32_t reg) +{ + return readl(A369_FTRTC011_BASE + reg); +} + +static void rtc_write(uint32_t reg, uint32_t val) +{ + writel(A369_FTRTC011_BASE + reg, val); +} + +static int rtc_get_irq(int irq) +{ +#if 0 /* It looks to me that get_irq() doesn't work well + * with edge interrupts. + */ + return get_irq(irq); +#else + switch (irq) { + case A369_FTRTC011_IRQ_ALARM: + return !!(rtc_read(REG_ISR) & ISR_ALARM); + case A369_FTRTC011_IRQ_SEC: + return !!(rtc_read(REG_ISR) & ISR_SEC); + case A369_FTRTC011_IRQ_MIN: + return !!(rtc_read(REG_ISR) & ISR_MIN); + case A369_FTRTC011_IRQ_HOUR: + return !!(rtc_read(REG_ISR) & ISR_HOUR); + default: + return 0; + } +#endif +} + +static int tm_cmp(struct tm *lhs, struct tm *rhs) +{ + time_t a, b; + struct tm d1, d2; + + memcpy(&d1, lhs, sizeof(d1)); + memcpy(&d2, rhs, sizeof(d2)); + + a = mktime(&d1); + b = mktime(&d2); + + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } + + return 0; +} + +static void rtc_start(void) +{ + time_t ts; + struct tm base; + + if (!ts_base) { + base.tm_isdst = -1; + base.tm_year = CFG_BASEYEAR - 1900; + base.tm_mon = 0; + base.tm_mday = 1; + base.tm_hour = 0; + base.tm_min = 0; + base.tm_sec = 0; + ts_base = mktime(&base); + } + + ts = time(NULL) - ts_base; + rtc_write(REG_WDAY, ts / 86400LL); + ts %= 86400LL; + rtc_write(REG_WHOUR, ts / 3600LL); + ts %= 3600LL; + rtc_write(REG_WMIN, ts / 60LL); + ts %= 60LL; + rtc_write(REG_WSEC, ts); + + rtc_write(REG_ISR, ISR_MASK); + rtc_write(REG_CR, CR_EN | CR_LOAD | CR_INTR_MASK); +} + +static void rtc_get_datetime(struct tm *date) +{ + time_t ts; + int64_t sec, min, hour, day; + + if (!ts_base) { + fprintf(stderr, "ts_base is not yet initialized!\n"); + exit(1); + } + + sec = rtc_read(REG_SEC); + min = rtc_read(REG_MIN); + hour = rtc_read(REG_HOUR); + day = rtc_read(REG_DAY); + ts = ts_base + (86400LL * day) + (hour * 3600LL) + (min * 60LL) + sec; + + localtime_r(&ts, date); +} + +static void rtc_test_check_time(int wiggle) +{ + struct tm start, date[4], end; + struct tm *datep; + time_t ts; + + rtc_start(); + + /* + * This check assumes a few things. + * First, we cannot guarantee that we get a consistent reading + * from the wall clock because we may hit an edge of the clock + * while reading. + * To work around this, we read four clock readings such that + * at least two of them should match. We need to assume that one + * reading is corrupt so we need four readings to ensure that we have at + * least two consecutive identical readings + * + * It's also possible that we'll cross an edge reading the host clock so + * simply check to make sure that the clock reading is within the period of + * when we expect it to be. + */ + + ts = time(NULL); + localtime_r(&ts, &start); + + rtc_get_datetime(&date[0]); + rtc_get_datetime(&date[1]); + rtc_get_datetime(&date[2]); + rtc_get_datetime(&date[3]); + + ts = time(NULL); + localtime_r(&ts, &end); + + if (tm_cmp(&date[0], &date[1]) == 0) { + datep = &date[0]; + } else if (tm_cmp(&date[1], &date[2]) == 0) { + datep = &date[1]; + } else if (tm_cmp(&date[2], &date[3]) == 0) { + datep = &date[2]; + } else { + g_assert_not_reached(); + } + + if (!(tm_cmp(&start, datep) <= 0 && tm_cmp(datep, &end) <= 0)) { + time_t t, s; + + start.tm_isdst = datep->tm_isdst; + + t = mktime(datep); + s = mktime(&start); + if (t < s) { + g_test_message("RTC is %ld second(s) behind wall-clock\n", + (s - t)); + } else { + g_test_message("RTC is %ld second(s) ahead of wall-clock\n", + (t - s)); + } + + g_assert_cmpint(ABS(t - s), <=, wiggle); + } +} + +static int wiggle = 2; + +static void rtc_test_intr_alarm(void) +{ + time_t ts; + int i; + + rtc_start(); + + g_assert(!rtc_get_irq(A369_FTRTC011_IRQ_ALARM)); + + /* schedule an alarm at 2 sec later */ + ts = time(NULL) + 2 - ts_base; + ts %= 86400LL; + rtc_write(REG_ALARM_HOUR, ts / 3600LL); + ts %= 3600LL; + rtc_write(REG_ALARM_MIN, ts / 60LL); + ts %= 60LL; + rtc_write(REG_ALARM_SEC, ts); + + for (i = 0; i < 2 + wiggle; i++) { + /* + * Since it's an edge interrupt (pulse), + * is it always possible to get the high level signal observed? + */ + if (rtc_get_irq(A369_FTRTC011_IRQ_ALARM)) { + break; + } + + clock_step(1000000000LL); + } + + g_assert(i < 2 + wiggle); +} + +static void rtc_test_intr_sec(void) +{ + int i; + + rtc_start(); + + g_assert(!rtc_get_irq(A369_FTRTC011_IRQ_SEC)); + + for (i = 2; i > 0; i--) { + + if (rtc_get_irq(A369_FTRTC011_IRQ_SEC)) { + break; + } + + clock_step(1000000000); + } + + g_assert(i > 0); +} + +static void rtc_test_intr_min(void) +{ + int i; + + rtc_start(); + + g_assert(!get_irq(A369_FTRTC011_IRQ_MIN)); + + for (i = 60 + 2; i > 0; i--) { + + if (rtc_get_irq(A369_FTRTC011_IRQ_MIN)) { + break; + } + + clock_step(1000000000); + } + + g_assert(i > 0); +} + +static void rtc_test_intr_hour(void) +{ + int i; + + rtc_start(); + + g_assert(!get_irq(A369_FTRTC011_IRQ_HOUR)); + + for (i = 3600 + 2; i > 0; i--) { + + if (rtc_get_irq(A369_FTRTC011_IRQ_HOUR)) { + break; + } + + clock_step(1000000000); + } + + g_assert(i > 0); +} + +static void set_time(int h, int m, int s) +{ + rtc_write(REG_WDAY, rtc_read(REG_DAY)); + rtc_write(REG_WHOUR, h); + rtc_write(REG_WMIN, m); + rtc_write(REG_WSEC, s); + + /* update rtc counter */ + rtc_write(REG_CR, rtc_read(REG_CR) | CR_LOAD); +} + +#define assert_time(h, m, s) \ + do { \ + g_assert_cmpint(rtc_read(REG_HOUR), ==, h); \ + g_assert_cmpint(rtc_read(REG_MIN), ==, m); \ + g_assert_cmpint(rtc_read(REG_SEC), ==, s); \ + } while (0) + +static void rtc_test_basic_24h(void) +{ + rtc_start(); + + /* set decimal 24 hour mode */ + set_time(9, 59, 0); + clock_step(1000000000LL); + assert_time(9, 59, 1); + clock_step(59000000000LL); + assert_time(10, 0, 0); + + /* test hour wraparound */ + set_time(9, 59, 0); + clock_step(60000000000LL); + assert_time(10, 0, 0); + + /* test day wraparound */ + set_time(23, 59, 0); + clock_step(60000000000LL); + assert_time(0, 0, 0); +} + +static void rtc_test_set_year(void) +{ + char t[256]; + int i, year; + time_t ts; + struct tm date; + + rtc_start(); + + for (i = 0; i < 1000; ++i) { + year = CFG_BASEYEAR + g_test_rand_int_range(0, 20); + + date.tm_isdst = -1; + date.tm_year = year - 1900; + date.tm_mon = g_test_rand_int_range(0, 11); + date.tm_mday = g_test_rand_int_range(1, 25); + date.tm_hour = g_test_rand_int_range(0, 23); + date.tm_min = g_test_rand_int_range(0, 59); + date.tm_sec = g_test_rand_int_range(0, 59); + + asctime_r(&date, t); + + ts = mktime(&date) - ts_base; + rtc_write(REG_WDAY, ts / 86400LL); + ts %= 86400LL; + rtc_write(REG_WHOUR, ts / 3600LL); + ts %= 3600LL; + rtc_write(REG_WMIN, ts / 60LL); + ts %= 60LL; + rtc_write(REG_WSEC, ts); + + /* update rtc counter */ + rtc_write(REG_CR, rtc_read(REG_CR) | CR_LOAD); + + rtc_get_datetime(&date); + + asctime_r(&date, t); + + g_assert(date.tm_year == year - 1900); + } +} + +/* success if no crash or abort */ +static void rtc_test_fuzz(void) +{ + unsigned int i; + + rtc_start(); + + for (i = 0; i < 1000; i++) { + uint8_t reg, val; + + reg = (uint8_t)g_test_rand_int_range(0x00, 0x34) & 0xfc; + val = (uint8_t)g_test_rand_int_range(0, 256); + + rtc_write(reg, val); + rtc_read(reg); + } +} + +int main(int argc, char **argv) +{ + QTestState *s = NULL; + int ret; + + g_test_init(&argc, &argv, NULL); + + s = qtest_start("-M a369 -nographic -kernel /dev/zero -rtc clock=vm"); + qtest_irq_intercept_in(s, "ftrtc011"); + qtest_add_func("/rtc/check-time", rtc_test_check_time); + qtest_add_func("/rtc/interrupt/alarm", rtc_test_intr_alarm); + qtest_add_func("/rtc/interrupt/sec", rtc_test_intr_sec); + qtest_add_func("/rtc/interrupt/min", rtc_test_intr_min); + qtest_add_func("/rtc/interrupt/hour", rtc_test_intr_hour); + qtest_add_func("/rtc/basic-24h", rtc_test_basic_24h); + qtest_add_func("/rtc/set-year", rtc_test_set_year); + qtest_add_func("/rtc/fuzz-registers", rtc_test_fuzz); + ret = g_test_run(); + + if (s) { + qtest_quit(s); + } + + return ret; +} -- 1.7.9.5