xref: /qemu/hw/watchdog/sbsa_gwdt.c (revision 06b40d250ecfa1633209c2e431a7a38acfd03a98)
14204c5f7SShashi Mallela /*
24204c5f7SShashi Mallela  * Generic watchdog device model for SBSA
34204c5f7SShashi Mallela  *
44204c5f7SShashi Mallela  * The watchdog device has been implemented as revision 1 variant of
54204c5f7SShashi Mallela  * the ARM SBSA specification v6.0
64204c5f7SShashi Mallela  * (https://developer.arm.com/documentation/den0029/d?lang=en)
74204c5f7SShashi Mallela  *
84204c5f7SShashi Mallela  * Copyright Linaro.org 2020
94204c5f7SShashi Mallela  *
104204c5f7SShashi Mallela  * Authors:
114204c5f7SShashi Mallela  *  Shashi Mallela <shashi.mallela@linaro.org>
124204c5f7SShashi Mallela  *
134204c5f7SShashi Mallela  * This work is licensed under the terms of the GNU GPL, version 2 or (at your
144204c5f7SShashi Mallela  * option) any later version.  See the COPYING file in the top-level directory.
154204c5f7SShashi Mallela  *
164204c5f7SShashi Mallela  */
174204c5f7SShashi Mallela 
184204c5f7SShashi Mallela #include "qemu/osdep.h"
1932cad1ffSPhilippe Mathieu-Daudé #include "system/reset.h"
2032cad1ffSPhilippe Mathieu-Daudé #include "system/watchdog.h"
2188c756bcSPeter Maydell #include "hw/qdev-properties.h"
224204c5f7SShashi Mallela #include "hw/watchdog/sbsa_gwdt.h"
234204c5f7SShashi Mallela #include "qemu/timer.h"
244204c5f7SShashi Mallela #include "migration/vmstate.h"
254204c5f7SShashi Mallela #include "qemu/log.h"
264204c5f7SShashi Mallela #include "qemu/module.h"
274204c5f7SShashi Mallela 
284204c5f7SShashi Mallela static const VMStateDescription vmstate_sbsa_gwdt = {
294204c5f7SShashi Mallela     .name = "sbsa-gwdt",
304204c5f7SShashi Mallela     .version_id = 1,
314204c5f7SShashi Mallela     .minimum_version_id = 1,
3245bc669eSRichard Henderson     .fields = (const VMStateField[]) {
334204c5f7SShashi Mallela         VMSTATE_TIMER_PTR(timer, SBSA_GWDTState),
344204c5f7SShashi Mallela         VMSTATE_UINT32(wcs, SBSA_GWDTState),
354204c5f7SShashi Mallela         VMSTATE_UINT32(worl, SBSA_GWDTState),
364204c5f7SShashi Mallela         VMSTATE_UINT32(woru, SBSA_GWDTState),
374204c5f7SShashi Mallela         VMSTATE_UINT32(wcvl, SBSA_GWDTState),
384204c5f7SShashi Mallela         VMSTATE_UINT32(wcvu, SBSA_GWDTState),
394204c5f7SShashi Mallela         VMSTATE_END_OF_LIST()
404204c5f7SShashi Mallela     }
414204c5f7SShashi Mallela };
424204c5f7SShashi Mallela 
434204c5f7SShashi Mallela typedef enum WdtRefreshType {
444204c5f7SShashi Mallela     EXPLICIT_REFRESH = 0,
454204c5f7SShashi Mallela     TIMEOUT_REFRESH = 1,
464204c5f7SShashi Mallela } WdtRefreshType;
474204c5f7SShashi Mallela 
sbsa_gwdt_rread(void * opaque,hwaddr addr,unsigned int size)484204c5f7SShashi Mallela static uint64_t sbsa_gwdt_rread(void *opaque, hwaddr addr, unsigned int size)
494204c5f7SShashi Mallela {
504204c5f7SShashi Mallela     SBSA_GWDTState *s = SBSA_GWDT(opaque);
514204c5f7SShashi Mallela     uint32_t ret = 0;
524204c5f7SShashi Mallela 
534204c5f7SShashi Mallela     switch (addr) {
544204c5f7SShashi Mallela     case SBSA_GWDT_WRR:
554204c5f7SShashi Mallela         /* watch refresh read has no effect and returns 0 */
564204c5f7SShashi Mallela         ret = 0;
574204c5f7SShashi Mallela         break;
584204c5f7SShashi Mallela     case SBSA_GWDT_W_IIDR:
594204c5f7SShashi Mallela         ret = s->id;
604204c5f7SShashi Mallela         break;
614204c5f7SShashi Mallela     default:
624204c5f7SShashi Mallela         qemu_log_mask(LOG_GUEST_ERROR, "bad address in refresh frame read :"
634204c5f7SShashi Mallela                         " 0x%x\n", (int)addr);
644204c5f7SShashi Mallela     }
654204c5f7SShashi Mallela     return ret;
664204c5f7SShashi Mallela }
674204c5f7SShashi Mallela 
sbsa_gwdt_read(void * opaque,hwaddr addr,unsigned int size)684204c5f7SShashi Mallela static uint64_t sbsa_gwdt_read(void *opaque, hwaddr addr, unsigned int size)
694204c5f7SShashi Mallela {
704204c5f7SShashi Mallela     SBSA_GWDTState *s = SBSA_GWDT(opaque);
714204c5f7SShashi Mallela     uint32_t ret = 0;
724204c5f7SShashi Mallela 
734204c5f7SShashi Mallela     switch (addr) {
744204c5f7SShashi Mallela     case SBSA_GWDT_WCS:
754204c5f7SShashi Mallela         ret = s->wcs;
764204c5f7SShashi Mallela         break;
774204c5f7SShashi Mallela     case SBSA_GWDT_WOR:
784204c5f7SShashi Mallela         ret = s->worl;
794204c5f7SShashi Mallela         break;
804204c5f7SShashi Mallela     case SBSA_GWDT_WORU:
814204c5f7SShashi Mallela          ret = s->woru;
824204c5f7SShashi Mallela          break;
834204c5f7SShashi Mallela     case SBSA_GWDT_WCV:
844204c5f7SShashi Mallela         ret = s->wcvl;
854204c5f7SShashi Mallela         break;
864204c5f7SShashi Mallela     case SBSA_GWDT_WCVU:
874204c5f7SShashi Mallela         ret = s->wcvu;
884204c5f7SShashi Mallela         break;
894204c5f7SShashi Mallela     case SBSA_GWDT_W_IIDR:
904204c5f7SShashi Mallela         ret = s->id;
914204c5f7SShashi Mallela         break;
924204c5f7SShashi Mallela     default:
934204c5f7SShashi Mallela         qemu_log_mask(LOG_GUEST_ERROR, "bad address in control frame read :"
944204c5f7SShashi Mallela                         " 0x%x\n", (int)addr);
954204c5f7SShashi Mallela     }
964204c5f7SShashi Mallela     return ret;
974204c5f7SShashi Mallela }
984204c5f7SShashi Mallela 
sbsa_gwdt_update_timer(SBSA_GWDTState * s,WdtRefreshType rtype)994204c5f7SShashi Mallela static void sbsa_gwdt_update_timer(SBSA_GWDTState *s, WdtRefreshType rtype)
1004204c5f7SShashi Mallela {
1014204c5f7SShashi Mallela     uint64_t timeout = 0;
1024204c5f7SShashi Mallela 
1034204c5f7SShashi Mallela     timer_del(s->timer);
1044204c5f7SShashi Mallela 
1054204c5f7SShashi Mallela     if (s->wcs & SBSA_GWDT_WCS_EN) {
1064204c5f7SShashi Mallela         /*
1074204c5f7SShashi Mallela          * Extract the upper 16 bits from woru & 32 bits from worl
1084204c5f7SShashi Mallela          * registers to construct the 48 bit offset value
1094204c5f7SShashi Mallela          */
1104204c5f7SShashi Mallela         timeout = s->woru;
1114204c5f7SShashi Mallela         timeout <<= 32;
1124204c5f7SShashi Mallela         timeout |= s->worl;
11388c756bcSPeter Maydell         timeout = muldiv64(timeout, NANOSECONDS_PER_SECOND, s->freq);
1144204c5f7SShashi Mallela         timeout += qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
1154204c5f7SShashi Mallela 
1164204c5f7SShashi Mallela         if ((rtype == EXPLICIT_REFRESH) || ((rtype == TIMEOUT_REFRESH) &&
1174204c5f7SShashi Mallela                 (!(s->wcs & SBSA_GWDT_WCS_WS0)))) {
1184204c5f7SShashi Mallela             /* store the current timeout value into compare registers */
1194204c5f7SShashi Mallela             s->wcvu = timeout >> 32;
1204204c5f7SShashi Mallela             s->wcvl = timeout;
1214204c5f7SShashi Mallela         }
1224204c5f7SShashi Mallela         timer_mod(s->timer, timeout);
1234204c5f7SShashi Mallela     }
1244204c5f7SShashi Mallela }
1254204c5f7SShashi Mallela 
sbsa_gwdt_rwrite(void * opaque,hwaddr offset,uint64_t data,unsigned size)1264204c5f7SShashi Mallela static void sbsa_gwdt_rwrite(void *opaque, hwaddr offset, uint64_t data,
1274204c5f7SShashi Mallela                              unsigned size) {
1284204c5f7SShashi Mallela     SBSA_GWDTState *s = SBSA_GWDT(opaque);
1294204c5f7SShashi Mallela 
1304204c5f7SShashi Mallela     if (offset == SBSA_GWDT_WRR) {
1314204c5f7SShashi Mallela         s->wcs &= ~(SBSA_GWDT_WCS_WS0 | SBSA_GWDT_WCS_WS1);
1324204c5f7SShashi Mallela 
1334204c5f7SShashi Mallela         sbsa_gwdt_update_timer(s, EXPLICIT_REFRESH);
1344204c5f7SShashi Mallela     } else {
1354204c5f7SShashi Mallela         qemu_log_mask(LOG_GUEST_ERROR, "bad address in refresh frame write :"
1364204c5f7SShashi Mallela                         " 0x%x\n", (int)offset);
1374204c5f7SShashi Mallela     }
1384204c5f7SShashi Mallela }
1394204c5f7SShashi Mallela 
sbsa_gwdt_write(void * opaque,hwaddr offset,uint64_t data,unsigned size)1404204c5f7SShashi Mallela static void sbsa_gwdt_write(void *opaque, hwaddr offset, uint64_t data,
1414204c5f7SShashi Mallela                              unsigned size) {
1424204c5f7SShashi Mallela     SBSA_GWDTState *s = SBSA_GWDT(opaque);
1434204c5f7SShashi Mallela 
1444204c5f7SShashi Mallela     switch (offset) {
1454204c5f7SShashi Mallela     case SBSA_GWDT_WCS:
1464204c5f7SShashi Mallela         s->wcs = data & SBSA_GWDT_WCS_EN;
1474204c5f7SShashi Mallela         qemu_set_irq(s->irq, 0);
1484204c5f7SShashi Mallela         sbsa_gwdt_update_timer(s, EXPLICIT_REFRESH);
1494204c5f7SShashi Mallela         break;
1504204c5f7SShashi Mallela 
1514204c5f7SShashi Mallela     case SBSA_GWDT_WOR:
1524204c5f7SShashi Mallela         s->worl = data;
1534204c5f7SShashi Mallela         s->wcs &= ~(SBSA_GWDT_WCS_WS0 | SBSA_GWDT_WCS_WS1);
1544204c5f7SShashi Mallela         qemu_set_irq(s->irq, 0);
1554204c5f7SShashi Mallela         sbsa_gwdt_update_timer(s, EXPLICIT_REFRESH);
1564204c5f7SShashi Mallela         break;
1574204c5f7SShashi Mallela 
1584204c5f7SShashi Mallela     case SBSA_GWDT_WORU:
1594204c5f7SShashi Mallela         s->woru = data & SBSA_GWDT_WOR_MASK;
1604204c5f7SShashi Mallela         s->wcs &= ~(SBSA_GWDT_WCS_WS0 | SBSA_GWDT_WCS_WS1);
1614204c5f7SShashi Mallela         qemu_set_irq(s->irq, 0);
1624204c5f7SShashi Mallela         sbsa_gwdt_update_timer(s, EXPLICIT_REFRESH);
1634204c5f7SShashi Mallela         break;
1644204c5f7SShashi Mallela 
1654204c5f7SShashi Mallela     case SBSA_GWDT_WCV:
1664204c5f7SShashi Mallela         s->wcvl = data;
1674204c5f7SShashi Mallela         break;
1684204c5f7SShashi Mallela 
1694204c5f7SShashi Mallela     case SBSA_GWDT_WCVU:
1704204c5f7SShashi Mallela         s->wcvu = data;
1714204c5f7SShashi Mallela         break;
1724204c5f7SShashi Mallela 
1734204c5f7SShashi Mallela     default:
1744204c5f7SShashi Mallela         qemu_log_mask(LOG_GUEST_ERROR, "bad address in control frame write :"
1754204c5f7SShashi Mallela                 " 0x%x\n", (int)offset);
1764204c5f7SShashi Mallela     }
1774204c5f7SShashi Mallela }
1784204c5f7SShashi Mallela 
wdt_sbsa_gwdt_reset(DeviceState * dev)1794204c5f7SShashi Mallela static void wdt_sbsa_gwdt_reset(DeviceState *dev)
1804204c5f7SShashi Mallela {
1814204c5f7SShashi Mallela     SBSA_GWDTState *s = SBSA_GWDT(dev);
1824204c5f7SShashi Mallela 
1834204c5f7SShashi Mallela     timer_del(s->timer);
1844204c5f7SShashi Mallela 
1854204c5f7SShashi Mallela     s->wcs  = 0;
1864204c5f7SShashi Mallela     s->wcvl = 0;
1874204c5f7SShashi Mallela     s->wcvu = 0;
1884204c5f7SShashi Mallela     s->worl = 0;
1894204c5f7SShashi Mallela     s->woru = 0;
1904204c5f7SShashi Mallela     s->id = SBSA_GWDT_ID;
1914204c5f7SShashi Mallela }
1924204c5f7SShashi Mallela 
sbsa_gwdt_timer_sysinterrupt(void * opaque)1934204c5f7SShashi Mallela static void sbsa_gwdt_timer_sysinterrupt(void *opaque)
1944204c5f7SShashi Mallela {
1954204c5f7SShashi Mallela     SBSA_GWDTState *s = SBSA_GWDT(opaque);
1964204c5f7SShashi Mallela 
1974204c5f7SShashi Mallela     if (!(s->wcs & SBSA_GWDT_WCS_WS0)) {
1984204c5f7SShashi Mallela         s->wcs |= SBSA_GWDT_WCS_WS0;
1994204c5f7SShashi Mallela         sbsa_gwdt_update_timer(s, TIMEOUT_REFRESH);
2004204c5f7SShashi Mallela         qemu_set_irq(s->irq, 1);
2014204c5f7SShashi Mallela     } else {
2024204c5f7SShashi Mallela         s->wcs |= SBSA_GWDT_WCS_WS1;
2034204c5f7SShashi Mallela         qemu_log_mask(CPU_LOG_RESET, "Watchdog timer expired.\n");
2044204c5f7SShashi Mallela         /*
2054204c5f7SShashi Mallela          * Reset the watchdog only if the guest gets notified about
2064204c5f7SShashi Mallela          * expiry. watchdog_perform_action() may temporarily relinquish
2074204c5f7SShashi Mallela          * the BQL; reset before triggering the action to avoid races with
2084204c5f7SShashi Mallela          * sbsa_gwdt instructions.
2094204c5f7SShashi Mallela          */
2104204c5f7SShashi Mallela         switch (get_watchdog_action()) {
2114204c5f7SShashi Mallela         case WATCHDOG_ACTION_DEBUG:
2124204c5f7SShashi Mallela         case WATCHDOG_ACTION_NONE:
2134204c5f7SShashi Mallela         case WATCHDOG_ACTION_PAUSE:
2144204c5f7SShashi Mallela             break;
2154204c5f7SShashi Mallela         default:
2164204c5f7SShashi Mallela             wdt_sbsa_gwdt_reset(DEVICE(s));
2174204c5f7SShashi Mallela         }
2184204c5f7SShashi Mallela         watchdog_perform_action();
2194204c5f7SShashi Mallela     }
2204204c5f7SShashi Mallela }
2214204c5f7SShashi Mallela 
2224204c5f7SShashi Mallela static const MemoryRegionOps sbsa_gwdt_rops = {
2234204c5f7SShashi Mallela     .read = sbsa_gwdt_rread,
2244204c5f7SShashi Mallela     .write = sbsa_gwdt_rwrite,
2254204c5f7SShashi Mallela     .endianness = DEVICE_LITTLE_ENDIAN,
2264204c5f7SShashi Mallela     .valid.min_access_size = 4,
2274204c5f7SShashi Mallela     .valid.max_access_size = 4,
2284204c5f7SShashi Mallela     .valid.unaligned = false,
2294204c5f7SShashi Mallela };
2304204c5f7SShashi Mallela 
2314204c5f7SShashi Mallela static const MemoryRegionOps sbsa_gwdt_ops = {
2324204c5f7SShashi Mallela     .read = sbsa_gwdt_read,
2334204c5f7SShashi Mallela     .write = sbsa_gwdt_write,
2344204c5f7SShashi Mallela     .endianness = DEVICE_LITTLE_ENDIAN,
2354204c5f7SShashi Mallela     .valid.min_access_size = 4,
2364204c5f7SShashi Mallela     .valid.max_access_size = 4,
2374204c5f7SShashi Mallela     .valid.unaligned = false,
2384204c5f7SShashi Mallela };
2394204c5f7SShashi Mallela 
wdt_sbsa_gwdt_realize(DeviceState * dev,Error ** errp)2404204c5f7SShashi Mallela static void wdt_sbsa_gwdt_realize(DeviceState *dev, Error **errp)
2414204c5f7SShashi Mallela {
2424204c5f7SShashi Mallela     SBSA_GWDTState *s = SBSA_GWDT(dev);
2434204c5f7SShashi Mallela     SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
2444204c5f7SShashi Mallela 
2454204c5f7SShashi Mallela     memory_region_init_io(&s->rmmio, OBJECT(dev),
2464204c5f7SShashi Mallela                           &sbsa_gwdt_rops, s,
2474204c5f7SShashi Mallela                           "sbsa_gwdt.refresh",
2484204c5f7SShashi Mallela                           SBSA_GWDT_RMMIO_SIZE);
2494204c5f7SShashi Mallela 
2504204c5f7SShashi Mallela     memory_region_init_io(&s->cmmio, OBJECT(dev),
2514204c5f7SShashi Mallela                           &sbsa_gwdt_ops, s,
2524204c5f7SShashi Mallela                           "sbsa_gwdt.control",
2534204c5f7SShashi Mallela                           SBSA_GWDT_CMMIO_SIZE);
2544204c5f7SShashi Mallela 
2554204c5f7SShashi Mallela     sysbus_init_mmio(sbd, &s->rmmio);
2564204c5f7SShashi Mallela     sysbus_init_mmio(sbd, &s->cmmio);
2574204c5f7SShashi Mallela 
2584204c5f7SShashi Mallela     sysbus_init_irq(sbd, &s->irq);
2594204c5f7SShashi Mallela 
2604204c5f7SShashi Mallela     s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, sbsa_gwdt_timer_sysinterrupt,
2614204c5f7SShashi Mallela                 dev);
2624204c5f7SShashi Mallela }
2634204c5f7SShashi Mallela 
26489996947SRichard Henderson static const Property wdt_sbsa_gwdt_props[] = {
26588c756bcSPeter Maydell     /*
26688c756bcSPeter Maydell      * Timer frequency in Hz. This must match the frequency used by
26788c756bcSPeter Maydell      * the CPU's generic timer. Default 62.5Hz matches QEMU's legacy
26888c756bcSPeter Maydell      * CPU timer frequency default.
26988c756bcSPeter Maydell      */
27088c756bcSPeter Maydell     DEFINE_PROP_UINT64("clock-frequency", struct SBSA_GWDTState, freq,
27188c756bcSPeter Maydell                        62500000),
27288c756bcSPeter Maydell };
27388c756bcSPeter Maydell 
wdt_sbsa_gwdt_class_init(ObjectClass * klass,const void * data)274*12d1a768SPhilippe Mathieu-Daudé static void wdt_sbsa_gwdt_class_init(ObjectClass *klass, const void *data)
2754204c5f7SShashi Mallela {
2764204c5f7SShashi Mallela     DeviceClass *dc = DEVICE_CLASS(klass);
2774204c5f7SShashi Mallela 
2784204c5f7SShashi Mallela     dc->realize = wdt_sbsa_gwdt_realize;
279e3d08143SPeter Maydell     device_class_set_legacy_reset(dc, wdt_sbsa_gwdt_reset);
2804204c5f7SShashi Mallela     dc->hotpluggable = false;
281b10cb627SPaolo Bonzini     set_bit(DEVICE_CATEGORY_WATCHDOG, dc->categories);
2824204c5f7SShashi Mallela     dc->vmsd = &vmstate_sbsa_gwdt;
283b10cb627SPaolo Bonzini     dc->desc = "SBSA-compliant generic watchdog device";
28488c756bcSPeter Maydell     device_class_set_props(dc, wdt_sbsa_gwdt_props);
2854204c5f7SShashi Mallela }
2864204c5f7SShashi Mallela 
2874204c5f7SShashi Mallela static const TypeInfo wdt_sbsa_gwdt_info = {
2884204c5f7SShashi Mallela     .class_init = wdt_sbsa_gwdt_class_init,
2894204c5f7SShashi Mallela     .parent = TYPE_SYS_BUS_DEVICE,
2904204c5f7SShashi Mallela     .name  = TYPE_WDT_SBSA,
2914204c5f7SShashi Mallela     .instance_size  = sizeof(SBSA_GWDTState),
2924204c5f7SShashi Mallela };
2934204c5f7SShashi Mallela 
wdt_sbsa_gwdt_register_types(void)2944204c5f7SShashi Mallela static void wdt_sbsa_gwdt_register_types(void)
2954204c5f7SShashi Mallela {
2964204c5f7SShashi Mallela     type_register_static(&wdt_sbsa_gwdt_info);
2974204c5f7SShashi Mallela }
2984204c5f7SShashi Mallela 
2994204c5f7SShashi Mallela type_init(wdt_sbsa_gwdt_register_types)
300