qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [RFC 1/1] Implement AVR watchdog timer


From: Fred Konrad
Subject: Re: [RFC 1/1] Implement AVR watchdog timer
Date: Mon, 3 May 2021 15:15:48 +0200
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.10.0



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!



reply via email to

[Prev in Thread] Current Thread [Next in Thread]