From: Michel Heily <michelhe...@gmail.com> Signed-off-by: Michel Heily <michelhe...@gmail.com> --- hw/arm/stellaris.c | 260 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 258 insertions(+), 2 deletions(-)
diff --git a/hw/arm/stellaris.c b/hw/arm/stellaris.c index 442529c..08baeb2 100644 --- a/hw/arm/stellaris.c +++ b/hw/arm/stellaris.c @@ -348,6 +348,241 @@ static void stellaris_gptm_init(Object *obj) } +/* Hardware watchdog. */ + +#define TYPE_STELLARIS_WDTIMER "stellaris-wdtimer" +#define STELLARIS_WDTIMER(obj) \ + OBJECT_CHECK(wdtimer_state, (obj), TYPE_STELLARIS_WDTIMER) + +typedef struct wdtimer_state { + SysBusDevice parent_obj; + + MemoryRegion iomem; + uint32_t control; + uint32_t load; + uint32_t tick; + uint32_t state; + uint32_t mask; + uint32_t stage; + bool locked; + bool reset; + + struct wdtimer_state *opaque; + QEMUTimer *timer; + qemu_irq irq; +} wdtimer_state; + +static void wdtimer_reset(wdtimer_state *s) +{ + s->load = 0xffffffff; + s->tick = 0xffffffff; + s->state = 0; + s->mask = 0; + s->locked = false; + s->reset = false; + s->stage = 1; +} + +static void wdtimer_update_irq(wdtimer_state *s) +{ + int level; + level = (s->state & s->mask) != 0; + qemu_set_irq(s->irq, level); +} + + +static void wdtimer_reload(wdtimer_state *s, uint32_t stage) +{ + s->stage = stage; + + uint64_t tick = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + timer_mod(s->timer, tick + s->load * system_clock_scale); +} + +static void wdtimer_expired(void *opaque) +{ + wdtimer_state **p = (wdtimer_state **)opaque; + wdtimer_state *s; + + s = *p; + + if (s->mask & 1) { + s->state = 1; /* assert IRQ */ + wdtimer_update_irq(s); + } + + if (s->stage == 1) { + wdtimer_reload(s, 2); + + } else if (s->stage == 2) { + if (s->reset && (s->state & s->mask) != 0) { + qemu_log_mask(CPU_LOG_RESET, + "WDT: system reset on 2nd time-out\n"); + wdtimer_reset(s); + qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_PANIC); + } else { + wdtimer_reload(s, 1); + } + } +} + +/* WDT offsets */ +#define WDT_O_LOAD 0x00000000 +#define WDT_O_VALUE 0x00000004 +#define WDT_O_CTL 0x00000008 +#define WDT_O_ICR 0x0000000C +#define WDT_O_RIS 0x00000010 +#define WDT_O_MIS 0x00000014 +#define WDT_O_TEST 0x00000418 +#define WDT_O_LOCK 0x00000C00 +#define WDT_O_PeriphID4 0x00000FD0 +#define WDT_O_PeriphID5 0x00000FD4 +#define WDT_O_PeriphID6 0x00000FD8 +#define WDT_O_PeriphID7 0x00000FDC +#define WDT_O_PeriphID0 0x00000FE0 +#define WDT_O_PeriphID1 0x00000FE4 +#define WDT_O_PeriphID2 0x00000FE8 +#define WDT_O_PeriphID3 0x00000FEC +#define WDT_O_PCellID0 0x00000FF0 +#define WDT_O_PCellID1 0x00000FF4 +#define WDT_O_PCellID2 0x00000FF8 +#define WDT_O_PCellID3 0x00000FFC + +static uint64_t wdtimer_read(void *opaque, hwaddr offset, + unsigned size) +{ + wdtimer_state *s = (wdtimer_state *)opaque; + + switch (offset) { + case WDT_O_LOAD: + return s->load; + case WDT_O_VALUE: + return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + case WDT_O_CTL: + return s->control; + case WDT_O_RIS: + return s->state; + case WDT_O_MIS: + return s->state & s->mask; + case WDT_O_TEST: + qemu_log_mask(LOG_UNIMP, + "WDT: stall not implemented\n"); + return 0; + case WDT_O_LOCK: + return s->locked ? 1 : 0; + case WDT_O_PeriphID4: /* fallthrough */ + case WDT_O_PeriphID5: + case WDT_O_PeriphID6: + case WDT_O_PeriphID7: + case WDT_O_PeriphID0: + case WDT_O_PeriphID1: + case WDT_O_PeriphID2: + case WDT_O_PeriphID3: + qemu_log_mask(LOG_UNIMP, + "WDT: Peripheral identification not implemented\n"); + return 0; + case WDT_O_PCellID0: + case WDT_O_PCellID1: + case WDT_O_PCellID2: + case WDT_O_PCellID3: + qemu_log_mask(LOG_UNIMP, + "WDT: PrimieCell identification not implemented\n"); + return 0; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "WDT: read at bad offset 0x02%" HWADDR_PRIx "\n", + offset); + return 0; + } +} + +static void wdtimer_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + wdtimer_state *s = (wdtimer_state *)opaque; + + if (s->locked) { + qemu_log_mask(LOG_GUEST_ERROR, + "WDT: write to watchdog registers in locked state\n"); + wdtimer_update_irq(s); + return; + } + + switch (offset) { + case WDT_O_LOAD: + s->load = value; + wdtimer_reload(s, 1); + break; + case WDT_O_CTL: + if (!(s->mask & 1)) { + if (value & 2) { + s->reset = true; + } + if (value & 1) { + s->mask = value; + wdtimer_reload(s, 1); + } + } + break; + case WDT_O_ICR: + /* clear the interrupt and reload the counter */ + s->state &= ~value; + wdtimer_reload(s, 1); + break; + case WDT_O_TEST: + qemu_log_mask(LOG_UNIMP, + "WDT: stall not implemented\n"); + break; + case WDT_O_LOCK: + s->locked = value == 0x1acce551; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "WDT: write at bad offset 0x02%" HWADDR_PRIx "\n", + offset); + } + wdtimer_update_irq(s); +} + +static const MemoryRegionOps wdtimer_ops = { + .read = wdtimer_read, + .write = wdtimer_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_stellaris_wdtimer = { + .name = "stellaris_wdtimer", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(control, wdtimer_state), + VMSTATE_UINT32(load, wdtimer_state), + VMSTATE_UINT32(tick, wdtimer_state), + VMSTATE_UINT32(state, wdtimer_state), + VMSTATE_UINT32(mask, wdtimer_state), + VMSTATE_UINT32(stage, wdtimer_state), + VMSTATE_BOOL(locked, wdtimer_state), + VMSTATE_BOOL(reset, wdtimer_state), + VMSTATE_END_OF_LIST() + } +}; + +static void stellaris_wdtimer_init(Object *obj) +{ + wdtimer_state *s = STELLARIS_WDTIMER(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + sysbus_init_irq(sbd, &s->irq); + memory_region_init_io(&s->iomem, obj, &wdtimer_ops, s, + "wdtimer", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + s->opaque = s; + + s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, wdtimer_expired, &s->opaque); + wdtimer_reset(s); +} + + /* System controller. */ typedef struct { @@ -1243,7 +1478,7 @@ static void stellaris_init(MachineState *ms, stellaris_board_info *board) * Stellaris LM3S6965 Microcontroller Data Sheet (rev I) * http://www.ti.com/lit/ds/symlink/lm3s6965.pdf * - * 40000000 wdtimer (unimplemented) + * 40000000 wdtimer * 40002000 i2c (unimplemented) * 40004000 GPIO * 40005000 GPIO @@ -1335,6 +1570,12 @@ static void stellaris_init(MachineState *ms, stellaris_board_info *board) } } + if (board->dc1 & (1 << 3)) { /* watchdog present */ + dev = sysbus_create_simple(TYPE_STELLARIS_WDTIMER, + 0x40000000, + qdev_get_gpio_in(nvic, 18)); + } + stellaris_sys_init(0x400fe000, qdev_get_gpio_in(nvic, 28), board, nd_table[0].macaddr.a); @@ -1431,7 +1672,6 @@ static void stellaris_init(MachineState *ms, stellaris_board_info *board) /* Add dummy regions for the devices we don't implement yet, * so guest accesses don't cause unlogged crashes. */ - create_unimplemented_device("wdtimer", 0x40000000, 0x1000); create_unimplemented_device("i2c-0", 0x40002000, 0x1000); create_unimplemented_device("i2c-2", 0x40021000, 0x1000); create_unimplemented_device("PWM", 0x40028000, 0x1000); @@ -1517,6 +1757,13 @@ static void stellaris_gptm_class_init(ObjectClass *klass, void *data) dc->vmsd = &vmstate_stellaris_gptm; } +static void stellaris_wdtimer_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->vmsd = &vmstate_stellaris_wdtimer; +} + static const TypeInfo stellaris_gptm_info = { .name = TYPE_STELLARIS_GPTM, .parent = TYPE_SYS_BUS_DEVICE, @@ -1525,6 +1772,14 @@ static const TypeInfo stellaris_gptm_info = { .class_init = stellaris_gptm_class_init, }; +static const TypeInfo stellaris_wdtimer_info = { + .name = TYPE_STELLARIS_WDTIMER, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(wdtimer_state), + .instance_init = stellaris_wdtimer_init, + .class_init = stellaris_wdtimer_class_init, +}; + static void stellaris_adc_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -1545,6 +1800,7 @@ static void stellaris_register_types(void) type_register_static(&stellaris_i2c_info); type_register_static(&stellaris_gptm_info); type_register_static(&stellaris_adc_info); + type_register_static(&stellaris_wdtimer_info); } type_init(stellaris_register_types) -- 2.7.4