diff options
-rw-r--r-- | default-configs/arm-softmmu.mak | 1 | ||||
-rw-r--r-- | hw/Makefile.objs | 1 | ||||
-rw-r--r-- | hw/arm/vexpress.c | 81 | ||||
-rw-r--r-- | hw/arm/xilinx_zynq.c | 24 | ||||
-rw-r--r-- | hw/arm_sysctl.c | 261 | ||||
-rw-r--r-- | hw/pl330.c | 1654 | ||||
-rw-r--r-- | hw/qdev-core.h | 3 | ||||
-rw-r--r-- | hw/qdev-properties.c | 104 | ||||
-rw-r--r-- | hw/qdev-properties.h | 39 | ||||
-rw-r--r-- | hw/xilinx_spips.c | 64 | ||||
-rw-r--r-- | include/qemu-common.h | 6 | ||||
-rw-r--r-- | util/Makefile.objs | 1 | ||||
-rw-r--r-- | util/hexdump.c | 37 | ||||
-rw-r--r-- | util/iov.c | 36 |
14 files changed, 2234 insertions, 78 deletions
diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak index 68b204547c..ab8703578e 100644 --- a/default-configs/arm-softmmu.mak +++ b/default-configs/arm-softmmu.mak @@ -42,6 +42,7 @@ CONFIG_PL110=y CONFIG_PL181=y CONFIG_PL190=y CONFIG_PL310=y +CONFIG_PL330=y CONFIG_CADENCE=y CONFIG_XGMAC=y diff --git a/hw/Makefile.objs b/hw/Makefile.objs index eb7eb31a19..11812c6a4e 100644 --- a/hw/Makefile.objs +++ b/hw/Makefile.objs @@ -98,6 +98,7 @@ common-obj-$(CONFIG_PL110) += pl110.o common-obj-$(CONFIG_PL181) += pl181.o common-obj-$(CONFIG_PL190) += pl190.o common-obj-$(CONFIG_PL310) += arm_l2x0.o +common-obj-$(CONFIG_PL330) += pl330.o common-obj-$(CONFIG_VERSATILE_PCI) += versatile_pci.o common-obj-$(CONFIG_VERSATILE_I2C) += versatile_i2c.o common-obj-$(CONFIG_CADENCE) += cadence_uart.o diff --git a/hw/arm/vexpress.c b/hw/arm/vexpress.c index 02922c38b3..2e1a5d0e5b 100644 --- a/hw/arm/vexpress.c +++ b/hw/arm/vexpress.c @@ -147,19 +147,24 @@ typedef struct VEDBoardInfo VEDBoardInfo; typedef void DBoardInitFn(const VEDBoardInfo *daughterboard, ram_addr_t ram_size, const char *cpu_model, - qemu_irq *pic, uint32_t *proc_id); + qemu_irq *pic); struct VEDBoardInfo { const hwaddr *motherboard_map; hwaddr loader_start; const hwaddr gic_cpu_if_addr; + uint32_t proc_id; + uint32_t num_voltage_sensors; + const uint32_t *voltages; + uint32_t num_clocks; + const uint32_t *clocks; DBoardInitFn *init; }; static void a9_daughterboard_init(const VEDBoardInfo *daughterboard, ram_addr_t ram_size, const char *cpu_model, - qemu_irq *pic, uint32_t *proc_id) + qemu_irq *pic) { MemoryRegion *sysmem = get_system_memory(); MemoryRegion *ram = g_new(MemoryRegion, 1); @@ -175,8 +180,6 @@ static void a9_daughterboard_init(const VEDBoardInfo *daughterboard, cpu_model = "cortex-a9"; } - *proc_id = 0x0c000191; - for (n = 0; n < smp_cpus; n++) { ARMCPU *cpu = cpu_arm_init(cpu_model); if (!cpu) { @@ -247,17 +250,41 @@ static void a9_daughterboard_init(const VEDBoardInfo *daughterboard, sysbus_create_varargs("l2x0", 0x1e00a000, NULL); } +/* Voltage values for SYS_CFG_VOLT daughterboard registers; + * values are in microvolts. + */ +static const uint32_t a9_voltages[] = { + 1000000, /* VD10 : 1.0V : SoC internal logic voltage */ + 1000000, /* VD10_S2 : 1.0V : PL310, L2 cache, RAM, non-PL310 logic */ + 1000000, /* VD10_S3 : 1.0V : Cortex-A9, cores, MPEs, SCU, PL310 logic */ + 1800000, /* VCC1V8 : 1.8V : DDR2 SDRAM, test chip DDR2 I/O supply */ + 900000, /* DDR2VTT : 0.9V : DDR2 SDRAM VTT termination voltage */ + 3300000, /* VCC3V3 : 3.3V : local board supply for misc external logic */ +}; + +/* Reset values for daughterboard oscillators (in Hz) */ +static const uint32_t a9_clocks[] = { + 45000000, /* AMBA AXI ACLK: 45MHz */ + 23750000, /* daughterboard CLCD clock: 23.75MHz */ + 66670000, /* Test chip reference clock: 66.67MHz */ +}; + static const VEDBoardInfo a9_daughterboard = { .motherboard_map = motherboard_legacy_map, .loader_start = 0x60000000, .gic_cpu_if_addr = 0x1e000100, + .proc_id = 0x0c000191, + .num_voltage_sensors = ARRAY_SIZE(a9_voltages), + .voltages = a9_voltages, + .num_clocks = ARRAY_SIZE(a9_clocks), + .clocks = a9_clocks, .init = a9_daughterboard_init, }; static void a15_daughterboard_init(const VEDBoardInfo *daughterboard, ram_addr_t ram_size, const char *cpu_model, - qemu_irq *pic, uint32_t *proc_id) + qemu_irq *pic) { int n; MemoryRegion *sysmem = get_system_memory(); @@ -271,8 +298,6 @@ static void a15_daughterboard_init(const VEDBoardInfo *daughterboard, cpu_model = "cortex-a15"; } - *proc_id = 0x14000237; - for (n = 0; n < smp_cpus; n++) { ARMCPU *cpu; qemu_irq *irqp; @@ -340,10 +365,31 @@ static void a15_daughterboard_init(const VEDBoardInfo *daughterboard, /* 0x7ffd0000: PL354 static memory controller: not modelled */ } +static const uint32_t a15_voltages[] = { + 900000, /* Vcore: 0.9V : CPU core voltage */ +}; + +static const uint32_t a15_clocks[] = { + 60000000, /* OSCCLK0: 60MHz : CPU_CLK reference */ + 0, /* OSCCLK1: reserved */ + 0, /* OSCCLK2: reserved */ + 0, /* OSCCLK3: reserved */ + 40000000, /* OSCCLK4: 40MHz : external AXI master clock */ + 23750000, /* OSCCLK5: 23.75MHz : HDLCD PLL reference */ + 50000000, /* OSCCLK6: 50MHz : static memory controller clock */ + 60000000, /* OSCCLK7: 60MHz : SYSCLK reference */ + 40000000, /* OSCCLK8: 40MHz : DDR2 PLL reference */ +}; + static const VEDBoardInfo a15_daughterboard = { .motherboard_map = motherboard_aseries_map, .loader_start = 0x80000000, .gic_cpu_if_addr = 0x2c002000, + .proc_id = 0x14000237, + .num_voltage_sensors = ARRAY_SIZE(a15_voltages), + .voltages = a15_voltages, + .num_clocks = ARRAY_SIZE(a15_clocks), + .clocks = a15_clocks, .init = a15_daughterboard_init, }; @@ -352,7 +398,6 @@ static void vexpress_common_init(const VEDBoardInfo *daughterboard, { DeviceState *dev, *sysctl, *pl041; qemu_irq pic[64]; - uint32_t proc_id; uint32_t sys_id; DriveInfo *dinfo; ram_addr_t vram_size, sram_size; @@ -360,9 +405,9 @@ static void vexpress_common_init(const VEDBoardInfo *daughterboard, MemoryRegion *vram = g_new(MemoryRegion, 1); MemoryRegion *sram = g_new(MemoryRegion, 1); const hwaddr *map = daughterboard->motherboard_map; + int i; - daughterboard->init(daughterboard, args->ram_size, args->cpu_model, - pic, &proc_id); + daughterboard->init(daughterboard, args->ram_size, args->cpu_model, pic); /* Motherboard peripherals: the wiring is the same but the * addresses vary between the legacy and A-Series memory maps. @@ -372,7 +417,21 @@ static void vexpress_common_init(const VEDBoardInfo *daughterboard, sysctl = qdev_create(NULL, "realview_sysctl"); qdev_prop_set_uint32(sysctl, "sys_id", sys_id); - qdev_prop_set_uint32(sysctl, "proc_id", proc_id); + qdev_prop_set_uint32(sysctl, "proc_id", daughterboard->proc_id); + qdev_prop_set_uint32(sysctl, "len-db-voltage", + daughterboard->num_voltage_sensors); + for (i = 0; i < daughterboard->num_voltage_sensors; i++) { + char *propname = g_strdup_printf("db-voltage[%d]", i); + qdev_prop_set_uint32(sysctl, propname, daughterboard->voltages[i]); + g_free(propname); + } + qdev_prop_set_uint32(sysctl, "len-db-clock", + daughterboard->num_clocks); + for (i = 0; i < daughterboard->num_clocks; i++) { + char *propname = g_strdup_printf("db-clock[%d]", i); + qdev_prop_set_uint32(sysctl, propname, daughterboard->clocks[i]); + g_free(propname); + } qdev_init_nofail(sysctl); sysbus_mmio_map(SYS_BUS_DEVICE(sysctl), 0, map[VE_SYSREGS]); diff --git a/hw/arm/xilinx_zynq.c b/hw/arm/xilinx_zynq.c index f78c47e43e..6f362865f9 100644 --- a/hw/arm/xilinx_zynq.c +++ b/hw/arm/xilinx_zynq.c @@ -35,6 +35,10 @@ #define IRQ_OFFSET 32 /* pic interrupts start from index 32 */ +static const int dma_irqs[8] = { + 46, 47, 48, 49, 72, 73, 74, 75 +}; + static struct arm_boot_info zynq_binfo = {}; static void gem_init(NICInfo *nd, uint32_t base, qemu_irq irq) @@ -196,6 +200,26 @@ static void zynq_init(QEMUMachineInitArgs *args) sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xE0101000); sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[79-IRQ_OFFSET]); + dev = qdev_create(NULL, "pl330"); + qdev_prop_set_uint8(dev, "num_chnls", 8); + qdev_prop_set_uint8(dev, "num_periph_req", 4); + qdev_prop_set_uint8(dev, "num_events", 16); + + qdev_prop_set_uint8(dev, "data_width", 64); + qdev_prop_set_uint8(dev, "wr_cap", 8); + qdev_prop_set_uint8(dev, "wr_q_dep", 16); + qdev_prop_set_uint8(dev, "rd_cap", 8); + qdev_prop_set_uint8(dev, "rd_q_dep", 16); + qdev_prop_set_uint16(dev, "data_buffer_dep", 256); + + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(busdev, 0, 0xF8003000); + sysbus_connect_irq(busdev, 0, pic[45-IRQ_OFFSET]); /* abort irq line */ + for (n = 0; n < 8; ++n) { /* event irqs */ + sysbus_connect_irq(busdev, n + 1, pic[dma_irqs[n] - IRQ_OFFSET]); + } + zynq_binfo.ram_size = ram_size; zynq_binfo.kernel_filename = kernel_filename; zynq_binfo.kernel_cmdline = kernel_cmdline; diff --git a/hw/arm_sysctl.c b/hw/arm_sysctl.c index a46f8d450e..25fc6eac94 100644 --- a/hw/arm_sysctl.c +++ b/hw/arm_sysctl.c @@ -9,6 +9,7 @@ #include "hw/hw.h" #include "qemu/timer.h" +#include "qemu/bitops.h" #include "hw/sysbus.h" #include "hw/primecell.h" #include "sysemu/sysemu.h" @@ -34,11 +35,17 @@ typedef struct { uint32_t sys_cfgctrl; uint32_t sys_cfgstat; uint32_t sys_clcd; + uint32_t mb_clock[6]; + uint32_t *db_clock; + uint32_t db_num_vsensors; + uint32_t *db_voltage; + uint32_t db_num_clocks; + uint32_t *db_clock_reset; } arm_sysctl_state; static const VMStateDescription vmstate_arm_sysctl = { .name = "realview_sysctl", - .version_id = 3, + .version_id = 4, .minimum_version_id = 1, .fields = (VMStateField[]) { VMSTATE_UINT32(leds, arm_sysctl_state), @@ -53,6 +60,9 @@ static const VMStateDescription vmstate_arm_sysctl = { VMSTATE_UINT32_V(sys_cfgctrl, arm_sysctl_state, 2), VMSTATE_UINT32_V(sys_cfgstat, arm_sysctl_state, 2), VMSTATE_UINT32_V(sys_clcd, arm_sysctl_state, 3), + VMSTATE_UINT32_ARRAY_V(mb_clock, arm_sysctl_state, 6, 4), + VMSTATE_VARRAY_UINT32(db_clock, arm_sysctl_state, db_num_clocks, + 4, vmstate_info_uint32, uint32_t), VMSTATE_END_OF_LIST() } }; @@ -76,6 +86,7 @@ static int board_id(arm_sysctl_state *s) static void arm_sysctl_reset(DeviceState *d) { arm_sysctl_state *s = FROM_SYSBUS(arm_sysctl_state, SYS_BUS_DEVICE(d)); + int i; s->leds = 0; s->lockval = 0; @@ -83,6 +94,17 @@ static void arm_sysctl_reset(DeviceState *d) s->cfgdata2 = 0; s->flags = 0; s->resetlevel = 0; + /* Motherboard oscillators (in Hz) */ + s->mb_clock[0] = 50000000; /* Static memory clock: 50MHz */ + s->mb_clock[1] = 23750000; /* motherboard CLCD clock: 23.75MHz */ + s->mb_clock[2] = 24000000; /* IO FPGA peripheral clock: 24MHz */ + s->mb_clock[3] = 24000000; /* IO FPGA reserved clock: 24MHz */ + s->mb_clock[4] = 24000000; /* System bus global clock: 24MHz */ + s->mb_clock[5] = 24000000; /* IO FPGA reserved clock: 24MHz */ + /* Daughterboard oscillators: reset from property values */ + for (i = 0; i < s->db_num_clocks; i++) { + s->db_clock[i] = s->db_clock_reset[i]; + } if (board_id(s) == BOARD_ID_VEXPRESS) { /* On VExpress this register will RAZ/WI */ s->sys_clcd = 0; @@ -191,6 +213,166 @@ static uint64_t arm_sysctl_read(void *opaque, hwaddr offset, } } +/* SYS_CFGCTRL functions */ +#define SYS_CFG_OSC 1 +#define SYS_CFG_VOLT 2 +#define SYS_CFG_AMP 3 +#define SYS_CFG_TEMP 4 +#define SYS_CFG_RESET 5 +#define SYS_CFG_SCC 6 +#define SYS_CFG_MUXFPGA 7 +#define SYS_CFG_SHUTDOWN 8 +#define SYS_CFG_REBOOT 9 +#define SYS_CFG_DVIMODE 11 +#define SYS_CFG_POWER 12 +#define SYS_CFG_ENERGY 13 + +/* SYS_CFGCTRL site field values */ +#define SYS_CFG_SITE_MB 0 +#define SYS_CFG_SITE_DB1 1 +#define SYS_CFG_SITE_DB2 2 + +/** + * vexpress_cfgctrl_read: + * @s: arm_sysctl_state pointer + * @dcc, @function, @site, @position, @device: split out values from + * SYS_CFGCTRL register + * @val: pointer to where to put the read data on success + * + * Handle a VExpress SYS_CFGCTRL register read. On success, return true and + * write the read value to *val. On failure, return false (and val may + * or may not be written to). + */ +static bool vexpress_cfgctrl_read(arm_sysctl_state *s, unsigned int dcc, + unsigned int function, unsigned int site, + unsigned int position, unsigned int device, + uint32_t *val) +{ + /* We don't support anything other than DCC 0, board stack position 0 + * or sites other than motherboard/daughterboard: + */ + if (dcc != 0 || position != 0 || + (site != SYS_CFG_SITE_MB && site != SYS_CFG_SITE_DB1)) { + goto cfgctrl_unimp; + } + + switch (function) { + case SYS_CFG_VOLT: + if (site == SYS_CFG_SITE_DB1 && device < s->db_num_vsensors) { + *val = s->db_voltage[device]; + return true; + } + if (site == SYS_CFG_SITE_MB && device == 0) { + /* There is only one motherboard voltage sensor: + * VIO : 3.3V : bus voltage between mother and daughterboard + */ + *val = 3300000; + return true; + } + break; + case SYS_CFG_OSC: + if (site == SYS_CFG_SITE_MB && device < sizeof(s->mb_clock)) { + /* motherboard clock */ + *val = s->mb_clock[device]; + return true; + } + if (site == SYS_CFG_SITE_DB1 && device < s->db_num_clocks) { + /* daughterboard clock */ + *val = s->db_clock[device]; + return true; + } + break; + default: + break; + } + +cfgctrl_unimp: + qemu_log_mask(LOG_UNIMP, + "arm_sysctl: Unimplemented SYS_CFGCTRL read of function " + "0x%x DCC 0x%x site 0x%x position 0x%x device 0x%x\n", + function, dcc, site, position, device); + return false; +} + +/** + * vexpress_cfgctrl_write: + * @s: arm_sysctl_state pointer + * @dcc, @function, @site, @position, @device: split out values from + * SYS_CFGCTRL register + * @val: data to write + * + * Handle a VExpress SYS_CFGCTRL register write. On success, return true. + * On failure, return false. + */ +static bool vexpress_cfgctrl_write(arm_sysctl_state *s, unsigned int dcc, + unsigned int function, unsigned int site, + unsigned int position, unsigned int device, + uint32_t val) +{ + /* We don't support anything other than DCC 0, board stack position 0 + * or sites other than motherboard/daughterboard: + */ + if (dcc != 0 || position != 0 || + (site != SYS_CFG_SITE_MB && site != SYS_CFG_SITE_DB1)) { + goto cfgctrl_unimp; + } + + switch (function) { + case SYS_CFG_OSC: + if (site == SYS_CFG_SITE_MB && device < sizeof(s->mb_clock)) { + /* motherboard clock */ + s->mb_clock[device] = val; + return true; + } + if (site == SYS_CFG_SITE_DB1 && device < s->db_num_clocks) { + /* daughterboard clock */ + s->db_clock[device] = val; + return true; + } + break; + case SYS_CFG_MUXFPGA: + if (site == SYS_CFG_SITE_MB && device == 0) { + /* Select whether video output comes from motherboard + * or daughterboard: log and ignore as QEMU doesn't + * support this. + */ + qemu_log_mask(LOG_UNIMP, "arm_sysctl: selection of video output " + "not supported, ignoring\n"); + return true; + } + break; + case SYS_CFG_SHUTDOWN: + if (site == SYS_CFG_SITE_MB && device == 0) { + qemu_system_shutdown_request(); + return true; + } + break; + case SYS_CFG_REBOOT: + if (site == SYS_CFG_SITE_MB && device == 0) { + qemu_system_reset_request(); + return true; + } + break; + case SYS_CFG_DVIMODE: + if (site == SYS_CFG_SITE_MB && device == 0) { + /* Selecting DVI mode is meaningless for QEMU: we will + * always display the output correctly according to the + * pixel height/width programmed into the CLCD controller. + */ + return true; + } + default: + break; + } + +cfgctrl_unimp: + qemu_log_mask(LOG_UNIMP, + "arm_sysctl: Unimplemented SYS_CFGCTRL write of function " + "0x%x DCC 0x%x site 0x%x position 0x%x device 0x%x\n", + function, dcc, site, position, device); + return false; +} + static void arm_sysctl_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) { @@ -322,17 +504,33 @@ static void arm_sysctl_write(void *opaque, hwaddr offset, if (board_id(s) != BOARD_ID_VEXPRESS) { goto bad_reg; } - s->sys_cfgctrl = val & ~(3 << 18); - s->sys_cfgstat = 1; /* complete */ - switch (s->sys_cfgctrl) { - case 0xc0800000: /* SYS_CFG_SHUTDOWN to motherboard */ - qemu_system_shutdown_request(); - break; - case 0xc0900000: /* SYS_CFG_REBOOT to motherboard */ - qemu_system_reset_request(); - break; - default: - s->sys_cfgstat |= 2; /* error */ + /* Undefined bits [19:18] are RAZ/WI, and writing to + * the start bit just triggers the action; it always reads + * as zero. + */ + s->sys_cfgctrl = val & ~((3 << 18) | (1 << 31)); + if (val & (1 << 31)) { + /* Start bit set -- actually do something */ + unsigned int dcc = extract32(s->sys_cfgctrl, 26, 4); + unsigned int function = extract32(s->sys_cfgctrl, 20, 6); + unsigned int site = extract32(s->sys_cfgctrl, 16, 2); + unsigned int position = extract32(s->sys_cfgctrl, 12, 4); + unsigned int device = extract32(s->sys_cfgctrl, 0, 12); + s->sys_cfgstat = 1; /* complete */ + if (s->sys_cfgctrl & (1 << 30)) { + if (!vexpress_cfgctrl_write(s, dcc, function, site, position, + device, s->sys_cfgdata)) { + s->sys_cfgstat |= 2; /* error */ + } + } else { + uint32_t val; + if (!vexpress_cfgctrl_read(s, dcc, function, site, position, + device, &val)) { + s->sys_cfgstat |= 2; /* error */ + } else { + s->sys_cfgdata = val; + } + } } s->sys_cfgctrl &= ~(1 << 31); return; @@ -385,29 +583,50 @@ static void arm_sysctl_gpio_set(void *opaque, int line, int level) } } -static int arm_sysctl_init(SysBusDevice *dev) +static void arm_sysctl_init(Object *obj) { - arm_sysctl_state *s = FROM_SYSBUS(arm_sysctl_state, dev); + DeviceState *dev = DEVICE(obj); + SysBusDevice *sd = SYS_BUS_DEVICE(obj); + arm_sysctl_state *s = FROM_SYSBUS(arm_sysctl_state, sd); memory_region_init_io(&s->iomem, &arm_sysctl_ops, s, "arm-sysctl", 0x1000); - sysbus_init_mmio(dev, &s->iomem); - qdev_init_gpio_in(&s->busdev.qdev, arm_sysctl_gpio_set, 2); - qdev_init_gpio_out(&s->busdev.qdev, &s->pl110_mux_ctrl, 1); - return 0; + sysbus_init_mmio(sd, &s->iomem); + qdev_init_gpio_in(dev, arm_sysctl_gpio_set, 2); + qdev_init_gpio_out(dev, &s->pl110_mux_ctrl, 1); +} + +static void arm_sysctl_realize(DeviceState *d, Error **errp) +{ + arm_sysctl_state *s = FROM_SYSBUS(arm_sysctl_state, SYS_BUS_DEVICE(d)); + s->db_clock = g_new0(uint32_t, s->db_num_clocks); +} + +static void arm_sysctl_finalize(Object *obj) +{ + SysBusDevice *dev = SYS_BUS_DEVICE(obj); + arm_sysctl_state *s = FROM_SYSBUS(arm_sysctl_state, dev); + g_free(s->db_voltage); + g_free(s->db_clock); + g_free(s->db_clock_reset); } static Property arm_sysctl_properties[] = { DEFINE_PROP_UINT32("sys_id", arm_sysctl_state, sys_id, 0), DEFINE_PROP_UINT32("proc_id", arm_sysctl_state, proc_id, 0), + /* Daughterboard power supply voltages (as reported via SYS_CFG) */ + DEFINE_PROP_ARRAY("db-voltage", arm_sysctl_state, db_num_vsensors, + db_voltage, qdev_prop_uint32, uint32_t), + /* Daughterboard clock reset values (as reported via SYS_CFG) */ + DEFINE_PROP_ARRAY("db-clock", arm_sysctl_state, db_num_clocks, + db_clock_reset, qdev_prop_uint32, uint32_t), DEFINE_PROP_END_OF_LIST(), }; static void arm_sysctl_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); - SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); - k->init = arm_sysctl_init; + dc->realize = arm_sysctl_realize; dc->reset = arm_sysctl_reset; dc->vmsd = &vmstate_arm_sysctl; dc->props = arm_sysctl_properties; @@ -417,6 +636,8 @@ static const TypeInfo arm_sysctl_info = { .name = "realview_sysctl", .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(arm_sysctl_state), + .instance_init = arm_sysctl_init, + .instance_finalize = arm_sysctl_finalize, .class_init = arm_sysctl_class_init, }; diff --git a/hw/pl330.c b/hw/pl330.c new file mode 100644 index 0000000000..1a04773a71 --- /dev/null +++ b/hw/pl330.c @@ -0,0 +1,1654 @@ +/* + * ARM PrimeCell PL330 DMA Controller + * + * Copyright (c) 2009 Samsung Electronics. + * Contributed by Kirill Batuzov <batuzovk@ispras.ru> + * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.crosthwaite@petalogix.com) + * Copyright (c) 2012 PetaLogix Pty Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 or later. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "sysbus.h" +#include "qemu/timer.h" +#include "sysemu/dma.h" + +#ifndef PL330_ERR_DEBUG +#define PL330_ERR_DEBUG 0 +#endif + +#define DB_PRINT_L(lvl, fmt, args...) do {\ + if (PL330_ERR_DEBUG >= lvl) {\ + fprintf(stderr, "PL330: %s:" fmt, __func__, ## args);\ + } \ +} while (0); + +#define DB_PRINT(fmt, args...) DB_PRINT_L(1, fmt, ## args) + +#define PL330_PERIPH_NUM 32 +#define PL330_MAX_BURST_LEN 128 +#define PL330_INSN_MAXSIZE 6 + +#define PL330_FIFO_OK 0 +#define PL330_FIFO_STALL 1 +#define PL330_FIFO_ERR (-1) + +#define PL330_FAULT_UNDEF_INSTR (1 << 0) +#define PL330_FAULT_OPERAND_INVALID (1 << 1) +#define PL330_FAULT_DMAGO_ERR (1 << 4) +#define PL330_FAULT_EVENT_ERR (1 << 5) +#define PL330_FAULT_CH_PERIPH_ERR (1 << 6) +#define PL330_FAULT_CH_RDWR_ERR (1 << 7) +#define PL330_FAULT_ST_DATA_UNAVAILABLE (1 << 12) +#define PL330_FAULT_FIFOEMPTY_ERR (1 << 13) +#define PL330_FAULT_INSTR_FETCH_ERR (1 << 16) +#define PL330_FAULT_DATA_WRITE_ERR (1 << 17) +#define PL330_FAULT_DATA_READ_ERR (1 << 18) +#define PL330_FAULT_DBG_INSTR (1 << 30) +#define PL330_FAULT_LOCKUP_ERR (1 << 31) + +#define PL330_UNTAGGED 0xff + +#define PL330_SINGLE 0x0 +#define PL330_BURST 0x1 + +#define PL330_WATCHDOG_LIMIT 1024 + +/* IOMEM mapped registers */ +#define PL330_REG_DSR 0x000 +#define PL330_REG_DPC 0x004 +#define PL330_REG_INTEN 0x020 +#define PL330_REG_INT_EVENT_RIS 0x024 +#define PL330_REG_INTMIS 0x028 +#define PL330_REG_INTCLR 0x02C +#define PL330_REG_FSRD 0x030 +#define PL330_REG_FSRC 0x034 +#define PL330_REG_FTRD 0x038 +#define PL330_REG_FTR_BASE 0x040 +#define PL330_REG_CSR_BASE 0x100 +#define PL330_REG_CPC_BASE 0x104 +#define PL330_REG_CHANCTRL 0x400 +#define PL330_REG_DBGSTATUS 0xD00 +#define PL330_REG_DBGCMD 0xD04 +#define PL330_REG_DBGINST0 0xD08 +#define PL330_REG_DBGINST1 0xD0C +#define PL330_REG_CR0_BASE 0xE00 +#define PL330_REG_PERIPH_ID 0xFE0 + +#define PL330_IOMEM_SIZE 0x1000 + +#define CFG_BOOT_ADDR 2 +#define CFG_INS 3 +#define CFG_PNS 4 +#define CFG_CRD 5 + +static const uint32_t pl330_id[] = { + 0x30, 0x13, 0x24, 0x00, 0x0D, 0xF0, 0x05, 0xB1 +}; + +/* DMA channel states as they are described in PL330 Technical Reference Manual + * Most of them will not be used in emulation. + */ +typedef enum { + pl330_chan_stopped = 0, + pl330_chan_executing = 1, + pl330_chan_cache_miss = 2, + pl330_chan_updating_pc = 3, + pl330_chan_waiting_event = 4, + pl330_chan_at_barrier = 5, + pl330_chan_queue_busy = 6, + pl330_chan_waiting_periph = 7, + pl330_chan_killing = 8, + pl330_chan_completing = 9, + pl330_chan_fault_completing = 14, + pl330_chan_fault = 15, +} PL330ChanState; + +typedef struct PL330State PL330State; + +typedef struct PL330Chan { + uint32_t src; + uint32_t dst; + uint32_t pc; + uint32_t control; + uint32_t status; + uint32_t lc[2]; + uint32_t fault_type; + uint32_t watchdog_timer; + + bool ns; + uint8_t request_flag; + uint8_t wakeup; + uint8_t wfp_sbp; + + uint8_t state; + uint8_t stall; + + bool is_manager; + PL330State *parent; + uint8_t tag; +} PL330Chan; + +static const VMStateDescription vmstate_pl330_chan = { + .name = "pl330_chan", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(src, PL330Chan), + VMSTATE_UINT32(dst, PL330Chan), + VMSTATE_UINT32(pc, PL330Chan), + VMSTATE_UINT32(control, PL330Chan), + VMSTATE_UINT32(status, PL330Chan), + VMSTATE_UINT32_ARRAY(lc, PL330Chan, 2), + VMSTATE_UINT32(fault_type, PL330Chan), + VMSTATE_UINT32(watchdog_timer, PL330Chan), + VMSTATE_BOOL(ns, PL330Chan), + VMSTATE_UINT8(request_flag, PL330Chan), + VMSTATE_UINT8(wakeup, PL330Chan), + VMSTATE_UINT8(wfp_sbp, PL330Chan), + VMSTATE_UINT8(state, PL330Chan), + VMSTATE_UINT8(stall, PL330Chan), + VMSTATE_END_OF_LIST() + } +}; + +typedef struct PL330Fifo { + uint8_t *buf; + uint8_t *tag; + uint32_t head; + uint32_t num; + uint32_t buf_size; +} PL330Fifo; + +static const VMStateDescription vmstate_pl330_fifo = { + .name = "pl330_chan", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_VBUFFER_UINT32(buf, PL330Fifo, 1, NULL, 0, buf_size), + VMSTATE_VBUFFER_UINT32(tag, PL330Fifo, 1, NULL, 0, buf_size), + VMSTATE_UINT32(head, PL330Fifo), + VMSTATE_UINT32(num, PL330Fifo), + VMSTATE_UINT32(buf_size, PL330Fifo), + VMSTATE_END_OF_LIST() + } +}; + +typedef struct PL330QueueEntry { + uint32_t addr; + uint32_t len; + uint8_t n; + bool inc; + bool z; + uint8_t tag; + uint8_t seqn; +} PL330QueueEntry; + +static const VMStateDescription vmstate_pl330_queue_entry = { + .name = "pl330_queue_entry", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(addr, PL330QueueEntry), + VMSTATE_UINT32(len, PL330QueueEntry), + VMSTATE_UINT8(n, PL330QueueEntry), + VMSTATE_BOOL(inc, PL330QueueEntry), + VMSTATE_BOOL(z, PL330QueueEntry), + VMSTATE_UINT8(tag, PL330QueueEntry), + VMSTATE_UINT8(seqn, PL330QueueEntry), + VMSTATE_END_OF_LIST() + } +}; + +typedef struct PL330Queue { + PL330State *parent; + PL330QueueEntry *queue; + uint32_t queue_size; +} PL330Queue; + +static const VMStateDescription vmstate_pl330_queue = { + .name = "pl330_queue", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT_VARRAY_UINT32(queue, PL330Queue, queue_size, 1, + vmstate_pl330_queue_entry, PL330QueueEntry), + VMSTATE_END_OF_LIST() + } +}; + +struct PL330State { + SysBusDevice busdev; + MemoryRegion iomem; + qemu_irq irq_abort; + qemu_irq *irq; + + /* Config registers. cfg[5] = CfgDn. */ + uint32_t cfg[6]; +#define EVENT_SEC_STATE 3 +#define PERIPH_SEC_STATE 4 + /* cfg 0 bits and pieces */ + uint32_t num_chnls; + uint8_t num_periph_req; + uint8_t num_events; + uint8_t mgr_ns_at_rst; + /* cfg 1 bits and pieces */ + uint8_t i_cache_len; + uint8_t num_i_cache_lines; + /* CRD bits and pieces */ + uint8_t data_width; + uint8_t wr_cap; + uint8_t wr_q_dep; + uint8_t rd_cap; + uint8_t rd_q_dep; + uint16_t data_buffer_dep; + + PL330Chan manager; + PL330Chan *chan; + PL330Fifo fifo; + PL330Queue read_queue; + PL330Queue write_queue; + uint8_t *lo_seqn; + uint8_t *hi_seqn; + QEMUTimer *timer; /* is used for restore dma. */ + + uint32_t inten; + uint32_t int_status; + uint32_t ev_status; + uint32_t dbg[2]; + uint8_t debug_status; + uint8_t num_faulting; + uint8_t periph_busy[PL330_PERIPH_NUM]; + +}; + +#define TYPE_PL330 "pl330" +#define PL330(obj) OBJECT_CHECK(PL330State, (obj), TYPE_PL330) + +static const VMStateDescription vmstate_pl330 = { + .name = "pl330", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(manager, PL330State, 0, vmstate_pl330_chan, PL330Chan), + VMSTATE_STRUCT_VARRAY_UINT32(chan, PL330State, num_chnls, 0, + vmstate_pl330_chan, PL330Chan), + VMSTATE_VBUFFER_UINT32(lo_seqn, PL330State, 1, NULL, 0, num_chnls), + VMSTATE_VBUFFER_UINT32(hi_seqn, PL330State, 1, NULL, 0, num_chnls), + VMSTATE_STRUCT(fifo, PL330State, 0, vmstate_pl330_fifo, PL330Fifo), + VMSTATE_STRUCT(read_queue, PL330State, 0, vmstate_pl330_queue, + PL330Queue), + VMSTATE_STRUCT(write_queue, PL330State, 0, vmstate_pl330_queue, + PL330Queue), + VMSTATE_TIMER(timer, PL330State), + VMSTATE_UINT32(inten, PL330State), + VMSTATE_UINT32(int_status, PL330State), + VMSTATE_UINT32(ev_status, PL330State), + VMSTATE_UINT32_ARRAY(dbg, PL330State, 2), + VMSTATE_UINT8(debug_status, PL330State), + VMSTATE_UINT8(num_faulting, PL330State), + VMSTATE_UINT8_ARRAY(periph_busy, PL330State, PL330_PERIPH_NUM), + VMSTATE_END_OF_LIST() + } +}; + +typedef struct PL330InsnDesc { + /* OPCODE of the instruction */ + uint8_t opcode; + /* Mask so we can select several sibling instructions, such as + DMALD, DMALDS and DMALDB */ + uint8_t opmask; + /* Size of instruction in bytes */ + uint8_t size; + /* Interpreter */ + void (*exec)(PL330Chan *, uint8_t opcode, uint8_t *args, int len); +} PL330InsnDesc; + + +/* MFIFO Implementation + * + * MFIFO is implemented as a cyclic buffer of BUF_SIZE size. Tagged bytes are + * stored in this buffer. Data is stored in BUF field, tags - in the + * corresponding array elements of TAG field. + */ + +/* Initialize queue. */ + +static void pl330_fifo_init(PL330Fifo *s, uint32_t size) +{ + s->buf = g_malloc0(size); + s->tag = g_malloc0(size); + s->buf_size = size; +} + +/* Cyclic increment */ + +static inline int pl330_fifo_inc(PL330Fifo *s, int x) +{ + return (x + 1) % s->buf_size; +} + +/* Number of empty bytes in MFIFO */ + +static inline int pl330_fifo_num_free(PL330Fifo *s) +{ + return s->buf_size - s->num; +} + +/* Push LEN bytes of data stored in BUF to MFIFO and tag it with TAG. + * Zero returned on success, PL330_FIFO_STALL if there is no enough free + * space in MFIFO to store requested amount of data. If push was unsuccessful + * no data is stored to MFIFO. + */ + +static int pl330_fifo_push(PL330Fifo *s, uint8_t *buf, int len, uint8_t tag) +{ + int i; + + if (s->buf_size - s->num < len) { + return PL330_FIFO_STALL; + } + for (i = 0; i < len; i++) { + int push_idx = (s->head + s->num + i) % s->buf_size; + s->buf[push_idx] = buf[i]; + s->tag[push_idx] = tag; + } + s->num += len; + return PL330_FIFO_OK; +} + +/* Get LEN bytes of data from MFIFO and store it to BUF. Tag value of each + * byte is verified. Zero returned on success, PL330_FIFO_ERR on tag mismatch + * and PL330_FIFO_STALL if there is no enough data in MFIFO. If get was + * unsuccessful no data is removed from MFIFO. + */ + +static int pl330_fifo_get(PL330Fifo *s, uint8_t *buf, int len, uint8_t tag) +{ + int i; + + if (s->num < len) { + return PL330_FIFO_STALL; + } + for (i = 0; i < len; i++) { + if (s->tag[s->head] == tag) { + int get_idx = (s->head + i) % s->buf_size; + buf[i] = s->buf[get_idx]; + } else { /* Tag mismatch - Rollback transaction */ + return PL330_FIFO_ERR; + } + } + s->head = (s->head + len) % s->buf_size; + s->num -= len; + return PL330_FIFO_OK; +} + +/* Reset MFIFO. This completely erases all data in it. */ + +static inline void pl330_fifo_reset(PL330Fifo *s) +{ + s->head = 0; + s->num = 0; +} + +/* Return tag of the first byte stored in MFIFO. If MFIFO is empty + * PL330_UNTAGGED is returned. + */ + +static inline uint8_t pl330_fifo_tag(PL330Fifo *s) +{ + return (!s->num) ? PL330_UNTAGGED : s->tag[s->head]; +} + +/* Returns non-zero if tag TAG is present in fifo or zero otherwise */ + +static int pl330_fifo_has_tag(PL330Fifo *s, uint8_t tag) +{ + int i, n; + + i = s->head; + for (n = 0; n < s->num; n++) { + if (s->tag[i] == tag) { + return 1; + } + i = pl330_fifo_inc(s, i); + } + return 0; +} + +/* Remove all entry tagged with TAG from MFIFO */ + +static void pl330_fifo_tagged_remove(PL330Fifo *s, uint8_t tag) +{ + int i, t, n; + + t = i = s->head; + for (n = 0; n < s->num; n++) { + if (s->tag[i] != tag) { + s->buf[t] = s->buf[i]; + s->tag[t] = s->tag[i]; + t = pl330_fifo_inc(s, t); + } else { + s->num = s->num - 1; + } + i = pl330_fifo_inc(s, i); + } +} + +/* Read-Write Queue implementation + * + * A Read-Write Queue stores up to QUEUE_SIZE instructions (loads or stores). + * Each instruction is described by source (for loads) or destination (for + * stores) address ADDR, width of data to be loaded/stored LEN, number of + * stores/loads to be performed N, INC bit, Z bit and TAG to identify channel + * this instruction belongs to. Queue does not store any information about + * nature of the instruction: is it load or store. PL330 has different queues + * for loads and stores so this is already known at the top level where it + * matters. + * + * Queue works as FIFO for instructions with equivalent tags, but can issue + * instructions with different tags in arbitrary order. SEQN field attached to + * each instruction helps to achieve this. For each TAG queue contains + * instructions with consecutive SEQN values ranging from LO_SEQN[TAG] to + * HI_SEQN[TAG]-1 inclusive. SEQN is 8-bit unsigned integer, so SEQN=255 is + * followed by SEQN=0. + * + * Z bit indicates that zeroes should be stored. No MFIFO fetches are performed + * in this case. + */ + +static void pl330_queue_reset(PL330Queue *s) +{ + int i; + + for (i = 0; i < s->queue_size; i++) { + s->queue[i].tag = PL330_UNTAGGED; + } +} + +/* Initialize queue */ +static void pl330_queue_init(PL330Queue *s, int size, PL330State *parent) +{ + s->parent = parent; + s->queue = g_new0(PL330QueueEntry, size); + s->queue_size = size; +} + +/* Returns pointer to an empty slot or NULL if queue is full */ +static PL330QueueEntry *pl330_queue_find_empty(PL330Queue *s) +{ + int i; + + for (i = 0; i < s->queue_size; i++) { + if (s->queue[i].tag == PL330_UNTAGGED) { + return &s->queue[i]; + } + } + return NULL; +} + +/* Put instruction in queue. + * Return value: + * - zero - OK + * - non-zero - queue is full + */ + +static int pl330_queue_put_insn(PL330Queue *s, uint32_t addr, + int len, int n, bool inc, bool z, uint8_t tag) +{ + PL330QueueEntry *entry = pl330_queue_find_empty(s); + + if (!entry) { + return 1; + } + entry->tag = tag; + entry->addr = addr; + entry->len = len; + entry->n = n; + entry->z = z; + entry->inc = inc; + entry->seqn = s->parent->hi_seqn[tag]; + s->parent->hi_seqn[tag]++; + return 0; +} + +/* Returns a pointer to queue slot containing instruction which satisfies + * following conditions: + * - it has valid tag value (not PL330_UNTAGGED) + * - if enforce_seq is set it has to be issuable without violating queue + * logic (see above) + * - if TAG argument is not PL330_UNTAGGED this instruction has tag value + * equivalent to the argument TAG value. + * If such instruction cannot be found NULL is returned. + */ + +static PL330QueueEntry *pl330_queue_find_insn(PL330Queue *s, uint8_t tag, + bool enforce_seq) +{ + int i; + + for (i = 0; i < s->queue_size; i++) { + if (s->queue[i].tag != PL330_UNTAGGED) { + if ((!enforce_seq || + s->queue[i].seqn == s->parent->lo_seqn[s->queue[i].tag]) && + (s->queue[i].tag == tag || tag == PL330_UNTAGGED || + s->queue[i].z)) { + return &s->queue[i]; + } + } + } + return NULL; +} + +/* Removes instruction from queue. */ + +static inline void pl330_queue_remove_insn(PL330Queue *s, PL330QueueEntry *e) +{ + s->parent->lo_seqn[e->tag]++; + e->tag = PL330_UNTAGGED; +} + +/* Removes all instructions tagged with TAG from queue. */ + +static inline void pl330_queue_remove_tagged(PL330Queue *s, uint8_t tag) +{ + int i; + + for (i = 0; i < s->queue_size; i++) { + if (s->queue[i].tag == tag) { + s->queue[i].tag = PL330_UNTAGGED; + } + } +} + +/* DMA instruction execution engine */ + +/* Moves DMA channel to the FAULT state and updates it's status. */ + +static inline void pl330_fault(PL330Chan *ch, uint32_t flags) +{ + DB_PRINT("ch: %p, flags: %x\n", ch, flags); + ch->fault_type |= flags; + if (ch->state == pl330_chan_fault) { + return; + } + ch->state = pl330_chan_fault; + ch->parent->num_faulting++; + if (ch->parent->num_faulting == 1) { + DB_PRINT("abort interrupt raised\n"); + qemu_irq_raise(ch->parent->irq_abort); + } +} + +/* + * For information about instructions see PL330 Technical Reference Manual. + * + * Arguments: + * CH - channel executing the instruction + * OPCODE - opcode + * ARGS - array of 8-bit arguments + * LEN - number of elements in ARGS array + */ + +static void pl330_dmaaddh(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len) +{ + uint16_t im = (((uint16_t)args[1]) << 8) | ((uint16_t)args[0]); + uint8_t ra = (opcode >> 1) & 1; + + if (ch->is_manager) { + pl330_fault(ch, PL330_FAULT_UNDEF_INSTR); + return; + } + if (ra) { + ch->dst += im; + } else { + ch->src += im; + } +} + +static void pl330_dmaend(PL330Chan *ch, uint8_t opcode, + uint8_t *args, int len) +{ + PL330State *s = ch->parent; + + if (ch->state == pl330_chan_executing && !ch->is_manager) { + /* Wait for all transfers to complete */ + if (pl330_fifo_has_tag(&s->fifo, ch->tag) || + pl330_queue_find_insn(&s->read_queue, ch->tag, false) != NULL || + pl330_queue_find_insn(&s->write_queue, ch->tag, false) != NULL) { + + ch->stall = 1; + return; + } + } + DB_PRINT("DMA ending!\n"); + pl330_fifo_tagged_remove(&s->fifo, ch->tag); + pl330_queue_remove_tagged(&s->read_queue, ch->tag); + pl330_queue_remove_tagged(&s->write_queue, ch->tag); + ch->state = pl330_chan_stopped; +} + +static void pl330_dmaflushp(PL330Chan *ch, uint8_t opcode, + uint8_t *args, int len) +{ + uint8_t periph_id; + + if (args[0] & 7) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + periph_id = (args[0] >> 3) & 0x1f; + if (periph_id >= ch->parent->num_periph_req) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + if (ch->ns && !(ch->parent->cfg[CFG_PNS] & (1 << periph_id))) { + pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR); + return; + } + /* Do nothing */ +} + +static void pl330_dmago(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len) +{ + uint8_t chan_id; + uint8_t ns; + uint32_t pc; + PL330Chan *s; + + DB_PRINT("\n"); + + if (!ch->is_manager) { + pl330_fault(ch, PL330_FAULT_UNDEF_INSTR); + return; + } + ns = !!(opcode & 2); + chan_id = args[0] & 7; + if ((args[0] >> 3)) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + if (chan_id >= ch->parent->num_chnls) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + pc = (((uint32_t)args[4]) << 24) | (((uint32_t)args[3]) << 16) | + (((uint32_t)args[2]) << 8) | (((uint32_t)args[1])); + if (ch->parent->chan[chan_id].state != pl330_chan_stopped) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + if (ch->ns && !ns) { + pl330_fault(ch, PL330_FAULT_DMAGO_ERR); + return; + } + s = &ch->parent->chan[chan_id]; + s->ns = ns; + s->pc = pc; + s->state = pl330_chan_executing; +} + +static void pl330_dmald(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len) +{ + uint8_t bs = opcode & 3; + uint32_t size, num; + bool inc; + + if (bs == 2) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + if ((bs == 1 && ch->request_flag == PL330_BURST) || + (bs == 3 && ch->request_flag == PL330_SINGLE)) { + /* Perform NOP */ + return; + } + if (bs == 1 && ch->request_flag == PL330_SINGLE) { + num = 1; + } else { + num = ((ch->control >> 4) & 0xf) + 1; + } + size = (uint32_t)1 << ((ch->control >> 1) & 0x7); + inc = !!(ch->control & 1); + ch->stall = pl330_queue_put_insn(&ch->parent->read_queue, ch->src, + size, num, inc, 0, ch->tag); + if (!ch->stall) { + DB_PRINT("channel:%d address:%08x size:%d num:%d %c\n", + ch->tag, ch->src, size, num, inc ? 'Y' : 'N'); + ch->src += inc ? size * num - (ch->src & (size - 1)) : 0; + } +} + +static void pl330_dmaldp(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len) +{ + uint8_t periph_id; + + if (args[0] & 7) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + periph_id = (args[0] >> 3) & 0x1f; + if (periph_id >= ch->parent->num_periph_req) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + if (ch->ns && !(ch->parent->cfg[CFG_PNS] & (1 << periph_id))) { + pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR); + return; + } + pl330_dmald(ch, opcode, args, len); +} + +static void pl330_dmalp(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len) +{ + uint8_t lc = (opcode & 2) >> 1; + + ch->lc[lc] = args[0]; +} + +static void pl330_dmakill(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len) +{ + if (ch->state == pl330_chan_fault || + ch->state == pl330_chan_fault_completing) { + /* This is the only way for a channel to leave the faulting state */ + ch->fault_type = 0; + ch->parent->num_faulting--; + if (ch->parent->num_faulting == 0) { + DB_PRINT("abort interrupt lowered\n"); + qemu_irq_lower(ch->parent->irq_abort); + } + } + ch->state = pl330_chan_killing; + pl330_fifo_tagged_remove(&ch->parent->fifo, ch->tag); + pl330_queue_remove_tagged(&ch->parent->read_queue, ch->tag); + pl330_queue_remove_tagged(&ch->parent->write_queue, ch->tag); + ch->state = pl330_chan_stopped; +} + +static void pl330_dmalpend(PL330Chan *ch, uint8_t opcode, + uint8_t *args, int len) +{ + uint8_t nf = (opcode & 0x10) >> 4; + uint8_t bs = opcode & 3; + uint8_t lc = (opcode & 4) >> 2; + + if (bs == 2) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + if ((bs == 1 && ch->request_flag == PL330_BURST) || + (bs == 3 && ch->request_flag == PL330_SINGLE)) { + /* Perform NOP */ + return; + } + if (!nf || ch->lc[lc]) { + if (nf) { + ch->lc[lc]--; + } + DB_PRINT("loop reiteration\n"); + ch->pc -= args[0]; + ch->pc -= len + 1; + /* "ch->pc -= args[0] + len + 1" is incorrect when args[0] == 256 */ + } else { + DB_PRINT("loop fallthrough\n"); + } +} + + +static void pl330_dmamov(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len) +{ + uint8_t rd = args[0] & 7; + uint32_t im; + + if ((args[0] >> 3)) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + im = (((uint32_t)args[4]) << 24) | (((uint32_t)args[3]) << 16) | + (((uint32_t)args[2]) << 8) | (((uint32_t)args[1])); + switch (rd) { + case 0: + ch->src = im; + break; + case 1: + ch->control = im; + break; + case 2: + ch->dst = im; + break; + default: + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } +} + +static void pl330_dmanop(PL330Chan *ch, uint8_t opcode, + uint8_t *args, int len) +{ + /* NOP is NOP. */ +} + +static void pl330_dmarmb(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len) +{ + if (pl330_queue_find_insn(&ch->parent->read_queue, ch->tag, false)) { + ch->state = pl330_chan_at_barrier; + ch->stall = 1; + return; + } else { + ch->state = pl330_chan_executing; + } +} + +static void pl330_dmasev(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len) +{ + uint8_t ev_id; + + if (args[0] & 7) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + ev_id = (args[0] >> 3) & 0x1f; + if (ev_id >= ch->parent->num_events) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + if (ch->ns && !(ch->parent->cfg[CFG_INS] & (1 << ev_id))) { + pl330_fault(ch, PL330_FAULT_EVENT_ERR); + return; + } + if (ch->parent->inten & (1 << ev_id)) { + ch->parent->int_status |= (1 << ev_id); + DB_PRINT("event interrupt raised %d\n", ev_id); + qemu_irq_raise(ch->parent->irq[ev_id]); + } else { + ch->parent->ev_status |= (1 << ev_id); + } +} + +static void pl330_dmast(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len) +{ + uint8_t bs = opcode & 3; + uint32_t size, num; + bool inc; + + if (bs == 2) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + if ((bs == 1 && ch->request_flag == PL330_BURST) || + (bs == 3 && ch->request_flag == PL330_SINGLE)) { + /* Perform NOP */ + return; + } + num = ((ch->control >> 18) & 0xf) + 1; + size = (uint32_t)1 << ((ch->control >> 15) & 0x7); + inc = !!((ch->control >> 14) & 1); + ch->stall = pl330_queue_put_insn(&ch->parent->write_queue, ch->dst, + size, num, inc, 0, ch->tag); + if (!ch->stall) { + DB_PRINT("channel:%d address:%08x size:%d num:%d %c\n", + ch->tag, ch->dst, size, num, inc ? 'Y' : 'N'); + ch->dst += inc ? size * num - (ch->dst & (size - 1)) : 0; + } +} + +static void pl330_dmastp(PL330Chan *ch, uint8_t opcode, + uint8_t *args, int len) +{ + uint8_t periph_id; + + if (args[0] & 7) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + periph_id = (args[0] >> 3) & 0x1f; + if (periph_id >= ch->parent->num_periph_req) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + if (ch->ns && !(ch->parent->cfg[CFG_PNS] & (1 << periph_id))) { + pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR); + return; + } + pl330_dmast(ch, opcode, args, len); +} + +static void pl330_dmastz(PL330Chan *ch, uint8_t opcode, + uint8_t *args, int len) +{ + uint32_t size, num; + bool inc; + + num = ((ch->control >> 18) & 0xf) + 1; + size = (uint32_t)1 << ((ch->control >> 15) & 0x7); + inc = !!((ch->control >> 14) & 1); + ch->stall = pl330_queue_put_insn(&ch->parent->write_queue, ch->dst, + size, num, inc, 1, ch->tag); + if (inc) { + ch->dst += size * num; + } +} + +static void pl330_dmawfe(PL330Chan *ch, uint8_t opcode, + uint8_t *args, int len) +{ + uint8_t ev_id; + int i; + + if (args[0] & 5) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + ev_id = (args[0] >> 3) & 0x1f; + if (ev_id >= ch->parent->num_events) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + if (ch->ns && !(ch->parent->cfg[CFG_INS] & (1 << ev_id))) { + pl330_fault(ch, PL330_FAULT_EVENT_ERR); + return; + } + ch->wakeup = ev_id; + ch->state = pl330_chan_waiting_event; + if (~ch->parent->inten & ch->parent->ev_status & 1 << ev_id) { + ch->state = pl330_chan_executing; + /* If anyone else is currently waiting on the same event, let them + * clear the ev_status so they pick up event as well + */ + for (i = 0; i < ch->parent->num_chnls; ++i) { + PL330Chan *peer = &ch->parent->chan[i]; + if (peer->state == pl330_chan_waiting_event && + peer->wakeup == ev_id) { + return; + } + } + ch->parent->ev_status &= ~(1 << ev_id); + } else { + ch->stall = 1; + } +} + +static void pl330_dmawfp(PL330Chan *ch, uint8_t opcode, + uint8_t *args, int len) +{ + uint8_t bs = opcode & 3; + uint8_t periph_id; + + if (args[0] & 7) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + periph_id = (args[0] >> 3) & 0x1f; + if (periph_id >= ch->parent->num_periph_req) { + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + if (ch->ns && !(ch->parent->cfg[CFG_PNS] & (1 << periph_id))) { + pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR); + return; + } + switch (bs) { + case 0: /* S */ + ch->request_flag = PL330_SINGLE; + ch->wfp_sbp = 0; + break; + case 1: /* P */ + ch->request_flag = PL330_BURST; + ch->wfp_sbp = 2; + break; + case 2: /* B */ + ch->request_flag = PL330_BURST; + ch->wfp_sbp = 1; + break; + default: + pl330_fault(ch, PL330_FAULT_OPERAND_INVALID); + return; + } + + if (ch->parent->periph_busy[periph_id]) { + ch->state = pl330_chan_waiting_periph; + ch->stall = 1; + } else if (ch->state == pl330_chan_waiting_periph) { + ch->state = pl330_chan_executing; + } +} + +static void pl330_dmawmb(PL330Chan *ch, uint8_t opcode, + uint8_t *args, int len) +{ + if (pl330_queue_find_insn(&ch->parent->write_queue, ch->tag, false)) { + ch->state = pl330_chan_at_barrier; + ch->stall = 1; + return; + } else { + ch->state = pl330_chan_executing; + } +} + +/* NULL terminated array of the instruction descriptions. */ +static const PL330InsnDesc insn_desc[] = { + { .opcode = 0x54, .opmask = 0xFD, .size = 3, .exec = pl330_dmaaddh, }, + { .opcode = 0x00, .opmask = 0xFF, .size = 1, .exec = pl330_dmaend, }, + { .opcode = 0x35, .opmask = 0xFF, .size = 2, .exec = pl330_dmaflushp, }, + { .opcode = 0xA0, .opmask = 0xFD, .size = 6, .exec = pl330_dmago, }, + { .opcode = 0x04, .opmask = 0xFC, .size = 1, .exec = pl330_dmald, }, + { .opcode = 0x25, .opmask = 0xFD, .size = 2, .exec = pl330_dmaldp, }, + { .opcode = 0x20, .opmask = 0xFD, .size = 2, .exec = pl330_dmalp, }, + /* dmastp must be before dmalpend in this list, because their maps + * are overlapping + */ + { .opcode = 0x29, .opmask = 0xFD, .size = 2, .exec = pl330_dmastp, }, + { .opcode = 0x28, .opmask = 0xE8, .size = 2, .exec = pl330_dmalpend, }, + { .opcode = 0x01, .opmask = 0xFF, .size = 1, .exec = pl330_dmakill, }, + { .opcode = 0xBC, .opmask = 0xFF, .size = 6, .exec = pl330_dmamov, }, + { .opcode = 0x18, .opmask = 0xFF, .size = 1, .exec = pl330_dmanop, }, + { .opcode = 0x12, .opmask = 0xFF, .size = 1, .exec = pl330_dmarmb, }, + { .opcode = 0x34, .opmask = 0xFF, .size = 2, .exec = pl330_dmasev, }, + { .opcode = 0x08, .opmask = 0xFC, .size = 1, .exec = pl330_dmast, }, + { .opcode = 0x0C, .opmask = 0xFF, .size = 1, .exec = pl330_dmastz, }, + { .opcode = 0x36, .opmask = 0xFF, .size = 2, .exec = pl330_dmawfe, }, + { .opcode = 0x30, .opmask = 0xFC, .size = 2, .exec = pl330_dmawfp, }, + { .opcode = 0x13, .opmask = 0xFF, .size = 1, .exec = pl330_dmawmb, }, + { .opcode = 0x00, .opmask = 0x00, .size = 0, .exec = NULL, } +}; + +/* Instructions which can be issued via debug registers. */ +static const PL330InsnDesc debug_insn_desc[] = { + { .opcode = 0xA0, .opmask = 0xFD, .size = 6, .exec = pl330_dmago, }, + { .opcode = 0x01, .opmask = 0xFF, .size = 1, .exec = pl330_dmakill, }, + { .opcode = 0x34, .opmask = 0xFF, .size = 2, .exec = pl330_dmasev, }, + { .opcode = 0x00, .opmask = 0x00, .size = 0, .exec = NULL, } +}; + +static inline const PL330InsnDesc *pl330_fetch_insn(PL330Chan *ch) +{ + uint8_t opcode; + int i; + + dma_memory_read(&dma_context_memory, ch->pc, &opcode, 1); + for (i = 0; insn_desc[i].size; i++) { + if ((opcode & insn_desc[i].opmask) == insn_desc[i].opcode) { + return &insn_desc[i]; + } + } + return NULL; +} + +static inline void pl330_exec_insn(PL330Chan *ch, const PL330InsnDesc *insn) +{ + uint8_t buf[PL330_INSN_MAXSIZE]; + + assert(insn->size <= PL330_INSN_MAXSIZE); + dma_memory_read(&dma_context_memory, ch->pc, buf, insn->size); + insn->exec(ch, buf[0], &buf[1], insn->size - 1); +} + +static inline void pl330_update_pc(PL330Chan *ch, + const PL330InsnDesc *insn) +{ + ch->pc += insn->size; +} + +/* Try to execute current instruction in channel CH. Number of executed + instructions is returned (0 or 1). */ +static int pl330_chan_exec(PL330Chan *ch) +{ + const PL330InsnDesc *insn; + + if (ch->state != pl330_chan_executing && + ch->state != pl330_chan_waiting_periph && + ch->state != pl330_chan_at_barrier && + ch->state != pl330_chan_waiting_event) { + DB_PRINT("%d\n", ch->state); + return 0; + } + ch->stall = 0; + insn = pl330_fetch_insn(ch); + if (!insn) { + DB_PRINT("pl330 undefined instruction\n"); + pl330_fault(ch, PL330_FAULT_UNDEF_INSTR); + return 0; + } + pl330_exec_insn(ch, insn); + if (!ch->stall) { + pl330_update_pc(ch, insn); + ch->watchdog_timer = 0; + return 1; + /* WDT only active in exec state */ + } else if (ch->state == pl330_chan_executing) { + ch->watchdog_timer++; + if (ch->watchdog_timer >= PL330_WATCHDOG_LIMIT) { + pl330_fault(ch, PL330_FAULT_LOCKUP_ERR); + } + } + return 0; +} + +/* Try to execute 1 instruction in each channel, one instruction from read + queue and one instruction from write queue. Number of successfully executed + instructions is returned. */ +static int pl330_exec_cycle(PL330Chan *channel) +{ + PL330State *s = channel->parent; + PL330QueueEntry *q; + int i; + int num_exec = 0; + int fifo_res = 0; + uint8_t buf[PL330_MAX_BURST_LEN]; + + /* Execute one instruction in each channel */ + num_exec += pl330_chan_exec(channel); + + /* Execute one instruction from read queue */ + q = pl330_queue_find_insn(&s->read_queue, PL330_UNTAGGED, true); + if (q != NULL && q->len <= pl330_fifo_num_free(&s->fifo)) { + int len = q->len - (q->addr & (q->len - 1)); + + dma_memory_read(&dma_context_memory, q->addr, buf, len); + if (PL330_ERR_DEBUG > 1) { + DB_PRINT("PL330 read from memory @%08x (size = %08x):\n", + q->addr, len); + hexdump((char *)buf, stderr, "", len); + } + fifo_res = pl330_fifo_push(&s->fifo, buf, len, q->tag); + if (fifo_res == PL330_FIFO_OK) { + if (q->inc) { + q->addr += len; + } + q->n--; + if (!q->n) { + pl330_queue_remove_insn(&s->read_queue, q); + } + num_exec++; + } + } + + /* Execute one instruction from write queue. */ + q = pl330_queue_find_insn(&s->write_queue, pl330_fifo_tag(&s->fifo), true); + if (q != NULL) { + int len = q->len - (q->addr & (q->len - 1)); + + if (q->z) { + for (i = 0; i < len; i++) { + buf[i] = 0; + } + } else { + fifo_res = pl330_fifo_get(&s->fifo, buf, len, q->tag); + } + if (fifo_res == PL330_FIFO_OK || q->z) { + dma_memory_write(&dma_context_memory, q->addr, buf, len); + if (PL330_ERR_DEBUG > 1) { + DB_PRINT("PL330 read from memory @%08x (size = %08x):\n", + q->addr, len); + hexdump((char *)buf, stderr, "", len); + } + if (q->inc) { + q->addr += len; + } + num_exec++; + } else if (fifo_res == PL330_FIFO_STALL) { + pl330_fault(&channel->parent->chan[q->tag], + PL330_FAULT_FIFOEMPTY_ERR); + } + q->n--; + if (!q->n) { + pl330_queue_remove_insn(&s->write_queue, q); + } + } + + return num_exec; +} + +static int pl330_exec_channel(PL330Chan *channel) +{ + int insr_exec = 0; + + /* TODO: Is it all right to execute everything or should we do per-cycle + simulation? */ + while (pl330_exec_cycle(channel)) { + insr_exec++; + } + + /* Detect deadlock */ + if (channel->state == pl330_chan_executing) { + pl330_fault(channel, PL330_FAULT_LOCKUP_ERR); + } + /* Situation when one of the queues has deadlocked but all channels + * have finished their programs should be impossible. + */ + + return insr_exec; +} + +static inline void pl330_exec(PL330State *s) +{ + DB_PRINT("\n"); + int i, insr_exec; + do { + insr_exec = pl330_exec_channel(&s->manager); + + for (i = 0; i < s->num_chnls; i++) { + insr_exec += pl330_exec_channel(&s->chan[i]); + } + } while (insr_exec); +} + +static void pl330_exec_cycle_timer(void *opaque) +{ + PL330State *s = (PL330State *)opaque; + pl330_exec(s); +} + +/* Stop or restore dma operations */ + +static void pl330_dma_stop_irq(void *opaque, int irq, int level) +{ + PL330State *s = (PL330State *)opaque; + + if (s->periph_busy[irq] != level) { + s->periph_busy[irq] = level; + qemu_mod_timer(s->timer, qemu_get_clock_ns(vm_clock)); + } +} + +static void pl330_debug_exec(PL330State *s) +{ + uint8_t args[5]; + uint8_t opcode; + uint8_t chan_id; + int i; + PL330Chan *ch; + const PL330InsnDesc *insn; + + s->debug_status = 1; + chan_id = (s->dbg[0] >> 8) & 0x07; + opcode = (s->dbg[0] >> 16) & 0xff; + args[0] = (s->dbg[0] >> 24) & 0xff; + args[1] = (s->dbg[1] >> 0) & 0xff; + args[2] = (s->dbg[1] >> 8) & 0xff; + args[3] = (s->dbg[1] >> 16) & 0xff; + args[4] = (s->dbg[1] >> 24) & 0xff; + DB_PRINT("chan id: %d\n", chan_id); + if (s->dbg[0] & 1) { + ch = &s->chan[chan_id]; + } else { + ch = &s->manager; + } + insn = NULL; + for (i = 0; debug_insn_desc[i].size; i++) { + if ((opcode & debug_insn_desc[i].opmask) == debug_insn_desc[i].opcode) { + insn = &debug_insn_desc[i]; + } + } + if (!insn) { + pl330_fault(ch, PL330_FAULT_UNDEF_INSTR | PL330_FAULT_DBG_INSTR); + return ; + } + ch->stall = 0; + insn->exec(ch, opcode, args, insn->size - 1); + if (ch->fault_type) { + ch->fault_type |= PL330_FAULT_DBG_INSTR; + } + if (ch->stall) { + qemu_log_mask(LOG_UNIMP, "pl330: stall of debug instruction not " + "implemented\n"); + } + s->debug_status = 0; +} + +/* IOMEM mapped registers */ + +static void pl330_iomem_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + PL330State *s = (PL330State *) opaque; + uint32_t i; + + DB_PRINT("addr: %08x data: %08x\n", (unsigned)offset, (unsigned)value); + + switch (offset) { + case PL330_REG_INTEN: + s->inten = value; + break; + case PL330_REG_INTCLR: + for (i = 0; i < s->num_events; i++) { + if (s->int_status & s->inten & value & (1 << i)) { + DB_PRINT("event interrupt lowered %d\n", i); + qemu_irq_lower(s->irq[i]); + } + } + s->ev_status &= ~(value & s->inten); + s->int_status &= ~(value & s->inten); + break; + case PL330_REG_DBGCMD: + if ((value & 3) == 0) { + pl330_debug_exec(s); + pl330_exec(s); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "pl330: write of illegal value %u " + "for offset " TARGET_FMT_plx "\n", (unsigned)value, + offset); + } + break; + case PL330_REG_DBGINST0: + DB_PRINT("s->dbg[0] = %08x\n", (unsigned)value); + s->dbg[0] = value; + break; + case PL330_REG_DBGINST1: + DB_PRINT("s->dbg[1] = %08x\n", (unsigned)value); + s->dbg[1] = value; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "pl330: bad write offset " TARGET_FMT_plx + "\n", offset); + break; + } +} + +static inline uint32_t pl330_iomem_read_imp(void *opaque, + hwaddr offset) +{ + PL330State *s = (PL330State *)opaque; + int chan_id; + int i; + uint32_t res; + + if (offset >= PL330_REG_PERIPH_ID && offset < PL330_REG_PERIPH_ID + 32) { + return pl330_id[(offset - PL330_REG_PERIPH_ID) >> 2]; + } + if (offset >= PL330_REG_CR0_BASE && offset < PL330_REG_CR0_BASE + 24) { + return s->cfg[(offset - PL330_REG_CR0_BASE) >> 2]; + } + if (offset >= PL330_REG_CHANCTRL && offset < PL330_REG_DBGSTATUS) { + offset -= PL330_REG_CHANCTRL; + chan_id = offset >> 5; + if (chan_id >= s->num_chnls) { + qemu_log_mask(LOG_GUEST_ERROR, "pl330: bad read offset " + TARGET_FMT_plx "\n", offset); + return 0; + } + switch (offset & 0x1f) { + case 0x00: + return s->chan[chan_id].src; + case 0x04: + return s->chan[chan_id].dst; + case 0x08: + return s->chan[chan_id].control; + case 0x0C: + return s->chan[chan_id].lc[0]; + case 0x10: + return s->chan[chan_id].lc[1]; + default: + qemu_log_mask(LOG_GUEST_ERROR, "pl330: bad read offset " + TARGET_FMT_plx "\n", offset); + return 0; + } + } + if (offset >= PL330_REG_CSR_BASE && offset < 0x400) { + offset -= PL330_REG_CSR_BASE; + chan_id = offset >> 3; + if (chan_id >= s->num_chnls) { + qemu_log_mask(LOG_GUEST_ERROR, "pl330: bad read offset " + TARGET_FMT_plx "\n", offset); + return 0; + } + switch ((offset >> 2) & 1) { + case 0x0: + res = (s->chan[chan_id].ns << 21) | + (s->chan[chan_id].wakeup << 4) | + (s->chan[chan_id].state) | + (s->chan[chan_id].wfp_sbp << 14); + return res; + case 0x1: + return s->chan[chan_id].pc; + default: + qemu_log_mask(LOG_GUEST_ERROR, "pl330: read error\n"); + return 0; + } + } + if (offset >= PL330_REG_FTR_BASE && offset < 0x100) { + offset -= PL330_REG_FTR_BASE; + chan_id = offset >> 2; + if (chan_id >= s->num_chnls) { + qemu_log_mask(LOG_GUEST_ERROR, "pl330: bad read offset " + TARGET_FMT_plx "\n", offset); + return 0; + } + return s->chan[chan_id].fault_type; + } + switch (offset) { + case PL330_REG_DSR: + return (s->manager.ns << 9) | (s->manager.wakeup << 4) | + (s->manager.state & 0xf); + case PL330_REG_DPC: + return s->manager.pc; + case PL330_REG_INTEN: + return s->inten; + case PL330_REG_INT_EVENT_RIS: + return s->ev_status; + case PL330_REG_INTMIS: + return s->int_status; + case PL330_REG_INTCLR: + /* Documentation says that we can't read this register + * but linux kernel does it + */ + return 0; + case PL330_REG_FSRD: + return s->manager.state ? 1 : 0; + case PL330_REG_FSRC: + res = 0; + for (i = 0; i < s->num_chnls; i++) { + if (s->chan[i].state == pl330_chan_fault || + s->chan[i].state == pl330_chan_fault_completing) { + res |= 1 << i; + } + } + return res; + case PL330_REG_FTRD: + return s->manager.fault_type; + case PL330_REG_DBGSTATUS: + return s->debug_status; + default: + qemu_log_mask(LOG_GUEST_ERROR, "pl330: bad read offset " + TARGET_FMT_plx "\n", offset); + } + return 0; +} + +static uint64_t pl330_iomem_read(void *opaque, hwaddr offset, + unsigned size) +{ + int ret = pl330_iomem_read_imp(opaque, offset); + DB_PRINT("addr: %08x data: %08x\n", (unsigned)offset, ret); + return ret; +} + +static const MemoryRegionOps pl330_ops = { + .read = pl330_iomem_read, + .write = pl330_iomem_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + } +}; + +/* Controller logic and initialization */ + +static void pl330_chan_reset(PL330Chan *ch) +{ + ch->src = 0; + ch->dst = 0; + ch->pc = 0; + ch->state = pl330_chan_stopped; + ch->watchdog_timer = 0; + ch->stall = 0; + ch->control = 0; + ch->status = 0; + ch->fault_type = 0; +} + +static void pl330_reset(DeviceState *d) +{ + int i; + PL330State *s = PL330(d); + + s->inten = 0; + s->int_status = 0; + s->ev_status = 0; + s->debug_status = 0; + s->num_faulting = 0; + s->manager.ns = s->mgr_ns_at_rst; + pl330_fifo_reset(&s->fifo); + pl330_queue_reset(&s->read_queue); + pl330_queue_reset(&s->write_queue); + + for (i = 0; i < s->num_chnls; i++) { + pl330_chan_reset(&s->chan[i]); + } + for (i = 0; i < s->num_periph_req; i++) { + s->periph_busy[i] = 0; + } + + qemu_del_timer(s->timer); +} + +static void pl330_realize(DeviceState *dev, Error **errp) +{ + int i; + PL330State *s = PL330(dev); + + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq_abort); + memory_region_init_io(&s->iomem, &pl330_ops, s, "dma", PL330_IOMEM_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); + + s->timer = qemu_new_timer_ns(vm_clock, pl330_exec_cycle_timer, s); + + s->cfg[0] = (s->mgr_ns_at_rst ? 0x4 : 0) | + (s->num_periph_req > 0 ? 1 : 0) | + ((s->num_chnls - 1) & 0x7) << 4 | + ((s->num_periph_req - 1) & 0x1f) << 12 | + ((s->num_events - 1) & 0x1f) << 17; + + switch (s->i_cache_len) { + case (4): + s->cfg[1] |= 2; + break; + case (8): + s->cfg[1] |= 3; + break; + case (16): + s->cfg[1] |= 4; + break; + case (32): + s->cfg[1] |= 5; + break; + default: + error_setg(errp, "Bad value for i-cache_len property: %d\n", + s->i_cache_len); + return; + } + s->cfg[1] |= ((s->num_i_cache_lines - 1) & 0xf) << 4; + + s->chan = g_new0(PL330Chan, s->num_chnls); + s->hi_seqn = g_new0(uint8_t, s->num_chnls); + s->lo_seqn = g_new0(uint8_t, s->num_chnls); + for (i = 0; i < s->num_chnls; i++) { + s->chan[i].parent = s; + s->chan[i].tag = (uint8_t)i; + } + s->manager.parent = s; + s->manager.tag = s->num_chnls; + s->manager.is_manager = true; + + s->irq = g_new0(qemu_irq, s->num_events); + for (i = 0; i < s->num_events; i++) { + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq[i]); + } + + qdev_init_gpio_in(dev, pl330_dma_stop_irq, PL330_PERIPH_NUM); + + switch (s->data_width) { + case (32): + s->cfg[CFG_CRD] |= 0x2; + break; + case (64): + s->cfg[CFG_CRD] |= 0x3; + break; + case (128): + s->cfg[CFG_CRD] |= 0x4; + break; + default: + error_setg(errp, "Bad value for data_width property: %d\n", + s->data_width); + return; + } + + s->cfg[CFG_CRD] |= ((s->wr_cap - 1) & 0x7) << 4 | + ((s->wr_q_dep - 1) & 0xf) << 8 | + ((s->rd_cap - 1) & 0x7) << 12 | + ((s->rd_q_dep - 1) & 0xf) << 16 | + ((s->data_buffer_dep - 1) & 0x1ff) << 20; + + pl330_queue_init(&s->read_queue, s->rd_q_dep, s); + pl330_queue_init(&s->write_queue, s->wr_q_dep, s); + pl330_fifo_init(&s->fifo, s->data_buffer_dep); +} + +static Property pl330_properties[] = { + /* CR0 */ + DEFINE_PROP_UINT32("num_chnls", PL330State, num_chnls, 8), + DEFINE_PROP_UINT8("num_periph_req", PL330State, num_periph_req, 4), + DEFINE_PROP_UINT8("num_events", PL330State, num_events, 16), + DEFINE_PROP_UINT8("mgr_ns_at_rst", PL330State, mgr_ns_at_rst, 0), + /* CR1 */ + DEFINE_PROP_UINT8("i-cache_len", PL330State, i_cache_len, 4), + DEFINE_PROP_UINT8("num_i-cache_lines", PL330State, num_i_cache_lines, 8), + /* CR2-4 */ + DEFINE_PROP_UINT32("boot_addr", PL330State, cfg[CFG_BOOT_ADDR], 0), + DEFINE_PROP_UINT32("INS", PL330State, cfg[CFG_INS], 0), + DEFINE_PROP_UINT32("PNS", PL330State, cfg[CFG_PNS], 0), + /* CRD */ + DEFINE_PROP_UINT8("data_width", PL330State, data_width, 64), + DEFINE_PROP_UINT8("wr_cap", PL330State, wr_cap, 8), + DEFINE_PROP_UINT8("wr_q_dep", PL330State, wr_q_dep, 16), + DEFINE_PROP_UINT8("rd_cap", PL330State, rd_cap, 8), + DEFINE_PROP_UINT8("rd_q_dep", PL330State, rd_q_dep, 16), + DEFINE_PROP_UINT16("data_buffer_dep", PL330State, data_buffer_dep, 256), + + DEFINE_PROP_END_OF_LIST(), +}; + +static void pl330_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = pl330_realize; + dc->reset = pl330_reset; + dc->props = pl330_properties; + dc->vmsd = &vmstate_pl330; +} + +static const TypeInfo pl330_type_info = { + .name = TYPE_PL330, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PL330State), + .class_init = pl330_class_init, +}; + +static void pl330_register_types(void) +{ + type_register_static(&pl330_type_info); +} + +type_init(pl330_register_types) diff --git a/hw/qdev-core.h b/hw/qdev-core.h index 2486f36853..547fbc7e0d 100644 --- a/hw/qdev-core.h +++ b/hw/qdev-core.h @@ -175,6 +175,9 @@ struct Property { uint8_t bitnr; uint8_t qtype; int64_t defval; + int arrayoffset; + PropertyInfo *arrayinfo; + int arrayfieldsize; }; struct PropertyInfo { diff --git a/hw/qdev-properties.c b/hw/qdev-properties.c index 0307a7830b..247ca6c5b4 100644 --- a/hw/qdev-properties.c +++ b/hw/qdev-properties.c @@ -779,6 +779,110 @@ PropertyInfo qdev_prop_pci_host_devaddr = { .set = set_pci_host_devaddr, }; +/* --- support for array properties --- */ + +/* Used as an opaque for the object properties we add for each + * array element. Note that the struct Property must be first + * in the struct so that a pointer to this works as the opaque + * for the underlying element's property hooks as well as for + * our own release callback. + */ +typedef struct { + struct Property prop; + char *propname; + ObjectPropertyRelease *release; +} ArrayElementProperty; + +/* object property release callback for array element properties: + * we call the underlying element's property release hook, and + * then free the memory we allocated when we added the property. + */ +static void array_element_release(Object *obj, const char *name, void *opaque) +{ + ArrayElementProperty *p = opaque; + if (p->release) { + p->release(obj, name, opaque); + } + g_free(p->propname); + g_free(p); +} + +static void set_prop_arraylen(Object *obj, Visitor *v, void *opaque, + const char *name, Error **errp) +{ + /* Setter for the property which defines the length of a + * variable-sized property array. As well as actually setting the + * array-length field in the device struct, we have to create the + * array itself and dynamically add the corresponding properties. + */ + DeviceState *dev = DEVICE(obj); + Property *prop = opaque; + uint32_t *alenptr = qdev_get_prop_ptr(dev, prop); + void **arrayptr = (void *)dev + prop->arrayoffset; + void *eltptr; + const char *arrayname; + int i; + + if (dev->realized) { + error_set(errp, QERR_PERMISSION_DENIED); + return; + } + if (*alenptr) { + error_setg(errp, "array size property %s may not be set more than once", + name); + return; + } + visit_type_uint32(v, alenptr, name, errp); + if (error_is_set(errp)) { + return; + } + if (!*alenptr) { + return; + } + + /* DEFINE_PROP_ARRAY guarantees that name should start with this prefix; + * strip it off so we can get the name of the array itself. + */ + assert(strncmp(name, PROP_ARRAY_LEN_PREFIX, + strlen(PROP_ARRAY_LEN_PREFIX)) == 0); + arrayname = name + strlen(PROP_ARRAY_LEN_PREFIX); + + /* Note that it is the responsibility of the individual device's deinit + * to free the array proper. + */ + *arrayptr = eltptr = g_malloc0(*alenptr * prop->arrayfieldsize); + for (i = 0; i < *alenptr; i++, eltptr += prop->arrayfieldsize) { + char *propname = g_strdup_printf("%s[%d]", arrayname, i); + ArrayElementProperty *arrayprop = g_new0(ArrayElementProperty, 1); + arrayprop->release = prop->arrayinfo->release; + arrayprop->propname = propname; + arrayprop->prop.info = prop->arrayinfo; + arrayprop->prop.name = propname; + /* This ugly piece of pointer arithmetic sets up the offset so + * that when the underlying get/set hooks call qdev_get_prop_ptr + * they get the right answer despite the array element not actually + * being inside the device struct. + */ + arrayprop->prop.offset = eltptr - (void *)dev; + assert(qdev_get_prop_ptr(dev, &arrayprop->prop) == eltptr); + object_property_add(obj, propname, + arrayprop->prop.info->name, + arrayprop->prop.info->get, + arrayprop->prop.info->set, + array_element_release, + arrayprop, errp); + if (error_is_set(errp)) { + return; + } + } +} + +PropertyInfo qdev_prop_arraylen = { + .name = "uint32", + .get = get_uint32, + .set = set_prop_arraylen, +}; + /* --- public helpers --- */ static Property *qdev_prop_walk(Property *props, const char *name) diff --git a/hw/qdev-properties.h b/hw/qdev-properties.h index 0b0465c9b3..c9bb246841 100644 --- a/hw/qdev-properties.h +++ b/hw/qdev-properties.h @@ -26,6 +26,7 @@ extern PropertyInfo qdev_prop_vlan; extern PropertyInfo qdev_prop_pci_devfn; extern PropertyInfo qdev_prop_blocksize; extern PropertyInfo qdev_prop_pci_host_devaddr; +extern PropertyInfo qdev_prop_arraylen; #define DEFINE_PROP(_name, _state, _field, _prop, _type) { \ .name = (_name), \ @@ -51,6 +52,44 @@ extern PropertyInfo qdev_prop_pci_host_devaddr; .defval = (bool)_defval, \ } +#define PROP_ARRAY_LEN_PREFIX "len-" + +/** + * DEFINE_PROP_ARRAY: + * @_name: name of the array + * @_state: name of the device state structure type + * @_field: uint32_t field in @_state to hold the array length + * @_arrayfield: field in @_state (of type '@_arraytype *') which + * will point to the array + * @_arrayprop: PropertyInfo defining what property the array elements have + * @_arraytype: C type of the array elements + * + * Define device properties for a variable-length array _name. A + * static property "len-arrayname" is defined. When the device creator + * sets this property to the desired length of array, further dynamic + * properties "arrayname[0]", "arrayname[1]", ... are defined so the + * device creator can set the array element values. Setting the + * "len-arrayname" property more than once is an error. + * + * When the array length is set, the @_field member of the device + * struct is set to the array length, and @_arrayfield is set to point + * to (zero-initialised) memory allocated for the array. For a zero + * length array, @_field will be set to 0 and @_arrayfield to NULL. + * It is the responsibility of the device deinit code to free the + * @_arrayfield memory. + */ +#define DEFINE_PROP_ARRAY(_name, _state, _field, \ + _arrayfield, _arrayprop, _arraytype) { \ + .name = (PROP_ARRAY_LEN_PREFIX _name), \ + .info = &(qdev_prop_arraylen), \ + .offset = offsetof(_state, _field) \ + + type_check(uint32_t, typeof_field(_state, _field)), \ + .qtype = QTYPE_QINT, \ + .arrayinfo = &(_arrayprop), \ + .arrayfieldsize = sizeof(_arraytype), \ + .arrayoffset = offsetof(_state, _arrayfield), \ + } + #define DEFINE_PROP_UINT8(_n, _s, _f, _d) \ DEFINE_PROP_DEFAULT(_n, _s, _f, _d, qdev_prop_uint8, uint8_t) #define DEFINE_PROP_UINT16(_n, _s, _f, _d) \ diff --git a/hw/xilinx_spips.c b/hw/xilinx_spips.c index 6c21b9668b..b2397f4a42 100644 --- a/hw/xilinx_spips.c +++ b/hw/xilinx_spips.c @@ -115,6 +115,19 @@ #define SNOOP_NONE 0xFE #define SNOOP_STRIPING 0 +typedef enum { + READ = 0x3, + FAST_READ = 0xb, + DOR = 0x3b, + QOR = 0x6b, + DIOR = 0xbb, + QIOR = 0xeb, + + PP = 0x2, + DPP = 0xa2, + QPP = 0x32, +} FlashCMD; + typedef struct { SysBusDevice busdev; MemoryRegion iomem; @@ -141,10 +154,15 @@ typedef struct { hwaddr lqspi_cached_addr; } XilinxSPIPS; +#define TYPE_XILINX_SPIPS "xilinx,spips" + +#define XILINX_SPIPS(obj) \ + OBJECT_CHECK(XilinxSPIPS, (obj), TYPE_XILINX_SPIPS) + static inline int num_effective_busses(XilinxSPIPS *s) { - return (s->regs[R_LQSPI_STS] & LQSPI_CFG_SEP_BUS && - s->regs[R_LQSPI_STS] & LQSPI_CFG_TWO_MEM) ? s->num_busses : 1; + return (s->regs[R_LQSPI_CFG] & LQSPI_CFG_SEP_BUS && + s->regs[R_LQSPI_CFG] & LQSPI_CFG_TWO_MEM) ? s->num_busses : 1; } static void xilinx_spips_update_cs_lines(XilinxSPIPS *s) @@ -197,7 +215,7 @@ static void xilinx_spips_update_ixr(XilinxSPIPS *s) static void xilinx_spips_reset(DeviceState *d) { - XilinxSPIPS *s = DO_UPCAST(XilinxSPIPS, busdev.qdev, d); + XilinxSPIPS *s = XILINX_SPIPS(d); int i; for (i = 0; i < R_MAX; i++) { @@ -251,15 +269,19 @@ static void xilinx_spips_flush_txfifo(XilinxSPIPS *s) switch (s->snoop_state) { case (SNOOP_CHECKING): switch (tx) { /* new instruction code */ - case 0x0b: /* dual/quad output read DOR/QOR */ - case 0x6b: - s->snoop_state = 4; + case READ: /* 3 address bytes, no dummy bytes/cycles */ + case PP: + case DPP: + case QPP: + s->snoop_state = 3; break; - /* FIXME: these vary between vendor - set to spansion */ - case 0xbb: /* high performance dual read DIOR */ + case FAST_READ: /* 3 address bytes, 1 dummy byte */ + case DOR: + case QOR: + case DIOR: /* FIXME: these vary between vendor - set to spansion */ s->snoop_state = 4; break; - case 0xeb: /* high performance quad read QIOR */ + case QIOR: /* 3 address bytes, 2 dummy bytes */ s->snoop_state = 6; break; default: @@ -483,9 +505,10 @@ static const MemoryRegionOps lqspi_ops = { } }; -static int xilinx_spips_init(SysBusDevice *dev) +static void xilinx_spips_realize(DeviceState *dev, Error **errp) { - XilinxSPIPS *s = FROM_SYSBUS(typeof(*s), dev); + XilinxSPIPS *s = XILINX_SPIPS(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); int i; DB_PRINT("inited device model\n"); @@ -494,31 +517,29 @@ static int xilinx_spips_init(SysBusDevice *dev) for (i = 0; i < s->num_busses; ++i) { char bus_name[16]; snprintf(bus_name, 16, "spi%d", i); - s->spi[i] = ssi_create_bus(&dev->qdev, bus_name); + s->spi[i] = ssi_create_bus(dev, bus_name); } - s->cs_lines = g_new(qemu_irq, s->num_cs * s->num_busses); + s->cs_lines = g_new0(qemu_irq, s->num_cs * s->num_busses); ssi_auto_connect_slaves(DEVICE(s), s->cs_lines, s->spi[0]); ssi_auto_connect_slaves(DEVICE(s), s->cs_lines, s->spi[1]); - sysbus_init_irq(dev, &s->irq); + sysbus_init_irq(sbd, &s->irq); for (i = 0; i < s->num_cs * s->num_busses; ++i) { - sysbus_init_irq(dev, &s->cs_lines[i]); + sysbus_init_irq(sbd, &s->cs_lines[i]); } memory_region_init_io(&s->iomem, &spips_ops, s, "spi", R_MAX*4); - sysbus_init_mmio(dev, &s->iomem); + sysbus_init_mmio(sbd, &s->iomem); memory_region_init_io(&s->mmlqspi, &lqspi_ops, s, "lqspi", (1 << LQSPI_ADDRESS_BITS) * 2); - sysbus_init_mmio(dev, &s->mmlqspi); + sysbus_init_mmio(sbd, &s->mmlqspi); s->irqline = -1; s->lqspi_cached_addr = ~0ULL; fifo8_create(&s->rx_fifo, RXFF_A); fifo8_create(&s->tx_fifo, TXFF_A); - - return 0; } static int xilinx_spips_post_load(void *opaque, int version_id) @@ -552,16 +573,15 @@ static Property xilinx_spips_properties[] = { static void xilinx_spips_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); - SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); - sdc->init = xilinx_spips_init; + dc->realize = xilinx_spips_realize; dc->reset = xilinx_spips_reset; dc->props = xilinx_spips_properties; dc->vmsd = &vmstate_xilinx_spips; } static const TypeInfo xilinx_spips_info = { - .name = "xilinx,spips", + .name = TYPE_XILINX_SPIPS, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(XilinxSPIPS), .class_init = xilinx_spips_class_init, diff --git a/include/qemu-common.h b/include/qemu-common.h index 5e137084b5..7754ee2fcc 100644 --- a/include/qemu-common.h +++ b/include/qemu-common.h @@ -442,4 +442,10 @@ int64_t pow2floor(int64_t value); int uleb128_encode_small(uint8_t *out, uint32_t n); int uleb128_decode_small(const uint8_t *in, uint32_t *n); +/* + * Hexdump a buffer to a file. An optional string prefix is added to every line + */ + +void hexdump(const char *buf, FILE *fp, const char *prefix, size_t size); + #endif diff --git a/util/Makefile.objs b/util/Makefile.objs index cad5ce87db..557bda7a15 100644 --- a/util/Makefile.objs +++ b/util/Makefile.objs @@ -9,3 +9,4 @@ util-obj-y += error.o qemu-error.o util-obj-$(CONFIG_POSIX) += compatfd.o util-obj-y += iov.o aes.o qemu-config.o qemu-sockets.o uri.o notify.o util-obj-y += qemu-option.o qemu-progress.o +util-obj-y += hexdump.o diff --git a/util/hexdump.c b/util/hexdump.c new file mode 100644 index 0000000000..0d0efc86de --- /dev/null +++ b/util/hexdump.c @@ -0,0 +1,37 @@ +/* + * Helper to hexdump a buffer + * + * Copyright (c) 2013 Red Hat, Inc. + * Copyright (c) 2013 Gerd Hoffmann <kraxel@redhat.com> + * Copyright (c) 2013 Peter Crosthwaite <peter.crosthwaite@xilinx.com> + * Copyright (c) 2013 Xilinx, Inc + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "qemu-common.h" + +void hexdump(const char *buf, FILE *fp, const char *prefix, size_t size) +{ + unsigned int b; + + for (b = 0; b < size; b++) { + if ((b % 16) == 0) { + fprintf(fp, "%s: %04x:", prefix, b); + } + if ((b % 4) == 0) { + fprintf(fp, " "); + } + fprintf(fp, " %02x", (unsigned char)buf[b]); + if ((b % 16) == 15) { + fprintf(fp, "\n"); + } + } + if ((b % 16) != 0) { + fprintf(fp, "\n"); + } +} diff --git a/util/iov.c b/util/iov.c index fbe675d373..9dae318197 100644 --- a/util/iov.c +++ b/util/iov.c @@ -201,32 +201,18 @@ ssize_t iov_send_recv(int sockfd, struct iovec *iov, unsigned iov_cnt, void iov_hexdump(const struct iovec *iov, const unsigned int iov_cnt, FILE *fp, const char *prefix, size_t limit) { - unsigned int i, v, b; - uint8_t *c; - - c = iov[0].iov_base; - for (i = 0, v = 0, b = 0; b < limit; i++, b++) { - if (i == iov[v].iov_len) { - i = 0; v++; - if (v == iov_cnt) { - break; - } - c = iov[v].iov_base; - } - if ((b % 16) == 0) { - fprintf(fp, "%s: %04x:", prefix, b); - } - if ((b % 4) == 0) { - fprintf(fp, " "); - } - fprintf(fp, " %02x", c[i]); - if ((b % 16) == 15) { - fprintf(fp, "\n"); - } - } - if ((b % 16) != 0) { - fprintf(fp, "\n"); + int v; + size_t size = 0; + char *buf; + + for (v = 0; v < iov_cnt; v++) { + size += iov[v].iov_len; } + size = size > limit ? limit : size; + buf = g_malloc(size); + iov_to_buf(iov, iov_cnt, 0, buf, size); + hexdump(buf, fp, prefix, size); + g_free(buf); } unsigned iov_copy(struct iovec *dst_iov, unsigned int dst_iov_cnt, |