diff options
Diffstat (limited to 'hw/dma/sparc32_dma.c')
-rw-r--r-- | hw/dma/sparc32_dma.c | 449 |
1 files changed, 449 insertions, 0 deletions
diff --git a/hw/dma/sparc32_dma.c b/hw/dma/sparc32_dma.c new file mode 100644 index 000000000..03bc50087 --- /dev/null +++ b/hw/dma/sparc32_dma.c @@ -0,0 +1,449 @@ +/* + * QEMU Sparc32 DMA controller emulation + * + * Copyright (c) 2006 Fabrice Bellard + * + * Modifications: + * 2010-Feb-14 Artyom Tarasenko : reworked irq generation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "hw/irq.h" +#include "hw/qdev-properties.h" +#include "hw/sparc/sparc32_dma.h" +#include "hw/sparc/sun4m_iommu.h" +#include "hw/sysbus.h" +#include "migration/vmstate.h" +#include "sysemu/dma.h" +#include "qapi/error.h" +#include "qemu/module.h" +#include "trace.h" + +/* + * This is the DMA controller part of chip STP2000 (Master I/O), also + * produced as NCR89C100. See + * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C100.txt + * and + * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/DMA2.txt + */ + +#define DMA_SIZE (4 * sizeof(uint32_t)) +/* We need the mask, because one instance of the device is not page + aligned (ledma, start address 0x0010) */ +#define DMA_MASK (DMA_SIZE - 1) +/* OBP says 0x20 bytes for ledma, the extras are aliased to espdma */ +#define DMA_ETH_SIZE (8 * sizeof(uint32_t)) +#define DMA_MAX_REG_OFFSET (2 * DMA_SIZE - 1) + +#define DMA_VER 0xa0000000 +#define DMA_INTR 1 +#define DMA_INTREN 0x10 +#define DMA_WRITE_MEM 0x100 +#define DMA_EN 0x200 +#define DMA_LOADED 0x04000000 +#define DMA_DRAIN_FIFO 0x40 +#define DMA_RESET 0x80 + +/* XXX SCSI and ethernet should have different read-only bit masks */ +#define DMA_CSR_RO_MASK 0xfe000007 + +enum { + GPIO_RESET = 0, + GPIO_DMA, +}; + +/* Note: on sparc, the lance 16 bit bus is swapped */ +void ledma_memory_read(void *opaque, hwaddr addr, + uint8_t *buf, int len, int do_bswap) +{ + DMADeviceState *s = opaque; + IOMMUState *is = (IOMMUState *)s->iommu; + int i; + + addr |= s->dmaregs[3]; + trace_ledma_memory_read(addr, len); + if (do_bswap) { + dma_memory_read(&is->iommu_as, addr, buf, len); + } else { + addr &= ~1; + len &= ~1; + dma_memory_read(&is->iommu_as, addr, buf, len); + for(i = 0; i < len; i += 2) { + bswap16s((uint16_t *)(buf + i)); + } + } +} + +void ledma_memory_write(void *opaque, hwaddr addr, + uint8_t *buf, int len, int do_bswap) +{ + DMADeviceState *s = opaque; + IOMMUState *is = (IOMMUState *)s->iommu; + int l, i; + uint16_t tmp_buf[32]; + + addr |= s->dmaregs[3]; + trace_ledma_memory_write(addr, len); + if (do_bswap) { + dma_memory_write(&is->iommu_as, addr, buf, len); + } else { + addr &= ~1; + len &= ~1; + while (len > 0) { + l = len; + if (l > sizeof(tmp_buf)) + l = sizeof(tmp_buf); + for(i = 0; i < l; i += 2) { + tmp_buf[i >> 1] = bswap16(*(uint16_t *)(buf + i)); + } + dma_memory_write(&is->iommu_as, addr, tmp_buf, l); + len -= l; + buf += l; + addr += l; + } + } +} + +static void dma_set_irq(void *opaque, int irq, int level) +{ + DMADeviceState *s = opaque; + if (level) { + s->dmaregs[0] |= DMA_INTR; + if (s->dmaregs[0] & DMA_INTREN) { + trace_sparc32_dma_set_irq_raise(); + qemu_irq_raise(s->irq); + } + } else { + if (s->dmaregs[0] & DMA_INTR) { + s->dmaregs[0] &= ~DMA_INTR; + if (s->dmaregs[0] & DMA_INTREN) { + trace_sparc32_dma_set_irq_lower(); + qemu_irq_lower(s->irq); + } + } + } +} + +void espdma_memory_read(void *opaque, uint8_t *buf, int len) +{ + DMADeviceState *s = opaque; + IOMMUState *is = (IOMMUState *)s->iommu; + + trace_espdma_memory_read(s->dmaregs[1], len); + dma_memory_read(&is->iommu_as, s->dmaregs[1], buf, len); + s->dmaregs[1] += len; +} + +void espdma_memory_write(void *opaque, uint8_t *buf, int len) +{ + DMADeviceState *s = opaque; + IOMMUState *is = (IOMMUState *)s->iommu; + + trace_espdma_memory_write(s->dmaregs[1], len); + dma_memory_write(&is->iommu_as, s->dmaregs[1], buf, len); + s->dmaregs[1] += len; +} + +static uint64_t dma_mem_read(void *opaque, hwaddr addr, + unsigned size) +{ + DMADeviceState *s = opaque; + uint32_t saddr; + + saddr = (addr & DMA_MASK) >> 2; + trace_sparc32_dma_mem_readl(addr, s->dmaregs[saddr]); + return s->dmaregs[saddr]; +} + +static void dma_mem_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + DMADeviceState *s = opaque; + uint32_t saddr; + + saddr = (addr & DMA_MASK) >> 2; + trace_sparc32_dma_mem_writel(addr, s->dmaregs[saddr], val); + switch (saddr) { + case 0: + if (val & DMA_INTREN) { + if (s->dmaregs[0] & DMA_INTR) { + trace_sparc32_dma_set_irq_raise(); + qemu_irq_raise(s->irq); + } + } else { + if (s->dmaregs[0] & (DMA_INTR | DMA_INTREN)) { + trace_sparc32_dma_set_irq_lower(); + qemu_irq_lower(s->irq); + } + } + if (val & DMA_RESET) { + qemu_irq_raise(s->gpio[GPIO_RESET]); + qemu_irq_lower(s->gpio[GPIO_RESET]); + } else if (val & DMA_DRAIN_FIFO) { + val &= ~DMA_DRAIN_FIFO; + } else if (val == 0) + val = DMA_DRAIN_FIFO; + + if (val & DMA_EN && !(s->dmaregs[0] & DMA_EN)) { + trace_sparc32_dma_enable_raise(); + qemu_irq_raise(s->gpio[GPIO_DMA]); + } else if (!(val & DMA_EN) && !!(s->dmaregs[0] & DMA_EN)) { + trace_sparc32_dma_enable_lower(); + qemu_irq_lower(s->gpio[GPIO_DMA]); + } + + val &= ~DMA_CSR_RO_MASK; + val |= DMA_VER; + s->dmaregs[0] = (s->dmaregs[0] & DMA_CSR_RO_MASK) | val; + break; + case 1: + s->dmaregs[0] |= DMA_LOADED; + /* fall through */ + default: + s->dmaregs[saddr] = val; + break; + } +} + +static const MemoryRegionOps dma_mem_ops = { + .read = dma_mem_read, + .write = dma_mem_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void sparc32_dma_device_reset(DeviceState *d) +{ + DMADeviceState *s = SPARC32_DMA_DEVICE(d); + + memset(s->dmaregs, 0, DMA_SIZE); + s->dmaregs[0] = DMA_VER; +} + +static const VMStateDescription vmstate_sparc32_dma_device = { + .name ="sparc32_dma", + .version_id = 2, + .minimum_version_id = 2, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(dmaregs, DMADeviceState, DMA_REGS), + VMSTATE_END_OF_LIST() + } +}; + +static void sparc32_dma_device_init(Object *obj) +{ + DeviceState *dev = DEVICE(obj); + DMADeviceState *s = SPARC32_DMA_DEVICE(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + sysbus_init_irq(sbd, &s->irq); + + sysbus_init_mmio(sbd, &s->iomem); + + object_property_add_link(OBJECT(dev), "iommu", TYPE_SUN4M_IOMMU, + (Object **) &s->iommu, + qdev_prop_allow_set_link_before_realize, + 0); + + qdev_init_gpio_in(dev, dma_set_irq, 1); + qdev_init_gpio_out(dev, s->gpio, 2); +} + +static void sparc32_dma_device_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = sparc32_dma_device_reset; + dc->vmsd = &vmstate_sparc32_dma_device; +} + +static const TypeInfo sparc32_dma_device_info = { + .name = TYPE_SPARC32_DMA_DEVICE, + .parent = TYPE_SYS_BUS_DEVICE, + .abstract = true, + .instance_size = sizeof(DMADeviceState), + .instance_init = sparc32_dma_device_init, + .class_init = sparc32_dma_device_class_init, +}; + +static void sparc32_espdma_device_init(Object *obj) +{ + DMADeviceState *s = SPARC32_DMA_DEVICE(obj); + ESPDMADeviceState *es = SPARC32_ESPDMA_DEVICE(obj); + + memory_region_init_io(&s->iomem, OBJECT(s), &dma_mem_ops, s, + "espdma-mmio", DMA_SIZE); + + object_initialize_child(obj, "esp", &es->esp, TYPE_SYSBUS_ESP); +} + +static void sparc32_espdma_device_realize(DeviceState *dev, Error **errp) +{ + ESPDMADeviceState *es = SPARC32_ESPDMA_DEVICE(dev); + SysBusESPState *sysbus = SYSBUS_ESP(&es->esp); + ESPState *esp = &sysbus->esp; + + esp->dma_memory_read = espdma_memory_read; + esp->dma_memory_write = espdma_memory_write; + esp->dma_opaque = SPARC32_DMA_DEVICE(dev); + sysbus->it_shift = 2; + esp->dma_enabled = 1; + sysbus_realize(SYS_BUS_DEVICE(sysbus), &error_fatal); +} + +static void sparc32_espdma_device_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = sparc32_espdma_device_realize; +} + +static const TypeInfo sparc32_espdma_device_info = { + .name = TYPE_SPARC32_ESPDMA_DEVICE, + .parent = TYPE_SPARC32_DMA_DEVICE, + .instance_size = sizeof(ESPDMADeviceState), + .instance_init = sparc32_espdma_device_init, + .class_init = sparc32_espdma_device_class_init, +}; + +static void sparc32_ledma_device_init(Object *obj) +{ + DMADeviceState *s = SPARC32_DMA_DEVICE(obj); + LEDMADeviceState *ls = SPARC32_LEDMA_DEVICE(obj); + + memory_region_init_io(&s->iomem, OBJECT(s), &dma_mem_ops, s, + "ledma-mmio", DMA_SIZE); + + object_initialize_child(obj, "lance", &ls->lance, TYPE_LANCE); +} + +static void sparc32_ledma_device_realize(DeviceState *dev, Error **errp) +{ + LEDMADeviceState *s = SPARC32_LEDMA_DEVICE(dev); + SysBusPCNetState *lance = SYSBUS_PCNET(&s->lance); + + object_property_set_link(OBJECT(lance), "dma", OBJECT(dev), &error_abort); + sysbus_realize(SYS_BUS_DEVICE(lance), &error_fatal); +} + +static void sparc32_ledma_device_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = sparc32_ledma_device_realize; +} + +static const TypeInfo sparc32_ledma_device_info = { + .name = TYPE_SPARC32_LEDMA_DEVICE, + .parent = TYPE_SPARC32_DMA_DEVICE, + .instance_size = sizeof(LEDMADeviceState), + .instance_init = sparc32_ledma_device_init, + .class_init = sparc32_ledma_device_class_init, +}; + +static void sparc32_dma_realize(DeviceState *dev, Error **errp) +{ + SPARC32DMAState *s = SPARC32_DMA(dev); + DeviceState *espdma, *esp, *ledma, *lance; + SysBusDevice *sbd; + Object *iommu; + + iommu = object_resolve_path_type("", TYPE_SUN4M_IOMMU, NULL); + if (!iommu) { + error_setg(errp, "unable to locate sun4m IOMMU device"); + return; + } + + espdma = DEVICE(&s->espdma); + object_property_set_link(OBJECT(espdma), "iommu", iommu, &error_abort); + sysbus_realize(SYS_BUS_DEVICE(espdma), &error_fatal); + + esp = DEVICE(object_resolve_path_component(OBJECT(espdma), "esp")); + sbd = SYS_BUS_DEVICE(esp); + sysbus_connect_irq(sbd, 0, qdev_get_gpio_in(espdma, 0)); + qdev_connect_gpio_out(espdma, 0, qdev_get_gpio_in(esp, 0)); + qdev_connect_gpio_out(espdma, 1, qdev_get_gpio_in(esp, 1)); + + sbd = SYS_BUS_DEVICE(espdma); + memory_region_add_subregion(&s->dmamem, 0x0, + sysbus_mmio_get_region(sbd, 0)); + + ledma = DEVICE(&s->ledma); + object_property_set_link(OBJECT(ledma), "iommu", iommu, &error_abort); + sysbus_realize(SYS_BUS_DEVICE(ledma), &error_fatal); + + lance = DEVICE(object_resolve_path_component(OBJECT(ledma), "lance")); + sbd = SYS_BUS_DEVICE(lance); + sysbus_connect_irq(sbd, 0, qdev_get_gpio_in(ledma, 0)); + qdev_connect_gpio_out(ledma, 0, qdev_get_gpio_in(lance, 0)); + + sbd = SYS_BUS_DEVICE(ledma); + memory_region_add_subregion(&s->dmamem, 0x10, + sysbus_mmio_get_region(sbd, 0)); + + /* Add ledma alias to handle SunOS 5.7 - Solaris 9 invalid access bug */ + memory_region_init_alias(&s->ledma_alias, OBJECT(dev), "ledma-alias", + sysbus_mmio_get_region(sbd, 0), 0x4, 0x4); + memory_region_add_subregion(&s->dmamem, 0x20, &s->ledma_alias); +} + +static void sparc32_dma_init(Object *obj) +{ + SPARC32DMAState *s = SPARC32_DMA(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + memory_region_init(&s->dmamem, OBJECT(s), "dma", DMA_SIZE + DMA_ETH_SIZE); + sysbus_init_mmio(sbd, &s->dmamem); + + object_initialize_child(obj, "espdma", &s->espdma, + TYPE_SPARC32_ESPDMA_DEVICE); + object_initialize_child(obj, "ledma", &s->ledma, + TYPE_SPARC32_LEDMA_DEVICE); +} + +static void sparc32_dma_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = sparc32_dma_realize; +} + +static const TypeInfo sparc32_dma_info = { + .name = TYPE_SPARC32_DMA, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SPARC32DMAState), + .instance_init = sparc32_dma_init, + .class_init = sparc32_dma_class_init, +}; + + +static void sparc32_dma_register_types(void) +{ + type_register_static(&sparc32_dma_device_info); + type_register_static(&sparc32_espdma_device_info); + type_register_static(&sparc32_ledma_device_info); + type_register_static(&sparc32_dma_info); +} + +type_init(sparc32_dma_register_types) |