summary refs log tree commit diff stats
path: root/hw
diff options
context:
space:
mode:
Diffstat (limited to 'hw')
-rw-r--r--hw/esp.c574
-rw-r--r--hw/imx_avic.c2
-rw-r--r--hw/megasas.c2
-rw-r--r--hw/omap.h8
-rw-r--r--hw/pci-stub.c15
-rw-r--r--hw/pci_ids.h1
-rw-r--r--hw/qdev-properties.c2
-rw-r--r--hw/qdev.h2
-rw-r--r--hw/scsi-bus.c5
-rw-r--r--hw/scsi.h1
-rw-r--r--hw/sh_serial.c6
-rw-r--r--hw/usb/Makefile.objs1
-rw-r--r--hw/usb/dev-uas.c779
-rw-r--r--hw/usb/hcd-ehci.c91
-rw-r--r--hw/usb/hcd-uhci.c12
-rw-r--r--hw/vga.c14
-rw-r--r--hw/vga_int.h2
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);