aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHao Wu <wuhaotsh@google.com>2020-10-23 14:06:34 -0700
committerPeter Maydell <peter.maydell@linaro.org>2020-10-27 11:10:01 +0000
commit7d378ed6e3b4a26f4da887fcccc4c6f1db3dcd42 (patch)
tree6770998f835720d3e7bc0879ad7a0705a9650cee
parent2ac88848cb03605e2fae6a035650eea461218af2 (diff)
downloadqemu-arm-7d378ed6e3b4a26f4da887fcccc4c6f1db3dcd42.tar.gz
hw/timer: Adding watchdog for NPCM7XX Timer.
The watchdog is part of NPCM7XX's timer module. Its behavior is controlled by the WTCR register in the timer. When enabled, the watchdog issues an interrupt signal after a pre-set amount of cycles, and issues a reset signal shortly after that. Reviewed-by: Tyrone Ting <kfting@nuvoton.com> Signed-off-by: Hao Wu <wuhaotsh@google.com> Signed-off-by: Havard Skinnemoen <hskinnemoen@google.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> [PMM: deleted blank line at end of npcm_watchdog_timer-test.c] Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
-rw-r--r--MAINTAINERS1
-rw-r--r--hw/arm/npcm7xx.c12
-rw-r--r--hw/misc/npcm7xx_clk.c28
-rw-r--r--hw/timer/npcm7xx_timer.c266
-rw-r--r--include/hw/misc/npcm7xx_clk.h2
-rw-r--r--include/hw/timer/npcm7xx_timer.h48
-rw-r--r--tests/qtest/meson.build2
-rw-r--r--tests/qtest/npcm7xx_watchdog_timer-test.c319
8 files changed, 624 insertions, 54 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index ef6f5c7399..1edc8bd4c9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -755,6 +755,7 @@ L: qemu-arm@nongnu.org
S: Supported
F: hw/*/npcm7xx*
F: include/hw/*/npcm7xx*
+F: tests/qtest/npcm7xx*
F: pc-bios/npcm7xx_bootrom.bin
F: roms/vbootrom
diff --git a/hw/arm/npcm7xx.c b/hw/arm/npcm7xx.c
index 037f3a26f2..c341dcab8b 100644
--- a/hw/arm/npcm7xx.c
+++ b/hw/arm/npcm7xx.c
@@ -86,6 +86,9 @@ enum NPCM7xxInterrupt {
NPCM7XX_TIMER12_IRQ,
NPCM7XX_TIMER13_IRQ,
NPCM7XX_TIMER14_IRQ,
+ NPCM7XX_WDG0_IRQ = 47, /* Timer Module 0 Watchdog */
+ NPCM7XX_WDG1_IRQ, /* Timer Module 1 Watchdog */
+ NPCM7XX_WDG2_IRQ, /* Timer Module 2 Watchdog */
};
/* Total number of GIC interrupts, including internal Cortex-A9 interrupts. */
@@ -353,6 +356,15 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp)
qemu_irq irq = npcm7xx_irq(s, first_irq + j);
sysbus_connect_irq(sbd, j, irq);
}
+
+ /* IRQ for watchdogs */
+ sysbus_connect_irq(sbd, NPCM7XX_TIMERS_PER_CTRL,
+ npcm7xx_irq(s, NPCM7XX_WDG0_IRQ + i));
+ /* GPIO that connects clk module with watchdog */
+ qdev_connect_gpio_out_named(DEVICE(&s->tim[i]),
+ NPCM7XX_WATCHDOG_RESET_GPIO_OUT, 0,
+ qdev_get_gpio_in_named(DEVICE(&s->clk),
+ NPCM7XX_WATCHDOG_RESET_GPIO_IN, i));
}
/* UART0..3 (16550 compatible) */
diff --git a/hw/misc/npcm7xx_clk.c b/hw/misc/npcm7xx_clk.c
index 21ab4200d1..6732437fe2 100644
--- a/hw/misc/npcm7xx_clk.c
+++ b/hw/misc/npcm7xx_clk.c
@@ -17,6 +17,7 @@
#include "qemu/osdep.h"
#include "hw/misc/npcm7xx_clk.h"
+#include "hw/timer/npcm7xx_timer.h"
#include "migration/vmstate.h"
#include "qemu/error-report.h"
#include "qemu/log.h"
@@ -24,6 +25,7 @@
#include "qemu/timer.h"
#include "qemu/units.h"
#include "trace.h"
+#include "sysemu/watchdog.h"
#define PLLCON_LOKI BIT(31)
#define PLLCON_LOKS BIT(30)
@@ -87,6 +89,12 @@ static const uint32_t cold_reset_values[NPCM7XX_CLK_NR_REGS] = {
[NPCM7XX_CLK_AHBCKFI] = 0x000000c8,
};
+/* Register Field Definitions */
+#define NPCM7XX_CLK_WDRCR_CA9C BIT(0) /* Cortex A9 Cores */
+
+/* The number of watchdogs that can trigger a reset. */
+#define NPCM7XX_NR_WATCHDOGS (3)
+
static uint64_t npcm7xx_clk_read(void *opaque, hwaddr offset, unsigned size)
{
uint32_t reg = offset / sizeof(uint32_t);
@@ -187,6 +195,24 @@ static void npcm7xx_clk_write(void *opaque, hwaddr offset,
s->regs[reg] = value;
}
+/* Perform reset action triggered by a watchdog */
+static void npcm7xx_clk_perform_watchdog_reset(void *opaque, int n,
+ int level)
+{
+ NPCM7xxCLKState *clk = NPCM7XX_CLK(opaque);
+ uint32_t rcr;
+
+ g_assert(n >= 0 && n <= NPCM7XX_NR_WATCHDOGS);
+ rcr = clk->regs[NPCM7XX_CLK_WD0RCR + n];
+ if (rcr & NPCM7XX_CLK_WDRCR_CA9C) {
+ watchdog_perform_action();
+ } else {
+ qemu_log_mask(LOG_UNIMP,
+ "%s: only CPU reset is implemented. (requested 0x%" PRIx32")\n",
+ __func__, rcr);
+ }
+}
+
static const struct MemoryRegionOps npcm7xx_clk_ops = {
.read = npcm7xx_clk_read,
.write = npcm7xx_clk_write,
@@ -226,6 +252,8 @@ static void npcm7xx_clk_init(Object *obj)
memory_region_init_io(&s->iomem, obj, &npcm7xx_clk_ops, s,
TYPE_NPCM7XX_CLK, 4 * KiB);
sysbus_init_mmio(&s->parent, &s->iomem);
+ qdev_init_gpio_in_named(DEVICE(s), npcm7xx_clk_perform_watchdog_reset,
+ NPCM7XX_WATCHDOG_RESET_GPIO_IN, NPCM7XX_NR_WATCHDOGS);
}
static const VMStateDescription vmstate_npcm7xx_clk = {
diff --git a/hw/timer/npcm7xx_timer.c b/hw/timer/npcm7xx_timer.c
index 2df9e3e496..d24445bd6e 100644
--- a/hw/timer/npcm7xx_timer.c
+++ b/hw/timer/npcm7xx_timer.c
@@ -17,6 +17,7 @@
#include "qemu/osdep.h"
#include "hw/irq.h"
+#include "hw/qdev-properties.h"
#include "hw/misc/npcm7xx_clk.h"
#include "hw/timer/npcm7xx_timer.h"
#include "migration/vmstate.h"
@@ -60,6 +61,50 @@ enum NPCM7xxTimerRegisters {
#define NPCM7XX_TCSR_PRESCALE_START 0
#define NPCM7XX_TCSR_PRESCALE_LEN 8
+#define NPCM7XX_WTCR_WTCLK(rv) extract32(rv, 10, 2)
+#define NPCM7XX_WTCR_FREEZE_EN BIT(9)
+#define NPCM7XX_WTCR_WTE BIT(7)
+#define NPCM7XX_WTCR_WTIE BIT(6)
+#define NPCM7XX_WTCR_WTIS(rv) extract32(rv, 4, 2)
+#define NPCM7XX_WTCR_WTIF BIT(3)
+#define NPCM7XX_WTCR_WTRF BIT(2)
+#define NPCM7XX_WTCR_WTRE BIT(1)
+#define NPCM7XX_WTCR_WTR BIT(0)
+
+/*
+ * The number of clock cycles between interrupt and reset in watchdog, used
+ * by the software to handle the interrupt before system is reset.
+ */
+#define NPCM7XX_WATCHDOG_INTERRUPT_TO_RESET_CYCLES 1024
+
+/* Start or resume the timer. */
+static void npcm7xx_timer_start(NPCM7xxBaseTimer *t)
+{
+ int64_t now;
+
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ t->expires_ns = now + t->remaining_ns;
+ timer_mod(&t->qtimer, t->expires_ns);
+}
+
+/* Stop counting. Record the time remaining so we can continue later. */
+static void npcm7xx_timer_pause(NPCM7xxBaseTimer *t)
+{
+ int64_t now;
+
+ timer_del(&t->qtimer);
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ t->remaining_ns = t->expires_ns - now;
+}
+
+/* Delete the timer and reset it to default state. */
+static void npcm7xx_timer_clear(NPCM7xxBaseTimer *t)
+{
+ timer_del(&t->qtimer);
+ t->expires_ns = 0;
+ t->remaining_ns = 0;
+}
+
/*
* Returns the index of timer in the tc->timer array. This can be used to
* locate the registers that belong to this timer.
@@ -102,6 +147,52 @@ static uint32_t npcm7xx_timer_ns_to_count(NPCM7xxTimer *t, int64_t ns)
return count;
}
+static uint32_t npcm7xx_watchdog_timer_prescaler(const NPCM7xxWatchdogTimer *t)
+{
+ switch (NPCM7XX_WTCR_WTCLK(t->wtcr)) {
+ case 0:
+ return 1;
+ case 1:
+ return 256;
+ case 2:
+ return 2048;
+ case 3:
+ return 65536;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static void npcm7xx_watchdog_timer_reset_cycles(NPCM7xxWatchdogTimer *t,
+ int64_t cycles)
+{
+ uint32_t prescaler = npcm7xx_watchdog_timer_prescaler(t);
+ int64_t ns = (NANOSECONDS_PER_SECOND / NPCM7XX_TIMER_REF_HZ) * cycles;
+
+ /*
+ * The reset function always clears the current timer. The caller of the
+ * this needs to decide whether to start the watchdog timer based on
+ * specific flag in WTCR.
+ */
+ npcm7xx_timer_clear(&t->base_timer);
+
+ ns *= prescaler;
+ t->base_timer.remaining_ns = ns;
+}
+
+static void npcm7xx_watchdog_timer_reset(NPCM7xxWatchdogTimer *t)
+{
+ int64_t cycles = 1;
+ uint32_t s = NPCM7XX_WTCR_WTIS(t->wtcr);
+
+ g_assert(s <= 3);
+
+ cycles <<= NPCM7XX_WATCHDOG_BASETIME_SHIFT;
+ cycles <<= 2 * s;
+
+ npcm7xx_watchdog_timer_reset_cycles(t, cycles);
+}
+
/*
* Raise the interrupt line if there's a pending interrupt and interrupts are
* enabled for this timer. If not, lower it.
@@ -116,16 +207,6 @@ static void npcm7xx_timer_check_interrupt(NPCM7xxTimer *t)
trace_npcm7xx_timer_irq(DEVICE(tc)->canonical_path, index, pending);
}
-/* Start or resume the timer. */
-static void npcm7xx_timer_start(NPCM7xxTimer *t)
-{
- int64_t now;
-
- now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
- t->expires_ns = now + t->remaining_ns;
- timer_mod(&t->qtimer, t->expires_ns);
-}
-
/*
* Called when the counter reaches zero. Sets the interrupt flag, and either
* restarts or disables the timer.
@@ -138,9 +219,9 @@ static void npcm7xx_timer_reached_zero(NPCM7xxTimer *t)
tc->tisr |= BIT(index);
if (t->tcsr & NPCM7XX_TCSR_PERIODIC) {
- t->remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr);
+ t->base_timer.remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr);
if (t->tcsr & NPCM7XX_TCSR_CEN) {
- npcm7xx_timer_start(t);
+ npcm7xx_timer_start(&t->base_timer);
}
} else {
t->tcsr &= ~(NPCM7XX_TCSR_CEN | NPCM7XX_TCSR_CACT);
@@ -149,15 +230,6 @@ static void npcm7xx_timer_reached_zero(NPCM7xxTimer *t)
npcm7xx_timer_check_interrupt(t);
}
-/* Stop counting. Record the time remaining so we can continue later. */
-static void npcm7xx_timer_pause(NPCM7xxTimer *t)
-{
- int64_t now;
-
- timer_del(&t->qtimer);
- now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
- t->remaining_ns = t->expires_ns - now;
-}
/*
* Restart the timer from its initial value. If the timer was enabled and stays
@@ -167,10 +239,10 @@ static void npcm7xx_timer_pause(NPCM7xxTimer *t)
*/
static void npcm7xx_timer_restart(NPCM7xxTimer *t, uint32_t old_tcsr)
{
- t->remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr);
+ t->base_timer.remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr);
if (old_tcsr & t->tcsr & NPCM7XX_TCSR_CEN) {
- npcm7xx_timer_start(t);
+ npcm7xx_timer_start(&t->base_timer);
}
}
@@ -181,10 +253,10 @@ static uint32_t npcm7xx_timer_read_tdr(NPCM7xxTimer *t)
if (t->tcsr & NPCM7XX_TCSR_CEN) {
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
- return npcm7xx_timer_ns_to_count(t, t->expires_ns - now);
+ return npcm7xx_timer_ns_to_count(t, t->base_timer.expires_ns - now);
}
- return npcm7xx_timer_ns_to_count(t, t->remaining_ns);
+ return npcm7xx_timer_ns_to_count(t, t->base_timer.remaining_ns);
}
static void npcm7xx_timer_write_tcsr(NPCM7xxTimer *t, uint32_t new_tcsr)
@@ -216,9 +288,9 @@ static void npcm7xx_timer_write_tcsr(NPCM7xxTimer *t, uint32_t new_tcsr)
if (npcm7xx_tcsr_prescaler(old_tcsr) != npcm7xx_tcsr_prescaler(new_tcsr)) {
/* Recalculate time remaining based on the current TDR value. */
- t->remaining_ns = npcm7xx_timer_count_to_ns(t, tdr);
+ t->base_timer.remaining_ns = npcm7xx_timer_count_to_ns(t, tdr);
if (old_tcsr & t->tcsr & NPCM7XX_TCSR_CEN) {
- npcm7xx_timer_start(t);
+ npcm7xx_timer_start(&t->base_timer);
}
}
@@ -232,11 +304,11 @@ static void npcm7xx_timer_write_tcsr(NPCM7xxTimer *t, uint32_t new_tcsr)
if ((old_tcsr ^ new_tcsr) & NPCM7XX_TCSR_CEN) {
if (new_tcsr & NPCM7XX_TCSR_CEN) {
t->tcsr |= NPCM7XX_TCSR_CACT;
- npcm7xx_timer_start(t);
+ npcm7xx_timer_start(&t->base_timer);
} else {
t->tcsr &= ~NPCM7XX_TCSR_CACT;
- npcm7xx_timer_pause(t);
- if (t->remaining_ns <= 0) {
+ npcm7xx_timer_pause(&t->base_timer);
+ if (t->base_timer.remaining_ns <= 0) {
npcm7xx_timer_reached_zero(t);
}
}
@@ -259,9 +331,47 @@ static void npcm7xx_timer_write_tisr(NPCM7xxTimerCtrlState *s, uint32_t value)
if (value & (1U << i)) {
npcm7xx_timer_check_interrupt(&s->timer[i]);
}
+
}
}
+static void npcm7xx_timer_write_wtcr(NPCM7xxWatchdogTimer *t, uint32_t new_wtcr)
+{
+ uint32_t old_wtcr = t->wtcr;
+
+ /*
+ * WTIF and WTRF are cleared by writing 1. Writing 0 makes these bits
+ * unchanged.
+ */
+ if (new_wtcr & NPCM7XX_WTCR_WTIF) {
+ new_wtcr &= ~NPCM7XX_WTCR_WTIF;
+ } else if (old_wtcr & NPCM7XX_WTCR_WTIF) {
+ new_wtcr |= NPCM7XX_WTCR_WTIF;
+ }
+ if (new_wtcr & NPCM7XX_WTCR_WTRF) {
+ new_wtcr &= ~NPCM7XX_WTCR_WTRF;
+ } else if (old_wtcr & NPCM7XX_WTCR_WTRF) {
+ new_wtcr |= NPCM7XX_WTCR_WTRF;
+ }
+
+ t->wtcr = new_wtcr;
+
+ if (new_wtcr & NPCM7XX_WTCR_WTR) {
+ t->wtcr &= ~NPCM7XX_WTCR_WTR;
+ npcm7xx_watchdog_timer_reset(t);
+ if (new_wtcr & NPCM7XX_WTCR_WTE) {
+ npcm7xx_timer_start(&t->base_timer);
+ }
+ } else if ((old_wtcr ^ new_wtcr) & NPCM7XX_WTCR_WTE) {
+ if (new_wtcr & NPCM7XX_WTCR_WTE) {
+ npcm7xx_timer_start(&t->base_timer);
+ } else {
+ npcm7xx_timer_pause(&t->base_timer);
+ }
+ }
+
+}
+
static hwaddr npcm7xx_tcsr_index(hwaddr reg)
{
switch (reg) {
@@ -353,7 +463,7 @@ static uint64_t npcm7xx_timer_read(void *opaque, hwaddr offset, unsigned size)
break;
case NPCM7XX_TIMER_WTCR:
- value = s->wtcr;
+ value = s->watchdog_timer.wtcr;
break;
default:
@@ -409,8 +519,7 @@ static void npcm7xx_timer_write(void *opaque, hwaddr offset,
return;
case NPCM7XX_TIMER_WTCR:
- qemu_log_mask(LOG_UNIMP, "%s: WTCR write not implemented: 0x%08x\n",
- __func__, value);
+ npcm7xx_timer_write_wtcr(&s->watchdog_timer, value);
return;
}
@@ -448,15 +557,42 @@ static void npcm7xx_timer_enter_reset(Object *obj, ResetType type)
for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) {
NPCM7xxTimer *t = &s->timer[i];
- timer_del(&t->qtimer);
- t->expires_ns = 0;
- t->remaining_ns = 0;
+ npcm7xx_timer_clear(&t->base_timer);
t->tcsr = 0x00000005;
t->ticr = 0x00000000;
}
s->tisr = 0x00000000;
- s->wtcr = 0x00000400;
+ /*
+ * Set WTCLK to 1(default) and reset all flags except WTRF.
+ * WTRF is not reset during a core domain reset.
+ */
+ s->watchdog_timer.wtcr = 0x00000400 | (s->watchdog_timer.wtcr &
+ NPCM7XX_WTCR_WTRF);
+}
+
+static void npcm7xx_watchdog_timer_expired(void *opaque)
+{
+ NPCM7xxWatchdogTimer *t = opaque;
+
+ if (t->wtcr & NPCM7XX_WTCR_WTE) {
+ if (t->wtcr & NPCM7XX_WTCR_WTIF) {
+ if (t->wtcr & NPCM7XX_WTCR_WTRE) {
+ t->wtcr |= NPCM7XX_WTCR_WTRF;
+ /* send reset signal to CLK module*/
+ qemu_irq_raise(t->reset_signal);
+ }
+ } else {
+ t->wtcr |= NPCM7XX_WTCR_WTIF;
+ if (t->wtcr & NPCM7XX_WTCR_WTIE) {
+ /* send interrupt */
+ qemu_irq_raise(t->irq);
+ }
+ npcm7xx_watchdog_timer_reset_cycles(t,
+ NPCM7XX_WATCHDOG_INTERRUPT_TO_RESET_CYCLES);
+ npcm7xx_timer_start(&t->base_timer);
+ }
+ }
}
static void npcm7xx_timer_hold_reset(Object *obj)
@@ -467,6 +603,7 @@ static void npcm7xx_timer_hold_reset(Object *obj)
for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) {
qemu_irq_lower(s->timer[i].irq);
}
+ qemu_irq_lower(s->watchdog_timer.irq);
}
static void npcm7xx_timer_realize(DeviceState *dev, Error **errp)
@@ -474,43 +611,80 @@ static void npcm7xx_timer_realize(DeviceState *dev, Error **errp)
NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(dev);
SysBusDevice *sbd = &s->parent;
int i;
+ NPCM7xxWatchdogTimer *w;
for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) {
NPCM7xxTimer *t = &s->timer[i];
t->ctrl = s;
- timer_init_ns(&t->qtimer, QEMU_CLOCK_VIRTUAL, npcm7xx_timer_expired, t);
+ timer_init_ns(&t->base_timer.qtimer, QEMU_CLOCK_VIRTUAL,
+ npcm7xx_timer_expired, t);
sysbus_init_irq(sbd, &t->irq);
}
+ w = &s->watchdog_timer;
+ w->ctrl = s;
+ timer_init_ns(&w->base_timer.qtimer, QEMU_CLOCK_VIRTUAL,
+ npcm7xx_watchdog_timer_expired, w);
+ sysbus_init_irq(sbd, &w->irq);
+
memory_region_init_io(&s->iomem, OBJECT(s), &npcm7xx_timer_ops, s,
TYPE_NPCM7XX_TIMER, 4 * KiB);
sysbus_init_mmio(sbd, &s->iomem);
+ qdev_init_gpio_out_named(dev, &w->reset_signal,
+ NPCM7XX_WATCHDOG_RESET_GPIO_OUT, 1);
}
-static const VMStateDescription vmstate_npcm7xx_timer = {
- .name = "npcm7xx-timer",
+static const VMStateDescription vmstate_npcm7xx_base_timer = {
+ .name = "npcm7xx-base-timer",
.version_id = 0,
.minimum_version_id = 0,
.fields = (VMStateField[]) {
- VMSTATE_TIMER(qtimer, NPCM7xxTimer),
- VMSTATE_INT64(expires_ns, NPCM7xxTimer),
- VMSTATE_INT64(remaining_ns, NPCM7xxTimer),
+ VMSTATE_TIMER(qtimer, NPCM7xxBaseTimer),
+ VMSTATE_INT64(expires_ns, NPCM7xxBaseTimer),
+ VMSTATE_INT64(remaining_ns, NPCM7xxBaseTimer),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static const VMStateDescription vmstate_npcm7xx_timer = {
+ .name = "npcm7xx-timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(base_timer, NPCM7xxTimer,
+ 0, vmstate_npcm7xx_base_timer,
+ NPCM7xxBaseTimer),
VMSTATE_UINT32(tcsr, NPCM7xxTimer),
VMSTATE_UINT32(ticr, NPCM7xxTimer),
VMSTATE_END_OF_LIST(),
},
};
-static const VMStateDescription vmstate_npcm7xx_timer_ctrl = {
- .name = "npcm7xx-timer-ctrl",
+static const VMStateDescription vmstate_npcm7xx_watchdog_timer = {
+ .name = "npcm7xx-watchdog-timer",
.version_id = 0,
.minimum_version_id = 0,
.fields = (VMStateField[]) {
+ VMSTATE_STRUCT(base_timer, NPCM7xxWatchdogTimer,
+ 0, vmstate_npcm7xx_base_timer,
+ NPCM7xxBaseTimer),
+ VMSTATE_UINT32(wtcr, NPCM7xxWatchdogTimer),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static const VMStateDescription vmstate_npcm7xx_timer_ctrl = {
+ .name = "npcm7xx-timer-ctrl",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
VMSTATE_UINT32(tisr, NPCM7xxTimerCtrlState),
- VMSTATE_UINT32(wtcr, NPCM7xxTimerCtrlState),
VMSTATE_STRUCT_ARRAY(timer, NPCM7xxTimerCtrlState,
NPCM7XX_TIMERS_PER_CTRL, 0, vmstate_npcm7xx_timer,
NPCM7xxTimer),
+ VMSTATE_STRUCT(watchdog_timer, NPCM7xxTimerCtrlState,
+ 0, vmstate_npcm7xx_watchdog_timer,
+ NPCM7xxWatchdogTimer),
VMSTATE_END_OF_LIST(),
},
};
diff --git a/include/hw/misc/npcm7xx_clk.h b/include/hw/misc/npcm7xx_clk.h
index cdcc9e8534..2338fbbdb5 100644
--- a/include/hw/misc/npcm7xx_clk.h
+++ b/include/hw/misc/npcm7xx_clk.h
@@ -31,6 +31,8 @@
*/
#define NPCM7XX_CLK_NR_REGS (0x70 / sizeof(uint32_t))
+#define NPCM7XX_WATCHDOG_RESET_GPIO_IN "npcm7xx-clk-watchdog-reset-gpio-in"
+
typedef struct NPCM7xxCLKState {
SysBusDevice parent;
diff --git a/include/hw/timer/npcm7xx_timer.h b/include/hw/timer/npcm7xx_timer.h
index 878a365a79..6993fd723a 100644
--- a/include/hw/timer/npcm7xx_timer.h
+++ b/include/hw/timer/npcm7xx_timer.h
@@ -29,14 +29,31 @@
*/
#define NPCM7XX_TIMER_NR_REGS (0x54 / sizeof(uint32_t))
+/* The basic watchdog timer period is 2^14 clock cycles. */
+#define NPCM7XX_WATCHDOG_BASETIME_SHIFT 14
+
+#define NPCM7XX_WATCHDOG_RESET_GPIO_OUT "npcm7xx-clk-watchdog-reset-gpio-out"
+
typedef struct NPCM7xxTimerCtrlState NPCM7xxTimerCtrlState;
/**
- * struct NPCM7xxTimer - Individual timer state.
- * @irq: GIC interrupt line to fire on expiration (if enabled).
+ * struct NPCM7xxBaseTimer - Basic functionality that both regular timer and
+ * watchdog timer use.
* @qtimer: QEMU timer that notifies us on expiration.
* @expires_ns: Absolute virtual expiration time.
* @remaining_ns: Remaining time until expiration if timer is paused.
+ */
+typedef struct NPCM7xxBaseTimer {
+ QEMUTimer qtimer;
+ int64_t expires_ns;
+ int64_t remaining_ns;
+} NPCM7xxBaseTimer;
+
+/**
+ * struct NPCM7xxTimer - Individual timer state.
+ * @ctrl: The timer module that owns this timer.
+ * @irq: GIC interrupt line to fire on expiration (if enabled).
+ * @base_timer: The basic timer functionality for this timer.
* @tcsr: The Timer Control and Status Register.
* @ticr: The Timer Initial Count Register.
*/
@@ -44,21 +61,38 @@ typedef struct NPCM7xxTimer {
NPCM7xxTimerCtrlState *ctrl;
qemu_irq irq;
- QEMUTimer qtimer;
- int64_t expires_ns;
- int64_t remaining_ns;
+ NPCM7xxBaseTimer base_timer;
uint32_t tcsr;
uint32_t ticr;
} NPCM7xxTimer;
/**
+ * struct NPCM7xxWatchdogTimer - The watchdog timer state.
+ * @ctrl: The timer module that owns this timer.
+ * @irq: GIC interrupt line to fire on expiration (if enabled).
+ * @reset_signal: The GPIO used to send a reset signal.
+ * @base_timer: The basic timer functionality for this timer.
+ * @wtcr: The Watchdog Timer Control Register.
+ */
+typedef struct NPCM7xxWatchdogTimer {
+ NPCM7xxTimerCtrlState *ctrl;
+
+ qemu_irq irq;
+ qemu_irq reset_signal;
+ NPCM7xxBaseTimer base_timer;
+
+ uint32_t wtcr;
+} NPCM7xxWatchdogTimer;
+
+/**
* struct NPCM7xxTimerCtrlState - Timer Module device state.
* @parent: System bus device.
* @iomem: Memory region through which registers are accessed.
+ * @index: The index of this timer module.
* @tisr: The Timer Interrupt Status Register.
- * @wtcr: The Watchdog Timer Control Register.
* @timer: The five individual timers managed by this module.
+ * @watchdog_timer: The watchdog timer managed by this module.
*/
struct NPCM7xxTimerCtrlState {
SysBusDevice parent;
@@ -66,9 +100,9 @@ struct NPCM7xxTimerCtrlState {
MemoryRegion iomem;
uint32_t tisr;
- uint32_t wtcr;
NPCM7xxTimer timer[NPCM7XX_TIMERS_PER_CTRL];
+ NPCM7xxWatchdogTimer watchdog_timer;
};
#define TYPE_NPCM7XX_TIMER "npcm7xx-timer"
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index ba8ebeead6..6c9d3426d1 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -133,7 +133,7 @@ qtests_sparc64 = \
(config_all_devices.has_key('CONFIG_ISA_TESTDEV') ? ['endianness-test'] : []) + \
['prom-env-test', 'boot-serial-test']
-qtests_npcm7xx = ['npcm7xx_timer-test']
+qtests_npcm7xx = ['npcm7xx_timer-test', 'npcm7xx_watchdog_timer-test']
qtests_arm = \
(config_all_devices.has_key('CONFIG_PFLASH_CFI02') ? ['pflash-cfi02-test'] : []) + \
(config_all_devices.has_key('CONFIG_NPCM7XX') ? qtests_npcm7xx : []) + \
diff --git a/tests/qtest/npcm7xx_watchdog_timer-test.c b/tests/qtest/npcm7xx_watchdog_timer-test.c
new file mode 100644
index 0000000000..54d5d6d8f2
--- /dev/null
+++ b/tests/qtest/npcm7xx_watchdog_timer-test.c
@@ -0,0 +1,319 @@
+/*
+ * 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();
+}