|  | /* | 
|  | *  QEMU model of the LatticeMico32 system control block. | 
|  | * | 
|  | *  Copyright (c) 2010 Michael Walle <michael@walle.cc> | 
|  | * | 
|  | * 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 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/>. | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * This model is mainly intended for testing purposes and doesn't fit to any | 
|  | * real hardware. On the one hand it provides a control register (R_CTRL) on | 
|  | * the other hand it supports the lm32 tests. | 
|  | * | 
|  | * A write to the control register causes a system shutdown. | 
|  | * Tests first write the pointer to a test name to the test name register | 
|  | * (R_TESTNAME) and then write a zero to the pass/fail register (R_PASSFAIL) if | 
|  | * the test is passed or any non-zero value to it if the test is failed. | 
|  | */ | 
|  |  | 
|  | #include "hw/hw.h" | 
|  | #include "hw/sysbus.h" | 
|  | #include "trace.h" | 
|  | #include "qemu/log.h" | 
|  | #include "qemu/error-report.h" | 
|  | #include "sysemu/sysemu.h" | 
|  |  | 
|  | enum { | 
|  | R_CTRL = 0, | 
|  | R_PASSFAIL, | 
|  | R_TESTNAME, | 
|  | R_MAX | 
|  | }; | 
|  |  | 
|  | #define MAX_TESTNAME_LEN 16 | 
|  |  | 
|  | #define TYPE_LM32_SYS "lm32-sys" | 
|  | #define LM32_SYS(obj) OBJECT_CHECK(LM32SysState, (obj), TYPE_LM32_SYS) | 
|  |  | 
|  | struct LM32SysState { | 
|  | SysBusDevice parent_obj; | 
|  |  | 
|  | MemoryRegion iomem; | 
|  | uint32_t base; | 
|  | uint32_t regs[R_MAX]; | 
|  | uint8_t testname[MAX_TESTNAME_LEN]; | 
|  | }; | 
|  | typedef struct LM32SysState LM32SysState; | 
|  |  | 
|  | static void copy_testname(LM32SysState *s) | 
|  | { | 
|  | cpu_physical_memory_read(s->regs[R_TESTNAME], s->testname, | 
|  | MAX_TESTNAME_LEN); | 
|  | s->testname[MAX_TESTNAME_LEN - 1] = '\0'; | 
|  | } | 
|  |  | 
|  | static void sys_write(void *opaque, hwaddr addr, | 
|  | uint64_t value, unsigned size) | 
|  | { | 
|  | LM32SysState *s = opaque; | 
|  | char *testname; | 
|  |  | 
|  | trace_lm32_sys_memory_write(addr, value); | 
|  |  | 
|  | addr >>= 2; | 
|  | switch (addr) { | 
|  | case R_CTRL: | 
|  | qemu_system_shutdown_request(); | 
|  | break; | 
|  | case R_PASSFAIL: | 
|  | s->regs[addr] = value; | 
|  | testname = (char *)s->testname; | 
|  | qemu_log("TC  %-16s %s\n", testname, (value) ? "FAILED" : "OK"); | 
|  | break; | 
|  | case R_TESTNAME: | 
|  | s->regs[addr] = value; | 
|  | copy_testname(s); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | error_report("lm32_sys: write access to unknown register 0x" | 
|  | TARGET_FMT_plx, addr << 2); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | static bool sys_ops_accepts(void *opaque, hwaddr addr, | 
|  | unsigned size, bool is_write) | 
|  | { | 
|  | return is_write && size == 4; | 
|  | } | 
|  |  | 
|  | static const MemoryRegionOps sys_ops = { | 
|  | .write = sys_write, | 
|  | .valid.accepts = sys_ops_accepts, | 
|  | .endianness = DEVICE_NATIVE_ENDIAN, | 
|  | }; | 
|  |  | 
|  | static void sys_reset(DeviceState *d) | 
|  | { | 
|  | LM32SysState *s = LM32_SYS(d); | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < R_MAX; i++) { | 
|  | s->regs[i] = 0; | 
|  | } | 
|  | memset(s->testname, 0, MAX_TESTNAME_LEN); | 
|  | } | 
|  |  | 
|  | static int lm32_sys_init(SysBusDevice *dev) | 
|  | { | 
|  | LM32SysState *s = LM32_SYS(dev); | 
|  |  | 
|  | memory_region_init_io(&s->iomem, OBJECT(dev), &sys_ops , s, | 
|  | "sys", R_MAX * 4); | 
|  | sysbus_init_mmio(dev, &s->iomem); | 
|  |  | 
|  | /* Note: This device is not created in the board initialization, | 
|  | * instead it has to be added with the -device parameter. Therefore, | 
|  | * the device maps itself. */ | 
|  | sysbus_mmio_map(dev, 0, s->base); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const VMStateDescription vmstate_lm32_sys = { | 
|  | .name = "lm32-sys", | 
|  | .version_id = 1, | 
|  | .minimum_version_id = 1, | 
|  | .minimum_version_id_old = 1, | 
|  | .fields      = (VMStateField[]) { | 
|  | VMSTATE_UINT32_ARRAY(regs, LM32SysState, R_MAX), | 
|  | VMSTATE_BUFFER(testname, LM32SysState), | 
|  | VMSTATE_END_OF_LIST() | 
|  | } | 
|  | }; | 
|  |  | 
|  | static Property lm32_sys_properties[] = { | 
|  | DEFINE_PROP_UINT32("base", LM32SysState, base, 0xffff0000), | 
|  | DEFINE_PROP_END_OF_LIST(), | 
|  | }; | 
|  |  | 
|  | static void lm32_sys_class_init(ObjectClass *klass, void *data) | 
|  | { | 
|  | DeviceClass *dc = DEVICE_CLASS(klass); | 
|  | SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); | 
|  |  | 
|  | k->init = lm32_sys_init; | 
|  | dc->reset = sys_reset; | 
|  | dc->vmsd = &vmstate_lm32_sys; | 
|  | dc->props = lm32_sys_properties; | 
|  | } | 
|  |  | 
|  | static const TypeInfo lm32_sys_info = { | 
|  | .name          = TYPE_LM32_SYS, | 
|  | .parent        = TYPE_SYS_BUS_DEVICE, | 
|  | .instance_size = sizeof(LM32SysState), | 
|  | .class_init    = lm32_sys_class_init, | 
|  | }; | 
|  |  | 
|  | static void lm32_sys_register_types(void) | 
|  | { | 
|  | type_register_static(&lm32_sys_info); | 
|  | } | 
|  |  | 
|  | type_init(lm32_sys_register_types) |