320 lines
10 KiB
C
320 lines
10 KiB
C
|
/*
|
||
|
* QTests for Nuvoton NPCM7xx Timer Watchdog Modules.
|
||
|
*
|
||
|
* Copyright 2020 Google LLC
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify it
|
||
|
* under the terms of the GNU General Public License as published by the
|
||
|
* Free Software Foundation; either version 2 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||
|
* for more details.
|
||
|
*/
|
||
|
|
||
|
#include "qemu/osdep.h"
|
||
|
#include "qemu/timer.h"
|
||
|
|
||
|
#include "libqos/libqtest.h"
|
||
|
#include "qapi/qmp/qdict.h"
|
||
|
|
||
|
#define WTCR_OFFSET 0x1c
|
||
|
#define REF_HZ (25000000)
|
||
|
|
||
|
/* WTCR bit fields */
|
||
|
#define WTCLK(rv) ((rv) << 10)
|
||
|
#define WTE BIT(7)
|
||
|
#define WTIE BIT(6)
|
||
|
#define WTIS(rv) ((rv) << 4)
|
||
|
#define WTIF BIT(3)
|
||
|
#define WTRF BIT(2)
|
||
|
#define WTRE BIT(1)
|
||
|
#define WTR BIT(0)
|
||
|
|
||
|
typedef struct Watchdog {
|
||
|
int irq;
|
||
|
uint64_t base_addr;
|
||
|
} Watchdog;
|
||
|
|
||
|
static const Watchdog watchdog_list[] = {
|
||
|
{
|
||
|
.irq = 47,
|
||
|
.base_addr = 0xf0008000
|
||
|
},
|
||
|
{
|
||
|
.irq = 48,
|
||
|
.base_addr = 0xf0009000
|
||
|
},
|
||
|
{
|
||
|
.irq = 49,
|
||
|
.base_addr = 0xf000a000
|
||
|
}
|
||
|
};
|
||
|
|
||
|
static int watchdog_index(const Watchdog *wd)
|
||
|
{
|
||
|
ptrdiff_t diff = wd - watchdog_list;
|
||
|
|
||
|
g_assert(diff >= 0 && diff < ARRAY_SIZE(watchdog_list));
|
||
|
|
||
|
return diff;
|
||
|
}
|
||
|
|
||
|
static uint32_t watchdog_read_wtcr(QTestState *qts, const Watchdog *wd)
|
||
|
{
|
||
|
return qtest_readl(qts, wd->base_addr + WTCR_OFFSET);
|
||
|
}
|
||
|
|
||
|
static void watchdog_write_wtcr(QTestState *qts, const Watchdog *wd,
|
||
|
uint32_t value)
|
||
|
{
|
||
|
qtest_writel(qts, wd->base_addr + WTCR_OFFSET, value);
|
||
|
}
|
||
|
|
||
|
static uint32_t watchdog_prescaler(QTestState *qts, const Watchdog *wd)
|
||
|
{
|
||
|
switch (extract32(watchdog_read_wtcr(qts, wd), 10, 2)) {
|
||
|
case 0:
|
||
|
return 1;
|
||
|
case 1:
|
||
|
return 256;
|
||
|
case 2:
|
||
|
return 2048;
|
||
|
case 3:
|
||
|
return 65536;
|
||
|
default:
|
||
|
g_assert_not_reached();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static QDict *get_watchdog_action(QTestState *qts)
|
||
|
{
|
||
|
QDict *ev = qtest_qmp_eventwait_ref(qts, "WATCHDOG");
|
||
|
QDict *data;
|
||
|
|
||
|
data = qdict_get_qdict(ev, "data");
|
||
|
qobject_ref(data);
|
||
|
qobject_unref(ev);
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
#define RESET_CYCLES 1024
|
||
|
static uint32_t watchdog_interrupt_cycles(QTestState *qts, const Watchdog *wd)
|
||
|
{
|
||
|
uint32_t wtis = extract32(watchdog_read_wtcr(qts, wd), 4, 2);
|
||
|
return 1 << (14 + 2 * wtis);
|
||
|
}
|
||
|
|
||
|
static int64_t watchdog_calculate_steps(uint32_t count, uint32_t prescale)
|
||
|
{
|
||
|
return (NANOSECONDS_PER_SECOND / REF_HZ) * count * prescale;
|
||
|
}
|
||
|
|
||
|
static int64_t watchdog_interrupt_steps(QTestState *qts, const Watchdog *wd)
|
||
|
{
|
||
|
return watchdog_calculate_steps(watchdog_interrupt_cycles(qts, wd),
|
||
|
watchdog_prescaler(qts, wd));
|
||
|
}
|
||
|
|
||
|
/* Check wtcr can be reset to default value */
|
||
|
static void test_init(gconstpointer watchdog)
|
||
|
{
|
||
|
const Watchdog *wd = watchdog;
|
||
|
QTestState *qts = qtest_init("-machine quanta-gsj");
|
||
|
|
||
|
qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
|
||
|
|
||
|
watchdog_write_wtcr(qts, wd, WTCLK(1) | WTRF | WTIF | WTR);
|
||
|
g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==, WTCLK(1));
|
||
|
|
||
|
qtest_quit(qts);
|
||
|
}
|
||
|
|
||
|
/* Check a watchdog can generate interrupt and reset actions */
|
||
|
static void test_reset_action(gconstpointer watchdog)
|
||
|
{
|
||
|
const Watchdog *wd = watchdog;
|
||
|
QTestState *qts = qtest_init("-machine quanta-gsj");
|
||
|
QDict *ad;
|
||
|
|
||
|
qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
|
||
|
|
||
|
watchdog_write_wtcr(qts, wd,
|
||
|
WTCLK(0) | WTE | WTRF | WTRE | WTIF | WTIE | WTR);
|
||
|
g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==,
|
||
|
WTCLK(0) | WTE | WTRE | WTIE);
|
||
|
|
||
|
/* Check a watchdog can generate an interrupt */
|
||
|
qtest_clock_step(qts, watchdog_interrupt_steps(qts, wd));
|
||
|
g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==,
|
||
|
WTCLK(0) | WTE | WTIF | WTIE | WTRE);
|
||
|
g_assert_true(qtest_get_irq(qts, wd->irq));
|
||
|
|
||
|
/* Check a watchdog can generate a reset signal */
|
||
|
qtest_clock_step(qts, watchdog_calculate_steps(RESET_CYCLES,
|
||
|
watchdog_prescaler(qts, wd)));
|
||
|
ad = get_watchdog_action(qts);
|
||
|
/* The signal is a reset signal */
|
||
|
g_assert_false(strcmp(qdict_get_str(ad, "action"), "reset"));
|
||
|
qobject_unref(ad);
|
||
|
qtest_qmp_eventwait(qts, "RESET");
|
||
|
/*
|
||
|
* Make sure WTCR is reset to default except for WTRF bit which shouldn't
|
||
|
* be reset.
|
||
|
*/
|
||
|
g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==, WTCLK(1) | WTRF);
|
||
|
qtest_quit(qts);
|
||
|
}
|
||
|
|
||
|
/* Check a watchdog works with all possible WTCLK prescalers and WTIS cycles */
|
||
|
static void test_prescaler(gconstpointer watchdog)
|
||
|
{
|
||
|
const Watchdog *wd = watchdog;
|
||
|
|
||
|
for (int wtclk = 0; wtclk < 4; ++wtclk) {
|
||
|
for (int wtis = 0; wtis < 4; ++wtis) {
|
||
|
QTestState *qts = qtest_init("-machine quanta-gsj");
|
||
|
|
||
|
qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
|
||
|
watchdog_write_wtcr(qts, wd,
|
||
|
WTCLK(wtclk) | WTE | WTIF | WTIS(wtis) | WTIE | WTR);
|
||
|
/*
|
||
|
* The interrupt doesn't fire until watchdog_interrupt_steps()
|
||
|
* cycles passed
|
||
|
*/
|
||
|
qtest_clock_step(qts, watchdog_interrupt_steps(qts, wd) - 1);
|
||
|
g_assert_false(watchdog_read_wtcr(qts, wd) & WTIF);
|
||
|
g_assert_false(qtest_get_irq(qts, wd->irq));
|
||
|
qtest_clock_step(qts, 1);
|
||
|
g_assert_true(watchdog_read_wtcr(qts, wd) & WTIF);
|
||
|
g_assert_true(qtest_get_irq(qts, wd->irq));
|
||
|
|
||
|
qtest_quit(qts);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Check a watchdog doesn't fire if corresponding flags (WTIE and WTRE) are not
|
||
|
* set.
|
||
|
*/
|
||
|
static void test_enabling_flags(gconstpointer watchdog)
|
||
|
{
|
||
|
const Watchdog *wd = watchdog;
|
||
|
QTestState *qts;
|
||
|
|
||
|
/* Neither WTIE or WTRE is set, no interrupt or reset should happen */
|
||
|
qts = qtest_init("-machine quanta-gsj");
|
||
|
qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
|
||
|
watchdog_write_wtcr(qts, wd, WTCLK(0) | WTE | WTIF | WTRF | WTR);
|
||
|
qtest_clock_step(qts, watchdog_interrupt_steps(qts, wd));
|
||
|
g_assert_true(watchdog_read_wtcr(qts, wd) & WTIF);
|
||
|
g_assert_false(qtest_get_irq(qts, wd->irq));
|
||
|
qtest_clock_step(qts, watchdog_calculate_steps(RESET_CYCLES,
|
||
|
watchdog_prescaler(qts, wd)));
|
||
|
g_assert_true(watchdog_read_wtcr(qts, wd) & WTIF);
|
||
|
g_assert_false(watchdog_read_wtcr(qts, wd) & WTRF);
|
||
|
qtest_quit(qts);
|
||
|
|
||
|
/* Only WTIE is set, interrupt is triggered but reset should not happen */
|
||
|
qts = qtest_init("-machine quanta-gsj");
|
||
|
qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
|
||
|
watchdog_write_wtcr(qts, wd, WTCLK(0) | WTE | WTIF | WTIE | WTRF | WTR);
|
||
|
qtest_clock_step(qts, watchdog_interrupt_steps(qts, wd));
|
||
|
g_assert_true(watchdog_read_wtcr(qts, wd) & WTIF);
|
||
|
g_assert_true(qtest_get_irq(qts, wd->irq));
|
||
|
qtest_clock_step(qts, watchdog_calculate_steps(RESET_CYCLES,
|
||
|
watchdog_prescaler(qts, wd)));
|
||
|
g_assert_true(watchdog_read_wtcr(qts, wd) & WTIF);
|
||
|
g_assert_false(watchdog_read_wtcr(qts, wd) & WTRF);
|
||
|
qtest_quit(qts);
|
||
|
|
||
|
/* Only WTRE is set, interrupt is triggered but reset should not happen */
|
||
|
qts = qtest_init("-machine quanta-gsj");
|
||
|
qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
|
||
|
watchdog_write_wtcr(qts, wd, WTCLK(0) | WTE | WTIF | WTRE | WTRF | WTR);
|
||
|
qtest_clock_step(qts, watchdog_interrupt_steps(qts, wd));
|
||
|
g_assert_true(watchdog_read_wtcr(qts, wd) & WTIF);
|
||
|
g_assert_false(qtest_get_irq(qts, wd->irq));
|
||
|
qtest_clock_step(qts, watchdog_calculate_steps(RESET_CYCLES,
|
||
|
watchdog_prescaler(qts, wd)));
|
||
|
g_assert_false(strcmp(qdict_get_str(get_watchdog_action(qts), "action"),
|
||
|
"reset"));
|
||
|
qtest_qmp_eventwait(qts, "RESET");
|
||
|
qtest_quit(qts);
|
||
|
|
||
|
/*
|
||
|
* The case when both flags are set is already tested in
|
||
|
* test_reset_action().
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
/* Check a watchdog can pause and resume by setting WTE bits */
|
||
|
static void test_pause(gconstpointer watchdog)
|
||
|
{
|
||
|
const Watchdog *wd = watchdog;
|
||
|
QTestState *qts;
|
||
|
int64_t remaining_steps, steps;
|
||
|
|
||
|
qts = qtest_init("-machine quanta-gsj");
|
||
|
qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
|
||
|
watchdog_write_wtcr(qts, wd, WTCLK(0) | WTE | WTIF | WTIE | WTRF | WTR);
|
||
|
remaining_steps = watchdog_interrupt_steps(qts, wd);
|
||
|
g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==, WTCLK(0) | WTE | WTIE);
|
||
|
|
||
|
/* Run for half of the execution period. */
|
||
|
steps = remaining_steps / 2;
|
||
|
remaining_steps -= steps;
|
||
|
qtest_clock_step(qts, steps);
|
||
|
|
||
|
/* Pause the watchdog */
|
||
|
watchdog_write_wtcr(qts, wd, WTCLK(0) | WTIE);
|
||
|
g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==, WTCLK(0) | WTIE);
|
||
|
|
||
|
/* Run for a long period of time, the watchdog shouldn't fire */
|
||
|
qtest_clock_step(qts, steps << 4);
|
||
|
g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==, WTCLK(0) | WTIE);
|
||
|
g_assert_false(qtest_get_irq(qts, wd->irq));
|
||
|
|
||
|
/* Resume the watchdog */
|
||
|
watchdog_write_wtcr(qts, wd, WTCLK(0) | WTE | WTIE);
|
||
|
g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==, WTCLK(0) | WTE | WTIE);
|
||
|
|
||
|
/* Run for the reset of the execution period, the watchdog should fire */
|
||
|
qtest_clock_step(qts, remaining_steps);
|
||
|
g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==,
|
||
|
WTCLK(0) | WTE | WTIF | WTIE);
|
||
|
g_assert_true(qtest_get_irq(qts, wd->irq));
|
||
|
|
||
|
qtest_quit(qts);
|
||
|
}
|
||
|
|
||
|
static void watchdog_add_test(const char *name, const Watchdog* wd,
|
||
|
GTestDataFunc fn)
|
||
|
{
|
||
|
g_autofree char *full_name = g_strdup_printf(
|
||
|
"npcm7xx_watchdog_timer[%d]/%s", watchdog_index(wd), name);
|
||
|
qtest_add_data_func(full_name, wd, fn);
|
||
|
}
|
||
|
#define add_test(name, td) watchdog_add_test(#name, td, test_##name)
|
||
|
|
||
|
int main(int argc, char **argv)
|
||
|
{
|
||
|
g_test_init(&argc, &argv, NULL);
|
||
|
g_test_set_nonfatal_assertions();
|
||
|
|
||
|
for (int i = 0; i < ARRAY_SIZE(watchdog_list); ++i) {
|
||
|
const Watchdog *wd = &watchdog_list[i];
|
||
|
|
||
|
add_test(init, wd);
|
||
|
add_test(reset_action, wd);
|
||
|
add_test(prescaler, wd);
|
||
|
add_test(enabling_flags, wd);
|
||
|
add_test(pause, wd);
|
||
|
}
|
||
|
|
||
|
return g_test_run();
|
||
|
}
|