| 1 | /* |
| 2 | * QTest testcase for the CMSDK APB watchdog device |
| 3 | * |
| 4 | * Copyright (c) 2021 Linaro Limited |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify it |
| 7 | * under the terms of the GNU General Public License as published by the |
| 8 | * Free Software Foundation; either version 2 of the License, or |
| 9 | * (at your option) any later version. |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, but WITHOUT |
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 14 | * for more details. |
| 15 | */ |
| 16 | |
| 17 | #include "qemu/osdep.h" |
| 18 | #include "exec/hwaddr.h" |
| 19 | #include "qemu/bitops.h" |
| 20 | #include "libqtest-single.h" |
| 21 | |
| 22 | #define WDOG_BASE 0x40000000 |
| 23 | #define WDOG_BASE_MPS2 0x40008000 |
| 24 | |
| 25 | #define WDOGLOAD 0 |
| 26 | #define WDOGVALUE 4 |
| 27 | #define WDOGCONTROL 8 |
| 28 | #define WDOGINTCLR 0xc |
| 29 | #define WDOGRIS 0x10 |
| 30 | #define WDOGMIS 0x14 |
| 31 | #define WDOGLOCK 0xc00 |
| 32 | |
| 33 | #define SSYS_BASE 0x400fe000 |
| 34 | #define RCC 0x60 |
| 35 | #define SYSDIV_SHIFT 23 |
| 36 | #define SYSDIV_LENGTH 4 |
| 37 | |
| 38 | #define WDOGLOAD_DEFAULT 0xFFFFFFFF |
| 39 | #define WDOGVALUE_DEFAULT 0xFFFFFFFF |
| 40 | |
| 41 | typedef struct CMSDKAPBWatchdogTestArgs { |
| 42 | int64_t tick; |
| 43 | hwaddr wdog_base; |
| 44 | const char *machine; |
| 45 | } CMSDKAPBWatchdogTestArgs; |
| 46 | |
| 47 | enum { |
| 48 | MACHINE_LM3S811EVB, |
| 49 | MACHINE_MPS2_AN385, |
| 50 | }; |
| 51 | |
| 52 | /* |
| 53 | * lm3s811evb watchdog; at board startup this runs at 200MHz / 16 == 12.5MHz, |
| 54 | * which is 80ns per tick. |
| 55 | * |
| 56 | * IoTKit/ARMSSE dualtimer; driven at 25MHz in mps2-an385, so 40ns per tick |
| 57 | */ |
| 58 | static const CMSDKAPBWatchdogTestArgs machine_info[] = { |
| 59 | [MACHINE_LM3S811EVB] = { |
| 60 | .tick = 80, |
| 61 | .wdog_base = WDOG_BASE, |
| 62 | .machine = "lm3s811evb", |
| 63 | }, |
| 64 | [MACHINE_MPS2_AN385] = { |
| 65 | .tick = 40, |
| 66 | .wdog_base = WDOG_BASE_MPS2, |
| 67 | .machine = "mps2-an385", |
| 68 | }, |
| 69 | }; |
| 70 | |
| 71 | static void system_reset(QTestState *qtest) |
| 72 | { |
| 73 | QDict *resp; |
| 74 | |
| 75 | resp = qtest_qmp(qtest, "{'execute': 'system_reset'}"); |
| 76 | g_assert(qdict_haskey(resp, "return")); |
| 77 | qobject_unref(resp); |
| 78 | qtest_qmp_eventwait(qtest, "RESET"); |
| 79 | } |
| 80 | |
| 81 | static void test_watchdog(const void *ptr) |
| 82 | { |
| 83 | const CMSDKAPBWatchdogTestArgs *args = ptr; |
| 84 | hwaddr wdog_base = args->wdog_base; |
| 85 | int64_t tick = args->tick; |
| 86 | g_autofree gchar *cmdline = g_strdup_printf("-machine %s", args->machine); |
| 87 | qtest_start(cmdline); |
| 88 | |
| 89 | g_assert_cmpuint(readl(wdog_base + WDOGRIS), ==, 0); |
| 90 | |
| 91 | writel(wdog_base + WDOGCONTROL, 1); |
| 92 | writel(wdog_base + WDOGLOAD, 1000); |
| 93 | |
| 94 | /* Step to just past the 500th tick */ |
| 95 | clock_step(500 * tick + 1); |
| 96 | g_assert_cmpuint(readl(wdog_base + WDOGRIS), ==, 0); |
| 97 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 500); |
| 98 | |
| 99 | /* Just past the 1000th tick: timer should have fired */ |
| 100 | clock_step(500 * tick); |
| 101 | g_assert_cmpuint(readl(wdog_base + WDOGRIS), ==, 1); |
| 102 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 0); |
| 103 | |
| 104 | /* VALUE reloads at following tick */ |
| 105 | clock_step(tick); |
| 106 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 1000); |
| 107 | |
| 108 | /* Writing any value to WDOGINTCLR clears the interrupt and reloads */ |
| 109 | clock_step(500 * tick); |
| 110 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 500); |
| 111 | g_assert_cmpuint(readl(wdog_base + WDOGRIS), ==, 1); |
| 112 | writel(wdog_base + WDOGINTCLR, 0); |
| 113 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 1000); |
| 114 | g_assert_cmpuint(readl(wdog_base + WDOGRIS), ==, 0); |
| 115 | |
| 116 | qtest_end(); |
| 117 | } |
| 118 | |
| 119 | /* |
| 120 | * This test can only be executed in the stellaris board since it relies on a |
| 121 | * component of the board to change the clocking parameters of the watchdog. |
| 122 | */ |
| 123 | static void test_clock_change(const void *ptr) |
| 124 | { |
| 125 | uint32_t rcc; |
| 126 | const CMSDKAPBWatchdogTestArgs *args = ptr; |
| 127 | g_autofree gchar *cmdline = g_strdup_printf("-machine %s", args->machine); |
| 128 | qtest_start(cmdline); |
| 129 | |
| 130 | /* |
| 131 | * Test that writing to the stellaris board's RCC register to |
| 132 | * change the system clock frequency causes the watchdog |
| 133 | * to change the speed it counts at. |
| 134 | */ |
| 135 | g_assert_cmpuint(readl(WDOG_BASE + WDOGRIS), ==, 0); |
| 136 | |
| 137 | writel(WDOG_BASE + WDOGCONTROL, 1); |
| 138 | writel(WDOG_BASE + WDOGLOAD, 1000); |
| 139 | |
| 140 | /* Step to just past the 500th tick */ |
| 141 | clock_step(80 * 500 + 1); |
| 142 | g_assert_cmpuint(readl(WDOG_BASE + WDOGRIS), ==, 0); |
| 143 | g_assert_cmpuint(readl(WDOG_BASE + WDOGVALUE), ==, 500); |
| 144 | |
| 145 | /* Rewrite RCC.SYSDIV from 16 to 8, so the clock is now 40ns per tick */ |
| 146 | rcc = readl(SSYS_BASE + RCC); |
| 147 | g_assert_cmphex(extract32(rcc, SYSDIV_SHIFT, SYSDIV_LENGTH), ==, 0xf); |
| 148 | rcc = deposit32(rcc, SYSDIV_SHIFT, SYSDIV_LENGTH, 7); |
| 149 | writel(SSYS_BASE + RCC, rcc); |
| 150 | |
| 151 | /* Just past the 1000th tick: timer should have fired */ |
| 152 | clock_step(40 * 500); |
| 153 | g_assert_cmpuint(readl(WDOG_BASE + WDOGRIS), ==, 1); |
| 154 | |
| 155 | g_assert_cmpuint(readl(WDOG_BASE + WDOGVALUE), ==, 0); |
| 156 | |
| 157 | /* VALUE reloads at following tick */ |
| 158 | clock_step(41); |
| 159 | g_assert_cmpuint(readl(WDOG_BASE + WDOGVALUE), ==, 1000); |
| 160 | |
| 161 | /* Writing any value to WDOGINTCLR clears the interrupt and reloads */ |
| 162 | clock_step(40 * 500); |
| 163 | g_assert_cmpuint(readl(WDOG_BASE + WDOGVALUE), ==, 500); |
| 164 | g_assert_cmpuint(readl(WDOG_BASE + WDOGRIS), ==, 1); |
| 165 | writel(WDOG_BASE + WDOGINTCLR, 0); |
| 166 | g_assert_cmpuint(readl(WDOG_BASE + WDOGVALUE), ==, 1000); |
| 167 | g_assert_cmpuint(readl(WDOG_BASE + WDOGRIS), ==, 0); |
| 168 | |
| 169 | qtest_end(); |
| 170 | } |
| 171 | |
| 172 | /* Tests the counter is not running after reset. */ |
| 173 | static void test_watchdog_reset(const void *ptr) |
| 174 | { |
| 175 | const CMSDKAPBWatchdogTestArgs *args = ptr; |
| 176 | hwaddr wdog_base = args->wdog_base; |
| 177 | int64_t tick = args->tick; |
| 178 | g_autofree gchar *cmdline = g_strdup_printf("-machine %s", args->machine); |
| 179 | qtest_start(cmdline); |
| 180 | g_assert_cmpuint(readl(wdog_base + WDOGRIS), ==, 0); |
| 181 | |
| 182 | g_assert_cmphex(readl(wdog_base + WDOGLOAD), ==, WDOGLOAD_DEFAULT); |
| 183 | g_assert_cmphex(readl(wdog_base + WDOGVALUE), ==, WDOGVALUE_DEFAULT); |
| 184 | |
| 185 | g_assert_cmphex(readl(wdog_base + WDOGCONTROL), ==, 0); |
| 186 | |
| 187 | /* |
| 188 | * The counter should not be running if WDOGCONTROL.INTEN has not been set, |
| 189 | * as it is the case after a cold reset. |
| 190 | */ |
| 191 | clock_step(15 * tick + 1); |
| 192 | g_assert_cmphex(readl(wdog_base + WDOGLOAD), ==, WDOGLOAD_DEFAULT); |
| 193 | g_assert_cmphex(readl(wdog_base + WDOGVALUE), ==, WDOGVALUE_DEFAULT); |
| 194 | |
| 195 | /* Let the counter run before reset */ |
| 196 | writel(wdog_base + WDOGLOAD, 3000); |
| 197 | writel(wdog_base + WDOGCONTROL, 1); |
| 198 | |
| 199 | /* Verify it is running */ |
| 200 | clock_step(1000 * tick + 1); |
| 201 | g_assert_cmpuint(readl(wdog_base + WDOGLOAD), ==, 3000); |
| 202 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 2000); |
| 203 | |
| 204 | system_reset(global_qtest); |
| 205 | |
| 206 | /* Check defaults after reset */ |
| 207 | g_assert_cmphex(readl(wdog_base + WDOGLOAD), ==, WDOGLOAD_DEFAULT); |
| 208 | g_assert_cmphex(readl(wdog_base + WDOGVALUE), ==, WDOGVALUE_DEFAULT); |
| 209 | |
| 210 | /* The counter should not be running after reset. */ |
| 211 | clock_step(1000 * tick + 1); |
| 212 | g_assert_cmphex(readl(wdog_base + WDOGLOAD), ==, WDOGLOAD_DEFAULT); |
| 213 | g_assert_cmphex(readl(wdog_base + WDOGVALUE), ==, WDOGVALUE_DEFAULT); |
| 214 | |
| 215 | qtest_end(); |
| 216 | } |
| 217 | |
| 218 | /* |
| 219 | * Tests inten works as the counter enable based on this description: |
| 220 | * |
| 221 | * Enable the interrupt event, WDOGINT. Set HIGH to enable the counter and the |
| 222 | * interrupt, or LOW to disable the counter and interrupt. Reloads the counter |
| 223 | * from the value in WDOGLOAD when the interrupt is enabled, after previously |
| 224 | * being disabled. |
| 225 | */ |
| 226 | static void test_watchdog_inten(const void *ptr) |
| 227 | { |
| 228 | const CMSDKAPBWatchdogTestArgs *args = ptr; |
| 229 | hwaddr wdog_base = args->wdog_base; |
| 230 | int64_t tick = args->tick; |
| 231 | g_autofree gchar *cmdline = g_strdup_printf("-machine %s", args->machine); |
| 232 | qtest_start(cmdline); |
| 233 | g_assert_cmpuint(readl(wdog_base + WDOGRIS), ==, 0); |
| 234 | |
| 235 | g_assert_cmphex(readl(wdog_base + WDOGLOAD), ==, WDOGLOAD_DEFAULT); |
| 236 | g_assert_cmphex(readl(wdog_base + WDOGVALUE), ==, WDOGVALUE_DEFAULT); |
| 237 | |
| 238 | /* |
| 239 | * When WDOGLOAD is written to, the count is immediately restarted from the |
| 240 | * new value. |
| 241 | * |
| 242 | * Note: the counter should not be running as long as WDOGCONTROL.INTEN is |
| 243 | * not set |
| 244 | */ |
| 245 | writel(wdog_base + WDOGLOAD, 4000); |
| 246 | g_assert_cmpuint(readl(wdog_base + WDOGLOAD), ==, 4000); |
| 247 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 4000); |
| 248 | clock_step(500 * tick + 1); |
| 249 | g_assert_cmpuint(readl(wdog_base + WDOGLOAD), ==, 4000); |
| 250 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 4000); |
| 251 | |
| 252 | /* Set HIGH WDOGCONTROL.INTEN to enable the counter and the interrupt */ |
| 253 | writel(wdog_base + WDOGCONTROL, 1); |
| 254 | clock_step(500 * tick + 1); |
| 255 | g_assert_cmpuint(readl(wdog_base + WDOGLOAD), ==, 4000); |
| 256 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 3500); |
| 257 | |
| 258 | /* or LOW to disable the counter and interrupt. */ |
| 259 | writel(wdog_base + WDOGCONTROL, 0); |
| 260 | clock_step(100 * tick); |
| 261 | g_assert_cmpuint(readl(wdog_base + WDOGLOAD), ==, 4000); |
| 262 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 3500); |
| 263 | |
| 264 | /* |
| 265 | * Reloads the counter from the value in WDOGLOAD when the interrupt is |
| 266 | * enabled, after previously being disabled. |
| 267 | */ |
| 268 | writel(wdog_base + WDOGCONTROL, 1); |
| 269 | g_assert_cmpuint(readl(wdog_base + WDOGLOAD), ==, 4000); |
| 270 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 4000); |
| 271 | |
| 272 | /* Test counter is still on */ |
| 273 | clock_step(50 * tick + 1); |
| 274 | g_assert_cmpuint(readl(wdog_base + WDOGLOAD), ==, 4000); |
| 275 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 3950); |
| 276 | |
| 277 | /* |
| 278 | * When WDOGLOAD is written to, the count is immediately restarted from the |
| 279 | * new value. |
| 280 | * |
| 281 | * Note: the counter should be running since WDOGCONTROL.INTEN is set |
| 282 | */ |
| 283 | writel(wdog_base + WDOGLOAD, 5000); |
| 284 | g_assert_cmpuint(readl(wdog_base + WDOGLOAD), ==, 5000); |
| 285 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 5000); |
| 286 | clock_step(4999 * tick + 1); |
| 287 | g_assert_cmpuint(readl(wdog_base + WDOGLOAD), ==, 5000); |
| 288 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 1); |
| 289 | g_assert_cmpuint(readl(wdog_base + WDOGRIS), ==, 0); |
| 290 | |
| 291 | /* Finally disable and check the conditions don't change */ |
| 292 | writel(wdog_base + WDOGCONTROL, 0); |
| 293 | clock_step(10 * tick); |
| 294 | g_assert_cmpuint(readl(wdog_base + WDOGLOAD), ==, 5000); |
| 295 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 1); |
| 296 | g_assert_cmpuint(readl(wdog_base + WDOGRIS), ==, 0); |
| 297 | |
| 298 | qtest_end(); |
| 299 | } |
| 300 | |
| 301 | /* |
| 302 | * Tests the following custom behavior: |
| 303 | * |
| 304 | * The Luminary version of this device ignores writes to this register after the |
| 305 | * guest has enabled interrupts (so they can only be disabled again via reset). |
| 306 | */ |
| 307 | static void test_watchdog_inten_luminary(const void *ptr) |
| 308 | { |
| 309 | const CMSDKAPBWatchdogTestArgs *args = ptr; |
| 310 | hwaddr wdog_base = args->wdog_base; |
| 311 | int64_t tick = args->tick; |
| 312 | g_autofree gchar *cmdline = g_strdup_printf("-machine %s", args->machine); |
| 313 | qtest_start(cmdline); |
| 314 | g_assert_cmpuint(readl(wdog_base + WDOGRIS), ==, 0); |
| 315 | |
| 316 | g_assert_cmphex(readl(wdog_base + WDOGLOAD), ==, WDOGLOAD_DEFAULT); |
| 317 | g_assert_cmphex(readl(wdog_base + WDOGVALUE), ==, WDOGVALUE_DEFAULT); |
| 318 | |
| 319 | /* |
| 320 | * When WDOGLOAD is written to, the count is immediately restarted from the |
| 321 | * new value. |
| 322 | * |
| 323 | * Note: the counter should not be running as long as WDOGCONTROL.INTEN is |
| 324 | * not set |
| 325 | */ |
| 326 | writel(wdog_base + WDOGLOAD, 4000); |
| 327 | g_assert_cmpuint(readl(wdog_base + WDOGLOAD), ==, 4000); |
| 328 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 4000); |
| 329 | clock_step(500 * tick + 1); |
| 330 | g_assert_cmpuint(readl(wdog_base + WDOGLOAD), ==, 4000); |
| 331 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 4000); |
| 332 | |
| 333 | /* Set HIGH WDOGCONTROL.INTEN to enable the counter and the interrupt */ |
| 334 | writel(wdog_base + WDOGCONTROL, 1); |
| 335 | clock_step(500 * tick + 1); |
| 336 | g_assert_cmpuint(readl(wdog_base + WDOGLOAD), ==, 4000); |
| 337 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 3500); |
| 338 | |
| 339 | /* |
| 340 | * The Luminary version of this device ignores writes to this register after |
| 341 | * the guest has enabled interrupts |
| 342 | */ |
| 343 | writel(wdog_base + WDOGCONTROL, 0); |
| 344 | clock_step(100 * tick); |
| 345 | g_assert_cmpuint(readl(wdog_base + WDOGLOAD), ==, 4000); |
| 346 | g_assert_cmpuint(readl(wdog_base + WDOGVALUE), ==, 3400); |
| 347 | g_assert_cmphex(readl(wdog_base + WDOGCONTROL), ==, 0x1); |
| 348 | |
| 349 | /* They can only be disabled again via reset */ |
| 350 | system_reset(global_qtest); |
| 351 | |
| 352 | /* Check defaults after reset */ |
| 353 | g_assert_cmphex(readl(wdog_base + WDOGLOAD), ==, WDOGLOAD_DEFAULT); |
| 354 | g_assert_cmphex(readl(wdog_base + WDOGVALUE), ==, WDOGVALUE_DEFAULT); |
| 355 | g_assert_cmphex(readl(wdog_base + WDOGCONTROL), ==, 0); |
| 356 | |
| 357 | /* The counter should not be running after reset. */ |
| 358 | clock_step(1000 * tick + 1); |
| 359 | g_assert_cmphex(readl(wdog_base + WDOGLOAD), ==, WDOGLOAD_DEFAULT); |
| 360 | g_assert_cmphex(readl(wdog_base + WDOGVALUE), ==, WDOGVALUE_DEFAULT); |
| 361 | |
| 362 | qtest_end(); |
| 363 | } |
| 364 | |
| 365 | int main(int argc, char **argv) |
| 366 | { |
| 367 | g_test_init(&argc, &argv, NULL); |
| 368 | g_test_set_nonfatal_assertions(); |
| 369 | |
| 370 | if (qtest_has_machine(machine_info[MACHINE_LM3S811EVB].machine)) { |
| 371 | qtest_add_data_func("/cmsdk-apb-watchdog/watchdog", |
| 372 | &machine_info[MACHINE_LM3S811EVB], test_watchdog); |
| 373 | qtest_add_data_func("/cmsdk-apb-watchdog/watchdog_clock_change", |
| 374 | &machine_info[MACHINE_LM3S811EVB], |
| 375 | test_clock_change); |
| 376 | qtest_add_data_func("/cmsdk-apb-watchdog/watchdog_reset", |
| 377 | &machine_info[MACHINE_LM3S811EVB], |
| 378 | test_watchdog_reset); |
| 379 | qtest_add_data_func("/cmsdk-apb-watchdog/watchdog_inten_luminary", |
| 380 | &machine_info[MACHINE_LM3S811EVB], |
| 381 | test_watchdog_inten_luminary); |
| 382 | } |
| 383 | if (qtest_has_machine(machine_info[MACHINE_MPS2_AN385].machine)) { |
| 384 | qtest_add_data_func("/cmsdk-apb-watchdog/watchdog_mps2", |
| 385 | &machine_info[MACHINE_MPS2_AN385], test_watchdog); |
| 386 | qtest_add_data_func("/cmsdk-apb-watchdog/watchdog_reset_mps2", |
| 387 | &machine_info[MACHINE_MPS2_AN385], |
| 388 | test_watchdog_reset); |
| 389 | qtest_add_data_func("/cmsdk-apb-watchdog/watchdog_inten", |
| 390 | &machine_info[MACHINE_MPS2_AN385], |
| 391 | test_watchdog_inten); |
| 392 | } |
| 393 | |
| 394 | return g_test_run(); |
| 395 | } |