summaryrefslogtreecommitdiffstats
path: root/hw/watchdog
diff options
context:
space:
mode:
Diffstat (limited to 'hw/watchdog')
-rw-r--r--hw/watchdog/Kconfig22
-rw-r--r--hw/watchdog/cmsdk-apb-watchdog.c414
-rw-r--r--hw/watchdog/meson.build9
-rw-r--r--hw/watchdog/sbsa_gwdt.c288
-rw-r--r--hw/watchdog/spapr_watchdog.c274
-rw-r--r--hw/watchdog/trace-events18
-rw-r--r--hw/watchdog/trace.h1
-rw-r--r--hw/watchdog/watchdog.c92
-rw-r--r--hw/watchdog/wdt_aspeed.c421
-rw-r--r--hw/watchdog/wdt_diag288.c139
-rw-r--r--hw/watchdog/wdt_i6300esb.c494
-rw-r--r--hw/watchdog/wdt_ib700.c154
-rw-r--r--hw/watchdog/wdt_imx2.c298
13 files changed, 2624 insertions, 0 deletions
diff --git a/hw/watchdog/Kconfig b/hw/watchdog/Kconfig
new file mode 100644
index 00000000..66e1d029
--- /dev/null
+++ b/hw/watchdog/Kconfig
@@ -0,0 +1,22 @@
+config CMSDK_APB_WATCHDOG
+ bool
+ select PTIMER
+
+config WDT_IB6300ESB
+ bool
+ default y if PCI_DEVICES
+ depends on PCI
+
+config WDT_IB700
+ bool
+ default y
+ depends on ISA_BUS
+
+config WDT_DIAG288
+ bool
+
+config WDT_IMX2
+ bool
+
+config WDT_SBSA
+ bool
diff --git a/hw/watchdog/cmsdk-apb-watchdog.c b/hw/watchdog/cmsdk-apb-watchdog.c
new file mode 100644
index 00000000..5a2cd46e
--- /dev/null
+++ b/hw/watchdog/cmsdk-apb-watchdog.c
@@ -0,0 +1,414 @@
+/*
+ * ARM CMSDK APB watchdog emulation
+ *
+ * Copyright (c) 2018 Linaro Limited
+ * Written by Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 or
+ * (at your option) any later version.
+ */
+
+/*
+ * This is a model of the "APB watchdog" which is part of the Cortex-M
+ * System Design Kit (CMSDK) and documented in the Cortex-M System
+ * Design Kit Technical Reference Manual (ARM DDI0479C):
+ * https://developer.arm.com/products/system-design/system-design-kits/cortex-m-system-design-kit
+ *
+ * We also support the variant of this device found in the TI
+ * Stellaris/Luminary boards and documented in:
+ * http://www.ti.com/lit/ds/symlink/lm3s6965.pdf
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "trace.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "sysemu/watchdog.h"
+#include "hw/sysbus.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/registerfields.h"
+#include "hw/qdev-clock.h"
+#include "hw/watchdog/cmsdk-apb-watchdog.h"
+#include "migration/vmstate.h"
+
+REG32(WDOGLOAD, 0x0)
+REG32(WDOGVALUE, 0x4)
+REG32(WDOGCONTROL, 0x8)
+ FIELD(WDOGCONTROL, INTEN, 0, 1)
+ FIELD(WDOGCONTROL, RESEN, 1, 1)
+#define R_WDOGCONTROL_VALID_MASK (R_WDOGCONTROL_INTEN_MASK | \
+ R_WDOGCONTROL_RESEN_MASK)
+REG32(WDOGINTCLR, 0xc)
+REG32(WDOGRIS, 0x10)
+ FIELD(WDOGRIS, INT, 0, 1)
+REG32(WDOGMIS, 0x14)
+REG32(WDOGTEST, 0x418) /* only in Stellaris/Luminary version of the device */
+REG32(WDOGLOCK, 0xc00)
+#define WDOG_UNLOCK_VALUE 0x1ACCE551
+REG32(WDOGITCR, 0xf00)
+ FIELD(WDOGITCR, ENABLE, 0, 1)
+#define R_WDOGITCR_VALID_MASK R_WDOGITCR_ENABLE_MASK
+REG32(WDOGITOP, 0xf04)
+ FIELD(WDOGITOP, WDOGRES, 0, 1)
+ FIELD(WDOGITOP, WDOGINT, 1, 1)
+#define R_WDOGITOP_VALID_MASK (R_WDOGITOP_WDOGRES_MASK | \
+ R_WDOGITOP_WDOGINT_MASK)
+REG32(PID4, 0xfd0)
+REG32(PID5, 0xfd4)
+REG32(PID6, 0xfd8)
+REG32(PID7, 0xfdc)
+REG32(PID0, 0xfe0)
+REG32(PID1, 0xfe4)
+REG32(PID2, 0xfe8)
+REG32(PID3, 0xfec)
+REG32(CID0, 0xff0)
+REG32(CID1, 0xff4)
+REG32(CID2, 0xff8)
+REG32(CID3, 0xffc)
+
+/* PID/CID values */
+static const uint32_t cmsdk_apb_watchdog_id[] = {
+ 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
+ 0x24, 0xb8, 0x1b, 0x00, /* PID0..PID3 */
+ 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
+};
+
+static const uint32_t luminary_watchdog_id[] = {
+ 0x00, 0x00, 0x00, 0x00, /* PID4..PID7 */
+ 0x05, 0x18, 0x18, 0x01, /* PID0..PID3 */
+ 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
+};
+
+static bool cmsdk_apb_watchdog_intstatus(CMSDKAPBWatchdog *s)
+{
+ /* Return masked interrupt status */
+ return s->intstatus && (s->control & R_WDOGCONTROL_INTEN_MASK);
+}
+
+static bool cmsdk_apb_watchdog_resetstatus(CMSDKAPBWatchdog *s)
+{
+ /* Return masked reset status */
+ return s->resetstatus && (s->control & R_WDOGCONTROL_RESEN_MASK);
+}
+
+static void cmsdk_apb_watchdog_update(CMSDKAPBWatchdog *s)
+{
+ bool wdogint;
+ bool wdogres;
+
+ if (s->itcr) {
+ /*
+ * Not checking that !s->is_luminary since s->itcr can't be written
+ * when s->is_luminary in the first place.
+ */
+ wdogint = s->itop & R_WDOGITOP_WDOGINT_MASK;
+ wdogres = s->itop & R_WDOGITOP_WDOGRES_MASK;
+ } else {
+ wdogint = cmsdk_apb_watchdog_intstatus(s);
+ wdogres = cmsdk_apb_watchdog_resetstatus(s);
+ }
+
+ qemu_set_irq(s->wdogint, wdogint);
+ if (wdogres) {
+ watchdog_perform_action();
+ }
+}
+
+static uint64_t cmsdk_apb_watchdog_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(opaque);
+ uint64_t r;
+
+ switch (offset) {
+ case A_WDOGLOAD:
+ r = ptimer_get_limit(s->timer);
+ break;
+ case A_WDOGVALUE:
+ r = ptimer_get_count(s->timer);
+ break;
+ case A_WDOGCONTROL:
+ r = s->control;
+ break;
+ case A_WDOGRIS:
+ r = s->intstatus;
+ break;
+ case A_WDOGMIS:
+ r = cmsdk_apb_watchdog_intstatus(s);
+ break;
+ case A_WDOGLOCK:
+ r = s->lock;
+ break;
+ case A_WDOGITCR:
+ if (s->is_luminary) {
+ goto bad_offset;
+ }
+ r = s->itcr;
+ break;
+ case A_PID4 ... A_CID3:
+ r = s->id[(offset - A_PID4) / 4];
+ break;
+ case A_WDOGINTCLR:
+ case A_WDOGITOP:
+ if (s->is_luminary) {
+ goto bad_offset;
+ }
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "CMSDK APB watchdog read: read of WO offset %x\n",
+ (int)offset);
+ r = 0;
+ break;
+ case A_WDOGTEST:
+ if (!s->is_luminary) {
+ goto bad_offset;
+ }
+ qemu_log_mask(LOG_UNIMP,
+ "Luminary watchdog read: stall not implemented\n");
+ r = 0;
+ break;
+ default:
+bad_offset:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "CMSDK APB watchdog read: bad offset %x\n", (int)offset);
+ r = 0;
+ break;
+ }
+ trace_cmsdk_apb_watchdog_read(offset, r, size);
+ return r;
+}
+
+static void cmsdk_apb_watchdog_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(opaque);
+
+ trace_cmsdk_apb_watchdog_write(offset, value, size);
+
+ if (s->lock && offset != A_WDOGLOCK) {
+ /* Write access is disabled via WDOGLOCK */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "CMSDK APB watchdog write: write to locked watchdog\n");
+ return;
+ }
+
+ switch (offset) {
+ case A_WDOGLOAD:
+ /*
+ * Reset the load value and the current count, and make sure
+ * we're counting.
+ */
+ ptimer_transaction_begin(s->timer);
+ ptimer_set_limit(s->timer, value, 1);
+ ptimer_run(s->timer, 0);
+ ptimer_transaction_commit(s->timer);
+ break;
+ case A_WDOGCONTROL:
+ if (s->is_luminary && 0 != (R_WDOGCONTROL_INTEN_MASK & s->control)) {
+ /*
+ * The Luminary version of this device ignores writes to
+ * this register after the guest has enabled interrupts
+ * (so they can only be disabled again via reset).
+ */
+ break;
+ }
+ s->control = value & R_WDOGCONTROL_VALID_MASK;
+ cmsdk_apb_watchdog_update(s);
+ break;
+ case A_WDOGINTCLR:
+ s->intstatus = 0;
+ ptimer_transaction_begin(s->timer);
+ ptimer_set_count(s->timer, ptimer_get_limit(s->timer));
+ ptimer_transaction_commit(s->timer);
+ cmsdk_apb_watchdog_update(s);
+ break;
+ case A_WDOGLOCK:
+ s->lock = (value != WDOG_UNLOCK_VALUE);
+ trace_cmsdk_apb_watchdog_lock(s->lock);
+ break;
+ case A_WDOGITCR:
+ if (s->is_luminary) {
+ goto bad_offset;
+ }
+ s->itcr = value & R_WDOGITCR_VALID_MASK;
+ cmsdk_apb_watchdog_update(s);
+ break;
+ case A_WDOGITOP:
+ if (s->is_luminary) {
+ goto bad_offset;
+ }
+ s->itop = value & R_WDOGITOP_VALID_MASK;
+ cmsdk_apb_watchdog_update(s);
+ break;
+ case A_WDOGVALUE:
+ case A_WDOGRIS:
+ case A_WDOGMIS:
+ case A_PID4 ... A_CID3:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "CMSDK APB watchdog write: write to RO offset 0x%x\n",
+ (int)offset);
+ break;
+ case A_WDOGTEST:
+ if (!s->is_luminary) {
+ goto bad_offset;
+ }
+ qemu_log_mask(LOG_UNIMP,
+ "Luminary watchdog write: stall not implemented\n");
+ break;
+ default:
+bad_offset:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "CMSDK APB watchdog write: bad offset 0x%x\n",
+ (int)offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps cmsdk_apb_watchdog_ops = {
+ .read = cmsdk_apb_watchdog_read,
+ .write = cmsdk_apb_watchdog_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ /* byte/halfword accesses are just zero-padded on reads and writes */
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+};
+
+static void cmsdk_apb_watchdog_tick(void *opaque)
+{
+ CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(opaque);
+
+ if (!s->intstatus) {
+ /* Count expired for the first time: raise interrupt */
+ s->intstatus = R_WDOGRIS_INT_MASK;
+ } else {
+ /* Count expired for the second time: raise reset and stop clock */
+ s->resetstatus = 1;
+ ptimer_stop(s->timer);
+ }
+ cmsdk_apb_watchdog_update(s);
+}
+
+static void cmsdk_apb_watchdog_reset(DeviceState *dev)
+{
+ CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(dev);
+
+ trace_cmsdk_apb_watchdog_reset();
+ s->control = 0;
+ s->intstatus = 0;
+ s->lock = 0;
+ s->itcr = 0;
+ s->itop = 0;
+ s->resetstatus = 0;
+ /* Set the limit and the count */
+ ptimer_transaction_begin(s->timer);
+ ptimer_set_limit(s->timer, 0xffffffff, 1);
+ ptimer_run(s->timer, 0);
+ ptimer_transaction_commit(s->timer);
+}
+
+static void cmsdk_apb_watchdog_clk_update(void *opaque, ClockEvent event)
+{
+ CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(opaque);
+
+ ptimer_transaction_begin(s->timer);
+ ptimer_set_period_from_clock(s->timer, s->wdogclk, 1);
+ ptimer_transaction_commit(s->timer);
+}
+
+static void cmsdk_apb_watchdog_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(obj);
+
+ memory_region_init_io(&s->iomem, obj, &cmsdk_apb_watchdog_ops,
+ s, "cmsdk-apb-watchdog", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->wdogint);
+ s->wdogclk = qdev_init_clock_in(DEVICE(s), "WDOGCLK",
+ cmsdk_apb_watchdog_clk_update, s,
+ ClockUpdate);
+
+ s->is_luminary = false;
+ s->id = cmsdk_apb_watchdog_id;
+}
+
+static void cmsdk_apb_watchdog_realize(DeviceState *dev, Error **errp)
+{
+ CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(dev);
+
+ if (!clock_has_source(s->wdogclk)) {
+ error_setg(errp,
+ "CMSDK APB watchdog: WDOGCLK clock must be connected");
+ return;
+ }
+
+ s->timer = ptimer_init(cmsdk_apb_watchdog_tick, s,
+ PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD |
+ PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT |
+ PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
+ PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
+
+ ptimer_transaction_begin(s->timer);
+ ptimer_set_period_from_clock(s->timer, s->wdogclk, 1);
+ ptimer_transaction_commit(s->timer);
+}
+
+static const VMStateDescription cmsdk_apb_watchdog_vmstate = {
+ .name = "cmsdk-apb-watchdog",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_CLOCK(wdogclk, CMSDKAPBWatchdog),
+ VMSTATE_PTIMER(timer, CMSDKAPBWatchdog),
+ VMSTATE_UINT32(control, CMSDKAPBWatchdog),
+ VMSTATE_UINT32(intstatus, CMSDKAPBWatchdog),
+ VMSTATE_UINT32(lock, CMSDKAPBWatchdog),
+ VMSTATE_UINT32(itcr, CMSDKAPBWatchdog),
+ VMSTATE_UINT32(itop, CMSDKAPBWatchdog),
+ VMSTATE_UINT32(resetstatus, CMSDKAPBWatchdog),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void cmsdk_apb_watchdog_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = cmsdk_apb_watchdog_realize;
+ dc->vmsd = &cmsdk_apb_watchdog_vmstate;
+ dc->reset = cmsdk_apb_watchdog_reset;
+}
+
+static const TypeInfo cmsdk_apb_watchdog_info = {
+ .name = TYPE_CMSDK_APB_WATCHDOG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(CMSDKAPBWatchdog),
+ .instance_init = cmsdk_apb_watchdog_init,
+ .class_init = cmsdk_apb_watchdog_class_init,
+};
+
+static void luminary_watchdog_init(Object *obj)
+{
+ CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(obj);
+
+ s->is_luminary = true;
+ s->id = luminary_watchdog_id;
+}
+
+static const TypeInfo luminary_watchdog_info = {
+ .name = TYPE_LUMINARY_WATCHDOG,
+ .parent = TYPE_CMSDK_APB_WATCHDOG,
+ .instance_init = luminary_watchdog_init
+};
+
+static void cmsdk_apb_watchdog_register_types(void)
+{
+ type_register_static(&cmsdk_apb_watchdog_info);
+ type_register_static(&luminary_watchdog_info);
+}
+
+type_init(cmsdk_apb_watchdog_register_types);
diff --git a/hw/watchdog/meson.build b/hw/watchdog/meson.build
new file mode 100644
index 00000000..8974b5cf
--- /dev/null
+++ b/hw/watchdog/meson.build
@@ -0,0 +1,9 @@
+softmmu_ss.add(files('watchdog.c'))
+softmmu_ss.add(when: 'CONFIG_CMSDK_APB_WATCHDOG', if_true: files('cmsdk-apb-watchdog.c'))
+softmmu_ss.add(when: 'CONFIG_WDT_IB6300ESB', if_true: files('wdt_i6300esb.c'))
+softmmu_ss.add(when: 'CONFIG_WDT_IB700', if_true: files('wdt_ib700.c'))
+softmmu_ss.add(when: 'CONFIG_WDT_DIAG288', if_true: files('wdt_diag288.c'))
+softmmu_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('wdt_aspeed.c'))
+softmmu_ss.add(when: 'CONFIG_WDT_IMX2', if_true: files('wdt_imx2.c'))
+softmmu_ss.add(when: 'CONFIG_WDT_SBSA', if_true: files('sbsa_gwdt.c'))
+specific_ss.add(when: 'CONFIG_PSERIES', if_true: files('spapr_watchdog.c'))
diff --git a/hw/watchdog/sbsa_gwdt.c b/hw/watchdog/sbsa_gwdt.c
new file mode 100644
index 00000000..7aa57a8c
--- /dev/null
+++ b/hw/watchdog/sbsa_gwdt.c
@@ -0,0 +1,288 @@
+/*
+ * Generic watchdog device model for SBSA
+ *
+ * The watchdog device has been implemented as revision 1 variant of
+ * the ARM SBSA specification v6.0
+ * (https://developer.arm.com/documentation/den0029/d?lang=en)
+ *
+ * Copyright Linaro.org 2020
+ *
+ * Authors:
+ * Shashi Mallela <shashi.mallela@linaro.org>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at your
+ * option) any later version. See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "sysemu/reset.h"
+#include "sysemu/watchdog.h"
+#include "hw/watchdog/sbsa_gwdt.h"
+#include "qemu/timer.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+static const VMStateDescription vmstate_sbsa_gwdt = {
+ .name = "sbsa-gwdt",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_TIMER_PTR(timer, SBSA_GWDTState),
+ VMSTATE_UINT32(wcs, SBSA_GWDTState),
+ VMSTATE_UINT32(worl, SBSA_GWDTState),
+ VMSTATE_UINT32(woru, SBSA_GWDTState),
+ VMSTATE_UINT32(wcvl, SBSA_GWDTState),
+ VMSTATE_UINT32(wcvu, SBSA_GWDTState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+typedef enum WdtRefreshType {
+ EXPLICIT_REFRESH = 0,
+ TIMEOUT_REFRESH = 1,
+} WdtRefreshType;
+
+static uint64_t sbsa_gwdt_rread(void *opaque, hwaddr addr, unsigned int size)
+{
+ SBSA_GWDTState *s = SBSA_GWDT(opaque);
+ uint32_t ret = 0;
+
+ switch (addr) {
+ case SBSA_GWDT_WRR:
+ /* watch refresh read has no effect and returns 0 */
+ ret = 0;
+ break;
+ case SBSA_GWDT_W_IIDR:
+ ret = s->id;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "bad address in refresh frame read :"
+ " 0x%x\n", (int)addr);
+ }
+ return ret;
+}
+
+static uint64_t sbsa_gwdt_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ SBSA_GWDTState *s = SBSA_GWDT(opaque);
+ uint32_t ret = 0;
+
+ switch (addr) {
+ case SBSA_GWDT_WCS:
+ ret = s->wcs;
+ break;
+ case SBSA_GWDT_WOR:
+ ret = s->worl;
+ break;
+ case SBSA_GWDT_WORU:
+ ret = s->woru;
+ break;
+ case SBSA_GWDT_WCV:
+ ret = s->wcvl;
+ break;
+ case SBSA_GWDT_WCVU:
+ ret = s->wcvu;
+ break;
+ case SBSA_GWDT_W_IIDR:
+ ret = s->id;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "bad address in control frame read :"
+ " 0x%x\n", (int)addr);
+ }
+ return ret;
+}
+
+static void sbsa_gwdt_update_timer(SBSA_GWDTState *s, WdtRefreshType rtype)
+{
+ uint64_t timeout = 0;
+
+ timer_del(s->timer);
+
+ if (s->wcs & SBSA_GWDT_WCS_EN) {
+ /*
+ * Extract the upper 16 bits from woru & 32 bits from worl
+ * registers to construct the 48 bit offset value
+ */
+ timeout = s->woru;
+ timeout <<= 32;
+ timeout |= s->worl;
+ timeout = muldiv64(timeout, NANOSECONDS_PER_SECOND, SBSA_TIMER_FREQ);
+ timeout += qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ if ((rtype == EXPLICIT_REFRESH) || ((rtype == TIMEOUT_REFRESH) &&
+ (!(s->wcs & SBSA_GWDT_WCS_WS0)))) {
+ /* store the current timeout value into compare registers */
+ s->wcvu = timeout >> 32;
+ s->wcvl = timeout;
+ }
+ timer_mod(s->timer, timeout);
+ }
+}
+
+static void sbsa_gwdt_rwrite(void *opaque, hwaddr offset, uint64_t data,
+ unsigned size) {
+ SBSA_GWDTState *s = SBSA_GWDT(opaque);
+
+ if (offset == SBSA_GWDT_WRR) {
+ s->wcs &= ~(SBSA_GWDT_WCS_WS0 | SBSA_GWDT_WCS_WS1);
+
+ sbsa_gwdt_update_timer(s, EXPLICIT_REFRESH);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "bad address in refresh frame write :"
+ " 0x%x\n", (int)offset);
+ }
+}
+
+static void sbsa_gwdt_write(void *opaque, hwaddr offset, uint64_t data,
+ unsigned size) {
+ SBSA_GWDTState *s = SBSA_GWDT(opaque);
+
+ switch (offset) {
+ case SBSA_GWDT_WCS:
+ s->wcs = data & SBSA_GWDT_WCS_EN;
+ qemu_set_irq(s->irq, 0);
+ sbsa_gwdt_update_timer(s, EXPLICIT_REFRESH);
+ break;
+
+ case SBSA_GWDT_WOR:
+ s->worl = data;
+ s->wcs &= ~(SBSA_GWDT_WCS_WS0 | SBSA_GWDT_WCS_WS1);
+ qemu_set_irq(s->irq, 0);
+ sbsa_gwdt_update_timer(s, EXPLICIT_REFRESH);
+ break;
+
+ case SBSA_GWDT_WORU:
+ s->woru = data & SBSA_GWDT_WOR_MASK;
+ s->wcs &= ~(SBSA_GWDT_WCS_WS0 | SBSA_GWDT_WCS_WS1);
+ qemu_set_irq(s->irq, 0);
+ sbsa_gwdt_update_timer(s, EXPLICIT_REFRESH);
+ break;
+
+ case SBSA_GWDT_WCV:
+ s->wcvl = data;
+ break;
+
+ case SBSA_GWDT_WCVU:
+ s->wcvu = data;
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "bad address in control frame write :"
+ " 0x%x\n", (int)offset);
+ }
+ return;
+}
+
+static void wdt_sbsa_gwdt_reset(DeviceState *dev)
+{
+ SBSA_GWDTState *s = SBSA_GWDT(dev);
+
+ timer_del(s->timer);
+
+ s->wcs = 0;
+ s->wcvl = 0;
+ s->wcvu = 0;
+ s->worl = 0;
+ s->woru = 0;
+ s->id = SBSA_GWDT_ID;
+}
+
+static void sbsa_gwdt_timer_sysinterrupt(void *opaque)
+{
+ SBSA_GWDTState *s = SBSA_GWDT(opaque);
+
+ if (!(s->wcs & SBSA_GWDT_WCS_WS0)) {
+ s->wcs |= SBSA_GWDT_WCS_WS0;
+ sbsa_gwdt_update_timer(s, TIMEOUT_REFRESH);
+ qemu_set_irq(s->irq, 1);
+ } else {
+ s->wcs |= SBSA_GWDT_WCS_WS1;
+ qemu_log_mask(CPU_LOG_RESET, "Watchdog timer expired.\n");
+ /*
+ * Reset the watchdog only if the guest gets notified about
+ * expiry. watchdog_perform_action() may temporarily relinquish
+ * the BQL; reset before triggering the action to avoid races with
+ * sbsa_gwdt instructions.
+ */
+ switch (get_watchdog_action()) {
+ case WATCHDOG_ACTION_DEBUG:
+ case WATCHDOG_ACTION_NONE:
+ case WATCHDOG_ACTION_PAUSE:
+ break;
+ default:
+ wdt_sbsa_gwdt_reset(DEVICE(s));
+ }
+ watchdog_perform_action();
+ }
+}
+
+static const MemoryRegionOps sbsa_gwdt_rops = {
+ .read = sbsa_gwdt_rread,
+ .write = sbsa_gwdt_rwrite,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .valid.unaligned = false,
+};
+
+static const MemoryRegionOps sbsa_gwdt_ops = {
+ .read = sbsa_gwdt_read,
+ .write = sbsa_gwdt_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .valid.unaligned = false,
+};
+
+static void wdt_sbsa_gwdt_realize(DeviceState *dev, Error **errp)
+{
+ SBSA_GWDTState *s = SBSA_GWDT(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ memory_region_init_io(&s->rmmio, OBJECT(dev),
+ &sbsa_gwdt_rops, s,
+ "sbsa_gwdt.refresh",
+ SBSA_GWDT_RMMIO_SIZE);
+
+ memory_region_init_io(&s->cmmio, OBJECT(dev),
+ &sbsa_gwdt_ops, s,
+ "sbsa_gwdt.control",
+ SBSA_GWDT_CMMIO_SIZE);
+
+ sysbus_init_mmio(sbd, &s->rmmio);
+ sysbus_init_mmio(sbd, &s->cmmio);
+
+ sysbus_init_irq(sbd, &s->irq);
+
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, sbsa_gwdt_timer_sysinterrupt,
+ dev);
+}
+
+static void wdt_sbsa_gwdt_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = wdt_sbsa_gwdt_realize;
+ dc->reset = wdt_sbsa_gwdt_reset;
+ dc->hotpluggable = false;
+ set_bit(DEVICE_CATEGORY_WATCHDOG, dc->categories);
+ dc->vmsd = &vmstate_sbsa_gwdt;
+ dc->desc = "SBSA-compliant generic watchdog device";
+}
+
+static const TypeInfo wdt_sbsa_gwdt_info = {
+ .class_init = wdt_sbsa_gwdt_class_init,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .name = TYPE_WDT_SBSA,
+ .instance_size = sizeof(SBSA_GWDTState),
+};
+
+static void wdt_sbsa_gwdt_register_types(void)
+{
+ type_register_static(&wdt_sbsa_gwdt_info);
+}
+
+type_init(wdt_sbsa_gwdt_register_types)
diff --git a/hw/watchdog/spapr_watchdog.c b/hw/watchdog/spapr_watchdog.c
new file mode 100644
index 00000000..55ff1f03
--- /dev/null
+++ b/hw/watchdog/spapr_watchdog.c
@@ -0,0 +1,274 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "target/ppc/cpu.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+#include "hw/ppc/spapr.h"
+
+#define FIELD_BE(reg, field, start, len) \
+ FIELD(reg, field, 64 - (start + len), len)
+
+/*
+ * Bits 47: "leaveOtherWatchdogsRunningOnTimeout", specified on
+ * the "Start watchdog" operation,
+ * 0 - stop out-standing watchdogs on timeout,
+ * 1 - leave outstanding watchdogs running on timeout
+ */
+FIELD_BE(PSERIES_WDTF, LEAVE_OTHER, 47, 1)
+
+/* Bits 48-55: "operation" */
+FIELD_BE(PSERIES_WDTF, OP, 48, 8)
+#define PSERIES_WDTF_OP_START 0x1
+#define PSERIES_WDTF_OP_STOP 0x2
+#define PSERIES_WDTF_OP_QUERY 0x3
+#define PSERIES_WDTF_OP_QUERY_LPM 0x4
+
+/* Bits 56-63: "timeoutAction" */
+FIELD_BE(PSERIES_WDTF, ACTION, 56, 8)
+#define PSERIES_WDTF_ACTION_HARD_POWER_OFF 0x1
+#define PSERIES_WDTF_ACTION_HARD_RESTART 0x2
+#define PSERIES_WDTF_ACTION_DUMP_RESTART 0x3
+
+FIELD_BE(PSERIES_WDTF, RESERVED, 0, 47)
+
+/* Special watchdogNumber for the "stop all watchdogs" operation */
+#define PSERIES_WDT_STOP_ALL ((uint64_t)~0)
+
+/*
+ * For the "Query watchdog capabilities" operation, a uint64 structure
+ * defined as:
+ * Bits 0-15: The minimum supported timeout in milliseconds
+ * Bits 16-31: The number of watchdogs supported
+ * Bits 32-63: Reserved
+ */
+FIELD_BE(PSERIES_WDTQ, MIN_TIMEOUT, 0, 16)
+FIELD_BE(PSERIES_WDTQ, NUM, 16, 16)
+
+/*
+ * For the "Query watchdog LPM requirement" operation:
+ * 1 = The given "watchdogNumber" must be stopped prior to suspending
+ * 2 = The given "watchdogNumber" does not have to be stopped prior to
+ * suspending
+ */
+#define PSERIES_WDTQL_STOPPED 1
+#define PSERIES_WDTQL_QUERY_NOT_STOPPED 2
+
+#define WDT_MIN_TIMEOUT 1 /* 1ms */
+
+static target_ulong watchdog_stop(unsigned watchdogNumber, SpaprWatchdog *w)
+{
+ target_ulong ret = H_NOOP;
+
+ if (timer_pending(&w->timer)) {
+ timer_del(&w->timer);
+ ret = H_SUCCESS;
+ }
+ trace_spapr_watchdog_stop(watchdogNumber, ret);
+
+ return ret;
+}
+
+static target_ulong watchdog_stop_all(SpaprMachineState *spapr)
+{
+ target_ulong ret = H_NOOP;
+ int i;
+
+ for (i = 1; i <= ARRAY_SIZE(spapr->wds); ++i) {
+ target_ulong r = watchdog_stop(i, &spapr->wds[i - 1]);
+
+ if (r != H_NOOP && r != H_SUCCESS) {
+ ret = r;
+ }
+ }
+
+ return ret;
+}
+
+static void watchdog_expired(void *pw)
+{
+ SpaprWatchdog *w = pw;
+ CPUState *cs;
+ SpaprMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
+ unsigned num = w - spapr->wds;
+
+ g_assert(num < ARRAY_SIZE(spapr->wds));
+ trace_spapr_watchdog_expired(num, w->action);
+ switch (w->action) {
+ case PSERIES_WDTF_ACTION_HARD_POWER_OFF:
+ qemu_system_vmstop_request(RUN_STATE_SHUTDOWN);
+ break;
+ case PSERIES_WDTF_ACTION_HARD_RESTART:
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ break;
+ case PSERIES_WDTF_ACTION_DUMP_RESTART:
+ CPU_FOREACH(cs) {
+ async_run_on_cpu(cs, spapr_do_system_reset_on_cpu, RUN_ON_CPU_NULL);
+ }
+ break;
+ }
+ if (!w->leave_others) {
+ watchdog_stop_all(spapr);
+ }
+}
+
+static target_ulong h_watchdog(PowerPCCPU *cpu,
+ SpaprMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong ret = H_SUCCESS;
+ target_ulong flags = args[0];
+ target_ulong watchdogNumber = args[1]; /* 1-Based per PAPR */
+ target_ulong timeoutInMs = args[2];
+ unsigned operation = FIELD_EX64(flags, PSERIES_WDTF, OP);
+ unsigned timeoutAction = FIELD_EX64(flags, PSERIES_WDTF, ACTION);
+ SpaprWatchdog *w;
+
+ if (FIELD_EX64(flags, PSERIES_WDTF, RESERVED)) {
+ return H_PARAMETER;
+ }
+
+ switch (operation) {
+ case PSERIES_WDTF_OP_START:
+ if (watchdogNumber > ARRAY_SIZE(spapr->wds)) {
+ return H_P2;
+ }
+ if (timeoutInMs <= WDT_MIN_TIMEOUT) {
+ return H_P3;
+ }
+
+ w = &spapr->wds[watchdogNumber - 1];
+ switch (timeoutAction) {
+ case PSERIES_WDTF_ACTION_HARD_POWER_OFF:
+ case PSERIES_WDTF_ACTION_HARD_RESTART:
+ case PSERIES_WDTF_ACTION_DUMP_RESTART:
+ w->action = timeoutAction;
+ break;
+ default:
+ return H_PARAMETER;
+ }
+ w->leave_others = FIELD_EX64(flags, PSERIES_WDTF, LEAVE_OTHER);
+ timer_mod(&w->timer,
+ qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + timeoutInMs);
+ trace_spapr_watchdog_start(flags, watchdogNumber, timeoutInMs);
+ break;
+ case PSERIES_WDTF_OP_STOP:
+ if (watchdogNumber == PSERIES_WDT_STOP_ALL) {
+ ret = watchdog_stop_all(spapr);
+ } else if (watchdogNumber <= ARRAY_SIZE(spapr->wds)) {
+ ret = watchdog_stop(watchdogNumber,
+ &spapr->wds[watchdogNumber - 1]);
+ } else {
+ return H_P2;
+ }
+ break;
+ case PSERIES_WDTF_OP_QUERY:
+ args[0] = FIELD_DP64(0, PSERIES_WDTQ, MIN_TIMEOUT, WDT_MIN_TIMEOUT);
+ args[0] = FIELD_DP64(args[0], PSERIES_WDTQ, NUM,
+ ARRAY_SIZE(spapr->wds));
+ trace_spapr_watchdog_query(args[0]);
+ break;
+ case PSERIES_WDTF_OP_QUERY_LPM:
+ if (watchdogNumber > ARRAY_SIZE(spapr->wds)) {
+ return H_P2;
+ }
+ args[0] = PSERIES_WDTQL_QUERY_NOT_STOPPED;
+ trace_spapr_watchdog_query_lpm(args[0]);
+ break;
+ default:
+ return H_PARAMETER;
+ }
+
+ return ret;
+}
+
+void spapr_watchdog_init(SpaprMachineState *spapr)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(spapr->wds); ++i) {
+ char name[16];
+ SpaprWatchdog *w = &spapr->wds[i];
+
+ snprintf(name, sizeof(name) - 1, "wdt%d", i + 1);
+ object_initialize_child_with_props(OBJECT(spapr), name, w,
+ sizeof(SpaprWatchdog),
+ TYPE_SPAPR_WDT,
+ &error_fatal, NULL);
+ qdev_realize(DEVICE(w), NULL, &error_fatal);
+ }
+}
+
+static bool watchdog_needed(void *opaque)
+{
+ SpaprWatchdog *w = opaque;
+
+ return timer_pending(&w->timer);
+}
+
+static const VMStateDescription vmstate_wdt = {
+ .name = "spapr_watchdog",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = watchdog_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_TIMER(timer, SpaprWatchdog),
+ VMSTATE_UINT8(action, SpaprWatchdog),
+ VMSTATE_UINT8(leave_others, SpaprWatchdog),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void spapr_wdt_realize(DeviceState *dev, Error **errp)
+{
+ SpaprWatchdog *w = SPAPR_WDT(dev);
+ Object *o = OBJECT(dev);
+
+ timer_init_ms(&w->timer, QEMU_CLOCK_VIRTUAL, watchdog_expired, w);
+
+ object_property_add_uint64_ptr(o, "expire",
+ (uint64_t *)&w->timer.expire_time,
+ OBJ_PROP_FLAG_READ);
+ object_property_add_uint8_ptr(o, "action", &w->action, OBJ_PROP_FLAG_READ);
+ object_property_add_uint8_ptr(o, "leaveOtherWatchdogsRunningOnTimeout",
+ &w->leave_others, OBJ_PROP_FLAG_READ);
+}
+
+static void spapr_wdt_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = spapr_wdt_realize;
+ dc->vmsd = &vmstate_wdt;
+ dc->user_creatable = false;
+}
+
+static const TypeInfo spapr_wdt_info = {
+ .name = TYPE_SPAPR_WDT,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(SpaprWatchdog),
+ .class_init = spapr_wdt_class_init,
+};
+
+static void spapr_watchdog_register_types(void)
+{
+ spapr_register_hypercall(H_WATCHDOG, h_watchdog);
+ type_register_static(&spapr_wdt_info);
+}
+
+type_init(spapr_watchdog_register_types)
diff --git a/hw/watchdog/trace-events b/hw/watchdog/trace-events
new file mode 100644
index 00000000..89ccbcfd
--- /dev/null
+++ b/hw/watchdog/trace-events
@@ -0,0 +1,18 @@
+# See docs/devel/tracing.rst for syntax documentation.
+
+# cmsdk-apb-watchdog.c
+cmsdk_apb_watchdog_read(uint64_t offset, uint64_t data, unsigned size) "CMSDK APB watchdog read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
+cmsdk_apb_watchdog_write(uint64_t offset, uint64_t data, unsigned size) "CMSDK APB watchdog write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
+cmsdk_apb_watchdog_reset(void) "CMSDK APB watchdog: reset"
+cmsdk_apb_watchdog_lock(uint32_t lock) "CMSDK APB watchdog: lock %" PRIu32
+
+# wdt-aspeed.c
+aspeed_wdt_read(uint64_t addr, uint32_t size) "@0x%" PRIx64 " size=%d"
+aspeed_wdt_write(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PRIx64 " size=%d value=0x%"PRIx64
+
+# spapr_watchdog.c
+spapr_watchdog_start(uint64_t flags, uint64_t num, uint64_t timeout) "Flags 0x%" PRIx64 " num=%" PRId64 " %" PRIu64 "ms"
+spapr_watchdog_stop(uint64_t num, uint64_t ret) "num=%" PRIu64 " ret=%" PRId64
+spapr_watchdog_query(uint64_t caps) "caps=0x%" PRIx64
+spapr_watchdog_query_lpm(uint64_t caps) "caps=0x%" PRIx64
+spapr_watchdog_expired(uint64_t num, unsigned action) "num=%" PRIu64 " action=%u"
diff --git a/hw/watchdog/trace.h b/hw/watchdog/trace.h
new file mode 100644
index 00000000..5d849575
--- /dev/null
+++ b/hw/watchdog/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-hw_watchdog.h"
diff --git a/hw/watchdog/watchdog.c b/hw/watchdog/watchdog.c
new file mode 100644
index 00000000..6c082a32
--- /dev/null
+++ b/hw/watchdog/watchdog.c
@@ -0,0 +1,92 @@
+/*
+ * Virtual hardware watchdog.
+ *
+ * Copyright (C) 2009 Red Hat Inc.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * By Richard W.M. Jones (rjones@redhat.com).
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/option.h"
+#include "qemu/config-file.h"
+#include "qemu/queue.h"
+#include "qapi/error.h"
+#include "qapi/qapi-commands-run-state.h"
+#include "qapi/qapi-events-run-state.h"
+#include "sysemu/runstate.h"
+#include "sysemu/watchdog.h"
+#include "hw/nmi.h"
+#include "qemu/help_option.h"
+
+static WatchdogAction watchdog_action = WATCHDOG_ACTION_RESET;
+
+WatchdogAction get_watchdog_action(void)
+{
+ return watchdog_action;
+}
+
+/* This actually performs the "action" once a watchdog has expired,
+ * ie. reboot, shutdown, exit, etc.
+ */
+void watchdog_perform_action(void)
+{
+ switch (watchdog_action) {
+ case WATCHDOG_ACTION_RESET: /* same as 'system_reset' in monitor */
+ qapi_event_send_watchdog(WATCHDOG_ACTION_RESET);
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ break;
+
+ case WATCHDOG_ACTION_SHUTDOWN: /* same as 'system_powerdown' in monitor */
+ qapi_event_send_watchdog(WATCHDOG_ACTION_SHUTDOWN);
+ qemu_system_powerdown_request();
+ break;
+
+ case WATCHDOG_ACTION_POWEROFF: /* same as 'quit' command in monitor */
+ qapi_event_send_watchdog(WATCHDOG_ACTION_POWEROFF);
+ exit(0);
+
+ case WATCHDOG_ACTION_PAUSE: /* same as 'stop' command in monitor */
+ /* In a timer callback, when vm_stop calls qemu_clock_enable
+ * you would get a deadlock. Bypass the problem.
+ */
+ qemu_system_vmstop_request_prepare();
+ qapi_event_send_watchdog(WATCHDOG_ACTION_PAUSE);
+ qemu_system_vmstop_request(RUN_STATE_WATCHDOG);
+ break;
+
+ case WATCHDOG_ACTION_DEBUG:
+ qapi_event_send_watchdog(WATCHDOG_ACTION_DEBUG);
+ fprintf(stderr, "watchdog: timer fired\n");
+ break;
+
+ case WATCHDOG_ACTION_NONE:
+ qapi_event_send_watchdog(WATCHDOG_ACTION_NONE);
+ break;
+
+ case WATCHDOG_ACTION_INJECT_NMI:
+ qapi_event_send_watchdog(WATCHDOG_ACTION_INJECT_NMI);
+ nmi_monitor_handle(0, NULL);
+ break;
+
+ default:
+ assert(0);
+ }
+}
+
+void qmp_watchdog_set_action(WatchdogAction action, Error **errp)
+{
+ watchdog_action = action;
+}
diff --git a/hw/watchdog/wdt_aspeed.c b/hw/watchdog/wdt_aspeed.c
new file mode 100644
index 00000000..d753693a
--- /dev/null
+++ b/hw/watchdog/wdt_aspeed.c
@@ -0,0 +1,421 @@
+/*
+ * ASPEED Watchdog Controller
+ *
+ * Copyright (C) 2016-2017 IBM Corp.
+ *
+ * This code is licensed under the GPL version 2 or later. See the
+ * COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "sysemu/watchdog.h"
+#include "hw/misc/aspeed_scu.h"
+#include "hw/qdev-properties.h"
+#include "hw/sysbus.h"
+#include "hw/watchdog/wdt_aspeed.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+#define WDT_STATUS (0x00 / 4)
+#define WDT_RELOAD_VALUE (0x04 / 4)
+#define WDT_RESTART (0x08 / 4)
+#define WDT_CTRL (0x0C / 4)
+#define WDT_CTRL_RESET_MODE_SOC (0x00 << 5)
+#define WDT_CTRL_RESET_MODE_FULL_CHIP (0x01 << 5)
+#define WDT_CTRL_1MHZ_CLK BIT(4)
+#define WDT_CTRL_WDT_EXT BIT(3)
+#define WDT_CTRL_WDT_INTR BIT(2)
+#define WDT_CTRL_RESET_SYSTEM BIT(1)
+#define WDT_CTRL_ENABLE BIT(0)
+#define WDT_RESET_WIDTH (0x18 / 4)
+#define WDT_RESET_WIDTH_ACTIVE_HIGH BIT(31)
+#define WDT_POLARITY_MASK (0xFF << 24)
+#define WDT_ACTIVE_HIGH_MAGIC (0xA5 << 24)
+#define WDT_ACTIVE_LOW_MAGIC (0x5A << 24)
+#define WDT_RESET_WIDTH_PUSH_PULL BIT(30)
+#define WDT_DRIVE_TYPE_MASK (0xFF << 24)
+#define WDT_PUSH_PULL_MAGIC (0xA8 << 24)
+#define WDT_OPEN_DRAIN_MAGIC (0x8A << 24)
+#define WDT_RESET_MASK1 (0x1c / 4)
+
+#define WDT_TIMEOUT_STATUS (0x10 / 4)
+#define WDT_TIMEOUT_CLEAR (0x14 / 4)
+
+#define WDT_RESTART_MAGIC 0x4755
+
+#define AST2600_SCU_RESET_CONTROL1 (0x40 / 4)
+#define SCU_RESET_CONTROL1 (0x04 / 4)
+#define SCU_RESET_SDRAM BIT(0)
+
+static bool aspeed_wdt_is_enabled(const AspeedWDTState *s)
+{
+ return s->regs[WDT_CTRL] & WDT_CTRL_ENABLE;
+}
+
+static uint64_t aspeed_wdt_read(void *opaque, hwaddr offset, unsigned size)
+{
+ AspeedWDTState *s = ASPEED_WDT(opaque);
+
+ trace_aspeed_wdt_read(offset, size);
+
+ offset >>= 2;
+
+ switch (offset) {
+ case WDT_STATUS:
+ return s->regs[WDT_STATUS];
+ case WDT_RELOAD_VALUE:
+ return s->regs[WDT_RELOAD_VALUE];
+ case WDT_RESTART:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: read from write-only reg at offset 0x%"
+ HWADDR_PRIx "\n", __func__, offset);
+ return 0;
+ case WDT_CTRL:
+ return s->regs[WDT_CTRL];
+ case WDT_RESET_WIDTH:
+ return s->regs[WDT_RESET_WIDTH];
+ case WDT_RESET_MASK1:
+ return s->regs[WDT_RESET_MASK1];
+ case WDT_TIMEOUT_STATUS:
+ case WDT_TIMEOUT_CLEAR:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: uninmplemented read at offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ return 0;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ return 0;
+ }
+
+}
+
+static void aspeed_wdt_reload(AspeedWDTState *s)
+{
+ uint64_t reload;
+
+ if (!(s->regs[WDT_CTRL] & WDT_CTRL_1MHZ_CLK)) {
+ reload = muldiv64(s->regs[WDT_RELOAD_VALUE], NANOSECONDS_PER_SECOND,
+ s->pclk_freq);
+ } else {
+ reload = s->regs[WDT_RELOAD_VALUE] * 1000ULL;
+ }
+
+ if (aspeed_wdt_is_enabled(s)) {
+ timer_mod(s->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + reload);
+ }
+}
+
+static void aspeed_wdt_reload_1mhz(AspeedWDTState *s)
+{
+ uint64_t reload = s->regs[WDT_RELOAD_VALUE] * 1000ULL;
+
+ if (aspeed_wdt_is_enabled(s)) {
+ timer_mod(s->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + reload);
+ }
+}
+
+static uint64_t aspeed_2400_sanitize_ctrl(uint64_t data)
+{
+ return data & 0xffff;
+}
+
+static uint64_t aspeed_2500_sanitize_ctrl(uint64_t data)
+{
+ return (data & ~(0xfUL << 8)) | WDT_CTRL_1MHZ_CLK;
+}
+
+static uint64_t aspeed_2600_sanitize_ctrl(uint64_t data)
+{
+ return data & ~(0x7UL << 7);
+}
+
+static void aspeed_wdt_write(void *opaque, hwaddr offset, uint64_t data,
+ unsigned size)
+{
+ AspeedWDTState *s = ASPEED_WDT(opaque);
+ AspeedWDTClass *awc = ASPEED_WDT_GET_CLASS(s);
+ bool enable;
+
+ trace_aspeed_wdt_write(offset, size, data);
+
+ offset >>= 2;
+
+ switch (offset) {
+ case WDT_STATUS:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write to read-only reg at offset 0x%"
+ HWADDR_PRIx "\n", __func__, offset);
+ break;
+ case WDT_RELOAD_VALUE:
+ s->regs[WDT_RELOAD_VALUE] = data;
+ break;
+ case WDT_RESTART:
+ if ((data & 0xFFFF) == WDT_RESTART_MAGIC) {
+ s->regs[WDT_STATUS] = s->regs[WDT_RELOAD_VALUE];
+ awc->wdt_reload(s);
+ }
+ break;
+ case WDT_CTRL:
+ data = awc->sanitize_ctrl(data);
+ enable = data & WDT_CTRL_ENABLE;
+ if (enable && !aspeed_wdt_is_enabled(s)) {
+ s->regs[WDT_CTRL] = data;
+ awc->wdt_reload(s);
+ } else if (!enable && aspeed_wdt_is_enabled(s)) {
+ s->regs[WDT_CTRL] = data;
+ timer_del(s->timer);
+ } else {
+ s->regs[WDT_CTRL] = data;
+ }
+ break;
+ case WDT_RESET_WIDTH:
+ if (awc->reset_pulse) {
+ awc->reset_pulse(s, data & WDT_POLARITY_MASK);
+ }
+ s->regs[WDT_RESET_WIDTH] &= ~awc->ext_pulse_width_mask;
+ s->regs[WDT_RESET_WIDTH] |= data & awc->ext_pulse_width_mask;
+ break;
+
+ case WDT_RESET_MASK1:
+ /* TODO: implement */
+ s->regs[WDT_RESET_MASK1] = data;
+ break;
+
+ case WDT_TIMEOUT_STATUS:
+ case WDT_TIMEOUT_CLEAR:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: uninmplemented write at offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ }
+ return;
+}
+
+static const VMStateDescription vmstate_aspeed_wdt = {
+ .name = "vmstate_aspeed_wdt",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_TIMER_PTR(timer, AspeedWDTState),
+ VMSTATE_UINT32_ARRAY(regs, AspeedWDTState, ASPEED_WDT_REGS_MAX),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const MemoryRegionOps aspeed_wdt_ops = {
+ .read = aspeed_wdt_read,
+ .write = aspeed_wdt_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .valid.unaligned = false,
+};
+
+static void aspeed_wdt_reset(DeviceState *dev)
+{
+ AspeedWDTState *s = ASPEED_WDT(dev);
+ AspeedWDTClass *awc = ASPEED_WDT_GET_CLASS(s);
+
+ s->regs[WDT_STATUS] = awc->default_status;
+ s->regs[WDT_RELOAD_VALUE] = awc->default_reload_value;
+ s->regs[WDT_RESTART] = 0;
+ s->regs[WDT_CTRL] = awc->sanitize_ctrl(0);
+ s->regs[WDT_RESET_WIDTH] = 0xFF;
+
+ timer_del(s->timer);
+}
+
+static void aspeed_wdt_timer_expired(void *dev)
+{
+ AspeedWDTState *s = ASPEED_WDT(dev);
+ uint32_t reset_ctrl_reg = ASPEED_WDT_GET_CLASS(s)->reset_ctrl_reg;
+
+ /* Do not reset on SDRAM controller reset */
+ if (s->scu->regs[reset_ctrl_reg] & SCU_RESET_SDRAM) {
+ timer_del(s->timer);
+ s->regs[WDT_CTRL] = 0;
+ return;
+ }
+
+ qemu_log_mask(CPU_LOG_RESET, "Watchdog timer %" HWADDR_PRIx " expired.\n",
+ s->iomem.addr);
+ watchdog_perform_action();
+ timer_del(s->timer);
+}
+
+#define PCLK_HZ 24000000
+
+static void aspeed_wdt_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ AspeedWDTState *s = ASPEED_WDT(dev);
+
+ assert(s->scu);
+
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, aspeed_wdt_timer_expired, dev);
+
+ /* FIXME: This setting should be derived from the SCU hw strapping
+ * register SCU70
+ */
+ s->pclk_freq = PCLK_HZ;
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_wdt_ops, s,
+ TYPE_ASPEED_WDT, ASPEED_WDT_REGS_MAX * 4);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static Property aspeed_wdt_properties[] = {
+ DEFINE_PROP_LINK("scu", AspeedWDTState, scu, TYPE_ASPEED_SCU,
+ AspeedSCUState *),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void aspeed_wdt_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "ASPEED Watchdog Controller";
+ dc->realize = aspeed_wdt_realize;
+ dc->reset = aspeed_wdt_reset;
+ set_bit(DEVICE_CATEGORY_WATCHDOG, dc->categories);
+ dc->vmsd = &vmstate_aspeed_wdt;
+ device_class_set_props(dc, aspeed_wdt_properties);
+ dc->desc = "Aspeed watchdog device";
+}
+
+static const TypeInfo aspeed_wdt_info = {
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .name = TYPE_ASPEED_WDT,
+ .instance_size = sizeof(AspeedWDTState),
+ .class_init = aspeed_wdt_class_init,
+ .class_size = sizeof(AspeedWDTClass),
+ .abstract = true,
+};
+
+static void aspeed_2400_wdt_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ AspeedWDTClass *awc = ASPEED_WDT_CLASS(klass);
+
+ dc->desc = "ASPEED 2400 Watchdog Controller";
+ awc->offset = 0x20;
+ awc->ext_pulse_width_mask = 0xff;
+ awc->reset_ctrl_reg = SCU_RESET_CONTROL1;
+ awc->wdt_reload = aspeed_wdt_reload;
+ awc->sanitize_ctrl = aspeed_2400_sanitize_ctrl;
+ awc->default_status = 0x03EF1480;
+ awc->default_reload_value = 0x03EF1480;
+}
+
+static const TypeInfo aspeed_2400_wdt_info = {
+ .name = TYPE_ASPEED_2400_WDT,
+ .parent = TYPE_ASPEED_WDT,
+ .instance_size = sizeof(AspeedWDTState),
+ .class_init = aspeed_2400_wdt_class_init,
+};
+
+static void aspeed_2500_wdt_reset_pulse(AspeedWDTState *s, uint32_t property)
+{
+ if (property) {
+ if (property == WDT_ACTIVE_HIGH_MAGIC) {
+ s->regs[WDT_RESET_WIDTH] |= WDT_RESET_WIDTH_ACTIVE_HIGH;
+ } else if (property == WDT_ACTIVE_LOW_MAGIC) {
+ s->regs[WDT_RESET_WIDTH] &= ~WDT_RESET_WIDTH_ACTIVE_HIGH;
+ } else if (property == WDT_PUSH_PULL_MAGIC) {
+ s->regs[WDT_RESET_WIDTH] |= WDT_RESET_WIDTH_PUSH_PULL;
+ } else if (property == WDT_OPEN_DRAIN_MAGIC) {
+ s->regs[WDT_RESET_WIDTH] &= ~WDT_RESET_WIDTH_PUSH_PULL;
+ }
+ }
+}
+
+static void aspeed_2500_wdt_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ AspeedWDTClass *awc = ASPEED_WDT_CLASS(klass);
+
+ dc->desc = "ASPEED 2500 Watchdog Controller";
+ awc->offset = 0x20;
+ awc->ext_pulse_width_mask = 0xfffff;
+ awc->reset_ctrl_reg = SCU_RESET_CONTROL1;
+ awc->reset_pulse = aspeed_2500_wdt_reset_pulse;
+ awc->wdt_reload = aspeed_wdt_reload_1mhz;
+ awc->sanitize_ctrl = aspeed_2500_sanitize_ctrl;
+ awc->default_status = 0x014FB180;
+ awc->default_reload_value = 0x014FB180;
+}
+
+static const TypeInfo aspeed_2500_wdt_info = {
+ .name = TYPE_ASPEED_2500_WDT,
+ .parent = TYPE_ASPEED_WDT,
+ .instance_size = sizeof(AspeedWDTState),
+ .class_init = aspeed_2500_wdt_class_init,
+};
+
+static void aspeed_2600_wdt_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ AspeedWDTClass *awc = ASPEED_WDT_CLASS(klass);
+
+ dc->desc = "ASPEED 2600 Watchdog Controller";
+ awc->offset = 0x40;
+ awc->ext_pulse_width_mask = 0xfffff; /* TODO */
+ awc->reset_ctrl_reg = AST2600_SCU_RESET_CONTROL1;
+ awc->reset_pulse = aspeed_2500_wdt_reset_pulse;
+ awc->wdt_reload = aspeed_wdt_reload_1mhz;
+ awc->sanitize_ctrl = aspeed_2600_sanitize_ctrl;
+ awc->default_status = 0x014FB180;
+ awc->default_reload_value = 0x014FB180;
+}
+
+static const TypeInfo aspeed_2600_wdt_info = {
+ .name = TYPE_ASPEED_2600_WDT,
+ .parent = TYPE_ASPEED_WDT,
+ .instance_size = sizeof(AspeedWDTState),
+ .class_init = aspeed_2600_wdt_class_init,
+};
+
+static void aspeed_1030_wdt_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ AspeedWDTClass *awc = ASPEED_WDT_CLASS(klass);
+
+ dc->desc = "ASPEED 1030 Watchdog Controller";
+ awc->offset = 0x80;
+ awc->ext_pulse_width_mask = 0xfffff; /* TODO */
+ awc->reset_ctrl_reg = AST2600_SCU_RESET_CONTROL1;
+ awc->reset_pulse = aspeed_2500_wdt_reset_pulse;
+ awc->wdt_reload = aspeed_wdt_reload_1mhz;
+ awc->sanitize_ctrl = aspeed_2600_sanitize_ctrl;
+ awc->default_status = 0x014FB180;
+ awc->default_reload_value = 0x014FB180;
+}
+
+static const TypeInfo aspeed_1030_wdt_info = {
+ .name = TYPE_ASPEED_1030_WDT,
+ .parent = TYPE_ASPEED_WDT,
+ .instance_size = sizeof(AspeedWDTState),
+ .class_init = aspeed_1030_wdt_class_init,
+};
+
+static void wdt_aspeed_register_types(void)
+{
+ type_register_static(&aspeed_wdt_info);
+ type_register_static(&aspeed_2400_wdt_info);
+ type_register_static(&aspeed_2500_wdt_info);
+ type_register_static(&aspeed_2600_wdt_info);
+ type_register_static(&aspeed_1030_wdt_info);
+}
+
+type_init(wdt_aspeed_register_types)
diff --git a/hw/watchdog/wdt_diag288.c b/hw/watchdog/wdt_diag288.c
new file mode 100644
index 00000000..76d89fbf
--- /dev/null
+++ b/hw/watchdog/wdt_diag288.c
@@ -0,0 +1,139 @@
+/*
+ * watchdog device diag288 support
+ *
+ * Copyright IBM, Corp. 2015
+ *
+ * Authors:
+ * Xu Wang <gesaint@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at your
+ * option) any later version. See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "sysemu/reset.h"
+#include "sysemu/watchdog.h"
+#include "qemu/timer.h"
+#include "hw/watchdog/wdt_diag288.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+
+static const VMStateDescription vmstate_diag288 = {
+ .name = "vmstate_diag288",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_TIMER_PTR(timer, DIAG288State),
+ VMSTATE_BOOL(enabled, DIAG288State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void wdt_diag288_reset(DeviceState *dev)
+{
+ DIAG288State *diag288 = DIAG288(dev);
+
+ diag288->enabled = false;
+ timer_del(diag288->timer);
+}
+
+static void diag288_reset(void *opaque)
+{
+ DeviceState *diag288 = opaque;
+
+ wdt_diag288_reset(diag288);
+}
+
+static void diag288_timer_expired(void *dev)
+{
+ qemu_log_mask(CPU_LOG_RESET, "Watchdog timer expired.\n");
+ /* Reset the watchdog only if the guest gets notified about
+ * expiry. watchdog_perform_action() may temporarily relinquish
+ * the BQL; reset before triggering the action to avoid races with
+ * diag288 instructions. */
+ switch (get_watchdog_action()) {
+ case WATCHDOG_ACTION_DEBUG:
+ case WATCHDOG_ACTION_NONE:
+ case WATCHDOG_ACTION_PAUSE:
+ break;
+ default:
+ wdt_diag288_reset(dev);
+ }
+ watchdog_perform_action();
+}
+
+static int wdt_diag288_handle_timer(DIAG288State *diag288,
+ uint64_t func, uint64_t timeout)
+{
+ switch (func) {
+ case WDT_DIAG288_INIT:
+ diag288->enabled = true;
+ /* fall through */
+ case WDT_DIAG288_CHANGE:
+ if (!diag288->enabled) {
+ return -1;
+ }
+ timer_mod(diag288->timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ timeout * NANOSECONDS_PER_SECOND);
+ break;
+ case WDT_DIAG288_CANCEL:
+ if (!diag288->enabled) {
+ return -1;
+ }
+ diag288->enabled = false;
+ timer_del(diag288->timer);
+ break;
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+static void wdt_diag288_realize(DeviceState *dev, Error **errp)
+{
+ DIAG288State *diag288 = DIAG288(dev);
+
+ qemu_register_reset(diag288_reset, diag288);
+ diag288->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, diag288_timer_expired,
+ dev);
+}
+
+static void wdt_diag288_unrealize(DeviceState *dev)
+{
+ DIAG288State *diag288 = DIAG288(dev);
+
+ timer_free(diag288->timer);
+}
+
+static void wdt_diag288_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ DIAG288Class *diag288 = DIAG288_CLASS(klass);
+
+ dc->realize = wdt_diag288_realize;
+ dc->unrealize = wdt_diag288_unrealize;
+ dc->reset = wdt_diag288_reset;
+ dc->hotpluggable = false;
+ set_bit(DEVICE_CATEGORY_WATCHDOG, dc->categories);
+ dc->vmsd = &vmstate_diag288;
+ diag288->handle_timer = wdt_diag288_handle_timer;
+ dc->desc = "diag288 device for s390x platform";
+}
+
+static const TypeInfo wdt_diag288_info = {
+ .class_init = wdt_diag288_class_init,
+ .parent = TYPE_DEVICE,
+ .name = TYPE_WDT_DIAG288,
+ .instance_size = sizeof(DIAG288State),
+ .class_size = sizeof(DIAG288Class),
+};
+
+static void wdt_diag288_register_types(void)
+{
+ type_register_static(&wdt_diag288_info);
+}
+
+type_init(wdt_diag288_register_types)
diff --git a/hw/watchdog/wdt_i6300esb.c b/hw/watchdog/wdt_i6300esb.c
new file mode 100644
index 00000000..5693ec6a
--- /dev/null
+++ b/hw/watchdog/wdt_i6300esb.c
@@ -0,0 +1,494 @@
+/*
+ * Virtual hardware watchdog.
+ *
+ * Copyright (C) 2009 Red Hat Inc.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * By Richard W.M. Jones (rjones@redhat.com).
+ */
+
+#include "qemu/osdep.h"
+
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "sysemu/watchdog.h"
+#include "hw/pci/pci.h"
+#include "migration/vmstate.h"
+#include "qom/object.h"
+
+/*#define I6300ESB_DEBUG 1*/
+
+#ifdef I6300ESB_DEBUG
+#define i6300esb_debug(fs,...) \
+ fprintf(stderr,"i6300esb: %s: "fs,__func__,##__VA_ARGS__)
+#else
+#define i6300esb_debug(fs,...)
+#endif
+
+/* PCI configuration registers */
+#define ESB_CONFIG_REG 0x60 /* Config register */
+#define ESB_LOCK_REG 0x68 /* WDT lock register */
+
+/* Memory mapped registers (offset from base address) */
+#define ESB_TIMER1_REG 0x00 /* Timer1 value after each reset */
+#define ESB_TIMER2_REG 0x04 /* Timer2 value after each reset */
+#define ESB_GINTSR_REG 0x08 /* General Interrupt Status Register */
+#define ESB_RELOAD_REG 0x0c /* Reload register */
+
+/* Lock register bits */
+#define ESB_WDT_FUNC (0x01 << 2) /* Watchdog functionality */
+#define ESB_WDT_ENABLE (0x01 << 1) /* Enable WDT */
+#define ESB_WDT_LOCK (0x01 << 0) /* Lock (nowayout) */
+
+/* Config register bits */
+#define ESB_WDT_REBOOT (0x01 << 5) /* Enable reboot on timeout */
+#define ESB_WDT_FREQ (0x01 << 2) /* Decrement frequency */
+#define ESB_WDT_INTTYPE (0x11 << 0) /* Interrupt type on timer1 timeout */
+
+/* Reload register bits */
+#define ESB_WDT_RELOAD (0x01 << 8) /* prevent timeout */
+
+/* Magic constants */
+#define ESB_UNLOCK1 0x80 /* Step 1 to unlock reset registers */
+#define ESB_UNLOCK2 0x86 /* Step 2 to unlock reset registers */
+
+/* Device state. */
+struct I6300State {
+ PCIDevice dev;
+ MemoryRegion io_mem;
+
+ int reboot_enabled; /* "Reboot" on timer expiry. The real action
+ * performed depends on the -watchdog-action
+ * param passed on QEMU command line.
+ */
+ int clock_scale; /* Clock scale. */
+#define CLOCK_SCALE_1KHZ 0
+#define CLOCK_SCALE_1MHZ 1
+
+ int int_type; /* Interrupt type generated. */
+#define INT_TYPE_IRQ 0 /* APIC 1, INT 10 */
+#define INT_TYPE_SMI 2
+#define INT_TYPE_DISABLED 3
+
+ int free_run; /* If true, reload timer on expiry. */
+ int locked; /* If true, enabled field cannot be changed. */
+ int enabled; /* If true, watchdog is enabled. */
+
+ QEMUTimer *timer; /* The actual watchdog timer. */
+
+ uint32_t timer1_preload; /* Values preloaded into timer1, timer2. */
+ uint32_t timer2_preload;
+ int stage; /* Stage (1 or 2). */
+
+ int unlock_state; /* Guest writes 0x80, 0x86 to unlock the
+ * registers, and we transition through
+ * states 0 -> 1 -> 2 when this happens.
+ */
+
+ int previous_reboot_flag; /* If the watchdog caused the previous
+ * reboot, this flag will be set.
+ */
+};
+
+
+#define TYPE_WATCHDOG_I6300ESB_DEVICE "i6300esb"
+OBJECT_DECLARE_SIMPLE_TYPE(I6300State, WATCHDOG_I6300ESB_DEVICE)
+
+/* This function is called when the watchdog has either been enabled
+ * (hence it starts counting down) or has been keep-alived.
+ */
+static void i6300esb_restart_timer(I6300State *d, int stage)
+{
+ int64_t timeout;
+
+ if (!d->enabled)
+ return;
+
+ d->stage = stage;
+
+ if (d->stage <= 1)
+ timeout = d->timer1_preload;
+ else
+ timeout = d->timer2_preload;
+
+ if (d->clock_scale == CLOCK_SCALE_1KHZ)
+ timeout <<= 15;
+ else
+ timeout <<= 5;
+
+ /* Get the timeout in nanoseconds. */
+
+ timeout = timeout * 30; /* on a PCI bus, 1 tick is 30 ns*/
+
+ i6300esb_debug("stage %d, timeout %" PRIi64 "\n", d->stage, timeout);
+
+ timer_mod(d->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + timeout);
+}
+
+/* This is called when the guest disables the watchdog. */
+static void i6300esb_disable_timer(I6300State *d)
+{
+ i6300esb_debug("timer disabled\n");
+
+ timer_del(d->timer);
+}
+
+static void i6300esb_reset(DeviceState *dev)
+{
+ PCIDevice *pdev = PCI_DEVICE(dev);
+ I6300State *d = WATCHDOG_I6300ESB_DEVICE(pdev);
+
+ i6300esb_debug("I6300State = %p\n", d);
+
+ i6300esb_disable_timer(d);
+
+ /* NB: Don't change d->previous_reboot_flag in this function. */
+
+ d->reboot_enabled = 1;
+ d->clock_scale = CLOCK_SCALE_1KHZ;
+ d->int_type = INT_TYPE_IRQ;
+ d->free_run = 0;
+ d->locked = 0;
+ d->enabled = 0;
+ d->timer1_preload = 0xfffff;
+ d->timer2_preload = 0xfffff;
+ d->stage = 1;
+ d->unlock_state = 0;
+}
+
+/* This function is called when the watchdog expires. Note that
+ * the hardware has two timers, and so expiry happens in two stages.
+ * If d->stage == 1 then we perform the first stage action (usually,
+ * sending an interrupt) and then restart the timer again for the
+ * second stage. If the second stage expires then the watchdog
+ * really has run out.
+ */
+static void i6300esb_timer_expired(void *vp)
+{
+ I6300State *d = vp;
+
+ i6300esb_debug("stage %d\n", d->stage);
+
+ if (d->stage == 1) {
+ /* What to do at the end of stage 1? */
+ switch (d->int_type) {
+ case INT_TYPE_IRQ:
+ fprintf(stderr, "i6300esb_timer_expired: I would send APIC 1 INT 10 here if I knew how (XXX)\n");
+ break;
+ case INT_TYPE_SMI:
+ fprintf(stderr, "i6300esb_timer_expired: I would send SMI here if I knew how (XXX)\n");
+ break;
+ }
+
+ /* Start the second stage. */
+ i6300esb_restart_timer(d, 2);
+ } else {
+ /* Second stage expired, reboot for real. */
+ if (d->reboot_enabled) {
+ d->previous_reboot_flag = 1;
+ watchdog_perform_action(); /* This reboots, exits, etc */
+ i6300esb_reset(DEVICE(d));
+ }
+
+ /* In "free running mode" we start stage 1 again. */
+ if (d->free_run)
+ i6300esb_restart_timer(d, 1);
+ }
+}
+
+static void i6300esb_config_write(PCIDevice *dev, uint32_t addr,
+ uint32_t data, int len)
+{
+ I6300State *d = WATCHDOG_I6300ESB_DEVICE(dev);
+ int old;
+
+ i6300esb_debug("addr = %x, data = %x, len = %d\n", addr, data, len);
+
+ if (addr == ESB_CONFIG_REG && len == 2) {
+ d->reboot_enabled = (data & ESB_WDT_REBOOT) == 0;
+ d->clock_scale =
+ (data & ESB_WDT_FREQ) != 0 ? CLOCK_SCALE_1MHZ : CLOCK_SCALE_1KHZ;
+ d->int_type = (data & ESB_WDT_INTTYPE);
+ } else if (addr == ESB_LOCK_REG && len == 1) {
+ if (!d->locked) {
+ d->locked = (data & ESB_WDT_LOCK) != 0;
+ d->free_run = (data & ESB_WDT_FUNC) != 0;
+ old = d->enabled;
+ d->enabled = (data & ESB_WDT_ENABLE) != 0;
+ if (!old && d->enabled) /* Enabled transitioned from 0 -> 1 */
+ i6300esb_restart_timer(d, 1);
+ else if (!d->enabled)
+ i6300esb_disable_timer(d);
+ }
+ } else {
+ pci_default_write_config(dev, addr, data, len);
+ }
+}
+
+static uint32_t i6300esb_config_read(PCIDevice *dev, uint32_t addr, int len)
+{
+ I6300State *d = WATCHDOG_I6300ESB_DEVICE(dev);
+ uint32_t data;
+
+ i6300esb_debug ("addr = %x, len = %d\n", addr, len);
+
+ if (addr == ESB_CONFIG_REG && len == 2) {
+ data =
+ (d->reboot_enabled ? 0 : ESB_WDT_REBOOT) |
+ (d->clock_scale == CLOCK_SCALE_1MHZ ? ESB_WDT_FREQ : 0) |
+ d->int_type;
+ return data;
+ } else if (addr == ESB_LOCK_REG && len == 1) {
+ data =
+ (d->free_run ? ESB_WDT_FUNC : 0) |
+ (d->locked ? ESB_WDT_LOCK : 0) |
+ (d->enabled ? ESB_WDT_ENABLE : 0);
+ return data;
+ } else {
+ return pci_default_read_config(dev, addr, len);
+ }
+}
+
+static uint32_t i6300esb_mem_readb(void *vp, hwaddr addr)
+{
+ i6300esb_debug ("addr = %x\n", (int) addr);
+
+ return 0;
+}
+
+static uint32_t i6300esb_mem_readw(void *vp, hwaddr addr)
+{
+ uint32_t data = 0;
+ I6300State *d = vp;
+
+ i6300esb_debug("addr = %x\n", (int) addr);
+
+ if (addr == 0xc) {
+ /* The previous reboot flag is really bit 9, but there is
+ * a bug in the Linux driver where it thinks it's bit 12.
+ * Set both.
+ */
+ data = d->previous_reboot_flag ? 0x1200 : 0;
+ }
+
+ return data;
+}
+
+static uint32_t i6300esb_mem_readl(void *vp, hwaddr addr)
+{
+ i6300esb_debug("addr = %x\n", (int) addr);
+
+ return 0;
+}
+
+static void i6300esb_mem_writeb(void *vp, hwaddr addr, uint32_t val)
+{
+ I6300State *d = vp;
+
+ i6300esb_debug("addr = %x, val = %x\n", (int) addr, val);
+
+ if (addr == 0xc && val == 0x80)
+ d->unlock_state = 1;
+ else if (addr == 0xc && val == 0x86 && d->unlock_state == 1)
+ d->unlock_state = 2;
+}
+
+static void i6300esb_mem_writew(void *vp, hwaddr addr, uint32_t val)
+{
+ I6300State *d = vp;
+
+ i6300esb_debug("addr = %x, val = %x\n", (int) addr, val);
+
+ if (addr == 0xc && val == 0x80)
+ d->unlock_state = 1;
+ else if (addr == 0xc && val == 0x86 && d->unlock_state == 1)
+ d->unlock_state = 2;
+ else {
+ if (d->unlock_state == 2) {
+ if (addr == 0xc) {
+ if ((val & 0x100) != 0)
+ /* This is the "ping" from the userspace watchdog in
+ * the guest ...
+ */
+ i6300esb_restart_timer(d, 1);
+
+ /* Setting bit 9 resets the previous reboot flag.
+ * There's a bug in the Linux driver where it sets
+ * bit 12 instead.
+ */
+ if ((val & 0x200) != 0 || (val & 0x1000) != 0) {
+ d->previous_reboot_flag = 0;
+ }
+ }
+
+ d->unlock_state = 0;
+ }
+ }
+}
+
+static void i6300esb_mem_writel(void *vp, hwaddr addr, uint32_t val)
+{
+ I6300State *d = vp;
+
+ i6300esb_debug ("addr = %x, val = %x\n", (int) addr, val);
+
+ if (addr == 0xc && val == 0x80)
+ d->unlock_state = 1;
+ else if (addr == 0xc && val == 0x86 && d->unlock_state == 1)
+ d->unlock_state = 2;
+ else {
+ if (d->unlock_state == 2) {
+ if (addr == 0)
+ d->timer1_preload = val & 0xfffff;
+ else if (addr == 4)
+ d->timer2_preload = val & 0xfffff;
+
+ d->unlock_state = 0;
+ }
+ }
+}
+
+static uint64_t i6300esb_mem_readfn(void *opaque, hwaddr addr, unsigned size)
+{
+ switch (size) {
+ case 1:
+ return i6300esb_mem_readb(opaque, addr);
+ case 2:
+ return i6300esb_mem_readw(opaque, addr);
+ case 4:
+ return i6300esb_mem_readl(opaque, addr);
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static void i6300esb_mem_writefn(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ switch (size) {
+ case 1:
+ i6300esb_mem_writeb(opaque, addr, value);
+ break;
+ case 2:
+ i6300esb_mem_writew(opaque, addr, value);
+ break;
+ case 4:
+ i6300esb_mem_writel(opaque, addr, value);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static const MemoryRegionOps i6300esb_ops = {
+ .read = i6300esb_mem_readfn,
+ .write = i6300esb_mem_writefn,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_i6300esb = {
+ .name = "i6300esb_wdt",
+ /* With this VMSD's introduction, version_id/minimum_version_id were
+ * erroneously set to sizeof(I6300State), causing a somewhat random
+ * version_id to be set for every build. This eventually broke
+ * migration.
+ *
+ * To correct this without breaking old->new migration for older
+ * versions of QEMU, we've set version_id to a value high enough
+ * to exceed all past values of sizeof(I6300State) across various
+ * build environments, and have reset minimum_version_id to 1,
+ * since this VMSD has never changed and thus can accept all past
+ * versions.
+ *
+ * For future changes we can treat these values as we normally would.
+ */
+ .version_id = 10000,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, I6300State),
+ VMSTATE_INT32(reboot_enabled, I6300State),
+ VMSTATE_INT32(clock_scale, I6300State),
+ VMSTATE_INT32(int_type, I6300State),
+ VMSTATE_INT32(free_run, I6300State),
+ VMSTATE_INT32(locked, I6300State),
+ VMSTATE_INT32(enabled, I6300State),
+ VMSTATE_TIMER_PTR(timer, I6300State),
+ VMSTATE_UINT32(timer1_preload, I6300State),
+ VMSTATE_UINT32(timer2_preload, I6300State),
+ VMSTATE_INT32(stage, I6300State),
+ VMSTATE_INT32(unlock_state, I6300State),
+ VMSTATE_INT32(previous_reboot_flag, I6300State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void i6300esb_realize(PCIDevice *dev, Error **errp)
+{
+ I6300State *d = WATCHDOG_I6300ESB_DEVICE(dev);
+
+ i6300esb_debug("I6300State = %p\n", d);
+
+ d->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, i6300esb_timer_expired, d);
+ d->previous_reboot_flag = 0;
+
+ memory_region_init_io(&d->io_mem, OBJECT(d), &i6300esb_ops, d,
+ "i6300esb", 0x10);
+ pci_register_bar(&d->dev, 0, 0, &d->io_mem);
+}
+
+static void i6300esb_exit(PCIDevice *dev)
+{
+ I6300State *d = WATCHDOG_I6300ESB_DEVICE(dev);
+
+ timer_free(d->timer);
+}
+
+static void i6300esb_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->config_read = i6300esb_config_read;
+ k->config_write = i6300esb_config_write;
+ k->realize = i6300esb_realize;
+ k->exit = i6300esb_exit;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_ESB_9;
+ k->class_id = PCI_CLASS_SYSTEM_OTHER;
+ dc->reset = i6300esb_reset;
+ dc->vmsd = &vmstate_i6300esb;
+ set_bit(DEVICE_CATEGORY_WATCHDOG, dc->categories);
+ dc->desc = "Intel 6300ESB";
+}
+
+static const TypeInfo i6300esb_info = {
+ .name = TYPE_WATCHDOG_I6300ESB_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(I6300State),
+ .class_init = i6300esb_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void i6300esb_register_types(void)
+{
+ type_register_static(&i6300esb_info);
+}
+
+type_init(i6300esb_register_types)
diff --git a/hw/watchdog/wdt_ib700.c b/hw/watchdog/wdt_ib700.c
new file mode 100644
index 00000000..b116c3a3
--- /dev/null
+++ b/hw/watchdog/wdt_ib700.c
@@ -0,0 +1,154 @@
+/*
+ * Virtual hardware watchdog.
+ *
+ * Copyright (C) 2009 Red Hat Inc.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * By Richard W.M. Jones (rjones@redhat.com).
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "sysemu/watchdog.h"
+#include "hw/isa/isa.h"
+#include "migration/vmstate.h"
+#include "qom/object.h"
+
+/*#define IB700_DEBUG 1*/
+
+#ifdef IB700_DEBUG
+#define ib700_debug(fs,...) \
+ fprintf(stderr,"ib700: %s: "fs,__func__,##__VA_ARGS__)
+#else
+#define ib700_debug(fs,...)
+#endif
+
+#define TYPE_IB700 "ib700"
+typedef struct IB700state IB700State;
+DECLARE_INSTANCE_CHECKER(IB700State, IB700,
+ TYPE_IB700)
+
+struct IB700state {
+ ISADevice parent_obj;
+
+ QEMUTimer *timer;
+
+ PortioList port_list;
+};
+
+/* This is the timer. We use a global here because the watchdog
+ * code ensures there is only one watchdog (it is located at a fixed,
+ * unchangeable IO port, so there could only ever be one anyway).
+ */
+
+/* A write to this register enables the timer. */
+static void ib700_write_enable_reg(void *vp, uint32_t addr, uint32_t data)
+{
+ IB700State *s = vp;
+ static int time_map[] = {
+ 30, 28, 26, 24, 22, 20, 18, 16,
+ 14, 12, 10, 8, 6, 4, 2, 0
+ };
+ int64_t timeout;
+
+ ib700_debug("addr = %x, data = %x\n", addr, data);
+
+ timeout = (int64_t) time_map[data & 0xF] * NANOSECONDS_PER_SECOND;
+ timer_mod(s->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + timeout);
+}
+
+/* A write (of any value) to this register disables the timer. */
+static void ib700_write_disable_reg(void *vp, uint32_t addr, uint32_t data)
+{
+ IB700State *s = vp;
+
+ ib700_debug("addr = %x, data = %x\n", addr, data);
+
+ timer_del(s->timer);
+}
+
+/* This is called when the watchdog expires. */
+static void ib700_timer_expired(void *vp)
+{
+ IB700State *s = vp;
+
+ ib700_debug("watchdog expired\n");
+
+ watchdog_perform_action();
+ timer_del(s->timer);
+}
+
+static const VMStateDescription vmstate_ib700 = {
+ .name = "ib700_wdt",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_TIMER_PTR(timer, IB700State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const MemoryRegionPortio wdt_portio_list[] = {
+ { 0x441, 2, 1, .write = ib700_write_disable_reg, },
+ { 0x443, 2, 1, .write = ib700_write_enable_reg, },
+ PORTIO_END_OF_LIST(),
+};
+
+static void wdt_ib700_realize(DeviceState *dev, Error **errp)
+{
+ IB700State *s = IB700(dev);
+
+ ib700_debug("watchdog init\n");
+
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, ib700_timer_expired, s);
+
+ portio_list_init(&s->port_list, OBJECT(s), wdt_portio_list, s, "ib700");
+ portio_list_add(&s->port_list, isa_address_space_io(&s->parent_obj), 0);
+}
+
+static void wdt_ib700_reset(DeviceState *dev)
+{
+ IB700State *s = IB700(dev);
+
+ ib700_debug("watchdog reset\n");
+
+ timer_del(s->timer);
+}
+
+static void wdt_ib700_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = wdt_ib700_realize;
+ dc->reset = wdt_ib700_reset;
+ dc->vmsd = &vmstate_ib700;
+ set_bit(DEVICE_CATEGORY_WATCHDOG, dc->categories);
+ dc->desc = "iBASE 700";
+}
+
+static const TypeInfo wdt_ib700_info = {
+ .name = TYPE_IB700,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(IB700State),
+ .class_init = wdt_ib700_class_init,
+};
+
+static void wdt_ib700_register_types(void)
+{
+ type_register_static(&wdt_ib700_info);
+}
+
+type_init(wdt_ib700_register_types)
diff --git a/hw/watchdog/wdt_imx2.c b/hw/watchdog/wdt_imx2.c
new file mode 100644
index 00000000..e776a2fb
--- /dev/null
+++ b/hw/watchdog/wdt_imx2.c
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2018, Impinj, Inc.
+ *
+ * i.MX2 Watchdog IP block
+ *
+ * Author: Andrey Smirnov <andrew.smirnov@gmail.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "qemu/module.h"
+#include "sysemu/watchdog.h"
+#include "migration/vmstate.h"
+#include "hw/qdev-properties.h"
+
+#include "hw/watchdog/wdt_imx2.h"
+
+static void imx2_wdt_interrupt(void *opaque)
+{
+ IMX2WdtState *s = IMX2_WDT(opaque);
+
+ s->wicr |= IMX2_WDT_WICR_WTIS;
+ qemu_set_irq(s->irq, 1);
+}
+
+static void imx2_wdt_expired(void *opaque)
+{
+ IMX2WdtState *s = IMX2_WDT(opaque);
+
+ s->wrsr = IMX2_WDT_WRSR_TOUT;
+
+ /* Perform watchdog action if watchdog is enabled */
+ if (s->wcr & IMX2_WDT_WCR_WDE) {
+ s->wrsr = IMX2_WDT_WRSR_TOUT;
+ watchdog_perform_action();
+ }
+}
+
+static void imx2_wdt_reset(DeviceState *dev)
+{
+ IMX2WdtState *s = IMX2_WDT(dev);
+
+ ptimer_transaction_begin(s->timer);
+ ptimer_stop(s->timer);
+ ptimer_transaction_commit(s->timer);
+
+ if (s->pretimeout_support) {
+ ptimer_transaction_begin(s->itimer);
+ ptimer_stop(s->itimer);
+ ptimer_transaction_commit(s->itimer);
+ }
+
+ s->wicr_locked = false;
+ s->wcr_locked = false;
+ s->wcr_wde_locked = false;
+
+ s->wcr = IMX2_WDT_WCR_WDA | IMX2_WDT_WCR_SRS;
+ s->wsr = 0;
+ s->wrsr &= ~(IMX2_WDT_WRSR_TOUT | IMX2_WDT_WRSR_SFTW);
+ s->wicr = IMX2_WDT_WICR_WICT_DEF;
+ s->wmcr = IMX2_WDT_WMCR_PDE;
+}
+
+static uint64_t imx2_wdt_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ IMX2WdtState *s = IMX2_WDT(opaque);
+
+ switch (addr) {
+ case IMX2_WDT_WCR:
+ return s->wcr;
+ case IMX2_WDT_WSR:
+ return s->wsr;
+ case IMX2_WDT_WRSR:
+ return s->wrsr;
+ case IMX2_WDT_WICR:
+ return s->wicr;
+ case IMX2_WDT_WMCR:
+ return s->wmcr;
+ }
+ return 0;
+}
+
+static void imx_wdt2_update_itimer(IMX2WdtState *s, bool start)
+{
+ bool running = (s->wcr & IMX2_WDT_WCR_WDE) && (s->wcr & IMX2_WDT_WCR_WT);
+ bool enabled = s->wicr & IMX2_WDT_WICR_WIE;
+
+ ptimer_transaction_begin(s->itimer);
+ if (start || !enabled) {
+ ptimer_stop(s->itimer);
+ }
+ if (running && enabled) {
+ int count = ptimer_get_count(s->timer);
+ int pretimeout = s->wicr & IMX2_WDT_WICR_WICT;
+
+ /*
+ * Only (re-)start pretimeout timer if its counter value is larger
+ * than 0. Otherwise it will fire right away and we'll get an
+ * interrupt loop.
+ */
+ if (count > pretimeout) {
+ ptimer_set_count(s->itimer, count - pretimeout);
+ if (start) {
+ ptimer_run(s->itimer, 1);
+ }
+ }
+ }
+ ptimer_transaction_commit(s->itimer);
+}
+
+static void imx_wdt2_update_timer(IMX2WdtState *s, bool start)
+{
+ ptimer_transaction_begin(s->timer);
+ if (start) {
+ ptimer_stop(s->timer);
+ }
+ if ((s->wcr & IMX2_WDT_WCR_WDE) && (s->wcr & IMX2_WDT_WCR_WT)) {
+ int count = (s->wcr & IMX2_WDT_WCR_WT) >> 8;
+
+ /* A value of 0 reflects one period (0.5s). */
+ ptimer_set_count(s->timer, count + 1);
+ if (start) {
+ ptimer_run(s->timer, 1);
+ }
+ }
+ ptimer_transaction_commit(s->timer);
+ if (s->pretimeout_support) {
+ imx_wdt2_update_itimer(s, start);
+ }
+}
+
+static void imx2_wdt_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned int size)
+{
+ IMX2WdtState *s = IMX2_WDT(opaque);
+
+ switch (addr) {
+ case IMX2_WDT_WCR:
+ if (s->wcr_locked) {
+ value &= ~IMX2_WDT_WCR_LOCK_MASK;
+ value |= (s->wicr & IMX2_WDT_WCR_LOCK_MASK);
+ }
+ s->wcr_locked = true;
+ if (s->wcr_wde_locked) {
+ value &= ~IMX2_WDT_WCR_WDE;
+ value |= (s->wicr & ~IMX2_WDT_WCR_WDE);
+ } else if (value & IMX2_WDT_WCR_WDE) {
+ s->wcr_wde_locked = true;
+ }
+ if (s->wcr_wdt_locked) {
+ value &= ~IMX2_WDT_WCR_WDT;
+ value |= (s->wicr & ~IMX2_WDT_WCR_WDT);
+ } else if (value & IMX2_WDT_WCR_WDT) {
+ s->wcr_wdt_locked = true;
+ }
+
+ s->wcr = value;
+ if (!(value & IMX2_WDT_WCR_SRS)) {
+ s->wrsr = IMX2_WDT_WRSR_SFTW;
+ }
+ if (!(value & (IMX2_WDT_WCR_WDA | IMX2_WDT_WCR_SRS)) ||
+ (!(value & IMX2_WDT_WCR_WT) && (value & IMX2_WDT_WCR_WDE))) {
+ watchdog_perform_action();
+ }
+ s->wcr |= IMX2_WDT_WCR_SRS;
+ imx_wdt2_update_timer(s, true);
+ break;
+ case IMX2_WDT_WSR:
+ if (s->wsr == IMX2_WDT_SEQ1 && value == IMX2_WDT_SEQ2) {
+ imx_wdt2_update_timer(s, false);
+ }
+ s->wsr = value;
+ break;
+ case IMX2_WDT_WRSR:
+ break;
+ case IMX2_WDT_WICR:
+ if (!s->pretimeout_support) {
+ return;
+ }
+ value &= IMX2_WDT_WICR_LOCK_MASK | IMX2_WDT_WICR_WTIS;
+ if (s->wicr_locked) {
+ value &= IMX2_WDT_WICR_WTIS;
+ value |= (s->wicr & IMX2_WDT_WICR_LOCK_MASK);
+ }
+ s->wicr = value | (s->wicr & IMX2_WDT_WICR_WTIS);
+ if (value & IMX2_WDT_WICR_WTIS) {
+ s->wicr &= ~IMX2_WDT_WICR_WTIS;
+ qemu_set_irq(s->irq, 0);
+ }
+ imx_wdt2_update_itimer(s, true);
+ s->wicr_locked = true;
+ break;
+ case IMX2_WDT_WMCR:
+ s->wmcr = value & IMX2_WDT_WMCR_PDE;
+ break;
+ }
+}
+
+static const MemoryRegionOps imx2_wdt_ops = {
+ .read = imx2_wdt_read,
+ .write = imx2_wdt_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ /*
+ * Our device would not work correctly if the guest was doing
+ * unaligned access. This might not be a limitation on the
+ * real device but in practice there is no reason for a guest
+ * to access this device unaligned.
+ */
+ .min_access_size = 2,
+ .max_access_size = 2,
+ .unaligned = false,
+ },
+};
+
+static const VMStateDescription vmstate_imx2_wdt = {
+ .name = "imx2.wdt",
+ .fields = (VMStateField[]) {
+ VMSTATE_PTIMER(timer, IMX2WdtState),
+ VMSTATE_PTIMER(itimer, IMX2WdtState),
+ VMSTATE_BOOL(wicr_locked, IMX2WdtState),
+ VMSTATE_BOOL(wcr_locked, IMX2WdtState),
+ VMSTATE_BOOL(wcr_wde_locked, IMX2WdtState),
+ VMSTATE_BOOL(wcr_wdt_locked, IMX2WdtState),
+ VMSTATE_UINT16(wcr, IMX2WdtState),
+ VMSTATE_UINT16(wsr, IMX2WdtState),
+ VMSTATE_UINT16(wrsr, IMX2WdtState),
+ VMSTATE_UINT16(wmcr, IMX2WdtState),
+ VMSTATE_UINT16(wicr, IMX2WdtState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void imx2_wdt_realize(DeviceState *dev, Error **errp)
+{
+ IMX2WdtState *s = IMX2_WDT(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ memory_region_init_io(&s->mmio, OBJECT(dev),
+ &imx2_wdt_ops, s,
+ TYPE_IMX2_WDT,
+ IMX2_WDT_MMIO_SIZE);
+ sysbus_init_mmio(sbd, &s->mmio);
+ sysbus_init_irq(sbd, &s->irq);
+
+ s->timer = ptimer_init(imx2_wdt_expired, s,
+ PTIMER_POLICY_NO_IMMEDIATE_TRIGGER |
+ PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
+ PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
+ ptimer_transaction_begin(s->timer);
+ ptimer_set_freq(s->timer, 2);
+ ptimer_set_limit(s->timer, 0xff, 1);
+ ptimer_transaction_commit(s->timer);
+ if (s->pretimeout_support) {
+ s->itimer = ptimer_init(imx2_wdt_interrupt, s,
+ PTIMER_POLICY_NO_IMMEDIATE_TRIGGER |
+ PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
+ PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
+ ptimer_transaction_begin(s->itimer);
+ ptimer_set_freq(s->itimer, 2);
+ ptimer_set_limit(s->itimer, 0xff, 1);
+ ptimer_transaction_commit(s->itimer);
+ }
+}
+
+static Property imx2_wdt_properties[] = {
+ DEFINE_PROP_BOOL("pretimeout-support", IMX2WdtState, pretimeout_support,
+ false),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void imx2_wdt_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_props(dc, imx2_wdt_properties);
+ dc->realize = imx2_wdt_realize;
+ dc->reset = imx2_wdt_reset;
+ dc->vmsd = &vmstate_imx2_wdt;
+ dc->desc = "i.MX2 watchdog timer";
+ set_bit(DEVICE_CATEGORY_WATCHDOG, dc->categories);
+}
+
+static const TypeInfo imx2_wdt_info = {
+ .name = TYPE_IMX2_WDT,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IMX2WdtState),
+ .class_init = imx2_wdt_class_init,
+};
+
+static void imx2_wdt_register_type(void)
+{
+ type_register_static(&imx2_wdt_info);
+}
+type_init(imx2_wdt_register_type)