qemu/hw/watchdog/cmsdk-apb-watchdog.c
<<
>>
Prefs
   1/*
   2 * ARM CMSDK APB watchdog emulation
   3 *
   4 * Copyright (c) 2018 Linaro Limited
   5 * Written by Peter Maydell
   6 *
   7 *  This program is free software; you can redistribute it and/or modify
   8 *  it under the terms of the GNU General Public License version 2 or
   9 *  (at your option) any later version.
  10 */
  11
  12/*
  13 * This is a model of the "APB watchdog" which is part of the Cortex-M
  14 * System Design Kit (CMSDK) and documented in the Cortex-M System
  15 * Design Kit Technical Reference Manual (ARM DDI0479C):
  16 * https://developer.arm.com/products/system-design/system-design-kits/cortex-m-system-design-kit
  17 *
  18 * We also support the variant of this device found in the TI
  19 * Stellaris/Luminary boards and documented in:
  20 * http://www.ti.com/lit/ds/symlink/lm3s6965.pdf
  21 */
  22
  23#include "qemu/osdep.h"
  24#include "qemu/log.h"
  25#include "trace.h"
  26#include "qapi/error.h"
  27#include "qemu/main-loop.h"
  28#include "sysemu/watchdog.h"
  29#include "hw/sysbus.h"
  30#include "hw/registerfields.h"
  31#include "hw/watchdog/cmsdk-apb-watchdog.h"
  32
  33REG32(WDOGLOAD, 0x0)
  34REG32(WDOGVALUE, 0x4)
  35REG32(WDOGCONTROL, 0x8)
  36    FIELD(WDOGCONTROL, INTEN, 0, 1)
  37    FIELD(WDOGCONTROL, RESEN, 1, 1)
  38#define R_WDOGCONTROL_VALID_MASK (R_WDOGCONTROL_INTEN_MASK | \
  39                                  R_WDOGCONTROL_RESEN_MASK)
  40REG32(WDOGINTCLR, 0xc)
  41REG32(WDOGRIS, 0x10)
  42    FIELD(WDOGRIS, INT, 0, 1)
  43REG32(WDOGMIS, 0x14)
  44REG32(WDOGTEST, 0x418) /* only in Stellaris/Luminary version of the device */
  45REG32(WDOGLOCK, 0xc00)
  46#define WDOG_UNLOCK_VALUE 0x1ACCE551
  47REG32(WDOGITCR, 0xf00)
  48    FIELD(WDOGITCR, ENABLE, 0, 1)
  49#define R_WDOGITCR_VALID_MASK R_WDOGITCR_ENABLE_MASK
  50REG32(WDOGITOP, 0xf04)
  51    FIELD(WDOGITOP, WDOGRES, 0, 1)
  52    FIELD(WDOGITOP, WDOGINT, 1, 1)
  53#define R_WDOGITOP_VALID_MASK (R_WDOGITOP_WDOGRES_MASK | \
  54                               R_WDOGITOP_WDOGINT_MASK)
  55REG32(PID4, 0xfd0)
  56REG32(PID5, 0xfd4)
  57REG32(PID6, 0xfd8)
  58REG32(PID7, 0xfdc)
  59REG32(PID0, 0xfe0)
  60REG32(PID1, 0xfe4)
  61REG32(PID2, 0xfe8)
  62REG32(PID3, 0xfec)
  63REG32(CID0, 0xff0)
  64REG32(CID1, 0xff4)
  65REG32(CID2, 0xff8)
  66REG32(CID3, 0xffc)
  67
  68/* PID/CID values */
  69static const uint32_t cmsdk_apb_watchdog_id[] = {
  70    0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
  71    0x24, 0xb8, 0x1b, 0x00, /* PID0..PID3 */
  72    0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
  73};
  74
  75static const uint32_t luminary_watchdog_id[] = {
  76    0x00, 0x00, 0x00, 0x00, /* PID4..PID7 */
  77    0x05, 0x18, 0x18, 0x01, /* PID0..PID3 */
  78    0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
  79};
  80
  81static bool cmsdk_apb_watchdog_intstatus(CMSDKAPBWatchdog *s)
  82{
  83    /* Return masked interrupt status */
  84    return s->intstatus && (s->control & R_WDOGCONTROL_INTEN_MASK);
  85}
  86
  87static bool cmsdk_apb_watchdog_resetstatus(CMSDKAPBWatchdog *s)
  88{
  89    /* Return masked reset status */
  90    return s->resetstatus && (s->control & R_WDOGCONTROL_RESEN_MASK);
  91}
  92
  93static void cmsdk_apb_watchdog_update(CMSDKAPBWatchdog *s)
  94{
  95    bool wdogint;
  96    bool wdogres;
  97
  98    if (s->itcr) {
  99        /*
 100         * Not checking that !s->is_luminary since s->itcr can't be written
 101         * when s->is_luminary in the first place.
 102         */
 103        wdogint = s->itop & R_WDOGITOP_WDOGINT_MASK;
 104        wdogres = s->itop & R_WDOGITOP_WDOGRES_MASK;
 105    } else {
 106        wdogint = cmsdk_apb_watchdog_intstatus(s);
 107        wdogres = cmsdk_apb_watchdog_resetstatus(s);
 108    }
 109
 110    qemu_set_irq(s->wdogint, wdogint);
 111    if (wdogres) {
 112        watchdog_perform_action();
 113    }
 114}
 115
 116static uint64_t cmsdk_apb_watchdog_read(void *opaque, hwaddr offset,
 117                                        unsigned size)
 118{
 119    CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(opaque);
 120    uint64_t r;
 121
 122    switch (offset) {
 123    case A_WDOGLOAD:
 124        r = ptimer_get_limit(s->timer);
 125        break;
 126    case A_WDOGVALUE:
 127        r = ptimer_get_count(s->timer);
 128        break;
 129    case A_WDOGCONTROL:
 130        r = s->control;
 131        break;
 132    case A_WDOGRIS:
 133        r = s->intstatus;
 134        break;
 135    case A_WDOGMIS:
 136        r = cmsdk_apb_watchdog_intstatus(s);
 137        break;
 138    case A_WDOGLOCK:
 139        r = s->lock;
 140        break;
 141    case A_WDOGITCR:
 142        if (s->is_luminary) {
 143            goto bad_offset;
 144        }
 145        r = s->itcr;
 146        break;
 147    case A_PID4 ... A_CID3:
 148        r = s->id[(offset - A_PID4) / 4];
 149        break;
 150    case A_WDOGINTCLR:
 151    case A_WDOGITOP:
 152        if (s->is_luminary) {
 153            goto bad_offset;
 154        }
 155        qemu_log_mask(LOG_GUEST_ERROR,
 156                      "CMSDK APB watchdog read: read of WO offset %x\n",
 157                      (int)offset);
 158        r = 0;
 159        break;
 160    case A_WDOGTEST:
 161        if (!s->is_luminary) {
 162            goto bad_offset;
 163        }
 164        qemu_log_mask(LOG_UNIMP,
 165                      "Luminary watchdog read: stall not implemented\n");
 166        r = 0;
 167        break;
 168    default:
 169bad_offset:
 170        qemu_log_mask(LOG_GUEST_ERROR,
 171                      "CMSDK APB watchdog read: bad offset %x\n", (int)offset);
 172        r = 0;
 173        break;
 174    }
 175    trace_cmsdk_apb_watchdog_read(offset, r, size);
 176    return r;
 177}
 178
 179static void cmsdk_apb_watchdog_write(void *opaque, hwaddr offset,
 180                                     uint64_t value, unsigned size)
 181{
 182    CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(opaque);
 183
 184    trace_cmsdk_apb_watchdog_write(offset, value, size);
 185
 186    if (s->lock && offset != A_WDOGLOCK) {
 187        /* Write access is disabled via WDOGLOCK */
 188        qemu_log_mask(LOG_GUEST_ERROR,
 189                      "CMSDK APB watchdog write: write to locked watchdog\n");
 190        return;
 191    }
 192
 193    switch (offset) {
 194    case A_WDOGLOAD:
 195        /*
 196         * Reset the load value and the current count, and make sure
 197         * we're counting.
 198         */
 199        ptimer_set_limit(s->timer, value, 1);
 200        ptimer_run(s->timer, 0);
 201        break;
 202    case A_WDOGCONTROL:
 203        if (s->is_luminary && 0 != (R_WDOGCONTROL_INTEN_MASK & s->control)) {
 204            /*
 205             * The Luminary version of this device ignores writes to
 206             * this register after the guest has enabled interrupts
 207             * (so they can only be disabled again via reset).
 208             */
 209            break;
 210        }
 211        s->control = value & R_WDOGCONTROL_VALID_MASK;
 212        cmsdk_apb_watchdog_update(s);
 213        break;
 214    case A_WDOGINTCLR:
 215        s->intstatus = 0;
 216        ptimer_set_count(s->timer, ptimer_get_limit(s->timer));
 217        cmsdk_apb_watchdog_update(s);
 218        break;
 219    case A_WDOGLOCK:
 220        s->lock = (value != WDOG_UNLOCK_VALUE);
 221        break;
 222    case A_WDOGITCR:
 223        if (s->is_luminary) {
 224            goto bad_offset;
 225        }
 226        s->itcr = value & R_WDOGITCR_VALID_MASK;
 227        cmsdk_apb_watchdog_update(s);
 228        break;
 229    case A_WDOGITOP:
 230        if (s->is_luminary) {
 231            goto bad_offset;
 232        }
 233        s->itop = value & R_WDOGITOP_VALID_MASK;
 234        cmsdk_apb_watchdog_update(s);
 235        break;
 236    case A_WDOGVALUE:
 237    case A_WDOGRIS:
 238    case A_WDOGMIS:
 239    case A_PID4 ... A_CID3:
 240        qemu_log_mask(LOG_GUEST_ERROR,
 241                      "CMSDK APB watchdog write: write to RO offset 0x%x\n",
 242                      (int)offset);
 243        break;
 244    case A_WDOGTEST:
 245        if (!s->is_luminary) {
 246            goto bad_offset;
 247        }
 248        qemu_log_mask(LOG_UNIMP,
 249                      "Luminary watchdog write: stall not implemented\n");
 250        break;
 251    default:
 252bad_offset:
 253        qemu_log_mask(LOG_GUEST_ERROR,
 254                      "CMSDK APB watchdog write: bad offset 0x%x\n",
 255                      (int)offset);
 256        break;
 257    }
 258}
 259
 260static const MemoryRegionOps cmsdk_apb_watchdog_ops = {
 261    .read = cmsdk_apb_watchdog_read,
 262    .write = cmsdk_apb_watchdog_write,
 263    .endianness = DEVICE_LITTLE_ENDIAN,
 264    /* byte/halfword accesses are just zero-padded on reads and writes */
 265    .impl.min_access_size = 4,
 266    .impl.max_access_size = 4,
 267    .valid.min_access_size = 1,
 268    .valid.max_access_size = 4,
 269};
 270
 271static void cmsdk_apb_watchdog_tick(void *opaque)
 272{
 273    CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(opaque);
 274
 275    if (!s->intstatus) {
 276        /* Count expired for the first time: raise interrupt */
 277        s->intstatus = R_WDOGRIS_INT_MASK;
 278    } else {
 279        /* Count expired for the second time: raise reset and stop clock */
 280        s->resetstatus = 1;
 281        ptimer_stop(s->timer);
 282    }
 283    cmsdk_apb_watchdog_update(s);
 284}
 285
 286static void cmsdk_apb_watchdog_reset(DeviceState *dev)
 287{
 288    CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(dev);
 289
 290    trace_cmsdk_apb_watchdog_reset();
 291    s->control = 0;
 292    s->intstatus = 0;
 293    s->lock = 0;
 294    s->itcr = 0;
 295    s->itop = 0;
 296    s->resetstatus = 0;
 297    /* Set the limit and the count */
 298    ptimer_set_limit(s->timer, 0xffffffff, 1);
 299    ptimer_run(s->timer, 0);
 300}
 301
 302static void cmsdk_apb_watchdog_init(Object *obj)
 303{
 304    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
 305    CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(obj);
 306
 307    memory_region_init_io(&s->iomem, obj, &cmsdk_apb_watchdog_ops,
 308                          s, "cmsdk-apb-watchdog", 0x1000);
 309    sysbus_init_mmio(sbd, &s->iomem);
 310    sysbus_init_irq(sbd, &s->wdogint);
 311
 312    s->is_luminary = false;
 313    s->id = cmsdk_apb_watchdog_id;
 314}
 315
 316static void cmsdk_apb_watchdog_realize(DeviceState *dev, Error **errp)
 317{
 318    CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(dev);
 319    QEMUBH *bh;
 320
 321    if (s->wdogclk_frq == 0) {
 322        error_setg(errp,
 323                   "CMSDK APB watchdog: wdogclk-frq property must be set");
 324        return;
 325    }
 326
 327    bh = qemu_bh_new(cmsdk_apb_watchdog_tick, s);
 328    s->timer = ptimer_init(bh,
 329                           PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD |
 330                           PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT |
 331                           PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
 332                           PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
 333
 334    ptimer_set_freq(s->timer, s->wdogclk_frq);
 335}
 336
 337static const VMStateDescription cmsdk_apb_watchdog_vmstate = {
 338    .name = "cmsdk-apb-watchdog",
 339    .version_id = 1,
 340    .minimum_version_id = 1,
 341    .fields = (VMStateField[]) {
 342        VMSTATE_PTIMER(timer, CMSDKAPBWatchdog),
 343        VMSTATE_UINT32(control, CMSDKAPBWatchdog),
 344        VMSTATE_UINT32(intstatus, CMSDKAPBWatchdog),
 345        VMSTATE_UINT32(lock, CMSDKAPBWatchdog),
 346        VMSTATE_UINT32(itcr, CMSDKAPBWatchdog),
 347        VMSTATE_UINT32(itop, CMSDKAPBWatchdog),
 348        VMSTATE_UINT32(resetstatus, CMSDKAPBWatchdog),
 349        VMSTATE_END_OF_LIST()
 350    }
 351};
 352
 353static Property cmsdk_apb_watchdog_properties[] = {
 354    DEFINE_PROP_UINT32("wdogclk-frq", CMSDKAPBWatchdog, wdogclk_frq, 0),
 355    DEFINE_PROP_END_OF_LIST(),
 356};
 357
 358static void cmsdk_apb_watchdog_class_init(ObjectClass *klass, void *data)
 359{
 360    DeviceClass *dc = DEVICE_CLASS(klass);
 361
 362    dc->realize = cmsdk_apb_watchdog_realize;
 363    dc->vmsd = &cmsdk_apb_watchdog_vmstate;
 364    dc->reset = cmsdk_apb_watchdog_reset;
 365    dc->props = cmsdk_apb_watchdog_properties;
 366}
 367
 368static const TypeInfo cmsdk_apb_watchdog_info = {
 369    .name = TYPE_CMSDK_APB_WATCHDOG,
 370    .parent = TYPE_SYS_BUS_DEVICE,
 371    .instance_size = sizeof(CMSDKAPBWatchdog),
 372    .instance_init = cmsdk_apb_watchdog_init,
 373    .class_init = cmsdk_apb_watchdog_class_init,
 374};
 375
 376static void luminary_watchdog_init(Object *obj)
 377{
 378    CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(obj);
 379
 380    s->is_luminary = true;
 381    s->id = luminary_watchdog_id;
 382}
 383
 384static const TypeInfo luminary_watchdog_info = {
 385    .name = TYPE_LUMINARY_WATCHDOG,
 386    .parent = TYPE_CMSDK_APB_WATCHDOG,
 387    .instance_init = luminary_watchdog_init
 388};
 389
 390static void cmsdk_apb_watchdog_register_types(void)
 391{
 392    type_register_static(&cmsdk_apb_watchdog_info);
 393    type_register_static(&luminary_watchdog_info);
 394}
 395
 396type_init(cmsdk_apb_watchdog_register_types);
 397