From 2ac88848cb03605e2fae6a035650eea461218af2 Mon Sep 17 00:00:00 2001 From: Havard Skinnemoen Date: Fri, 23 Oct 2020 14:06:33 -0700 Subject: Move npcm7xx_timer_reached_zero call out of npcm7xx_timer_pause This allows us to reuse npcm7xx_timer_pause for the watchdog timer. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Havard Skinnemoen Signed-off-by: Peter Maydell --- hw/timer/npcm7xx_timer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'hw/timer') diff --git a/hw/timer/npcm7xx_timer.c b/hw/timer/npcm7xx_timer.c index 5703e43d40..2df9e3e496 100644 --- a/hw/timer/npcm7xx_timer.c +++ b/hw/timer/npcm7xx_timer.c @@ -157,9 +157,6 @@ static void npcm7xx_timer_pause(NPCM7xxTimer *t) timer_del(&t->qtimer); now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); t->remaining_ns = t->expires_ns - now; - if (t->remaining_ns <= 0) { - npcm7xx_timer_reached_zero(t); - } } /* @@ -239,6 +236,9 @@ static void npcm7xx_timer_write_tcsr(NPCM7xxTimer *t, uint32_t new_tcsr) } else { t->tcsr &= ~NPCM7XX_TCSR_CACT; npcm7xx_timer_pause(t); + if (t->remaining_ns <= 0) { + npcm7xx_timer_reached_zero(t); + } } } } -- cgit v1.2.3-55-g7522 From 7d378ed6e3b4a26f4da887fcccc4c6f1db3dcd42 Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Fri, 23 Oct 2020 14:06:34 -0700 Subject: 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 Signed-off-by: Hao Wu Signed-off-by: Havard Skinnemoen Reviewed-by: Peter Maydell [PMM: deleted blank line at end of npcm_watchdog_timer-test.c] Signed-off-by: Peter Maydell --- MAINTAINERS | 1 + hw/arm/npcm7xx.c | 12 ++ hw/misc/npcm7xx_clk.c | 28 +++ hw/timer/npcm7xx_timer.c | 266 ++++++++++++++++++++----- include/hw/misc/npcm7xx_clk.h | 2 + include/hw/timer/npcm7xx_timer.h | 48 ++++- tests/qtest/meson.build | 2 +- tests/qtest/npcm7xx_watchdog_timer-test.c | 319 ++++++++++++++++++++++++++++++ 8 files changed, 624 insertions(+), 54 deletions(-) create mode 100644 tests/qtest/npcm7xx_watchdog_timer-test.c (limited to 'hw/timer') 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(); +} -- cgit v1.2.3-55-g7522 From 32bd322a0134ed89db00f2b9b3894982db3dedcb Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 15 Oct 2020 16:18:29 +0100 Subject: hw/timer/armv7m_systick: Rewrite to use ptimers The armv7m systick timer is a 24-bit decrementing, wrap-on-zero, clear-on-write counter. Our current implementation has various bugs and dubious workarounds in it (for instance see https://bugs.launchpad.net/qemu/+bug/1872237). We have an implementation of a simple decrementing counter and we put a lot of effort into making sure it handles the interesting corner cases (like "spend a cycle at 0 before reloading") -- ptimer. Rewrite the systick timer to use a ptimer rather than a raw QEMU timer. Unfortunately this is a migration compatibility break, which will affect all M-profile boards. Among other bugs, this fixes https://bugs.launchpad.net/qemu/+bug/1872237 : now writes to SYST_CVR when the timer is enabled correctly do nothing; when the timer is enabled via SYST_CSR.ENABLE, the ptimer code will (because of POLICY_NO_IMMEDIATE_RELOAD) arrange that after one timer tick the counter is reloaded from SYST_RVR and then counts down from there, as the architecture requires. Signed-off-by: Peter Maydell Reviewed-by: Philippe Mathieu-Daudé Message-id: 20201015151829.14656-3-peter.maydell@linaro.org --- hw/timer/armv7m_systick.c | 124 ++++++++++++++++---------------------- include/hw/timer/armv7m_systick.h | 3 +- 2 files changed, 54 insertions(+), 73 deletions(-) (limited to 'hw/timer') diff --git a/hw/timer/armv7m_systick.c b/hw/timer/armv7m_systick.c index a8cec7eb56..2f192011eb 100644 --- a/hw/timer/armv7m_systick.c +++ b/hw/timer/armv7m_systick.c @@ -39,26 +39,6 @@ static inline int64_t systick_scale(SysTickState *s) } } -static void systick_reload(SysTickState *s, int reset) -{ - /* The Cortex-M3 Devices Generic User Guide says that "When the - * ENABLE bit is set to 1, the counter loads the RELOAD value from the - * SYST RVR register and then counts down". So, we need to check the - * ENABLE bit before reloading the value. - */ - trace_systick_reload(); - - if ((s->control & SYSTICK_ENABLE) == 0) { - return; - } - - if (reset) { - s->tick = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - } - s->tick += (s->reload + 1) * systick_scale(s); - timer_mod(s->timer, s->tick); -} - static void systick_timer_tick(void *opaque) { SysTickState *s = (SysTickState *)opaque; @@ -70,10 +50,12 @@ static void systick_timer_tick(void *opaque) /* Tell the NVIC to pend the SysTick exception */ qemu_irq_pulse(s->irq); } - if (s->reload == 0) { - s->control &= ~SYSTICK_ENABLE; - } else { - systick_reload(s, 0); + if (ptimer_get_limit(s->ptimer) == 0) { + /* + * Timer expiry with SYST_RVR zero disables the timer + * (but doesn't clear SYST_CSR.ENABLE) + */ + ptimer_stop(s->ptimer); } } @@ -94,30 +76,11 @@ static MemTxResult systick_read(void *opaque, hwaddr addr, uint64_t *data, s->control &= ~SYSTICK_COUNTFLAG; break; case 0x4: /* SysTick Reload Value. */ - val = s->reload; + val = ptimer_get_limit(s->ptimer); break; case 0x8: /* SysTick Current Value. */ - { - int64_t t; - - if ((s->control & SYSTICK_ENABLE) == 0) { - val = 0; - break; - } - t = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - if (t >= s->tick) { - val = 0; - break; - } - val = ((s->tick - (t + 1)) / systick_scale(s)) + 1; - /* The interrupt in triggered when the timer reaches zero. - However the counter is not reloaded until the next clock - tick. This is a hack to return zero during the first tick. */ - if (val > s->reload) { - val = 0; - } + val = ptimer_get_count(s->ptimer); break; - } case 0xc: /* SysTick Calibration Value. */ val = 10000; break; @@ -149,39 +112,50 @@ static MemTxResult systick_write(void *opaque, hwaddr addr, switch (addr) { case 0x0: /* SysTick Control and Status. */ { - uint32_t oldval = s->control; + uint32_t oldval; + ptimer_transaction_begin(s->ptimer); + oldval = s->control; s->control &= 0xfffffff8; s->control |= value & 7; + if ((oldval ^ value) & SYSTICK_ENABLE) { - int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); if (value & SYSTICK_ENABLE) { - if (s->tick) { - s->tick += now; - timer_mod(s->timer, s->tick); - } else { - systick_reload(s, 1); - } + /* + * Always reload the period in case board code has + * changed system_clock_scale. If we ever replace that + * global with a more sensible API then we might be able + * to set the period only when it actually changes. + */ + ptimer_set_period(s->ptimer, systick_scale(s)); + ptimer_run(s->ptimer, 0); } else { - timer_del(s->timer); - s->tick -= now; - if (s->tick < 0) { - s->tick = 0; - } + ptimer_stop(s->ptimer); } } else if ((oldval ^ value) & SYSTICK_CLKSOURCE) { - /* This is a hack. Force the timer to be reloaded - when the reference clock is changed. */ - systick_reload(s, 1); + ptimer_set_period(s->ptimer, systick_scale(s)); } + ptimer_transaction_commit(s->ptimer); break; } case 0x4: /* SysTick Reload Value. */ - s->reload = value; + ptimer_transaction_begin(s->ptimer); + ptimer_set_limit(s->ptimer, value & 0xffffff, 0); + ptimer_transaction_commit(s->ptimer); break; - case 0x8: /* SysTick Current Value. Writes reload the timer. */ - systick_reload(s, 1); + case 0x8: /* SysTick Current Value. */ + /* + * Writing any value clears SYST_CVR to zero and clears + * SYST_CSR.COUNTFLAG. The counter will then reload from SYST_RVR + * on the next clock edge unless SYST_RVR is zero. + */ + ptimer_transaction_begin(s->ptimer); + if (ptimer_get_limit(s->ptimer) == 0) { + ptimer_stop(s->ptimer); + } + ptimer_set_count(s->ptimer, 0); s->control &= ~SYSTICK_COUNTFLAG; + ptimer_transaction_commit(s->ptimer); break; default: qemu_log_mask(LOG_GUEST_ERROR, @@ -210,10 +184,13 @@ static void systick_reset(DeviceState *dev) */ assert(system_clock_scale != 0); + ptimer_transaction_begin(s->ptimer); s->control = 0; - s->reload = 0; - s->tick = 0; - timer_del(s->timer); + ptimer_stop(s->ptimer); + ptimer_set_count(s->ptimer, 0); + ptimer_set_limit(s->ptimer, 0, 0); + ptimer_set_period(s->ptimer, systick_scale(s)); + ptimer_transaction_commit(s->ptimer); } static void systick_instance_init(Object *obj) @@ -229,18 +206,21 @@ static void systick_instance_init(Object *obj) static void systick_realize(DeviceState *dev, Error **errp) { SysTickState *s = SYSTICK(dev); - s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, systick_timer_tick, s); + s->ptimer = ptimer_init(systick_timer_tick, s, + PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD | + PTIMER_POLICY_NO_COUNTER_ROUND_DOWN | + PTIMER_POLICY_NO_IMMEDIATE_RELOAD | + PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT); } static const VMStateDescription vmstate_systick = { .name = "armv7m_systick", - .version_id = 1, - .minimum_version_id = 1, + .version_id = 2, + .minimum_version_id = 2, .fields = (VMStateField[]) { VMSTATE_UINT32(control, SysTickState), - VMSTATE_UINT32(reload, SysTickState), VMSTATE_INT64(tick, SysTickState), - VMSTATE_TIMER_PTR(timer, SysTickState), + VMSTATE_PTIMER(ptimer, SysTickState), VMSTATE_END_OF_LIST() } }; diff --git a/include/hw/timer/armv7m_systick.h b/include/hw/timer/armv7m_systick.h index 97cb345ddb..84496faaf9 100644 --- a/include/hw/timer/armv7m_systick.h +++ b/include/hw/timer/armv7m_systick.h @@ -14,6 +14,7 @@ #include "hw/sysbus.h" #include "qom/object.h" +#include "hw/ptimer.h" #define TYPE_SYSTICK "armv7m_systick" @@ -27,7 +28,7 @@ struct SysTickState { uint32_t control; uint32_t reload; int64_t tick; - QEMUTimer *timer; + ptimer_state *ptimer; MemoryRegion iomem; qemu_irq irq; }; -- cgit v1.2.3-55-g7522