| 1 | /* |
| 2 | * QTest testcase for the M48T59 and M48T08 real-time clocks |
| 3 | * |
| 4 | * Based on MC146818 RTC test: |
| 5 | * Copyright IBM, Corp. 2012 |
| 6 | * |
| 7 | * Authors: |
| 8 | * Anthony Liguori <aliguori@us.ibm.com> |
| 9 | * |
| 10 | * This work is licensed under the terms of the GNU GPL, version 2 or later. |
| 11 | * See the COPYING file in the top-level directory. |
| 12 | * |
| 13 | */ |
| 14 | |
| 15 | #include "qemu/osdep.h" |
| 16 | |
| 17 | #include "libqtest.h" |
| 18 | |
| 19 | #define RTC_SECONDS 0x9 |
| 20 | #define RTC_MINUTES 0xa |
| 21 | #define RTC_HOURS 0xb |
| 22 | |
| 23 | #define RTC_DAY_OF_WEEK 0xc |
| 24 | #define RTC_DAY_OF_MONTH 0xd |
| 25 | #define RTC_MONTH 0xe |
| 26 | #define RTC_YEAR 0xf |
| 27 | |
| 28 | static uint32_t base; |
| 29 | static uint16_t reg_base = 0x1ff0; /* 0x7f0 for m48t02 */ |
| 30 | static int base_year; |
| 31 | static const char *base_machine; |
| 32 | static bool use_mmio; |
| 33 | |
| 34 | static uint8_t cmos_read_mmio(QTestState *s, uint8_t reg) |
| 35 | { |
| 36 | return qtest_readb(s, base + (uint32_t)reg_base + (uint32_t)reg); |
| 37 | } |
| 38 | |
| 39 | static void cmos_write_mmio(QTestState *s, uint8_t reg, uint8_t val) |
| 40 | { |
| 41 | uint8_t data = val; |
| 42 | |
| 43 | qtest_writeb(s, base + (uint32_t)reg_base + (uint32_t)reg, data); |
| 44 | } |
| 45 | |
| 46 | static uint8_t cmos_read_ioio(QTestState *s, uint8_t reg) |
| 47 | { |
| 48 | qtest_outw(s, base + 0, reg_base + (uint16_t)reg); |
| 49 | return qtest_inb(s, base + 3); |
| 50 | } |
| 51 | |
| 52 | static void cmos_write_ioio(QTestState *s, uint8_t reg, uint8_t val) |
| 53 | { |
| 54 | qtest_outw(s, base + 0, reg_base + (uint16_t)reg); |
| 55 | qtest_outb(s, base + 3, val); |
| 56 | } |
| 57 | |
| 58 | static uint8_t cmos_read(QTestState *s, uint8_t reg) |
| 59 | { |
| 60 | if (use_mmio) { |
| 61 | return cmos_read_mmio(s, reg); |
| 62 | } else { |
| 63 | return cmos_read_ioio(s, reg); |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | static void cmos_write(QTestState *s, uint8_t reg, uint8_t val) |
| 68 | { |
| 69 | if (use_mmio) { |
| 70 | cmos_write_mmio(s, reg, val); |
| 71 | } else { |
| 72 | cmos_write_ioio(s, reg, val); |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | static int bcd2dec(int value) |
| 77 | { |
| 78 | return (((value >> 4) & 0x0F) * 10) + (value & 0x0F); |
| 79 | } |
| 80 | |
| 81 | static int tm_cmp(struct tm *lhs, struct tm *rhs) |
| 82 | { |
| 83 | time_t a, b; |
| 84 | struct tm d1, d2; |
| 85 | |
| 86 | memcpy(&d1, lhs, sizeof(d1)); |
| 87 | memcpy(&d2, rhs, sizeof(d2)); |
| 88 | |
| 89 | a = mktime(&d1); |
| 90 | b = mktime(&d2); |
| 91 | |
| 92 | if (a < b) { |
| 93 | return -1; |
| 94 | } else if (a > b) { |
| 95 | return 1; |
| 96 | } |
| 97 | |
| 98 | return 0; |
| 99 | } |
| 100 | |
| 101 | #if 0 |
| 102 | static void print_tm(struct tm *tm) |
| 103 | { |
| 104 | printf("%04d-%02d-%02d %02d:%02d:%02d %+02ld\n", |
| 105 | tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, |
| 106 | tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_gmtoff); |
| 107 | } |
| 108 | #endif |
| 109 | |
| 110 | static void cmos_get_date_time(QTestState *s, struct tm *date) |
| 111 | { |
| 112 | int sec, min, hour, mday, mon, year; |
| 113 | time_t ts; |
| 114 | struct tm dummy; |
| 115 | |
| 116 | sec = cmos_read(s, RTC_SECONDS); |
| 117 | min = cmos_read(s, RTC_MINUTES); |
| 118 | hour = cmos_read(s, RTC_HOURS); |
| 119 | mday = cmos_read(s, RTC_DAY_OF_MONTH); |
| 120 | mon = cmos_read(s, RTC_MONTH); |
| 121 | year = cmos_read(s, RTC_YEAR); |
| 122 | |
| 123 | sec = bcd2dec(sec); |
| 124 | min = bcd2dec(min); |
| 125 | hour = bcd2dec(hour); |
| 126 | mday = bcd2dec(mday); |
| 127 | mon = bcd2dec(mon); |
| 128 | year = bcd2dec(year); |
| 129 | |
| 130 | ts = time(NULL); |
| 131 | localtime_r(&ts, &dummy); |
| 132 | |
| 133 | date->tm_isdst = dummy.tm_isdst; |
| 134 | date->tm_sec = sec; |
| 135 | date->tm_min = min; |
| 136 | date->tm_hour = hour; |
| 137 | date->tm_mday = mday; |
| 138 | date->tm_mon = mon - 1; |
| 139 | date->tm_year = base_year + year - 1900; |
| 140 | #if !defined(__sun__) && !defined(_WIN32) |
| 141 | date->tm_gmtoff = 0; |
| 142 | #endif |
| 143 | |
| 144 | ts = mktime(date); |
| 145 | } |
| 146 | |
| 147 | static QTestState *m48t59_qtest_start(void) |
| 148 | { |
| 149 | return qtest_initf("-M %s -rtc clock=vm", base_machine); |
| 150 | } |
| 151 | |
| 152 | static void bcd_check_time(void) |
| 153 | { |
| 154 | struct tm start, date[4], end; |
| 155 | struct tm *datep; |
| 156 | time_t ts; |
| 157 | const int wiggle = 2; |
| 158 | QTestState *qts = m48t59_qtest_start(); |
| 159 | |
| 160 | /* |
| 161 | * This check assumes a few things. First, we cannot guarantee that we get |
| 162 | * a consistent reading from the wall clock because we may hit an edge of |
| 163 | * the clock while reading. To work around this, we read four clock readings |
| 164 | * such that at least two of them should match. We need to assume that one |
| 165 | * reading is corrupt so we need four readings to ensure that we have at |
| 166 | * least two consecutive identical readings |
| 167 | * |
| 168 | * It's also possible that we'll cross an edge reading the host clock so |
| 169 | * simply check to make sure that the clock reading is within the period of |
| 170 | * when we expect it to be. |
| 171 | */ |
| 172 | |
| 173 | ts = time(NULL); |
| 174 | gmtime_r(&ts, &start); |
| 175 | |
| 176 | cmos_get_date_time(qts, &date[0]); |
| 177 | cmos_get_date_time(qts, &date[1]); |
| 178 | cmos_get_date_time(qts, &date[2]); |
| 179 | cmos_get_date_time(qts, &date[3]); |
| 180 | |
| 181 | ts = time(NULL); |
| 182 | gmtime_r(&ts, &end); |
| 183 | |
| 184 | if (tm_cmp(&date[0], &date[1]) == 0) { |
| 185 | datep = &date[0]; |
| 186 | } else if (tm_cmp(&date[1], &date[2]) == 0) { |
| 187 | datep = &date[1]; |
| 188 | } else if (tm_cmp(&date[2], &date[3]) == 0) { |
| 189 | datep = &date[2]; |
| 190 | } else { |
| 191 | g_assert_not_reached(); |
| 192 | } |
| 193 | |
| 194 | if (!(tm_cmp(&start, datep) <= 0 && tm_cmp(datep, &end) <= 0)) { |
| 195 | long date_s, start_s; |
| 196 | unsigned long diff; |
| 197 | |
| 198 | start.tm_isdst = datep->tm_isdst; |
| 199 | |
| 200 | date_s = (long)mktime(datep); |
| 201 | start_s = (long)mktime(&start); |
| 202 | if (date_s < start_s) { |
| 203 | diff = start_s - date_s; |
| 204 | g_test_message("RTC is %ld second(s) behind wall-clock", diff); |
| 205 | } else { |
| 206 | diff = date_s - start_s; |
| 207 | g_test_message("RTC is %ld second(s) ahead of wall-clock", diff); |
| 208 | } |
| 209 | |
| 210 | g_assert_cmpint(diff, <=, wiggle); |
| 211 | } |
| 212 | |
| 213 | qtest_quit(qts); |
| 214 | } |
| 215 | |
| 216 | /* success if no crash or abort */ |
| 217 | static void fuzz_registers(void) |
| 218 | { |
| 219 | unsigned int i; |
| 220 | QTestState *s = m48t59_qtest_start(); |
| 221 | |
| 222 | for (i = 0; i < 1000; i++) { |
| 223 | uint8_t reg, val; |
| 224 | |
| 225 | reg = (uint8_t)g_test_rand_int_range(0, 16); |
| 226 | val = (uint8_t)g_test_rand_int_range(0, 256); |
| 227 | |
| 228 | if (reg == 7) { |
| 229 | /* watchdog setup register, may trigger system reset, skip */ |
| 230 | continue; |
| 231 | } |
| 232 | |
| 233 | cmos_write(s, reg, val); |
| 234 | cmos_read(s, reg); |
| 235 | } |
| 236 | |
| 237 | qtest_quit(s); |
| 238 | } |
| 239 | |
| 240 | static void base_setup(void) |
| 241 | { |
| 242 | const char *arch = qtest_get_arch(); |
| 243 | |
| 244 | if (g_str_equal(arch, "sparc")) { |
| 245 | /* Note: For sparc64, we'd need to map-in the PCI bridge memory first */ |
| 246 | base = 0x71200000; |
| 247 | base_year = 1968; |
| 248 | base_machine = "SS-5"; |
| 249 | use_mmio = true; |
| 250 | } else { |
| 251 | g_assert_not_reached(); |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | int main(int argc, char **argv) |
| 256 | { |
| 257 | base_setup(); |
| 258 | |
| 259 | g_test_init(&argc, &argv, NULL); |
| 260 | if (qtest_has_machine(base_machine)) { |
| 261 | if (g_test_slow()) { |
| 262 | /* Do not run this in timing-sensitive environments */ |
| 263 | qtest_add_func("/rtc/bcd-check-time", bcd_check_time); |
| 264 | } |
| 265 | qtest_add_func("/rtc/fuzz-registers", fuzz_registers); |
| 266 | } |
| 267 | return g_test_run(); |
| 268 | } |