diff options
Diffstat (limited to 'hw')
| -rw-r--r-- | hw/esp.c | 574 | ||||
| -rw-r--r-- | hw/imx_avic.c | 2 | ||||
| -rw-r--r-- | hw/megasas.c | 2 | ||||
| -rw-r--r-- | hw/omap.h | 8 | ||||
| -rw-r--r-- | hw/pci-stub.c | 15 | ||||
| -rw-r--r-- | hw/pci_ids.h | 1 | ||||
| -rw-r--r-- | hw/qdev-properties.c | 2 | ||||
| -rw-r--r-- | hw/qdev.h | 2 | ||||
| -rw-r--r-- | hw/scsi-bus.c | 5 | ||||
| -rw-r--r-- | hw/scsi.h | 1 | ||||
| -rw-r--r-- | hw/sh_serial.c | 6 | ||||
| -rw-r--r-- | hw/usb/Makefile.objs | 1 | ||||
| -rw-r--r-- | hw/usb/dev-uas.c | 779 | ||||
| -rw-r--r-- | hw/usb/hcd-ehci.c | 91 | ||||
| -rw-r--r-- | hw/usb/hcd-uhci.c | 12 | ||||
| -rw-r--r-- | hw/vga.c | 14 | ||||
| -rw-r--r-- | hw/vga_int.h | 2 |
17 files changed, 1387 insertions, 130 deletions
diff --git a/hw/esp.c b/hw/esp.c index 8d73e56886..c6422ad340 100644 --- a/hw/esp.c +++ b/hw/esp.c @@ -2,6 +2,7 @@ * QEMU ESP/NCR53C9x emulation * * Copyright (c) 2005-2006 Fabrice Bellard + * Copyright (c) 2012 Herve Poussineau * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,9 +24,11 @@ */ #include "sysbus.h" +#include "pci.h" #include "scsi.h" #include "esp.h" #include "trace.h" +#include "qemu-log.h" /* * On Sparc32, this is the ESP (NCR53C90) part of chip STP2000 (Master I/O), @@ -35,21 +38,16 @@ * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR53C9X.txt */ -#define ESP_ERROR(fmt, ...) \ - do { printf("ESP ERROR: %s: " fmt, __func__ , ## __VA_ARGS__); } while (0) - #define ESP_REGS 16 #define TI_BUFSZ 16 typedef struct ESPState ESPState; struct ESPState { - SysBusDevice busdev; - MemoryRegion iomem; uint8_t rregs[ESP_REGS]; uint8_t wregs[ESP_REGS]; qemu_irq irq; - uint32_t it_shift; + uint8_t chip_id; int32_t ti_size; uint32_t ti_rptr, ti_wptr; uint32_t status; @@ -113,10 +111,12 @@ struct ESPState { #define CMD_MSGACC 0x12 #define CMD_PAD 0x18 #define CMD_SATN 0x1a +#define CMD_RSTATN 0x1b #define CMD_SEL 0x41 #define CMD_SELATN 0x42 #define CMD_SELATNS 0x43 #define CMD_ENSEL 0x44 +#define CMD_DISSEL 0x45 #define STAT_DO 0x00 #define STAT_DI 0x01 @@ -144,6 +144,7 @@ struct ESPState { #define CFG1_RESREPT 0x40 #define TCHI_FAS100A 0x4 +#define TCHI_AM53C974 0x12 static void esp_raise_irq(ESPState *s) { @@ -163,11 +164,8 @@ static void esp_lower_irq(ESPState *s) } } -static void esp_dma_enable(void *opaque, int irq, int level) +static void esp_dma_enable(ESPState *s, int irq, int level) { - DeviceState *d = opaque; - ESPState *s = container_of(d, ESPState, busdev.qdev); - if (level) { s->dma_enabled = 1; trace_esp_dma_enable(); @@ -183,7 +181,7 @@ static void esp_dma_enable(void *opaque, int irq, int level) static void esp_request_cancelled(SCSIRequest *req) { - ESPState *s = DO_UPCAST(ESPState, busdev.qdev, req->bus->qbus.parent); + ESPState *s = req->hba_private; if (req == s->current_req) { scsi_req_unref(s->current_req); @@ -239,7 +237,7 @@ static void do_busid_cmd(ESPState *s, uint8_t *buf, uint8_t busid) trace_esp_do_busid_cmd(busid); lun = busid & 7; current_lun = scsi_device_find(&s->bus, 0, s->current_dev->id, lun); - s->current_req = scsi_req_new(current_lun, 0, lun, buf, NULL); + s->current_req = scsi_req_new(current_lun, 0, lun, buf, s); datalen = scsi_req_enqueue(s->current_req); s->ti_size = datalen; if (datalen != 0) { @@ -270,7 +268,7 @@ static void handle_satn(ESPState *s) uint8_t buf[32]; int len; - if (!s->dma_enabled) { + if (s->dma && !s->dma_enabled) { s->dma_cb = handle_satn; return; } @@ -284,7 +282,7 @@ static void handle_s_without_atn(ESPState *s) uint8_t buf[32]; int len; - if (!s->dma_enabled) { + if (s->dma && !s->dma_enabled) { s->dma_cb = handle_s_without_atn; return; } @@ -296,7 +294,7 @@ static void handle_s_without_atn(ESPState *s) static void handle_satn_stop(ESPState *s) { - if (!s->dma_enabled) { + if (s->dma && !s->dma_enabled) { s->dma_cb = handle_satn_stop; return; } @@ -393,7 +391,7 @@ static void esp_do_dma(ESPState *s) static void esp_command_complete(SCSIRequest *req, uint32_t status, size_t resid) { - ESPState *s = DO_UPCAST(ESPState, busdev.qdev, req->bus->qbus.parent); + ESPState *s = req->hba_private; trace_esp_command_complete(); if (s->ti_size != 0) { @@ -417,7 +415,7 @@ static void esp_command_complete(SCSIRequest *req, uint32_t status, static void esp_transfer_data(SCSIRequest *req, uint32_t len) { - ESPState *s = DO_UPCAST(ESPState, busdev.qdev, req->bus->qbus.parent); + ESPState *s = req->hba_private; trace_esp_transfer_data(s->dma_left, s->ti_size); s->async_len = len; @@ -435,6 +433,11 @@ static void handle_ti(ESPState *s) { uint32_t dmalen, minlen; + if (s->dma && !s->dma_enabled) { + s->dma_cb = handle_ti; + return; + } + dmalen = s->rregs[ESP_TCLO] | (s->rregs[ESP_TCMID] << 8); if (dmalen==0) { dmalen=0x10000; @@ -462,13 +465,11 @@ static void handle_ti(ESPState *s) } } -static void esp_hard_reset(DeviceState *d) +static void esp_hard_reset(ESPState *s) { - ESPState *s = container_of(d, ESPState, busdev.qdev); - memset(s->rregs, 0, ESP_REGS); memset(s->wregs, 0, ESP_REGS); - s->rregs[ESP_TCHI] = TCHI_FAS100A; // Indicate fas100a + s->rregs[ESP_TCHI] = s->chip_id; s->ti_size = 0; s->ti_rptr = 0; s->ti_wptr = 0; @@ -479,40 +480,23 @@ static void esp_hard_reset(DeviceState *d) s->rregs[ESP_CFG1] = 7; } -static void esp_soft_reset(DeviceState *d) +static void esp_soft_reset(ESPState *s) { - ESPState *s = container_of(d, ESPState, busdev.qdev); - qemu_irq_lower(s->irq); - esp_hard_reset(d); + esp_hard_reset(s); } -static void parent_esp_reset(void *opaque, int irq, int level) +static void parent_esp_reset(ESPState *s, int irq, int level) { if (level) { - esp_soft_reset(opaque); + esp_soft_reset(s); } } -static void esp_gpio_demux(void *opaque, int irq, int level) +static uint64_t esp_reg_read(ESPState *s, uint32_t saddr) { - switch (irq) { - case 0: - parent_esp_reset(opaque, irq, level); - break; - case 1: - esp_dma_enable(opaque, irq, level); - break; - } -} - -static uint64_t esp_mem_read(void *opaque, target_phys_addr_t addr, - unsigned size) -{ - ESPState *s = opaque; - uint32_t saddr, old_val; + uint32_t old_val; - saddr = addr >> s->it_shift; trace_esp_mem_readb(saddr, s->rregs[saddr]); switch (saddr) { case ESP_FIFO: @@ -520,7 +504,8 @@ static uint64_t esp_mem_read(void *opaque, target_phys_addr_t addr, s->ti_size--; if ((s->rregs[ESP_RSTAT] & STAT_PIO_MASK) == 0) { /* Data out. */ - ESP_ERROR("PIO data read not implemented\n"); + qemu_log_mask(LOG_UNIMP, + "esp: PIO data read not implemented\n"); s->rregs[ESP_FIFO] = 0; } else { s->rregs[ESP_FIFO] = s->ti_buf[s->ti_rptr++]; @@ -548,13 +533,8 @@ static uint64_t esp_mem_read(void *opaque, target_phys_addr_t addr, return s->rregs[saddr]; } -static void esp_mem_write(void *opaque, target_phys_addr_t addr, - uint64_t val, unsigned size) +static void esp_reg_write(ESPState *s, uint32_t saddr, uint64_t val) { - ESPState *s = opaque; - uint32_t saddr; - - saddr = addr >> s->it_shift; trace_esp_mem_writeb(saddr, s->wregs[saddr], val); switch (saddr) { case ESP_TCLO: @@ -565,7 +545,7 @@ static void esp_mem_write(void *opaque, target_phys_addr_t addr, if (s->do_cmd) { s->cmdbuf[s->cmdlen++] = val & 0xff; } else if (s->ti_size == TI_BUFSZ - 1) { - ESP_ERROR("fifo overrun\n"); + trace_esp_error_fifo_overrun(); } else { s->ti_size++; s->ti_buf[s->ti_wptr++] = val & 0xff; @@ -594,7 +574,7 @@ static void esp_mem_write(void *opaque, target_phys_addr_t addr, break; case CMD_RESET: trace_esp_mem_writeb_cmd_reset(val); - esp_soft_reset(&s->busdev.qdev); + esp_soft_reset(s); break; case CMD_BUSRESET: trace_esp_mem_writeb_cmd_bus_reset(val); @@ -628,6 +608,9 @@ static void esp_mem_write(void *opaque, target_phys_addr_t addr, case CMD_SATN: trace_esp_mem_writeb_cmd_satn(val); break; + case CMD_RSTATN: + trace_esp_mem_writeb_cmd_rstatn(val); + break; case CMD_SEL: trace_esp_mem_writeb_cmd_sel(val); handle_s_without_atn(s); @@ -644,8 +627,13 @@ static void esp_mem_write(void *opaque, target_phys_addr_t addr, trace_esp_mem_writeb_cmd_ensel(val); s->rregs[ESP_RINTR] = 0; break; + case CMD_DISSEL: + trace_esp_mem_writeb_cmd_dissel(val); + s->rregs[ESP_RINTR] = 0; + esp_raise_irq(s); + break; default: - ESP_ERROR("Unhandled ESP command (%2.2x)\n", (unsigned)val); + trace_esp_error_unhandled_command(val); break; } break; @@ -660,7 +648,7 @@ static void esp_mem_write(void *opaque, target_phys_addr_t addr, s->rregs[saddr] = val; break; default: - ESP_ERROR("invalid write of 0x%02x at [0x%x]\n", (unsigned)val, saddr); + trace_esp_error_invalid_write(val, saddr); return; } s->wregs[saddr] = val; @@ -672,13 +660,6 @@ static bool esp_mem_accepts(void *opaque, target_phys_addr_t addr, return (size == 1) || (is_write && size == 4); } -static const MemoryRegionOps esp_mem_ops = { - .read = esp_mem_read, - .write = esp_mem_write, - .endianness = DEVICE_NATIVE_ENDIAN, - .valid.accepts = esp_mem_accepts, -}; - static const VMStateDescription vmstate_esp = { .name ="esp", .version_id = 3, @@ -701,6 +682,40 @@ static const VMStateDescription vmstate_esp = { } }; +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + uint32_t it_shift; + ESPState esp; +} SysBusESPState; + +static void sysbus_esp_mem_write(void *opaque, target_phys_addr_t addr, + uint64_t val, unsigned int size) +{ + SysBusESPState *sysbus = opaque; + uint32_t saddr; + + saddr = addr >> sysbus->it_shift; + esp_reg_write(&sysbus->esp, saddr, val); +} + +static uint64_t sysbus_esp_mem_read(void *opaque, target_phys_addr_t addr, + unsigned int size) +{ + SysBusESPState *sysbus = opaque; + uint32_t saddr; + + saddr = addr >> sysbus->it_shift; + return esp_reg_read(&sysbus->esp, saddr); +} + +static const MemoryRegionOps sysbus_esp_mem_ops = { + .read = sysbus_esp_mem_read, + .write = sysbus_esp_mem_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid.accepts = esp_mem_accepts, +}; + void esp_init(target_phys_addr_t espaddr, int it_shift, ESPDMAMemoryReadWriteFunc dma_memory_read, ESPDMAMemoryReadWriteFunc dma_memory_write, @@ -709,14 +724,16 @@ void esp_init(target_phys_addr_t espaddr, int it_shift, { DeviceState *dev; SysBusDevice *s; + SysBusESPState *sysbus; ESPState *esp; dev = qdev_create(NULL, "esp"); - esp = DO_UPCAST(ESPState, busdev.qdev, dev); + sysbus = DO_UPCAST(SysBusESPState, busdev.qdev, dev); + esp = &sysbus->esp; esp->dma_memory_read = dma_memory_read; esp->dma_memory_write = dma_memory_write; esp->dma_opaque = dma_opaque; - esp->it_shift = it_shift; + sysbus->it_shift = it_shift; /* XXX for now until rc4030 has been changed to use DMA enable signal */ esp->dma_enabled = 1; qdev_init_nofail(dev); @@ -737,48 +754,441 @@ static const struct SCSIBusInfo esp_scsi_info = { .cancel = esp_request_cancelled }; -static int esp_init1(SysBusDevice *dev) +static void sysbus_esp_gpio_demux(void *opaque, int irq, int level) { - ESPState *s = FROM_SYSBUS(ESPState, dev); + DeviceState *d = opaque; + SysBusESPState *sysbus = container_of(d, SysBusESPState, busdev.qdev); + ESPState *s = &sysbus->esp; + + switch (irq) { + case 0: + parent_esp_reset(s, irq, level); + break; + case 1: + esp_dma_enable(opaque, irq, level); + break; + } +} + +static int sysbus_esp_init(SysBusDevice *dev) +{ + SysBusESPState *sysbus = FROM_SYSBUS(SysBusESPState, dev); + ESPState *s = &sysbus->esp; sysbus_init_irq(dev, &s->irq); - assert(s->it_shift != -1); + assert(sysbus->it_shift != -1); - memory_region_init_io(&s->iomem, &esp_mem_ops, s, - "esp", ESP_REGS << s->it_shift); - sysbus_init_mmio(dev, &s->iomem); + s->chip_id = TCHI_FAS100A; + memory_region_init_io(&sysbus->iomem, &sysbus_esp_mem_ops, sysbus, + "esp", ESP_REGS << sysbus->it_shift); + sysbus_init_mmio(dev, &sysbus->iomem); - qdev_init_gpio_in(&dev->qdev, esp_gpio_demux, 2); + qdev_init_gpio_in(&dev->qdev, sysbus_esp_gpio_demux, 2); scsi_bus_new(&s->bus, &dev->qdev, &esp_scsi_info); return scsi_bus_legacy_handle_cmdline(&s->bus); } -static Property esp_properties[] = { - {.name = NULL}, +static void sysbus_esp_hard_reset(DeviceState *dev) +{ + SysBusESPState *sysbus = DO_UPCAST(SysBusESPState, busdev.qdev, dev); + esp_hard_reset(&sysbus->esp); +} + +static const VMStateDescription vmstate_sysbus_esp_scsi = { + .name = "sysbusespscsi", + .version_id = 0, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(esp, SysBusESPState, 0, vmstate_esp, ESPState), + VMSTATE_END_OF_LIST() + } }; -static void esp_class_init(ObjectClass *klass, void *data) +static void sysbus_esp_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); - k->init = esp_init1; - dc->reset = esp_hard_reset; - dc->vmsd = &vmstate_esp; - dc->props = esp_properties; + k->init = sysbus_esp_init; + dc->reset = sysbus_esp_hard_reset; + dc->vmsd = &vmstate_sysbus_esp_scsi; } -static TypeInfo esp_info = { +static TypeInfo sysbus_esp_info = { .name = "esp", .parent = TYPE_SYS_BUS_DEVICE, - .instance_size = sizeof(ESPState), - .class_init = esp_class_init, + .instance_size = sizeof(SysBusESPState), + .class_init = sysbus_esp_class_init, +}; + +#define DMA_CMD 0x0 +#define DMA_STC 0x1 +#define DMA_SPA 0x2 +#define DMA_WBC 0x3 +#define DMA_WAC 0x4 +#define DMA_STAT 0x5 +#define DMA_SMDLA 0x6 +#define DMA_WMAC 0x7 + +#define DMA_CMD_MASK 0x03 +#define DMA_CMD_DIAG 0x04 +#define DMA_CMD_MDL 0x10 +#define DMA_CMD_INTE_P 0x20 +#define DMA_CMD_INTE_D 0x40 +#define DMA_CMD_DIR 0x80 + +#define DMA_STAT_PWDN 0x01 +#define DMA_STAT_ERROR 0x02 +#define DMA_STAT_ABORT 0x04 +#define DMA_STAT_DONE 0x08 +#define DMA_STAT_SCSIINT 0x10 +#define DMA_STAT_BCMBLT 0x20 + +#define SBAC_STATUS 0x1000 + +typedef struct PCIESPState { + PCIDevice dev; + MemoryRegion io; + uint32_t dma_regs[8]; + uint32_t sbac; + ESPState esp; +} PCIESPState; + +static void esp_pci_handle_idle(PCIESPState *pci, uint32_t val) +{ + trace_esp_pci_dma_idle(val); + esp_dma_enable(&pci->esp, 0, 0); +} + +static void esp_pci_handle_blast(PCIESPState *pci, uint32_t val) +{ + trace_esp_pci_dma_blast(val); + qemu_log_mask(LOG_UNIMP, "am53c974: cmd BLAST not implemented\n"); +} + +static void esp_pci_handle_abort(PCIESPState *pci, uint32_t val) +{ + trace_esp_pci_dma_abort(val); + if (pci->esp.current_req) { + scsi_req_cancel(pci->esp.current_req); + } +} + +static void esp_pci_handle_start(PCIESPState *pci, uint32_t val) +{ + trace_esp_pci_dma_start(val); + + pci->dma_regs[DMA_WBC] = pci->dma_regs[DMA_STC]; + pci->dma_regs[DMA_WAC] = pci->dma_regs[DMA_SPA]; + pci->dma_regs[DMA_WMAC] = pci->dma_regs[DMA_SMDLA]; + + pci->dma_regs[DMA_STAT] &= ~(DMA_STAT_BCMBLT | DMA_STAT_SCSIINT + | DMA_STAT_DONE | DMA_STAT_ABORT + | DMA_STAT_ERROR | DMA_STAT_PWDN); + + esp_dma_enable(&pci->esp, 0, 1); +} + +static void esp_pci_dma_write(PCIESPState *pci, uint32_t saddr, uint32_t val) +{ + trace_esp_pci_dma_write(saddr, pci->dma_regs[saddr], val); + switch (saddr) { + case DMA_CMD: + pci->dma_regs[saddr] = val; + switch (val & DMA_CMD_MASK) { + case 0x0: /* IDLE */ + esp_pci_handle_idle(pci, val); + break; + case 0x1: /* BLAST */ + esp_pci_handle_blast(pci, val); + break; + case 0x2: /* ABORT */ + esp_pci_handle_abort(pci, val); + break; + case 0x3: /* START */ + esp_pci_handle_start(pci, val); + break; + default: /* can't happen */ + abort(); + } + break; + case DMA_STC: + case DMA_SPA: + case DMA_SMDLA: + pci->dma_regs[saddr] = val; + break; + case DMA_STAT: + if (!(pci->sbac & SBAC_STATUS)) { + /* clear some bits on write */ + uint32_t mask = DMA_STAT_ERROR | DMA_STAT_ABORT | DMA_STAT_DONE; + pci->dma_regs[DMA_STAT] &= ~(val & mask); + } + break; + default: + trace_esp_pci_error_invalid_write_dma(val, saddr); + return; + } +} + +static uint32_t esp_pci_dma_read(PCIESPState *pci, uint32_t saddr) +{ + uint32_t val; + + val = pci->dma_regs[saddr]; + if (saddr == DMA_STAT) { + if (pci->esp.rregs[ESP_RSTAT] & STAT_INT) { + val |= DMA_STAT_SCSIINT; + } + if (pci->sbac & SBAC_STATUS) { + pci->dma_regs[DMA_STAT] &= ~(DMA_STAT_ERROR | DMA_STAT_ABORT | + DMA_STAT_DONE); + } + } + + trace_esp_pci_dma_read(saddr, val); + return val; +} + +static void esp_pci_io_write(void *opaque, target_phys_addr_t addr, + uint64_t val, unsigned int size) +{ + PCIESPState *pci = opaque; + + if (size < 4 || addr & 3) { + /* need to upgrade request: we only support 4-bytes accesses */ + uint32_t current = 0, mask; + int shift; + + if (addr < 0x40) { + current = pci->esp.wregs[addr >> 2]; + } else if (addr < 0x60) { + current = pci->dma_regs[(addr - 0x40) >> 2]; + } else if (addr < 0x74) { + current = pci->sbac; + } + + shift = (4 - size) * 8; + mask = (~(uint32_t)0 << shift) >> shift; + + shift = ((4 - (addr & 3)) & 3) * 8; + val <<= shift; + val |= current & ~(mask << shift); + addr &= ~3; + size = 4; + } + + if (addr < 0x40) { + /* SCSI core reg */ + esp_reg_write(&pci->esp, addr >> 2, val); + } else if (addr < 0x60) { + /* PCI DMA CCB */ + esp_pci_dma_write(pci, (addr - 0x40) >> 2, val); + } else if (addr == 0x70) { + /* DMA SCSI Bus and control */ + trace_esp_pci_sbac_write(pci->sbac, val); + pci->sbac = val; + } else { + trace_esp_pci_error_invalid_write((int)addr); + } +} + +static uint64_t esp_pci_io_read(void *opaque, target_phys_addr_t addr, + unsigned int size) +{ + PCIESPState *pci = opaque; + uint32_t ret; + + if (addr < 0x40) { + /* SCSI core reg */ + ret = esp_reg_read(&pci->esp, addr >> 2); + } else if (addr < 0x60) { + /* PCI DMA CCB */ + ret = esp_pci_dma_read(pci, (addr - 0x40) >> 2); + } else if (addr == 0x70) { + /* DMA SCSI Bus and control */ + trace_esp_pci_sbac_read(pci->sbac); + ret = pci->sbac; + } else { + /* Invalid region */ + trace_esp_pci_error_invalid_read((int)addr); + ret = 0; + } + + /* give only requested data */ + ret >>= (addr & 3) * 8; + ret &= ~(~(uint64_t)0 << (8 * size)); + + return ret; +} + +static void esp_pci_dma_memory_rw(PCIESPState *pci, uint8_t *buf, int len, + DMADirection dir) +{ + dma_addr_t addr; + DMADirection expected_dir; + + if (pci->dma_regs[DMA_CMD] & DMA_CMD_DIR) { + expected_dir = DMA_DIRECTION_FROM_DEVICE; + } else { + expected_dir = DMA_DIRECTION_TO_DEVICE; + } + + if (dir != expected_dir) { + trace_esp_pci_error_invalid_dma_direction(); + return; + } + + if (pci->dma_regs[DMA_STAT] & DMA_CMD_MDL) { + qemu_log_mask(LOG_UNIMP, "am53c974: MDL transfer not implemented\n"); + } + + addr = pci->dma_regs[DMA_SPA]; + if (pci->dma_regs[DMA_WBC] < len) { + len = pci->dma_regs[DMA_WBC]; + } + + pci_dma_rw(&pci->dev, addr, buf, len, dir); + + /* update status registers */ + pci->dma_regs[DMA_WBC] -= len; + pci->dma_regs[DMA_WAC] += len; +} + +static void esp_pci_dma_memory_read(void *opaque, uint8_t *buf, int len) +{ + PCIESPState *pci = opaque; + esp_pci_dma_memory_rw(pci, buf, len, DMA_DIRECTION_TO_DEVICE); +} + +static void esp_pci_dma_memory_write(void *opaque, uint8_t *buf, int len) +{ + PCIESPState *pci = opaque; + esp_pci_dma_memory_rw(pci, buf, len, DMA_DIRECTION_FROM_DEVICE); +} + +static const MemoryRegionOps esp_pci_io_ops = { + .read = esp_pci_io_read, + .write = esp_pci_io_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +static void esp_pci_hard_reset(DeviceState *dev) +{ + PCIESPState *pci = DO_UPCAST(PCIESPState, dev.qdev, dev); + esp_hard_reset(&pci->esp); + pci->dma_regs[DMA_CMD] &= ~(DMA_CMD_DIR | DMA_CMD_INTE_D | DMA_CMD_INTE_P + | DMA_CMD_MDL | DMA_CMD_DIAG | DMA_CMD_MASK); + pci->dma_regs[DMA_WBC] &= ~0xffff; + pci->dma_regs[DMA_WAC] = 0xffffffff; + pci->dma_regs[DMA_STAT] &= ~(DMA_STAT_BCMBLT | DMA_STAT_SCSIINT + | DMA_STAT_DONE | DMA_STAT_ABORT + | DMA_STAT_ERROR); + pci->dma_regs[DMA_WMAC] = 0xfffffffd; +} + +static const VMStateDescription vmstate_esp_pci_scsi = { + .name = "pciespscsi", + .version_id = 0, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(dev, PCIESPState), + VMSTATE_BUFFER_UNSAFE(dma_regs, PCIESPState, 0, 8 * sizeof(uint32_t)), + VMSTATE_STRUCT(esp, PCIESPState, 0, vmstate_esp, ESPState), + VMSTATE_END_OF_LIST() + } +}; + +static void esp_pci_command_complete(SCSIRequest *req, uint32_t status, + size_t resid) +{ + ESPState *s = req->hba_private; + PCIESPState *pci = container_of(s, PCIESPState, esp); + + esp_command_complete(req, status, resid); + pci->dma_regs[DMA_WBC] = 0; + pci->dma_regs[DMA_STAT] |= DMA_STAT_DONE; +} + +static const struct SCSIBusInfo esp_pci_scsi_info = { + .tcq = false, + .max_target = ESP_MAX_DEVS, + .max_lun = 7, + + .transfer_data = esp_transfer_data, + .complete = esp_pci_command_complete, + .cancel = esp_request_cancelled, +}; + +static int esp_pci_scsi_init(PCIDevice *dev) +{ + PCIESPState *pci = DO_UPCAST(PCIESPState, dev, dev); + ESPState *s = &pci->esp; + uint8_t *pci_conf; + + pci_conf = pci->dev.config; + + /* Interrupt pin A */ + pci_conf[PCI_INTERRUPT_PIN] = 0x01; + + s->dma_memory_read = esp_pci_dma_memory_read; + s->dma_memory_write = esp_pci_dma_memory_write; + s->dma_opaque = pci; + s->chip_id = TCHI_AM53C974; + memory_region_init_io(&pci->io, &esp_pci_io_ops, pci, "esp-io", 0x80); + + pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &pci->io); + s->irq = pci->dev.irq[0]; + + scsi_bus_new(&s->bus, &dev->qdev, &esp_pci_scsi_info); + if (!dev->qdev.hotplugged) { + return scsi_bus_legacy_handle_cmdline(&s->bus); + } + return 0; +} + +static int esp_pci_scsi_uninit(PCIDevice *d) +{ + PCIESPState *pci = DO_UPCAST(PCIESPState, dev, d); + + memory_region_destroy(&pci->io); + + return 0; +} + +static void esp_pci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = esp_pci_scsi_init; + k->exit = esp_pci_scsi_uninit; + k->vendor_id = PCI_VENDOR_ID_AMD; + k->device_id = PCI_DEVICE_ID_AMD_SCSI; + k->revision = 0x10; + k->class_id = PCI_CLASS_STORAGE_SCSI; + dc->desc = "AMD Am53c974 PCscsi-PCI SCSI adapter"; + dc->reset = esp_pci_hard_reset; + dc->vmsd = &vmstate_esp_pci_scsi; +} + +static TypeInfo esp_pci_info = { + .name = "am53c974", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(PCIESPState), + .class_init = esp_pci_class_init, }; static void esp_register_types(void) { - type_register_static(&esp_info); + type_register_static(&sysbus_esp_info); + type_register_static(&esp_pci_info); } type_init(esp_register_types) diff --git a/hw/imx_avic.c b/hw/imx_avic.c index 25f47f331b..4f010e8ee2 100644 --- a/hw/imx_avic.c +++ b/hw/imx_avic.c @@ -267,7 +267,7 @@ static void imx_avic_write(void *opaque, target_phys_addr_t offset, /* Vector Registers not yet supported */ if (offset >= 0x100 && offset <= 0x2fc) { IPRINTF("imx_avic_write to vector register %d ignored\n", - (offset - 0x100) >> 2); + (unsigned int)((offset - 0x100) >> 2)); return; } diff --git a/hw/megasas.c b/hw/megasas.c index b48836fff1..b99fa9792e 100644 --- a/hw/megasas.c +++ b/hw/megasas.c @@ -45,7 +45,7 @@ #define MEGASAS_FLAG_USE_QUEUE64 2 #define MEGASAS_MASK_USE_QUEUE64 (1 << MEGASAS_FLAG_USE_QUEUE64) -const char *mfi_frame_desc[] = { +static const char *mfi_frame_desc[] = { "MFI init", "LD Read", "LD Write", "LD SCSI", "PD SCSI", "MFI Doorbell", "MFI Abort", "MFI SMP", "MFI Stop"}; diff --git a/hw/omap.h b/hw/omap.h index 3d98941b72..413851bc34 100644 --- a/hw/omap.h +++ b/hw/omap.h @@ -942,13 +942,7 @@ struct omap_mpu_state_s *omap2420_mpu_init(MemoryRegion *sysmem, unsigned long sdram_size, const char *core); -# if TARGET_PHYS_ADDR_BITS == 32 -# define OMAP_FMT_plx "%#08x" -# elif TARGET_PHYS_ADDR_BITS == 64 -# define OMAP_FMT_plx "%#08" PRIx64 -# else -# error TARGET_PHYS_ADDR_BITS undefined -# endif +#define OMAP_FMT_plx "%#08" TARGET_PRIxPHYS uint32_t omap_badwidth_read8(void *opaque, target_phys_addr_t addr); void omap_badwidth_write8(void *opaque, target_phys_addr_t addr, diff --git a/hw/pci-stub.c b/hw/pci-stub.c index 134c4484b6..e083191529 100644 --- a/hw/pci-stub.c +++ b/hw/pci-stub.c @@ -34,6 +34,21 @@ static void pci_error_message(Monitor *mon) monitor_printf(mon, "PCI devices not supported\n"); } +void pci_register_bar(PCIDevice *pci_dev, int region_num, + uint8_t type, MemoryRegion *memory) +{ +} + +const VMStateDescription vmstate_pci_device = { + .name = "PCIDeviceStub", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_END_OF_LIST() + } +}; + int do_pcie_aer_inject_error(Monitor *mon, const QDict *qdict, QObject **ret_data) { diff --git a/hw/pci_ids.h b/hw/pci_ids.h index b4801d2168..301bf1cd86 100644 --- a/hw/pci_ids.h +++ b/hw/pci_ids.h @@ -59,6 +59,7 @@ #define PCI_VENDOR_ID_AMD 0x1022 #define PCI_DEVICE_ID_AMD_LANCE 0x2000 +#define PCI_DEVICE_ID_AMD_SCSI 0x2020 #define PCI_VENDOR_ID_TI 0x104c diff --git a/hw/qdev-properties.c b/hw/qdev-properties.c index 0b894620c9..3571cf3017 100644 --- a/hw/qdev-properties.c +++ b/hw/qdev-properties.c @@ -1123,7 +1123,7 @@ void qdev_prop_set_uint64(DeviceState *dev, const char *name, uint64_t value) assert_no_error(errp); } -void qdev_prop_set_string(DeviceState *dev, const char *name, char *value) +void qdev_prop_set_string(DeviceState *dev, const char *name, const char *value) { Error *errp = NULL; object_property_set_str(OBJECT(dev), value, name, &errp); diff --git a/hw/qdev.h b/hw/qdev.h index f4683dc771..a0770b085a 100644 --- a/hw/qdev.h +++ b/hw/qdev.h @@ -316,7 +316,7 @@ void qdev_prop_set_uint16(DeviceState *dev, const char *name, uint16_t value); void qdev_prop_set_uint32(DeviceState *dev, const char *name, uint32_t value); void qdev_prop_set_int32(DeviceState *dev, const char *name, int32_t value); void qdev_prop_set_uint64(DeviceState *dev, const char *name, uint64_t value); -void qdev_prop_set_string(DeviceState *dev, const char *name, char *value); +void qdev_prop_set_string(DeviceState *dev, const char *name, const char *value); void qdev_prop_set_chr(DeviceState *dev, const char *name, CharDriverState *value); void qdev_prop_set_netdev(DeviceState *dev, const char *name, VLANClientState *value); void qdev_prop_set_vlan(DeviceState *dev, const char *name, VLANState *value); diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c index 5ad1013be1..dc7406389d 100644 --- a/hw/scsi-bus.c +++ b/hw/scsi-bus.c @@ -1354,6 +1354,7 @@ static const char *scsi_command_name(uint8_t cmd) SCSIRequest *scsi_req_ref(SCSIRequest *req) { + assert(req->refcount > 0); req->refcount++; return req; } @@ -1362,6 +1363,10 @@ void scsi_req_unref(SCSIRequest *req) { assert(req->refcount > 0); if (--req->refcount == 0) { + SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, req->dev->qdev.parent_bus); + if (bus->info->free_request && req->hba_private) { + bus->info->free_request(bus, req->hba_private); + } if (req->ops->free_req) { req->ops->free_req(req); } diff --git a/hw/scsi.h b/hw/scsi.h index 76f06d41de..367a346020 100644 --- a/hw/scsi.h +++ b/hw/scsi.h @@ -134,6 +134,7 @@ struct SCSIBusInfo { void (*save_request)(QEMUFile *f, SCSIRequest *req); void *(*load_request)(QEMUFile *f, SCSIRequest *req); + void (*free_request)(SCSIBus *bus, void *priv); }; #define TYPE_SCSI_BUS "SCSI" diff --git a/hw/sh_serial.c b/hw/sh_serial.c index 43b0eb1c1d..1d1883dd20 100644 --- a/hw/sh_serial.c +++ b/hw/sh_serial.c @@ -186,7 +186,8 @@ static void sh_serial_write(void *opaque, target_phys_addr_t offs, } } - fprintf(stderr, "sh_serial: unsupported write to 0x%02x\n", offs); + fprintf(stderr, "sh_serial: unsupported write to 0x%02" + TARGET_PRIxPHYS "\n", offs); abort(); } @@ -287,7 +288,8 @@ static uint64_t sh_serial_read(void *opaque, target_phys_addr_t offs, #endif if (ret & ~((1 << 16) - 1)) { - fprintf(stderr, "sh_serial: unsupported read from 0x%02x\n", offs); + fprintf(stderr, "sh_serial: unsupported read from 0x%02" + TARGET_PRIxPHYS "\n", offs); abort(); } diff --git a/hw/usb/Makefile.objs b/hw/usb/Makefile.objs index 9c7ddf5cb2..4225136d0f 100644 --- a/hw/usb/Makefile.objs +++ b/hw/usb/Makefile.objs @@ -11,3 +11,4 @@ common-obj-y += core.o bus.o desc.o dev-hub.o common-obj-y += host-$(HOST_USB).o dev-bluetooth.o common-obj-y += dev-hid.o dev-storage.o dev-wacom.o common-obj-y += dev-serial.o dev-network.o dev-audio.o +common-obj-y += dev-uas.o diff --git a/hw/usb/dev-uas.c b/hw/usb/dev-uas.c new file mode 100644 index 0000000000..9b02ff48fa --- /dev/null +++ b/hw/usb/dev-uas.c @@ -0,0 +1,779 @@ +/* + * UAS (USB Attached SCSI) emulation + * + * Copyright Red Hat, Inc. 2012 + * + * Author: Gerd Hoffmann <kraxel@redhat.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu-common.h" +#include "qemu-option.h" +#include "qemu-config.h" +#include "trace.h" + +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "hw/scsi.h" +#include "hw/scsi-defs.h" + +/* --------------------------------------------------------------------- */ + +#define UAS_UI_COMMAND 0x01 +#define UAS_UI_SENSE 0x03 +#define UAS_UI_RESPONSE 0x04 +#define UAS_UI_TASK_MGMT 0x05 +#define UAS_UI_READ_READY 0x06 +#define UAS_UI_WRITE_READY 0x07 + +#define UAS_RC_TMF_COMPLETE 0x00 +#define UAS_RC_INVALID_INFO_UNIT 0x02 +#define UAS_RC_TMF_NOT_SUPPORTED 0x04 +#define UAS_RC_TMF_FAILED 0x05 +#define UAS_RC_TMF_SUCCEEDED 0x08 +#define UAS_RC_INCORRECT_LUN 0x09 +#define UAS_RC_OVERLAPPED_TAG 0x0a + +#define UAS_TMF_ABORT_TASK 0x01 +#define UAS_TMF_ABORT_TASK_SET 0x02 +#define UAS_TMF_CLEAR_TASK_SET 0x04 +#define UAS_TMF_LOGICAL_UNIT_RESET 0x08 +#define UAS_TMF_I_T_NEXUS_RESET 0x10 +#define UAS_TMF_CLEAR_ACA 0x40 +#define UAS_TMF_QUERY_TASK 0x80 +#define UAS_TMF_QUERY_TASK_SET 0x81 +#define UAS_TMF_QUERY_ASYNC_EVENT 0x82 + +#define UAS_PIPE_ID_COMMAND 0x01 +#define UAS_PIPE_ID_STATUS 0x02 +#define UAS_PIPE_ID_DATA_IN 0x03 +#define UAS_PIPE_ID_DATA_OUT 0x04 + +typedef struct { + uint8_t id; + uint8_t reserved; + uint16_t tag; +} QEMU_PACKED uas_ui_header; + +typedef struct { + uint8_t prio_taskattr; /* 6:3 priority, 2:0 task attribute */ + uint8_t reserved_1; + uint8_t add_cdb_length; /* 7:2 additional adb length (dwords) */ + uint8_t reserved_2; + uint64_t lun; + uint8_t cdb[16]; + uint8_t add_cdb[]; +} QEMU_PACKED uas_ui_command; + +typedef struct { + uint16_t status_qualifier; + uint8_t status; + uint8_t reserved[7]; + uint16_t sense_length; + uint8_t sense_data[18]; +} QEMU_PACKED uas_ui_sense; + +typedef struct { + uint16_t add_response_info; + uint8_t response_code; +} QEMU_PACKED uas_ui_response; + +typedef struct { + uint8_t function; + uint8_t reserved; + uint16_t task_tag; + uint64_t lun; +} QEMU_PACKED uas_ui_task_mgmt; + +typedef struct { + uas_ui_header hdr; + union { + uas_ui_command command; + uas_ui_sense sense; + uas_ui_task_mgmt task; + uas_ui_response response; + }; +} QEMU_PACKED uas_ui; + +/* --------------------------------------------------------------------- */ + +typedef struct UASDevice UASDevice; +typedef struct UASRequest UASRequest; +typedef struct UASStatus UASStatus; + +struct UASDevice { + USBDevice dev; + SCSIBus bus; + UASRequest *datain; + UASRequest *dataout; + USBPacket *status; + QEMUBH *status_bh; + QTAILQ_HEAD(, UASStatus) results; + QTAILQ_HEAD(, UASRequest) requests; +}; + +struct UASRequest { + uint16_t tag; + uint64_t lun; + UASDevice *uas; + SCSIDevice *dev; + SCSIRequest *req; + USBPacket *data; + bool data_async; + bool active; + bool complete; + uint32_t buf_off; + uint32_t buf_size; + uint32_t data_off; + uint32_t data_size; + QTAILQ_ENTRY(UASRequest) next; +}; + +struct UASStatus { + uas_ui status; + uint32_t length; + QTAILQ_ENTRY(UASStatus) next; +}; + +/* --------------------------------------------------------------------- */ + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT, + STR_SERIALNUMBER, + STR_CONFIG_HIGH, +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU", + [STR_PRODUCT] = "USB Attached SCSI HBA", + [STR_SERIALNUMBER] = "27842", + [STR_CONFIG_HIGH] = "High speed config (usb 2.0)", +}; + +static const USBDescIface desc_iface_high = { + .bInterfaceNumber = 0, + .bNumEndpoints = 4, + .bInterfaceClass = USB_CLASS_MASS_STORAGE, + .bInterfaceSubClass = 0x06, /* SCSI */ + .bInterfaceProtocol = 0x62, /* UAS */ + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_OUT | UAS_PIPE_ID_COMMAND, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 512, + .extra = (uint8_t[]) { + 0x04, /* u8 bLength */ + 0x24, /* u8 bDescriptorType */ + UAS_PIPE_ID_COMMAND, + 0x00, /* u8 bReserved */ + }, + },{ + .bEndpointAddress = USB_DIR_IN | UAS_PIPE_ID_STATUS, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 512, + .extra = (uint8_t[]) { + 0x04, /* u8 bLength */ + 0x24, /* u8 bDescriptorType */ + UAS_PIPE_ID_STATUS, + 0x00, /* u8 bReserved */ + }, + },{ + .bEndpointAddress = USB_DIR_IN | UAS_PIPE_ID_DATA_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 512, + .extra = (uint8_t[]) { + 0x04, /* u8 bLength */ + 0x24, /* u8 bDescriptorType */ + UAS_PIPE_ID_DATA_IN, + 0x00, /* u8 bReserved */ + }, + },{ + .bEndpointAddress = USB_DIR_OUT | UAS_PIPE_ID_DATA_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 512, + .extra = (uint8_t[]) { + 0x04, /* u8 bLength */ + 0x24, /* u8 bDescriptorType */ + UAS_PIPE_ID_DATA_OUT, + 0x00, /* u8 bReserved */ + }, + }, + } +}; + +static const USBDescDevice desc_device_high = { + .bcdUSB = 0x0200, + .bMaxPacketSize0 = 64, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_HIGH, + .bmAttributes = 0xc0, + .nif = 1, + .ifs = &desc_iface_high, + }, + }, +}; + +static const USBDesc desc = { + .id = { + .idVendor = 0x46f4, /* CRC16() of "QEMU" */ + .idProduct = 0x0002, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT, + .iSerialNumber = STR_SERIALNUMBER, + }, + .high = &desc_device_high, + .str = desc_strings, +}; + +/* --------------------------------------------------------------------- */ + +static UASStatus *usb_uas_alloc_status(uint8_t id, uint16_t tag) +{ + UASStatus *st = g_new0(UASStatus, 1); + + st->status.hdr.id = id; + st->status.hdr.tag = cpu_to_be16(tag); + st->length = sizeof(uas_ui_header); + return st; +} + +static void usb_uas_send_status_bh(void *opaque) +{ + UASDevice *uas = opaque; + UASStatus *st = QTAILQ_FIRST(&uas->results); + USBPacket *p = uas->status; + + assert(p != NULL); + assert(st != NULL); + + uas->status = NULL; + usb_packet_copy(p, &st->status, st->length); + p->result = st->length; + QTAILQ_REMOVE(&uas->results, st, next); + g_free(st); + + usb_packet_complete(&uas->dev, p); +} + +static void usb_uas_queue_status(UASDevice *uas, UASStatus *st, int length) +{ + st->length += length; + QTAILQ_INSERT_TAIL(&uas->results, st, next); + if (uas->status) { + /* + * Just schedule bh make sure any in-flight data transaction + * is finished before completing (sending) the status packet. + */ + qemu_bh_schedule(uas->status_bh); + } else { + USBEndpoint *ep = usb_ep_get(&uas->dev, USB_TOKEN_IN, + UAS_PIPE_ID_STATUS); + usb_wakeup(ep); + } +} + +static void usb_uas_queue_response(UASDevice *uas, uint16_t tag, + uint8_t code, uint16_t add_info) +{ + UASStatus *st = usb_uas_alloc_status(UAS_UI_RESPONSE, tag); + + trace_usb_uas_response(uas->dev.addr, tag, code); + st->status.response.response_code = code; + st->status.response.add_response_info = cpu_to_be16(add_info); + usb_uas_queue_status(uas, st, sizeof(uas_ui_response)); +} + +static void usb_uas_queue_sense(UASRequest *req, uint8_t status) +{ + UASStatus *st = usb_uas_alloc_status(UAS_UI_SENSE, req->tag); + int len, slen = 0; + + trace_usb_uas_sense(req->uas->dev.addr, req->tag, status); + st->status.sense.status = status; + st->status.sense.status_qualifier = cpu_to_be16(0); + if (status != GOOD) { + slen = scsi_req_get_sense(req->req, st->status.sense.sense_data, + sizeof(st->status.sense.sense_data)); + st->status.sense.sense_length = cpu_to_be16(slen); + } + len = sizeof(uas_ui_sense) - sizeof(st->status.sense.sense_data) + slen; + usb_uas_queue_status(req->uas, st, len); +} + +static void usb_uas_queue_read_ready(UASRequest *req) +{ + UASStatus *st = usb_uas_alloc_status(UAS_UI_READ_READY, req->tag); + + trace_usb_uas_read_ready(req->uas->dev.addr, req->tag); + usb_uas_queue_status(req->uas, st, 0); +} + +static void usb_uas_queue_write_ready(UASRequest *req) +{ + UASStatus *st = usb_uas_alloc_status(UAS_UI_WRITE_READY, req->tag); + + trace_usb_uas_write_ready(req->uas->dev.addr, req->tag); + usb_uas_queue_status(req->uas, st, 0); +} + +/* --------------------------------------------------------------------- */ + +static int usb_uas_get_lun(uint64_t lun64) +{ + return (lun64 >> 48) & 0xff; +} + +static SCSIDevice *usb_uas_get_dev(UASDevice *uas, uint64_t lun64) +{ + if ((lun64 >> 56) != 0x00) { + return NULL; + } + return scsi_device_find(&uas->bus, 0, 0, usb_uas_get_lun(lun64)); +} + +static void usb_uas_complete_data_packet(UASRequest *req) +{ + USBPacket *p; + + if (!req->data_async) { + return; + } + p = req->data; + req->data = NULL; + req->data_async = false; + usb_packet_complete(&req->uas->dev, p); +} + +static void usb_uas_copy_data(UASRequest *req) +{ + uint32_t length; + + length = MIN(req->buf_size - req->buf_off, + req->data->iov.size - req->data->result); + trace_usb_uas_xfer_data(req->uas->dev.addr, req->tag, length, + req->data->result, req->data->iov.size, + req->buf_off, req->buf_size); + usb_packet_copy(req->data, scsi_req_get_buf(req->req) + req->buf_off, + length); + req->buf_off += length; + req->data_off += length; + + if (req->data->result == req->data->iov.size) { + usb_uas_complete_data_packet(req); + } + if (req->buf_size && req->buf_off == req->buf_size) { + req->buf_off = 0; + req->buf_size = 0; + scsi_req_continue(req->req); + } +} + +static void usb_uas_start_next_transfer(UASDevice *uas) +{ + UASRequest *req; + + QTAILQ_FOREACH(req, &uas->requests, next) { + if (req->active || req->complete) { + continue; + } + if (req->req->cmd.mode == SCSI_XFER_FROM_DEV && uas->datain == NULL) { + uas->datain = req; + usb_uas_queue_read_ready(req); + req->active = true; + return; + } + if (req->req->cmd.mode == SCSI_XFER_TO_DEV && uas->dataout == NULL) { + uas->dataout = req; + usb_uas_queue_write_ready(req); + req->active = true; + return; + } + } +} + +static UASRequest *usb_uas_alloc_request(UASDevice *uas, uas_ui *ui) +{ + UASRequest *req; + + req = g_new0(UASRequest, 1); + req->uas = uas; + req->tag = be16_to_cpu(ui->hdr.tag); + req->lun = be64_to_cpu(ui->command.lun); + req->dev = usb_uas_get_dev(req->uas, req->lun); + return req; +} + +static void usb_uas_scsi_free_request(SCSIBus *bus, void *priv) +{ + UASRequest *req = priv; + UASDevice *uas = req->uas; + + if (req == uas->datain) { + uas->datain = NULL; + } + if (req == uas->dataout) { + uas->dataout = NULL; + } + QTAILQ_REMOVE(&uas->requests, req, next); + g_free(req); +} + +static UASRequest *usb_uas_find_request(UASDevice *uas, uint16_t tag) +{ + UASRequest *req; + + QTAILQ_FOREACH(req, &uas->requests, next) { + if (req->tag == tag) { + return req; + } + } + return NULL; +} + +static void usb_uas_scsi_transfer_data(SCSIRequest *r, uint32_t len) +{ + UASRequest *req = r->hba_private; + + trace_usb_uas_scsi_data(req->uas->dev.addr, req->tag, len); + req->buf_off = 0; + req->buf_size = len; + if (req->data) { + usb_uas_copy_data(req); + } else { + usb_uas_start_next_transfer(req->uas); + } +} + +static void usb_uas_scsi_command_complete(SCSIRequest *r, + uint32_t status, size_t resid) +{ + UASRequest *req = r->hba_private; + UASDevice *uas = req->uas; + + trace_usb_uas_scsi_complete(req->uas->dev.addr, req->tag, status, resid); + req->complete = true; + if (req->data) { + usb_uas_complete_data_packet(req); + } + usb_uas_queue_sense(req, status); + scsi_req_unref(req->req); + usb_uas_start_next_transfer(uas); +} + +static void usb_uas_scsi_request_cancelled(SCSIRequest *r) +{ + UASRequest *req = r->hba_private; + + /* FIXME: queue notification to status pipe? */ + scsi_req_unref(req->req); +} + +static const struct SCSIBusInfo usb_uas_scsi_info = { + .tcq = true, + .max_target = 0, + .max_lun = 255, + + .transfer_data = usb_uas_scsi_transfer_data, + .complete = usb_uas_scsi_command_complete, + .cancel = usb_uas_scsi_request_cancelled, + .free_request = usb_uas_scsi_free_request, +}; + +/* --------------------------------------------------------------------- */ + +static void usb_uas_handle_reset(USBDevice *dev) +{ + UASDevice *uas = DO_UPCAST(UASDevice, dev, dev); + UASRequest *req, *nreq; + UASStatus *st, *nst; + + trace_usb_uas_reset(dev->addr); + QTAILQ_FOREACH_SAFE(req, &uas->requests, next, nreq) { + scsi_req_cancel(req->req); + } + QTAILQ_FOREACH_SAFE(st, &uas->results, next, nst) { + QTAILQ_REMOVE(&uas->results, st, next); + g_free(st); + } +} + +static int usb_uas_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + int ret; + + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return ret; + } + fprintf(stderr, "%s: unhandled control request\n", __func__); + return USB_RET_STALL; +} + +static void usb_uas_cancel_io(USBDevice *dev, USBPacket *p) +{ + UASDevice *uas = DO_UPCAST(UASDevice, dev, dev); + UASRequest *req, *nreq; + + if (uas->status == p) { + uas->status = NULL; + qemu_bh_cancel(uas->status_bh); + return; + } + QTAILQ_FOREACH_SAFE(req, &uas->requests, next, nreq) { + if (req->data == p) { + req->data = NULL; + return; + } + } + assert(!"canceled usb packet not found"); +} + +static void usb_uas_command(UASDevice *uas, uas_ui *ui) +{ + UASRequest *req; + uint32_t len; + + req = usb_uas_find_request(uas, be16_to_cpu(ui->hdr.tag)); + if (req) { + goto overlapped_tag; + } + req = usb_uas_alloc_request(uas, ui); + if (req->dev == NULL) { + goto bad_target; + } + + trace_usb_uas_command(uas->dev.addr, req->tag, + usb_uas_get_lun(req->lun), + req->lun >> 32, req->lun & 0xffffffff); + QTAILQ_INSERT_TAIL(&uas->requests, req, next); + req->req = scsi_req_new(req->dev, req->tag, + usb_uas_get_lun(req->lun), + ui->command.cdb, req); + len = scsi_req_enqueue(req->req); + if (len) { + req->data_size = len; + scsi_req_continue(req->req); + } + return; + +overlapped_tag: + usb_uas_queue_response(uas, req->tag, UAS_RC_OVERLAPPED_TAG, 0); + return; + +bad_target: + /* + * FIXME: Seems to upset linux, is this wrong? + * NOTE: Happens only with no scsi devices at the bus, not sure + * this is a valid UAS setup in the first place. + */ + usb_uas_queue_response(uas, req->tag, UAS_RC_INVALID_INFO_UNIT, 0); + g_free(req); + return; +} + +static void usb_uas_task(UASDevice *uas, uas_ui *ui) +{ + uint16_t tag = be16_to_cpu(ui->hdr.tag); + uint64_t lun64 = be64_to_cpu(ui->task.lun); + SCSIDevice *dev = usb_uas_get_dev(uas, lun64); + int lun = usb_uas_get_lun(lun64); + UASRequest *req; + uint16_t task_tag; + + req = usb_uas_find_request(uas, be16_to_cpu(ui->hdr.tag)); + if (req) { + goto overlapped_tag; + } + + switch (ui->task.function) { + case UAS_TMF_ABORT_TASK: + task_tag = be16_to_cpu(ui->task.task_tag); + trace_usb_uas_tmf_abort_task(uas->dev.addr, tag, task_tag); + if (dev == NULL) { + goto bad_target; + } + if (dev->lun != lun) { + goto incorrect_lun; + } + req = usb_uas_find_request(uas, task_tag); + if (req && req->dev == dev) { + scsi_req_cancel(req->req); + } + usb_uas_queue_response(uas, tag, UAS_RC_TMF_COMPLETE, 0); + break; + + case UAS_TMF_LOGICAL_UNIT_RESET: + trace_usb_uas_tmf_logical_unit_reset(uas->dev.addr, tag, lun); + if (dev == NULL) { + goto bad_target; + } + if (dev->lun != lun) { + goto incorrect_lun; + } + qdev_reset_all(&dev->qdev); + usb_uas_queue_response(uas, tag, UAS_RC_TMF_COMPLETE, 0); + break; + + default: + trace_usb_uas_tmf_unsupported(uas->dev.addr, tag, ui->task.function); + usb_uas_queue_response(uas, tag, UAS_RC_TMF_NOT_SUPPORTED, 0); + break; + } + return; + +overlapped_tag: + usb_uas_queue_response(uas, req->tag, UAS_RC_OVERLAPPED_TAG, 0); + return; + +bad_target: + /* FIXME: correct? [see long comment in usb_uas_command()] */ + usb_uas_queue_response(uas, tag, UAS_RC_INVALID_INFO_UNIT, 0); + return; + +incorrect_lun: + usb_uas_queue_response(uas, tag, UAS_RC_INCORRECT_LUN, 0); + return; +} + +static int usb_uas_handle_data(USBDevice *dev, USBPacket *p) +{ + UASDevice *uas = DO_UPCAST(UASDevice, dev, dev); + uas_ui ui; + UASStatus *st; + UASRequest *req; + int length, ret = 0; + + switch (p->ep->nr) { + case UAS_PIPE_ID_COMMAND: + length = MIN(sizeof(ui), p->iov.size); + usb_packet_copy(p, &ui, length); + switch (ui.hdr.id) { + case UAS_UI_COMMAND: + usb_uas_command(uas, &ui); + ret = length; + break; + case UAS_UI_TASK_MGMT: + usb_uas_task(uas, &ui); + ret = length; + break; + default: + fprintf(stderr, "%s: unknown command ui: id 0x%x\n", + __func__, ui.hdr.id); + ret = USB_RET_STALL; + break; + } + break; + case UAS_PIPE_ID_STATUS: + st = QTAILQ_FIRST(&uas->results); + if (st == NULL) { + assert(uas->status == NULL); + uas->status = p; + ret = USB_RET_ASYNC; + break; + } + usb_packet_copy(p, &st->status, st->length); + ret = st->length; + QTAILQ_REMOVE(&uas->results, st, next); + g_free(st); + break; + case UAS_PIPE_ID_DATA_IN: + case UAS_PIPE_ID_DATA_OUT: + req = (p->ep->nr == UAS_PIPE_ID_DATA_IN) ? uas->datain : uas->dataout; + if (req == NULL) { + fprintf(stderr, "%s: no inflight request\n", __func__); + ret = USB_RET_STALL; + break; + } + scsi_req_ref(req->req); + req->data = p; + usb_uas_copy_data(req); + if (p->result == p->iov.size || req->complete) { + req->data = NULL; + ret = p->result; + } else { + req->data_async = true; + ret = USB_RET_ASYNC; + } + scsi_req_unref(req->req); + usb_uas_start_next_transfer(uas); + break; + default: + fprintf(stderr, "%s: invalid endpoint %d\n", __func__, p->ep->nr); + ret = USB_RET_STALL; + break; + } + return ret; +} + +static void usb_uas_handle_destroy(USBDevice *dev) +{ + UASDevice *uas = DO_UPCAST(UASDevice, dev, dev); + + qemu_bh_delete(uas->status_bh); +} + +static int usb_uas_init(USBDevice *dev) +{ + UASDevice *uas = DO_UPCAST(UASDevice, dev, dev); + + usb_desc_create_serial(dev); + usb_desc_init(dev); + + QTAILQ_INIT(&uas->results); + QTAILQ_INIT(&uas->requests); + uas->status_bh = qemu_bh_new(usb_uas_send_status_bh, uas); + + scsi_bus_new(&uas->bus, &uas->dev.qdev, &usb_uas_scsi_info); + + return 0; +} + +static const VMStateDescription vmstate_usb_uas = { + .name = "usb-uas", + .unmigratable = 1, + .fields = (VMStateField[]) { + VMSTATE_USB_DEVICE(dev, UASDevice), + VMSTATE_END_OF_LIST() + } +}; + +static void usb_uas_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->init = usb_uas_init; + uc->product_desc = desc_strings[STR_PRODUCT]; + uc->usb_desc = &desc; + uc->cancel_packet = usb_uas_cancel_io; + uc->handle_attach = usb_desc_attach; + uc->handle_reset = usb_uas_handle_reset; + uc->handle_control = usb_uas_handle_control; + uc->handle_data = usb_uas_handle_data; + uc->handle_destroy = usb_uas_handle_destroy; + dc->fw_name = "storage"; + dc->vmsd = &vmstate_usb_uas; +} + +static TypeInfo uas_info = { + .name = "usb-uas", + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(UASDevice), + .class_init = usb_uas_class_initfn, +}; + +static void usb_uas_register_types(void) +{ + type_register_static(&uas_info); +} + +type_init(usb_uas_register_types) diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c index 080f62c00d..b043e7c23e 100644 --- a/hw/usb/hcd-ehci.c +++ b/hw/usb/hcd-ehci.c @@ -420,6 +420,7 @@ struct EHCIState { USBPort ports[NB_PORTS]; USBPort *companion_ports[NB_PORTS]; uint32_t usbsts_pending; + uint32_t usbsts_frindex; EHCIQueueHead aqueues; EHCIQueueHead pqueues; @@ -558,34 +559,45 @@ static inline void ehci_clear_usbsts(EHCIState *s, int mask) s->usbsts &= ~mask; } -static inline void ehci_set_interrupt(EHCIState *s, int intr) +/* update irq line */ +static inline void ehci_update_irq(EHCIState *s) { int level = 0; - // TODO honour interrupt threshold requests - - ehci_set_usbsts(s, intr); - if ((s->usbsts & USBINTR_MASK) & s->usbintr) { level = 1; } - trace_usb_ehci_interrupt(level, s->usbsts, s->usbintr); + trace_usb_ehci_irq(level, s->frindex, s->usbsts, s->usbintr); qemu_set_irq(s->irq, level); } -static inline void ehci_record_interrupt(EHCIState *s, int intr) +/* flag interrupt condition */ +static inline void ehci_raise_irq(EHCIState *s, int intr) { s->usbsts_pending |= intr; } -static inline void ehci_commit_interrupt(EHCIState *s) +/* + * Commit pending interrupts (added via ehci_raise_irq), + * at the rate allowed by "Interrupt Threshold Control". + */ +static inline void ehci_commit_irq(EHCIState *s) { + uint32_t itc; + if (!s->usbsts_pending) { return; } - ehci_set_interrupt(s, s->usbsts_pending); + if (s->usbsts_frindex > s->frindex) { + return; + } + + itc = (s->usbcmd >> 16) & 0xff; + s->usbsts |= s->usbsts_pending; s->usbsts_pending = 0; + s->usbsts_frindex = s->frindex + itc; + ehci_update_irq(s); } static void ehci_update_halt(EHCIState *s) @@ -849,7 +861,8 @@ static void ehci_attach(USBPort *port) *portsc |= PORTSC_CONNECT; *portsc |= PORTSC_CSC; - ehci_set_interrupt(s, USBSTS_PCD); + ehci_raise_irq(s, USBSTS_PCD); + ehci_commit_irq(s); } static void ehci_detach(USBPort *port) @@ -878,7 +891,8 @@ static void ehci_detach(USBPort *port) *portsc &= ~(PORTSC_CONNECT|PORTSC_PED); *portsc |= PORTSC_CSC; - ehci_set_interrupt(s, USBSTS_PCD); + ehci_raise_irq(s, USBSTS_PCD); + ehci_commit_irq(s); } static void ehci_child_detach(USBPort *port, USBDevice *child) @@ -997,6 +1011,8 @@ static void ehci_reset(void *opaque) s->usbcmd = NB_MAXINTRATE << USBCMD_ITC_SH; s->usbsts = USBSTS_HALT; + s->usbsts_pending = 0; + s->usbsts_frindex = 0; s->astate = EST_INACTIVE; s->pstate = EST_INACTIVE; @@ -1188,7 +1204,7 @@ static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val) val &= USBSTS_RO_MASK; // bits 6 through 31 are RO ehci_clear_usbsts(s, val); // bits 0 through 5 are R/WC val = s->usbsts; - ehci_set_interrupt(s, 0); + ehci_update_irq(s); break; case USBINTR: @@ -1419,18 +1435,18 @@ static void ehci_execute_complete(EHCIQueue *q) case USB_RET_NODEV: q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_XACTERR); set_field(&q->qh.token, 0, QTD_TOKEN_CERR); - ehci_record_interrupt(q->ehci, USBSTS_ERRINT); + ehci_raise_irq(q->ehci, USBSTS_ERRINT); break; case USB_RET_STALL: q->qh.token |= QTD_TOKEN_HALT; - ehci_record_interrupt(q->ehci, USBSTS_ERRINT); + ehci_raise_irq(q->ehci, USBSTS_ERRINT); break; case USB_RET_NAK: set_field(&q->qh.altnext_qtd, 0, QH_ALTNEXT_NAKCNT); return; /* We're not done yet with this transaction */ case USB_RET_BABBLE: q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_BABBLE); - ehci_record_interrupt(q->ehci, USBSTS_ERRINT); + ehci_raise_irq(q->ehci, USBSTS_ERRINT); break; default: /* should not be triggerable */ @@ -1441,7 +1457,7 @@ static void ehci_execute_complete(EHCIQueue *q) } else if ((p->usb_status > p->tbytes) && (p->pid == USB_TOKEN_IN)) { p->usb_status = USB_RET_BABBLE; q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_BABBLE); - ehci_record_interrupt(q->ehci, USBSTS_ERRINT); + ehci_raise_irq(q->ehci, USBSTS_ERRINT); } else { // TODO check 4.12 for splits @@ -1462,7 +1478,7 @@ static void ehci_execute_complete(EHCIQueue *q) q->qh.token &= ~QTD_TOKEN_ACTIVE; if (q->qh.token & QTD_TOKEN_IOC) { - ehci_record_interrupt(q->ehci, USBSTS_INT); + ehci_raise_irq(q->ehci, USBSTS_INT); } } @@ -1597,12 +1613,12 @@ static int ehci_process_itd(EHCIState *ehci, /* 3.3.2: XACTERR is only allowed on IN transactions */ if (dir) { itd->transact[i] |= ITD_XACT_XACTERR; - ehci_record_interrupt(ehci, USBSTS_ERRINT); + ehci_raise_irq(ehci, USBSTS_ERRINT); } break; case USB_RET_BABBLE: itd->transact[i] |= ITD_XACT_BABBLE; - ehci_record_interrupt(ehci, USBSTS_ERRINT); + ehci_raise_irq(ehci, USBSTS_ERRINT); break; case USB_RET_NAK: /* no data for us, so do a zero-length transfer */ @@ -1620,7 +1636,7 @@ static int ehci_process_itd(EHCIState *ehci, } } if (itd->transact[i] & ITD_XACT_IOC) { - ehci_record_interrupt(ehci, USBSTS_INT); + ehci_raise_irq(ehci, USBSTS_INT); } itd->transact[i] &= ~ITD_XACT_ACTIVE; } @@ -2208,8 +2224,6 @@ static void ehci_advance_state(EHCIState *ehci, int async) } } while (again); - - ehci_commit_interrupt(ehci); } static void ehci_advance_async_state(EHCIState *ehci) @@ -2255,7 +2269,7 @@ static void ehci_advance_async_state(EHCIState *ehci) ehci_queues_tag_unused_async(ehci); DPRINTF("ASYNC: doorbell request acknowledged\n"); ehci->usbcmd &= ~USBCMD_IAAD; - ehci_set_interrupt(ehci, USBSTS_IAA); + ehci_raise_irq(ehci, USBSTS_IAA); } break; @@ -2328,12 +2342,17 @@ static void ehci_update_frindex(EHCIState *ehci, int frames) ehci->frindex += 8; if (ehci->frindex == 0x00002000) { - ehci_set_interrupt(ehci, USBSTS_FLR); + ehci_raise_irq(ehci, USBSTS_FLR); } if (ehci->frindex == 0x00004000) { - ehci_set_interrupt(ehci, USBSTS_FLR); + ehci_raise_irq(ehci, USBSTS_FLR); ehci->frindex = 0; + if (ehci->usbsts_frindex > 0x00004000) { + ehci->usbsts_frindex -= 0x00004000; + } else { + ehci->usbsts_frindex = 0; + } } } } @@ -2341,7 +2360,7 @@ static void ehci_update_frindex(EHCIState *ehci, int frames) static void ehci_frame_timer(void *opaque) { EHCIState *ehci = opaque; - int schedules = 0; + int need_timer = 0; int64_t expire_time, t_now; uint64_t ns_elapsed; int frames, skipped_frames; @@ -2352,8 +2371,8 @@ static void ehci_frame_timer(void *opaque) frames = ns_elapsed / FRAME_TIMER_NS; if (ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE) { - schedules++; - expire_time = t_now + (get_ticks_per_sec() / FRAME_TIMER_FREQ); + need_timer++; + ehci->async_stepdown = 0; if (frames > ehci->maxframes) { skipped_frames = frames - ehci->maxframes; @@ -2372,8 +2391,6 @@ static void ehci_frame_timer(void *opaque) if (ehci->async_stepdown < ehci->maxframes / 2) { ehci->async_stepdown++; } - expire_time = t_now + (get_ticks_per_sec() - * ehci->async_stepdown / FRAME_TIMER_FREQ); ehci_update_frindex(ehci, frames); ehci->last_run_ns += FRAME_TIMER_NS * frames; } @@ -2382,11 +2399,19 @@ static void ehci_frame_timer(void *opaque) * called */ if (ehci_async_enabled(ehci) || ehci->astate != EST_INACTIVE) { - schedules++; - qemu_bh_schedule(ehci->async_bh); + need_timer++; + ehci_advance_async_state(ehci); + } + + ehci_commit_irq(ehci); + if (ehci->usbsts_pending) { + need_timer++; + ehci->async_stepdown = 0; } - if (schedules) { + if (need_timer) { + expire_time = t_now + (get_ticks_per_sec() + * (ehci->async_stepdown+1) / FRAME_TIMER_FREQ); qemu_mod_timer(ehci->frame_timer, expire_time); } } diff --git a/hw/usb/hcd-uhci.c b/hw/usb/hcd-uhci.c index 8f652d2f4a..2aac8a2505 100644 --- a/hw/usb/hcd-uhci.c +++ b/hw/usb/hcd-uhci.c @@ -388,11 +388,23 @@ static const VMStateDescription vmstate_uhci_port = { } }; +static int uhci_post_load(void *opaque, int version_id) +{ + UHCIState *s = opaque; + + if (version_id < 2) { + s->expire_time = qemu_get_clock_ns(vm_clock) + + (get_ticks_per_sec() / FRAME_TIMER_FREQ); + } + return 0; +} + static const VMStateDescription vmstate_uhci = { .name = "uhci", .version_id = 2, .minimum_version_id = 1, .minimum_version_id_old = 1, + .post_load = uhci_post_load, .fields = (VMStateField []) { VMSTATE_PCI_DEVICE(dev, UHCIState), VMSTATE_UINT8_EQUAL(num_ports_vmstate, UHCIState), diff --git a/hw/vga.c b/hw/vga.c index acb3f7d924..f82ced8e66 100644 --- a/hw/vga.c +++ b/hw/vga.c @@ -38,6 +38,9 @@ //#define DEBUG_BOCHS_VBE +/* 16 state changes per vertical frame @60 Hz */ +#define VGA_TEXT_CURSOR_PERIOD_MS (1000 * 2 * 16 / 60) + /* * Video Graphics Array (VGA) * @@ -1300,6 +1303,7 @@ static void vga_draw_text(VGACommonState *s, int full_update) uint32_t *ch_attr_ptr; vga_draw_glyph8_func *vga_draw_glyph8; vga_draw_glyph9_func *vga_draw_glyph9; + int64_t now = qemu_get_clock_ms(vm_clock); /* compute font data address (in plane 2) */ v = s->sr[VGA_SEQ_CHARACTER_MAP]; @@ -1370,6 +1374,10 @@ static void vga_draw_text(VGACommonState *s, int full_update) s->cursor_end = s->cr[VGA_CRTC_CURSOR_END]; } cursor_ptr = s->vram_ptr + (s->start_addr + cursor_offset) * 4; + if (now >= s->cursor_blink_time) { + s->cursor_blink_time = now + VGA_TEXT_CURSOR_PERIOD_MS / 2; + s->cursor_visible_phase = !s->cursor_visible_phase; + } depth_index = get_depth_index(s->ds); if (cw == 16) @@ -1390,7 +1398,7 @@ static void vga_draw_text(VGACommonState *s, int full_update) cx_max = -1; for(cx = 0; cx < width; cx++) { ch_attr = *(uint16_t *)src; - if (full_update || ch_attr != *ch_attr_ptr) { + if (full_update || ch_attr != *ch_attr_ptr || src == cursor_ptr) { if (cx < cx_min) cx_min = cx; if (cx > cx_max) @@ -1420,7 +1428,8 @@ static void vga_draw_text(VGACommonState *s, int full_update) font_ptr, cheight, fgcol, bgcol, dup9); } if (src == cursor_ptr && - !(s->cr[VGA_CRTC_CURSOR_START] & 0x20)) { + !(s->cr[VGA_CRTC_CURSOR_START] & 0x20) && + s->cursor_visible_phase) { int line_start, line_last, h; /* draw the cursor */ line_start = s->cr[VGA_CRTC_CURSOR_START] & 0x1f; @@ -1884,6 +1893,7 @@ static void vga_update_display(void *opaque) } if (graphic_mode != s->graphic_mode) { s->graphic_mode = graphic_mode; + s->cursor_blink_time = qemu_get_clock_ms(vm_clock); full_update = 1; } switch(graphic_mode) { diff --git a/hw/vga_int.h b/hw/vga_int.h index 3b38764a38..8938093682 100644 --- a/hw/vga_int.h +++ b/hw/vga_int.h @@ -156,6 +156,8 @@ typedef struct VGACommonState { uint32_t last_scr_width, last_scr_height; /* in pixels */ uint32_t last_depth; /* in bits */ uint8_t cursor_start, cursor_end; + bool cursor_visible_phase; + int64_t cursor_blink_time; uint32_t cursor_offset; unsigned int (*rgb_to_pixel)(unsigned int r, unsigned int g, unsigned b); |