qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [RFC v1 0/1] Implement AVR WDT (watchdog timer)
@ 2021-05-05 21:18 Michael Rolnik
  2021-05-05 21:18 ` [RFC 1/1] Implement AVR watchdog timer Michael Rolnik
  0 siblings, 1 reply; 9+ messages in thread
From: Michael Rolnik @ 2021-05-05 21:18 UTC (permalink / raw)
  To: qemu-devel; +Cc: Michael Rolnik, me, konrad, richard.henderson, f4bug

1.  Initial implementation of AVR WDT
    There are two issues with this implementation so I need your help here
    a. when I configure the WDT to fire an interrupt every 15ms it actually happens every 6 instructions
    b. when I specify --icount shift=0 qemu stucks

changes since v1
1.  correct RW or RW1C behavior is implemented
2.  icount functionality is fixed
3.  I still observe something strange, it takes AVR 150 instructions to simulate 15ms


*** BLURB HERE ***

Michael Rolnik (1):
  Implement AVR watchdog timer

 MAINTAINERS                   |   2 +
 hw/avr/Kconfig                |   1 +
 hw/avr/atmega.c               |  15 +-
 hw/avr/atmega.h               |   2 +
 hw/watchdog/Kconfig           |   3 +
 hw/watchdog/avr_wdt.c         | 274 ++++++++++++++++++++++++++++++++++
 hw/watchdog/meson.build       |   2 +
 hw/watchdog/trace-events      |   5 +
 include/hw/watchdog/avr_wdt.h |  47 ++++++
 target/avr/cpu.c              |   3 +
 target/avr/cpu.h              |   1 +
 target/avr/helper.c           |   7 +-
 target/avr/translate.c        |  38 ++++-
 13 files changed, 391 insertions(+), 9 deletions(-)
 create mode 100644 hw/watchdog/avr_wdt.c
 create mode 100644 include/hw/watchdog/avr_wdt.h

-- 
2.25.1



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [RFC 1/1] Implement AVR watchdog timer
  2021-05-05 21:18 [RFC v1 0/1] Implement AVR WDT (watchdog timer) Michael Rolnik
@ 2021-05-05 21:18 ` Michael Rolnik
  2021-05-13 12:27   ` Pavel Dovgalyuk
  0 siblings, 1 reply; 9+ messages in thread
From: Michael Rolnik @ 2021-05-05 21:18 UTC (permalink / raw)
  To: qemu-devel; +Cc: Michael Rolnik, me, konrad, richard.henderson, f4bug

Signed-off-by: Michael Rolnik <mrolnik@gmail.com>
---
 MAINTAINERS                   |   2 +
 hw/avr/Kconfig                |   1 +
 hw/avr/atmega.c               |  15 +-
 hw/avr/atmega.h               |   2 +
 hw/watchdog/Kconfig           |   3 +
 hw/watchdog/avr_wdt.c         | 274 ++++++++++++++++++++++++++++++++++
 hw/watchdog/meson.build       |   2 +
 hw/watchdog/trace-events      |   5 +
 include/hw/watchdog/avr_wdt.h |  47 ++++++
 target/avr/cpu.c              |   3 +
 target/avr/cpu.h              |   1 +
 target/avr/helper.c           |   7 +-
 target/avr/translate.c        |  38 ++++-
 13 files changed, 391 insertions(+), 9 deletions(-)
 create mode 100644 hw/watchdog/avr_wdt.c
 create mode 100644 include/hw/watchdog/avr_wdt.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 4c05ff8bba..e1fce736d2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1052,6 +1052,8 @@ F: include/hw/timer/avr_timer16.h
 F: hw/timer/avr_timer16.c
 F: include/hw/misc/avr_power.h
 F: hw/misc/avr_power.c
+F: include/hw/watchdog/avr_wdt.h
+F: hw/watchdog/avr_wdt.c
 
 Arduino
 M: Philippe Mathieu-Daudé <f4bug@amsat.org>
diff --git a/hw/avr/Kconfig b/hw/avr/Kconfig
index d31298c3cc..9939e4902f 100644
--- a/hw/avr/Kconfig
+++ b/hw/avr/Kconfig
@@ -3,6 +3,7 @@ config AVR_ATMEGA_MCU
     select AVR_TIMER16
     select AVR_USART
     select AVR_POWER
+    select AVR_WDT
 
 config ARDUINO
     select AVR_ATMEGA_MCU
diff --git a/hw/avr/atmega.c b/hw/avr/atmega.c
index 44c6afebbb..31ceb1c21c 100644
--- a/hw/avr/atmega.c
+++ b/hw/avr/atmega.c
@@ -28,6 +28,7 @@ enum AtmegaPeripheral {
     GPIOG, GPIOH, GPIOI, GPIOJ, GPIOK, GPIOL,
     USART0, USART1, USART2, USART3,
     TIMER0, TIMER1, TIMER2, TIMER3, TIMER4, TIMER5,
+    WDT,
     PERIFMAX
 };
 
@@ -75,6 +76,7 @@ static const peripheral_cfg dev168_328[PERIFMAX] = {
     [GPIOD]         = {  0x29 },
     [GPIOC]         = {  0x26 },
     [GPIOB]         = {  0x23 },
+    [WDT]           = {  0x60 },
 }, dev1280_2560[PERIFMAX] = {
     [USART3]        = { 0x130, POWER1, 2 },
     [TIMER5]        = { 0x120, POWER1, 5, 0x73, 0x3a, true },
@@ -99,6 +101,7 @@ static const peripheral_cfg dev168_328[PERIFMAX] = {
     [GPIOC]         = {  0x26 },
     [GPIOB]         = {  0x23 },
     [GPIOA]         = {  0x20 },
+    [WDT]           = {  0x60 },
 };
 
 enum AtmegaIrq {
@@ -118,6 +121,7 @@ enum AtmegaIrq {
         TIMER4_COMPC_IRQ, TIMER4_OVF_IRQ,
     TIMER5_CAPT_IRQ, TIMER5_COMPA_IRQ, TIMER5_COMPB_IRQ,
         TIMER5_COMPC_IRQ, TIMER5_OVF_IRQ,
+    WATCHDOG_TIMER_IRQ,
     IRQ_COUNT
 };
 
@@ -133,6 +137,7 @@ enum AtmegaIrq {
 #define TIMER_OVF_IRQ(n)    (n * TIMER_IRQ_COUNT + TIMER0_OVF_IRQ)
 
 static const uint8_t irq168_328[IRQ_COUNT] = {
+    [WATCHDOG_TIMER_IRQ]    = 7,
     [TIMER2_COMPA_IRQ]      = 8,
     [TIMER2_COMPB_IRQ]      = 9,
     [TIMER2_OVF_IRQ]        = 10,
@@ -147,6 +152,7 @@ static const uint8_t irq168_328[IRQ_COUNT] = {
     [USART0_DRE_IRQ]        = 20,
     [USART0_TXC_IRQ]        = 21,
 }, irq1280_2560[IRQ_COUNT] = {
+    [WATCHDOG_TIMER_IRQ]    = 13,
     [TIMER2_COMPA_IRQ]      = 14,
     [TIMER2_COMPB_IRQ]      = 15,
     [TIMER2_OVF_IRQ]        = 16,
@@ -344,10 +350,17 @@ static void atmega_realize(DeviceState *dev, Error **errp)
         g_free(devname);
     }
 
+    /* Watchdog Timer */
+    object_initialize_child(OBJECT(dev), "wdt", &s->wdt, TYPE_AVR_WDT);
+    sysbus_realize(SYS_BUS_DEVICE(&s->wdt), &error_abort);
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt), 0,
+                    OFFSET_DATA + mc->dev[WDT].addr);
+    qdev_connect_gpio_out_named(cpudev, "wdr", 0,
+                    qdev_get_gpio_in_named(DEVICE(&s->wdt), "wdr", 0));
+
     create_unimplemented_device("avr-twi",          OFFSET_DATA + 0x0b8, 6);
     create_unimplemented_device("avr-adc",          OFFSET_DATA + 0x078, 8);
     create_unimplemented_device("avr-ext-mem-ctrl", OFFSET_DATA + 0x074, 2);
-    create_unimplemented_device("avr-watchdog",     OFFSET_DATA + 0x060, 1);
     create_unimplemented_device("avr-spi",          OFFSET_DATA + 0x04c, 3);
     create_unimplemented_device("avr-eeprom",       OFFSET_DATA + 0x03f, 3);
 }
diff --git a/hw/avr/atmega.h b/hw/avr/atmega.h
index a99ee15c7e..60bbd44bdd 100644
--- a/hw/avr/atmega.h
+++ b/hw/avr/atmega.h
@@ -13,6 +13,7 @@
 
 #include "hw/char/avr_usart.h"
 #include "hw/timer/avr_timer16.h"
+#include "hw/watchdog/avr_wdt.h"
 #include "hw/misc/avr_power.h"
 #include "target/avr/cpu.h"
 #include "qom/object.h"
@@ -45,6 +46,7 @@ struct AtmegaMcuState {
     AVRMaskState pwr[POWER_MAX];
     AVRUsartState usart[USART_MAX];
     AVRTimer16State timer[TIMER_MAX];
+    AVRWatchdogState wdt;
     uint64_t xtal_freq_hz;
 };
 
diff --git a/hw/watchdog/Kconfig b/hw/watchdog/Kconfig
index 66e1d029e3..e0f89d2fe0 100644
--- a/hw/watchdog/Kconfig
+++ b/hw/watchdog/Kconfig
@@ -20,3 +20,6 @@ config WDT_IMX2
 
 config WDT_SBSA
     bool
+
+config AVR_WDT
+    bool
diff --git a/hw/watchdog/avr_wdt.c b/hw/watchdog/avr_wdt.c
new file mode 100644
index 0000000000..cbd6457c8b
--- /dev/null
+++ b/hw/watchdog/avr_wdt.c
@@ -0,0 +1,274 @@
+/*
+ * AVR watchdog
+ *
+ * Copyright (c) 2021 Michael Rolnik
+ *
+ * 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/lgpl-2.1.html>
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "hw/irq.h"
+#include "hw/watchdog/avr_wdt.h"
+#include "trace.h"
+#include "target/avr/cpu.h"
+#include "sysemu/runstate.h"
+
+/* Field masks */
+#define WDTCSR_MASK_WDP0    0x01
+#define WDTCSR_MASK_WDP1    0x02
+#define WDTCSR_MASK_WDP2    0x04
+#define WDTCSR_MASK_WDE     0x08
+#define WDTCSR_MASK_WDCE    0x10
+#define WDTCSR_MASK_WDP3    0x20
+#define WDTCSR_MASK_WDIE    0x40
+#define WDTCSR_MASK_WDIF    0x80
+
+#define WDTCSR_SHFT_WDP0    0x00
+#define WDTCSR_SHFT_WDP1    0x01
+#define WDTCSR_SHFT_WDP2    0x02
+#define WDTCSR_SHFT_WDE     0x03
+#define WDTCSR_SHFT_WDCE    0x04
+#define WDTCSR_SHFT_WDP3    0x05
+#define WDTCSR_SHFT_WDIE    0x06
+#define WDTCSR_SHFT_WDIF    0x07
+
+#define MCUSR_MASK_WDRF     0x04
+#define MCUSR_ADDR      (OFFSET_DATA + 0x55)
+
+/* Helper macros */
+#define WDP0(csr)       ((csr & WDTCSR_MASK_WDP0) >> WDTCSR_SHFT_WDP0)
+#define WDP1(csr)       ((csr & WDTCSR_MASK_WDP1) >> WDTCSR_SHFT_WDP1)
+#define WDP2(csr)       ((csr & WDTCSR_MASK_WDP2) >> WDTCSR_SHFT_WDP2)
+#define WDP3(csr)       ((csr & WDTCSR_MASK_WDP3) >> WDTCSR_SHFT_WDP3)
+#define WDP(csr)        ((WDP3(csr) << 3) | (WDP2(csr) << 2) | \
+                         (WDP1(csr) << 1) | (WDP0(csr) << 0))
+#define WDIE(csr)       ((csr & WDTCSR_MASK_WDIE) >> WDTCSR_SHFT_WDIE)
+#define WDE(csr)        ((csr & WDTCSR_MASK_WDE) >> WDTCSR_SHFT_WDE)
+#define WCE(csr)        ((csr & WDTCSR_MASK_WCE) >> WDTCSR_SHFT_WCE)
+
+#define DB_PRINT(fmt, args...) /* Nothing */
+
+#define MS2NS(n)        ((n) * 1000000ull)
+
+static void set_bits(uint8_t *addr, uint8_t bits)
+{
+    *addr |= bits;
+}
+
+static void rst_bits(uint8_t *addr, uint8_t bits)
+{
+    *addr &= ~bits;
+}
+
+static void avr_wdt_reset_alarm(AVRWatchdogState *wdt)
+{
+    uint32_t csr = wdt->csr;
+    int wdp = WDP(csr);
+
+    if (WDIE(csr) == 0 && WDE(csr) == 0) {
+        /* watchdog is stopped */
+        return;
+    }
+
+    timer_mod_ns(wdt->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+            (MS2NS(15) << wdp));
+}
+
+static void avr_wdt_interrupt(void *opaque)
+{
+    AVRWatchdogState *wdt = opaque;
+    int8_t csr = wdt->csr;
+
+    if (WDIE(csr) == 1) {
+        /* Interrupt Mode */
+        set_bits(&wdt->csr, WDTCSR_MASK_WDIF);
+        qemu_set_irq(wdt->irq, 1);
+        trace_avr_wdt_interrupt();
+        rst_bits(&wdt->csr, WDTCSR_MASK_WDIF | WDTCSR_MASK_WDIE);
+    }
+
+    if (WDE(csr) == 1) {
+        /* System Reset Mode */
+        qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+    }
+
+    avr_wdt_reset_alarm(wdt);
+}
+
+static void avr_wdt_reset(DeviceState *dev)
+{
+    AVRWatchdogState *wdt = AVR_WDT(dev);
+
+    wdt->csr = 0;
+    qemu_set_irq(wdt->irq, 0);
+    avr_wdt_reset_alarm(wdt);
+}
+
+static uint64_t avr_wdt_read(void *opaque, hwaddr offset, unsigned size)
+{
+    assert(size == 1);
+    AVRWatchdogState *wdt = opaque;
+    uint8_t retval = wdt->csr;
+
+    trace_avr_wdt_read(offset, retval);
+
+    return (uint64_t)retval;
+}
+
+static void avr_wdt_write(void *opaque, hwaddr offset,
+                              uint64_t val64, unsigned size)
+{
+    assert(size == 1);
+    AVRWatchdogState *wdt = opaque;
+    uint8_t val = (uint8_t)val64;
+    uint8_t set1 = val; /* bits that should be set to 1 */
+    uint8_t set0 = ~val;/* bits that should be set to 0 */
+    uint8_t mcusr = 0;
+
+    /*
+     *  Bit 7 - WDIF: Watchdog Interrupt Flag
+     *  This bit is set when a time-out occurs in the Watchdog Timer and the
+     *  Watchdog Timer is configured for interrupt. WDIF is cleared by hardware
+     *  when executing the corresponding interrupt handling vector.
+     *  Alternatively, WDIF is cleared by writing a logic one to the flag.
+     *  When the I-bit in SREG and WDIE are set, the Watchdog Time-out Interrupt
+     *  is executed.
+     */
+    if (val & WDTCSR_MASK_WDIF) {
+        rst_bits(&set1, WDTCSR_MASK_WDIF);  /* don't set 1 */
+        set_bits(&set0, WDTCSR_MASK_WDIF);  /* set 0 */
+    } else {
+        rst_bits(&set1, WDTCSR_MASK_WDIF);  /* don't set 1 */
+        rst_bits(&set0, WDTCSR_MASK_WDIF);  /* don't set 0 */
+    }
+
+    /*
+     *  Bit 4 - WDCE: Watchdog Change Enable
+     *  This bit is used in timed sequences for changing WDE and prescaler
+     *  bits. To clear the WDE bit, and/or change the prescaler bits,
+     *  WDCE must be set.
+     *  Once written to one, hardware will clear WDCE after four clock cycles.
+     */
+    if (!(val & WDTCSR_MASK_WDCE)) {
+        uint8_t bits = WDTCSR_MASK_WDE | WDTCSR_MASK_WDP0 | WDTCSR_MASK_WDP1 |
+                       WDTCSR_MASK_WDP2 | WDTCSR_MASK_WDP3;
+        rst_bits(&set1, bits);
+        rst_bits(&set0, bits);
+    }
+
+    /*
+     *  Bit 3 - WDE: Watchdog System Reset Enable
+     *  WDE is overridden by WDRF in MCUSR. This means that WDE is always set
+     *  when WDRF is set. To clear WDE, WDRF must be cleared first. This
+     *  feature ensures multiple resets during conditions causing failure, and
+     *  a safe start-up after the failure.
+     */
+    cpu_physical_memory_read(MCUSR_ADDR, &mcusr, sizeof(mcusr));
+    if (mcusr & MCUSR_MASK_WDRF) {
+        set_bits(&set1, WDTCSR_MASK_WDE);
+        rst_bits(&set0, WDTCSR_MASK_WDE);
+    }
+
+    /*  update CSR value */
+    assert((set0 & set1) == 0);
+
+    val = wdt->csr;
+    set_bits(&val, set1);
+    rst_bits(&val, set0);
+    wdt->csr = val;
+    trace_avr_wdt_write(offset, val);
+    avr_wdt_reset_alarm(wdt);
+
+    /*
+     *  Bit 6 - WDIE: Watchdog Interrupt Enable
+     *  When this bit is written to one and the I-bit in the Status Register is
+     *  set, the Watchdog Interrupt is enabled. If WDE is cleared in
+     *  combination with this setting, the Watchdog Timer is in Interrupt Mode,
+     *  and the corresponding interrupt is executed if time-out in the Watchdog
+     *  Timer occurs.
+     *  If WDE is set, the Watchdog Timer is in Interrupt and System Reset Mode.
+     *  The first time-out in the Watchdog Timer will set WDIF. Executing the
+     *  corresponding interrupt vector will clear WDIE and WDIF automatically by
+     *  hardware (the Watchdog goes to System Reset Mode). This is useful for
+     *  keeping the Watchdog Timer security while using the interrupt. To stay
+     *  in Interrupt and System Reset Mode, WDIE must be set after each
+     *  interrupt. This should however not be done within the interrupt service
+     *  routine itself, as this might compromise the safety-function of the
+     *  Watchdog System Reset mode. If the interrupt is not executed before the
+     *  next time-out, a System Reset will be applied.
+     */
+    if ((val & WDTCSR_MASK_WDIE) && (wdt->csr & WDTCSR_MASK_WDIF)) {
+        avr_wdt_interrupt(opaque);
+    }
+}
+
+static const MemoryRegionOps avr_wdt_ops = {
+    .read = avr_wdt_read,
+    .write = avr_wdt_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .impl = {.max_access_size = 1}
+};
+
+static void avr_wdt_wdr(void *opaque, int irq, int level)
+{
+    AVRWatchdogState *wdt = AVR_WDT(opaque);
+
+    avr_wdt_reset_alarm(wdt);
+}
+
+static void avr_wdt_init(Object *obj)
+{
+    AVRWatchdogState *s = AVR_WDT(obj);
+
+    sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+
+    memory_region_init_io(&s->iomem, obj, &avr_wdt_ops,
+                          s, "avr-wdt", 0xa);
+
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+    qdev_init_gpio_in_named(DEVICE(s), avr_wdt_wdr, "wdr", 1);
+}
+
+static void avr_wdt_realize(DeviceState *dev, Error **errp)
+{
+    AVRWatchdogState *s = AVR_WDT(dev);
+
+    s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, avr_wdt_interrupt, s);
+}
+
+static void avr_wdt_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->reset = avr_wdt_reset;
+    dc->realize = avr_wdt_realize;
+}
+
+static const TypeInfo avr_wdt_info = {
+    .name          = TYPE_AVR_WDT,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(AVRWatchdogState),
+    .instance_init = avr_wdt_init,
+    .class_init    = avr_wdt_class_init,
+};
+
+static void avr_wdt_register_types(void)
+{
+    type_register_static(&avr_wdt_info);
+}
+
+type_init(avr_wdt_register_types)
diff --git a/hw/watchdog/meson.build b/hw/watchdog/meson.build
index 054c403dea..8db2be8317 100644
--- a/hw/watchdog/meson.build
+++ b/hw/watchdog/meson.build
@@ -6,3 +6,5 @@ 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_AVR_WDT', if_true: files('avr_wdt.c'))
diff --git a/hw/watchdog/trace-events b/hw/watchdog/trace-events
index 3124ca1f1b..ac14773179 100644
--- a/hw/watchdog/trace-events
+++ b/hw/watchdog/trace-events
@@ -5,3 +5,8 @@ cmsdk_apb_watchdog_read(uint64_t offset, uint64_t data, unsigned size) "CMSDK AP
 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
+
+# avr_wdt.c
+avr_wdt_read(uint8_t addr, uint8_t value) "wdt read addr:%u value:%u"
+avr_wdt_write(uint8_t addr, uint8_t value) "wdt write addr:%u value:%u"
+avr_wdt_interrupt(void) ""
diff --git a/include/hw/watchdog/avr_wdt.h b/include/hw/watchdog/avr_wdt.h
new file mode 100644
index 0000000000..8433663013
--- /dev/null
+++ b/include/hw/watchdog/avr_wdt.h
@@ -0,0 +1,47 @@
+/*
+ * AVR watchdog
+ *
+ * Copyright (c) 2021 Michael Rolnik
+ *
+ * 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/lgpl-2.1.html>
+ */
+
+#ifndef HW_WATCHDOG_AVR_WDT_H
+#define HW_WATCHDOG_AVR_WDT_H
+
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "hw/hw.h"
+#include "qom/object.h"
+
+#define TYPE_AVR_WDT "avr-wdt"
+OBJECT_DECLARE_SIMPLE_TYPE(AVRWatchdogState, AVR_WDT)
+
+struct AVRWatchdogState {
+    /* <private> */
+    SysBusDevice parent_obj;
+
+    /* <public> */
+    MemoryRegion iomem;
+    MemoryRegion imsk_iomem;
+    MemoryRegion ifr_iomem;
+    QEMUTimer *timer;
+    qemu_irq irq;
+
+    /* registers */
+    uint8_t csr;
+};
+
+#endif /* HW_WATCHDOG_AVR_WDT_H */
diff --git a/target/avr/cpu.c b/target/avr/cpu.c
index 0f4596932b..d5eb785833 100644
--- a/target/avr/cpu.c
+++ b/target/avr/cpu.c
@@ -131,6 +131,9 @@ static void avr_cpu_initfn(Object *obj)
     /* Set the number of interrupts supported by the CPU. */
     qdev_init_gpio_in(DEVICE(cpu), avr_cpu_set_int,
                       sizeof(cpu->env.intsrc) * 8);
+
+    /* register watchdog timer reset interrupt */
+    qdev_init_gpio_out_named(DEVICE(cpu), &cpu->wdr, "wdr", 1);
 }
 
 static ObjectClass *avr_cpu_class_by_name(const char *cpu_model)
diff --git a/target/avr/cpu.h b/target/avr/cpu.h
index d148e8c75a..f8f5641c8b 100644
--- a/target/avr/cpu.h
+++ b/target/avr/cpu.h
@@ -152,6 +152,7 @@ typedef struct AVRCPU {
 
     CPUNegativeOffsetState neg;
     CPUAVRState env;
+    qemu_irq wdr; /* reset WDT */
 } AVRCPU;
 
 extern const struct VMStateDescription vms_avr_cpu;
diff --git a/target/avr/helper.c b/target/avr/helper.c
index 35e1019594..dd88057e5f 100644
--- a/target/avr/helper.c
+++ b/target/avr/helper.c
@@ -24,6 +24,7 @@
 #include "exec/exec-all.h"
 #include "exec/address-spaces.h"
 #include "exec/helper-proto.h"
+#include "hw/irq.h"
 
 bool avr_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
 {
@@ -188,11 +189,9 @@ void helper_break(CPUAVRState *env)
 
 void helper_wdr(CPUAVRState *env)
 {
-    CPUState *cs = env_cpu(env);
+    AVRCPU *cpu = env_archcpu(env);
 
-    /* WD is not implemented yet, placeholder */
-    cs->exception_index = EXCP_DEBUG;
-    cpu_loop_exit(cs);
+    qemu_set_irq(cpu->wdr, 1);
 }
 
 /*
diff --git a/target/avr/translate.c b/target/avr/translate.c
index 850c5941d9..b7b716f4a0 100644
--- a/target/avr/translate.c
+++ b/target/avr/translate.c
@@ -1411,6 +1411,9 @@ static bool trans_SBIC(DisasContext *ctx, arg_SBIC *a)
 {
     TCGv temp = tcg_const_i32(a->reg);
 
+    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
+        gen_io_start();
+    }
     gen_helper_inb(temp, cpu_env, temp);
     tcg_gen_andi_tl(temp, temp, 1 << a->bit);
     ctx->skip_cond = TCG_COND_EQ;
@@ -1429,6 +1432,9 @@ static bool trans_SBIS(DisasContext *ctx, arg_SBIS *a)
 {
     TCGv temp = tcg_const_i32(a->reg);
 
+    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
+        gen_io_start();
+    }
     gen_helper_inb(temp, cpu_env, temp);
     tcg_gen_andi_tl(temp, temp, 1 << a->bit);
     ctx->skip_cond = TCG_COND_NE;
@@ -1611,6 +1617,9 @@ static TCGv gen_get_zaddr(void)
 static void gen_data_store(DisasContext *ctx, TCGv data, TCGv addr)
 {
     if (ctx->tb->flags & TB_FLAGS_FULL_ACCESS) {
+        if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
+            gen_io_start();
+        }
         gen_helper_fullwr(cpu_env, data, addr);
     } else {
         tcg_gen_qemu_st8(data, addr, MMU_DATA_IDX); /* mem[addr] = data */
@@ -1620,6 +1629,9 @@ static void gen_data_store(DisasContext *ctx, TCGv data, TCGv addr)
 static void gen_data_load(DisasContext *ctx, TCGv data, TCGv addr)
 {
     if (ctx->tb->flags & TB_FLAGS_FULL_ACCESS) {
+        if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
+            gen_io_start();
+        }
         gen_helper_fullrd(data, cpu_env, addr);
     } else {
         tcg_gen_qemu_ld8u(data, addr, MMU_DATA_IDX); /* data = mem[addr] */
@@ -2325,6 +2337,9 @@ static bool trans_IN(DisasContext *ctx, arg_IN *a)
     TCGv Rd = cpu_r[a->rd];
     TCGv port = tcg_const_i32(a->imm);
 
+    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
+        gen_io_start();
+    }
     gen_helper_inb(Rd, cpu_env, port);
 
     tcg_temp_free_i32(port);
@@ -2341,6 +2356,9 @@ static bool trans_OUT(DisasContext *ctx, arg_OUT *a)
     TCGv Rd = cpu_r[a->rd];
     TCGv port = tcg_const_i32(a->imm);
 
+    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
+        gen_io_start();
+    }
     gen_helper_outb(cpu_env, port, Rd);
 
     tcg_temp_free_i32(port);
@@ -2641,6 +2659,9 @@ static bool trans_SBI(DisasContext *ctx, arg_SBI *a)
     TCGv data = tcg_temp_new_i32();
     TCGv port = tcg_const_i32(a->reg);
 
+    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
+        gen_io_start();
+    }
     gen_helper_inb(data, cpu_env, port);
     tcg_gen_ori_tl(data, data, 1 << a->bit);
     gen_helper_outb(cpu_env, port, data);
@@ -2660,6 +2681,9 @@ static bool trans_CBI(DisasContext *ctx, arg_CBI *a)
     TCGv data = tcg_temp_new_i32();
     TCGv port = tcg_const_i32(a->reg);
 
+    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
+        gen_io_start();
+    }
     gen_helper_inb(data, cpu_env, port);
     tcg_gen_andi_tl(data, data, ~(1 << a->bit));
     gen_helper_outb(cpu_env, port, data);
@@ -2830,6 +2854,9 @@ static bool trans_SLEEP(DisasContext *ctx, arg_SLEEP *a)
  */
 static bool trans_WDR(DisasContext *ctx, arg_WDR *a)
 {
+    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
+        gen_io_start();
+    }
     gen_helper_wdr(cpu_env);
 
     return true;
@@ -2919,6 +2946,9 @@ void gen_intermediate_code(CPUState *cs, TranslationBlock *tb, int max_insns)
          * This flag is set by ST/LD instruction we will regenerate it ONLY
          * with mem/cpu memory access instead of mem access
          */
+        if (tb_cflags(tb) & CF_USE_ICOUNT) {
+            gen_io_start();
+        }
         max_insns = 1;
     }
     if (ctx.singlestep) {
@@ -2955,6 +2985,10 @@ void gen_intermediate_code(CPUState *cs, TranslationBlock *tb, int max_insns)
             goto done_generating;
         }
 
+        if (num_insns == max_insns && (tb_cflags(tb) & CF_LAST_IO)) {
+            gen_io_start();
+        }
+
         /* Conditionally skip the next instruction, if indicated.  */
         if (ctx.skip_cond != TCG_COND_NEVER) {
             skip_label = gen_new_label();
@@ -2998,10 +3032,6 @@ void gen_intermediate_code(CPUState *cs, TranslationBlock *tb, int max_insns)
              && (ctx.npc - pc_start) * 2 < TARGET_PAGE_SIZE - 4
              && !tcg_op_buf_full());
 
-    if (tb->cflags & CF_LAST_IO) {
-        gen_io_end();
-    }
-
     bool nonconst_skip = canonicalize_skip(&ctx);
 
     switch (ctx.bstate) {
-- 
2.25.1



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [RFC 1/1] Implement AVR watchdog timer
  2021-05-05 21:18 ` [RFC 1/1] Implement AVR watchdog timer Michael Rolnik
@ 2021-05-13 12:27   ` Pavel Dovgalyuk
  2021-05-14  7:20     ` Michael Rolnik
  0 siblings, 1 reply; 9+ messages in thread
From: Pavel Dovgalyuk @ 2021-05-13 12:27 UTC (permalink / raw)
  To: Michael Rolnik, qemu-devel; +Cc: me, konrad, richard.henderson, f4bug

On 06.05.2021 00:18, Michael Rolnik wrote:
> Signed-off-by: Michael Rolnik <mrolnik@gmail.com>
> ---
>   MAINTAINERS                   |   2 +
>   hw/avr/Kconfig                |   1 +
>   hw/avr/atmega.c               |  15 +-
>   hw/avr/atmega.h               |   2 +
>   hw/watchdog/Kconfig           |   3 +
>   hw/watchdog/avr_wdt.c         | 274 ++++++++++++++++++++++++++++++++++
>   hw/watchdog/meson.build       |   2 +
>   hw/watchdog/trace-events      |   5 +
>   include/hw/watchdog/avr_wdt.h |  47 ++++++
>   target/avr/cpu.c              |   3 +
>   target/avr/cpu.h              |   1 +
>   target/avr/helper.c           |   7 +-
>   target/avr/translate.c        |  38 ++++-
>   13 files changed, 391 insertions(+), 9 deletions(-)
>   create mode 100644 hw/watchdog/avr_wdt.c
>   create mode 100644 include/hw/watchdog/avr_wdt.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 4c05ff8bba..e1fce736d2 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1052,6 +1052,8 @@ F: include/hw/timer/avr_timer16.h
>   F: hw/timer/avr_timer16.c
>   F: include/hw/misc/avr_power.h
>   F: hw/misc/avr_power.c
> +F: include/hw/watchdog/avr_wdt.h
> +F: hw/watchdog/avr_wdt.c
>   
>   Arduino
>   M: Philippe Mathieu-Daudé <f4bug@amsat.org>
> diff --git a/hw/avr/Kconfig b/hw/avr/Kconfig
> index d31298c3cc..9939e4902f 100644
> --- a/hw/avr/Kconfig
> +++ b/hw/avr/Kconfig
> @@ -3,6 +3,7 @@ config AVR_ATMEGA_MCU
>       select AVR_TIMER16
>       select AVR_USART
>       select AVR_POWER
> +    select AVR_WDT
>   
>   config ARDUINO
>       select AVR_ATMEGA_MCU
> diff --git a/hw/avr/atmega.c b/hw/avr/atmega.c
> index 44c6afebbb..31ceb1c21c 100644
> --- a/hw/avr/atmega.c
> +++ b/hw/avr/atmega.c
> @@ -28,6 +28,7 @@ enum AtmegaPeripheral {
>       GPIOG, GPIOH, GPIOI, GPIOJ, GPIOK, GPIOL,
>       USART0, USART1, USART2, USART3,
>       TIMER0, TIMER1, TIMER2, TIMER3, TIMER4, TIMER5,
> +    WDT,
>       PERIFMAX
>   };
>   
> @@ -75,6 +76,7 @@ static const peripheral_cfg dev168_328[PERIFMAX] = {
>       [GPIOD]         = {  0x29 },
>       [GPIOC]         = {  0x26 },
>       [GPIOB]         = {  0x23 },
> +    [WDT]           = {  0x60 },
>   }, dev1280_2560[PERIFMAX] = {
>       [USART3]        = { 0x130, POWER1, 2 },
>       [TIMER5]        = { 0x120, POWER1, 5, 0x73, 0x3a, true },
> @@ -99,6 +101,7 @@ static const peripheral_cfg dev168_328[PERIFMAX] = {
>       [GPIOC]         = {  0x26 },
>       [GPIOB]         = {  0x23 },
>       [GPIOA]         = {  0x20 },
> +    [WDT]           = {  0x60 },
>   };
>   
>   enum AtmegaIrq {
> @@ -118,6 +121,7 @@ enum AtmegaIrq {
>           TIMER4_COMPC_IRQ, TIMER4_OVF_IRQ,
>       TIMER5_CAPT_IRQ, TIMER5_COMPA_IRQ, TIMER5_COMPB_IRQ,
>           TIMER5_COMPC_IRQ, TIMER5_OVF_IRQ,
> +    WATCHDOG_TIMER_IRQ,
>       IRQ_COUNT
>   };
>   
> @@ -133,6 +137,7 @@ enum AtmegaIrq {
>   #define TIMER_OVF_IRQ(n)    (n * TIMER_IRQ_COUNT + TIMER0_OVF_IRQ)
>   
>   static const uint8_t irq168_328[IRQ_COUNT] = {
> +    [WATCHDOG_TIMER_IRQ]    = 7,
>       [TIMER2_COMPA_IRQ]      = 8,
>       [TIMER2_COMPB_IRQ]      = 9,
>       [TIMER2_OVF_IRQ]        = 10,
> @@ -147,6 +152,7 @@ static const uint8_t irq168_328[IRQ_COUNT] = {
>       [USART0_DRE_IRQ]        = 20,
>       [USART0_TXC_IRQ]        = 21,
>   }, irq1280_2560[IRQ_COUNT] = {
> +    [WATCHDOG_TIMER_IRQ]    = 13,
>       [TIMER2_COMPA_IRQ]      = 14,
>       [TIMER2_COMPB_IRQ]      = 15,
>       [TIMER2_OVF_IRQ]        = 16,
> @@ -344,10 +350,17 @@ static void atmega_realize(DeviceState *dev, Error **errp)
>           g_free(devname);
>       }
>   
> +    /* Watchdog Timer */
> +    object_initialize_child(OBJECT(dev), "wdt", &s->wdt, TYPE_AVR_WDT);
> +    sysbus_realize(SYS_BUS_DEVICE(&s->wdt), &error_abort);
> +    sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt), 0,
> +                    OFFSET_DATA + mc->dev[WDT].addr);
> +    qdev_connect_gpio_out_named(cpudev, "wdr", 0,
> +                    qdev_get_gpio_in_named(DEVICE(&s->wdt), "wdr", 0));
> +
>       create_unimplemented_device("avr-twi",          OFFSET_DATA + 0x0b8, 6);
>       create_unimplemented_device("avr-adc",          OFFSET_DATA + 0x078, 8);
>       create_unimplemented_device("avr-ext-mem-ctrl", OFFSET_DATA + 0x074, 2);
> -    create_unimplemented_device("avr-watchdog",     OFFSET_DATA + 0x060, 1);
>       create_unimplemented_device("avr-spi",          OFFSET_DATA + 0x04c, 3);
>       create_unimplemented_device("avr-eeprom",       OFFSET_DATA + 0x03f, 3);
>   }
> diff --git a/hw/avr/atmega.h b/hw/avr/atmega.h
> index a99ee15c7e..60bbd44bdd 100644
> --- a/hw/avr/atmega.h
> +++ b/hw/avr/atmega.h
> @@ -13,6 +13,7 @@
>   
>   #include "hw/char/avr_usart.h"
>   #include "hw/timer/avr_timer16.h"
> +#include "hw/watchdog/avr_wdt.h"
>   #include "hw/misc/avr_power.h"
>   #include "target/avr/cpu.h"
>   #include "qom/object.h"
> @@ -45,6 +46,7 @@ struct AtmegaMcuState {
>       AVRMaskState pwr[POWER_MAX];
>       AVRUsartState usart[USART_MAX];
>       AVRTimer16State timer[TIMER_MAX];
> +    AVRWatchdogState wdt;
>       uint64_t xtal_freq_hz;
>   };
>   
> diff --git a/hw/watchdog/Kconfig b/hw/watchdog/Kconfig
> index 66e1d029e3..e0f89d2fe0 100644
> --- a/hw/watchdog/Kconfig
> +++ b/hw/watchdog/Kconfig
> @@ -20,3 +20,6 @@ config WDT_IMX2
>   
>   config WDT_SBSA
>       bool
> +
> +config AVR_WDT
> +    bool
> diff --git a/hw/watchdog/avr_wdt.c b/hw/watchdog/avr_wdt.c
> new file mode 100644
> index 0000000000..cbd6457c8b
> --- /dev/null
> +++ b/hw/watchdog/avr_wdt.c
> @@ -0,0 +1,274 @@
> +/*
> + * AVR watchdog
> + *
> + * Copyright (c) 2021 Michael Rolnik
> + *
> + * 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/lgpl-2.1.html>
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "qemu/log.h"
> +#include "hw/irq.h"
> +#include "hw/watchdog/avr_wdt.h"
> +#include "trace.h"
> +#include "target/avr/cpu.h"
> +#include "sysemu/runstate.h"
> +
> +/* Field masks */
> +#define WDTCSR_MASK_WDP0    0x01
> +#define WDTCSR_MASK_WDP1    0x02
> +#define WDTCSR_MASK_WDP2    0x04
> +#define WDTCSR_MASK_WDE     0x08
> +#define WDTCSR_MASK_WDCE    0x10
> +#define WDTCSR_MASK_WDP3    0x20
> +#define WDTCSR_MASK_WDIE    0x40
> +#define WDTCSR_MASK_WDIF    0x80
> +
> +#define WDTCSR_SHFT_WDP0    0x00
> +#define WDTCSR_SHFT_WDP1    0x01
> +#define WDTCSR_SHFT_WDP2    0x02
> +#define WDTCSR_SHFT_WDE     0x03
> +#define WDTCSR_SHFT_WDCE    0x04
> +#define WDTCSR_SHFT_WDP3    0x05
> +#define WDTCSR_SHFT_WDIE    0x06
> +#define WDTCSR_SHFT_WDIF    0x07
> +
> +#define MCUSR_MASK_WDRF     0x04
> +#define MCUSR_ADDR      (OFFSET_DATA + 0x55)
> +
> +/* Helper macros */
> +#define WDP0(csr)       ((csr & WDTCSR_MASK_WDP0) >> WDTCSR_SHFT_WDP0)
> +#define WDP1(csr)       ((csr & WDTCSR_MASK_WDP1) >> WDTCSR_SHFT_WDP1)
> +#define WDP2(csr)       ((csr & WDTCSR_MASK_WDP2) >> WDTCSR_SHFT_WDP2)
> +#define WDP3(csr)       ((csr & WDTCSR_MASK_WDP3) >> WDTCSR_SHFT_WDP3)
> +#define WDP(csr)        ((WDP3(csr) << 3) | (WDP2(csr) << 2) | \
> +                         (WDP1(csr) << 1) | (WDP0(csr) << 0))
> +#define WDIE(csr)       ((csr & WDTCSR_MASK_WDIE) >> WDTCSR_SHFT_WDIE)
> +#define WDE(csr)        ((csr & WDTCSR_MASK_WDE) >> WDTCSR_SHFT_WDE)
> +#define WCE(csr)        ((csr & WDTCSR_MASK_WCE) >> WDTCSR_SHFT_WCE)
> +
> +#define DB_PRINT(fmt, args...) /* Nothing */
> +
> +#define MS2NS(n)        ((n) * 1000000ull)

I think there is SCALE_MS define for that.

> +
> +static void set_bits(uint8_t *addr, uint8_t bits)
> +{
> +    *addr |= bits;
> +}
> +
> +static void rst_bits(uint8_t *addr, uint8_t bits)
> +{
> +    *addr &= ~bits;
> +}
> +
> +static void avr_wdt_reset_alarm(AVRWatchdogState *wdt)
> +{
> +    uint32_t csr = wdt->csr;
> +    int wdp = WDP(csr);
> +
> +    if (WDIE(csr) == 0 && WDE(csr) == 0) {
> +        /* watchdog is stopped */
> +        return;
> +    }
> +
> +    timer_mod_ns(wdt->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
> +            (MS2NS(15) << wdp));
> +}
> +
> +static void avr_wdt_interrupt(void *opaque)
> +{
> +    AVRWatchdogState *wdt = opaque;
> +    int8_t csr = wdt->csr;
> +
> +    if (WDIE(csr) == 1) {
> +        /* Interrupt Mode */
> +        set_bits(&wdt->csr, WDTCSR_MASK_WDIF);
> +        qemu_set_irq(wdt->irq, 1);
> +        trace_avr_wdt_interrupt();
> +        rst_bits(&wdt->csr, WDTCSR_MASK_WDIF | WDTCSR_MASK_WDIE);
> +    }
> +
> +    if (WDE(csr) == 1) {
> +        /* System Reset Mode */
> +        qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
> +    }
> +
> +    avr_wdt_reset_alarm(wdt);
> +}
> +
> +static void avr_wdt_reset(DeviceState *dev)
> +{
> +    AVRWatchdogState *wdt = AVR_WDT(dev);
> +
> +    wdt->csr = 0;
> +    qemu_set_irq(wdt->irq, 0);
> +    avr_wdt_reset_alarm(wdt);
> +}
> +
> +static uint64_t avr_wdt_read(void *opaque, hwaddr offset, unsigned size)
> +{
> +    assert(size == 1);
> +    AVRWatchdogState *wdt = opaque;
> +    uint8_t retval = wdt->csr;
> +
> +    trace_avr_wdt_read(offset, retval);
> +
> +    return (uint64_t)retval;
> +}
> +
> +static void avr_wdt_write(void *opaque, hwaddr offset,
> +                              uint64_t val64, unsigned size)
> +{
> +    assert(size == 1);
> +    AVRWatchdogState *wdt = opaque;
> +    uint8_t val = (uint8_t)val64;
> +    uint8_t set1 = val; /* bits that should be set to 1 */
> +    uint8_t set0 = ~val;/* bits that should be set to 0 */
> +    uint8_t mcusr = 0;
> +
> +    /*
> +     *  Bit 7 - WDIF: Watchdog Interrupt Flag
> +     *  This bit is set when a time-out occurs in the Watchdog Timer and the
> +     *  Watchdog Timer is configured for interrupt. WDIF is cleared by hardware
> +     *  when executing the corresponding interrupt handling vector.
> +     *  Alternatively, WDIF is cleared by writing a logic one to the flag.
> +     *  When the I-bit in SREG and WDIE are set, the Watchdog Time-out Interrupt
> +     *  is executed.
> +     */
> +    if (val & WDTCSR_MASK_WDIF) {
> +        rst_bits(&set1, WDTCSR_MASK_WDIF);  /* don't set 1 */
> +        set_bits(&set0, WDTCSR_MASK_WDIF);  /* set 0 */
> +    } else {
> +        rst_bits(&set1, WDTCSR_MASK_WDIF);  /* don't set 1 */
> +        rst_bits(&set0, WDTCSR_MASK_WDIF);  /* don't set 0 */
> +    }
> +
> +    /*
> +     *  Bit 4 - WDCE: Watchdog Change Enable
> +     *  This bit is used in timed sequences for changing WDE and prescaler
> +     *  bits. To clear the WDE bit, and/or change the prescaler bits,
> +     *  WDCE must be set.
> +     *  Once written to one, hardware will clear WDCE after four clock cycles.
> +     */
> +    if (!(val & WDTCSR_MASK_WDCE)) {
> +        uint8_t bits = WDTCSR_MASK_WDE | WDTCSR_MASK_WDP0 | WDTCSR_MASK_WDP1 |
> +                       WDTCSR_MASK_WDP2 | WDTCSR_MASK_WDP3;
> +        rst_bits(&set1, bits);
> +        rst_bits(&set0, bits);
> +    }
> +
> +    /*
> +     *  Bit 3 - WDE: Watchdog System Reset Enable
> +     *  WDE is overridden by WDRF in MCUSR. This means that WDE is always set
> +     *  when WDRF is set. To clear WDE, WDRF must be cleared first. This
> +     *  feature ensures multiple resets during conditions causing failure, and
> +     *  a safe start-up after the failure.
> +     */
> +    cpu_physical_memory_read(MCUSR_ADDR, &mcusr, sizeof(mcusr));
> +    if (mcusr & MCUSR_MASK_WDRF) {
> +        set_bits(&set1, WDTCSR_MASK_WDE);
> +        rst_bits(&set0, WDTCSR_MASK_WDE);
> +    }
> +
> +    /*  update CSR value */
> +    assert((set0 & set1) == 0);
> +
> +    val = wdt->csr;
> +    set_bits(&val, set1);
> +    rst_bits(&val, set0);
> +    wdt->csr = val;
> +    trace_avr_wdt_write(offset, val);
> +    avr_wdt_reset_alarm(wdt);
> +
> +    /*
> +     *  Bit 6 - WDIE: Watchdog Interrupt Enable
> +     *  When this bit is written to one and the I-bit in the Status Register is
> +     *  set, the Watchdog Interrupt is enabled. If WDE is cleared in
> +     *  combination with this setting, the Watchdog Timer is in Interrupt Mode,
> +     *  and the corresponding interrupt is executed if time-out in the Watchdog
> +     *  Timer occurs.
> +     *  If WDE is set, the Watchdog Timer is in Interrupt and System Reset Mode.
> +     *  The first time-out in the Watchdog Timer will set WDIF. Executing the
> +     *  corresponding interrupt vector will clear WDIE and WDIF automatically by
> +     *  hardware (the Watchdog goes to System Reset Mode). This is useful for
> +     *  keeping the Watchdog Timer security while using the interrupt. To stay
> +     *  in Interrupt and System Reset Mode, WDIE must be set after each
> +     *  interrupt. This should however not be done within the interrupt service
> +     *  routine itself, as this might compromise the safety-function of the
> +     *  Watchdog System Reset mode. If the interrupt is not executed before the
> +     *  next time-out, a System Reset will be applied.
> +     */
> +    if ((val & WDTCSR_MASK_WDIE) && (wdt->csr & WDTCSR_MASK_WDIF)) {
> +        avr_wdt_interrupt(opaque);
> +    }
> +}
> +
> +static const MemoryRegionOps avr_wdt_ops = {
> +    .read = avr_wdt_read,
> +    .write = avr_wdt_write,
> +    .endianness = DEVICE_NATIVE_ENDIAN,
> +    .impl = {.max_access_size = 1}
> +};
> +
> +static void avr_wdt_wdr(void *opaque, int irq, int level)
> +{
> +    AVRWatchdogState *wdt = AVR_WDT(opaque);
> +
> +    avr_wdt_reset_alarm(wdt);
> +}
> +
> +static void avr_wdt_init(Object *obj)
> +{
> +    AVRWatchdogState *s = AVR_WDT(obj);
> +
> +    sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
> +
> +    memory_region_init_io(&s->iomem, obj, &avr_wdt_ops,
> +                          s, "avr-wdt", 0xa);
> +
> +    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
> +    qdev_init_gpio_in_named(DEVICE(s), avr_wdt_wdr, "wdr", 1);
> +}
> +
> +static void avr_wdt_realize(DeviceState *dev, Error **errp)
> +{
> +    AVRWatchdogState *s = AVR_WDT(dev);
> +
> +    s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, avr_wdt_interrupt, s);
> +}
> +
> +static void avr_wdt_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->reset = avr_wdt_reset;
> +    dc->realize = avr_wdt_realize;
> +}
> +
> +static const TypeInfo avr_wdt_info = {
> +    .name          = TYPE_AVR_WDT,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(AVRWatchdogState),
> +    .instance_init = avr_wdt_init,
> +    .class_init    = avr_wdt_class_init,
> +};
> +
> +static void avr_wdt_register_types(void)
> +{
> +    type_register_static(&avr_wdt_info);
> +}
> +
> +type_init(avr_wdt_register_types)
> diff --git a/hw/watchdog/meson.build b/hw/watchdog/meson.build
> index 054c403dea..8db2be8317 100644
> --- a/hw/watchdog/meson.build
> +++ b/hw/watchdog/meson.build
> @@ -6,3 +6,5 @@ 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_AVR_WDT', if_true: files('avr_wdt.c'))
> diff --git a/hw/watchdog/trace-events b/hw/watchdog/trace-events
> index 3124ca1f1b..ac14773179 100644
> --- a/hw/watchdog/trace-events
> +++ b/hw/watchdog/trace-events
> @@ -5,3 +5,8 @@ cmsdk_apb_watchdog_read(uint64_t offset, uint64_t data, unsigned size) "CMSDK AP
>   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
> +
> +# avr_wdt.c
> +avr_wdt_read(uint8_t addr, uint8_t value) "wdt read addr:%u value:%u"
> +avr_wdt_write(uint8_t addr, uint8_t value) "wdt write addr:%u value:%u"
> +avr_wdt_interrupt(void) ""
> diff --git a/include/hw/watchdog/avr_wdt.h b/include/hw/watchdog/avr_wdt.h
> new file mode 100644
> index 0000000000..8433663013
> --- /dev/null
> +++ b/include/hw/watchdog/avr_wdt.h
> @@ -0,0 +1,47 @@
> +/*
> + * AVR watchdog
> + *
> + * Copyright (c) 2021 Michael Rolnik
> + *
> + * 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/lgpl-2.1.html>
> + */
> +
> +#ifndef HW_WATCHDOG_AVR_WDT_H
> +#define HW_WATCHDOG_AVR_WDT_H
> +
> +#include "hw/sysbus.h"
> +#include "qemu/timer.h"
> +#include "hw/hw.h"
> +#include "qom/object.h"
> +
> +#define TYPE_AVR_WDT "avr-wdt"
> +OBJECT_DECLARE_SIMPLE_TYPE(AVRWatchdogState, AVR_WDT)
> +
> +struct AVRWatchdogState {
> +    /* <private> */
> +    SysBusDevice parent_obj;
> +
> +    /* <public> */
> +    MemoryRegion iomem;
> +    MemoryRegion imsk_iomem;
> +    MemoryRegion ifr_iomem;
> +    QEMUTimer *timer;
> +    qemu_irq irq;
> +
> +    /* registers */
> +    uint8_t csr;
> +};
> +

You should add vmstate which includes timer and csr. It will allow 
reverse debugging available with icount enabled.

> +#endif /* HW_WATCHDOG_AVR_WDT_H */
> diff --git a/target/avr/cpu.c b/target/avr/cpu.c
> index 0f4596932b..d5eb785833 100644
> --- a/target/avr/cpu.c
> +++ b/target/avr/cpu.c
> @@ -131,6 +131,9 @@ static void avr_cpu_initfn(Object *obj)
>       /* Set the number of interrupts supported by the CPU. */
>       qdev_init_gpio_in(DEVICE(cpu), avr_cpu_set_int,
>                         sizeof(cpu->env.intsrc) * 8);
> +
> +    /* register watchdog timer reset interrupt */
> +    qdev_init_gpio_out_named(DEVICE(cpu), &cpu->wdr, "wdr", 1);
>   }
>   
>   static ObjectClass *avr_cpu_class_by_name(const char *cpu_model)
> diff --git a/target/avr/cpu.h b/target/avr/cpu.h
> index d148e8c75a..f8f5641c8b 100644
> --- a/target/avr/cpu.h
> +++ b/target/avr/cpu.h
> @@ -152,6 +152,7 @@ typedef struct AVRCPU {
>   
>       CPUNegativeOffsetState neg;
>       CPUAVRState env;
> +    qemu_irq wdr; /* reset WDT */
>   } AVRCPU;
>   
>   extern const struct VMStateDescription vms_avr_cpu;
> diff --git a/target/avr/helper.c b/target/avr/helper.c
> index 35e1019594..dd88057e5f 100644
> --- a/target/avr/helper.c
> +++ b/target/avr/helper.c
> @@ -24,6 +24,7 @@
>   #include "exec/exec-all.h"
>   #include "exec/address-spaces.h"
>   #include "exec/helper-proto.h"
> +#include "hw/irq.h"
>   
>   bool avr_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
>   {
> @@ -188,11 +189,9 @@ void helper_break(CPUAVRState *env)
>   
>   void helper_wdr(CPUAVRState *env)
>   {
> -    CPUState *cs = env_cpu(env);
> +    AVRCPU *cpu = env_archcpu(env);
>   
> -    /* WD is not implemented yet, placeholder */
> -    cs->exception_index = EXCP_DEBUG;
> -    cpu_loop_exit(cs);
> +    qemu_set_irq(cpu->wdr, 1);
>   }
>   
>   /*
> diff --git a/target/avr/translate.c b/target/avr/translate.c
> index 850c5941d9..b7b716f4a0 100644
> --- a/target/avr/translate.c
> +++ b/target/avr/translate.c
> @@ -1411,6 +1411,9 @@ static bool trans_SBIC(DisasContext *ctx, arg_SBIC *a)
>   {
>       TCGv temp = tcg_const_i32(a->reg);
>   
> +    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> +        gen_io_start();
> +    }
>       gen_helper_inb(temp, cpu_env, temp);
>       tcg_gen_andi_tl(temp, temp, 1 << a->bit);
>       ctx->skip_cond = TCG_COND_EQ;

Translation should stop at inb-related instructions in icount mode, 
because icount is valid only at the end of the block. Reading it in the 
middle makes execution non-deterministic.

> @@ -1429,6 +1432,9 @@ static bool trans_SBIS(DisasContext *ctx, arg_SBIS *a)
>   {
>       TCGv temp = tcg_const_i32(a->reg);
>   
> +    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> +        gen_io_start();
> +    }
>       gen_helper_inb(temp, cpu_env, temp);
>       tcg_gen_andi_tl(temp, temp, 1 << a->bit);
>       ctx->skip_cond = TCG_COND_NE;
> @@ -1611,6 +1617,9 @@ static TCGv gen_get_zaddr(void)
>   static void gen_data_store(DisasContext *ctx, TCGv data, TCGv addr)
>   {
>       if (ctx->tb->flags & TB_FLAGS_FULL_ACCESS) {
> +        if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> +            gen_io_start();
> +        }
>           gen_helper_fullwr(cpu_env, data, addr);
>       } else {
>           tcg_gen_qemu_st8(data, addr, MMU_DATA_IDX); /* mem[addr] = data */
> @@ -1620,6 +1629,9 @@ static void gen_data_store(DisasContext *ctx, TCGv data, TCGv addr)
>   static void gen_data_load(DisasContext *ctx, TCGv data, TCGv addr)
>   {
>       if (ctx->tb->flags & TB_FLAGS_FULL_ACCESS) {
> +        if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> +            gen_io_start();
> +        }
>           gen_helper_fullrd(data, cpu_env, addr);
>       } else {
>           tcg_gen_qemu_ld8u(data, addr, MMU_DATA_IDX); /* data = mem[addr] */
> @@ -2325,6 +2337,9 @@ static bool trans_IN(DisasContext *ctx, arg_IN *a)
>       TCGv Rd = cpu_r[a->rd];
>       TCGv port = tcg_const_i32(a->imm);
>   
> +    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> +        gen_io_start();
> +    }
>       gen_helper_inb(Rd, cpu_env, port);
>   
>       tcg_temp_free_i32(port);
> @@ -2341,6 +2356,9 @@ static bool trans_OUT(DisasContext *ctx, arg_OUT *a)
>       TCGv Rd = cpu_r[a->rd];
>       TCGv port = tcg_const_i32(a->imm);
>   
> +    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> +        gen_io_start();
> +    }
>       gen_helper_outb(cpu_env, port, Rd);
>   
>       tcg_temp_free_i32(port);
> @@ -2641,6 +2659,9 @@ static bool trans_SBI(DisasContext *ctx, arg_SBI *a)
>       TCGv data = tcg_temp_new_i32();
>       TCGv port = tcg_const_i32(a->reg);
>   
> +    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> +        gen_io_start();
> +    }
>       gen_helper_inb(data, cpu_env, port);
>       tcg_gen_ori_tl(data, data, 1 << a->bit);
>       gen_helper_outb(cpu_env, port, data);
> @@ -2660,6 +2681,9 @@ static bool trans_CBI(DisasContext *ctx, arg_CBI *a)
>       TCGv data = tcg_temp_new_i32();
>       TCGv port = tcg_const_i32(a->reg);
>   
> +    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> +        gen_io_start();
> +    }
>       gen_helper_inb(data, cpu_env, port);
>       tcg_gen_andi_tl(data, data, ~(1 << a->bit));
>       gen_helper_outb(cpu_env, port, data);
> @@ -2830,6 +2854,9 @@ static bool trans_SLEEP(DisasContext *ctx, arg_SLEEP *a)
>    */
>   static bool trans_WDR(DisasContext *ctx, arg_WDR *a)
>   {
> +    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> +        gen_io_start();
> +    }
>       gen_helper_wdr(cpu_env);
>   
>       return true;
> @@ -2919,6 +2946,9 @@ void gen_intermediate_code(CPUState *cs, TranslationBlock *tb, int max_insns)
>            * This flag is set by ST/LD instruction we will regenerate it ONLY
>            * with mem/cpu memory access instead of mem access
>            */
> +        if (tb_cflags(tb) & CF_USE_ICOUNT) {
> +            gen_io_start();
> +        }
>           max_insns = 1;
>       }
>       if (ctx.singlestep) {
> @@ -2955,6 +2985,10 @@ void gen_intermediate_code(CPUState *cs, TranslationBlock *tb, int max_insns)
>               goto done_generating;
>           }
>   
> +        if (num_insns == max_insns && (tb_cflags(tb) & CF_LAST_IO)) {
> +            gen_io_start();
> +        }
> +
>           /* Conditionally skip the next instruction, if indicated.  */
>           if (ctx.skip_cond != TCG_COND_NEVER) {
>               skip_label = gen_new_label();
> @@ -2998,10 +3032,6 @@ void gen_intermediate_code(CPUState *cs, TranslationBlock *tb, int max_insns)
>                && (ctx.npc - pc_start) * 2 < TARGET_PAGE_SIZE - 4
>                && !tcg_op_buf_full());
>   
> -    if (tb->cflags & CF_LAST_IO) {
> -        gen_io_end();
> -    }
> -
>       bool nonconst_skip = canonicalize_skip(&ctx);
>   
>       switch (ctx.bstate) {
> 



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [RFC 1/1] Implement AVR watchdog timer
  2021-05-13 12:27   ` Pavel Dovgalyuk
@ 2021-05-14  7:20     ` Michael Rolnik
  0 siblings, 0 replies; 9+ messages in thread
From: Michael Rolnik @ 2021-05-14  7:20 UTC (permalink / raw)
  To: Pavel Dovgalyuk
  Cc: Joaquin de Andres, Fred Konrad, Richard Henderson,
	QEMU Developers, Philippe Mathieu-Daudé

[-- Attachment #1: Type: text/plain, Size: 27121 bytes --]

Ok, thanks.

Sent from my cell phone, please ignore typos

On Thu, May 13, 2021, 3:27 PM Pavel Dovgalyuk <pavel.dovgalyuk@ispras.ru>
wrote:

> On 06.05.2021 00:18, Michael Rolnik wrote:
> > Signed-off-by: Michael Rolnik <mrolnik@gmail.com>
> > ---
> >   MAINTAINERS                   |   2 +
> >   hw/avr/Kconfig                |   1 +
> >   hw/avr/atmega.c               |  15 +-
> >   hw/avr/atmega.h               |   2 +
> >   hw/watchdog/Kconfig           |   3 +
> >   hw/watchdog/avr_wdt.c         | 274 ++++++++++++++++++++++++++++++++++
> >   hw/watchdog/meson.build       |   2 +
> >   hw/watchdog/trace-events      |   5 +
> >   include/hw/watchdog/avr_wdt.h |  47 ++++++
> >   target/avr/cpu.c              |   3 +
> >   target/avr/cpu.h              |   1 +
> >   target/avr/helper.c           |   7 +-
> >   target/avr/translate.c        |  38 ++++-
> >   13 files changed, 391 insertions(+), 9 deletions(-)
> >   create mode 100644 hw/watchdog/avr_wdt.c
> >   create mode 100644 include/hw/watchdog/avr_wdt.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 4c05ff8bba..e1fce736d2 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -1052,6 +1052,8 @@ F: include/hw/timer/avr_timer16.h
> >   F: hw/timer/avr_timer16.c
> >   F: include/hw/misc/avr_power.h
> >   F: hw/misc/avr_power.c
> > +F: include/hw/watchdog/avr_wdt.h
> > +F: hw/watchdog/avr_wdt.c
> >
> >   Arduino
> >   M: Philippe Mathieu-Daudé <f4bug@amsat.org>
> > diff --git a/hw/avr/Kconfig b/hw/avr/Kconfig
> > index d31298c3cc..9939e4902f 100644
> > --- a/hw/avr/Kconfig
> > +++ b/hw/avr/Kconfig
> > @@ -3,6 +3,7 @@ config AVR_ATMEGA_MCU
> >       select AVR_TIMER16
> >       select AVR_USART
> >       select AVR_POWER
> > +    select AVR_WDT
> >
> >   config ARDUINO
> >       select AVR_ATMEGA_MCU
> > diff --git a/hw/avr/atmega.c b/hw/avr/atmega.c
> > index 44c6afebbb..31ceb1c21c 100644
> > --- a/hw/avr/atmega.c
> > +++ b/hw/avr/atmega.c
> > @@ -28,6 +28,7 @@ enum AtmegaPeripheral {
> >       GPIOG, GPIOH, GPIOI, GPIOJ, GPIOK, GPIOL,
> >       USART0, USART1, USART2, USART3,
> >       TIMER0, TIMER1, TIMER2, TIMER3, TIMER4, TIMER5,
> > +    WDT,
> >       PERIFMAX
> >   };
> >
> > @@ -75,6 +76,7 @@ static const peripheral_cfg dev168_328[PERIFMAX] = {
> >       [GPIOD]         = {  0x29 },
> >       [GPIOC]         = {  0x26 },
> >       [GPIOB]         = {  0x23 },
> > +    [WDT]           = {  0x60 },
> >   }, dev1280_2560[PERIFMAX] = {
> >       [USART3]        = { 0x130, POWER1, 2 },
> >       [TIMER5]        = { 0x120, POWER1, 5, 0x73, 0x3a, true },
> > @@ -99,6 +101,7 @@ static const peripheral_cfg dev168_328[PERIFMAX] = {
> >       [GPIOC]         = {  0x26 },
> >       [GPIOB]         = {  0x23 },
> >       [GPIOA]         = {  0x20 },
> > +    [WDT]           = {  0x60 },
> >   };
> >
> >   enum AtmegaIrq {
> > @@ -118,6 +121,7 @@ enum AtmegaIrq {
> >           TIMER4_COMPC_IRQ, TIMER4_OVF_IRQ,
> >       TIMER5_CAPT_IRQ, TIMER5_COMPA_IRQ, TIMER5_COMPB_IRQ,
> >           TIMER5_COMPC_IRQ, TIMER5_OVF_IRQ,
> > +    WATCHDOG_TIMER_IRQ,
> >       IRQ_COUNT
> >   };
> >
> > @@ -133,6 +137,7 @@ enum AtmegaIrq {
> >   #define TIMER_OVF_IRQ(n)    (n * TIMER_IRQ_COUNT + TIMER0_OVF_IRQ)
> >
> >   static const uint8_t irq168_328[IRQ_COUNT] = {
> > +    [WATCHDOG_TIMER_IRQ]    = 7,
> >       [TIMER2_COMPA_IRQ]      = 8,
> >       [TIMER2_COMPB_IRQ]      = 9,
> >       [TIMER2_OVF_IRQ]        = 10,
> > @@ -147,6 +152,7 @@ static const uint8_t irq168_328[IRQ_COUNT] = {
> >       [USART0_DRE_IRQ]        = 20,
> >       [USART0_TXC_IRQ]        = 21,
> >   }, irq1280_2560[IRQ_COUNT] = {
> > +    [WATCHDOG_TIMER_IRQ]    = 13,
> >       [TIMER2_COMPA_IRQ]      = 14,
> >       [TIMER2_COMPB_IRQ]      = 15,
> >       [TIMER2_OVF_IRQ]        = 16,
> > @@ -344,10 +350,17 @@ static void atmega_realize(DeviceState *dev, Error
> **errp)
> >           g_free(devname);
> >       }
> >
> > +    /* Watchdog Timer */
> > +    object_initialize_child(OBJECT(dev), "wdt", &s->wdt, TYPE_AVR_WDT);
> > +    sysbus_realize(SYS_BUS_DEVICE(&s->wdt), &error_abort);
> > +    sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt), 0,
> > +                    OFFSET_DATA + mc->dev[WDT].addr);
> > +    qdev_connect_gpio_out_named(cpudev, "wdr", 0,
> > +                    qdev_get_gpio_in_named(DEVICE(&s->wdt), "wdr", 0));
> > +
> >       create_unimplemented_device("avr-twi",          OFFSET_DATA +
> 0x0b8, 6);
> >       create_unimplemented_device("avr-adc",          OFFSET_DATA +
> 0x078, 8);
> >       create_unimplemented_device("avr-ext-mem-ctrl", OFFSET_DATA +
> 0x074, 2);
> > -    create_unimplemented_device("avr-watchdog",     OFFSET_DATA +
> 0x060, 1);
> >       create_unimplemented_device("avr-spi",          OFFSET_DATA +
> 0x04c, 3);
> >       create_unimplemented_device("avr-eeprom",       OFFSET_DATA +
> 0x03f, 3);
> >   }
> > diff --git a/hw/avr/atmega.h b/hw/avr/atmega.h
> > index a99ee15c7e..60bbd44bdd 100644
> > --- a/hw/avr/atmega.h
> > +++ b/hw/avr/atmega.h
> > @@ -13,6 +13,7 @@
> >
> >   #include "hw/char/avr_usart.h"
> >   #include "hw/timer/avr_timer16.h"
> > +#include "hw/watchdog/avr_wdt.h"
> >   #include "hw/misc/avr_power.h"
> >   #include "target/avr/cpu.h"
> >   #include "qom/object.h"
> > @@ -45,6 +46,7 @@ struct AtmegaMcuState {
> >       AVRMaskState pwr[POWER_MAX];
> >       AVRUsartState usart[USART_MAX];
> >       AVRTimer16State timer[TIMER_MAX];
> > +    AVRWatchdogState wdt;
> >       uint64_t xtal_freq_hz;
> >   };
> >
> > diff --git a/hw/watchdog/Kconfig b/hw/watchdog/Kconfig
> > index 66e1d029e3..e0f89d2fe0 100644
> > --- a/hw/watchdog/Kconfig
> > +++ b/hw/watchdog/Kconfig
> > @@ -20,3 +20,6 @@ config WDT_IMX2
> >
> >   config WDT_SBSA
> >       bool
> > +
> > +config AVR_WDT
> > +    bool
> > diff --git a/hw/watchdog/avr_wdt.c b/hw/watchdog/avr_wdt.c
> > new file mode 100644
> > index 0000000000..cbd6457c8b
> > --- /dev/null
> > +++ b/hw/watchdog/avr_wdt.c
> > @@ -0,0 +1,274 @@
> > +/*
> > + * AVR watchdog
> > + *
> > + * Copyright (c) 2021 Michael Rolnik
> > + *
> > + * 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/lgpl-2.1.html>
> > + */
> > +
> > +#include "qemu/osdep.h"
> > +#include "qapi/error.h"
> > +#include "qemu/log.h"
> > +#include "hw/irq.h"
> > +#include "hw/watchdog/avr_wdt.h"
> > +#include "trace.h"
> > +#include "target/avr/cpu.h"
> > +#include "sysemu/runstate.h"
> > +
> > +/* Field masks */
> > +#define WDTCSR_MASK_WDP0    0x01
> > +#define WDTCSR_MASK_WDP1    0x02
> > +#define WDTCSR_MASK_WDP2    0x04
> > +#define WDTCSR_MASK_WDE     0x08
> > +#define WDTCSR_MASK_WDCE    0x10
> > +#define WDTCSR_MASK_WDP3    0x20
> > +#define WDTCSR_MASK_WDIE    0x40
> > +#define WDTCSR_MASK_WDIF    0x80
> > +
> > +#define WDTCSR_SHFT_WDP0    0x00
> > +#define WDTCSR_SHFT_WDP1    0x01
> > +#define WDTCSR_SHFT_WDP2    0x02
> > +#define WDTCSR_SHFT_WDE     0x03
> > +#define WDTCSR_SHFT_WDCE    0x04
> > +#define WDTCSR_SHFT_WDP3    0x05
> > +#define WDTCSR_SHFT_WDIE    0x06
> > +#define WDTCSR_SHFT_WDIF    0x07
> > +
> > +#define MCUSR_MASK_WDRF     0x04
> > +#define MCUSR_ADDR      (OFFSET_DATA + 0x55)
> > +
> > +/* Helper macros */
> > +#define WDP0(csr)       ((csr & WDTCSR_MASK_WDP0) >> WDTCSR_SHFT_WDP0)
> > +#define WDP1(csr)       ((csr & WDTCSR_MASK_WDP1) >> WDTCSR_SHFT_WDP1)
> > +#define WDP2(csr)       ((csr & WDTCSR_MASK_WDP2) >> WDTCSR_SHFT_WDP2)
> > +#define WDP3(csr)       ((csr & WDTCSR_MASK_WDP3) >> WDTCSR_SHFT_WDP3)
> > +#define WDP(csr)        ((WDP3(csr) << 3) | (WDP2(csr) << 2) | \
> > +                         (WDP1(csr) << 1) | (WDP0(csr) << 0))
> > +#define WDIE(csr)       ((csr & WDTCSR_MASK_WDIE) >> WDTCSR_SHFT_WDIE)
> > +#define WDE(csr)        ((csr & WDTCSR_MASK_WDE) >> WDTCSR_SHFT_WDE)
> > +#define WCE(csr)        ((csr & WDTCSR_MASK_WCE) >> WDTCSR_SHFT_WCE)
> > +
> > +#define DB_PRINT(fmt, args...) /* Nothing */
> > +
> > +#define MS2NS(n)        ((n) * 1000000ull)
>
> I think there is SCALE_MS define for that.
>
> > +
> > +static void set_bits(uint8_t *addr, uint8_t bits)
> > +{
> > +    *addr |= bits;
> > +}
> > +
> > +static void rst_bits(uint8_t *addr, uint8_t bits)
> > +{
> > +    *addr &= ~bits;
> > +}
> > +
> > +static void avr_wdt_reset_alarm(AVRWatchdogState *wdt)
> > +{
> > +    uint32_t csr = wdt->csr;
> > +    int wdp = WDP(csr);
> > +
> > +    if (WDIE(csr) == 0 && WDE(csr) == 0) {
> > +        /* watchdog is stopped */
> > +        return;
> > +    }
> > +
> > +    timer_mod_ns(wdt->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
> > +            (MS2NS(15) << wdp));
> > +}
> > +
> > +static void avr_wdt_interrupt(void *opaque)
> > +{
> > +    AVRWatchdogState *wdt = opaque;
> > +    int8_t csr = wdt->csr;
> > +
> > +    if (WDIE(csr) == 1) {
> > +        /* Interrupt Mode */
> > +        set_bits(&wdt->csr, WDTCSR_MASK_WDIF);
> > +        qemu_set_irq(wdt->irq, 1);
> > +        trace_avr_wdt_interrupt();
> > +        rst_bits(&wdt->csr, WDTCSR_MASK_WDIF | WDTCSR_MASK_WDIE);
> > +    }
> > +
> > +    if (WDE(csr) == 1) {
> > +        /* System Reset Mode */
> > +        qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
> > +    }
> > +
> > +    avr_wdt_reset_alarm(wdt);
> > +}
> > +
> > +static void avr_wdt_reset(DeviceState *dev)
> > +{
> > +    AVRWatchdogState *wdt = AVR_WDT(dev);
> > +
> > +    wdt->csr = 0;
> > +    qemu_set_irq(wdt->irq, 0);
> > +    avr_wdt_reset_alarm(wdt);
> > +}
> > +
> > +static uint64_t avr_wdt_read(void *opaque, hwaddr offset, unsigned size)
> > +{
> > +    assert(size == 1);
> > +    AVRWatchdogState *wdt = opaque;
> > +    uint8_t retval = wdt->csr;
> > +
> > +    trace_avr_wdt_read(offset, retval);
> > +
> > +    return (uint64_t)retval;
> > +}
> > +
> > +static void avr_wdt_write(void *opaque, hwaddr offset,
> > +                              uint64_t val64, unsigned size)
> > +{
> > +    assert(size == 1);
> > +    AVRWatchdogState *wdt = opaque;
> > +    uint8_t val = (uint8_t)val64;
> > +    uint8_t set1 = val; /* bits that should be set to 1 */
> > +    uint8_t set0 = ~val;/* bits that should be set to 0 */
> > +    uint8_t mcusr = 0;
> > +
> > +    /*
> > +     *  Bit 7 - WDIF: Watchdog Interrupt Flag
> > +     *  This bit is set when a time-out occurs in the Watchdog Timer
> and the
> > +     *  Watchdog Timer is configured for interrupt. WDIF is cleared by
> hardware
> > +     *  when executing the corresponding interrupt handling vector.
> > +     *  Alternatively, WDIF is cleared by writing a logic one to the
> flag.
> > +     *  When the I-bit in SREG and WDIE are set, the Watchdog Time-out
> Interrupt
> > +     *  is executed.
> > +     */
> > +    if (val & WDTCSR_MASK_WDIF) {
> > +        rst_bits(&set1, WDTCSR_MASK_WDIF);  /* don't set 1 */
> > +        set_bits(&set0, WDTCSR_MASK_WDIF);  /* set 0 */
> > +    } else {
> > +        rst_bits(&set1, WDTCSR_MASK_WDIF);  /* don't set 1 */
> > +        rst_bits(&set0, WDTCSR_MASK_WDIF);  /* don't set 0 */
> > +    }
> > +
> > +    /*
> > +     *  Bit 4 - WDCE: Watchdog Change Enable
> > +     *  This bit is used in timed sequences for changing WDE and
> prescaler
> > +     *  bits. To clear the WDE bit, and/or change the prescaler bits,
> > +     *  WDCE must be set.
> > +     *  Once written to one, hardware will clear WDCE after four clock
> cycles.
> > +     */
> > +    if (!(val & WDTCSR_MASK_WDCE)) {
> > +        uint8_t bits = WDTCSR_MASK_WDE | WDTCSR_MASK_WDP0 |
> WDTCSR_MASK_WDP1 |
> > +                       WDTCSR_MASK_WDP2 | WDTCSR_MASK_WDP3;
> > +        rst_bits(&set1, bits);
> > +        rst_bits(&set0, bits);
> > +    }
> > +
> > +    /*
> > +     *  Bit 3 - WDE: Watchdog System Reset Enable
> > +     *  WDE is overridden by WDRF in MCUSR. This means that WDE is
> always set
> > +     *  when WDRF is set. To clear WDE, WDRF must be cleared first. This
> > +     *  feature ensures multiple resets during conditions causing
> failure, and
> > +     *  a safe start-up after the failure.
> > +     */
> > +    cpu_physical_memory_read(MCUSR_ADDR, &mcusr, sizeof(mcusr));
> > +    if (mcusr & MCUSR_MASK_WDRF) {
> > +        set_bits(&set1, WDTCSR_MASK_WDE);
> > +        rst_bits(&set0, WDTCSR_MASK_WDE);
> > +    }
> > +
> > +    /*  update CSR value */
> > +    assert((set0 & set1) == 0);
> > +
> > +    val = wdt->csr;
> > +    set_bits(&val, set1);
> > +    rst_bits(&val, set0);
> > +    wdt->csr = val;
> > +    trace_avr_wdt_write(offset, val);
> > +    avr_wdt_reset_alarm(wdt);
> > +
> > +    /*
> > +     *  Bit 6 - WDIE: Watchdog Interrupt Enable
> > +     *  When this bit is written to one and the I-bit in the Status
> Register is
> > +     *  set, the Watchdog Interrupt is enabled. If WDE is cleared in
> > +     *  combination with this setting, the Watchdog Timer is in
> Interrupt Mode,
> > +     *  and the corresponding interrupt is executed if time-out in the
> Watchdog
> > +     *  Timer occurs.
> > +     *  If WDE is set, the Watchdog Timer is in Interrupt and System
> Reset Mode.
> > +     *  The first time-out in the Watchdog Timer will set WDIF.
> Executing the
> > +     *  corresponding interrupt vector will clear WDIE and WDIF
> automatically by
> > +     *  hardware (the Watchdog goes to System Reset Mode). This is
> useful for
> > +     *  keeping the Watchdog Timer security while using the interrupt.
> To stay
> > +     *  in Interrupt and System Reset Mode, WDIE must be set after each
> > +     *  interrupt. This should however not be done within the interrupt
> service
> > +     *  routine itself, as this might compromise the safety-function of
> the
> > +     *  Watchdog System Reset mode. If the interrupt is not executed
> before the
> > +     *  next time-out, a System Reset will be applied.
> > +     */
> > +    if ((val & WDTCSR_MASK_WDIE) && (wdt->csr & WDTCSR_MASK_WDIF)) {
> > +        avr_wdt_interrupt(opaque);
> > +    }
> > +}
> > +
> > +static const MemoryRegionOps avr_wdt_ops = {
> > +    .read = avr_wdt_read,
> > +    .write = avr_wdt_write,
> > +    .endianness = DEVICE_NATIVE_ENDIAN,
> > +    .impl = {.max_access_size = 1}
> > +};
> > +
> > +static void avr_wdt_wdr(void *opaque, int irq, int level)
> > +{
> > +    AVRWatchdogState *wdt = AVR_WDT(opaque);
> > +
> > +    avr_wdt_reset_alarm(wdt);
> > +}
> > +
> > +static void avr_wdt_init(Object *obj)
> > +{
> > +    AVRWatchdogState *s = AVR_WDT(obj);
> > +
> > +    sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
> > +
> > +    memory_region_init_io(&s->iomem, obj, &avr_wdt_ops,
> > +                          s, "avr-wdt", 0xa);
> > +
> > +    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
> > +    qdev_init_gpio_in_named(DEVICE(s), avr_wdt_wdr, "wdr", 1);
> > +}
> > +
> > +static void avr_wdt_realize(DeviceState *dev, Error **errp)
> > +{
> > +    AVRWatchdogState *s = AVR_WDT(dev);
> > +
> > +    s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, avr_wdt_interrupt, s);
> > +}
> > +
> > +static void avr_wdt_class_init(ObjectClass *klass, void *data)
> > +{
> > +    DeviceClass *dc = DEVICE_CLASS(klass);
> > +
> > +    dc->reset = avr_wdt_reset;
> > +    dc->realize = avr_wdt_realize;
> > +}
> > +
> > +static const TypeInfo avr_wdt_info = {
> > +    .name          = TYPE_AVR_WDT,
> > +    .parent        = TYPE_SYS_BUS_DEVICE,
> > +    .instance_size = sizeof(AVRWatchdogState),
> > +    .instance_init = avr_wdt_init,
> > +    .class_init    = avr_wdt_class_init,
> > +};
> > +
> > +static void avr_wdt_register_types(void)
> > +{
> > +    type_register_static(&avr_wdt_info);
> > +}
> > +
> > +type_init(avr_wdt_register_types)
> > diff --git a/hw/watchdog/meson.build b/hw/watchdog/meson.build
> > index 054c403dea..8db2be8317 100644
> > --- a/hw/watchdog/meson.build
> > +++ b/hw/watchdog/meson.build
> > @@ -6,3 +6,5 @@ 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_AVR_WDT', if_true: files('avr_wdt.c'))
> > diff --git a/hw/watchdog/trace-events b/hw/watchdog/trace-events
> > index 3124ca1f1b..ac14773179 100644
> > --- a/hw/watchdog/trace-events
> > +++ b/hw/watchdog/trace-events
> > @@ -5,3 +5,8 @@ cmsdk_apb_watchdog_read(uint64_t offset, uint64_t data,
> unsigned size) "CMSDK AP
> >   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
> > +
> > +# avr_wdt.c
> > +avr_wdt_read(uint8_t addr, uint8_t value) "wdt read addr:%u value:%u"
> > +avr_wdt_write(uint8_t addr, uint8_t value) "wdt write addr:%u value:%u"
> > +avr_wdt_interrupt(void) ""
> > diff --git a/include/hw/watchdog/avr_wdt.h
> b/include/hw/watchdog/avr_wdt.h
> > new file mode 100644
> > index 0000000000..8433663013
> > --- /dev/null
> > +++ b/include/hw/watchdog/avr_wdt.h
> > @@ -0,0 +1,47 @@
> > +/*
> > + * AVR watchdog
> > + *
> > + * Copyright (c) 2021 Michael Rolnik
> > + *
> > + * 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/lgpl-2.1.html>
> > + */
> > +
> > +#ifndef HW_WATCHDOG_AVR_WDT_H
> > +#define HW_WATCHDOG_AVR_WDT_H
> > +
> > +#include "hw/sysbus.h"
> > +#include "qemu/timer.h"
> > +#include "hw/hw.h"
> > +#include "qom/object.h"
> > +
> > +#define TYPE_AVR_WDT "avr-wdt"
> > +OBJECT_DECLARE_SIMPLE_TYPE(AVRWatchdogState, AVR_WDT)
> > +
> > +struct AVRWatchdogState {
> > +    /* <private> */
> > +    SysBusDevice parent_obj;
> > +
> > +    /* <public> */
> > +    MemoryRegion iomem;
> > +    MemoryRegion imsk_iomem;
> > +    MemoryRegion ifr_iomem;
> > +    QEMUTimer *timer;
> > +    qemu_irq irq;
> > +
> > +    /* registers */
> > +    uint8_t csr;
> > +};
> > +
>
> You should add vmstate which includes timer and csr. It will allow
> reverse debugging available with icount enabled.
>
> > +#endif /* HW_WATCHDOG_AVR_WDT_H */
> > diff --git a/target/avr/cpu.c b/target/avr/cpu.c
> > index 0f4596932b..d5eb785833 100644
> > --- a/target/avr/cpu.c
> > +++ b/target/avr/cpu.c
> > @@ -131,6 +131,9 @@ static void avr_cpu_initfn(Object *obj)
> >       /* Set the number of interrupts supported by the CPU. */
> >       qdev_init_gpio_in(DEVICE(cpu), avr_cpu_set_int,
> >                         sizeof(cpu->env.intsrc) * 8);
> > +
> > +    /* register watchdog timer reset interrupt */
> > +    qdev_init_gpio_out_named(DEVICE(cpu), &cpu->wdr, "wdr", 1);
> >   }
> >
> >   static ObjectClass *avr_cpu_class_by_name(const char *cpu_model)
> > diff --git a/target/avr/cpu.h b/target/avr/cpu.h
> > index d148e8c75a..f8f5641c8b 100644
> > --- a/target/avr/cpu.h
> > +++ b/target/avr/cpu.h
> > @@ -152,6 +152,7 @@ typedef struct AVRCPU {
> >
> >       CPUNegativeOffsetState neg;
> >       CPUAVRState env;
> > +    qemu_irq wdr; /* reset WDT */
> >   } AVRCPU;
> >
> >   extern const struct VMStateDescription vms_avr_cpu;
> > diff --git a/target/avr/helper.c b/target/avr/helper.c
> > index 35e1019594..dd88057e5f 100644
> > --- a/target/avr/helper.c
> > +++ b/target/avr/helper.c
> > @@ -24,6 +24,7 @@
> >   #include "exec/exec-all.h"
> >   #include "exec/address-spaces.h"
> >   #include "exec/helper-proto.h"
> > +#include "hw/irq.h"
> >
> >   bool avr_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
> >   {
> > @@ -188,11 +189,9 @@ void helper_break(CPUAVRState *env)
> >
> >   void helper_wdr(CPUAVRState *env)
> >   {
> > -    CPUState *cs = env_cpu(env);
> > +    AVRCPU *cpu = env_archcpu(env);
> >
> > -    /* WD is not implemented yet, placeholder */
> > -    cs->exception_index = EXCP_DEBUG;
> > -    cpu_loop_exit(cs);
> > +    qemu_set_irq(cpu->wdr, 1);
> >   }
> >
> >   /*
> > diff --git a/target/avr/translate.c b/target/avr/translate.c
> > index 850c5941d9..b7b716f4a0 100644
> > --- a/target/avr/translate.c
> > +++ b/target/avr/translate.c
> > @@ -1411,6 +1411,9 @@ static bool trans_SBIC(DisasContext *ctx, arg_SBIC
> *a)
> >   {
> >       TCGv temp = tcg_const_i32(a->reg);
> >
> > +    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> > +        gen_io_start();
> > +    }
> >       gen_helper_inb(temp, cpu_env, temp);
> >       tcg_gen_andi_tl(temp, temp, 1 << a->bit);
> >       ctx->skip_cond = TCG_COND_EQ;
>
> Translation should stop at inb-related instructions in icount mode,
> because icount is valid only at the end of the block. Reading it in the
> middle makes execution non-deterministic.
>
> > @@ -1429,6 +1432,9 @@ static bool trans_SBIS(DisasContext *ctx, arg_SBIS
> *a)
> >   {
> >       TCGv temp = tcg_const_i32(a->reg);
> >
> > +    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> > +        gen_io_start();
> > +    }
> >       gen_helper_inb(temp, cpu_env, temp);
> >       tcg_gen_andi_tl(temp, temp, 1 << a->bit);
> >       ctx->skip_cond = TCG_COND_NE;
> > @@ -1611,6 +1617,9 @@ static TCGv gen_get_zaddr(void)
> >   static void gen_data_store(DisasContext *ctx, TCGv data, TCGv addr)
> >   {
> >       if (ctx->tb->flags & TB_FLAGS_FULL_ACCESS) {
> > +        if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> > +            gen_io_start();
> > +        }
> >           gen_helper_fullwr(cpu_env, data, addr);
> >       } else {
> >           tcg_gen_qemu_st8(data, addr, MMU_DATA_IDX); /* mem[addr] =
> data */
> > @@ -1620,6 +1629,9 @@ static void gen_data_store(DisasContext *ctx, TCGv
> data, TCGv addr)
> >   static void gen_data_load(DisasContext *ctx, TCGv data, TCGv addr)
> >   {
> >       if (ctx->tb->flags & TB_FLAGS_FULL_ACCESS) {
> > +        if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> > +            gen_io_start();
> > +        }
> >           gen_helper_fullrd(data, cpu_env, addr);
> >       } else {
> >           tcg_gen_qemu_ld8u(data, addr, MMU_DATA_IDX); /* data =
> mem[addr] */
> > @@ -2325,6 +2337,9 @@ static bool trans_IN(DisasContext *ctx, arg_IN *a)
> >       TCGv Rd = cpu_r[a->rd];
> >       TCGv port = tcg_const_i32(a->imm);
> >
> > +    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> > +        gen_io_start();
> > +    }
> >       gen_helper_inb(Rd, cpu_env, port);
> >
> >       tcg_temp_free_i32(port);
> > @@ -2341,6 +2356,9 @@ static bool trans_OUT(DisasContext *ctx, arg_OUT
> *a)
> >       TCGv Rd = cpu_r[a->rd];
> >       TCGv port = tcg_const_i32(a->imm);
> >
> > +    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> > +        gen_io_start();
> > +    }
> >       gen_helper_outb(cpu_env, port, Rd);
> >
> >       tcg_temp_free_i32(port);
> > @@ -2641,6 +2659,9 @@ static bool trans_SBI(DisasContext *ctx, arg_SBI
> *a)
> >       TCGv data = tcg_temp_new_i32();
> >       TCGv port = tcg_const_i32(a->reg);
> >
> > +    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> > +        gen_io_start();
> > +    }
> >       gen_helper_inb(data, cpu_env, port);
> >       tcg_gen_ori_tl(data, data, 1 << a->bit);
> >       gen_helper_outb(cpu_env, port, data);
> > @@ -2660,6 +2681,9 @@ static bool trans_CBI(DisasContext *ctx, arg_CBI
> *a)
> >       TCGv data = tcg_temp_new_i32();
> >       TCGv port = tcg_const_i32(a->reg);
> >
> > +    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> > +        gen_io_start();
> > +    }
> >       gen_helper_inb(data, cpu_env, port);
> >       tcg_gen_andi_tl(data, data, ~(1 << a->bit));
> >       gen_helper_outb(cpu_env, port, data);
> > @@ -2830,6 +2854,9 @@ static bool trans_SLEEP(DisasContext *ctx,
> arg_SLEEP *a)
> >    */
> >   static bool trans_WDR(DisasContext *ctx, arg_WDR *a)
> >   {
> > +    if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> > +        gen_io_start();
> > +    }
> >       gen_helper_wdr(cpu_env);
> >
> >       return true;
> > @@ -2919,6 +2946,9 @@ void gen_intermediate_code(CPUState *cs,
> TranslationBlock *tb, int max_insns)
> >            * This flag is set by ST/LD instruction we will regenerate it
> ONLY
> >            * with mem/cpu memory access instead of mem access
> >            */
> > +        if (tb_cflags(tb) & CF_USE_ICOUNT) {
> > +            gen_io_start();
> > +        }
> >           max_insns = 1;
> >       }
> >       if (ctx.singlestep) {
> > @@ -2955,6 +2985,10 @@ void gen_intermediate_code(CPUState *cs,
> TranslationBlock *tb, int max_insns)
> >               goto done_generating;
> >           }
> >
> > +        if (num_insns == max_insns && (tb_cflags(tb) & CF_LAST_IO)) {
> > +            gen_io_start();
> > +        }
> > +
> >           /* Conditionally skip the next instruction, if indicated.  */
> >           if (ctx.skip_cond != TCG_COND_NEVER) {
> >               skip_label = gen_new_label();
> > @@ -2998,10 +3032,6 @@ void gen_intermediate_code(CPUState *cs,
> TranslationBlock *tb, int max_insns)
> >                && (ctx.npc - pc_start) * 2 < TARGET_PAGE_SIZE - 4
> >                && !tcg_op_buf_full());
> >
> > -    if (tb->cflags & CF_LAST_IO) {
> > -        gen_io_end();
> > -    }
> > -
> >       bool nonconst_skip = canonicalize_skip(&ctx);
> >
> >       switch (ctx.bstate) {
> >
>
>

[-- Attachment #2: Type: text/html, Size: 33807 bytes --]

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [RFC 1/1] Implement AVR watchdog timer
  2021-05-03 20:08       ` Michael Rolnik
@ 2021-05-15  9:45         ` Philippe Mathieu-Daudé
  0 siblings, 0 replies; 9+ messages in thread
From: Philippe Mathieu-Daudé @ 2021-05-15  9:45 UTC (permalink / raw)
  To: Michael Rolnik, Fred Konrad
  Cc: Alex Bennée, Joaquin de Andres, Richard Henderson,
	QEMU Developers, Pavel Dovgalyuk

+Pavel/Alex

On 5/3/21 10:08 PM, Michael Rolnik wrote:
> Hi all,
> 
> I was about to make icount work. but, there is something I still don't
> understand. I have this code 
> 
> timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, avr_wdt_interrupt, s);
> 
> and then
> 
> void avr_wdt_interrupt(/* some arguments */) {
> #define MS2NS(n)        ((n) * 1000000ull)
>     timer_mod_ns(timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + MS2NS(15));
> }
> 
> when running with --icount shift=0, *avr_wdt_interrupt* is called about
> every 1K instructions, however it should have been called 15M
> instructions as shift=0 makes every instruction to be executed in 1
> virtual ns.
> 
> What am I doing wrong?
> 
> Thank you,
> Michael Rolnik
> 
> 
> On Mon, May 3, 2021 at 4:36 PM Michael Rolnik <mrolnik@gmail.com
> <mailto:mrolnik@gmail.com>> wrote:
> 
>     Hi Fred.
> 
>     1. thanks
>     2. It seems I have forgotten to set those flags.
>     3. 15ms is easy to test 8s will take 533 times longer, so in my case
>     3200 instructions which is totally incorrect. I don't understand why
>     as I program the timer in virtual nanoseconds and not host time.
>     4. I hope Richard could help with icount.
> 
>     best regards,
>     Michael Rolnik
> 
>     On Mon, May 3, 2021 at 4:15 PM Fred Konrad <konrad@adacore.com
>     <mailto:konrad@adacore.com>> wrote:
> 
> 
> 
>         Le 5/2/21 à 10:10 PM, Michael Rolnik a écrit :
>         > Signed-off-by: Michael Rolnik <mrolnik@gmail.com
>         <mailto:mrolnik@gmail.com>>
>         > ---
>         >   hw/avr/Kconfig                |   1 +
>         >   hw/avr/atmega.c               |  15 ++-
>         >   hw/avr/atmega.h               |   2 +
>         >   hw/watchdog/Kconfig           |   3 +
>         >   hw/watchdog/avr_wdt.c         | 190
>         ++++++++++++++++++++++++++++++++++
>         >   hw/watchdog/meson.build       |   2 +
>         >   hw/watchdog/trace-events      |   5 +
>         >   include/hw/watchdog/avr_wdt.h |  47 +++++++++
>         >   target/avr/cpu.c              |   3 +
>         >   target/avr/cpu.h              |   1 +
>         >   target/avr/helper.c           |   7 +-
>         >   11 files changed, 271 insertions(+), 5 deletions(-)
>         >   create mode 100644 hw/watchdog/avr_wdt.c
>         >   create mode 100644 include/hw/watchdog/avr_wdt.h
>         >
>         > diff --git a/hw/avr/Kconfig b/hw/avr/Kconfig
>         > index d31298c3cc..9939e4902f 100644
>         > --- a/hw/avr/Kconfig
>         > +++ b/hw/avr/Kconfig
>         > @@ -3,6 +3,7 @@ config AVR_ATMEGA_MCU
>         >       select AVR_TIMER16
>         >       select AVR_USART
>         >       select AVR_POWER
>         > +    select AVR_WDT
>         >   
>         >   config ARDUINO
>         >       select AVR_ATMEGA_MCU
>         > diff --git a/hw/avr/atmega.c b/hw/avr/atmega.c
>         > index 44c6afebbb..31ceb1c21c 100644
>         > --- a/hw/avr/atmega.c
>         > +++ b/hw/avr/atmega.c
>         > @@ -28,6 +28,7 @@ enum AtmegaPeripheral {
>         >       GPIOG, GPIOH, GPIOI, GPIOJ, GPIOK, GPIOL,
>         >       USART0, USART1, USART2, USART3,
>         >       TIMER0, TIMER1, TIMER2, TIMER3, TIMER4, TIMER5,
>         > +    WDT,
>         >       PERIFMAX
>         >   };
>         >   
>         > @@ -75,6 +76,7 @@ static const peripheral_cfg
>         dev168_328[PERIFMAX] = {
>         >       [GPIOD]         = {  0x29 },
>         >       [GPIOC]         = {  0x26 },
>         >       [GPIOB]         = {  0x23 },
>         > +    [WDT]           = {  0x60 },
>         >   }, dev1280_2560[PERIFMAX] = {
>         >       [USART3]        = { 0x130, POWER1, 2 },
>         >       [TIMER5]        = { 0x120, POWER1, 5, 0x73, 0x3a, true },
>         > @@ -99,6 +101,7 @@ static const peripheral_cfg
>         dev168_328[PERIFMAX] = {
>         >       [GPIOC]         = {  0x26 },
>         >       [GPIOB]         = {  0x23 },
>         >       [GPIOA]         = {  0x20 },
>         > +    [WDT]           = {  0x60 },
>         >   };
>         >   
>         >   enum AtmegaIrq {
>         > @@ -118,6 +121,7 @@ enum AtmegaIrq {
>         >           TIMER4_COMPC_IRQ, TIMER4_OVF_IRQ,
>         >       TIMER5_CAPT_IRQ, TIMER5_COMPA_IRQ, TIMER5_COMPB_IRQ,
>         >           TIMER5_COMPC_IRQ, TIMER5_OVF_IRQ,
>         > +    WATCHDOG_TIMER_IRQ,
>         >       IRQ_COUNT
>         >   };
>         >   
>         > @@ -133,6 +137,7 @@ enum AtmegaIrq {
>         >   #define TIMER_OVF_IRQ(n)    (n * TIMER_IRQ_COUNT +
>         TIMER0_OVF_IRQ)
>         >   
>         >   static const uint8_t irq168_328[IRQ_COUNT] = {
>         > +    [WATCHDOG_TIMER_IRQ]    = 7,
>         >       [TIMER2_COMPA_IRQ]      = 8,
>         >       [TIMER2_COMPB_IRQ]      = 9,
>         >       [TIMER2_OVF_IRQ]        = 10,
>         > @@ -147,6 +152,7 @@ static const uint8_t irq168_328[IRQ_COUNT] = {
>         >       [USART0_DRE_IRQ]        = 20,
>         >       [USART0_TXC_IRQ]        = 21,
>         >   }, irq1280_2560[IRQ_COUNT] = {
>         > +    [WATCHDOG_TIMER_IRQ]    = 13,
>         >       [TIMER2_COMPA_IRQ]      = 14,
>         >       [TIMER2_COMPB_IRQ]      = 15,
>         >       [TIMER2_OVF_IRQ]        = 16,
>         > @@ -344,10 +350,17 @@ static void atmega_realize(DeviceState
>         *dev, Error **errp)
>         >           g_free(devname);
>         >       }
>         >   
>         > +    /* Watchdog Timer */
>         > +    object_initialize_child(OBJECT(dev), "wdt", &s->wdt,
>         TYPE_AVR_WDT);
>         > +    sysbus_realize(SYS_BUS_DEVICE(&s->wdt), &error_abort);
>         > +    sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt), 0,
>         > +                    OFFSET_DATA + mc->dev[WDT].addr);
>         > +    qdev_connect_gpio_out_named(cpudev, "wdr", 0,
>         > +                    qdev_get_gpio_in_named(DEVICE(&s->wdt),
>         "wdr", 0));
>         > +
>         >       create_unimplemented_device("avr-twi",         
>         OFFSET_DATA + 0x0b8, 6);
>         >       create_unimplemented_device("avr-adc",         
>         OFFSET_DATA + 0x078, 8);
>         >       create_unimplemented_device("avr-ext-mem-ctrl",
>         OFFSET_DATA + 0x074, 2);
>         > -    create_unimplemented_device("avr-watchdog",   
>          OFFSET_DATA + 0x060, 1);
>         >       create_unimplemented_device("avr-spi",         
>         OFFSET_DATA + 0x04c, 3);
>         >       create_unimplemented_device("avr-eeprom",     
>          OFFSET_DATA + 0x03f, 3);
>         >   }
>         > diff --git a/hw/avr/atmega.h b/hw/avr/atmega.h
>         > index a99ee15c7e..60bbd44bdd 100644
>         > --- a/hw/avr/atmega.h
>         > +++ b/hw/avr/atmega.h
>         > @@ -13,6 +13,7 @@
>         >   
>         >   #include "hw/char/avr_usart.h"
>         >   #include "hw/timer/avr_timer16.h"
>         > +#include "hw/watchdog/avr_wdt.h"
>         >   #include "hw/misc/avr_power.h"
>         >   #include "target/avr/cpu.h"
>         >   #include "qom/object.h"
>         > @@ -45,6 +46,7 @@ struct AtmegaMcuState {
>         >       AVRMaskState pwr[POWER_MAX];
>         >       AVRUsartState usart[USART_MAX];
>         >       AVRTimer16State timer[TIMER_MAX];
>         > +    AVRWatchdogState wdt;
>         >       uint64_t xtal_freq_hz;
>         >   };
>         >   
>         > diff --git a/hw/watchdog/Kconfig b/hw/watchdog/Kconfig
>         > index 66e1d029e3..e0f89d2fe0 100644
>         > --- a/hw/watchdog/Kconfig
>         > +++ b/hw/watchdog/Kconfig
>         > @@ -20,3 +20,6 @@ config WDT_IMX2
>         >   
>         >   config WDT_SBSA
>         >       bool
>         > +
>         > +config AVR_WDT
>         > +    bool
>         > diff --git a/hw/watchdog/avr_wdt.c b/hw/watchdog/avr_wdt.c
>         > new file mode 100644
>         > index 0000000000..4ce1029a64
>         > --- /dev/null
>         > +++ b/hw/watchdog/avr_wdt.c
>         > @@ -0,0 +1,190 @@
>         > +/*
>         > + * AVR watchdog
>         > + *
>         > + * Copyright (c) 2018 Michael Rolnik
> 
>         2021?
> 
>         > + *
>         > + * 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/lgpl-2.1.html
>         <http://www.gnu.org/licenses/lgpl-2.1.html>>
>         > + */
>         > +
>         > +#include "qemu/osdep.h"
>         > +#include "qapi/error.h"
>         > +#include "qemu/log.h"
>         > +#include "hw/irq.h"
>         > +#include "hw/watchdog/avr_wdt.h"
>         > +#include "trace.h"
>         > +
>         > +/* Field masks */
>         > +#define WDTCSR_MASK_WDP0     0x01
>         > +#define WDTCSR_MASK_WDP1     0x02
>         > +#define WDTCSR_MASK_WDP2     0x04
>         > +#define WDTCSR_MASK_WDE      0x08
>         > +#define WDTCSR_MASK_WCE      0x10
>         > +#define WDTCSR_MASK_WDP3     0x20
>         > +#define WDTCSR_MASK_WDIE     0x40
>         > +#define WDTCSR_MASK_WDIF     0x80
>         > +
>         > +#define WDTCSR_SHFT_WDP0     0x00
>         > +#define WDTCSR_SHFT_WDP1     0x01
>         > +#define WDTCSR_SHFT_WDP2     0x02
>         > +#define WDTCSR_SHFT_WDE      0x03
>         > +#define WDTCSR_SHFT_WCE      0x04
>         > +#define WDTCSR_SHFT_WDP3     0x05
>         > +#define WDTCSR_SHFT_WDIE     0x06
>         > +#define WDTCSR_SHFT_WDIF     0x07
>         > +
>         > +/* Helper macros */
>         > +#define WDP0(csr)       ((csr & WDTCSR_MASK_WDP0) >>
>         WDTCSR_SHFT_WDP0)
>         > +#define WDP1(csr)       ((csr & WDTCSR_MASK_WDP1) >>
>         WDTCSR_SHFT_WDP1)
>         > +#define WDP2(csr)       ((csr & WDTCSR_MASK_WDP2) >>
>         WDTCSR_SHFT_WDP2)
>         > +#define WDP3(csr)       ((csr & WDTCSR_MASK_WDP3) >>
>         WDTCSR_SHFT_WDP3)
>         > +#define WDP(csr)        ((WDP3(csr) << 3) | (WDP2(csr) << 2) | \
>         > +                         (WDP1(csr) << 1) | (WDP0(csr) << 0))
>         > +#define WDIE(csr)       ((csr & WDTCSR_MASK_WDIE) >>
>         WDTCSR_SHFT_WDIE)
>         > +#define WDE(csr)        ((csr & WDTCSR_MASK_WDE) >>
>         WDTCSR_SHFT_WDE)
>         > +#define WCE(csr)        ((csr & WDTCSR_MASK_WCE) >>
>         WDTCSR_SHFT_WCE)
>         > +
>         > +#define DB_PRINT(fmt, args...) /* Nothing */
>         > +
>         > +#define MS2NS(n)        ((n) * 1000000ull)
>         > +
>         > +static void avr_wdt_reset_alarm(AVRWatchdogState *wdt)
>         > +{
>         > +    uint32_t csr = wdt->csr;
>         > +    int wdp = WDP(csr);
>         > +    assert(wdp <= 9);
> 
>         Maybe qemu_log(..) instead and pick a default value?
> 
>         > +
>         > +    if (WDIE(csr) == 0 && WDE(csr) == 0) {
>         > +        /* watchdog is stopped */
>         > +        return;
>         > +    }
>         > +
>         > +    timer_mod_ns(wdt->timer,
>         qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
>         > +            (MS2NS(15) << wdp));
>         > +}
>         > +
>         > +static void avr_wdt_interrupt(void *opaque)
>         > +{
>         > +    AVRWatchdogState *wdt = opaque;
>         > +    int8_t csr = wdt->csr;
>         > +
>         > +    if (WDE(csr) == 0 && WDIE(csr) == 0) {
>         > +        /* Stopped */
>         > +
>         > +    } else if (WDE(csr) == 0 && WDIE(csr) == 1) {
>         > +        /* Interrupt Mode */
>         > +        wdt->csr |= WDTCSR_MASK_WDIF;
>         > +        qemu_set_irq(wdt->irq, 1);
>         > +        trace_avr_wdt_interrupt();
>         > +    } else if (WDE(csr) == 1 && WDIE(csr) == 0) {
>         > +        /* System Reset Mode */
> 
>                 qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
> 
>         +/- setting the MCUSR, WDRF flags would be nice.
> 
>         > +    } else if (WDE(csr) == 1 && WDIE(csr) == 1) {
>         > +        /* Interrupt and System Reset Mode */
>         > +        wdt->csr |= WDTCSR_MASK_WDIF;
>         > +        qemu_set_irq(wdt->irq, 1);
>         > +        trace_avr_wdt_interrupt();
>         > +    }
>         > +
>         > +    avr_wdt_reset_alarm(wdt);
>         > +}
>         > +
>         > +static void avr_wdt_reset(DeviceState *dev)
>         > +{
>         > +    AVRWatchdogState *wdt = AVR_WDT(dev);
>         > +
>         > +    wdt->csr = 0;
>         > +    qemu_set_irq(wdt->irq, 0);
>         > +    avr_wdt_reset_alarm(wdt);
>         > +}
>         > +
>         > +static uint64_t avr_wdt_read(void *opaque, hwaddr offset,
>         unsigned size)
>         > +{
>         > +    assert(size == 1);
>         > +    AVRWatchdogState *wdt = opaque;
>         > +    uint8_t retval = wdt->csr;
>         > +
>         > +    trace_avr_wdt_read(offset, retval);
>         > +
>         > +    return (uint64_t)retval;
>         > +}
>         > +
>         > +static void avr_wdt_write(void *opaque, hwaddr offset,
>         > +                              uint64_t val64, unsigned size)
>         > +{
>         > +    assert(size == 1);
>         > +    AVRWatchdogState *wdt = opaque;
>         > +    uint8_t val8 = (uint8_t)val64;
>         > +
>         > +    trace_avr_wdt_write(offset, val8);
>         > +
>         > +    wdt->csr = val8;
>         > +    avr_wdt_reset_alarm(wdt);
>         > +}
>         > +
>         > +static const MemoryRegionOps avr_wdt_ops = {
>         > +    .read = avr_wdt_read,
>         > +    .write = avr_wdt_write,
>         > +    .endianness = DEVICE_NATIVE_ENDIAN,
>         > +    .impl = {.max_access_size = 1}
>         > +};
>         > +
>         > +static void avr_wdt_wdr(void *opaque, int irq, int level)
>         > +{
>         > +    AVRWatchdogState *wdt = AVR_WDT(opaque);
>         > +
>         > +    avr_wdt_reset_alarm(wdt);
>         > +}
>         > +
>         > +static void avr_wdt_init(Object *obj)
>         > +{
>         > +    AVRWatchdogState *s = AVR_WDT(obj);
>         > +
>         > +    sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
>         > +
>         > +    memory_region_init_io(&s->iomem, obj, &avr_wdt_ops,
>         > +                          s, "avr-wdt", 0xa);
>         > +
>         > +    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
>         > +    qdev_init_gpio_in_named(DEVICE(s), avr_wdt_wdr, "wdr", 1);
>         > +}
>         > +
>         > +static void avr_wdt_realize(DeviceState *dev, Error **errp)
>         > +{
>         > +    AVRWatchdogState *s = AVR_WDT(dev);
>         > +
>         > +    s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
>         avr_wdt_interrupt, s);
>         > +}
>         > +
>         > +static void avr_wdt_class_init(ObjectClass *klass, void *data)
>         > +{
>         > +    DeviceClass *dc = DEVICE_CLASS(klass);
>         > +
>         > +    dc->reset = avr_wdt_reset;
>         > +    dc->realize = avr_wdt_realize;
>         > +}
>         > +
>         > +static const TypeInfo avr_wdt_info = {
>         > +    .name          = TYPE_AVR_WDT,
>         > +    .parent        = TYPE_SYS_BUS_DEVICE,
>         > +    .instance_size = sizeof(AVRWatchdogState),
>         > +    .instance_init = avr_wdt_init,
>         > +    .class_init    = avr_wdt_class_init,
>         > +};
>         > +
>         > +static void avr_wdt_register_types(void)
>         > +{
>         > +    type_register_static(&avr_wdt_info);
>         > +}
>         > +
>         > +type_init(avr_wdt_register_types)
>         > diff --git a/hw/watchdog/meson.build b/hw/watchdog/meson.build
>         > index 054c403dea..8db2be8317 100644
>         > --- a/hw/watchdog/meson.build
>         > +++ b/hw/watchdog/meson.build
>         > @@ -6,3 +6,5 @@ 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_AVR_WDT', if_true:
>         files('avr_wdt.c'))
>         > diff --git a/hw/watchdog/trace-events b/hw/watchdog/trace-events
>         > index 3124ca1f1b..ac14773179 100644
>         > --- a/hw/watchdog/trace-events
>         > +++ b/hw/watchdog/trace-events
>         > @@ -5,3 +5,8 @@ cmsdk_apb_watchdog_read(uint64_t offset,
>         uint64_t data, unsigned size) "CMSDK AP
>         >   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
>         > +
>         > +# avr_wdt.c
>         > +avr_wdt_read(uint8_t addr, uint8_t value) "wdt read addr:%u
>         value:%u"
>         > +avr_wdt_write(uint8_t addr, uint8_t value) "wdt write addr:%u
>         value:%u"
>         > +avr_wdt_interrupt(void) ""
>         > diff --git a/include/hw/watchdog/avr_wdt.h
>         b/include/hw/watchdog/avr_wdt.h
>         > new file mode 100644
>         > index 0000000000..2679e8f2a6
>         > --- /dev/null
>         > +++ b/include/hw/watchdog/avr_wdt.h
>         > @@ -0,0 +1,47 @@
>         > +/*
>         > + * AVR 16-bit timer
> 
>         AVR Watchdog?
> 
>         > + *
>         > + * Copyright (c) 2021 Michael Rolnik
>         > + *
>         > + * 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/lgpl-2.1.html
>         <http://www.gnu.org/licenses/lgpl-2.1.html>>
>         > + */
>         > +
>         > +#ifndef HW_WATCHDOG_AVR_WDT_H
>         > +#define HW_WATCHDOG_AVR_WDT_H
>         > +
>         > +#include "hw/sysbus.h"
>         > +#include "qemu/timer.h"
>         > +#include "hw/hw.h"
>         > +#include "qom/object.h"
>         > +
>         > +#define TYPE_AVR_WDT "avr-wdt"
>         > +OBJECT_DECLARE_SIMPLE_TYPE(AVRWatchdogState, AVR_WDT)
>         > +
>         > +struct AVRWatchdogState {
>         > +    /* <private> */
>         > +    SysBusDevice parent_obj;
>         > +
>         > +    /* <public> */
>         > +    MemoryRegion iomem;
>         > +    MemoryRegion imsk_iomem;
>         > +    MemoryRegion ifr_iomem;
>         > +    QEMUTimer *timer;
>         > +    qemu_irq irq;
>         > +
>         > +    /* registers */
>         > +    uint8_t csr;
>         > +};
>         > +
>         > +#endif /* HW_WATCHDOG_AVR_WDT_H */
>         > diff --git a/target/avr/cpu.c b/target/avr/cpu.c
>         > index 0f4596932b..d5eb785833 100644
>         > --- a/target/avr/cpu.c
>         > +++ b/target/avr/cpu.c
>         > @@ -131,6 +131,9 @@ static void avr_cpu_initfn(Object *obj)
>         >       /* Set the number of interrupts supported by the CPU. */
>         >       qdev_init_gpio_in(DEVICE(cpu), avr_cpu_set_int,
>         >                         sizeof(cpu->env.intsrc) * 8);
>         > +
>         > +    /* register watchdog timer reset interrupt */
>         > +    qdev_init_gpio_out_named(DEVICE(cpu), &cpu->wdr, "wdr", 1);
>         >   }
>         >   
>         >   static ObjectClass *avr_cpu_class_by_name(const char *cpu_model)
>         > diff --git a/target/avr/cpu.h b/target/avr/cpu.h
>         > index d148e8c75a..f8f5641c8b 100644
>         > --- a/target/avr/cpu.h
>         > +++ b/target/avr/cpu.h
>         > @@ -152,6 +152,7 @@ typedef struct AVRCPU {
>         >   
>         >       CPUNegativeOffsetState neg;
>         >       CPUAVRState env;
>         > +    qemu_irq wdr; /* reset WDT */
>         >   } AVRCPU;
>         >   
>         >   extern const struct VMStateDescription vms_avr_cpu;
>         > diff --git a/target/avr/helper.c b/target/avr/helper.c
>         > index 35e1019594..dd88057e5f 100644
>         > --- a/target/avr/helper.c
>         > +++ b/target/avr/helper.c
>         > @@ -24,6 +24,7 @@
>         >   #include "exec/exec-all.h"
>         >   #include "exec/address-spaces.h"
>         >   #include "exec/helper-proto.h"
>         > +#include "hw/irq.h"
>         >   
>         >   bool avr_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
>         >   {
>         > @@ -188,11 +189,9 @@ void helper_break(CPUAVRState *env)
>         >   
>         >   void helper_wdr(CPUAVRState *env)
>         >   {
>         > -    CPUState *cs = env_cpu(env);
>         > +    AVRCPU *cpu = env_archcpu(env);
>         >   
>         > -    /* WD is not implemented yet, placeholder */
>         > -    cs->exception_index = EXCP_DEBUG;
>         > -    cpu_loop_exit(cs);
>         > +    qemu_set_irq(cpu->wdr, 1);
>         >   }
>         >   
>         >   /*
>         >
> 
>         Thanks!
> 
> 
> 
>     -- 
>     Best Regards,
>     Michael Rolnik
> 
> 
> 
> -- 
> Best Regards,
> Michael Rolnik


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [RFC 1/1] Implement AVR watchdog timer
  2021-05-03 13:36     ` Michael Rolnik
@ 2021-05-03 20:08       ` Michael Rolnik
  2021-05-15  9:45         ` Philippe Mathieu-Daudé
  0 siblings, 1 reply; 9+ messages in thread
From: Michael Rolnik @ 2021-05-03 20:08 UTC (permalink / raw)
  To: Fred Konrad
  Cc: Joaquin de Andres, Richard Henderson, QEMU Developers,
	Philippe Mathieu-Daudé

[-- Attachment #1: Type: text/plain, Size: 18944 bytes --]

Hi all,

I was about to make icount work. but, there is something I still don't
understand. I have this code

timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, avr_wdt_interrupt, s);

and then

void avr_wdt_interrupt(/* some arguments */) {
#define MS2NS(n)        ((n) * 1000000ull)
    timer_mod_ns(timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + MS2NS(15));
}

when running with --icount shift=0, *avr_wdt_interrupt* is called about
every 1K instructions, however it should have been called 15M instructions
as shift=0 makes every instruction to be executed in 1 virtual ns.

What am I doing wrong?

Thank you,
Michael Rolnik


On Mon, May 3, 2021 at 4:36 PM Michael Rolnik <mrolnik@gmail.com> wrote:

> Hi Fred.
>
> 1. thanks
> 2. It seems I have forgotten to set those flags.
> 3. 15ms is easy to test 8s will take 533 times longer, so in my case 3200
> instructions which is totally incorrect. I don't understand why as
> I program the timer in virtual nanoseconds and not host time.
> 4. I hope Richard could help with icount.
>
> best regards,
> Michael Rolnik
>
> On Mon, May 3, 2021 at 4:15 PM Fred Konrad <konrad@adacore.com> wrote:
>
>>
>>
>> Le 5/2/21 à 10:10 PM, Michael Rolnik a écrit :
>> > Signed-off-by: Michael Rolnik <mrolnik@gmail.com>
>> > ---
>> >   hw/avr/Kconfig                |   1 +
>> >   hw/avr/atmega.c               |  15 ++-
>> >   hw/avr/atmega.h               |   2 +
>> >   hw/watchdog/Kconfig           |   3 +
>> >   hw/watchdog/avr_wdt.c         | 190 ++++++++++++++++++++++++++++++++++
>> >   hw/watchdog/meson.build       |   2 +
>> >   hw/watchdog/trace-events      |   5 +
>> >   include/hw/watchdog/avr_wdt.h |  47 +++++++++
>> >   target/avr/cpu.c              |   3 +
>> >   target/avr/cpu.h              |   1 +
>> >   target/avr/helper.c           |   7 +-
>> >   11 files changed, 271 insertions(+), 5 deletions(-)
>> >   create mode 100644 hw/watchdog/avr_wdt.c
>> >   create mode 100644 include/hw/watchdog/avr_wdt.h
>> >
>> > diff --git a/hw/avr/Kconfig b/hw/avr/Kconfig
>> > index d31298c3cc..9939e4902f 100644
>> > --- a/hw/avr/Kconfig
>> > +++ b/hw/avr/Kconfig
>> > @@ -3,6 +3,7 @@ config AVR_ATMEGA_MCU
>> >       select AVR_TIMER16
>> >       select AVR_USART
>> >       select AVR_POWER
>> > +    select AVR_WDT
>> >
>> >   config ARDUINO
>> >       select AVR_ATMEGA_MCU
>> > diff --git a/hw/avr/atmega.c b/hw/avr/atmega.c
>> > index 44c6afebbb..31ceb1c21c 100644
>> > --- a/hw/avr/atmega.c
>> > +++ b/hw/avr/atmega.c
>> > @@ -28,6 +28,7 @@ enum AtmegaPeripheral {
>> >       GPIOG, GPIOH, GPIOI, GPIOJ, GPIOK, GPIOL,
>> >       USART0, USART1, USART2, USART3,
>> >       TIMER0, TIMER1, TIMER2, TIMER3, TIMER4, TIMER5,
>> > +    WDT,
>> >       PERIFMAX
>> >   };
>> >
>> > @@ -75,6 +76,7 @@ static const peripheral_cfg dev168_328[PERIFMAX] = {
>> >       [GPIOD]         = {  0x29 },
>> >       [GPIOC]         = {  0x26 },
>> >       [GPIOB]         = {  0x23 },
>> > +    [WDT]           = {  0x60 },
>> >   }, dev1280_2560[PERIFMAX] = {
>> >       [USART3]        = { 0x130, POWER1, 2 },
>> >       [TIMER5]        = { 0x120, POWER1, 5, 0x73, 0x3a, true },
>> > @@ -99,6 +101,7 @@ static const peripheral_cfg dev168_328[PERIFMAX] = {
>> >       [GPIOC]         = {  0x26 },
>> >       [GPIOB]         = {  0x23 },
>> >       [GPIOA]         = {  0x20 },
>> > +    [WDT]           = {  0x60 },
>> >   };
>> >
>> >   enum AtmegaIrq {
>> > @@ -118,6 +121,7 @@ enum AtmegaIrq {
>> >           TIMER4_COMPC_IRQ, TIMER4_OVF_IRQ,
>> >       TIMER5_CAPT_IRQ, TIMER5_COMPA_IRQ, TIMER5_COMPB_IRQ,
>> >           TIMER5_COMPC_IRQ, TIMER5_OVF_IRQ,
>> > +    WATCHDOG_TIMER_IRQ,
>> >       IRQ_COUNT
>> >   };
>> >
>> > @@ -133,6 +137,7 @@ enum AtmegaIrq {
>> >   #define TIMER_OVF_IRQ(n)    (n * TIMER_IRQ_COUNT + TIMER0_OVF_IRQ)
>> >
>> >   static const uint8_t irq168_328[IRQ_COUNT] = {
>> > +    [WATCHDOG_TIMER_IRQ]    = 7,
>> >       [TIMER2_COMPA_IRQ]      = 8,
>> >       [TIMER2_COMPB_IRQ]      = 9,
>> >       [TIMER2_OVF_IRQ]        = 10,
>> > @@ -147,6 +152,7 @@ static const uint8_t irq168_328[IRQ_COUNT] = {
>> >       [USART0_DRE_IRQ]        = 20,
>> >       [USART0_TXC_IRQ]        = 21,
>> >   }, irq1280_2560[IRQ_COUNT] = {
>> > +    [WATCHDOG_TIMER_IRQ]    = 13,
>> >       [TIMER2_COMPA_IRQ]      = 14,
>> >       [TIMER2_COMPB_IRQ]      = 15,
>> >       [TIMER2_OVF_IRQ]        = 16,
>> > @@ -344,10 +350,17 @@ static void atmega_realize(DeviceState *dev,
>> Error **errp)
>> >           g_free(devname);
>> >       }
>> >
>> > +    /* Watchdog Timer */
>> > +    object_initialize_child(OBJECT(dev), "wdt", &s->wdt, TYPE_AVR_WDT);
>> > +    sysbus_realize(SYS_BUS_DEVICE(&s->wdt), &error_abort);
>> > +    sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt), 0,
>> > +                    OFFSET_DATA + mc->dev[WDT].addr);
>> > +    qdev_connect_gpio_out_named(cpudev, "wdr", 0,
>> > +                    qdev_get_gpio_in_named(DEVICE(&s->wdt), "wdr", 0));
>> > +
>> >       create_unimplemented_device("avr-twi",          OFFSET_DATA +
>> 0x0b8, 6);
>> >       create_unimplemented_device("avr-adc",          OFFSET_DATA +
>> 0x078, 8);
>> >       create_unimplemented_device("avr-ext-mem-ctrl", OFFSET_DATA +
>> 0x074, 2);
>> > -    create_unimplemented_device("avr-watchdog",     OFFSET_DATA +
>> 0x060, 1);
>> >       create_unimplemented_device("avr-spi",          OFFSET_DATA +
>> 0x04c, 3);
>> >       create_unimplemented_device("avr-eeprom",       OFFSET_DATA +
>> 0x03f, 3);
>> >   }
>> > diff --git a/hw/avr/atmega.h b/hw/avr/atmega.h
>> > index a99ee15c7e..60bbd44bdd 100644
>> > --- a/hw/avr/atmega.h
>> > +++ b/hw/avr/atmega.h
>> > @@ -13,6 +13,7 @@
>> >
>> >   #include "hw/char/avr_usart.h"
>> >   #include "hw/timer/avr_timer16.h"
>> > +#include "hw/watchdog/avr_wdt.h"
>> >   #include "hw/misc/avr_power.h"
>> >   #include "target/avr/cpu.h"
>> >   #include "qom/object.h"
>> > @@ -45,6 +46,7 @@ struct AtmegaMcuState {
>> >       AVRMaskState pwr[POWER_MAX];
>> >       AVRUsartState usart[USART_MAX];
>> >       AVRTimer16State timer[TIMER_MAX];
>> > +    AVRWatchdogState wdt;
>> >       uint64_t xtal_freq_hz;
>> >   };
>> >
>> > diff --git a/hw/watchdog/Kconfig b/hw/watchdog/Kconfig
>> > index 66e1d029e3..e0f89d2fe0 100644
>> > --- a/hw/watchdog/Kconfig
>> > +++ b/hw/watchdog/Kconfig
>> > @@ -20,3 +20,6 @@ config WDT_IMX2
>> >
>> >   config WDT_SBSA
>> >       bool
>> > +
>> > +config AVR_WDT
>> > +    bool
>> > diff --git a/hw/watchdog/avr_wdt.c b/hw/watchdog/avr_wdt.c
>> > new file mode 100644
>> > index 0000000000..4ce1029a64
>> > --- /dev/null
>> > +++ b/hw/watchdog/avr_wdt.c
>> > @@ -0,0 +1,190 @@
>> > +/*
>> > + * AVR watchdog
>> > + *
>> > + * Copyright (c) 2018 Michael Rolnik
>>
>> 2021?
>>
>> > + *
>> > + * 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/lgpl-2.1.html>
>> > + */
>> > +
>> > +#include "qemu/osdep.h"
>> > +#include "qapi/error.h"
>> > +#include "qemu/log.h"
>> > +#include "hw/irq.h"
>> > +#include "hw/watchdog/avr_wdt.h"
>> > +#include "trace.h"
>> > +
>> > +/* Field masks */
>> > +#define WDTCSR_MASK_WDP0     0x01
>> > +#define WDTCSR_MASK_WDP1     0x02
>> > +#define WDTCSR_MASK_WDP2     0x04
>> > +#define WDTCSR_MASK_WDE      0x08
>> > +#define WDTCSR_MASK_WCE      0x10
>> > +#define WDTCSR_MASK_WDP3     0x20
>> > +#define WDTCSR_MASK_WDIE     0x40
>> > +#define WDTCSR_MASK_WDIF     0x80
>> > +
>> > +#define WDTCSR_SHFT_WDP0     0x00
>> > +#define WDTCSR_SHFT_WDP1     0x01
>> > +#define WDTCSR_SHFT_WDP2     0x02
>> > +#define WDTCSR_SHFT_WDE      0x03
>> > +#define WDTCSR_SHFT_WCE      0x04
>> > +#define WDTCSR_SHFT_WDP3     0x05
>> > +#define WDTCSR_SHFT_WDIE     0x06
>> > +#define WDTCSR_SHFT_WDIF     0x07
>> > +
>> > +/* Helper macros */
>> > +#define WDP0(csr)       ((csr & WDTCSR_MASK_WDP0) >> WDTCSR_SHFT_WDP0)
>> > +#define WDP1(csr)       ((csr & WDTCSR_MASK_WDP1) >> WDTCSR_SHFT_WDP1)
>> > +#define WDP2(csr)       ((csr & WDTCSR_MASK_WDP2) >> WDTCSR_SHFT_WDP2)
>> > +#define WDP3(csr)       ((csr & WDTCSR_MASK_WDP3) >> WDTCSR_SHFT_WDP3)
>> > +#define WDP(csr)        ((WDP3(csr) << 3) | (WDP2(csr) << 2) | \
>> > +                         (WDP1(csr) << 1) | (WDP0(csr) << 0))
>> > +#define WDIE(csr)       ((csr & WDTCSR_MASK_WDIE) >> WDTCSR_SHFT_WDIE)
>> > +#define WDE(csr)        ((csr & WDTCSR_MASK_WDE) >> WDTCSR_SHFT_WDE)
>> > +#define WCE(csr)        ((csr & WDTCSR_MASK_WCE) >> WDTCSR_SHFT_WCE)
>> > +
>> > +#define DB_PRINT(fmt, args...) /* Nothing */
>> > +
>> > +#define MS2NS(n)        ((n) * 1000000ull)
>> > +
>> > +static void avr_wdt_reset_alarm(AVRWatchdogState *wdt)
>> > +{
>> > +    uint32_t csr = wdt->csr;
>> > +    int wdp = WDP(csr);
>> > +    assert(wdp <= 9);
>>
>> Maybe qemu_log(..) instead and pick a default value?
>>
>> > +
>> > +    if (WDIE(csr) == 0 && WDE(csr) == 0) {
>> > +        /* watchdog is stopped */
>> > +        return;
>> > +    }
>> > +
>> > +    timer_mod_ns(wdt->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
>> > +            (MS2NS(15) << wdp));
>> > +}
>> > +
>> > +static void avr_wdt_interrupt(void *opaque)
>> > +{
>> > +    AVRWatchdogState *wdt = opaque;
>> > +    int8_t csr = wdt->csr;
>> > +
>> > +    if (WDE(csr) == 0 && WDIE(csr) == 0) {
>> > +        /* Stopped */
>> > +
>> > +    } else if (WDE(csr) == 0 && WDIE(csr) == 1) {
>> > +        /* Interrupt Mode */
>> > +        wdt->csr |= WDTCSR_MASK_WDIF;
>> > +        qemu_set_irq(wdt->irq, 1);
>> > +        trace_avr_wdt_interrupt();
>> > +    } else if (WDE(csr) == 1 && WDIE(csr) == 0) {
>> > +        /* System Reset Mode */
>>
>>         qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
>>
>> +/- setting the MCUSR, WDRF flags would be nice.
>>
>> > +    } else if (WDE(csr) == 1 && WDIE(csr) == 1) {
>> > +        /* Interrupt and System Reset Mode */
>> > +        wdt->csr |= WDTCSR_MASK_WDIF;
>> > +        qemu_set_irq(wdt->irq, 1);
>> > +        trace_avr_wdt_interrupt();
>> > +    }
>> > +
>> > +    avr_wdt_reset_alarm(wdt);
>> > +}
>> > +
>> > +static void avr_wdt_reset(DeviceState *dev)
>> > +{
>> > +    AVRWatchdogState *wdt = AVR_WDT(dev);
>> > +
>> > +    wdt->csr = 0;
>> > +    qemu_set_irq(wdt->irq, 0);
>> > +    avr_wdt_reset_alarm(wdt);
>> > +}
>> > +
>> > +static uint64_t avr_wdt_read(void *opaque, hwaddr offset, unsigned
>> size)
>> > +{
>> > +    assert(size == 1);
>> > +    AVRWatchdogState *wdt = opaque;
>> > +    uint8_t retval = wdt->csr;
>> > +
>> > +    trace_avr_wdt_read(offset, retval);
>> > +
>> > +    return (uint64_t)retval;
>> > +}
>> > +
>> > +static void avr_wdt_write(void *opaque, hwaddr offset,
>> > +                              uint64_t val64, unsigned size)
>> > +{
>> > +    assert(size == 1);
>> > +    AVRWatchdogState *wdt = opaque;
>> > +    uint8_t val8 = (uint8_t)val64;
>> > +
>> > +    trace_avr_wdt_write(offset, val8);
>> > +
>> > +    wdt->csr = val8;
>> > +    avr_wdt_reset_alarm(wdt);
>> > +}
>> > +
>> > +static const MemoryRegionOps avr_wdt_ops = {
>> > +    .read = avr_wdt_read,
>> > +    .write = avr_wdt_write,
>> > +    .endianness = DEVICE_NATIVE_ENDIAN,
>> > +    .impl = {.max_access_size = 1}
>> > +};
>> > +
>> > +static void avr_wdt_wdr(void *opaque, int irq, int level)
>> > +{
>> > +    AVRWatchdogState *wdt = AVR_WDT(opaque);
>> > +
>> > +    avr_wdt_reset_alarm(wdt);
>> > +}
>> > +
>> > +static void avr_wdt_init(Object *obj)
>> > +{
>> > +    AVRWatchdogState *s = AVR_WDT(obj);
>> > +
>> > +    sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
>> > +
>> > +    memory_region_init_io(&s->iomem, obj, &avr_wdt_ops,
>> > +                          s, "avr-wdt", 0xa);
>> > +
>> > +    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
>> > +    qdev_init_gpio_in_named(DEVICE(s), avr_wdt_wdr, "wdr", 1);
>> > +}
>> > +
>> > +static void avr_wdt_realize(DeviceState *dev, Error **errp)
>> > +{
>> > +    AVRWatchdogState *s = AVR_WDT(dev);
>> > +
>> > +    s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, avr_wdt_interrupt, s);
>> > +}
>> > +
>> > +static void avr_wdt_class_init(ObjectClass *klass, void *data)
>> > +{
>> > +    DeviceClass *dc = DEVICE_CLASS(klass);
>> > +
>> > +    dc->reset = avr_wdt_reset;
>> > +    dc->realize = avr_wdt_realize;
>> > +}
>> > +
>> > +static const TypeInfo avr_wdt_info = {
>> > +    .name          = TYPE_AVR_WDT,
>> > +    .parent        = TYPE_SYS_BUS_DEVICE,
>> > +    .instance_size = sizeof(AVRWatchdogState),
>> > +    .instance_init = avr_wdt_init,
>> > +    .class_init    = avr_wdt_class_init,
>> > +};
>> > +
>> > +static void avr_wdt_register_types(void)
>> > +{
>> > +    type_register_static(&avr_wdt_info);
>> > +}
>> > +
>> > +type_init(avr_wdt_register_types)
>> > diff --git a/hw/watchdog/meson.build b/hw/watchdog/meson.build
>> > index 054c403dea..8db2be8317 100644
>> > --- a/hw/watchdog/meson.build
>> > +++ b/hw/watchdog/meson.build
>> > @@ -6,3 +6,5 @@ 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_AVR_WDT', if_true: files('avr_wdt.c'))
>> > diff --git a/hw/watchdog/trace-events b/hw/watchdog/trace-events
>> > index 3124ca1f1b..ac14773179 100644
>> > --- a/hw/watchdog/trace-events
>> > +++ b/hw/watchdog/trace-events
>> > @@ -5,3 +5,8 @@ cmsdk_apb_watchdog_read(uint64_t offset, uint64_t data,
>> unsigned size) "CMSDK AP
>> >   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
>> > +
>> > +# avr_wdt.c
>> > +avr_wdt_read(uint8_t addr, uint8_t value) "wdt read addr:%u value:%u"
>> > +avr_wdt_write(uint8_t addr, uint8_t value) "wdt write addr:%u value:%u"
>> > +avr_wdt_interrupt(void) ""
>> > diff --git a/include/hw/watchdog/avr_wdt.h
>> b/include/hw/watchdog/avr_wdt.h
>> > new file mode 100644
>> > index 0000000000..2679e8f2a6
>> > --- /dev/null
>> > +++ b/include/hw/watchdog/avr_wdt.h
>> > @@ -0,0 +1,47 @@
>> > +/*
>> > + * AVR 16-bit timer
>>
>> AVR Watchdog?
>>
>> > + *
>> > + * Copyright (c) 2021 Michael Rolnik
>> > + *
>> > + * 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/lgpl-2.1.html>
>> > + */
>> > +
>> > +#ifndef HW_WATCHDOG_AVR_WDT_H
>> > +#define HW_WATCHDOG_AVR_WDT_H
>> > +
>> > +#include "hw/sysbus.h"
>> > +#include "qemu/timer.h"
>> > +#include "hw/hw.h"
>> > +#include "qom/object.h"
>> > +
>> > +#define TYPE_AVR_WDT "avr-wdt"
>> > +OBJECT_DECLARE_SIMPLE_TYPE(AVRWatchdogState, AVR_WDT)
>> > +
>> > +struct AVRWatchdogState {
>> > +    /* <private> */
>> > +    SysBusDevice parent_obj;
>> > +
>> > +    /* <public> */
>> > +    MemoryRegion iomem;
>> > +    MemoryRegion imsk_iomem;
>> > +    MemoryRegion ifr_iomem;
>> > +    QEMUTimer *timer;
>> > +    qemu_irq irq;
>> > +
>> > +    /* registers */
>> > +    uint8_t csr;
>> > +};
>> > +
>> > +#endif /* HW_WATCHDOG_AVR_WDT_H */
>> > diff --git a/target/avr/cpu.c b/target/avr/cpu.c
>> > index 0f4596932b..d5eb785833 100644
>> > --- a/target/avr/cpu.c
>> > +++ b/target/avr/cpu.c
>> > @@ -131,6 +131,9 @@ static void avr_cpu_initfn(Object *obj)
>> >       /* Set the number of interrupts supported by the CPU. */
>> >       qdev_init_gpio_in(DEVICE(cpu), avr_cpu_set_int,
>> >                         sizeof(cpu->env.intsrc) * 8);
>> > +
>> > +    /* register watchdog timer reset interrupt */
>> > +    qdev_init_gpio_out_named(DEVICE(cpu), &cpu->wdr, "wdr", 1);
>> >   }
>> >
>> >   static ObjectClass *avr_cpu_class_by_name(const char *cpu_model)
>> > diff --git a/target/avr/cpu.h b/target/avr/cpu.h
>> > index d148e8c75a..f8f5641c8b 100644
>> > --- a/target/avr/cpu.h
>> > +++ b/target/avr/cpu.h
>> > @@ -152,6 +152,7 @@ typedef struct AVRCPU {
>> >
>> >       CPUNegativeOffsetState neg;
>> >       CPUAVRState env;
>> > +    qemu_irq wdr; /* reset WDT */
>> >   } AVRCPU;
>> >
>> >   extern const struct VMStateDescription vms_avr_cpu;
>> > diff --git a/target/avr/helper.c b/target/avr/helper.c
>> > index 35e1019594..dd88057e5f 100644
>> > --- a/target/avr/helper.c
>> > +++ b/target/avr/helper.c
>> > @@ -24,6 +24,7 @@
>> >   #include "exec/exec-all.h"
>> >   #include "exec/address-spaces.h"
>> >   #include "exec/helper-proto.h"
>> > +#include "hw/irq.h"
>> >
>> >   bool avr_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
>> >   {
>> > @@ -188,11 +189,9 @@ void helper_break(CPUAVRState *env)
>> >
>> >   void helper_wdr(CPUAVRState *env)
>> >   {
>> > -    CPUState *cs = env_cpu(env);
>> > +    AVRCPU *cpu = env_archcpu(env);
>> >
>> > -    /* WD is not implemented yet, placeholder */
>> > -    cs->exception_index = EXCP_DEBUG;
>> > -    cpu_loop_exit(cs);
>> > +    qemu_set_irq(cpu->wdr, 1);
>> >   }
>> >
>> >   /*
>> >
>>
>> Thanks!
>>
>
>
> --
> Best Regards,
> Michael Rolnik
>


-- 
Best Regards,
Michael Rolnik

[-- Attachment #2: Type: text/html, Size: 24044 bytes --]

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [RFC 1/1] Implement AVR watchdog timer
  2021-05-03 13:15   ` Fred Konrad
@ 2021-05-03 13:36     ` Michael Rolnik
  2021-05-03 20:08       ` Michael Rolnik
  0 siblings, 1 reply; 9+ messages in thread
From: Michael Rolnik @ 2021-05-03 13:36 UTC (permalink / raw)
  To: Fred Konrad
  Cc: Joaquin de Andres, Richard Henderson, QEMU Developers,
	Philippe Mathieu-Daudé

[-- Attachment #1: Type: text/plain, Size: 17655 bytes --]

Hi Fred.

1. thanks
2. It seems I have forgotten to set those flags.
3. 15ms is easy to test 8s will take 533 times longer, so in my case 3200
instructions which is totally incorrect. I don't understand why as
I program the timer in virtual nanoseconds and not host time.
4. I hope Richard could help with icount.

best regards,
Michael Rolnik

On Mon, May 3, 2021 at 4:15 PM Fred Konrad <konrad@adacore.com> wrote:

>
>
> Le 5/2/21 à 10:10 PM, Michael Rolnik a écrit :
> > Signed-off-by: Michael Rolnik <mrolnik@gmail.com>
> > ---
> >   hw/avr/Kconfig                |   1 +
> >   hw/avr/atmega.c               |  15 ++-
> >   hw/avr/atmega.h               |   2 +
> >   hw/watchdog/Kconfig           |   3 +
> >   hw/watchdog/avr_wdt.c         | 190 ++++++++++++++++++++++++++++++++++
> >   hw/watchdog/meson.build       |   2 +
> >   hw/watchdog/trace-events      |   5 +
> >   include/hw/watchdog/avr_wdt.h |  47 +++++++++
> >   target/avr/cpu.c              |   3 +
> >   target/avr/cpu.h              |   1 +
> >   target/avr/helper.c           |   7 +-
> >   11 files changed, 271 insertions(+), 5 deletions(-)
> >   create mode 100644 hw/watchdog/avr_wdt.c
> >   create mode 100644 include/hw/watchdog/avr_wdt.h
> >
> > diff --git a/hw/avr/Kconfig b/hw/avr/Kconfig
> > index d31298c3cc..9939e4902f 100644
> > --- a/hw/avr/Kconfig
> > +++ b/hw/avr/Kconfig
> > @@ -3,6 +3,7 @@ config AVR_ATMEGA_MCU
> >       select AVR_TIMER16
> >       select AVR_USART
> >       select AVR_POWER
> > +    select AVR_WDT
> >
> >   config ARDUINO
> >       select AVR_ATMEGA_MCU
> > diff --git a/hw/avr/atmega.c b/hw/avr/atmega.c
> > index 44c6afebbb..31ceb1c21c 100644
> > --- a/hw/avr/atmega.c
> > +++ b/hw/avr/atmega.c
> > @@ -28,6 +28,7 @@ enum AtmegaPeripheral {
> >       GPIOG, GPIOH, GPIOI, GPIOJ, GPIOK, GPIOL,
> >       USART0, USART1, USART2, USART3,
> >       TIMER0, TIMER1, TIMER2, TIMER3, TIMER4, TIMER5,
> > +    WDT,
> >       PERIFMAX
> >   };
> >
> > @@ -75,6 +76,7 @@ static const peripheral_cfg dev168_328[PERIFMAX] = {
> >       [GPIOD]         = {  0x29 },
> >       [GPIOC]         = {  0x26 },
> >       [GPIOB]         = {  0x23 },
> > +    [WDT]           = {  0x60 },
> >   }, dev1280_2560[PERIFMAX] = {
> >       [USART3]        = { 0x130, POWER1, 2 },
> >       [TIMER5]        = { 0x120, POWER1, 5, 0x73, 0x3a, true },
> > @@ -99,6 +101,7 @@ static const peripheral_cfg dev168_328[PERIFMAX] = {
> >       [GPIOC]         = {  0x26 },
> >       [GPIOB]         = {  0x23 },
> >       [GPIOA]         = {  0x20 },
> > +    [WDT]           = {  0x60 },
> >   };
> >
> >   enum AtmegaIrq {
> > @@ -118,6 +121,7 @@ enum AtmegaIrq {
> >           TIMER4_COMPC_IRQ, TIMER4_OVF_IRQ,
> >       TIMER5_CAPT_IRQ, TIMER5_COMPA_IRQ, TIMER5_COMPB_IRQ,
> >           TIMER5_COMPC_IRQ, TIMER5_OVF_IRQ,
> > +    WATCHDOG_TIMER_IRQ,
> >       IRQ_COUNT
> >   };
> >
> > @@ -133,6 +137,7 @@ enum AtmegaIrq {
> >   #define TIMER_OVF_IRQ(n)    (n * TIMER_IRQ_COUNT + TIMER0_OVF_IRQ)
> >
> >   static const uint8_t irq168_328[IRQ_COUNT] = {
> > +    [WATCHDOG_TIMER_IRQ]    = 7,
> >       [TIMER2_COMPA_IRQ]      = 8,
> >       [TIMER2_COMPB_IRQ]      = 9,
> >       [TIMER2_OVF_IRQ]        = 10,
> > @@ -147,6 +152,7 @@ static const uint8_t irq168_328[IRQ_COUNT] = {
> >       [USART0_DRE_IRQ]        = 20,
> >       [USART0_TXC_IRQ]        = 21,
> >   }, irq1280_2560[IRQ_COUNT] = {
> > +    [WATCHDOG_TIMER_IRQ]    = 13,
> >       [TIMER2_COMPA_IRQ]      = 14,
> >       [TIMER2_COMPB_IRQ]      = 15,
> >       [TIMER2_OVF_IRQ]        = 16,
> > @@ -344,10 +350,17 @@ static void atmega_realize(DeviceState *dev, Error
> **errp)
> >           g_free(devname);
> >       }
> >
> > +    /* Watchdog Timer */
> > +    object_initialize_child(OBJECT(dev), "wdt", &s->wdt, TYPE_AVR_WDT);
> > +    sysbus_realize(SYS_BUS_DEVICE(&s->wdt), &error_abort);
> > +    sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt), 0,
> > +                    OFFSET_DATA + mc->dev[WDT].addr);
> > +    qdev_connect_gpio_out_named(cpudev, "wdr", 0,
> > +                    qdev_get_gpio_in_named(DEVICE(&s->wdt), "wdr", 0));
> > +
> >       create_unimplemented_device("avr-twi",          OFFSET_DATA +
> 0x0b8, 6);
> >       create_unimplemented_device("avr-adc",          OFFSET_DATA +
> 0x078, 8);
> >       create_unimplemented_device("avr-ext-mem-ctrl", OFFSET_DATA +
> 0x074, 2);
> > -    create_unimplemented_device("avr-watchdog",     OFFSET_DATA +
> 0x060, 1);
> >       create_unimplemented_device("avr-spi",          OFFSET_DATA +
> 0x04c, 3);
> >       create_unimplemented_device("avr-eeprom",       OFFSET_DATA +
> 0x03f, 3);
> >   }
> > diff --git a/hw/avr/atmega.h b/hw/avr/atmega.h
> > index a99ee15c7e..60bbd44bdd 100644
> > --- a/hw/avr/atmega.h
> > +++ b/hw/avr/atmega.h
> > @@ -13,6 +13,7 @@
> >
> >   #include "hw/char/avr_usart.h"
> >   #include "hw/timer/avr_timer16.h"
> > +#include "hw/watchdog/avr_wdt.h"
> >   #include "hw/misc/avr_power.h"
> >   #include "target/avr/cpu.h"
> >   #include "qom/object.h"
> > @@ -45,6 +46,7 @@ struct AtmegaMcuState {
> >       AVRMaskState pwr[POWER_MAX];
> >       AVRUsartState usart[USART_MAX];
> >       AVRTimer16State timer[TIMER_MAX];
> > +    AVRWatchdogState wdt;
> >       uint64_t xtal_freq_hz;
> >   };
> >
> > diff --git a/hw/watchdog/Kconfig b/hw/watchdog/Kconfig
> > index 66e1d029e3..e0f89d2fe0 100644
> > --- a/hw/watchdog/Kconfig
> > +++ b/hw/watchdog/Kconfig
> > @@ -20,3 +20,6 @@ config WDT_IMX2
> >
> >   config WDT_SBSA
> >       bool
> > +
> > +config AVR_WDT
> > +    bool
> > diff --git a/hw/watchdog/avr_wdt.c b/hw/watchdog/avr_wdt.c
> > new file mode 100644
> > index 0000000000..4ce1029a64
> > --- /dev/null
> > +++ b/hw/watchdog/avr_wdt.c
> > @@ -0,0 +1,190 @@
> > +/*
> > + * AVR watchdog
> > + *
> > + * Copyright (c) 2018 Michael Rolnik
>
> 2021?
>
> > + *
> > + * 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/lgpl-2.1.html>
> > + */
> > +
> > +#include "qemu/osdep.h"
> > +#include "qapi/error.h"
> > +#include "qemu/log.h"
> > +#include "hw/irq.h"
> > +#include "hw/watchdog/avr_wdt.h"
> > +#include "trace.h"
> > +
> > +/* Field masks */
> > +#define WDTCSR_MASK_WDP0     0x01
> > +#define WDTCSR_MASK_WDP1     0x02
> > +#define WDTCSR_MASK_WDP2     0x04
> > +#define WDTCSR_MASK_WDE      0x08
> > +#define WDTCSR_MASK_WCE      0x10
> > +#define WDTCSR_MASK_WDP3     0x20
> > +#define WDTCSR_MASK_WDIE     0x40
> > +#define WDTCSR_MASK_WDIF     0x80
> > +
> > +#define WDTCSR_SHFT_WDP0     0x00
> > +#define WDTCSR_SHFT_WDP1     0x01
> > +#define WDTCSR_SHFT_WDP2     0x02
> > +#define WDTCSR_SHFT_WDE      0x03
> > +#define WDTCSR_SHFT_WCE      0x04
> > +#define WDTCSR_SHFT_WDP3     0x05
> > +#define WDTCSR_SHFT_WDIE     0x06
> > +#define WDTCSR_SHFT_WDIF     0x07
> > +
> > +/* Helper macros */
> > +#define WDP0(csr)       ((csr & WDTCSR_MASK_WDP0) >> WDTCSR_SHFT_WDP0)
> > +#define WDP1(csr)       ((csr & WDTCSR_MASK_WDP1) >> WDTCSR_SHFT_WDP1)
> > +#define WDP2(csr)       ((csr & WDTCSR_MASK_WDP2) >> WDTCSR_SHFT_WDP2)
> > +#define WDP3(csr)       ((csr & WDTCSR_MASK_WDP3) >> WDTCSR_SHFT_WDP3)
> > +#define WDP(csr)        ((WDP3(csr) << 3) | (WDP2(csr) << 2) | \
> > +                         (WDP1(csr) << 1) | (WDP0(csr) << 0))
> > +#define WDIE(csr)       ((csr & WDTCSR_MASK_WDIE) >> WDTCSR_SHFT_WDIE)
> > +#define WDE(csr)        ((csr & WDTCSR_MASK_WDE) >> WDTCSR_SHFT_WDE)
> > +#define WCE(csr)        ((csr & WDTCSR_MASK_WCE) >> WDTCSR_SHFT_WCE)
> > +
> > +#define DB_PRINT(fmt, args...) /* Nothing */
> > +
> > +#define MS2NS(n)        ((n) * 1000000ull)
> > +
> > +static void avr_wdt_reset_alarm(AVRWatchdogState *wdt)
> > +{
> > +    uint32_t csr = wdt->csr;
> > +    int wdp = WDP(csr);
> > +    assert(wdp <= 9);
>
> Maybe qemu_log(..) instead and pick a default value?
>
> > +
> > +    if (WDIE(csr) == 0 && WDE(csr) == 0) {
> > +        /* watchdog is stopped */
> > +        return;
> > +    }
> > +
> > +    timer_mod_ns(wdt->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
> > +            (MS2NS(15) << wdp));
> > +}
> > +
> > +static void avr_wdt_interrupt(void *opaque)
> > +{
> > +    AVRWatchdogState *wdt = opaque;
> > +    int8_t csr = wdt->csr;
> > +
> > +    if (WDE(csr) == 0 && WDIE(csr) == 0) {
> > +        /* Stopped */
> > +
> > +    } else if (WDE(csr) == 0 && WDIE(csr) == 1) {
> > +        /* Interrupt Mode */
> > +        wdt->csr |= WDTCSR_MASK_WDIF;
> > +        qemu_set_irq(wdt->irq, 1);
> > +        trace_avr_wdt_interrupt();
> > +    } else if (WDE(csr) == 1 && WDIE(csr) == 0) {
> > +        /* System Reset Mode */
>
>         qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
>
> +/- setting the MCUSR, WDRF flags would be nice.
>
> > +    } else if (WDE(csr) == 1 && WDIE(csr) == 1) {
> > +        /* Interrupt and System Reset Mode */
> > +        wdt->csr |= WDTCSR_MASK_WDIF;
> > +        qemu_set_irq(wdt->irq, 1);
> > +        trace_avr_wdt_interrupt();
> > +    }
> > +
> > +    avr_wdt_reset_alarm(wdt);
> > +}
> > +
> > +static void avr_wdt_reset(DeviceState *dev)
> > +{
> > +    AVRWatchdogState *wdt = AVR_WDT(dev);
> > +
> > +    wdt->csr = 0;
> > +    qemu_set_irq(wdt->irq, 0);
> > +    avr_wdt_reset_alarm(wdt);
> > +}
> > +
> > +static uint64_t avr_wdt_read(void *opaque, hwaddr offset, unsigned size)
> > +{
> > +    assert(size == 1);
> > +    AVRWatchdogState *wdt = opaque;
> > +    uint8_t retval = wdt->csr;
> > +
> > +    trace_avr_wdt_read(offset, retval);
> > +
> > +    return (uint64_t)retval;
> > +}
> > +
> > +static void avr_wdt_write(void *opaque, hwaddr offset,
> > +                              uint64_t val64, unsigned size)
> > +{
> > +    assert(size == 1);
> > +    AVRWatchdogState *wdt = opaque;
> > +    uint8_t val8 = (uint8_t)val64;
> > +
> > +    trace_avr_wdt_write(offset, val8);
> > +
> > +    wdt->csr = val8;
> > +    avr_wdt_reset_alarm(wdt);
> > +}
> > +
> > +static const MemoryRegionOps avr_wdt_ops = {
> > +    .read = avr_wdt_read,
> > +    .write = avr_wdt_write,
> > +    .endianness = DEVICE_NATIVE_ENDIAN,
> > +    .impl = {.max_access_size = 1}
> > +};
> > +
> > +static void avr_wdt_wdr(void *opaque, int irq, int level)
> > +{
> > +    AVRWatchdogState *wdt = AVR_WDT(opaque);
> > +
> > +    avr_wdt_reset_alarm(wdt);
> > +}
> > +
> > +static void avr_wdt_init(Object *obj)
> > +{
> > +    AVRWatchdogState *s = AVR_WDT(obj);
> > +
> > +    sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
> > +
> > +    memory_region_init_io(&s->iomem, obj, &avr_wdt_ops,
> > +                          s, "avr-wdt", 0xa);
> > +
> > +    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
> > +    qdev_init_gpio_in_named(DEVICE(s), avr_wdt_wdr, "wdr", 1);
> > +}
> > +
> > +static void avr_wdt_realize(DeviceState *dev, Error **errp)
> > +{
> > +    AVRWatchdogState *s = AVR_WDT(dev);
> > +
> > +    s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, avr_wdt_interrupt, s);
> > +}
> > +
> > +static void avr_wdt_class_init(ObjectClass *klass, void *data)
> > +{
> > +    DeviceClass *dc = DEVICE_CLASS(klass);
> > +
> > +    dc->reset = avr_wdt_reset;
> > +    dc->realize = avr_wdt_realize;
> > +}
> > +
> > +static const TypeInfo avr_wdt_info = {
> > +    .name          = TYPE_AVR_WDT,
> > +    .parent        = TYPE_SYS_BUS_DEVICE,
> > +    .instance_size = sizeof(AVRWatchdogState),
> > +    .instance_init = avr_wdt_init,
> > +    .class_init    = avr_wdt_class_init,
> > +};
> > +
> > +static void avr_wdt_register_types(void)
> > +{
> > +    type_register_static(&avr_wdt_info);
> > +}
> > +
> > +type_init(avr_wdt_register_types)
> > diff --git a/hw/watchdog/meson.build b/hw/watchdog/meson.build
> > index 054c403dea..8db2be8317 100644
> > --- a/hw/watchdog/meson.build
> > +++ b/hw/watchdog/meson.build
> > @@ -6,3 +6,5 @@ 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_AVR_WDT', if_true: files('avr_wdt.c'))
> > diff --git a/hw/watchdog/trace-events b/hw/watchdog/trace-events
> > index 3124ca1f1b..ac14773179 100644
> > --- a/hw/watchdog/trace-events
> > +++ b/hw/watchdog/trace-events
> > @@ -5,3 +5,8 @@ cmsdk_apb_watchdog_read(uint64_t offset, uint64_t data,
> unsigned size) "CMSDK AP
> >   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
> > +
> > +# avr_wdt.c
> > +avr_wdt_read(uint8_t addr, uint8_t value) "wdt read addr:%u value:%u"
> > +avr_wdt_write(uint8_t addr, uint8_t value) "wdt write addr:%u value:%u"
> > +avr_wdt_interrupt(void) ""
> > diff --git a/include/hw/watchdog/avr_wdt.h
> b/include/hw/watchdog/avr_wdt.h
> > new file mode 100644
> > index 0000000000..2679e8f2a6
> > --- /dev/null
> > +++ b/include/hw/watchdog/avr_wdt.h
> > @@ -0,0 +1,47 @@
> > +/*
> > + * AVR 16-bit timer
>
> AVR Watchdog?
>
> > + *
> > + * Copyright (c) 2021 Michael Rolnik
> > + *
> > + * 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/lgpl-2.1.html>
> > + */
> > +
> > +#ifndef HW_WATCHDOG_AVR_WDT_H
> > +#define HW_WATCHDOG_AVR_WDT_H
> > +
> > +#include "hw/sysbus.h"
> > +#include "qemu/timer.h"
> > +#include "hw/hw.h"
> > +#include "qom/object.h"
> > +
> > +#define TYPE_AVR_WDT "avr-wdt"
> > +OBJECT_DECLARE_SIMPLE_TYPE(AVRWatchdogState, AVR_WDT)
> > +
> > +struct AVRWatchdogState {
> > +    /* <private> */
> > +    SysBusDevice parent_obj;
> > +
> > +    /* <public> */
> > +    MemoryRegion iomem;
> > +    MemoryRegion imsk_iomem;
> > +    MemoryRegion ifr_iomem;
> > +    QEMUTimer *timer;
> > +    qemu_irq irq;
> > +
> > +    /* registers */
> > +    uint8_t csr;
> > +};
> > +
> > +#endif /* HW_WATCHDOG_AVR_WDT_H */
> > diff --git a/target/avr/cpu.c b/target/avr/cpu.c
> > index 0f4596932b..d5eb785833 100644
> > --- a/target/avr/cpu.c
> > +++ b/target/avr/cpu.c
> > @@ -131,6 +131,9 @@ static void avr_cpu_initfn(Object *obj)
> >       /* Set the number of interrupts supported by the CPU. */
> >       qdev_init_gpio_in(DEVICE(cpu), avr_cpu_set_int,
> >                         sizeof(cpu->env.intsrc) * 8);
> > +
> > +    /* register watchdog timer reset interrupt */
> > +    qdev_init_gpio_out_named(DEVICE(cpu), &cpu->wdr, "wdr", 1);
> >   }
> >
> >   static ObjectClass *avr_cpu_class_by_name(const char *cpu_model)
> > diff --git a/target/avr/cpu.h b/target/avr/cpu.h
> > index d148e8c75a..f8f5641c8b 100644
> > --- a/target/avr/cpu.h
> > +++ b/target/avr/cpu.h
> > @@ -152,6 +152,7 @@ typedef struct AVRCPU {
> >
> >       CPUNegativeOffsetState neg;
> >       CPUAVRState env;
> > +    qemu_irq wdr; /* reset WDT */
> >   } AVRCPU;
> >
> >   extern const struct VMStateDescription vms_avr_cpu;
> > diff --git a/target/avr/helper.c b/target/avr/helper.c
> > index 35e1019594..dd88057e5f 100644
> > --- a/target/avr/helper.c
> > +++ b/target/avr/helper.c
> > @@ -24,6 +24,7 @@
> >   #include "exec/exec-all.h"
> >   #include "exec/address-spaces.h"
> >   #include "exec/helper-proto.h"
> > +#include "hw/irq.h"
> >
> >   bool avr_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
> >   {
> > @@ -188,11 +189,9 @@ void helper_break(CPUAVRState *env)
> >
> >   void helper_wdr(CPUAVRState *env)
> >   {
> > -    CPUState *cs = env_cpu(env);
> > +    AVRCPU *cpu = env_archcpu(env);
> >
> > -    /* WD is not implemented yet, placeholder */
> > -    cs->exception_index = EXCP_DEBUG;
> > -    cpu_loop_exit(cs);
> > +    qemu_set_irq(cpu->wdr, 1);
> >   }
> >
> >   /*
> >
>
> Thanks!
>


-- 
Best Regards,
Michael Rolnik

[-- Attachment #2: Type: text/html, Size: 22356 bytes --]

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [RFC 1/1] Implement AVR watchdog timer
  2021-05-02 20:10 ` [RFC 1/1] Implement AVR watchdog timer Michael Rolnik
@ 2021-05-03 13:15   ` Fred Konrad
  2021-05-03 13:36     ` Michael Rolnik
  0 siblings, 1 reply; 9+ messages in thread
From: Fred Konrad @ 2021-05-03 13:15 UTC (permalink / raw)
  To: Michael Rolnik, qemu-devel; +Cc: me, richard.henderson, f4bug



Le 5/2/21 à 10:10 PM, Michael Rolnik a écrit :
> Signed-off-by: Michael Rolnik <mrolnik@gmail.com>
> ---
>   hw/avr/Kconfig                |   1 +
>   hw/avr/atmega.c               |  15 ++-
>   hw/avr/atmega.h               |   2 +
>   hw/watchdog/Kconfig           |   3 +
>   hw/watchdog/avr_wdt.c         | 190 ++++++++++++++++++++++++++++++++++
>   hw/watchdog/meson.build       |   2 +
>   hw/watchdog/trace-events      |   5 +
>   include/hw/watchdog/avr_wdt.h |  47 +++++++++
>   target/avr/cpu.c              |   3 +
>   target/avr/cpu.h              |   1 +
>   target/avr/helper.c           |   7 +-
>   11 files changed, 271 insertions(+), 5 deletions(-)
>   create mode 100644 hw/watchdog/avr_wdt.c
>   create mode 100644 include/hw/watchdog/avr_wdt.h
> 
> diff --git a/hw/avr/Kconfig b/hw/avr/Kconfig
> index d31298c3cc..9939e4902f 100644
> --- a/hw/avr/Kconfig
> +++ b/hw/avr/Kconfig
> @@ -3,6 +3,7 @@ config AVR_ATMEGA_MCU
>       select AVR_TIMER16
>       select AVR_USART
>       select AVR_POWER
> +    select AVR_WDT
>   
>   config ARDUINO
>       select AVR_ATMEGA_MCU
> diff --git a/hw/avr/atmega.c b/hw/avr/atmega.c
> index 44c6afebbb..31ceb1c21c 100644
> --- a/hw/avr/atmega.c
> +++ b/hw/avr/atmega.c
> @@ -28,6 +28,7 @@ enum AtmegaPeripheral {
>       GPIOG, GPIOH, GPIOI, GPIOJ, GPIOK, GPIOL,
>       USART0, USART1, USART2, USART3,
>       TIMER0, TIMER1, TIMER2, TIMER3, TIMER4, TIMER5,
> +    WDT,
>       PERIFMAX
>   };
>   
> @@ -75,6 +76,7 @@ static const peripheral_cfg dev168_328[PERIFMAX] = {
>       [GPIOD]         = {  0x29 },
>       [GPIOC]         = {  0x26 },
>       [GPIOB]         = {  0x23 },
> +    [WDT]           = {  0x60 },
>   }, dev1280_2560[PERIFMAX] = {
>       [USART3]        = { 0x130, POWER1, 2 },
>       [TIMER5]        = { 0x120, POWER1, 5, 0x73, 0x3a, true },
> @@ -99,6 +101,7 @@ static const peripheral_cfg dev168_328[PERIFMAX] = {
>       [GPIOC]         = {  0x26 },
>       [GPIOB]         = {  0x23 },
>       [GPIOA]         = {  0x20 },
> +    [WDT]           = {  0x60 },
>   };
>   
>   enum AtmegaIrq {
> @@ -118,6 +121,7 @@ enum AtmegaIrq {
>           TIMER4_COMPC_IRQ, TIMER4_OVF_IRQ,
>       TIMER5_CAPT_IRQ, TIMER5_COMPA_IRQ, TIMER5_COMPB_IRQ,
>           TIMER5_COMPC_IRQ, TIMER5_OVF_IRQ,
> +    WATCHDOG_TIMER_IRQ,
>       IRQ_COUNT
>   };
>   
> @@ -133,6 +137,7 @@ enum AtmegaIrq {
>   #define TIMER_OVF_IRQ(n)    (n * TIMER_IRQ_COUNT + TIMER0_OVF_IRQ)
>   
>   static const uint8_t irq168_328[IRQ_COUNT] = {
> +    [WATCHDOG_TIMER_IRQ]    = 7,
>       [TIMER2_COMPA_IRQ]      = 8,
>       [TIMER2_COMPB_IRQ]      = 9,
>       [TIMER2_OVF_IRQ]        = 10,
> @@ -147,6 +152,7 @@ static const uint8_t irq168_328[IRQ_COUNT] = {
>       [USART0_DRE_IRQ]        = 20,
>       [USART0_TXC_IRQ]        = 21,
>   }, irq1280_2560[IRQ_COUNT] = {
> +    [WATCHDOG_TIMER_IRQ]    = 13,
>       [TIMER2_COMPA_IRQ]      = 14,
>       [TIMER2_COMPB_IRQ]      = 15,
>       [TIMER2_OVF_IRQ]        = 16,
> @@ -344,10 +350,17 @@ static void atmega_realize(DeviceState *dev, Error **errp)
>           g_free(devname);
>       }
>   
> +    /* Watchdog Timer */
> +    object_initialize_child(OBJECT(dev), "wdt", &s->wdt, TYPE_AVR_WDT);
> +    sysbus_realize(SYS_BUS_DEVICE(&s->wdt), &error_abort);
> +    sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt), 0,
> +                    OFFSET_DATA + mc->dev[WDT].addr);
> +    qdev_connect_gpio_out_named(cpudev, "wdr", 0,
> +                    qdev_get_gpio_in_named(DEVICE(&s->wdt), "wdr", 0));
> +
>       create_unimplemented_device("avr-twi",          OFFSET_DATA + 0x0b8, 6);
>       create_unimplemented_device("avr-adc",          OFFSET_DATA + 0x078, 8);
>       create_unimplemented_device("avr-ext-mem-ctrl", OFFSET_DATA + 0x074, 2);
> -    create_unimplemented_device("avr-watchdog",     OFFSET_DATA + 0x060, 1);
>       create_unimplemented_device("avr-spi",          OFFSET_DATA + 0x04c, 3);
>       create_unimplemented_device("avr-eeprom",       OFFSET_DATA + 0x03f, 3);
>   }
> diff --git a/hw/avr/atmega.h b/hw/avr/atmega.h
> index a99ee15c7e..60bbd44bdd 100644
> --- a/hw/avr/atmega.h
> +++ b/hw/avr/atmega.h
> @@ -13,6 +13,7 @@
>   
>   #include "hw/char/avr_usart.h"
>   #include "hw/timer/avr_timer16.h"
> +#include "hw/watchdog/avr_wdt.h"
>   #include "hw/misc/avr_power.h"
>   #include "target/avr/cpu.h"
>   #include "qom/object.h"
> @@ -45,6 +46,7 @@ struct AtmegaMcuState {
>       AVRMaskState pwr[POWER_MAX];
>       AVRUsartState usart[USART_MAX];
>       AVRTimer16State timer[TIMER_MAX];
> +    AVRWatchdogState wdt;
>       uint64_t xtal_freq_hz;
>   };
>   
> diff --git a/hw/watchdog/Kconfig b/hw/watchdog/Kconfig
> index 66e1d029e3..e0f89d2fe0 100644
> --- a/hw/watchdog/Kconfig
> +++ b/hw/watchdog/Kconfig
> @@ -20,3 +20,6 @@ config WDT_IMX2
>   
>   config WDT_SBSA
>       bool
> +
> +config AVR_WDT
> +    bool
> diff --git a/hw/watchdog/avr_wdt.c b/hw/watchdog/avr_wdt.c
> new file mode 100644
> index 0000000000..4ce1029a64
> --- /dev/null
> +++ b/hw/watchdog/avr_wdt.c
> @@ -0,0 +1,190 @@
> +/*
> + * AVR watchdog
> + *
> + * Copyright (c) 2018 Michael Rolnik

2021?

> + *
> + * 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/lgpl-2.1.html>
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "qemu/log.h"
> +#include "hw/irq.h"
> +#include "hw/watchdog/avr_wdt.h"
> +#include "trace.h"
> +
> +/* Field masks */
> +#define WDTCSR_MASK_WDP0     0x01
> +#define WDTCSR_MASK_WDP1     0x02
> +#define WDTCSR_MASK_WDP2     0x04
> +#define WDTCSR_MASK_WDE      0x08
> +#define WDTCSR_MASK_WCE      0x10
> +#define WDTCSR_MASK_WDP3     0x20
> +#define WDTCSR_MASK_WDIE     0x40
> +#define WDTCSR_MASK_WDIF     0x80
> +
> +#define WDTCSR_SHFT_WDP0     0x00
> +#define WDTCSR_SHFT_WDP1     0x01
> +#define WDTCSR_SHFT_WDP2     0x02
> +#define WDTCSR_SHFT_WDE      0x03
> +#define WDTCSR_SHFT_WCE      0x04
> +#define WDTCSR_SHFT_WDP3     0x05
> +#define WDTCSR_SHFT_WDIE     0x06
> +#define WDTCSR_SHFT_WDIF     0x07
> +
> +/* Helper macros */
> +#define WDP0(csr)       ((csr & WDTCSR_MASK_WDP0) >> WDTCSR_SHFT_WDP0)
> +#define WDP1(csr)       ((csr & WDTCSR_MASK_WDP1) >> WDTCSR_SHFT_WDP1)
> +#define WDP2(csr)       ((csr & WDTCSR_MASK_WDP2) >> WDTCSR_SHFT_WDP2)
> +#define WDP3(csr)       ((csr & WDTCSR_MASK_WDP3) >> WDTCSR_SHFT_WDP3)
> +#define WDP(csr)        ((WDP3(csr) << 3) | (WDP2(csr) << 2) | \
> +                         (WDP1(csr) << 1) | (WDP0(csr) << 0))
> +#define WDIE(csr)       ((csr & WDTCSR_MASK_WDIE) >> WDTCSR_SHFT_WDIE)
> +#define WDE(csr)        ((csr & WDTCSR_MASK_WDE) >> WDTCSR_SHFT_WDE)
> +#define WCE(csr)        ((csr & WDTCSR_MASK_WCE) >> WDTCSR_SHFT_WCE)
> +
> +#define DB_PRINT(fmt, args...) /* Nothing */
> +
> +#define MS2NS(n)        ((n) * 1000000ull)
> +
> +static void avr_wdt_reset_alarm(AVRWatchdogState *wdt)
> +{
> +    uint32_t csr = wdt->csr;
> +    int wdp = WDP(csr);
> +    assert(wdp <= 9);

Maybe qemu_log(..) instead and pick a default value?

> +
> +    if (WDIE(csr) == 0 && WDE(csr) == 0) {
> +        /* watchdog is stopped */
> +        return;
> +    }
> +
> +    timer_mod_ns(wdt->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
> +            (MS2NS(15) << wdp));
> +}
> +
> +static void avr_wdt_interrupt(void *opaque)
> +{
> +    AVRWatchdogState *wdt = opaque;
> +    int8_t csr = wdt->csr;
> +
> +    if (WDE(csr) == 0 && WDIE(csr) == 0) {
> +        /* Stopped */
> +
> +    } else if (WDE(csr) == 0 && WDIE(csr) == 1) {
> +        /* Interrupt Mode */
> +        wdt->csr |= WDTCSR_MASK_WDIF;
> +        qemu_set_irq(wdt->irq, 1);
> +        trace_avr_wdt_interrupt();
> +    } else if (WDE(csr) == 1 && WDIE(csr) == 0) {
> +        /* System Reset Mode */

        qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);

+/- setting the MCUSR, WDRF flags would be nice.

> +    } else if (WDE(csr) == 1 && WDIE(csr) == 1) {
> +        /* Interrupt and System Reset Mode */
> +        wdt->csr |= WDTCSR_MASK_WDIF;
> +        qemu_set_irq(wdt->irq, 1);
> +        trace_avr_wdt_interrupt();
> +    }
> +
> +    avr_wdt_reset_alarm(wdt);
> +}
> +
> +static void avr_wdt_reset(DeviceState *dev)
> +{
> +    AVRWatchdogState *wdt = AVR_WDT(dev);
> +
> +    wdt->csr = 0;
> +    qemu_set_irq(wdt->irq, 0);
> +    avr_wdt_reset_alarm(wdt);
> +}
> +
> +static uint64_t avr_wdt_read(void *opaque, hwaddr offset, unsigned size)
> +{
> +    assert(size == 1);
> +    AVRWatchdogState *wdt = opaque;
> +    uint8_t retval = wdt->csr;
> +
> +    trace_avr_wdt_read(offset, retval);
> +
> +    return (uint64_t)retval;
> +}
> +
> +static void avr_wdt_write(void *opaque, hwaddr offset,
> +                              uint64_t val64, unsigned size)
> +{
> +    assert(size == 1);
> +    AVRWatchdogState *wdt = opaque;
> +    uint8_t val8 = (uint8_t)val64;
> +
> +    trace_avr_wdt_write(offset, val8);
> +
> +    wdt->csr = val8;
> +    avr_wdt_reset_alarm(wdt);
> +}
> +
> +static const MemoryRegionOps avr_wdt_ops = {
> +    .read = avr_wdt_read,
> +    .write = avr_wdt_write,
> +    .endianness = DEVICE_NATIVE_ENDIAN,
> +    .impl = {.max_access_size = 1}
> +};
> +
> +static void avr_wdt_wdr(void *opaque, int irq, int level)
> +{
> +    AVRWatchdogState *wdt = AVR_WDT(opaque);
> +
> +    avr_wdt_reset_alarm(wdt);
> +}
> +
> +static void avr_wdt_init(Object *obj)
> +{
> +    AVRWatchdogState *s = AVR_WDT(obj);
> +
> +    sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
> +
> +    memory_region_init_io(&s->iomem, obj, &avr_wdt_ops,
> +                          s, "avr-wdt", 0xa);
> +
> +    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
> +    qdev_init_gpio_in_named(DEVICE(s), avr_wdt_wdr, "wdr", 1);
> +}
> +
> +static void avr_wdt_realize(DeviceState *dev, Error **errp)
> +{
> +    AVRWatchdogState *s = AVR_WDT(dev);
> +
> +    s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, avr_wdt_interrupt, s);
> +}
> +
> +static void avr_wdt_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->reset = avr_wdt_reset;
> +    dc->realize = avr_wdt_realize;
> +}
> +
> +static const TypeInfo avr_wdt_info = {
> +    .name          = TYPE_AVR_WDT,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(AVRWatchdogState),
> +    .instance_init = avr_wdt_init,
> +    .class_init    = avr_wdt_class_init,
> +};
> +
> +static void avr_wdt_register_types(void)
> +{
> +    type_register_static(&avr_wdt_info);
> +}
> +
> +type_init(avr_wdt_register_types)
> diff --git a/hw/watchdog/meson.build b/hw/watchdog/meson.build
> index 054c403dea..8db2be8317 100644
> --- a/hw/watchdog/meson.build
> +++ b/hw/watchdog/meson.build
> @@ -6,3 +6,5 @@ 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_AVR_WDT', if_true: files('avr_wdt.c'))
> diff --git a/hw/watchdog/trace-events b/hw/watchdog/trace-events
> index 3124ca1f1b..ac14773179 100644
> --- a/hw/watchdog/trace-events
> +++ b/hw/watchdog/trace-events
> @@ -5,3 +5,8 @@ cmsdk_apb_watchdog_read(uint64_t offset, uint64_t data, unsigned size) "CMSDK AP
>   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
> +
> +# avr_wdt.c
> +avr_wdt_read(uint8_t addr, uint8_t value) "wdt read addr:%u value:%u"
> +avr_wdt_write(uint8_t addr, uint8_t value) "wdt write addr:%u value:%u"
> +avr_wdt_interrupt(void) ""
> diff --git a/include/hw/watchdog/avr_wdt.h b/include/hw/watchdog/avr_wdt.h
> new file mode 100644
> index 0000000000..2679e8f2a6
> --- /dev/null
> +++ b/include/hw/watchdog/avr_wdt.h
> @@ -0,0 +1,47 @@
> +/*
> + * AVR 16-bit timer

AVR Watchdog?

> + *
> + * Copyright (c) 2021 Michael Rolnik
> + *
> + * 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/lgpl-2.1.html>
> + */
> +
> +#ifndef HW_WATCHDOG_AVR_WDT_H
> +#define HW_WATCHDOG_AVR_WDT_H
> +
> +#include "hw/sysbus.h"
> +#include "qemu/timer.h"
> +#include "hw/hw.h"
> +#include "qom/object.h"
> +
> +#define TYPE_AVR_WDT "avr-wdt"
> +OBJECT_DECLARE_SIMPLE_TYPE(AVRWatchdogState, AVR_WDT)
> +
> +struct AVRWatchdogState {
> +    /* <private> */
> +    SysBusDevice parent_obj;
> +
> +    /* <public> */
> +    MemoryRegion iomem;
> +    MemoryRegion imsk_iomem;
> +    MemoryRegion ifr_iomem;
> +    QEMUTimer *timer;
> +    qemu_irq irq;
> +
> +    /* registers */
> +    uint8_t csr;
> +};
> +
> +#endif /* HW_WATCHDOG_AVR_WDT_H */
> diff --git a/target/avr/cpu.c b/target/avr/cpu.c
> index 0f4596932b..d5eb785833 100644
> --- a/target/avr/cpu.c
> +++ b/target/avr/cpu.c
> @@ -131,6 +131,9 @@ static void avr_cpu_initfn(Object *obj)
>       /* Set the number of interrupts supported by the CPU. */
>       qdev_init_gpio_in(DEVICE(cpu), avr_cpu_set_int,
>                         sizeof(cpu->env.intsrc) * 8);
> +
> +    /* register watchdog timer reset interrupt */
> +    qdev_init_gpio_out_named(DEVICE(cpu), &cpu->wdr, "wdr", 1);
>   }
>   
>   static ObjectClass *avr_cpu_class_by_name(const char *cpu_model)
> diff --git a/target/avr/cpu.h b/target/avr/cpu.h
> index d148e8c75a..f8f5641c8b 100644
> --- a/target/avr/cpu.h
> +++ b/target/avr/cpu.h
> @@ -152,6 +152,7 @@ typedef struct AVRCPU {
>   
>       CPUNegativeOffsetState neg;
>       CPUAVRState env;
> +    qemu_irq wdr; /* reset WDT */
>   } AVRCPU;
>   
>   extern const struct VMStateDescription vms_avr_cpu;
> diff --git a/target/avr/helper.c b/target/avr/helper.c
> index 35e1019594..dd88057e5f 100644
> --- a/target/avr/helper.c
> +++ b/target/avr/helper.c
> @@ -24,6 +24,7 @@
>   #include "exec/exec-all.h"
>   #include "exec/address-spaces.h"
>   #include "exec/helper-proto.h"
> +#include "hw/irq.h"
>   
>   bool avr_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
>   {
> @@ -188,11 +189,9 @@ void helper_break(CPUAVRState *env)
>   
>   void helper_wdr(CPUAVRState *env)
>   {
> -    CPUState *cs = env_cpu(env);
> +    AVRCPU *cpu = env_archcpu(env);
>   
> -    /* WD is not implemented yet, placeholder */
> -    cs->exception_index = EXCP_DEBUG;
> -    cpu_loop_exit(cs);
> +    qemu_set_irq(cpu->wdr, 1);
>   }
>   
>   /*
> 

Thanks!


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [RFC 1/1] Implement AVR watchdog timer
  2021-05-02 20:10 [RFC 0/1] Implement AVR WDT (watchdog timer) Michael Rolnik
@ 2021-05-02 20:10 ` Michael Rolnik
  2021-05-03 13:15   ` Fred Konrad
  0 siblings, 1 reply; 9+ messages in thread
From: Michael Rolnik @ 2021-05-02 20:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: Michael Rolnik, me, konrad, richard.henderson, f4bug

Signed-off-by: Michael Rolnik <mrolnik@gmail.com>
---
 hw/avr/Kconfig                |   1 +
 hw/avr/atmega.c               |  15 ++-
 hw/avr/atmega.h               |   2 +
 hw/watchdog/Kconfig           |   3 +
 hw/watchdog/avr_wdt.c         | 190 ++++++++++++++++++++++++++++++++++
 hw/watchdog/meson.build       |   2 +
 hw/watchdog/trace-events      |   5 +
 include/hw/watchdog/avr_wdt.h |  47 +++++++++
 target/avr/cpu.c              |   3 +
 target/avr/cpu.h              |   1 +
 target/avr/helper.c           |   7 +-
 11 files changed, 271 insertions(+), 5 deletions(-)
 create mode 100644 hw/watchdog/avr_wdt.c
 create mode 100644 include/hw/watchdog/avr_wdt.h

diff --git a/hw/avr/Kconfig b/hw/avr/Kconfig
index d31298c3cc..9939e4902f 100644
--- a/hw/avr/Kconfig
+++ b/hw/avr/Kconfig
@@ -3,6 +3,7 @@ config AVR_ATMEGA_MCU
     select AVR_TIMER16
     select AVR_USART
     select AVR_POWER
+    select AVR_WDT
 
 config ARDUINO
     select AVR_ATMEGA_MCU
diff --git a/hw/avr/atmega.c b/hw/avr/atmega.c
index 44c6afebbb..31ceb1c21c 100644
--- a/hw/avr/atmega.c
+++ b/hw/avr/atmega.c
@@ -28,6 +28,7 @@ enum AtmegaPeripheral {
     GPIOG, GPIOH, GPIOI, GPIOJ, GPIOK, GPIOL,
     USART0, USART1, USART2, USART3,
     TIMER0, TIMER1, TIMER2, TIMER3, TIMER4, TIMER5,
+    WDT,
     PERIFMAX
 };
 
@@ -75,6 +76,7 @@ static const peripheral_cfg dev168_328[PERIFMAX] = {
     [GPIOD]         = {  0x29 },
     [GPIOC]         = {  0x26 },
     [GPIOB]         = {  0x23 },
+    [WDT]           = {  0x60 },
 }, dev1280_2560[PERIFMAX] = {
     [USART3]        = { 0x130, POWER1, 2 },
     [TIMER5]        = { 0x120, POWER1, 5, 0x73, 0x3a, true },
@@ -99,6 +101,7 @@ static const peripheral_cfg dev168_328[PERIFMAX] = {
     [GPIOC]         = {  0x26 },
     [GPIOB]         = {  0x23 },
     [GPIOA]         = {  0x20 },
+    [WDT]           = {  0x60 },
 };
 
 enum AtmegaIrq {
@@ -118,6 +121,7 @@ enum AtmegaIrq {
         TIMER4_COMPC_IRQ, TIMER4_OVF_IRQ,
     TIMER5_CAPT_IRQ, TIMER5_COMPA_IRQ, TIMER5_COMPB_IRQ,
         TIMER5_COMPC_IRQ, TIMER5_OVF_IRQ,
+    WATCHDOG_TIMER_IRQ,
     IRQ_COUNT
 };
 
@@ -133,6 +137,7 @@ enum AtmegaIrq {
 #define TIMER_OVF_IRQ(n)    (n * TIMER_IRQ_COUNT + TIMER0_OVF_IRQ)
 
 static const uint8_t irq168_328[IRQ_COUNT] = {
+    [WATCHDOG_TIMER_IRQ]    = 7,
     [TIMER2_COMPA_IRQ]      = 8,
     [TIMER2_COMPB_IRQ]      = 9,
     [TIMER2_OVF_IRQ]        = 10,
@@ -147,6 +152,7 @@ static const uint8_t irq168_328[IRQ_COUNT] = {
     [USART0_DRE_IRQ]        = 20,
     [USART0_TXC_IRQ]        = 21,
 }, irq1280_2560[IRQ_COUNT] = {
+    [WATCHDOG_TIMER_IRQ]    = 13,
     [TIMER2_COMPA_IRQ]      = 14,
     [TIMER2_COMPB_IRQ]      = 15,
     [TIMER2_OVF_IRQ]        = 16,
@@ -344,10 +350,17 @@ static void atmega_realize(DeviceState *dev, Error **errp)
         g_free(devname);
     }
 
+    /* Watchdog Timer */
+    object_initialize_child(OBJECT(dev), "wdt", &s->wdt, TYPE_AVR_WDT);
+    sysbus_realize(SYS_BUS_DEVICE(&s->wdt), &error_abort);
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt), 0,
+                    OFFSET_DATA + mc->dev[WDT].addr);
+    qdev_connect_gpio_out_named(cpudev, "wdr", 0,
+                    qdev_get_gpio_in_named(DEVICE(&s->wdt), "wdr", 0));
+
     create_unimplemented_device("avr-twi",          OFFSET_DATA + 0x0b8, 6);
     create_unimplemented_device("avr-adc",          OFFSET_DATA + 0x078, 8);
     create_unimplemented_device("avr-ext-mem-ctrl", OFFSET_DATA + 0x074, 2);
-    create_unimplemented_device("avr-watchdog",     OFFSET_DATA + 0x060, 1);
     create_unimplemented_device("avr-spi",          OFFSET_DATA + 0x04c, 3);
     create_unimplemented_device("avr-eeprom",       OFFSET_DATA + 0x03f, 3);
 }
diff --git a/hw/avr/atmega.h b/hw/avr/atmega.h
index a99ee15c7e..60bbd44bdd 100644
--- a/hw/avr/atmega.h
+++ b/hw/avr/atmega.h
@@ -13,6 +13,7 @@
 
 #include "hw/char/avr_usart.h"
 #include "hw/timer/avr_timer16.h"
+#include "hw/watchdog/avr_wdt.h"
 #include "hw/misc/avr_power.h"
 #include "target/avr/cpu.h"
 #include "qom/object.h"
@@ -45,6 +46,7 @@ struct AtmegaMcuState {
     AVRMaskState pwr[POWER_MAX];
     AVRUsartState usart[USART_MAX];
     AVRTimer16State timer[TIMER_MAX];
+    AVRWatchdogState wdt;
     uint64_t xtal_freq_hz;
 };
 
diff --git a/hw/watchdog/Kconfig b/hw/watchdog/Kconfig
index 66e1d029e3..e0f89d2fe0 100644
--- a/hw/watchdog/Kconfig
+++ b/hw/watchdog/Kconfig
@@ -20,3 +20,6 @@ config WDT_IMX2
 
 config WDT_SBSA
     bool
+
+config AVR_WDT
+    bool
diff --git a/hw/watchdog/avr_wdt.c b/hw/watchdog/avr_wdt.c
new file mode 100644
index 0000000000..4ce1029a64
--- /dev/null
+++ b/hw/watchdog/avr_wdt.c
@@ -0,0 +1,190 @@
+/*
+ * AVR watchdog
+ *
+ * Copyright (c) 2018 Michael Rolnik
+ *
+ * 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/lgpl-2.1.html>
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "hw/irq.h"
+#include "hw/watchdog/avr_wdt.h"
+#include "trace.h"
+
+/* Field masks */
+#define WDTCSR_MASK_WDP0     0x01
+#define WDTCSR_MASK_WDP1     0x02
+#define WDTCSR_MASK_WDP2     0x04
+#define WDTCSR_MASK_WDE      0x08
+#define WDTCSR_MASK_WCE      0x10
+#define WDTCSR_MASK_WDP3     0x20
+#define WDTCSR_MASK_WDIE     0x40
+#define WDTCSR_MASK_WDIF     0x80
+
+#define WDTCSR_SHFT_WDP0     0x00
+#define WDTCSR_SHFT_WDP1     0x01
+#define WDTCSR_SHFT_WDP2     0x02
+#define WDTCSR_SHFT_WDE      0x03
+#define WDTCSR_SHFT_WCE      0x04
+#define WDTCSR_SHFT_WDP3     0x05
+#define WDTCSR_SHFT_WDIE     0x06
+#define WDTCSR_SHFT_WDIF     0x07
+
+/* Helper macros */
+#define WDP0(csr)       ((csr & WDTCSR_MASK_WDP0) >> WDTCSR_SHFT_WDP0)
+#define WDP1(csr)       ((csr & WDTCSR_MASK_WDP1) >> WDTCSR_SHFT_WDP1)
+#define WDP2(csr)       ((csr & WDTCSR_MASK_WDP2) >> WDTCSR_SHFT_WDP2)
+#define WDP3(csr)       ((csr & WDTCSR_MASK_WDP3) >> WDTCSR_SHFT_WDP3)
+#define WDP(csr)        ((WDP3(csr) << 3) | (WDP2(csr) << 2) | \
+                         (WDP1(csr) << 1) | (WDP0(csr) << 0))
+#define WDIE(csr)       ((csr & WDTCSR_MASK_WDIE) >> WDTCSR_SHFT_WDIE)
+#define WDE(csr)        ((csr & WDTCSR_MASK_WDE) >> WDTCSR_SHFT_WDE)
+#define WCE(csr)        ((csr & WDTCSR_MASK_WCE) >> WDTCSR_SHFT_WCE)
+
+#define DB_PRINT(fmt, args...) /* Nothing */
+
+#define MS2NS(n)        ((n) * 1000000ull)
+
+static void avr_wdt_reset_alarm(AVRWatchdogState *wdt)
+{
+    uint32_t csr = wdt->csr;
+    int wdp = WDP(csr);
+    assert(wdp <= 9);
+
+    if (WDIE(csr) == 0 && WDE(csr) == 0) {
+        /* watchdog is stopped */
+        return;
+    }
+
+    timer_mod_ns(wdt->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+            (MS2NS(15) << wdp));
+}
+
+static void avr_wdt_interrupt(void *opaque)
+{
+    AVRWatchdogState *wdt = opaque;
+    int8_t csr = wdt->csr;
+
+    if (WDE(csr) == 0 && WDIE(csr) == 0) {
+        /* Stopped */
+
+    } else if (WDE(csr) == 0 && WDIE(csr) == 1) {
+        /* Interrupt Mode */
+        wdt->csr |= WDTCSR_MASK_WDIF;
+        qemu_set_irq(wdt->irq, 1);
+        trace_avr_wdt_interrupt();
+    } else if (WDE(csr) == 1 && WDIE(csr) == 0) {
+        /* System Reset Mode */
+    } else if (WDE(csr) == 1 && WDIE(csr) == 1) {
+        /* Interrupt and System Reset Mode */
+        wdt->csr |= WDTCSR_MASK_WDIF;
+        qemu_set_irq(wdt->irq, 1);
+        trace_avr_wdt_interrupt();
+    }
+
+    avr_wdt_reset_alarm(wdt);
+}
+
+static void avr_wdt_reset(DeviceState *dev)
+{
+    AVRWatchdogState *wdt = AVR_WDT(dev);
+
+    wdt->csr = 0;
+    qemu_set_irq(wdt->irq, 0);
+    avr_wdt_reset_alarm(wdt);
+}
+
+static uint64_t avr_wdt_read(void *opaque, hwaddr offset, unsigned size)
+{
+    assert(size == 1);
+    AVRWatchdogState *wdt = opaque;
+    uint8_t retval = wdt->csr;
+
+    trace_avr_wdt_read(offset, retval);
+
+    return (uint64_t)retval;
+}
+
+static void avr_wdt_write(void *opaque, hwaddr offset,
+                              uint64_t val64, unsigned size)
+{
+    assert(size == 1);
+    AVRWatchdogState *wdt = opaque;
+    uint8_t val8 = (uint8_t)val64;
+
+    trace_avr_wdt_write(offset, val8);
+
+    wdt->csr = val8;
+    avr_wdt_reset_alarm(wdt);
+}
+
+static const MemoryRegionOps avr_wdt_ops = {
+    .read = avr_wdt_read,
+    .write = avr_wdt_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .impl = {.max_access_size = 1}
+};
+
+static void avr_wdt_wdr(void *opaque, int irq, int level)
+{
+    AVRWatchdogState *wdt = AVR_WDT(opaque);
+
+    avr_wdt_reset_alarm(wdt);
+}
+
+static void avr_wdt_init(Object *obj)
+{
+    AVRWatchdogState *s = AVR_WDT(obj);
+
+    sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+
+    memory_region_init_io(&s->iomem, obj, &avr_wdt_ops,
+                          s, "avr-wdt", 0xa);
+
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+    qdev_init_gpio_in_named(DEVICE(s), avr_wdt_wdr, "wdr", 1);
+}
+
+static void avr_wdt_realize(DeviceState *dev, Error **errp)
+{
+    AVRWatchdogState *s = AVR_WDT(dev);
+
+    s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, avr_wdt_interrupt, s);
+}
+
+static void avr_wdt_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->reset = avr_wdt_reset;
+    dc->realize = avr_wdt_realize;
+}
+
+static const TypeInfo avr_wdt_info = {
+    .name          = TYPE_AVR_WDT,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(AVRWatchdogState),
+    .instance_init = avr_wdt_init,
+    .class_init    = avr_wdt_class_init,
+};
+
+static void avr_wdt_register_types(void)
+{
+    type_register_static(&avr_wdt_info);
+}
+
+type_init(avr_wdt_register_types)
diff --git a/hw/watchdog/meson.build b/hw/watchdog/meson.build
index 054c403dea..8db2be8317 100644
--- a/hw/watchdog/meson.build
+++ b/hw/watchdog/meson.build
@@ -6,3 +6,5 @@ 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_AVR_WDT', if_true: files('avr_wdt.c'))
diff --git a/hw/watchdog/trace-events b/hw/watchdog/trace-events
index 3124ca1f1b..ac14773179 100644
--- a/hw/watchdog/trace-events
+++ b/hw/watchdog/trace-events
@@ -5,3 +5,8 @@ cmsdk_apb_watchdog_read(uint64_t offset, uint64_t data, unsigned size) "CMSDK AP
 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
+
+# avr_wdt.c
+avr_wdt_read(uint8_t addr, uint8_t value) "wdt read addr:%u value:%u"
+avr_wdt_write(uint8_t addr, uint8_t value) "wdt write addr:%u value:%u"
+avr_wdt_interrupt(void) ""
diff --git a/include/hw/watchdog/avr_wdt.h b/include/hw/watchdog/avr_wdt.h
new file mode 100644
index 0000000000..2679e8f2a6
--- /dev/null
+++ b/include/hw/watchdog/avr_wdt.h
@@ -0,0 +1,47 @@
+/*
+ * AVR 16-bit timer
+ *
+ * Copyright (c) 2021 Michael Rolnik
+ *
+ * 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/lgpl-2.1.html>
+ */
+
+#ifndef HW_WATCHDOG_AVR_WDT_H
+#define HW_WATCHDOG_AVR_WDT_H
+
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "hw/hw.h"
+#include "qom/object.h"
+
+#define TYPE_AVR_WDT "avr-wdt"
+OBJECT_DECLARE_SIMPLE_TYPE(AVRWatchdogState, AVR_WDT)
+
+struct AVRWatchdogState {
+    /* <private> */
+    SysBusDevice parent_obj;
+
+    /* <public> */
+    MemoryRegion iomem;
+    MemoryRegion imsk_iomem;
+    MemoryRegion ifr_iomem;
+    QEMUTimer *timer;
+    qemu_irq irq;
+
+    /* registers */
+    uint8_t csr;
+};
+
+#endif /* HW_WATCHDOG_AVR_WDT_H */
diff --git a/target/avr/cpu.c b/target/avr/cpu.c
index 0f4596932b..d5eb785833 100644
--- a/target/avr/cpu.c
+++ b/target/avr/cpu.c
@@ -131,6 +131,9 @@ static void avr_cpu_initfn(Object *obj)
     /* Set the number of interrupts supported by the CPU. */
     qdev_init_gpio_in(DEVICE(cpu), avr_cpu_set_int,
                       sizeof(cpu->env.intsrc) * 8);
+
+    /* register watchdog timer reset interrupt */
+    qdev_init_gpio_out_named(DEVICE(cpu), &cpu->wdr, "wdr", 1);
 }
 
 static ObjectClass *avr_cpu_class_by_name(const char *cpu_model)
diff --git a/target/avr/cpu.h b/target/avr/cpu.h
index d148e8c75a..f8f5641c8b 100644
--- a/target/avr/cpu.h
+++ b/target/avr/cpu.h
@@ -152,6 +152,7 @@ typedef struct AVRCPU {
 
     CPUNegativeOffsetState neg;
     CPUAVRState env;
+    qemu_irq wdr; /* reset WDT */
 } AVRCPU;
 
 extern const struct VMStateDescription vms_avr_cpu;
diff --git a/target/avr/helper.c b/target/avr/helper.c
index 35e1019594..dd88057e5f 100644
--- a/target/avr/helper.c
+++ b/target/avr/helper.c
@@ -24,6 +24,7 @@
 #include "exec/exec-all.h"
 #include "exec/address-spaces.h"
 #include "exec/helper-proto.h"
+#include "hw/irq.h"
 
 bool avr_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
 {
@@ -188,11 +189,9 @@ void helper_break(CPUAVRState *env)
 
 void helper_wdr(CPUAVRState *env)
 {
-    CPUState *cs = env_cpu(env);
+    AVRCPU *cpu = env_archcpu(env);
 
-    /* WD is not implemented yet, placeholder */
-    cs->exception_index = EXCP_DEBUG;
-    cpu_loop_exit(cs);
+    qemu_set_irq(cpu->wdr, 1);
 }
 
 /*
-- 
2.25.1



^ permalink raw reply related	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2021-05-15  9:47 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-05-05 21:18 [RFC v1 0/1] Implement AVR WDT (watchdog timer) Michael Rolnik
2021-05-05 21:18 ` [RFC 1/1] Implement AVR watchdog timer Michael Rolnik
2021-05-13 12:27   ` Pavel Dovgalyuk
2021-05-14  7:20     ` Michael Rolnik
  -- strict thread matches above, loose matches on Subject: below --
2021-05-02 20:10 [RFC 0/1] Implement AVR WDT (watchdog timer) Michael Rolnik
2021-05-02 20:10 ` [RFC 1/1] Implement AVR watchdog timer Michael Rolnik
2021-05-03 13:15   ` Fred Konrad
2021-05-03 13:36     ` Michael Rolnik
2021-05-03 20:08       ` Michael Rolnik
2021-05-15  9:45         ` Philippe Mathieu-Daudé

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).