From nobody Thu May 8 22:24:45 2025 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=linaro.org ARC-Seal: i=1; a=rsa-sha256; t=1603802009; cv=none; d=zohomail.com; s=zohoarc; b=Qk7SFHin88ke55NasFDUxvhAEA3sA8kE8EWbjWYoJYzR+xjXejDOpdwJJxCWrBG3GpctrZGtrIYpMvUlmAikpbxDVscXC7dpEm02dO8jJYqwDdypMHs9CZlPkIF61KfbRlrt3h8BTGrcs+ikkmQcrK8HtB8L4OqowC7yjA8tgh4= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1603802009; h=Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=lTwvq3tRYxRzSLnqAUd2JbWIHxJ2BL0rJeSEuQRJEno=; b=RqGoEpNd7sTsoz5QHSXmTrohfS0sWH/v+2hS6V5bM6QYZr8qEODmHDm7n1daCBw7YXVuBLU5g+eNeFh7TnHMu4XxKoU87SRTFrGWr5SC9V6VQnrXOeBbE+O7ZAKiPaIM/zKgFnc+BOJelAYE+Kxa32fuC5VMb8bkUPHDOEDzugY= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from=<peter.maydell@linaro.org> (p=none dis=none) header.from=<peter.maydell@linaro.org> Return-Path: <qemu-devel-bounces+importer=patchew.org@nongnu.org> Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1603802008996761.4632010166782; Tue, 27 Oct 2020 05:33:28 -0700 (PDT) Received: from localhost ([::1]:35322 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from <qemu-devel-bounces+importer=patchew.org@nongnu.org>) id 1kXOAI-0004M6-Pk for importer@patchew.org; Tue, 27 Oct 2020 08:33:26 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:51834) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <peter.maydell@linaro.org>) id 1kXNPa-0001OY-Tl for qemu-devel@nongnu.org; Tue, 27 Oct 2020 07:45:10 -0400 Received: from mail-wm1-x32f.google.com ([2a00:1450:4864:20::32f]:50427) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from <peter.maydell@linaro.org>) id 1kXNPU-0004Vk-AZ for qemu-devel@nongnu.org; Tue, 27 Oct 2020 07:45:10 -0400 Received: by mail-wm1-x32f.google.com with SMTP id 13so1070259wmf.0 for <qemu-devel@nongnu.org>; Tue, 27 Oct 2020 04:45:03 -0700 (PDT) Received: from orth.archaic.org.uk (orth.archaic.org.uk. [81.2.115.148]) by smtp.gmail.com with ESMTPSA id 32sm1712203wro.31.2020.10.27.04.44.59 for <qemu-devel@nongnu.org> (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 27 Oct 2020 04:45:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:subject:date:message-id:in-reply-to:references:mime-version :content-transfer-encoding; bh=lTwvq3tRYxRzSLnqAUd2JbWIHxJ2BL0rJeSEuQRJEno=; b=nYrEQIXJ3HbpQUSbeA35ZyhIn6QIEjojCws8pRG1tOfZd9UYhWiqttP6HC0xbHRP5N /bkafQpGyfet3hYIVmnMT2KIlaNPT2qJSPBsdB1v9l6KBWx3L1fQZ86C/1n0ingJzowu dZYEaWUym1oUva5Y68LUSduDnJs1uAG6ZaV+0z8LX2Rqf2Ve+Fs9hCA2J91T1ABbZxaw 9TXENWCabagwzyzT72QjkW+LFgD/IDwnnZe8kVquFZ2lmT3YyZdvgZcjPsbZx5ipP53k s77D7YsMMFNSqsP6W0UeN+6BBTzvXmgnpli1ZAHPQ3K5oFVq9eXKosMOL30dSCDEt1FX 8Dzw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=lTwvq3tRYxRzSLnqAUd2JbWIHxJ2BL0rJeSEuQRJEno=; b=IiVD2CC5rZkZe8/vKLV6EBZrDx6G2LJq4HQKm5NHo/m6W+ak2raWW8O5rpANYDeLuE YYdPuDf4qdZlJmjbfaIDtUUIJewGfdXj/m7RAHFn10xzbYFsgErJNLCLU2nwHRl7IxAT /T3GUH7JyeyBznx5aM/5J05PJg7CwwjKyJwV8uT7fv+Np0+6x8r/lZ4Yer3RfNpHsP/X zvIpbbMNcum+TzBek+W+8Tdy6mLr9R+Mr/vt3apQ/p5cz6ClYlQtrhZ/Xjv43ZsPFrm1 Wf+J9tyDQDUDY/ADYQiqZk+UaV8DIC2E6jB03tjdbezfhruczK9urbGOa4I72Gp3XfYB CoXA== X-Gm-Message-State: AOAM533bE9mqJL9j7VcTRheRznz0J+Bbd4vEqsZX+ZFd5RYeAqacmnXm mVD/T5x1Pxi2SFrpeMUo5mYLNEHBGocSFA== X-Google-Smtp-Source: ABdhPJwvweT1PuE+QaoZveEnyfUfgLQlUkOa8yqhK9OfMWwyt9m0XB/WQwTu8yQp/BLUzvB5zwZTmg== X-Received: by 2002:a1c:20d0:: with SMTP id g199mr2412176wmg.38.1603799101153; Tue, 27 Oct 2020 04:45:01 -0700 (PDT) From: Peter Maydell <peter.maydell@linaro.org> To: qemu-devel@nongnu.org Subject: [PULL 16/48] hw/timer: Adding watchdog for NPCM7XX Timer. Date: Tue, 27 Oct 2020 11:44:06 +0000 Message-Id: <20201027114438.17662-17-peter.maydell@linaro.org> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201027114438.17662-1-peter.maydell@linaro.org> References: <20201027114438.17662-1-peter.maydell@linaro.org> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::32f; envelope-from=peter.maydell@linaro.org; helo=mail-wm1-x32f.google.com X-detected-operating-system: by eggs.gnu.org: No matching host in p0f cache. That's all we know. X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: <qemu-devel.nongnu.org> List-Unsubscribe: <https://lists.nongnu.org/mailman/options/qemu-devel>, <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe> List-Archive: <https://lists.nongnu.org/archive/html/qemu-devel> List-Post: <mailto:qemu-devel@nongnu.org> List-Help: <mailto:qemu-devel-request@nongnu.org?subject=help> List-Subscribe: <https://lists.nongnu.org/mailman/listinfo/qemu-devel>, <mailto:qemu-devel-request@nongnu.org?subject=subscribe> Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" <qemu-devel-bounces+importer=patchew.org@nongnu.org> X-ZohoMail-DKIM: pass (identity @linaro.org) Content-Type: text/plain; charset="utf-8" From: Hao Wu <wuhaotsh@google.com> 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> --- include/hw/misc/npcm7xx_clk.h | 2 + include/hw/timer/npcm7xx_timer.h | 48 +++- hw/arm/npcm7xx.c | 12 + hw/misc/npcm7xx_clk.c | 28 ++ hw/timer/npcm7xx_timer.c | 266 ++++++++++++++---- tests/qtest/npcm7xx_watchdog_timer-test.c | 319 ++++++++++++++++++++++ MAINTAINERS | 1 + tests/qtest/meson.build | 2 +- 8 files changed, 624 insertions(+), 54 deletions(-) create mode 100644 tests/qtest/npcm7xx_watchdog_timer-test.c diff --git a/include/hw/misc/npcm7xx_clk.h b/include/hw/misc/npcm7xx_clk.h index cdcc9e85342..2338fbbdb5d 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)) =20 +#define NPCM7XX_WATCHDOG_RESET_GPIO_IN "npcm7xx-clk-watchdog-reset-gpio-in" + typedef struct NPCM7xxCLKState { SysBusDevice parent; =20 diff --git a/include/hw/timer/npcm7xx_timer.h b/include/hw/timer/npcm7xx_ti= mer.h index 878a365a790..6993fd723a1 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)) =20 +/* 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-o= ut" + typedef struct NPCM7xxTimerCtrlState NPCM7xxTimerCtrlState; =20 /** - * struct NPCM7xxTimer - Individual timer state. - * @irq: GIC interrupt line to fire on expiration (if enabled). + * struct NPCM7xxBaseTimer - Basic functionality that both regular timer a= nd + * 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; =20 qemu_irq irq; - QEMUTimer qtimer; - int64_t expires_ns; - int64_t remaining_ns; + NPCM7xxBaseTimer base_timer; =20 uint32_t tcsr; uint32_t ticr; } NPCM7xxTimer; =20 +/** + * 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; =20 uint32_t tisr; - uint32_t wtcr; =20 NPCM7xxTimer timer[NPCM7XX_TIMERS_PER_CTRL]; + NPCM7xxWatchdogTimer watchdog_timer; }; =20 #define TYPE_NPCM7XX_TIMER "npcm7xx-timer" diff --git a/hw/arm/npcm7xx.c b/hw/arm/npcm7xx.c index 037f3a26f2e..c341dcab8b3 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 =3D 47, /* Timer Module 0 Watchdog */ + NPCM7XX_WDG1_IRQ, /* Timer Module 1 Watchdog */ + NPCM7XX_WDG2_IRQ, /* Timer Module 2 Watchdog */ }; =20 /* 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 =3D 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)); } =20 /* UART0..3 (16550 compatible) */ diff --git a/hw/misc/npcm7xx_clk.c b/hw/misc/npcm7xx_clk.c index 21ab4200d1c..6732437fe22 100644 --- a/hw/misc/npcm7xx_clk.c +++ b/hw/misc/npcm7xx_clk.c @@ -17,6 +17,7 @@ #include "qemu/osdep.h" =20 #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" =20 #define PLLCON_LOKI BIT(31) #define PLLCON_LOKS BIT(30) @@ -87,6 +89,12 @@ static const uint32_t cold_reset_values[NPCM7XX_CLK_NR_R= EGS] =3D { [NPCM7XX_CLK_AHBCKFI] =3D 0x000000c8, }; =20 +/* 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 siz= e) { uint32_t reg =3D offset / sizeof(uint32_t); @@ -187,6 +195,24 @@ static void npcm7xx_clk_write(void *opaque, hwaddr off= set, s->regs[reg] =3D value; } =20 +/* Perform reset action triggered by a watchdog */ +static void npcm7xx_clk_perform_watchdog_reset(void *opaque, int n, + int level) +{ + NPCM7xxCLKState *clk =3D NPCM7XX_CLK(opaque); + uint32_t rcr; + + g_assert(n >=3D 0 && n <=3D NPCM7XX_NR_WATCHDOGS); + rcr =3D 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 =3D { .read =3D npcm7xx_clk_read, .write =3D 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); } =20 static const VMStateDescription vmstate_npcm7xx_clk =3D { diff --git a/hw/timer/npcm7xx_timer.c b/hw/timer/npcm7xx_timer.c index 2df9e3e4969..d24445bd6e4 100644 --- a/hw/timer/npcm7xx_timer.c +++ b/hw/timer/npcm7xx_timer.c @@ -17,6 +17,7 @@ #include "qemu/osdep.h" =20 #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 =20 +#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 =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + t->expires_ns =3D 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 =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + t->remaining_ns =3D 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 =3D 0; + t->remaining_ns =3D 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; } =20 +static uint32_t npcm7xx_watchdog_timer_prescaler(const NPCM7xxWatchdogTime= r *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 =3D npcm7xx_watchdog_timer_prescaler(t); + int64_t ns =3D (NANOSECONDS_PER_SECOND / NPCM7XX_TIMER_REF_HZ) * cycle= s; + + /* + * The reset function always clears the current timer. The caller of t= he + * this needs to decide whether to start the watchdog timer based on + * specific flag in WTCR. + */ + npcm7xx_timer_clear(&t->base_timer); + + ns *=3D prescaler; + t->base_timer.remaining_ns =3D ns; +} + +static void npcm7xx_watchdog_timer_reset(NPCM7xxWatchdogTimer *t) +{ + int64_t cycles =3D 1; + uint32_t s =3D NPCM7XX_WTCR_WTIS(t->wtcr); + + g_assert(s <=3D 3); + + cycles <<=3D NPCM7XX_WATCHDOG_BASETIME_SHIFT; + cycles <<=3D 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); } =20 -/* Start or resume the timer. */ -static void npcm7xx_timer_start(NPCM7xxTimer *t) -{ - int64_t now; - - now =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - t->expires_ns =3D now + t->remaining_ns; - timer_mod(&t->qtimer, t->expires_ns); -} - /* * Called when the counter reaches zero. Sets the interrupt flag, and eith= er * restarts or disables the timer. @@ -138,9 +219,9 @@ static void npcm7xx_timer_reached_zero(NPCM7xxTimer *t) tc->tisr |=3D BIT(index); =20 if (t->tcsr & NPCM7XX_TCSR_PERIODIC) { - t->remaining_ns =3D npcm7xx_timer_count_to_ns(t, t->ticr); + t->base_timer.remaining_ns =3D npcm7xx_timer_count_to_ns(t, t->tic= r); if (t->tcsr & NPCM7XX_TCSR_CEN) { - npcm7xx_timer_start(t); + npcm7xx_timer_start(&t->base_timer); } } else { t->tcsr &=3D ~(NPCM7XX_TCSR_CEN | NPCM7XX_TCSR_CACT); @@ -149,15 +230,6 @@ static void npcm7xx_timer_reached_zero(NPCM7xxTimer *t) npcm7xx_timer_check_interrupt(t); } =20 -/* 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 =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - t->remaining_ns =3D t->expires_ns - now; -} =20 /* * 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 =3D npcm7xx_timer_count_to_ns(t, t->ticr); + t->base_timer.remaining_ns =3D npcm7xx_timer_count_to_ns(t, t->ticr); =20 if (old_tcsr & t->tcsr & NPCM7XX_TCSR_CEN) { - npcm7xx_timer_start(t); + npcm7xx_timer_start(&t->base_timer); } } =20 @@ -181,10 +253,10 @@ static uint32_t npcm7xx_timer_read_tdr(NPCM7xxTimer *= t) if (t->tcsr & NPCM7XX_TCSR_CEN) { int64_t now =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); =20 - return npcm7xx_timer_ns_to_count(t, t->expires_ns - now); + return npcm7xx_timer_ns_to_count(t, t->base_timer.expires_ns - now= ); } =20 - return npcm7xx_timer_ns_to_count(t, t->remaining_ns); + return npcm7xx_timer_ns_to_count(t, t->base_timer.remaining_ns); } =20 static void npcm7xx_timer_write_tcsr(NPCM7xxTimer *t, uint32_t new_tcsr) @@ -216,9 +288,9 @@ static void npcm7xx_timer_write_tcsr(NPCM7xxTimer *t, u= int32_t new_tcsr) =20 if (npcm7xx_tcsr_prescaler(old_tcsr) !=3D npcm7xx_tcsr_prescaler(new_t= csr)) { /* Recalculate time remaining based on the current TDR value. */ - t->remaining_ns =3D npcm7xx_timer_count_to_ns(t, tdr); + t->base_timer.remaining_ns =3D 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); } } =20 @@ -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 |=3D NPCM7XX_TCSR_CACT; - npcm7xx_timer_start(t); + npcm7xx_timer_start(&t->base_timer); } else { t->tcsr &=3D ~NPCM7XX_TCSR_CACT; - npcm7xx_timer_pause(t); - if (t->remaining_ns <=3D 0) { + npcm7xx_timer_pause(&t->base_timer); + if (t->base_timer.remaining_ns <=3D 0) { npcm7xx_timer_reached_zero(t); } } @@ -259,9 +331,47 @@ static void npcm7xx_timer_write_tisr(NPCM7xxTimerCtrlS= tate *s, uint32_t value) if (value & (1U << i)) { npcm7xx_timer_check_interrupt(&s->timer[i]); } + } } =20 +static void npcm7xx_timer_write_wtcr(NPCM7xxWatchdogTimer *t, uint32_t new= _wtcr) +{ + uint32_t old_wtcr =3D t->wtcr; + + /* + * WTIF and WTRF are cleared by writing 1. Writing 0 makes these bits + * unchanged. + */ + if (new_wtcr & NPCM7XX_WTCR_WTIF) { + new_wtcr &=3D ~NPCM7XX_WTCR_WTIF; + } else if (old_wtcr & NPCM7XX_WTCR_WTIF) { + new_wtcr |=3D NPCM7XX_WTCR_WTIF; + } + if (new_wtcr & NPCM7XX_WTCR_WTRF) { + new_wtcr &=3D ~NPCM7XX_WTCR_WTRF; + } else if (old_wtcr & NPCM7XX_WTCR_WTRF) { + new_wtcr |=3D NPCM7XX_WTCR_WTRF; + } + + t->wtcr =3D new_wtcr; + + if (new_wtcr & NPCM7XX_WTCR_WTR) { + t->wtcr &=3D ~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; =20 case NPCM7XX_TIMER_WTCR: - value =3D s->wtcr; + value =3D s->watchdog_timer.wtcr; break; =20 default: @@ -409,8 +519,7 @@ static void npcm7xx_timer_write(void *opaque, hwaddr of= fset, return; =20 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; } =20 @@ -448,15 +557,42 @@ static void npcm7xx_timer_enter_reset(Object *obj, Re= setType type) for (i =3D 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) { NPCM7xxTimer *t =3D &s->timer[i]; =20 - timer_del(&t->qtimer); - t->expires_ns =3D 0; - t->remaining_ns =3D 0; + npcm7xx_timer_clear(&t->base_timer); t->tcsr =3D 0x00000005; t->ticr =3D 0x00000000; } =20 s->tisr =3D 0x00000000; - s->wtcr =3D 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 =3D 0x00000400 | (s->watchdog_timer.wtcr & + NPCM7XX_WTCR_WTRF); +} + +static void npcm7xx_watchdog_timer_expired(void *opaque) +{ + NPCM7xxWatchdogTimer *t =3D opaque; + + if (t->wtcr & NPCM7XX_WTCR_WTE) { + if (t->wtcr & NPCM7XX_WTCR_WTIF) { + if (t->wtcr & NPCM7XX_WTCR_WTRE) { + t->wtcr |=3D NPCM7XX_WTCR_WTRF; + /* send reset signal to CLK module*/ + qemu_irq_raise(t->reset_signal); + } + } else { + t->wtcr |=3D 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); + } + } } =20 static void npcm7xx_timer_hold_reset(Object *obj) @@ -467,6 +603,7 @@ static void npcm7xx_timer_hold_reset(Object *obj) for (i =3D 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) { qemu_irq_lower(s->timer[i].irq); } + qemu_irq_lower(s->watchdog_timer.irq); } =20 static void npcm7xx_timer_realize(DeviceState *dev, Error **errp) @@ -474,43 +611,80 @@ static void npcm7xx_timer_realize(DeviceState *dev, E= rror **errp) NPCM7xxTimerCtrlState *s =3D NPCM7XX_TIMER(dev); SysBusDevice *sbd =3D &s->parent; int i; + NPCM7xxWatchdogTimer *w; =20 for (i =3D 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) { NPCM7xxTimer *t =3D &s->timer[i]; t->ctrl =3D s; - timer_init_ns(&t->qtimer, QEMU_CLOCK_VIRTUAL, npcm7xx_timer_expire= d, t); + timer_init_ns(&t->base_timer.qtimer, QEMU_CLOCK_VIRTUAL, + npcm7xx_timer_expired, t); sysbus_init_irq(sbd, &t->irq); } =20 + w =3D &s->watchdog_timer; + w->ctrl =3D 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); } =20 -static const VMStateDescription vmstate_npcm7xx_timer =3D { - .name =3D "npcm7xx-timer", +static const VMStateDescription vmstate_npcm7xx_base_timer =3D { + .name =3D "npcm7xx-base-timer", .version_id =3D 0, .minimum_version_id =3D 0, .fields =3D (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 =3D { + .name =3D "npcm7xx-timer", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (VMStateField[]) { + VMSTATE_STRUCT(base_timer, NPCM7xxTimer, + 0, vmstate_npcm7xx_base_timer, + NPCM7xxBaseTimer), VMSTATE_UINT32(tcsr, NPCM7xxTimer), VMSTATE_UINT32(ticr, NPCM7xxTimer), VMSTATE_END_OF_LIST(), }, }; =20 -static const VMStateDescription vmstate_npcm7xx_timer_ctrl =3D { - .name =3D "npcm7xx-timer-ctrl", +static const VMStateDescription vmstate_npcm7xx_watchdog_timer =3D { + .name =3D "npcm7xx-watchdog-timer", .version_id =3D 0, .minimum_version_id =3D 0, + .fields =3D (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 =3D { + .name =3D "npcm7xx-timer-ctrl", + .version_id =3D 1, + .minimum_version_id =3D 1, .fields =3D (VMStateField[]) { VMSTATE_UINT32(tisr, NPCM7xxTimerCtrlState), - VMSTATE_UINT32(wtcr, NPCM7xxTimerCtrlState), VMSTATE_STRUCT_ARRAY(timer, NPCM7xxTimerCtrlState, NPCM7XX_TIMERS_PER_CTRL, 0, vmstate_npcm7xx_t= imer, NPCM7xxTimer), + VMSTATE_STRUCT(watchdog_timer, NPCM7xxTimerCtrlState, + 0, vmstate_npcm7xx_watchdog_timer, + NPCM7xxWatchdogTimer), VMSTATE_END_OF_LIST(), }, }; diff --git a/tests/qtest/npcm7xx_watchdog_timer-test.c b/tests/qtest/npcm7x= x_watchdog_timer-test.c new file mode 100644 index 00000000000..54d5d6d8f2f --- /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 WIT= HOUT + * 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[] =3D { + { + .irq =3D 47, + .base_addr =3D 0xf0008000 + }, + { + .irq =3D 48, + .base_addr =3D 0xf0009000 + }, + { + .irq =3D 49, + .base_addr =3D 0xf000a000 + } +}; + +static int watchdog_index(const Watchdog *wd) +{ + ptrdiff_t diff =3D wd - watchdog_list; + + g_assert(diff >=3D 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 =3D qtest_qmp_eventwait_ref(qts, "WATCHDOG"); + QDict *data; + + data =3D 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 =3D 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 *w= d) +{ + 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 =3D watchdog; + QTestState *qts =3D 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), =3D=3D, WTCLK(1)); + + qtest_quit(qts); +} + +/* Check a watchdog can generate interrupt and reset actions */ +static void test_reset_action(gconstpointer watchdog) +{ + const Watchdog *wd =3D watchdog; + QTestState *qts =3D 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), =3D=3D, + 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), =3D=3D, + 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 =3D 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 should= n't + * be reset. + */ + g_assert_cmphex(watchdog_read_wtcr(qts, wd), =3D=3D, WTCLK(1) | WTRF); + qtest_quit(qts); +} + +/* Check a watchdog works with all possible WTCLK prescalers and WTIS cycl= es */ +static void test_prescaler(gconstpointer watchdog) +{ + const Watchdog *wd =3D watchdog; + + for (int wtclk =3D 0; wtclk < 4; ++wtclk) { + for (int wtis =3D 0; wtis < 4; ++wtis) { + QTestState *qts =3D 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) ar= e not + * set. + */ +static void test_enabling_flags(gconstpointer watchdog) +{ + const Watchdog *wd =3D watchdog; + QTestState *qts; + + /* Neither WTIE or WTRE is set, no interrupt or reset should happen */ + qts =3D 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 happe= n */ + qts =3D 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 happe= n */ + qts =3D 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 =3D watchdog; + QTestState *qts; + int64_t remaining_steps, steps; + + qts =3D 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 =3D watchdog_interrupt_steps(qts, wd); + g_assert_cmphex(watchdog_read_wtcr(qts, wd), =3D=3D, WTCLK(0) | WTE | = WTIE); + + /* Run for half of the execution period. */ + steps =3D remaining_steps / 2; + remaining_steps -=3D 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), =3D=3D, 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), =3D=3D, 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), =3D=3D, 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), =3D=3D, + 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 =3D 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 =3D 0; i < ARRAY_SIZE(watchdog_list); ++i) { + const Watchdog *wd =3D &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(); +} diff --git a/MAINTAINERS b/MAINTAINERS index ef6f5c73998..1edc8bd4c9a 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 =20 diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index ba8ebeead6a..6c9d3426d1a 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -133,7 +133,7 @@ qtests_sparc64 =3D \ (config_all_devices.has_key('CONFIG_ISA_TESTDEV') ? ['endianness-test'] = : []) + \ ['prom-env-test', 'boot-serial-test'] =20 -qtests_npcm7xx =3D ['npcm7xx_timer-test'] +qtests_npcm7xx =3D ['npcm7xx_timer-test', 'npcm7xx_watchdog_timer-test'] qtests_arm =3D \ (config_all_devices.has_key('CONFIG_PFLASH_CFI02') ? ['pflash-cfi02-test= '] : []) + \ (config_all_devices.has_key('CONFIG_NPCM7XX') ? qtests_npcm7xx : []) + \ --=20 2.20.1