diff options
| -rw-r--r-- | Makefile.objs | 1 | ||||
| -rw-r--r-- | default-configs/pci.mak | 1 | ||||
| -rw-r--r-- | docs/usb2.txt | 38 | ||||
| -rw-r--r-- | hw/bt-hid.c | 6 | ||||
| -rw-r--r-- | hw/esp.c | 119 | ||||
| -rw-r--r-- | hw/lsi53c895a.c | 200 | ||||
| -rw-r--r-- | hw/pci_ids.h | 1 | ||||
| -rw-r--r-- | hw/scsi-bus.c | 220 | ||||
| -rw-r--r-- | hw/scsi-disk.c | 271 | ||||
| -rw-r--r-- | hw/scsi-generic.c | 223 | ||||
| -rw-r--r-- | hw/scsi.h | 91 | ||||
| -rw-r--r-- | hw/spapr_vscsi.c | 187 | ||||
| -rw-r--r-- | hw/usb-bt.c | 6 | ||||
| -rw-r--r-- | hw/usb-ccid.c | 4 | ||||
| -rw-r--r-- | hw/usb-desc.c | 56 | ||||
| -rw-r--r-- | hw/usb-desc.h | 24 | ||||
| -rw-r--r-- | hw/usb-ehci.c | 2037 | ||||
| -rw-r--r-- | hw/usb-hid.c | 9 | ||||
| -rw-r--r-- | hw/usb-hub.c | 9 | ||||
| -rw-r--r-- | hw/usb-msd.c | 140 | ||||
| -rw-r--r-- | hw/usb-musb.c | 2 | ||||
| -rw-r--r-- | hw/usb-net.c | 6 | ||||
| -rw-r--r-- | hw/usb-ohci.c | 4 | ||||
| -rw-r--r-- | hw/usb-serial.c | 7 | ||||
| -rw-r--r-- | hw/usb-uhci.c | 6 | ||||
| -rw-r--r-- | hw/usb-wacom.c | 7 | ||||
| -rw-r--r-- | hw/usb.c | 101 | ||||
| -rw-r--r-- | hw/usb.h | 40 | ||||
| -rw-r--r-- | hw/virtio-console.c | 47 | ||||
| -rw-r--r-- | hw/virtio-serial-bus.c | 83 | ||||
| -rw-r--r-- | hw/virtio-serial.h | 11 | ||||
| -rw-r--r-- | trace-events | 8 | ||||
| -rw-r--r-- | usb-bsd.c | 1 | ||||
| -rw-r--r-- | usb-linux.c | 444 |
34 files changed, 3379 insertions, 1031 deletions
diff --git a/Makefile.objs b/Makefile.objs index 4478c61e0c..90838f6a0d 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -193,6 +193,7 @@ hw-obj-$(CONFIG_PCSPK) += pcspk.o hw-obj-$(CONFIG_PCKBD) += pckbd.o hw-obj-$(CONFIG_USB_UHCI) += usb-uhci.o hw-obj-$(CONFIG_USB_OHCI) += usb-ohci.o +hw-obj-$(CONFIG_USB_EHCI) += usb-ehci.o hw-obj-$(CONFIG_FDC) += fdc.o hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o hw-obj-$(CONFIG_APM) += pm_smbus.o apm.o diff --git a/default-configs/pci.mak b/default-configs/pci.mak index 0471efbcd8..22bd3502d4 100644 --- a/default-configs/pci.mak +++ b/default-configs/pci.mak @@ -3,6 +3,7 @@ CONFIG_VIRTIO_PCI=y CONFIG_VIRTIO=y CONFIG_USB_UHCI=y CONFIG_USB_OHCI=y +CONFIG_USB_EHCI=y CONFIG_NE2000_PCI=y CONFIG_EEPRO100_PCI=y CONFIG_PCNET_PCI=y diff --git a/docs/usb2.txt b/docs/usb2.txt new file mode 100644 index 0000000000..b283c138e0 --- /dev/null +++ b/docs/usb2.txt @@ -0,0 +1,38 @@ + +USB 2.0 Quick Start +=================== + +The QEMU EHCI Adapter does *not* support companion controllers. That +implies there are two completely separate USB busses: One USB 1.1 bus +driven by the UHCI controller and one USB 2.0 bus driven by the EHCI +controller. Devices must be attached to the correct controller +manually. + +The '-usb' switch will make qemu create the UHCI controller as part of +the PIIX3 chipset. The USB 1.1 bus will carry the name "usb.0". + +You can use the standard -device switch to add a EHCI controller to +your virtual machine. It is strongly recommended to specify an ID for +the controller so the USB 2.0 bus gets a individual name, for example +'-device usb-ehci,id=ehci". This will give you a USB 2.0 bus named +"ehci.0". + +I strongly recomment to also use -device to attach usb devices because +you can specify the bus they should be attached to this way. Here is +a complete example: + + qemu -M pc ${otheroptions} \ + -drive if=none,id=usbstick,file=/path/to/image \ + -usb \ + -device usb-ehci,id=ehci \ + -device usb-tablet,bus=usb.0 \ + -device usb-storage,bus=ehci.0,drive=usbstick + +This attaches a usb tablet to the UHCI adapter and a usb mass storage +device to the EHCI adapter. + +enjoy, + Gerd + +-- +Gerd Hoffmann <kraxel@redhat.com> diff --git a/hw/bt-hid.c b/hw/bt-hid.c index abdfd35e86..09120af074 100644 --- a/hw/bt-hid.c +++ b/hw/bt-hid.c @@ -323,7 +323,7 @@ static void bt_hid_control_transaction(struct bt_hid_device_s *s, break; } s->proto = parameter; - s->usbdev->info->handle_control(s->usbdev, SET_PROTOCOL, s->proto, 0, 0, + s->usbdev->info->handle_control(s->usbdev, NULL, SET_PROTOCOL, s->proto, 0, 0, NULL); ret = BT_HS_SUCCESSFUL; break; @@ -333,7 +333,7 @@ static void bt_hid_control_transaction(struct bt_hid_device_s *s, ret = BT_HS_ERR_INVALID_PARAMETER; break; } - s->usbdev->info->handle_control(s->usbdev, GET_IDLE, 0, 0, 1, + s->usbdev->info->handle_control(s->usbdev, NULL, GET_IDLE, 0, 0, 1, s->control->sdu_out(s->control, 1)); s->control->sdu_submit(s->control); break; @@ -346,7 +346,7 @@ static void bt_hid_control_transaction(struct bt_hid_device_s *s, /* We don't need to know about the Idle Rate here really, * so just pass it on to the device. */ - ret = s->usbdev->info->handle_control(s->usbdev, + ret = s->usbdev->info->handle_control(s->usbdev, NULL, SET_IDLE, data[1], 0, 0, NULL) ? BT_HS_SUCCESSFUL : BT_HS_ERR_INVALID_PARAMETER; /* XXX: Does this generate a handshake? */ diff --git a/hw/esp.c b/hw/esp.c index fa9d2a2706..6d3f5d239b 100644 --- a/hw/esp.c +++ b/hw/esp.c @@ -61,10 +61,11 @@ struct ESPState { int32_t ti_size; uint32_t ti_rptr, ti_wptr; uint8_t ti_buf[TI_BUFSZ]; - uint32_t sense; + uint32_t status; uint32_t dma; SCSIBus bus; SCSIDevice *current_dev; + SCSIRequest *current_req; uint8_t cmdbuf[TI_BUFSZ]; uint32_t cmdlen; uint32_t do_cmd; @@ -187,6 +188,17 @@ 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); + + if (req == s->current_req) { + scsi_req_unref(s->current_req); + s->current_req = NULL; + s->current_dev = NULL; + } +} + static uint32_t get_cmd(ESPState *s, uint8_t *buf) { uint32_t dmalen; @@ -209,7 +221,7 @@ static uint32_t get_cmd(ESPState *s, uint8_t *buf) if (s->current_dev) { /* Started a new command before the old one finished. Cancel it. */ - s->current_dev->info->cancel_io(s->current_dev, 0); + scsi_req_cancel(s->current_req); s->async_len = 0; } @@ -232,7 +244,8 @@ static void do_busid_cmd(ESPState *s, uint8_t *buf, uint8_t busid) DPRINTF("do_busid_cmd: busid 0x%x\n", busid); lun = busid & 7; - datalen = s->current_dev->info->send_command(s->current_dev, 0, buf, lun); + s->current_req = scsi_req_new(s->current_dev, 0, lun); + datalen = scsi_req_enqueue(s->current_req, buf); s->ti_size = datalen; if (datalen != 0) { s->rregs[ESP_RSTAT] = STAT_TC; @@ -240,11 +253,10 @@ static void do_busid_cmd(ESPState *s, uint8_t *buf, uint8_t busid) s->dma_counter = 0; if (datalen > 0) { s->rregs[ESP_RSTAT] |= STAT_DI; - s->current_dev->info->read_data(s->current_dev, 0); } else { s->rregs[ESP_RSTAT] |= STAT_DO; - s->current_dev->info->write_data(s->current_dev, 0); } + scsi_req_continue(s->current_req); } s->rregs[ESP_RINTR] = INTR_BS | INTR_FC; s->rregs[ESP_RSEQ] = SEQ_CD; @@ -306,8 +318,8 @@ static void handle_satn_stop(ESPState *s) static void write_response(ESPState *s) { - DPRINTF("Transfer status (sense=%d)\n", s->sense); - s->ti_buf[0] = s->sense; + DPRINTF("Transfer status (status=%d)\n", s->status); + s->ti_buf[0] = s->status; s->ti_buf[1] = 0; if (s->dma) { s->dma_memory_write(s->dma_opaque, s->ti_buf, 2); @@ -370,53 +382,56 @@ static void esp_do_dma(ESPState *s) else s->ti_size -= len; if (s->async_len == 0) { - if (to_device) { - // ti_size is negative - s->current_dev->info->write_data(s->current_dev, 0); - } else { - s->current_dev->info->read_data(s->current_dev, 0); - /* If there is still data to be read from the device then - complete the DMA operation immediately. Otherwise defer - until the scsi layer has completed. */ - if (s->dma_left == 0 && s->ti_size > 0) { - esp_dma_done(s); - } + scsi_req_continue(s->current_req); + /* If there is still data to be read from the device then + complete the DMA operation immediately. Otherwise defer + until the scsi layer has completed. */ + if (to_device || s->dma_left != 0 || s->ti_size == 0) { + return; } - } else { - /* Partially filled a scsi buffer. Complete immediately. */ - esp_dma_done(s); } + + /* Partially filled a scsi buffer. Complete immediately. */ + esp_dma_done(s); } -static void esp_command_complete(SCSIBus *bus, int reason, uint32_t tag, - uint32_t arg) +static void esp_command_complete(SCSIRequest *req, uint32_t status) { - ESPState *s = DO_UPCAST(ESPState, busdev.qdev, bus->qbus.parent); + ESPState *s = DO_UPCAST(ESPState, busdev.qdev, req->bus->qbus.parent); - if (reason == SCSI_REASON_DONE) { - DPRINTF("SCSI Command complete\n"); - if (s->ti_size != 0) - DPRINTF("SCSI command completed unexpectedly\n"); - s->ti_size = 0; - s->dma_left = 0; - s->async_len = 0; - if (arg) - DPRINTF("Command failed\n"); - s->sense = arg; - s->rregs[ESP_RSTAT] = STAT_ST; - esp_dma_done(s); + DPRINTF("SCSI Command complete\n"); + if (s->ti_size != 0) { + DPRINTF("SCSI command completed unexpectedly\n"); + } + s->ti_size = 0; + s->dma_left = 0; + s->async_len = 0; + if (status) { + DPRINTF("Command failed\n"); + } + s->status = status; + s->rregs[ESP_RSTAT] = STAT_ST; + esp_dma_done(s); + if (s->current_req) { + scsi_req_unref(s->current_req); + s->current_req = NULL; s->current_dev = NULL; - } else { - DPRINTF("transfer %d/%d\n", s->dma_left, s->ti_size); - s->async_len = arg; - s->async_buf = s->current_dev->info->get_buf(s->current_dev, 0); - if (s->dma_left) { - esp_do_dma(s); - } else if (s->dma_counter != 0 && s->ti_size <= 0) { - /* If this was the last part of a DMA transfer then the - completion interrupt is deferred to here. */ - esp_dma_done(s); - } + } +} + +static void esp_transfer_data(SCSIRequest *req, uint32_t len) +{ + ESPState *s = DO_UPCAST(ESPState, busdev.qdev, req->bus->qbus.parent); + + DPRINTF("transfer %d/%d\n", s->dma_left, s->ti_size); + s->async_len = len; + s->async_buf = scsi_req_get_buf(req); + if (s->dma_left) { + esp_do_dma(s); + } else if (s->dma_counter != 0 && s->ti_size <= 0) { + /* If this was the last part of a DMA transfer then the + completion interrupt is deferred to here. */ + esp_dma_done(s); } } @@ -678,7 +693,7 @@ static const VMStateDescription vmstate_esp = { VMSTATE_UINT32(ti_rptr, ESPState), VMSTATE_UINT32(ti_wptr, ESPState), VMSTATE_BUFFER(ti_buf, ESPState), - VMSTATE_UINT32(sense, ESPState), + VMSTATE_UINT32(status, ESPState), VMSTATE_UINT32(dma, ESPState), VMSTATE_BUFFER(cmdbuf, ESPState), VMSTATE_UINT32(cmdlen, ESPState), @@ -714,6 +729,12 @@ void esp_init(target_phys_addr_t espaddr, int it_shift, *dma_enable = qdev_get_gpio_in(dev, 1); } +static const struct SCSIBusOps esp_scsi_ops = { + .transfer_data = esp_transfer_data, + .complete = esp_command_complete, + .cancel = esp_request_cancelled +}; + static int esp_init1(SysBusDevice *dev) { ESPState *s = FROM_SYSBUS(ESPState, dev); @@ -728,7 +749,7 @@ static int esp_init1(SysBusDevice *dev) qdev_init_gpio_in(&dev->qdev, esp_gpio_demux, 2); - scsi_bus_new(&s->bus, &dev->qdev, 0, ESP_MAX_DEVS, esp_command_complete); + scsi_bus_new(&s->bus, &dev->qdev, 0, ESP_MAX_DEVS, &esp_scsi_ops); return scsi_bus_legacy_handle_cmdline(&s->bus); } diff --git a/hw/lsi53c895a.c b/hw/lsi53c895a.c index 2ce38a97ae..83084b6b6a 100644 --- a/hw/lsi53c895a.c +++ b/hw/lsi53c895a.c @@ -174,6 +174,7 @@ do { fprintf(stderr, "lsi_scsi: error: " fmt , ## __VA_ARGS__);} while (0) #define LSI_TAG_VALID (1 << 16) typedef struct lsi_request { + SCSIRequest *req; uint32_t tag; uint32_t dma_len; uint8_t *dma_buf; @@ -567,11 +568,9 @@ static void lsi_do_dma(LSIState *s, int out) s->csbc += count; s->dnad += count; s->dbc -= count; - - if (s->current->dma_buf == NULL) { - s->current->dma_buf = dev->info->get_buf(dev, s->current->tag); + if (s->current->dma_buf == NULL) { + s->current->dma_buf = scsi_req_get_buf(s->current->req); } - /* ??? Set SFBR to first data byte. */ if (out) { cpu_physical_memory_read(addr, s->current->dma_buf, count); @@ -581,13 +580,7 @@ static void lsi_do_dma(LSIState *s, int out) s->current->dma_len -= count; if (s->current->dma_len == 0) { s->current->dma_buf = NULL; - if (out) { - /* Write the data. */ - dev->info->write_data(dev, s->current->tag); - } else { - /* Request any remaining data. */ - dev->info->read_data(dev, s->current->tag); - } + scsi_req_continue(s->current->req); } else { s->current->dma_buf += count; lsi_resume_script(s); @@ -652,82 +645,123 @@ static void lsi_reselect(LSIState *s, lsi_request *p) } } -/* Record that data is available for a queued command. Returns zero if - the device was reselected, nonzero if the IO is deferred. */ -static int lsi_queue_tag(LSIState *s, uint32_t tag, uint32_t arg) +static lsi_request *lsi_find_by_tag(LSIState *s, uint32_t tag) { lsi_request *p; QTAILQ_FOREACH(p, &s->queue, next) { if (p->tag == tag) { - if (p->pending) { - BADF("Multiple IO pending for tag %d\n", tag); - } - p->pending = arg; - /* Reselect if waiting for it, or if reselection triggers an IRQ - and the bus is free. - Since no interrupt stacking is implemented in the emulation, it - is also required that there are no pending interrupts waiting - for service from the device driver. */ - if (s->waiting == 1 || - (lsi_irq_on_rsl(s) && !(s->scntl1 & LSI_SCNTL1_CON) && - !(s->istat0 & (LSI_ISTAT0_SIP | LSI_ISTAT0_DIP)))) { - /* Reselect device. */ - lsi_reselect(s, p); - return 0; - } else { - DPRINTF("Queueing IO tag=0x%x\n", tag); - p->pending = arg; - return 1; - } + return p; } } - BADF("IO with unknown tag %d\n", tag); - return 1; + + return NULL; +} + +static void lsi_request_cancelled(SCSIRequest *req) +{ + LSIState *s = DO_UPCAST(LSIState, dev.qdev, req->bus->qbus.parent); + lsi_request *p; + + if (s->current && req == s->current->req) { + scsi_req_unref(req); + qemu_free(s->current); + s->current = NULL; + return; + } + + p = lsi_find_by_tag(s, req->tag); + if (p) { + QTAILQ_REMOVE(&s->queue, p, next); + scsi_req_unref(req); + qemu_free(p); + } } -/* Callback to indicate that the SCSI layer has completed a transfer. */ -static void lsi_command_complete(SCSIBus *bus, int reason, uint32_t tag, - uint32_t arg) +/* Record that data is available for a queued command. Returns zero if + the device was reselected, nonzero if the IO is deferred. */ +static int lsi_queue_tag(LSIState *s, uint32_t tag, uint32_t len) { - LSIState *s = DO_UPCAST(LSIState, dev.qdev, bus->qbus.parent); + lsi_request *p; + + p = lsi_find_by_tag(s, tag); + if (!p) { + BADF("IO with unknown tag %d\n", tag); + return 1; + } + + if (p->pending) { + BADF("Multiple IO pending for tag %d\n", tag); + } + p->pending = len; + /* Reselect if waiting for it, or if reselection triggers an IRQ + and the bus is free. + Since no interrupt stacking is implemented in the emulation, it + is also required that there are no pending interrupts waiting + for service from the device driver. */ + if (s->waiting == 1 || + (lsi_irq_on_rsl(s) && !(s->scntl1 & LSI_SCNTL1_CON) && + !(s->istat0 & (LSI_ISTAT0_SIP | LSI_ISTAT0_DIP)))) { + /* Reselect device. */ + lsi_reselect(s, p); + return 0; + } else { + DPRINTF("Queueing IO tag=0x%x\n", tag); + p->pending = len; + return 1; + } +} + + /* Callback to indicate that the SCSI layer has completed a command. */ +static void lsi_command_complete(SCSIRequest *req, uint32_t status) +{ + LSIState *s = DO_UPCAST(LSIState, dev.qdev, req->bus->qbus.parent); int out; out = (s->sstat1 & PHASE_MASK) == PHASE_DO; - if (reason == SCSI_REASON_DONE) { - DPRINTF("Command complete status=%d\n", (int)arg); - s->status = arg; - s->command_complete = 2; - if (s->waiting && s->dbc != 0) { - /* Raise phase mismatch for short transfers. */ - lsi_bad_phase(s, out, PHASE_ST); - } else { - lsi_set_phase(s, PHASE_ST); - } + DPRINTF("Command complete status=%d\n", (int)status); + s->status = status; + s->command_complete = 2; + if (s->waiting && s->dbc != 0) { + /* Raise phase mismatch for short transfers. */ + lsi_bad_phase(s, out, PHASE_ST); + } else { + lsi_set_phase(s, PHASE_ST); + } + if (s->current && req == s->current->req) { + scsi_req_unref(s->current->req); qemu_free(s->current); s->current = NULL; - - lsi_resume_script(s); - return; } + lsi_resume_script(s); +} + + /* Callback to indicate that the SCSI layer has completed a transfer. */ +static void lsi_transfer_data(SCSIRequest *req, uint32_t len) +{ + LSIState *s = DO_UPCAST(LSIState, dev.qdev, req->bus->qbus.parent); + int out; - if (s->waiting == 1 || !s->current || tag != s->current->tag || + if (s->waiting == 1 || !s->current || req->tag != s->current->tag || (lsi_irq_on_rsl(s) && !(s->scntl1 & LSI_SCNTL1_CON))) { - if (lsi_queue_tag(s, tag, arg)) + if (lsi_queue_tag(s, req->tag, len)) { return; + } } + out = (s->sstat1 & PHASE_MASK) == PHASE_DO; + /* host adapter (re)connected */ - DPRINTF("Data ready tag=0x%x len=%d\n", tag, arg); - s->current->dma_len = arg; + DPRINTF("Data ready tag=0x%x len=%d\n", req->tag, len); + s->current->dma_len = len; s->command_complete = 1; - if (!s->waiting) - return; - if (s->waiting == 1 || s->dbc == 0) { - lsi_resume_script(s); - } else { - lsi_do_dma(s, out); + if (s->waiting) { + if (s->waiting == 1 || s->dbc == 0) { + lsi_resume_script(s); + } else { + lsi_do_dma(s, out); + } } } @@ -755,16 +789,17 @@ static void lsi_do_command(LSIState *s) assert(s->current == NULL); s->current = qemu_mallocz(sizeof(lsi_request)); s->current->tag = s->select_tag; + s->current->req = scsi_req_new(dev, s->current->tag, s->current_lun); - n = dev->info->send_command(dev, s->current->tag, buf, s->current_lun); - if (n > 0) { - lsi_set_phase(s, PHASE_DI); - dev->info->read_data(dev, s->current->tag); - } else if (n < 0) { - lsi_set_phase(s, PHASE_DO); - dev->info->write_data(dev, s->current->tag); + n = scsi_req_enqueue(s->current->req, buf); + if (n) { + if (n > 0) { + lsi_set_phase(s, PHASE_DI); + } else if (n < 0) { + lsi_set_phase(s, PHASE_DO); + } + scsi_req_continue(s->current->req); } - if (!s->command_complete) { if (n) { /* Command did not complete immediately so disconnect. */ @@ -855,13 +890,15 @@ static void lsi_do_msgout(LSIState *s) int len; uint32_t current_tag; SCSIDevice *current_dev; - lsi_request *p, *p_next; + lsi_request *current_req, *p, *p_next; int id; if (s->current) { current_tag = s->current->tag; + current_req = s->current; } else { current_tag = s->select_tag; + current_req = lsi_find_by_tag(s, current_tag); } id = (current_tag >> 8) & 0xf; current_dev = s->bus.devs[id]; @@ -913,7 +950,9 @@ static void lsi_do_msgout(LSIState *s) case 0x0d: /* The ABORT TAG message clears the current I/O process only. */ DPRINTF("MSG: ABORT TAG tag=0x%x\n", current_tag); - current_dev->info->cancel_io(current_dev, current_tag); + if (current_req) { + scsi_req_cancel(current_req->req); + } lsi_disconnect(s); break; case 0x06: @@ -936,7 +975,9 @@ static void lsi_do_msgout(LSIState *s) } /* clear the current I/O process */ - current_dev->info->cancel_io(current_dev, current_tag); + if (s->current) { + scsi_req_cancel(s->current->req); + } /* As the current implemented devices scsi_disk and scsi_generic only support one LUN, we don't need to keep track of LUNs. @@ -948,8 +989,7 @@ static void lsi_do_msgout(LSIState *s) id = current_tag & 0x0000ff00; QTAILQ_FOREACH_SAFE(p, &s->queue, next, p_next) { if ((p->tag & 0x0000ff00) == id) { - current_dev->info->cancel_io(current_dev, p->tag); - QTAILQ_REMOVE(&s->queue, p, next); + scsi_req_cancel(p->req); } } @@ -2205,6 +2245,12 @@ static int lsi_scsi_uninit(PCIDevice *d) return 0; } +static const struct SCSIBusOps lsi_scsi_ops = { + .transfer_data = lsi_transfer_data, + .complete = lsi_command_complete, + .cancel = lsi_request_cancelled +}; + static int lsi_scsi_init(PCIDevice *dev) { LSIState *s = DO_UPCAST(LSIState, dev, dev); @@ -2241,7 +2287,7 @@ static int lsi_scsi_init(PCIDevice *dev) PCI_BASE_ADDRESS_SPACE_MEMORY, lsi_ram_mapfunc); QTAILQ_INIT(&s->queue); - scsi_bus_new(&s->bus, &dev->qdev, 1, LSI_MAX_DEVS, lsi_command_complete); + scsi_bus_new(&s->bus, &dev->qdev, 1, LSI_MAX_DEVS, &lsi_scsi_ops); if (!dev->qdev.hotplugged) { return scsi_bus_legacy_handle_cmdline(&s->bus); } diff --git a/hw/pci_ids.h b/hw/pci_ids.h index ea3418cef2..d9457ed3f4 100644 --- a/hw/pci_ids.h +++ b/hw/pci_ids.h @@ -100,6 +100,7 @@ #define PCI_VENDOR_ID_INTEL 0x8086 #define PCI_DEVICE_ID_INTEL_82441 0x1237 #define PCI_DEVICE_ID_INTEL_82801AA_5 0x2415 +#define PCI_DEVICE_ID_INTEL_82801D 0x24CD #define PCI_DEVICE_ID_INTEL_ESB_9 0x25ab #define PCI_DEVICE_ID_INTEL_82371SB_0 0x7000 #define PCI_DEVICE_ID_INTEL_82371SB_1 0x7010 diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c index ceeb4ecb91..837f24e212 100644 --- a/hw/scsi-bus.c +++ b/hw/scsi-bus.c @@ -4,6 +4,7 @@ #include "scsi-defs.h" #include "qdev.h" #include "blockdev.h" +#include "trace.h" static char *scsibus_get_fw_dev_path(DeviceState *dev); @@ -20,13 +21,13 @@ static int next_scsi_bus; /* Create a scsi bus, and attach devices to it. */ void scsi_bus_new(SCSIBus *bus, DeviceState *host, int tcq, int ndev, - scsi_completionfn complete) + const SCSIBusOps *ops) { qbus_create_inplace(&bus->qbus, &scsi_bus_info, host, NULL); bus->busnr = next_scsi_bus++; bus->tcq = tcq; bus->ndev = ndev; - bus->complete = complete; + bus->ops = ops; bus->qbus.allow_hotplug = 1; } @@ -135,42 +136,60 @@ SCSIRequest *scsi_req_alloc(size_t size, SCSIDevice *d, uint32_t tag, uint32_t l SCSIRequest *req; req = qemu_mallocz(size); + req->refcount = 1; req->bus = scsi_bus_from_device(d); req->dev = d; req->tag = tag; req->lun = lun; req->status = -1; - req->enqueued = true; - QTAILQ_INSERT_TAIL(&d->requests, req, next); + trace_scsi_req_alloc(req->dev->id, req->lun, req->tag); return req; } -SCSIRequest *scsi_req_find(SCSIDevice *d, uint32_t tag) +SCSIRequest *scsi_req_new(SCSIDevice *d, uint32_t tag, uint32_t lun) { - SCSIRequest *req; + return d->info->alloc_req(d, tag, lun); +} - QTAILQ_FOREACH(req, &d->requests, next) { - if (req->tag == tag) { - return req; - } +uint8_t *scsi_req_get_buf(SCSIRequest *req) +{ + return req->dev->info->get_buf(req); +} + +int scsi_req_get_sense(SCSIRequest *req, uint8_t *buf, int len) +{ + if (req->dev->info->get_sense) { + return req->dev->info->get_sense(req, buf, len); + } else { + return 0; } - return NULL; +} + +int32_t scsi_req_enqueue(SCSIRequest *req, uint8_t *buf) +{ + int32_t rc; + + assert(!req->enqueued); + scsi_req_ref(req); + req->enqueued = true; + QTAILQ_INSERT_TAIL(&req->dev->requests, req, next); + + scsi_req_ref(req); + rc = req->dev->info->send_command(req, buf); + scsi_req_unref(req); + return rc; } static void scsi_req_dequeue(SCSIRequest *req) { + trace_scsi_req_dequeue(req->dev->id, req->lun, req->tag); if (req->enqueued) { QTAILQ_REMOVE(&req->dev->requests, req, next); req->enqueued = false; + scsi_req_unref(req); } } -void scsi_req_free(SCSIRequest *req) -{ - scsi_req_dequeue(req); - qemu_free(req); -} - static int scsi_req_length(SCSIRequest *req, uint8_t *cmd) { switch (cmd[0] >> 5) { @@ -195,6 +214,7 @@ static int scsi_req_length(SCSIRequest *req, uint8_t *cmd) req->cmd.len = 12; break; default: + trace_scsi_req_parse_bad(req->dev->id, req->lun, req->tag, cmd[0]); return -1; } @@ -392,9 +412,100 @@ int scsi_req_parse(SCSIRequest *req, uint8_t *buf) memcpy(req->cmd.buf, buf, req->cmd.len); scsi_req_xfer_mode(req); req->cmd.lba = scsi_req_lba(req); + trace_scsi_req_parsed(req->dev->id, req->lun, req->tag, buf[0], + req->cmd.mode, req->cmd.xfer, req->cmd.lba); return 0; } +/* + * Predefined sense codes + */ + +/* No sense data available */ +const struct SCSISense sense_code_NO_SENSE = { + .key = NO_SENSE , .asc = 0x00 , .ascq = 0x00 +}; + +/* LUN not ready, Manual intervention required */ +const struct SCSISense sense_code_LUN_NOT_READY = { + .key = NOT_READY, .asc = 0x04, .ascq = 0x03 +}; + +/* LUN not ready, Medium not present */ +const struct SCSISense sense_code_NO_MEDIUM = { + .key = NOT_READY, .asc = 0x3a, .ascq = 0x00 +}; + +/* Hardware error, internal target failure */ +const struct SCSISense sense_code_TARGET_FAILURE = { + .key = HARDWARE_ERROR, .asc = 0x44, .ascq = 0x00 +}; + +/* Illegal request, invalid command operation code */ +const struct SCSISense sense_code_INVALID_OPCODE = { + .key = ILLEGAL_REQUEST, .asc = 0x20, .ascq = 0x00 +}; + +/* Illegal request, LBA out of range */ +const struct SCSISense sense_code_LBA_OUT_OF_RANGE = { + .key = ILLEGAL_REQUEST, .asc = 0x21, .ascq = 0x00 +}; + +/* Illegal request, Invalid field in CDB */ +const struct SCSISense sense_code_INVALID_FIELD = { + .key = ILLEGAL_REQUEST, .asc = 0x24, .ascq = 0x00 +}; + +/* Illegal request, LUN not supported */ +const struct SCSISense sense_code_LUN_NOT_SUPPORTED = { + .key = ILLEGAL_REQUEST, .asc = 0x25, .ascq = 0x00 +}; + +/* Command aborted, I/O process terminated */ +const struct SCSISense sense_code_IO_ERROR = { + .key = ABORTED_COMMAND, .asc = 0x00, .ascq = 0x06 +}; + +/* Command aborted, I_T Nexus loss occurred */ +const struct SCSISense sense_code_I_T_NEXUS_LOSS = { + .key = ABORTED_COMMAND, .asc = 0x29, .ascq = 0x07 +}; + +/* Command aborted, Logical Unit failure */ +const struct SCSISense sense_code_LUN_FAILURE = { + .key = ABORTED_COMMAND, .asc = 0x3e, .ascq = 0x01 +}; + +/* + * scsi_build_sense + * + * Build a sense buffer + */ +int scsi_build_sense(SCSISense sense, uint8_t *buf, int len, int fixed) +{ + if (!fixed && len < 8) { + return 0; + } + + memset(buf, 0, len); + if (fixed) { + /* Return fixed format sense buffer */ + buf[0] = 0xf0; + buf[2] = sense.key; + buf[7] = 7; + buf[12] = sense.asc; + buf[13] = sense.ascq; + return MIN(len, 18); + } else { + /* Return descriptor format sense buffer */ + buf[0] = 0x72; + buf[1] = sense.key; + buf[2] = sense.asc; + buf[3] = sense.ascq; + return 8; + } +} + static const char *scsi_command_name(uint8_t cmd) { static const char *names[] = { @@ -489,6 +600,43 @@ static const char *scsi_command_name(uint8_t cmd) return names[cmd]; } +SCSIRequest *scsi_req_ref(SCSIRequest *req) +{ + req->refcount++; + return req; +} + +void scsi_req_unref(SCSIRequest *req) +{ + if (--req->refcount == 0) { + if (req->dev->info->free_req) { + req->dev->info->free_req(req); + } + qemu_free(req); + } +} + +/* Tell the device that we finished processing this chunk of I/O. It + will start the next chunk or complete the command. */ +void scsi_req_continue(SCSIRequest *req) +{ + trace_scsi_req_continue(req->dev->id, req->lun, req->tag); + if (req->cmd.mode == SCSI_XFER_TO_DEV) { + req->dev->info->write_data(req); + } else { + req->dev->info->read_data(req); + } +} + +/* Called by the devices when data is ready for the HBA. The HBA should + start a DMA operation to read or fill the device's data buffer. + Once it completes, calling scsi_req_continue will restart I/O. */ +void scsi_req_data(SCSIRequest *req, int len) +{ + trace_scsi_req_data(req->dev->id, req->lun, req->tag, len); + req->bus->ops->transfer_data(req, len); +} + void scsi_req_print(SCSIRequest *req) { FILE *fp = stderr; @@ -520,10 +668,42 @@ void scsi_req_print(SCSIRequest *req) void scsi_req_complete(SCSIRequest *req) { assert(req->status != -1); + scsi_req_ref(req); + scsi_req_dequeue(req); + req->bus->ops->complete(req, req->status); + scsi_req_unref(req); +} + +void scsi_req_cancel(SCSIRequest *req) +{ + if (req->dev && req->dev->info->cancel_io) { + req->dev->info->cancel_io(req); + } + scsi_req_ref(req); scsi_req_dequeue(req); - req->bus->complete(req->bus, SCSI_REASON_DONE, - req->tag, - req->status); + if (req->bus->ops->cancel) { + req->bus->ops->cancel(req); + } + scsi_req_unref(req); +} + +void scsi_req_abort(SCSIRequest *req, int status) +{ + req->status = status; + if (req->dev && req->dev->info->cancel_io) { + req->dev->info->cancel_io(req); + } + scsi_req_complete(req); +} + +void scsi_device_purge_requests(SCSIDevice *sdev) +{ + SCSIRequest *req; + + while (!QTAILQ_EMPTY(&sdev->requests)) { + req = QTAILQ_FIRST(&sdev->requests); + scsi_req_cancel(req); + } } static char *scsibus_get_fw_dev_path(DeviceState *dev) diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index 397b9d6b39..a8c7372d3e 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -49,14 +49,8 @@ do { fprintf(stderr, "scsi-disk: " fmt , ## __VA_ARGS__); } while (0) typedef struct SCSIDiskState SCSIDiskState; -typedef struct SCSISense { - uint8_t key; -} SCSISense; - typedef struct SCSIDiskReq { SCSIRequest req; - /* ??? We should probably keep track of whether the data transfer is - a read or a write. Currently we rely on the host getting it right. */ /* Both sector and sector_count are in terms of qemu 512 byte blocks. */ uint64_t sector; uint32_t sector_count; @@ -86,27 +80,24 @@ struct SCSIDiskState static int scsi_handle_rw_error(SCSIDiskReq *r, int error, int type); static int scsi_disk_emulate_command(SCSIDiskReq *r, uint8_t *outbuf); -static SCSIDiskReq *scsi_new_request(SCSIDiskState *s, uint32_t tag, +static SCSIRequest *scsi_new_request(SCSIDevice *d, uint32_t tag, uint32_t lun) { + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d); SCSIRequest *req; SCSIDiskReq *r; req = scsi_req_alloc(sizeof(SCSIDiskReq), &s->qdev, tag, lun); r = DO_UPCAST(SCSIDiskReq, req, req); r->iov.iov_base = qemu_blockalign(s->bs, SCSI_DMA_BUF_SIZE); - return r; + return req; } -static void scsi_remove_request(SCSIDiskReq *r) +static void scsi_free_request(SCSIRequest *req) { - qemu_vfree(r->iov.iov_base); - scsi_req_free(&r->req); -} + SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req); -static SCSIDiskReq *scsi_find_request(SCSIDiskState *s, uint32_t tag) -{ - return DO_UPCAST(SCSIDiskReq, req, scsi_req_find(&s->qdev, tag)); + qemu_vfree(r->iov.iov_base); } static void scsi_disk_clear_sense(SCSIDiskState *s) @@ -114,42 +105,33 @@ static void scsi_disk_clear_sense(SCSIDiskState *s) memset(&s->sense, 0, sizeof(s->sense)); } -static void scsi_disk_set_sense(SCSIDiskState *s, uint8_t key) -{ - s->sense.key = key; -} - -static void scsi_req_set_status(SCSIDiskReq *r, int status, int sense_code) +static void scsi_req_set_status(SCSIDiskReq *r, int status, SCSISense sense) { SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev); r->req.status = status; - scsi_disk_set_sense(s, sense_code); + s->sense = sense; } /* Helper function for command completion. */ -static void scsi_command_complete(SCSIDiskReq *r, int status, int sense) +static void scsi_command_complete(SCSIDiskReq *r, int status, SCSISense sense) { - DPRINTF("Command complete tag=0x%x status=%d sense=%d\n", - r->req.tag, status, sense); + DPRINTF("Command complete tag=0x%x status=%d sense=%d/%d/%d\n", + r->req.tag, status, sense.key, sense.asc, sense.ascq); scsi_req_set_status(r, status, sense); scsi_req_complete(&r->req); - scsi_remove_request(r); } /* Cancel a pending data transfer. */ -static void scsi_cancel_io(SCSIDevice *d, uint32_t tag) +static void scsi_cancel_io(SCSIRequest *req) { - SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d); - SCSIDiskReq *r; - DPRINTF("Cancel tag=0x%x\n", tag); - r = scsi_find_request(s, tag); - if (r) { - if (r->req.aiocb) - bdrv_aio_cancel(r->req.aiocb); - r->req.aiocb = NULL; - scsi_remove_request(r); + SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req); + + DPRINTF("Cancel tag=0x%x\n", req->tag); + if (r->req.aiocb) { + bdrv_aio_cancel(r->req.aiocb); } + r->req.aiocb = NULL; } static void scsi_read_complete(void * opaque, int ret) @@ -170,30 +152,38 @@ static void scsi_read_complete(void * opaque, int ret) n = r->iov.iov_len / 512; r->sector += n; r->sector_count -= n; - r->req.bus->complete(r->req.bus, SCSI_REASON_DATA, r->req.tag, r->iov.iov_len); + scsi_req_data(&r->req, r->iov.iov_len); } -static void scsi_read_request(SCSIDiskReq *r) +/* Read more data from scsi device into buffer. */ +static void scsi_read_data(SCSIRequest *req) { + SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req); SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev); uint32_t n; if (r->sector_count == (uint32_t)-1) { DPRINTF("Read buf_len=%zd\n", r->iov.iov_len); r->sector_count = 0; - r->req.bus->complete(r->req.bus, SCSI_REASON_DATA, r->req.tag, r->iov.iov_len); + scsi_req_data(&r->req, r->iov.iov_len); return; } DPRINTF("Read sector_count=%d\n", r->sector_count); if (r->sector_count == 0) { - scsi_command_complete(r, GOOD, NO_SENSE); + scsi_command_complete(r, GOOD, SENSE_CODE(NO_SENSE)); return; } /* No data transfer may already be in progress */ assert(r->req.aiocb == NULL); + if (r->req.cmd.mode == SCSI_XFER_TO_DEV) { + DPRINTF("Data transfer direction invalid\n"); + scsi_read_complete(r, -EINVAL); + return; + } + n = r->sector_count; if (n > SCSI_DMA_BUF_SIZE / 512) n = SCSI_DMA_BUF_SIZE / 512; @@ -207,23 +197,6 @@ static void scsi_read_request(SCSIDiskReq *r) } } -/* Read more data from scsi device into buffer. */ -static void scsi_read_data(SCSIDevice *d, uint32_t tag) -{ - SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d); - SCSIDiskReq *r; - - r = scsi_find_request(s, tag); - if (!r) { - BADF("Bad read tag 0x%x\n", tag); - /* ??? This is the wrong error. */ - scsi_command_complete(r, CHECK_CONDITION, HARDWARE_ERROR); - return; - } - - scsi_read_request(r); -} - static int scsi_handle_rw_error(SCSIDiskReq *r, int error, int type) { int is_read = (type == SCSI_REQ_STATUS_RETRY_READ); @@ -245,13 +218,24 @@ static int scsi_handle_rw_error(SCSIDiskReq *r, int error, int type) vm_stop(VMSTOP_DISKFULL); } else { if (type == SCSI_REQ_STATUS_RETRY_READ) { - r->req.bus->complete(r->req.bus, SCSI_REASON_DATA, r->req.tag, 0); + scsi_req_data(&r->req, 0); + } + switch (error) { + case ENOMEM: + scsi_command_complete(r, CHECK_CONDITION, + SENSE_CODE(TARGET_FAILURE)); + break; + case EINVAL: + scsi_command_complete(r, CHECK_CONDITION, + SENSE_CODE(INVALID_FIELD)); + break; + default: + scsi_command_complete(r, CHECK_CONDITION, + SENSE_CODE(IO_ERROR)); + break; } - scsi_command_complete(r, CHECK_CONDITION, - HARDWARE_ERROR); bdrv_mon_event(s->bs, BDRV_ACTION_REPORT, is_read); } - return 1; } @@ -273,7 +257,7 @@ static void scsi_write_complete(void * opaque, int ret) r->sector += n; r->sector_count -= n; if (r->sector_count == 0) { - scsi_command_complete(r, GOOD, NO_SENSE); + scsi_command_complete(r, GOOD, SENSE_CODE(NO_SENSE)); } else { len = r->sector_count * 512; if (len > SCSI_DMA_BUF_SIZE) { @@ -281,25 +265,32 @@ static void scsi_write_complete(void * opaque, int ret) } r->iov.iov_len = len; DPRINTF("Write complete tag=0x%x more=%d\n", r->req.tag, len); - r->req.bus->complete(r->req.bus, SCSI_REASON_DATA, r->req.tag, len); + scsi_req_data(&r->req, len); } } -static void scsi_write_request(SCSIDiskReq *r) +static void scsi_write_data(SCSIRequest *req) { + SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req); SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev); uint32_t n; /* No data transfer may already be in progress */ assert(r->req.aiocb == NULL); + if (r->req.cmd.mode != SCSI_XFER_TO_DEV) { + DPRINTF("Data transfer direction invalid\n"); + scsi_write_complete(r, -EINVAL); + return; + } + n = r->iov.iov_len / 512; if (n) { qemu_iovec_init_external(&r->qiov, &r->iov, 1); r->req.aiocb = bdrv_aio_writev(s->bs, r->sector, &r->qiov, n, scsi_write_complete, r); if (r->req.aiocb == NULL) { - scsi_write_complete(r, -EIO); + scsi_write_complete(r, -ENOMEM); } } else { /* Invoke completion routine to fetch data from host. */ @@ -307,26 +298,6 @@ static void scsi_write_request(SCSIDiskReq *r) } } -/* Write data to a scsi device. Returns nonzero on failure. - The transfer may complete asynchronously. */ -static int scsi_write_data(SCSIDevice *d, uint32_t tag) -{ - SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d); - SCSIDiskReq *r; - - DPRINTF("Write data tag=0x%x\n", tag); - r = scsi_find_request(s, tag); - if (!r) { - BADF("Bad write tag 0x%x\n", tag); - scsi_command_complete(r, CHECK_CONDITION, HARDWARE_ERROR); - return 1; - } - - scsi_write_request(r); - - return 0; -} - static void scsi_dma_restart_bh(void *opaque) { SCSIDiskState *s = opaque; @@ -347,15 +318,15 @@ static void scsi_dma_restart_bh(void *opaque) switch (status & SCSI_REQ_STATUS_RETRY_TYPE_MASK) { case SCSI_REQ_STATUS_RETRY_READ: - scsi_read_request(r); + scsi_read_data(&r->req); break; case SCSI_REQ_STATUS_RETRY_WRITE: - scsi_write_request(r); + scsi_write_data(&r->req); break; case SCSI_REQ_STATUS_RETRY_FLUSH: ret = scsi_disk_emulate_command(r, r->iov.iov_base); if (ret == 0) { - scsi_command_complete(r, GOOD, NO_SENSE); + scsi_command_complete(r, GOOD, SENSE_CODE(NO_SENSE)); } } } @@ -376,19 +347,21 @@ static void scsi_dma_restart_cb(void *opaque, int running, int reason) } /* Return a pointer to the data buffer. */ -static uint8_t *scsi_get_buf(SCSIDevice *d, uint32_t tag) +static uint8_t *scsi_get_buf(SCSIRequest *req) { - SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d); - SCSIDiskReq *r; + SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req); - r = scsi_find_request(s, tag); - if (!r) { - BADF("Bad buffer tag 0x%x\n", tag); - return NULL; - } return (uint8_t *)r->iov.iov_base; } +/* Copy sense information into the provided buffer */ +static int scsi_get_sense(SCSIRequest *req, uint8_t *outbuf, int len) +{ + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev); + + return scsi_build_sense(s->sense, outbuf, len, len > 14); +} + static int scsi_disk_emulate_inquiry(SCSIRequest *req, uint8_t *outbuf) { SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev); @@ -545,7 +518,7 @@ static int scsi_disk_emulate_inquiry(SCSIRequest *req, uint8_t *outbuf) memset(outbuf, 0, buflen); - if (req->lun || req->cmd.buf[1] >> 5) { + if (req->lun) { outbuf[0] = 0x7f; /* LUN not supported */ return buflen; } @@ -860,19 +833,8 @@ static int scsi_disk_emulate_command(SCSIDiskReq *r, uint8_t *outbuf) case REQUEST_SENSE: if (req->cmd.xfer < 4) goto illegal_request; - memset(outbuf, 0, 4); - buflen = 4; - if (s->sense.key == NOT_READY && req->cmd.xfer >= 18) { - memset(outbuf, 0, 18); - buflen = 18; - outbuf[7] = 10; - /* asc 0x3a, ascq 0: Medium not present */ - outbuf[12] = 0x3a; - outbuf[13] = 0; - } - outbuf[0] = 0xf0; - outbuf[1] = 0; - outbuf[2] = s->sense.key; + buflen = scsi_build_sense(s->sense, outbuf, req->cmd.xfer, + req->cmd.xfer > 13); scsi_disk_clear_sense(s); break; case INQUIRY: @@ -1010,17 +972,22 @@ static int scsi_disk_emulate_command(SCSIDiskReq *r, uint8_t *outbuf) } break; default: - goto illegal_request; + scsi_command_complete(r, CHECK_CONDITION, SENSE_CODE(INVALID_OPCODE)); + return -1; } - scsi_req_set_status(r, GOOD, NO_SENSE); + scsi_req_set_status(r, GOOD, SENSE_CODE(NO_SENSE)); return buflen; not_ready: - scsi_command_complete(r, CHECK_CONDITION, NOT_READY); + if (!bdrv_is_inserted(s->bs)) { + scsi_command_complete(r, CHECK_CONDITION, SENSE_CODE(NO_MEDIUM)); + } else { + scsi_command_complete(r, CHECK_CONDITION, SENSE_CODE(LUN_NOT_READY)); + } return -1; illegal_request: - scsi_command_complete(r, CHECK_CONDITION, ILLEGAL_REQUEST); + scsi_command_complete(r, CHECK_CONDITION, SENSE_CODE(INVALID_FIELD)); return -1; } @@ -1029,33 +996,23 @@ illegal_request: (eg. disk reads), negative for transfers to the device (eg. disk writes), and zero if the command does not transfer any data. */ -static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, - uint8_t *buf, int lun) +static int32_t scsi_send_command(SCSIRequest *req, uint8_t *buf) { - SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d); - uint32_t len; - int is_write; + SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req); + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev); + int32_t len; uint8_t command; uint8_t *outbuf; - SCSIDiskReq *r; int rc; command = buf[0]; - r = scsi_find_request(s, tag); - if (r) { - BADF("Tag 0x%x already in use\n", tag); - scsi_cancel_io(d, tag); - } - /* ??? Tags are not unique for different luns. We only implement a - single lun, so this should not matter. */ - r = scsi_new_request(s, tag, lun); outbuf = (uint8_t *)r->iov.iov_base; - is_write = 0; DPRINTF("Command: lun=%d tag=0x%x data=0x%02x", lun, tag, buf[0]); if (scsi_req_parse(&r->req, buf) != 0) { BADF("Unsupported command length, command %x\n", command); - goto fail; + scsi_command_complete(r, CHECK_CONDITION, SENSE_CODE(INVALID_OPCODE)); + return 0; } #ifdef DEBUG_SCSI { @@ -1067,11 +1024,14 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, } #endif - if (lun || buf[1] >> 5) { + if (req->lun) { /* Only LUN 0 supported. */ - DPRINTF("Unimplemented LUN %d\n", lun ? lun : buf[1] >> 5); - if (command != REQUEST_SENSE && command != INQUIRY) - goto fail; + DPRINTF("Unimplemented LUN %d\n", req->lun); + if (command != REQUEST_SENSE && command != INQUIRY) { + scsi_command_complete(r, CHECK_CONDITION, + SENSE_CODE(LUN_NOT_SUPPORTED)); + return 0; + } } switch (command) { case TEST_UNIT_READY: @@ -1104,7 +1064,7 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, case READ_10: case READ_12: case READ_16: - len = r->req.cmd.xfer / d->blocksize; + len = r->req.cmd.xfer / s->qdev.blocksize; DPRINTF("Read (sector %" PRId64 ", count %d)\n", r->req.cmd.lba, len); if (r->req.cmd.lba > s->max_lba) goto illegal_lba; @@ -1118,7 +1078,7 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, case WRITE_VERIFY: case WRITE_VERIFY_12: case WRITE_VERIFY_16: - len = r->req.cmd.xfer / d->blocksize; + len = r->req.cmd.xfer / s->qdev.blocksize; DPRINTF("Write %s(sector %" PRId64 ", count %d)\n", (command & 0xe) == 0xe ? "And Verify " : "", r->req.cmd.lba, len); @@ -1126,7 +1086,6 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, goto illegal_lba; r->sector = r->req.cmd.lba * s->cluster_size; r->sector_count = len * s->cluster_size; - is_write = 1; break; case MODE_SELECT: DPRINTF("Mode Select(6) (len %lu)\n", (long)r->req.cmd.xfer); @@ -1153,7 +1112,7 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, } break; case WRITE_SAME_16: - len = r->req.cmd.xfer / d->blocksize; + len = r->req.cmd.xfer / s->qdev.blocksize; DPRINTF("WRITE SAME(16) (sector %" PRId64 ", count %d)\n", r->req.cmd.lba, len); @@ -1179,18 +1138,20 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, break; default: DPRINTF("Unknown SCSI command (%2.2x)\n", buf[0]); + scsi_command_complete(r, CHECK_CONDITION, SENSE_CODE(INVALID_OPCODE)); + return 0; fail: - scsi_command_complete(r, CHECK_CONDITION, ILLEGAL_REQUEST); + scsi_command_complete(r, CHECK_CONDITION, SENSE_CODE(INVALID_FIELD)); return 0; illegal_lba: - scsi_command_complete(r, CHECK_CONDITION, HARDWARE_ERROR); + scsi_command_complete(r, CHECK_CONDITION, SENSE_CODE(LBA_OUT_OF_RANGE)); return 0; } if (r->sector_count == 0 && r->iov.iov_len == 0) { - scsi_command_complete(r, GOOD, NO_SENSE); + scsi_command_complete(r, GOOD, SENSE_CODE(NO_SENSE)); } len = r->sector_count * 512 + r->iov.iov_len; - if (is_write) { + if (r->req.cmd.mode == SCSI_XFER_TO_DEV) { return -len; } else { if (!r->sector_count) @@ -1199,25 +1160,12 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, } } -static void scsi_disk_purge_requests(SCSIDiskState *s) -{ - SCSIDiskReq *r; - - while (!QTAILQ_EMPTY(&s->qdev.requests)) { - r = DO_UPCAST(SCSIDiskReq, req, QTAILQ_FIRST(&s->qdev.requests)); - if (r->req.aiocb) { - bdrv_aio_cancel(r->req.aiocb); - } - scsi_remove_request(r); - } -} - static void scsi_disk_reset(DeviceState *dev) { SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev.qdev, dev); uint64_t nb_sectors; - scsi_disk_purge_requests(s); + scsi_device_purge_requests(&s->qdev); bdrv_get_geometry(s->bs, &nb_sectors); nb_sectors /= s->cluster_size; @@ -1231,7 +1179,7 @@ static void scsi_destroy(SCSIDevice *dev) { SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev); - scsi_disk_purge_requests(s); + scsi_device_purge_requests(&s->qdev); blockdev_mark_auto_del(s->qdev.conf.bs); } @@ -1321,11 +1269,14 @@ static SCSIDeviceInfo scsi_disk_info[] = { .qdev.reset = scsi_disk_reset, .init = scsi_hd_initfn, .destroy = scsi_destroy, + .alloc_req = scsi_new_request, + .free_req = scsi_free_request, .send_command = scsi_send_command, .read_data = scsi_read_data, .write_data = scsi_write_data, .cancel_io = scsi_cancel_io, .get_buf = scsi_get_buf, + .get_sense = scsi_get_sense, .qdev.props = (Property[]) { DEFINE_SCSI_DISK_PROPERTIES(), DEFINE_PROP_BIT("removable", SCSIDiskState, removable, 0, false), @@ -1339,11 +1290,14 @@ static SCSIDeviceInfo scsi_disk_info[] = { .qdev.reset = scsi_disk_reset, .init = scsi_cd_initfn, .destroy = scsi_destroy, + .alloc_req = scsi_new_request, + .free_req = scsi_free_request, .send_command = scsi_send_command, .read_data = scsi_read_data, .write_data = scsi_write_data, .cancel_io = scsi_cancel_io, .get_buf = scsi_get_buf, + .get_sense = scsi_get_sense, .qdev.props = (Property[]) { DEFINE_SCSI_DISK_PROPERTIES(), DEFINE_PROP_END_OF_LIST(), @@ -1356,11 +1310,14 @@ static SCSIDeviceInfo scsi_disk_info[] = { .qdev.reset = scsi_disk_reset, .init = scsi_disk_initfn, .destroy = scsi_destroy, + .alloc_req = scsi_new_request, + .free_req = scsi_free_request, .send_command = scsi_send_command, .read_data = scsi_read_data, .write_data = scsi_write_data, .cancel_io = scsi_cancel_io, .get_buf = scsi_get_buf, + .get_sense = scsi_get_sense, .qdev.props = (Property[]) { DEFINE_SCSI_DISK_PROPERTIES(), DEFINE_PROP_BIT("removable", SCSIDiskState, removable, 0, false), diff --git a/hw/scsi-generic.c b/hw/scsi-generic.c index 9be1cca4c3..8e59c7ee89 100644 --- a/hw/scsi-generic.c +++ b/hw/scsi-generic.c @@ -66,23 +66,49 @@ struct SCSIGenericState uint8_t senselen; }; -static SCSIGenericReq *scsi_new_request(SCSIDevice *d, uint32_t tag, uint32_t lun) +static void scsi_set_sense(SCSIGenericState *s, SCSISense sense) { - SCSIRequest *req; + s->senselen = scsi_build_sense(sense, s->sensebuf, SCSI_SENSE_BUF_SIZE, 0); + s->driver_status = SG_ERR_DRIVER_SENSE; +} - req = scsi_req_alloc(sizeof(SCSIGenericReq), d, tag, lun); - return DO_UPCAST(SCSIGenericReq, req, req); +static void scsi_clear_sense(SCSIGenericState *s) +{ + memset(s->sensebuf, 0, SCSI_SENSE_BUF_SIZE); + s->senselen = 0; + s->driver_status = 0; } -static void scsi_remove_request(SCSIGenericReq *r) +static int scsi_get_sense(SCSIRequest *req, uint8_t *outbuf, int len) { - qemu_free(r->buf); - scsi_req_free(&r->req); + SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, req->dev); + int size = SCSI_SENSE_BUF_SIZE; + + if (!(s->driver_status & SG_ERR_DRIVER_SENSE)) { + size = scsi_build_sense(SENSE_CODE(NO_SENSE), s->sensebuf, + SCSI_SENSE_BUF_SIZE, 0); + } + if (size > len) { + size = len; + } + memcpy(outbuf, s->sensebuf, size); + + return size; } -static SCSIGenericReq *scsi_find_request(SCSIGenericState *s, uint32_t tag) +static SCSIRequest *scsi_new_request(SCSIDevice *d, uint32_t tag, uint32_t lun) { - return DO_UPCAST(SCSIGenericReq, req, scsi_req_find(&s->qdev, tag)); + SCSIRequest *req; + + req = scsi_req_alloc(sizeof(SCSIGenericReq), d, tag, lun); + return req; +} + +static void scsi_free_request(SCSIRequest *req) +{ + SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req); + + qemu_free(r->buf); } /* Helper function for command completion. */ @@ -91,13 +117,30 @@ static void scsi_command_complete(void *opaque, int ret) SCSIGenericReq *r = (SCSIGenericReq *)opaque; SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, r->req.dev); + r->req.aiocb = NULL; s->driver_status = r->io_header.driver_status; if (s->driver_status & SG_ERR_DRIVER_SENSE) s->senselen = r->io_header.sb_len_wr; - if (ret != 0) - r->req.status = BUSY; - else { + if (ret != 0) { + switch (ret) { + case -EDOM: + r->req.status = TASK_SET_FULL; + break; + case -EINVAL: + r->req.status = CHECK_CONDITION; + scsi_set_sense(s, SENSE_CODE(INVALID_FIELD)); + break; + case -ENOMEM: + r->req.status = CHECK_CONDITION; + scsi_set_sense(s, SENSE_CODE(TARGET_FAILURE)); + break; + default: + r->req.status = CHECK_CONDITION; + scsi_set_sense(s, SENSE_CODE(IO_ERROR)); + break; + } + } else { if (s->driver_status & SG_ERR_DRIVER_TIMEOUT) { r->req.status = BUSY; BADF("Driver Timeout\n"); @@ -112,23 +155,18 @@ static void scsi_command_complete(void *opaque, int ret) r, r->req.tag, r->req.status); scsi_req_complete(&r->req); - scsi_remove_request(r); } /* Cancel a pending data transfer. */ -static void scsi_cancel_io(SCSIDevice *d, uint32_t tag) +static void scsi_cancel_io(SCSIRequest *req) { - DPRINTF("scsi_cancel_io 0x%x\n", tag); - SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, d); - SCSIGenericReq *r; - DPRINTF("Cancel tag=0x%x\n", tag); - r = scsi_find_request(s, tag); - if (r) { - if (r->req.aiocb) - bdrv_aio_cancel(r->req.aiocb); - r->req.aiocb = NULL; - scsi_remove_request(r); + SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req); + + DPRINTF("Cancel tag=0x%x\n", req->tag); + if (r->req.aiocb) { + bdrv_aio_cancel(r->req.aiocb); } + r->req.aiocb = NULL; } static int execute_command(BlockDriverState *bdrv, @@ -152,7 +190,7 @@ static int execute_command(BlockDriverState *bdrv, r->req.aiocb = bdrv_aio_ioctl(bdrv, SG_IO, &r->io_header, complete, r); if (r->req.aiocb == NULL) { BADF("execute_command: read failed !\n"); - return -1; + return -ENOMEM; } return 0; @@ -163,6 +201,7 @@ static void scsi_read_complete(void * opaque, int ret) SCSIGenericReq *r = (SCSIGenericReq *)opaque; int len; + r->req.aiocb = NULL; if (ret) { DPRINTF("IO error ret %d\n", ret); scsi_command_complete(r, ret); @@ -172,27 +211,21 @@ static void scsi_read_complete(void * opaque, int ret) DPRINTF("Data ready tag=0x%x len=%d\n", r->req.tag, len); r->len = -1; - r->req.bus->complete(r->req.bus, SCSI_REASON_DATA, r->req.tag, len); - if (len == 0) + if (len == 0) { scsi_command_complete(r, 0); + } else { + scsi_req_data(&r->req, len); + } } /* Read more data from scsi device into buffer. */ -static void scsi_read_data(SCSIDevice *d, uint32_t tag) +static void scsi_read_data(SCSIRequest *req) { - SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, d); - SCSIGenericReq *r; + SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req); + SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, r->req.dev); int ret; - DPRINTF("scsi_read_data 0x%x\n", tag); - r = scsi_find_request(s, tag); - if (!r) { - BADF("Bad read tag 0x%x\n", tag); - /* ??? This is the wrong error. */ - scsi_command_complete(r, -EINVAL); - return; - } - + DPRINTF("scsi_read_data 0x%x\n", req->tag); if (r->len == -1) { scsi_command_complete(r, 0); return; @@ -210,13 +243,15 @@ static void scsi_read_data(SCSIDevice *d, uint32_t tag) DPRINTF("Sense: %d %d %d %d %d %d %d %d\n", r->buf[0], r->buf[1], r->buf[2], r->buf[3], r->buf[4], r->buf[5], r->buf[6], r->buf[7]); - r->req.bus->complete(r->req.bus, SCSI_REASON_DATA, r->req.tag, s->senselen); + scsi_req_data(&r->req, s->senselen); + /* Clear sensebuf after REQUEST_SENSE */ + scsi_clear_sense(s); return; } ret = execute_command(s->bs, r, SG_DXFER_FROM_DEV, scsi_read_complete); - if (ret == -1) { - scsi_command_complete(r, -EINVAL); + if (ret < 0) { + scsi_command_complete(r, ret); return; } } @@ -227,6 +262,7 @@ static void scsi_write_complete(void * opaque, int ret) SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, r->req.dev); DPRINTF("scsi_write_complete() ret = %d\n", ret); + r->req.aiocb = NULL; if (ret) { DPRINTF("IO error\n"); scsi_command_complete(r, ret); @@ -244,46 +280,30 @@ static void scsi_write_complete(void * opaque, int ret) /* Write data to a scsi device. Returns nonzero on failure. The transfer may complete asynchronously. */ -static int scsi_write_data(SCSIDevice *d, uint32_t tag) +static void scsi_write_data(SCSIRequest *req) { - SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, d); - SCSIGenericReq *r; + SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, req->dev); + SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req); int ret; - DPRINTF("scsi_write_data 0x%x\n", tag); - r = scsi_find_request(s, tag); - if (!r) { - BADF("Bad write tag 0x%x\n", tag); - /* ??? This is the wrong error. */ - scsi_command_complete(r, -EINVAL); - return 0; - } - + DPRINTF("scsi_write_data 0x%x\n", req->tag); if (r->len == 0) { r->len = r->buflen; - r->req.bus->complete(r->req.bus, SCSI_REASON_DATA, r->req.tag, r->len); - return 0; + scsi_req_data(&r->req, r->len); + return; } ret = execute_command(s->bs, r, SG_DXFER_TO_DEV, scsi_write_complete); - if (ret == -1) { - scsi_command_complete(r, -EINVAL); - return 1; + if (ret < 0) { + scsi_command_complete(r, ret); } - - return 0; } /* Return a pointer to the data buffer. */ -static uint8_t *scsi_get_buf(SCSIDevice *d, uint32_t tag) +static uint8_t *scsi_get_buf(SCSIRequest *req) { - SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, d); - SCSIGenericReq *r; - r = scsi_find_request(s, tag); - if (!r) { - BADF("Bad buffer tag 0x%x\n", tag); - return NULL; - } + SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req); + return r->buf; } @@ -311,42 +331,23 @@ static void scsi_req_fixup(SCSIRequest *req) (eg. disk reads), negative for transfers to the device (eg. disk writes), and zero if the command does not transfer any data. */ -static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, - uint8_t *cmd, int lun) +static int32_t scsi_send_command(SCSIRequest *req, uint8_t *cmd) { - SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, d); - SCSIGenericReq *r; - SCSIBus *bus; + SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, req->dev); + SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req); int ret; - if (cmd[0] != REQUEST_SENSE && - (lun != s->lun || (cmd[1] >> 5) != s->lun)) { - DPRINTF("Unimplemented LUN %d\n", lun ? lun : cmd[1] >> 5); - - s->sensebuf[0] = 0x70; - s->sensebuf[1] = 0x00; - s->sensebuf[2] = ILLEGAL_REQUEST; - s->sensebuf[3] = 0x00; - s->sensebuf[4] = 0x00; - s->sensebuf[5] = 0x00; - s->sensebuf[6] = 0x00; - s->senselen = 7; - s->driver_status = SG_ERR_DRIVER_SENSE; - bus = scsi_bus_from_device(d); - bus->complete(bus, SCSI_REASON_DONE, tag, CHECK_CONDITION); + if (cmd[0] != REQUEST_SENSE && req->lun != s->lun) { + DPRINTF("Unimplemented LUN %d\n", req->lun); + scsi_set_sense(s, SENSE_CODE(LUN_NOT_SUPPORTED)); + r->req.status = CHECK_CONDITION; + scsi_req_complete(&r->req); return 0; } - r = scsi_find_request(s, tag); - if (r) { - BADF("Tag 0x%x already in use %p\n", tag, r); - scsi_cancel_io(d, tag); - } - r = scsi_new_request(d, tag, lun); - if (-1 == scsi_req_parse(&r->req, cmd)) { BADF("Unsupported command length, command %x\n", cmd[0]); - scsi_remove_request(r); + scsi_command_complete(r, -EINVAL); return 0; } scsi_req_fixup(&r->req); @@ -370,8 +371,8 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, r->buflen = 0; r->buf = NULL; ret = execute_command(s->bs, r, SG_DXFER_NONE, scsi_command_complete); - if (ret == -1) { - scsi_command_complete(r, -EINVAL); + if (ret < 0) { + scsi_command_complete(r, ret); return 0; } return 0; @@ -389,9 +390,9 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, if (r->req.cmd.mode == SCSI_XFER_TO_DEV) { r->len = 0; return -r->req.cmd.xfer; + } else { + return r->req.cmd.xfer; } - - return r->req.cmd.xfer; } static int get_blocksize(BlockDriverState *bdrv) @@ -455,31 +456,18 @@ static int get_stream_blocksize(BlockDriverState *bdrv) return (buf[9] << 16) | (buf[10] << 8) | buf[11]; } -static void scsi_generic_purge_requests(SCSIGenericState *s) -{ - SCSIGenericReq *r; - - while (!QTAILQ_EMPTY(&s->qdev.requests)) { - r = DO_UPCAST(SCSIGenericReq, req, QTAILQ_FIRST(&s->qdev.requests)); - if (r->req.aiocb) { - bdrv_aio_cancel(r->req.aiocb); - } - scsi_remove_request(r); - } -} - static void scsi_generic_reset(DeviceState *dev) { SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev.qdev, dev); - scsi_generic_purge_requests(s); + scsi_device_purge_requests(&s->qdev); } static void scsi_destroy(SCSIDevice *d) { SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, d); - scsi_generic_purge_requests(s); + scsi_device_purge_requests(&s->qdev); blockdev_mark_auto_del(s->qdev.conf.bs); } @@ -556,11 +544,14 @@ static SCSIDeviceInfo scsi_generic_info = { .qdev.reset = scsi_generic_reset, .init = scsi_generic_initfn, .destroy = scsi_destroy, + .alloc_req = scsi_new_request, + .free_req = scsi_free_request, .send_command = scsi_send_command, .read_data = scsi_read_data, .write_data = scsi_write_data, .cancel_io = scsi_cancel_io, .get_buf = scsi_get_buf, + .get_sense = scsi_get_sense, .qdev.props = (Property[]) { DEFINE_BLOCK_PROPERTIES(SCSIGenericState, qdev.conf), DEFINE_PROP_END_OF_LIST(), diff --git a/hw/scsi.h b/hw/scsi.h index d3b5d56cd6..c1dca35b86 100644 --- a/hw/scsi.h +++ b/hw/scsi.h @@ -9,17 +9,11 @@ #define SCSI_CMD_BUF_SIZE 16 -/* scsi-disk.c */ -enum scsi_reason { - SCSI_REASON_DONE, /* Command complete. */ - SCSI_REASON_DATA /* Transfer complete, more data required. */ -}; - typedef struct SCSIBus SCSIBus; +typedef struct SCSIBusOps SCSIBusOps; typedef struct SCSIDevice SCSIDevice; typedef struct SCSIDeviceInfo SCSIDeviceInfo; -typedef void (*scsi_completionfn)(SCSIBus *bus, int reason, uint32_t tag, - uint32_t arg); +typedef struct SCSIRequest SCSIRequest; enum SCSIXferMode { SCSI_XFER_NONE, /* TEST_UNIT_READY, ... */ @@ -27,9 +21,16 @@ enum SCSIXferMode { SCSI_XFER_TO_DEV, /* WRITE, MODE_SELECT, ... */ }; -typedef struct SCSIRequest { +typedef struct SCSISense { + uint8_t key; + uint8_t asc; + uint8_t ascq; +} SCSISense; + +struct SCSIRequest { SCSIBus *bus; SCSIDevice *dev; + uint32_t refcount; uint32_t tag; uint32_t lun; uint32_t status; @@ -43,7 +44,7 @@ typedef struct SCSIRequest { BlockDriverAIOCB *aiocb; bool enqueued; QTAILQ_ENTRY(SCSIRequest) next; -} SCSIRequest; +}; struct SCSIDevice { @@ -66,28 +67,34 @@ struct SCSIDeviceInfo { DeviceInfo qdev; scsi_qdev_initfn init; void (*destroy)(SCSIDevice *s); - int32_t (*send_command)(SCSIDevice *s, uint32_t tag, uint8_t *buf, - int lun); - void (*read_data)(SCSIDevice *s, uint32_t tag); - int (*write_data)(SCSIDevice *s, uint32_t tag); - void (*cancel_io)(SCSIDevice *s, uint32_t tag); - uint8_t *(*get_buf)(SCSIDevice *s, uint32_t tag); + SCSIRequest *(*alloc_req)(SCSIDevice *s, uint32_t tag, uint32_t lun); + void (*free_req)(SCSIRequest *req); + int32_t (*send_command)(SCSIRequest *req, uint8_t *buf); + void (*read_data)(SCSIRequest *req); + void (*write_data)(SCSIRequest *req); + void (*cancel_io)(SCSIRequest *req); + uint8_t *(*get_buf)(SCSIRequest *req); + int (*get_sense)(SCSIRequest *req, uint8_t *buf, int len); +}; + +struct SCSIBusOps { + void (*transfer_data)(SCSIRequest *req, uint32_t arg); + void (*complete)(SCSIRequest *req, uint32_t arg); + void (*cancel)(SCSIRequest *req); }; -typedef void (*SCSIAttachFn)(DeviceState *host, BlockDriverState *bdrv, - int unit); struct SCSIBus { BusState qbus; int busnr; int tcq, ndev; - scsi_completionfn complete; + const SCSIBusOps *ops; SCSIDevice *devs[MAX_SCSI_DEVS]; }; void scsi_bus_new(SCSIBus *bus, DeviceState *host, int tcq, int ndev, - scsi_completionfn complete); + const SCSIBusOps *ops); void scsi_qdev_register(SCSIDeviceInfo *info); static inline SCSIBus *scsi_bus_from_device(SCSIDevice *d) @@ -99,12 +106,54 @@ SCSIDevice *scsi_bus_legacy_add_drive(SCSIBus *bus, BlockDriverState *bdrv, int unit, bool removable); int scsi_bus_legacy_handle_cmdline(SCSIBus *bus); +/* + * Predefined sense codes + */ + +/* No sense data available */ +extern const struct SCSISense sense_code_NO_SENSE; +/* LUN not ready, Manual intervention required */ +extern const struct SCSISense sense_code_LUN_NOT_READY; +/* LUN not ready, Medium not present */ +extern const struct SCSISense sense_code_NO_MEDIUM; +/* Hardware error, internal target failure */ +extern const struct SCSISense sense_code_TARGET_FAILURE; +/* Illegal request, invalid command operation code */ +extern const struct SCSISense sense_code_INVALID_OPCODE; +/* Illegal request, LBA out of range */ +extern const struct SCSISense sense_code_LBA_OUT_OF_RANGE; +/* Illegal request, Invalid field in CDB */ +extern const struct SCSISense sense_code_INVALID_FIELD; +/* Illegal request, LUN not supported */ +extern const struct SCSISense sense_code_LUN_NOT_SUPPORTED; +/* Command aborted, I/O process terminated */ +extern const struct SCSISense sense_code_IO_ERROR; +/* Command aborted, I_T Nexus loss occurred */ +extern const struct SCSISense sense_code_I_T_NEXUS_LOSS; +/* Command aborted, Logical Unit failure */ +extern const struct SCSISense sense_code_LUN_FAILURE; + +#define SENSE_CODE(x) sense_code_ ## x + +int scsi_build_sense(SCSISense sense, uint8_t *buf, int len, int fixed); +int scsi_sense_valid(SCSISense sense); + SCSIRequest *scsi_req_alloc(size_t size, SCSIDevice *d, uint32_t tag, uint32_t lun); -SCSIRequest *scsi_req_find(SCSIDevice *d, uint32_t tag); +SCSIRequest *scsi_req_new(SCSIDevice *d, uint32_t tag, uint32_t lun); +int32_t scsi_req_enqueue(SCSIRequest *req, uint8_t *buf); void scsi_req_free(SCSIRequest *req); +SCSIRequest *scsi_req_ref(SCSIRequest *req); +void scsi_req_unref(SCSIRequest *req); int scsi_req_parse(SCSIRequest *req, uint8_t *buf); void scsi_req_print(SCSIRequest *req); +void scsi_req_continue(SCSIRequest *req); +void scsi_req_data(SCSIRequest *req, int len); void scsi_req_complete(SCSIRequest *req); +uint8_t *scsi_req_get_buf(SCSIRequest *req); +int scsi_req_get_sense(SCSIRequest *req, uint8_t *buf, int len); +void scsi_req_abort(SCSIRequest *req, int status); +void scsi_req_cancel(SCSIRequest *req); +void scsi_device_purge_requests(SCSIDevice *sdev); #endif diff --git a/hw/spapr_vscsi.c b/hw/spapr_vscsi.c index 992833450c..1c901ef6eb 100644 --- a/hw/spapr_vscsi.c +++ b/hw/spapr_vscsi.c @@ -74,7 +74,7 @@ typedef struct vscsi_req { union viosrp_iu iu; /* SCSI request tracking */ - SCSIDevice *sdev; + SCSIRequest *sreq; uint32_t qtag; /* qemu tag != srp tag */ int lun; int active; @@ -123,11 +123,16 @@ static struct vscsi_req *vscsi_get_req(VSCSIState *s) static void vscsi_put_req(VSCSIState *s, vscsi_req *req) { + if (req->sreq != NULL) { + scsi_req_unref(req->sreq); + } + req->sreq = NULL; req->active = 0; } -static vscsi_req *vscsi_find_req(VSCSIState *s, uint32_t tag) +static vscsi_req *vscsi_find_req(VSCSIState *s, SCSIRequest *req) { + uint32_t tag = req->tag; if (tag >= VSCSI_REQ_LIMIT || !s->reqs[tag].active) { return NULL; } @@ -442,10 +447,18 @@ static int vscsi_preprocess_desc(vscsi_req *req) static void vscsi_send_request_sense(VSCSIState *s, vscsi_req *req) { - SCSIDevice *sdev = req->sdev; uint8_t *cdb = req->iu.srp.cmd.cdb; int n; + n = scsi_req_get_sense(req->sreq, req->sense, sizeof(req->sense)); + if (n) { + req->senselen = n; + vscsi_send_rsp(s, req, CHECK_CONDITION, 0, 0); + vscsi_put_req(s, req); + return; + } + + dprintf("VSCSI: Got CHECK_CONDITION, requesting sense...\n"); cdb[0] = 3; cdb[1] = 0; cdb[2] = 0; @@ -453,66 +466,92 @@ static void vscsi_send_request_sense(VSCSIState *s, vscsi_req *req) cdb[4] = 96; cdb[5] = 0; req->sensing = 1; - n = sdev->info->send_command(sdev, req->qtag, cdb, req->lun); + n = scsi_req_enqueue(req->sreq, cdb); dprintf("VSCSI: Queued request sense tag 0x%x\n", req->qtag); if (n < 0) { fprintf(stderr, "VSCSI: REQUEST_SENSE wants write data !?!?!?\n"); - sdev->info->cancel_io(sdev, req->qtag); vscsi_makeup_sense(s, req, HARDWARE_ERROR, 0, 0); - vscsi_send_rsp(s, req, CHECK_CONDITION, 0, 0); - vscsi_put_req(s, req); + scsi_req_abort(req->sreq, CHECK_CONDITION); return; } else if (n == 0) { return; } - sdev->info->read_data(sdev, req->qtag); + scsi_req_continue(req->sreq); } /* Callback to indicate that the SCSI layer has completed a transfer. */ -static void vscsi_command_complete(SCSIBus *bus, int reason, uint32_t tag, - uint32_t arg) +static void vscsi_transfer_data(SCSIRequest *sreq, uint32_t len) { - VSCSIState *s = DO_UPCAST(VSCSIState, vdev.qdev, bus->qbus.parent); - vscsi_req *req = vscsi_find_req(s, tag); - SCSIDevice *sdev; + VSCSIState *s = DO_UPCAST(VSCSIState, vdev.qdev, sreq->bus->qbus.parent); + vscsi_req *req = vscsi_find_req(s, sreq); uint8_t *buf; - int32_t res_in = 0, res_out = 0; - int len, rc = 0; + int rc = 0; - dprintf("VSCSI: SCSI cmd complete, r=0x%x tag=0x%x arg=0x%x, req=%p\n", - reason, tag, arg, req); + dprintf("VSCSI: SCSI xfer complete tag=0x%x len=0x%x, req=%p\n", + sreq->tag, len, req); if (req == NULL) { - fprintf(stderr, "VSCSI: Can't find request for tag 0x%x\n", tag); + fprintf(stderr, "VSCSI: Can't find request for tag 0x%x\n", sreq->tag); return; } - sdev = req->sdev; if (req->sensing) { - if (reason == SCSI_REASON_DONE) { - dprintf("VSCSI: Sense done !\n"); - vscsi_send_rsp(s, req, CHECK_CONDITION, 0, 0); - vscsi_put_req(s, req); - } else { - uint8_t *buf = sdev->info->get_buf(sdev, tag); - - len = MIN(arg, SCSI_SENSE_BUF_SIZE); - dprintf("VSCSI: Sense data, %d bytes:\n", len); - dprintf(" %02x %02x %02x %02x %02x %02x %02x %02x\n", - buf[0], buf[1], buf[2], buf[3], - buf[4], buf[5], buf[6], buf[7]); - dprintf(" %02x %02x %02x %02x %02x %02x %02x %02x\n", - buf[8], buf[9], buf[10], buf[11], - buf[12], buf[13], buf[14], buf[15]); - memcpy(req->sense, buf, len); - req->senselen = len; - sdev->info->read_data(sdev, req->qtag); - } + uint8_t *buf = scsi_req_get_buf(sreq); + + len = MIN(len, SCSI_SENSE_BUF_SIZE); + dprintf("VSCSI: Sense data, %d bytes:\n", len); + dprintf(" %02x %02x %02x %02x %02x %02x %02x %02x\n", + buf[0], buf[1], buf[2], buf[3], + buf[4], buf[5], buf[6], buf[7]); + dprintf(" %02x %02x %02x %02x %02x %02x %02x %02x\n", + buf[8], buf[9], buf[10], buf[11], + buf[12], buf[13], buf[14], buf[15]); + memcpy(req->sense, buf, len); + req->senselen = len; + scsi_req_continue(req->sreq); + return; + } + + if (len) { + buf = scsi_req_get_buf(sreq); + rc = vscsi_srp_transfer_data(s, req, req->writing, buf, len); + } + if (rc < 0) { + fprintf(stderr, "VSCSI: RDMA error rc=%d!\n", rc); + vscsi_makeup_sense(s, req, HARDWARE_ERROR, 0, 0); + scsi_req_abort(req->sreq, CHECK_CONDITION); + return; + } + + /* Start next chunk */ + req->data_len -= rc; + scsi_req_continue(sreq); +} + +/* Callback to indicate that the SCSI layer has completed a transfer. */ +static void vscsi_command_complete(SCSIRequest *sreq, uint32_t status) +{ + VSCSIState *s = DO_UPCAST(VSCSIState, vdev.qdev, sreq->bus->qbus.parent); + vscsi_req *req = vscsi_find_req(s, sreq); + int32_t res_in = 0, res_out = 0; + + dprintf("VSCSI: SCSI cmd complete, r=0x%x tag=0x%x status=0x%x, req=%p\n", + reason, sreq->tag, status, req); + if (req == NULL) { + fprintf(stderr, "VSCSI: Can't find request for tag 0x%x\n", sreq->tag); + return; + } + + if (!req->sensing && status == CHECK_CONDITION) { + vscsi_send_request_sense(s, req); return; } - if (reason == SCSI_REASON_DONE) { - dprintf("VSCSI: Command complete err=%d\n", arg); - if (arg == 0) { + if (req->sensing) { + dprintf("VSCSI: Sense done !\n"); + status = CHECK_CONDITION; + } else { + dprintf("VSCSI: Command complete err=%d\n", status); + if (status == 0) { /* We handle overflows, not underflows for normal commands, * but hopefully nobody cares */ @@ -521,41 +560,18 @@ static void vscsi_command_complete(SCSIBus *bus, int reason, uint32_t tag, } else { res_in = req->data_len; } - vscsi_send_rsp(s, req, 0, res_in, res_out); - } else if (arg == CHECK_CONDITION) { - dprintf("VSCSI: Got CHECK_CONDITION, requesting sense...\n"); - vscsi_send_request_sense(s, req); - return; - } else { - vscsi_send_rsp(s, req, arg, 0, 0); } - vscsi_put_req(s, req); - return; } + vscsi_send_rsp(s, req, 0, res_in, res_out); + vscsi_put_req(s, req); +} - /* "arg" is how much we have read for reads and how much we want - * to write for writes (ie, how much is to be DMA'd) - */ - if (arg) { - buf = sdev->info->get_buf(sdev, tag); - rc = vscsi_srp_transfer_data(s, req, req->writing, buf, arg); - } - if (rc < 0) { - fprintf(stderr, "VSCSI: RDMA error rc=%d!\n", rc); - sdev->info->cancel_io(sdev, req->qtag); - vscsi_makeup_sense(s, req, HARDWARE_ERROR, 0, 0); - vscsi_send_rsp(s, req, CHECK_CONDITION, 0, 0); - vscsi_put_req(s, req); - return; - } +static void vscsi_request_cancelled(SCSIRequest *sreq) +{ + VSCSIState *s = DO_UPCAST(VSCSIState, vdev.qdev, sreq->bus->qbus.parent); + vscsi_req *req = vscsi_find_req(s, sreq); - /* Start next chunk */ - req->data_len -= rc; - if (req->writing) { - sdev->info->write_data(sdev, req->qtag); - } else { - sdev->info->read_data(sdev, req->qtag); - } + vscsi_put_req(s, req); } static void vscsi_process_login(VSCSIState *s, vscsi_req *req) @@ -642,9 +658,9 @@ static int vscsi_queue_cmd(VSCSIState *s, vscsi_req *req) } return 1; } - req->sdev = sdev; req->lun = lun; - n = sdev->info->send_command(sdev, req->qtag, srp->cmd.cdb, lun); + req->sreq = scsi_req_new(sdev, req->qtag, lun); + n = scsi_req_enqueue(req->sreq, srp->cmd.cdb); dprintf("VSCSI: Queued command tag 0x%x CMD 0x%x ID %d LUN %d ret: %d\n", req->qtag, srp->cmd.cdb[0], id, lun, n); @@ -657,15 +673,14 @@ static int vscsi_queue_cmd(VSCSIState *s, vscsi_req *req) /* Preprocess RDMA descriptors */ vscsi_preprocess_desc(req); - } - /* Get transfer direction and initiate transfer */ - if (n > 0) { - req->data_len = n; - sdev->info->read_data(sdev, req->qtag); - } else if (n < 0) { - req->data_len = -n; - sdev->info->write_data(sdev, req->qtag); + /* Get transfer direction and initiate transfer */ + if (n > 0) { + req->data_len = n; + } else if (n < 0) { + req->data_len = -n; + } + scsi_req_continue(req->sreq); } /* Don't touch req here, it may have been recycled already */ @@ -907,6 +922,12 @@ static int vscsi_do_crq(struct VIOsPAPRDevice *dev, uint8_t *crq_data) return 0; } +static const struct SCSIBusOps vscsi_scsi_ops = { + .transfer_data = vscsi_transfer_data, + .complete = vscsi_command_complete, + .cancel = vscsi_request_cancelled +}; + static int spapr_vscsi_init(VIOsPAPRDevice *dev) { VSCSIState *s = DO_UPCAST(VSCSIState, vdev, dev); @@ -923,7 +944,7 @@ static int spapr_vscsi_init(VIOsPAPRDevice *dev) dev->crq.SendFunc = vscsi_do_crq; scsi_bus_new(&s->bus, &dev->qdev, 1, VSCSI_REQ_LIMIT, - vscsi_command_complete); + &vscsi_scsi_ops); if (!dev->qdev.hotplugged) { scsi_bus_legacy_handle_cmdline(&s->bus); } diff --git a/hw/usb-bt.c b/hw/usb-bt.c index 22e6845049..baae4876ea 100644 --- a/hw/usb-bt.c +++ b/hw/usb-bt.c @@ -372,13 +372,13 @@ static void usb_bt_handle_reset(USBDevice *dev) s->altsetting = 0; } -static int usb_bt_handle_control(USBDevice *dev, int request, int value, - int index, int length, uint8_t *data) +static int usb_bt_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) { struct USBBtState *s = (struct USBBtState *) dev->opaque; int ret; - ret = usb_desc_handle_control(dev, request, value, index, length, data); + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); if (ret >= 0) { switch (request) { case DeviceRequest | USB_REQ_GET_CONFIGURATION: diff --git a/hw/usb-ccid.c b/hw/usb-ccid.c index 079b4a2555..5b6878bea4 100644 --- a/hw/usb-ccid.c +++ b/hw/usb-ccid.c @@ -602,8 +602,8 @@ static void ccid_handle_reset(USBDevice *dev) ccid_reset(s); } -static int ccid_handle_control(USBDevice *dev, int request, int value, - int index, int length, uint8_t *data) +static int ccid_handle_control(USBDevice *dev, USBPacket *p, int request, + int value, int index, int length, uint8_t *data) { USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); int ret = 0; diff --git a/hw/usb-desc.c b/hw/usb-desc.c index 62591f20aa..e4a4680fee 100644 --- a/hw/usb-desc.c +++ b/hw/usb-desc.c @@ -76,7 +76,7 @@ int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len) { uint8_t bLength = 0x09; uint16_t wTotalLength = 0; - int i, rc, count; + int i, rc; if (len < bLength) { return -1; @@ -91,8 +91,19 @@ int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len) dest[0x08] = conf->bMaxPower; wTotalLength += bLength; - count = conf->nif ? conf->nif : conf->bNumInterfaces; - for (i = 0; i < count; i++) { + /* handle grouped interfaces if any*/ + for (i = 0; i < conf->nif_groups; i++) { + rc = usb_desc_iface_group(&(conf->if_groups[i]), + dest + wTotalLength, + len - wTotalLength); + if (rc < 0) { + return rc; + } + wTotalLength += rc; + } + + /* handle normal (ungrouped / no IAD) interfaces if any */ + for (i = 0; i < conf->nif; i++) { rc = usb_desc_iface(conf->ifs + i, dest + wTotalLength, len - wTotalLength); if (rc < 0) { return rc; @@ -105,6 +116,41 @@ int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len) return wTotalLength; } +int usb_desc_iface_group(const USBDescIfaceAssoc *iad, uint8_t *dest, + size_t len) +{ + int pos = 0; + int i = 0; + + /* handle interface association descriptor */ + uint8_t bLength = 0x08; + + if (len < bLength) { + return -1; + } + + dest[0x00] = bLength; + dest[0x01] = USB_DT_INTERFACE_ASSOC; + dest[0x02] = iad->bFirstInterface; + dest[0x03] = iad->bInterfaceCount; + dest[0x04] = iad->bFunctionClass; + dest[0x05] = iad->bFunctionSubClass; + dest[0x06] = iad->bFunctionProtocol; + dest[0x07] = iad->iFunction; + pos += bLength; + + /* handle associated interfaces in this group */ + for (i = 0; i < iad->nif; i++) { + int rc = usb_desc_iface(&(iad->ifs[i]), dest + pos, len - pos); + if (rc < 0) { + return rc; + } + pos += rc; + } + + return pos; +} + int usb_desc_iface(const USBDescIface *iface, uint8_t *dest, size_t len) { uint8_t bLength = 0x09; @@ -344,8 +390,8 @@ int usb_desc_get_descriptor(USBDevice *dev, int value, uint8_t *dest, size_t len return ret; } -int usb_desc_handle_control(USBDevice *dev, int request, int value, - int index, int length, uint8_t *data) +int usb_desc_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) { const USBDesc *desc = dev->info->usb_desc; int i, ret = -1; diff --git a/hw/usb-desc.h b/hw/usb-desc.h index ac734ab088..9d7ed599ce 100644 --- a/hw/usb-desc.h +++ b/hw/usb-desc.h @@ -30,6 +30,24 @@ struct USBDescConfig { uint8_t bmAttributes; uint8_t bMaxPower; + /* grouped interfaces */ + uint8_t nif_groups; + const USBDescIfaceAssoc *if_groups; + + /* "normal" interfaces */ + uint8_t nif; + const USBDescIface *ifs; +}; + +/* conceptually an Interface Association Descriptor, and releated interfaces */ +struct USBDescIfaceAssoc { + uint8_t bFirstInterface; + uint8_t bInterfaceCount; + uint8_t bFunctionClass; + uint8_t bFunctionSubClass; + uint8_t bFunctionProtocol; + uint8_t iFunction; + uint8_t nif; const USBDescIface *ifs; }; @@ -75,6 +93,8 @@ int usb_desc_device(const USBDescID *id, const USBDescDevice *dev, int usb_desc_device_qualifier(const USBDescDevice *dev, uint8_t *dest, size_t len); int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len); +int usb_desc_iface_group(const USBDescIfaceAssoc *iad, uint8_t *dest, + size_t len); int usb_desc_iface(const USBDescIface *iface, uint8_t *dest, size_t len); int usb_desc_endpoint(const USBDescEndpoint *ep, uint8_t *dest, size_t len); int usb_desc_other(const USBDescOther *desc, uint8_t *dest, size_t len); @@ -86,7 +106,7 @@ void usb_desc_set_string(USBDevice *dev, uint8_t index, const char *str); const char *usb_desc_get_string(USBDevice *dev, uint8_t index); int usb_desc_string(USBDevice *dev, int index, uint8_t *dest, size_t len); int usb_desc_get_descriptor(USBDevice *dev, int value, uint8_t *dest, size_t len); -int usb_desc_handle_control(USBDevice *dev, int request, int value, - int index, int length, uint8_t *data); +int usb_desc_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data); #endif /* QEMU_HW_USB_DESC_H */ diff --git a/hw/usb-ehci.c b/hw/usb-ehci.c new file mode 100644 index 0000000000..f63519ecf9 --- /dev/null +++ b/hw/usb-ehci.c @@ -0,0 +1,2037 @@ +/* + * QEMU USB EHCI Emulation + * + * Copyright(c) 2008 Emutex Ltd. (address@hidden) + * + * EHCI project was started by Mark Burkley, with contributions by + * Niels de Vos. David S. Ahern continued working on it. Kevin Wolf, + * Jan Kiszka and Vincent Palatin contributed bugfixes. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or(at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * TODO: + * o Downstream port handoff + */ + +#include "hw.h" +#include "qemu-timer.h" +#include "usb.h" +#include "pci.h" +#include "monitor.h" + +#define EHCI_DEBUG 0 +#define STATE_DEBUG 0 /* state transitions */ + +#if EHCI_DEBUG || STATE_DEBUG +#define DPRINTF printf +#else +#define DPRINTF(...) +#endif + +#if STATE_DEBUG +#define DPRINTF_ST DPRINTF +#else +#define DPRINTF_ST(...) +#endif + +/* internal processing - reset HC to try and recover */ +#define USB_RET_PROCERR (-99) + +#define MMIO_SIZE 0x1000 + +/* Capability Registers Base Address - section 2.2 */ +#define CAPREGBASE 0x0000 +#define CAPLENGTH CAPREGBASE + 0x0000 // 1-byte, 0x0001 reserved +#define HCIVERSION CAPREGBASE + 0x0002 // 2-bytes, i/f version # +#define HCSPARAMS CAPREGBASE + 0x0004 // 4-bytes, structural params +#define HCCPARAMS CAPREGBASE + 0x0008 // 4-bytes, capability params +#define EECP HCCPARAMS + 1 +#define HCSPPORTROUTE1 CAPREGBASE + 0x000c +#define HCSPPORTROUTE2 CAPREGBASE + 0x0010 + +#define OPREGBASE 0x0020 // Operational Registers Base Address + +#define USBCMD OPREGBASE + 0x0000 +#define USBCMD_RUNSTOP (1 << 0) // run / Stop +#define USBCMD_HCRESET (1 << 1) // HC Reset +#define USBCMD_FLS (3 << 2) // Frame List Size +#define USBCMD_FLS_SH 2 // Frame List Size Shift +#define USBCMD_PSE (1 << 4) // Periodic Schedule Enable +#define USBCMD_ASE (1 << 5) // Asynch Schedule Enable +#define USBCMD_IAAD (1 << 6) // Int Asynch Advance Doorbell +#define USBCMD_LHCR (1 << 7) // Light Host Controller Reset +#define USBCMD_ASPMC (3 << 8) // Async Sched Park Mode Count +#define USBCMD_ASPME (1 << 11) // Async Sched Park Mode Enable +#define USBCMD_ITC (0x7f << 16) // Int Threshold Control +#define USBCMD_ITC_SH 16 // Int Threshold Control Shift + +#define USBSTS OPREGBASE + 0x0004 +#define USBSTS_RO_MASK 0x0000003f +#define USBSTS_INT (1 << 0) // USB Interrupt +#define USBSTS_ERRINT (1 << 1) // Error Interrupt +#define USBSTS_PCD (1 << 2) // Port Change Detect +#define USBSTS_FLR (1 << 3) // Frame List Rollover +#define USBSTS_HSE (1 << 4) // Host System Error +#define USBSTS_IAA (1 << 5) // Interrupt on Async Advance +#define USBSTS_HALT (1 << 12) // HC Halted +#define USBSTS_REC (1 << 13) // Reclamation +#define USBSTS_PSS (1 << 14) // Periodic Schedule Status +#define USBSTS_ASS (1 << 15) // Asynchronous Schedule Status + +/* + * Interrupt enable bits correspond to the interrupt active bits in USBSTS + * so no need to redefine here. + */ +#define USBINTR OPREGBASE + 0x0008 +#define USBINTR_MASK 0x0000003f + +#define FRINDEX OPREGBASE + 0x000c +#define CTRLDSSEGMENT OPREGBASE + 0x0010 +#define PERIODICLISTBASE OPREGBASE + 0x0014 +#define ASYNCLISTADDR OPREGBASE + 0x0018 +#define ASYNCLISTADDR_MASK 0xffffffe0 + +#define CONFIGFLAG OPREGBASE + 0x0040 + +#define PORTSC (OPREGBASE + 0x0044) +#define PORTSC_BEGIN PORTSC +#define PORTSC_END (PORTSC + 4 * NB_PORTS) +/* + * Bits that are reserverd or are read-only are masked out of values + * written to us by software + */ +#define PORTSC_RO_MASK 0x007021c5 +#define PORTSC_RWC_MASK 0x0000002a +#define PORTSC_WKOC_E (1 << 22) // Wake on Over Current Enable +#define PORTSC_WKDS_E (1 << 21) // Wake on Disconnect Enable +#define PORTSC_WKCN_E (1 << 20) // Wake on Connect Enable +#define PORTSC_PTC (15 << 16) // Port Test Control +#define PORTSC_PTC_SH 16 // Port Test Control shift +#define PORTSC_PIC (3 << 14) // Port Indicator Control +#define PORTSC_PIC_SH 14 // Port Indicator Control Shift +#define PORTSC_POWNER (1 << 13) // Port Owner +#define PORTSC_PPOWER (1 << 12) // Port Power +#define PORTSC_LINESTAT (3 << 10) // Port Line Status +#define PORTSC_LINESTAT_SH 10 // Port Line Status Shift +#define PORTSC_PRESET (1 << 8) // Port Reset +#define PORTSC_SUSPEND (1 << 7) // Port Suspend +#define PORTSC_FPRES (1 << 6) // Force Port Resume +#define PORTSC_OCC (1 << 5) // Over Current Change +#define PORTSC_OCA (1 << 4) // Over Current Active +#define PORTSC_PEDC (1 << 3) // Port Enable/Disable Change +#define PORTSC_PED (1 << 2) // Port Enable/Disable +#define PORTSC_CSC (1 << 1) // Connect Status Change +#define PORTSC_CONNECT (1 << 0) // Current Connect Status + +#define FRAME_TIMER_FREQ 1000 +#define FRAME_TIMER_USEC (1000000 / FRAME_TIMER_FREQ) + +#define NB_MAXINTRATE 8 // Max rate at which controller issues ints +#define NB_PORTS 4 // Number of downstream ports +#define BUFF_SIZE 5*4096 // Max bytes to transfer per transaction +#define MAX_ITERATIONS 20 // Max number of QH before we break the loop +#define MAX_QH 100 // Max allowable queue heads in a chain + +/* Internal periodic / asynchronous schedule state machine states + */ +typedef enum { + EST_INACTIVE = 1000, + EST_ACTIVE, + EST_EXECUTING, + EST_SLEEPING, + /* The following states are internal to the state machine function + */ + EST_WAITLISTHEAD, + EST_FETCHENTRY, + EST_FETCHQH, + EST_FETCHITD, + EST_ADVANCEQUEUE, + EST_FETCHQTD, + EST_EXECUTE, + EST_WRITEBACK, + EST_HORIZONTALQH +} EHCI_STATES; + +/* macros for accessing fields within next link pointer entry */ +#define NLPTR_GET(x) ((x) & 0xffffffe0) +#define NLPTR_TYPE_GET(x) (((x) >> 1) & 3) +#define NLPTR_TBIT(x) ((x) & 1) // 1=invalid, 0=valid + +/* link pointer types */ +#define NLPTR_TYPE_ITD 0 // isoc xfer descriptor +#define NLPTR_TYPE_QH 1 // queue head +#define NLPTR_TYPE_STITD 2 // split xaction, isoc xfer descriptor +#define NLPTR_TYPE_FSTN 3 // frame span traversal node + + +/* EHCI spec version 1.0 Section 3.3 + */ +typedef struct EHCIitd { + uint32_t next; + + uint32_t transact[8]; +#define ITD_XACT_ACTIVE (1 << 31) +#define ITD_XACT_DBERROR (1 << 30) +#define ITD_XACT_BABBLE (1 << 29) +#define ITD_XACT_XACTERR (1 << 28) +#define ITD_XACT_LENGTH_MASK 0x0fff0000 +#define ITD_XACT_LENGTH_SH 16 +#define ITD_XACT_IOC (1 << 15) +#define ITD_XACT_PGSEL_MASK 0x00007000 +#define ITD_XACT_PGSEL_SH 12 +#define ITD_XACT_OFFSET_MASK 0x00000fff + + uint32_t bufptr[7]; +#define ITD_BUFPTR_MASK 0xfffff000 +#define ITD_BUFPTR_SH 12 +#define ITD_BUFPTR_EP_MASK 0x00000f00 +#define ITD_BUFPTR_EP_SH 8 +#define ITD_BUFPTR_DEVADDR_MASK 0x0000007f +#define ITD_BUFPTR_DEVADDR_SH 0 +#define ITD_BUFPTR_DIRECTION (1 << 11) +#define ITD_BUFPTR_MAXPKT_MASK 0x000007ff +#define ITD_BUFPTR_MAXPKT_SH 0 +#define ITD_BUFPTR_MULT_MASK 0x00000003 +} EHCIitd; + +/* EHCI spec version 1.0 Section 3.4 + */ +typedef struct EHCIsitd { + uint32_t next; // Standard next link pointer + uint32_t epchar; +#define SITD_EPCHAR_IO (1 << 31) +#define SITD_EPCHAR_PORTNUM_MASK 0x7f000000 +#define SITD_EPCHAR_PORTNUM_SH 24 +#define SITD_EPCHAR_HUBADD_MASK 0x007f0000 +#define SITD_EPCHAR_HUBADDR_SH 16 +#define SITD_EPCHAR_EPNUM_MASK 0x00000f00 +#define SITD_EPCHAR_EPNUM_SH 8 +#define SITD_EPCHAR_DEVADDR_MASK 0x0000007f + + uint32_t uframe; +#define SITD_UFRAME_CMASK_MASK 0x0000ff00 +#define SITD_UFRAME_CMASK_SH 8 +#define SITD_UFRAME_SMASK_MASK 0x000000ff + + uint32_t results; +#define SITD_RESULTS_IOC (1 << 31) +#define SITD_RESULTS_PGSEL (1 << 30) +#define SITD_RESULTS_TBYTES_MASK 0x03ff0000 +#define SITD_RESULTS_TYBYTES_SH 16 +#define SITD_RESULTS_CPROGMASK_MASK 0x0000ff00 +#define SITD_RESULTS_CPROGMASK_SH 8 +#define SITD_RESULTS_ACTIVE (1 << 7) +#define SITD_RESULTS_ERR (1 << 6) +#define SITD_RESULTS_DBERR (1 << 5) +#define SITD_RESULTS_BABBLE (1 << 4) +#define SITD_RESULTS_XACTERR (1 << 3) +#define SITD_RESULTS_MISSEDUF (1 << 2) +#define SITD_RESULTS_SPLITXSTATE (1 << 1) + + uint32_t bufptr[2]; +#define SITD_BUFPTR_MASK 0xfffff000 +#define SITD_BUFPTR_CURROFF_MASK 0x00000fff +#define SITD_BUFPTR_TPOS_MASK 0x00000018 +#define SITD_BUFPTR_TPOS_SH 3 +#define SITD_BUFPTR_TCNT_MASK 0x00000007 + + uint32_t backptr; // Standard next link pointer +} EHCIsitd; + +/* EHCI spec version 1.0 Section 3.5 + */ +typedef struct EHCIqtd { + uint32_t next; // Standard next link pointer + uint32_t altnext; // Standard next link pointer + uint32_t token; +#define QTD_TOKEN_DTOGGLE (1 << 31) +#define QTD_TOKEN_TBYTES_MASK 0x7fff0000 +#define QTD_TOKEN_TBYTES_SH 16 +#define QTD_TOKEN_IOC (1 << 15) +#define QTD_TOKEN_CPAGE_MASK 0x00007000 +#define QTD_TOKEN_CPAGE_SH 12 +#define QTD_TOKEN_CERR_MASK 0x00000c00 +#define QTD_TOKEN_CERR_SH 10 +#define QTD_TOKEN_PID_MASK 0x00000300 +#define QTD_TOKEN_PID_SH 8 +#define QTD_TOKEN_ACTIVE (1 << 7) +#define QTD_TOKEN_HALT (1 << 6) +#define QTD_TOKEN_DBERR (1 << 5) +#define QTD_TOKEN_BABBLE (1 << 4) +#define QTD_TOKEN_XACTERR (1 << 3) +#define QTD_TOKEN_MISSEDUF (1 << 2) +#define QTD_TOKEN_SPLITXSTATE (1 << 1) +#define QTD_TOKEN_PING (1 << 0) + + uint32_t bufptr[5]; // Standard buffer pointer +#define QTD_BUFPTR_MASK 0xfffff000 +} EHCIqtd; + +/* EHCI spec version 1.0 Section 3.6 + */ +typedef struct EHCIqh { + uint32_t next; // Standard next link pointer + + /* endpoint characteristics */ + uint32_t epchar; +#define QH_EPCHAR_RL_MASK 0xf0000000 +#define QH_EPCHAR_RL_SH 28 +#define QH_EPCHAR_C (1 << 27) +#define QH_EPCHAR_MPLEN_MASK 0x07FF0000 +#define QH_EPCHAR_MPLEN_SH 16 +#define QH_EPCHAR_H (1 << 15) +#define QH_EPCHAR_DTC (1 << 14) +#define QH_EPCHAR_EPS_MASK 0x00003000 +#define QH_EPCHAR_EPS_SH 12 +#define EHCI_QH_EPS_FULL 0 +#define EHCI_QH_EPS_LOW 1 +#define EHCI_QH_EPS_HIGH 2 +#define EHCI_QH_EPS_RESERVED 3 + +#define QH_EPCHAR_EP_MASK 0x00000f00 +#define QH_EPCHAR_EP_SH 8 +#define QH_EPCHAR_I (1 << 7) +#define QH_EPCHAR_DEVADDR_MASK 0x0000007f +#define QH_EPCHAR_DEVADDR_SH 0 + + /* endpoint capabilities */ + uint32_t epcap; +#define QH_EPCAP_MULT_MASK 0xc0000000 +#define QH_EPCAP_MULT_SH 30 +#define QH_EPCAP_PORTNUM_MASK 0x3f800000 +#define QH_EPCAP_PORTNUM_SH 23 +#define QH_EPCAP_HUBADDR_MASK 0x007f0000 +#define QH_EPCAP_HUBADDR_SH 16 +#define QH_EPCAP_CMASK_MASK 0x0000ff00 +#define QH_EPCAP_CMASK_SH 8 +#define QH_EPCAP_SMASK_MASK 0x000000ff +#define QH_EPCAP_SMASK_SH 0 + + uint32_t current_qtd; // Standard next link pointer + uint32_t next_qtd; // Standard next link pointer + uint32_t altnext_qtd; +#define QH_ALTNEXT_NAKCNT_MASK 0x0000001e +#define QH_ALTNEXT_NAKCNT_SH 1 + + uint32_t token; // Same as QTD token + uint32_t bufptr[5]; // Standard buffer pointer +#define BUFPTR_CPROGMASK_MASK 0x000000ff +#define BUFPTR_FRAMETAG_MASK 0x0000001f +#define BUFPTR_SBYTES_MASK 0x00000fe0 +#define BUFPTR_SBYTES_SH 5 +} EHCIqh; + +/* EHCI spec version 1.0 Section 3.7 + */ +typedef struct EHCIfstn { + uint32_t next; // Standard next link pointer + uint32_t backptr; // Standard next link pointer +} EHCIfstn; + +typedef struct { + PCIDevice dev; + qemu_irq irq; + target_phys_addr_t mem_base; + int mem; + int num_ports; + /* + * EHCI spec version 1.0 Section 2.3 + * Host Controller Operational Registers + */ + union { + uint8_t mmio[MMIO_SIZE]; + struct { + uint8_t cap[OPREGBASE]; + uint32_t usbcmd; + uint32_t usbsts; + uint32_t usbintr; + uint32_t frindex; + uint32_t ctrldssegment; + uint32_t periodiclistbase; + uint32_t asynclistaddr; + uint32_t notused[9]; + uint32_t configflag; + uint32_t portsc[NB_PORTS]; + }; + }; + /* + * Internal states, shadow registers, etc + */ + uint32_t sofv; + QEMUTimer *frame_timer; + int attach_poll_counter; + int astate; // Current state in asynchronous schedule + int pstate; // Current state in periodic schedule + USBPort ports[NB_PORTS]; + uint8_t buffer[BUFF_SIZE]; + uint32_t usbsts_pending; + + /* cached data from guest - needs to be flushed + * when guest removes an entry (doorbell, handshake sequence) + */ + EHCIqh qh; // copy of current QH (being worked on) + uint32_t qhaddr; // address QH read from + + EHCIqtd qtd; // copy of current QTD (being worked on) + uint32_t qtdaddr; // address QTD read from + + uint32_t itdaddr; // current ITD + + uint32_t fetch_addr; // which address to look at next + + USBBus bus; + USBPacket usb_packet; + int async_complete; + uint32_t tbytes; + int pid; + int exec_status; + int isoch_pause; + uint32_t last_run_usec; + uint32_t frame_end_usec; +} EHCIState; + +#define SET_LAST_RUN_CLOCK(s) \ + (s)->last_run_usec = qemu_get_clock_ns(vm_clock) / 1000; + +/* nifty macros from Arnon's EHCI version */ +#define get_field(data, field) \ + (((data) & field##_MASK) >> field##_SH) + +#define set_field(data, newval, field) do { \ + uint32_t val = *data; \ + val &= ~ field##_MASK; \ + val |= ((newval) << field##_SH) & field##_MASK; \ + *data = val; \ + } while(0) + + +#if EHCI_DEBUG +static const char *addr2str(unsigned addr) +{ + const char *r = " unknown"; + const char *n[] = { + [ CAPLENGTH ] = " CAPLENGTH", + [ HCIVERSION ] = "HCIVERSION", + [ HCSPARAMS ] = " HCSPARAMS", + [ HCCPARAMS ] = " HCCPARAMS", + [ USBCMD ] = " COMMAND", + [ USBSTS ] = " STATUS", + [ USBINTR ] = " INTERRUPT", + [ FRINDEX ] = " FRAME IDX", + [ PERIODICLISTBASE ] = "P-LIST BASE", + [ ASYNCLISTADDR ] = "A-LIST ADDR", + [ PORTSC_BEGIN ... + PORTSC_END ] = "PORT STATUS", + [ CONFIGFLAG ] = "CONFIG FLAG", + }; + + if (addr < ARRAY_SIZE(n) && n[addr] != NULL) { + return n[addr]; + } else { + return r; + } +} +#endif + + +static inline void ehci_set_interrupt(EHCIState *s, int intr) +{ + int level = 0; + + // TODO honour interrupt threshold requests + + s->usbsts |= intr; + + if ((s->usbsts & USBINTR_MASK) & s->usbintr) { + level = 1; + } + + qemu_set_irq(s->irq, level); +} + +static inline void ehci_record_interrupt(EHCIState *s, int intr) +{ + s->usbsts_pending |= intr; +} + +static inline void ehci_commit_interrupt(EHCIState *s) +{ + if (!s->usbsts_pending) { + return; + } + ehci_set_interrupt(s, s->usbsts_pending); + s->usbsts_pending = 0; +} + +/* Attach or detach a device on root hub */ + +static void ehci_attach(USBPort *port) +{ + EHCIState *s = port->opaque; + uint32_t *portsc = &s->portsc[port->index]; + + DPRINTF("ehci_attach invoked for index %d, portsc 0x%x, desc %s\n", + port->index, *portsc, port->dev->product_desc); + + *portsc |= PORTSC_CONNECT; + *portsc |= PORTSC_CSC; + + /* + * If a high speed device is attached then we own this port(indicated + * by zero in the PORTSC_POWNER bit field) so set the status bit + * and set an interrupt if enabled. + */ + if ( !(*portsc & PORTSC_POWNER)) { + ehci_set_interrupt(s, USBSTS_PCD); + } +} + +static void ehci_detach(USBPort *port) +{ + EHCIState *s = port->opaque; + uint32_t *portsc = &s->portsc[port->index]; + + DPRINTF("ehci_attach invoked for index %d, portsc 0x%x\n", + port->index, *portsc); + + *portsc &= ~PORTSC_CONNECT; + *portsc |= PORTSC_CSC; + + /* + * If a high speed device is attached then we own this port(indicated + * by zero in the PORTSC_POWNER bit field) so set the status bit + * and set an interrupt if enabled. + */ + if ( !(*portsc & PORTSC_POWNER)) { + ehci_set_interrupt(s, USBSTS_PCD); + } +} + +/* 4.1 host controller initialization */ +static void ehci_reset(void *opaque) +{ + EHCIState *s = opaque; + uint8_t *pci_conf; + int i; + + pci_conf = s->dev.config; + + memset(&s->mmio[OPREGBASE], 0x00, MMIO_SIZE - OPREGBASE); + + s->usbcmd = NB_MAXINTRATE << USBCMD_ITC_SH; + s->usbsts = USBSTS_HALT; + + s->astate = EST_INACTIVE; + s->pstate = EST_INACTIVE; + s->async_complete = 0; + s->isoch_pause = -1; + s->attach_poll_counter = 0; + + for(i = 0; i < NB_PORTS; i++) { + s->portsc[i] = PORTSC_POWNER | PORTSC_PPOWER; + + if (s->ports[i].dev) { + usb_attach(&s->ports[i], s->ports[i].dev); + } + } +} + +static uint32_t ehci_mem_readb(void *ptr, target_phys_addr_t addr) +{ + EHCIState *s = ptr; + uint32_t val; + + val = s->mmio[addr]; + + return val; +} + +static uint32_t ehci_mem_readw(void *ptr, target_phys_addr_t addr) +{ + EHCIState *s = ptr; + uint32_t val; + + val = s->mmio[addr] | (s->mmio[addr+1] << 8); + + return val; +} + +static uint32_t ehci_mem_readl(void *ptr, target_phys_addr_t addr) +{ + EHCIState *s = ptr; + uint32_t val; + + val = s->mmio[addr] | (s->mmio[addr+1] << 8) | + (s->mmio[addr+2] << 16) | (s->mmio[addr+3] << 24); + + return val; +} + +static void ehci_mem_writeb(void *ptr, target_phys_addr_t addr, uint32_t val) +{ + fprintf(stderr, "EHCI doesn't handle byte writes to MMIO\n"); + exit(1); +} + +static void ehci_mem_writew(void *ptr, target_phys_addr_t addr, uint32_t val) +{ + fprintf(stderr, "EHCI doesn't handle 16-bit writes to MMIO\n"); + exit(1); +} + +static void handle_port_status_write(EHCIState *s, int port, uint32_t val) +{ + uint32_t *portsc = &s->portsc[port]; + int rwc; + USBDevice *dev = s->ports[port].dev; + + DPRINTF("port_status_write: " + "PORTSC (port %d) curr %08X new %08X rw-clear %08X rw %08X\n", + port, *portsc, val, (val & PORTSC_RWC_MASK), val & PORTSC_RO_MASK); + + rwc = val & PORTSC_RWC_MASK; + val &= PORTSC_RO_MASK; + + // handle_read_write_clear(&val, portsc, PORTSC_PEDC | PORTSC_CSC); + + *portsc &= ~rwc; + + if ((val & PORTSC_PRESET) && !(*portsc & PORTSC_PRESET)) { + DPRINTF("port_status_write: USBTRAN Port %d reset begin\n", port); + } + + if (!(val & PORTSC_PRESET) &&(*portsc & PORTSC_PRESET)) { + DPRINTF("port_status_write: USBTRAN Port %d reset done\n", port); + usb_attach(&s->ports[port], dev); + + // TODO how to handle reset of ports with no device + if (dev) { + usb_send_msg(dev, USB_MSG_RESET); + } + + if (s->ports[port].dev) { + DPRINTF("port_status_write: " + "Device was connected before reset, clearing CSC bit\n"); + *portsc &= ~PORTSC_CSC; + } + + /* Table 2.16 Set the enable bit(and enable bit change) to indicate + * to SW that this port has a high speed device attached + * + * TODO - when to disable? + */ + val |= PORTSC_PED; + val |= PORTSC_PEDC; + } + + *portsc &= ~PORTSC_RO_MASK; + *portsc |= val; + DPRINTF("port_status_write: Port %d status set to 0x%08x\n", port, *portsc); +} + +static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val) +{ + EHCIState *s = ptr; + int i; +#if EHCI_DEBUG + const char *str; +#endif + + /* Only aligned reads are allowed on OHCI */ + if (addr & 3) { + fprintf(stderr, "usb-ehci: Mis-aligned write to addr 0x" + TARGET_FMT_plx "\n", addr); + return; + } + + if (addr >= PORTSC && addr < PORTSC + 4 * NB_PORTS) { + handle_port_status_write(s, (addr-PORTSC)/4, val); + return; + } + + if (addr < OPREGBASE) { + fprintf(stderr, "usb-ehci: write attempt to read-only register" + TARGET_FMT_plx "\n", addr); + return; + } + + + /* Do any register specific pre-write processing here. */ +#if EHCI_DEBUG + str = addr2str((unsigned) addr); +#endif + switch(addr) { + case USBCMD: + DPRINTF("ehci_mem_writel: USBCMD val=0x%08X, current cmd=0x%08X\n", + val, s->usbcmd); + + if ((val & USBCMD_RUNSTOP) && !(s->usbcmd & USBCMD_RUNSTOP)) { + DPRINTF("ehci_mem_writel: %s run, clear halt\n", str); + qemu_mod_timer(s->frame_timer, qemu_get_clock_ns(vm_clock)); + SET_LAST_RUN_CLOCK(s); + s->usbsts &= ~USBSTS_HALT; + } + + if (!(val & USBCMD_RUNSTOP) && (s->usbcmd & USBCMD_RUNSTOP)) { + DPRINTF(" ** STOP **\n"); + qemu_del_timer(s->frame_timer); + // TODO - should finish out some stuff before setting halt + s->usbsts |= USBSTS_HALT; + } + + if (val & USBCMD_HCRESET) { + DPRINTF("ehci_mem_writel: %s run, resetting\n", str); + ehci_reset(s); + val &= ~USBCMD_HCRESET; + } + + /* not supporting dynamic frame list size at the moment */ + if ((val & USBCMD_FLS) && !(s->usbcmd & USBCMD_FLS)) { + fprintf(stderr, "attempt to set frame list size -- value %d\n", + val & USBCMD_FLS); + val &= ~USBCMD_FLS; + } +#if EHCI_DEBUG + if ((val & USBCMD_PSE) && !(s->usbcmd & USBCMD_PSE)) { + DPRINTF("periodic scheduling enabled\n"); + } + if (!(val & USBCMD_PSE) && (s->usbcmd & USBCMD_PSE)) { + DPRINTF("periodic scheduling disabled\n"); + } + if ((val & USBCMD_ASE) && !(s->usbcmd & USBCMD_ASE)) { + DPRINTF("asynchronous scheduling enabled\n"); + } + if (!(val & USBCMD_ASE) && (s->usbcmd & USBCMD_ASE)) { + DPRINTF("asynchronous scheduling disabled\n"); + } + if ((val & USBCMD_IAAD) && !(s->usbcmd & USBCMD_IAAD)) { + DPRINTF("doorbell request received\n"); + } + if ((val & USBCMD_LHCR) && !(s->usbcmd & USBCMD_LHCR)) { + DPRINTF("light host controller reset received\n"); + } + if ((val & USBCMD_ITC) != (s->usbcmd & USBCMD_ITC)) { + DPRINTF("interrupt threshold control set to %x\n", + (val & USBCMD_ITC)>>USBCMD_ITC_SH); + } +#endif + break; + + + case USBSTS: + val &= USBSTS_RO_MASK; // bits 6 thru 31 are RO + DPRINTF("ehci_mem_writel: %s RWC set to 0x%08X\n", str, val); + + val = (s->usbsts &= ~val); // bits 0 thru 5 are R/WC + + DPRINTF("ehci_mem_writel: %s updating interrupt condition\n", str); + ehci_set_interrupt(s, 0); + break; + + + case USBINTR: + val &= USBINTR_MASK; + DPRINTF("ehci_mem_writel: %s set to 0x%08X\n", str, val); + break; + + case FRINDEX: + s->sofv = val >> 3; + DPRINTF("ehci_mem_writel: %s set to 0x%08X\n", str, val); + break; + + case CONFIGFLAG: + DPRINTF("ehci_mem_writel: %s set to 0x%08X\n", str, val); + val &= 0x1; + if (val) { + for(i = 0; i < NB_PORTS; i++) + s->portsc[i] &= ~PORTSC_POWNER; + } + break; + + case PERIODICLISTBASE: + if ((s->usbcmd & USBCMD_PSE) && (s->usbcmd & USBCMD_RUNSTOP)) { + fprintf(stderr, + "ehci: PERIODIC list base register set while periodic schedule\n" + " is enabled and HC is enabled\n"); + } + DPRINTF("ehci_mem_writel: P-LIST BASE set to 0x%08X\n", val); + break; + + case ASYNCLISTADDR: + if ((s->usbcmd & USBCMD_ASE) && (s->usbcmd & USBCMD_RUNSTOP)) { + fprintf(stderr, + "ehci: ASYNC list address register set while async schedule\n" + " is enabled and HC is enabled\n"); + } + DPRINTF("ehci_mem_writel: A-LIST ADDR set to 0x%08X\n", val); + break; + } + + *(uint32_t *)(&s->mmio[addr]) = val; +} + + +// TODO : Put in common header file, duplication from usb-ohci.c + +/* Get an array of dwords from main memory */ +static inline int get_dwords(uint32_t addr, uint32_t *buf, int num) +{ + int i; + + for(i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { + cpu_physical_memory_rw(addr,(uint8_t *)buf, sizeof(*buf), 0); + *buf = le32_to_cpu(*buf); + } + + return 1; +} + +/* Put an array of dwords in to main memory */ +static inline int put_dwords(uint32_t addr, uint32_t *buf, int num) +{ + int i; + + for(i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { + uint32_t tmp = cpu_to_le32(*buf); + cpu_physical_memory_rw(addr,(uint8_t *)&tmp, sizeof(tmp), 1); + } + + return 1; +} + +// 4.10.2 + +static int ehci_qh_do_overlay(EHCIState *ehci, EHCIqh *qh, EHCIqtd *qtd) +{ + int i; + int dtoggle; + int ping; + int eps; + int reload; + + // remember values in fields to preserve in qh after overlay + + dtoggle = qh->token & QTD_TOKEN_DTOGGLE; + ping = qh->token & QTD_TOKEN_PING; + + DPRINTF("setting qh.current from %08X to 0x%08X\n", qh->current_qtd, + ehci->qtdaddr); + qh->current_qtd = ehci->qtdaddr; + qh->next_qtd = qtd->next; + qh->altnext_qtd = qtd->altnext; + qh->token = qtd->token; + + + eps = get_field(qh->epchar, QH_EPCHAR_EPS); + if (eps == EHCI_QH_EPS_HIGH) { + qh->token &= ~QTD_TOKEN_PING; + qh->token |= ping; + } + + reload = get_field(qh->epchar, QH_EPCHAR_RL); + set_field(&qh->altnext_qtd, reload, QH_ALTNEXT_NAKCNT); + + for (i = 0; i < 5; i++) { + qh->bufptr[i] = qtd->bufptr[i]; + } + + if (!(qh->epchar & QH_EPCHAR_DTC)) { + // preserve QH DT bit + qh->token &= ~QTD_TOKEN_DTOGGLE; + qh->token |= dtoggle; + } + + qh->bufptr[1] &= ~BUFPTR_CPROGMASK_MASK; + qh->bufptr[2] &= ~BUFPTR_FRAMETAG_MASK; + + put_dwords(NLPTR_GET(ehci->qhaddr), (uint32_t *) qh, sizeof(EHCIqh) >> 2); + + return 0; +} + +static int ehci_buffer_rw(uint8_t *buffer, EHCIqh *qh, int bytes, int rw) +{ + int bufpos = 0; + int cpage, offset; + uint32_t head; + uint32_t tail; + + + if (!bytes) { + return 0; + } + + cpage = get_field(qh->token, QTD_TOKEN_CPAGE); + if (cpage > 4) { + fprintf(stderr, "cpage out of range (%d)\n", cpage); + return USB_RET_PROCERR; + } + + offset = qh->bufptr[0] & ~QTD_BUFPTR_MASK; + DPRINTF("ehci_buffer_rw: %sing %d bytes %08x cpage %d offset %d\n", + rw ? "writ" : "read", bytes, qh->bufptr[0], cpage, offset); + + do { + /* start and end of this page */ + head = qh->bufptr[cpage] & QTD_BUFPTR_MASK; + tail = head + ~QTD_BUFPTR_MASK + 1; + /* add offset into page */ + head |= offset; + + if (bytes <= (tail - head)) { + tail = head + bytes; + } + + DPRINTF("DATA %s cpage:%d head:%08X tail:%08X target:%08X\n", + rw ? "WRITE" : "READ ", cpage, head, tail, bufpos); + + cpu_physical_memory_rw(head, &buffer[bufpos], tail - head, rw); + + bufpos += (tail - head); + bytes -= (tail - head); + + if (bytes > 0) { + cpage++; + offset = 0; + } + } while (bytes > 0); + + /* save cpage */ + set_field(&qh->token, cpage, QTD_TOKEN_CPAGE); + + /* save offset into cpage */ + offset = tail - head; + qh->bufptr[0] &= ~QTD_BUFPTR_MASK; + qh->bufptr[0] |= offset; + + return 0; +} + +static void ehci_async_complete_packet(USBDevice *dev, USBPacket *packet) +{ + EHCIState *ehci = container_of(packet, EHCIState, usb_packet); + + DPRINTF("Async packet complete\n"); + ehci->async_complete = 1; + ehci->exec_status = packet->len; +} + +static int ehci_execute_complete(EHCIState *ehci, EHCIqh *qh, int ret) +{ + int c_err, reload; + + if (ret == USB_RET_ASYNC && !ehci->async_complete) { + DPRINTF("not done yet\n"); + return ret; + } + + ehci->async_complete = 0; + + DPRINTF("execute_complete: qhaddr 0x%x, next %x, qtdaddr 0x%x, status %d\n", + ehci->qhaddr, qh->next, ehci->qtdaddr, ret); + + if (ret < 0) { +err: + /* TO-DO: put this is in a function that can be invoked below as well */ + c_err = get_field(qh->token, QTD_TOKEN_CERR); + c_err--; + set_field(&qh->token, c_err, QTD_TOKEN_CERR); + + switch(ret) { + case USB_RET_NODEV: + fprintf(stderr, "USB no device\n"); + break; + case USB_RET_STALL: + fprintf(stderr, "USB stall\n"); + qh->token |= QTD_TOKEN_HALT; + ehci_record_interrupt(ehci, USBSTS_ERRINT); + break; + case USB_RET_NAK: + /* 4.10.3 */ + reload = get_field(qh->epchar, QH_EPCHAR_RL); + if ((ehci->pid == USB_TOKEN_IN) && reload) { + int nakcnt = get_field(qh->altnext_qtd, QH_ALTNEXT_NAKCNT); + nakcnt--; + set_field(&qh->altnext_qtd, nakcnt, QH_ALTNEXT_NAKCNT); + } else if (!reload) { + return USB_RET_NAK; + } + break; + case USB_RET_BABBLE: + fprintf(stderr, "USB babble TODO\n"); + qh->token |= QTD_TOKEN_BABBLE; + ehci_record_interrupt(ehci, USBSTS_ERRINT); + break; + default: + fprintf(stderr, "USB invalid response %d to handle\n", ret); + /* TO-DO: transaction error */ + ret = USB_RET_PROCERR; + break; + } + } else { + // DPRINTF("Short packet condition\n"); + // TODO check 4.12 for splits + + if ((ret > ehci->tbytes) && (ehci->pid == USB_TOKEN_IN)) { + ret = USB_RET_BABBLE; + goto err; + } + + if (ehci->tbytes && ehci->pid == USB_TOKEN_IN) { + if (ehci_buffer_rw(ehci->buffer, qh, ret, 1) != 0) { + return USB_RET_PROCERR; + } + ehci->tbytes -= ret; + } else { + ehci->tbytes = 0; + } + + DPRINTF("updating tbytes to %d\n", ehci->tbytes); + set_field(&qh->token, ehci->tbytes, QTD_TOKEN_TBYTES); + } + + qh->token ^= QTD_TOKEN_DTOGGLE; + qh->token &= ~QTD_TOKEN_ACTIVE; + + if ((ret >= 0) && (qh->token & QTD_TOKEN_IOC)) { + ehci_record_interrupt(ehci, USBSTS_INT); + } + + return ret; +} + +// 4.10.3 + +static int ehci_execute(EHCIState *ehci, EHCIqh *qh) +{ + USBPort *port; + USBDevice *dev; + int ret; + int i; + int endp; + int devadr; + + if ( !(qh->token & QTD_TOKEN_ACTIVE)) { + fprintf(stderr, "Attempting to execute inactive QH\n"); + return USB_RET_PROCERR; + } + + ehci->tbytes = (qh->token & QTD_TOKEN_TBYTES_MASK) >> QTD_TOKEN_TBYTES_SH; + if (ehci->tbytes > BUFF_SIZE) { + fprintf(stderr, "Request for more bytes than allowed\n"); + return USB_RET_PROCERR; + } + + ehci->pid = (qh->token & QTD_TOKEN_PID_MASK) >> QTD_TOKEN_PID_SH; + switch(ehci->pid) { + case 0: ehci->pid = USB_TOKEN_OUT; break; + case 1: ehci->pid = USB_TOKEN_IN; break; + case 2: ehci->pid = USB_TOKEN_SETUP; break; + default: fprintf(stderr, "bad token\n"); break; + } + + if ((ehci->tbytes && ehci->pid != USB_TOKEN_IN) && + (ehci_buffer_rw(ehci->buffer, qh, ehci->tbytes, 0) != 0)) { + return USB_RET_PROCERR; + } + + endp = get_field(qh->epchar, QH_EPCHAR_EP); + devadr = get_field(qh->epchar, QH_EPCHAR_DEVADDR); + + ret = USB_RET_NODEV; + + // TO-DO: associating device with ehci port + for(i = 0; i < NB_PORTS; i++) { + port = &ehci->ports[i]; + dev = port->dev; + + // TODO sometime we will also need to check if we are the port owner + + if (!(ehci->portsc[i] &(PORTSC_CONNECT))) { + DPRINTF("Port %d, no exec, not connected(%08X)\n", + i, ehci->portsc[i]); + continue; + } + + ehci->usb_packet.pid = ehci->pid; + ehci->usb_packet.devaddr = devadr; + ehci->usb_packet.devep = endp; + ehci->usb_packet.data = ehci->buffer; + ehci->usb_packet.len = ehci->tbytes; + + ret = usb_handle_packet(dev, &ehci->usb_packet); + + DPRINTF("submit: qh %x next %x qtd %x pid %x len %d (total %d) endp %x ret %d\n", + ehci->qhaddr, qh->next, ehci->qtdaddr, ehci->pid, + ehci->usb_packet.len, ehci->tbytes, endp, ret); + + if (ret != USB_RET_NODEV) { + break; + } + } + + if (ret > BUFF_SIZE) { + fprintf(stderr, "ret from usb_handle_packet > BUFF_SIZE\n"); + return USB_RET_PROCERR; + } + + if (ret == USB_RET_ASYNC) { + ehci->async_complete = 0; + } + + return ret; +} + +/* 4.7.2 + */ + +static int ehci_process_itd(EHCIState *ehci, + EHCIitd *itd) +{ + USBPort *port; + USBDevice *dev; + int ret; + int i, j; + int ptr; + int pid; + int pg; + int len; + int dir; + int devadr; + int endp; + int maxpkt; + + dir =(itd->bufptr[1] & ITD_BUFPTR_DIRECTION); + devadr = get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR); + endp = get_field(itd->bufptr[0], ITD_BUFPTR_EP); + maxpkt = get_field(itd->bufptr[1], ITD_BUFPTR_MAXPKT); + + for(i = 0; i < 8; i++) { + if (itd->transact[i] & ITD_XACT_ACTIVE) { + DPRINTF("ISOCHRONOUS active for frame %d, interval %d\n", + ehci->frindex >> 3, i); + + pg = get_field(itd->transact[i], ITD_XACT_PGSEL); + ptr = (itd->bufptr[pg] & ITD_BUFPTR_MASK) | + (itd->transact[i] & ITD_XACT_OFFSET_MASK); + len = get_field(itd->transact[i], ITD_XACT_LENGTH); + + if (len > BUFF_SIZE) { + return USB_RET_PROCERR; + } + + DPRINTF("ISOCH: buffer %08X len %d\n", ptr, len); + + if (!dir) { + cpu_physical_memory_rw(ptr, &ehci->buffer[0], len, 0); + pid = USB_TOKEN_OUT; + } else + pid = USB_TOKEN_IN; + + ret = USB_RET_NODEV; + + for (j = 0; j < NB_PORTS; j++) { + port = &ehci->ports[j]; + dev = port->dev; + + // TODO sometime we will also need to check if we are the port owner + + if (!(ehci->portsc[j] &(PORTSC_CONNECT))) { + DPRINTF("Port %d, no exec, not connected(%08X)\n", + j, ehci->portsc[j]); + continue; + } + + ehci->usb_packet.pid = ehci->pid; + ehci->usb_packet.devaddr = devadr; + ehci->usb_packet.devep = endp; + ehci->usb_packet.data = ehci->buffer; + ehci->usb_packet.len = len; + + DPRINTF("calling usb_handle_packet\n"); + ret = usb_handle_packet(dev, &ehci->usb_packet); + + if (ret != USB_RET_NODEV) { + break; + } + } + + /* In isoch, there is no facility to indicate a NAK so let's + * instead just complete a zero-byte transaction. Setting + * DBERR seems too draconian. + */ + + if (ret == USB_RET_NAK) { + if (ehci->isoch_pause > 0) { + DPRINTF("ISOCH: received a NAK but paused so returning\n"); + ehci->isoch_pause--; + return 0; + } else if (ehci->isoch_pause == -1) { + DPRINTF("ISOCH: recv NAK & isoch pause inactive, setting\n"); + // Pause frindex for up to 50 msec waiting for data from + // remote + ehci->isoch_pause = 50; + return 0; + } else { + DPRINTF("ISOCH: isoch pause timeout! return 0\n"); + ret = 0; + } + } else { + DPRINTF("ISOCH: received ACK, clearing pause\n"); + ehci->isoch_pause = -1; + } + + if (ret >= 0) { + itd->transact[i] &= ~ITD_XACT_ACTIVE; + + if (itd->transact[i] & ITD_XACT_IOC) { + ehci_record_interrupt(ehci, USBSTS_INT); + } + } + + if (ret >= 0 && dir) { + cpu_physical_memory_rw(ptr, &ehci->buffer[0], len, 1); + + if (ret != len) { + DPRINTF("ISOCH IN expected %d, got %d\n", + len, ret); + set_field(&itd->transact[i], ret, ITD_XACT_LENGTH); + } + } + } + } + return 0; +} + +/* This state is the entry point for asynchronous schedule + * processing. Entry here consitutes a EHCI start event state (4.8.5) + */ +static int ehci_state_waitlisthead(EHCIState *ehci, int async, int *state) +{ + EHCIqh *qh = &ehci->qh; + int i = 0; + int again = 0; + uint32_t entry = ehci->asynclistaddr; + + /* set reclamation flag at start event (4.8.6) */ + if (async) { + ehci->usbsts |= USBSTS_REC; + } + + /* Find the head of the list (4.9.1.1) */ + for(i = 0; i < MAX_QH; i++) { + get_dwords(NLPTR_GET(entry), (uint32_t *) qh, sizeof(EHCIqh) >> 2); + + if (qh->epchar & QH_EPCHAR_H) { + DPRINTF_ST("WAITLISTHEAD: QH %08X is the HEAD of the list\n", + entry); + if (async) { + entry |= (NLPTR_TYPE_QH << 1); + } + + ehci->fetch_addr = entry; + *state = EST_FETCHENTRY; + again = 1; + goto out; + } + + DPRINTF_ST("WAITLISTHEAD: QH %08X is NOT the HEAD of the list\n", + entry); + entry = qh->next; + if (entry == ehci->asynclistaddr) { + DPRINTF("WAITLISTHEAD: reached beginning of QH list\n"); + break; + } + } + + /* no head found for list. */ + + *state = EST_ACTIVE; + +out: + return again; +} + + +/* This state is the entry point for periodic schedule processing as + * well as being a continuation state for async processing. + */ +static int ehci_state_fetchentry(EHCIState *ehci, int async, int *state) +{ + int again = 0; + uint32_t entry = ehci->fetch_addr; + +#if EHCI_DEBUG == 0 + if (qemu_get_clock_ns(vm_clock) / 1000 >= ehci->frame_end_usec) { + if (async) { + DPRINTF("FETCHENTRY: FRAME timer elapsed, exit state machine\n"); + goto out; + } else { + DPRINTF("FETCHENTRY: WARNING " + "- frame timer elapsed during periodic\n"); + } + } +#endif + if (entry < 0x1000) { + DPRINTF("fetchentry: entry invalid (0x%08x)\n", entry); + *state = EST_ACTIVE; + goto out; + } + + /* section 4.8, only QH in async schedule */ + if (async && (NLPTR_TYPE_GET(entry) != NLPTR_TYPE_QH)) { + fprintf(stderr, "non queue head request in async schedule\n"); + return -1; + } + + switch (NLPTR_TYPE_GET(entry)) { + case NLPTR_TYPE_QH: + DPRINTF_ST("FETCHENTRY: entry %X is a Queue Head\n", entry); + *state = EST_FETCHQH; + ehci->qhaddr = entry; + again = 1; + break; + + case NLPTR_TYPE_ITD: + DPRINTF_ST("FETCHENTRY: entry %X is an ITD\n", entry); + *state = EST_FETCHITD; + ehci->itdaddr = entry; + again = 1; + break; + + default: + // TODO: handle siTD and FSTN types + fprintf(stderr, "FETCHENTRY: entry at %X is of type %d " + "which is not supported yet\n", entry, NLPTR_TYPE_GET(entry)); + return -1; + } + +out: + return again; +} + +static int ehci_state_fetchqh(EHCIState *ehci, int async, int *state) +{ + EHCIqh *qh = &ehci->qh; + int reload; + int again = 0; + + get_dwords(NLPTR_GET(ehci->qhaddr), (uint32_t *) qh, sizeof(EHCIqh) >> 2); + + if (async && (qh->epchar & QH_EPCHAR_H)) { + + /* EHCI spec version 1.0 Section 4.8.3 & 4.10.1 */ + if (ehci->usbsts & USBSTS_REC) { + ehci->usbsts &= ~USBSTS_REC; + } else { + DPRINTF("FETCHQH: QH 0x%08x. H-bit set, reclamation status reset" + " - done processing\n", ehci->qhaddr); + *state = EST_ACTIVE; + goto out; + } + } + +#if EHCI_DEBUG + if (ehci->qhaddr != qh->next) { + DPRINTF("FETCHQH: QH 0x%08x (h %x halt %x active %x) next 0x%08x\n", + ehci->qhaddr, + qh->epchar & QH_EPCHAR_H, + qh->token & QTD_TOKEN_HALT, + qh->token & QTD_TOKEN_ACTIVE, + qh->next); + } +#endif + + reload = get_field(qh->epchar, QH_EPCHAR_RL); + if (reload) { + DPRINTF_ST("FETCHQH: reloading nakcnt to %d\n", reload); + set_field(&qh->altnext_qtd, reload, QH_ALTNEXT_NAKCNT); + } + + if (qh->token & QTD_TOKEN_HALT) { + DPRINTF_ST("FETCHQH: QH Halted, go horizontal\n"); + *state = EST_HORIZONTALQH; + again = 1; + + } else if ((qh->token & QTD_TOKEN_ACTIVE) && (qh->current_qtd > 0x1000)) { + DPRINTF_ST("FETCHQH: Active, !Halt, execute - fetch qTD\n"); + ehci->qtdaddr = qh->current_qtd; + *state = EST_FETCHQTD; + again = 1; + + } else { + /* EHCI spec version 1.0 Section 4.10.2 */ + DPRINTF_ST("FETCHQH: !Active, !Halt, advance queue\n"); + *state = EST_ADVANCEQUEUE; + again = 1; + } + +out: + return again; +} + +static int ehci_state_fetchitd(EHCIState *ehci, int async, int *state) +{ + EHCIitd itd; + + get_dwords(NLPTR_GET(ehci->itdaddr),(uint32_t *) &itd, + sizeof(EHCIitd) >> 2); + DPRINTF_ST("FETCHITD: Fetched ITD at address %08X " "(next is %08X)\n", + ehci->itdaddr, itd.next); + + if (ehci_process_itd(ehci, &itd) != 0) { + return -1; + } + + put_dwords(NLPTR_GET(ehci->itdaddr), (uint32_t *) &itd, + sizeof(EHCIitd) >> 2); + ehci->fetch_addr = itd.next; + *state = EST_FETCHENTRY; + + return 1; +} + +/* Section 4.10.2 - paragraph 3 */ +static int ehci_state_advqueue(EHCIState *ehci, int async, int *state) +{ +#if 0 + /* TO-DO: 4.10.2 - paragraph 2 + * if I-bit is set to 1 and QH is not active + * go to horizontal QH + */ + if (I-bit set) { + *state = EST_HORIZONTALQH; + goto out; + } +#endif + + /* + * want data and alt-next qTD is valid + */ + if (((ehci->qh.token & QTD_TOKEN_TBYTES_MASK) != 0) && + (ehci->qh.altnext_qtd > 0x1000) && + (NLPTR_TBIT(ehci->qh.altnext_qtd) == 0)) { + DPRINTF_ST("ADVQUEUE: goto alt next qTD. " + "curr 0x%08x next 0x%08x alt 0x%08x (next qh %x)\n", + ehci->qh.current_qtd, ehci->qh.altnext_qtd, + ehci->qh.next_qtd, ehci->qh.next); + ehci->qtdaddr = ehci->qh.altnext_qtd; + *state = EST_FETCHQTD; + + /* + * next qTD is valid + */ + } else if ((ehci->qh.next_qtd > 0x1000) && + (NLPTR_TBIT(ehci->qh.next_qtd) == 0)) { + DPRINTF_ST("ADVQUEUE: next qTD. " + "curr 0x%08x next 0x%08x alt 0x%08x (next qh %x)\n", + ehci->qh.current_qtd, ehci->qh.altnext_qtd, + ehci->qh.next_qtd, ehci->qh.next); + ehci->qtdaddr = ehci->qh.next_qtd; + *state = EST_FETCHQTD; + + /* + * no valid qTD, try next QH + */ + } else { + DPRINTF_ST("ADVQUEUE: go to horizontal QH\n"); + *state = EST_HORIZONTALQH; + } + + return 1; +} + +/* Section 4.10.2 - paragraph 4 */ +static int ehci_state_fetchqtd(EHCIState *ehci, int async, int *state) +{ + EHCIqtd *qtd = &ehci->qtd; + int again = 0; + + get_dwords(NLPTR_GET(ehci->qtdaddr),(uint32_t *) qtd, sizeof(EHCIqtd) >> 2); + + if (qtd->token & QTD_TOKEN_ACTIVE) { + *state = EST_EXECUTE; + again = 1; + } else { + *state = EST_HORIZONTALQH; + again = 1; + } + + return again; +} + +static int ehci_state_horizqh(EHCIState *ehci, int async, int *state) +{ + int again = 0; + + if (ehci->fetch_addr != ehci->qh.next) { + ehci->fetch_addr = ehci->qh.next; + *state = EST_FETCHENTRY; + again = 1; + } else { + *state = EST_ACTIVE; + } + + return again; +} + +static int ehci_state_execute(EHCIState *ehci, int async, int *state) +{ + EHCIqh *qh = &ehci->qh; + EHCIqtd *qtd = &ehci->qtd; + int again = 0; + int reload, nakcnt; + int smask; + + if (async) { + DPRINTF_ST(">>>>> ASYNC STATE MACHINE execute QH 0x%08x, QTD 0x%08x\n", + ehci->qhaddr, ehci->qtdaddr); + } else { + DPRINTF_ST(">>>>> PERIODIC STATE MACHINE execute\n"); + } + + if (ehci_qh_do_overlay(ehci, qh, qtd) != 0) { + return -1; + } + + smask = get_field(qh->epcap, QH_EPCAP_SMASK); + + if (!smask) { + reload = get_field(qh->epchar, QH_EPCHAR_RL); + nakcnt = get_field(qh->altnext_qtd, QH_ALTNEXT_NAKCNT); + if (reload && !nakcnt) { + DPRINTF_ST("EXECUTE: RL != 0 but NakCnt == 0 -- no execute\n"); + *state = EST_HORIZONTALQH; + again = 1; + goto out; + } + } + + // TODO verify enough time remains in the uframe as in 4.4.1.1 + // TODO write back ptr to async list when done or out of time + // TODO Windows does not seem to ever set the MULT field + + if (!async) { + int transactCtr = get_field(qh->epcap, QH_EPCAP_MULT); + if (!transactCtr) { + DPRINTF("ZERO transactctr for int qh, go HORIZ\n"); + *state = EST_HORIZONTALQH; + again = 1; + goto out; + } + } + + if (async) { + ehci->usbsts |= USBSTS_REC; + } + + ehci->exec_status = ehci_execute(ehci, qh); + if (ehci->exec_status == USB_RET_PROCERR) { + again = -1; + goto out; + } + *state = EST_EXECUTING; + + if (ehci->exec_status != USB_RET_ASYNC) { + again = 1; + } + +out: + return again; +} + +static int ehci_state_executing(EHCIState *ehci, int async, int *state) +{ + EHCIqh *qh = &ehci->qh; + int again = 0; + int reload, nakcnt; + + ehci->exec_status = ehci_execute_complete(ehci, qh, ehci->exec_status); + if (ehci->exec_status == USB_RET_ASYNC) { + goto out; + } + if (ehci->exec_status == USB_RET_PROCERR) { + again = -1; + goto out; + } + + // 4.10.3 + if (!async) { + int transactCtr = get_field(qh->epcap, QH_EPCAP_MULT); + transactCtr--; + set_field(&qh->epcap, transactCtr, QH_EPCAP_MULT); + // 4.10.3, bottom of page 82, should exit this state when transaction + // counter decrements to 0 + } + + + reload = get_field(qh->epchar, QH_EPCHAR_RL); + if (reload) { + nakcnt = get_field(qh->altnext_qtd, QH_ALTNEXT_NAKCNT); + if (ehci->exec_status == USB_RET_NAK) { + if (nakcnt) { + nakcnt--; + } + DPRINTF_ST("EXECUTING: Nak occured and RL != 0, dec NakCnt to %d\n", + nakcnt); + } else { + nakcnt = reload; + DPRINTF_ST("EXECUTING: Nak didn't occur, reloading to %d\n", + nakcnt); + } + set_field(&qh->altnext_qtd, nakcnt, QH_ALTNEXT_NAKCNT); + } + + /* + * Write the qh back to guest physical memory. This step isn't + * in the EHCI spec but we need to do it since we don't share + * physical memory with our guest VM. + */ + + DPRINTF("EXECUTING: write QH to VM memory: qhaddr 0x%x, next 0x%x\n", + ehci->qhaddr, qh->next); + put_dwords(NLPTR_GET(ehci->qhaddr), (uint32_t *) qh, sizeof(EHCIqh) >> 2); + + /* 4.10.5 */ + if ((ehci->exec_status == USB_RET_NAK) || (qh->token & QTD_TOKEN_ACTIVE)) { + *state = EST_HORIZONTALQH; + } else { + *state = EST_WRITEBACK; + } + + again = 1; + +out: + return again; +} + + +static int ehci_state_writeback(EHCIState *ehci, int async, int *state) +{ + EHCIqh *qh = &ehci->qh; + int again = 0; + + /* Write back the QTD from the QH area */ + DPRINTF_ST("WRITEBACK: write QTD to VM memory\n"); + put_dwords(NLPTR_GET(ehci->qtdaddr),(uint32_t *) &qh->next_qtd, + sizeof(EHCIqtd) >> 2); + + /* TODO confirm next state. For now, keep going if async + * but stop after one qtd if periodic + */ + //if (async) { + *state = EST_ADVANCEQUEUE; + again = 1; + //} else { + // *state = EST_ACTIVE; + //} + return again; +} + +/* + * This is the state machine that is common to both async and periodic + */ + +static int ehci_advance_state(EHCIState *ehci, + int async, + int state) +{ + int again; + int iter = 0; + + do { + if (state == EST_FETCHQH) { + iter++; + /* if we are roaming a lot of QH without executing a qTD + * something is wrong with the linked list. TO-DO: why is + * this hack needed? + */ + if (iter > MAX_ITERATIONS) { + DPRINTF("\n*** advance_state: bailing on MAX ITERATIONS***\n"); + state = EST_ACTIVE; + break; + } + } + switch(state) { + case EST_WAITLISTHEAD: + again = ehci_state_waitlisthead(ehci, async, &state); + break; + + case EST_FETCHENTRY: + again = ehci_state_fetchentry(ehci, async, &state); + break; + + case EST_FETCHQH: + again = ehci_state_fetchqh(ehci, async, &state); + break; + + case EST_FETCHITD: + again = ehci_state_fetchitd(ehci, async, &state); + break; + + case EST_ADVANCEQUEUE: + again = ehci_state_advqueue(ehci, async, &state); + break; + + case EST_FETCHQTD: + again = ehci_state_fetchqtd(ehci, async, &state); + break; + + case EST_HORIZONTALQH: + again = ehci_state_horizqh(ehci, async, &state); + break; + + case EST_EXECUTE: + iter = 0; + again = ehci_state_execute(ehci, async, &state); + break; + + case EST_EXECUTING: + again = ehci_state_executing(ehci, async, &state); + break; + + case EST_WRITEBACK: + again = ehci_state_writeback(ehci, async, &state); + break; + + default: + fprintf(stderr, "Bad state!\n"); + again = -1; + break; + } + + if (again < 0) { + fprintf(stderr, "processing error - resetting ehci HC\n"); + ehci_reset(ehci); + again = 0; + } + } + while (again); + + ehci_commit_interrupt(ehci); + return state; +} + +static void ehci_advance_async_state(EHCIState *ehci) +{ + EHCIqh qh; + int state = ehci->astate; + + switch(state) { + case EST_INACTIVE: + if (!(ehci->usbcmd & USBCMD_ASE)) { + break; + } + ehci->usbsts |= USBSTS_ASS; + ehci->astate = EST_ACTIVE; + // No break, fall through to ACTIVE + + case EST_ACTIVE: + if ( !(ehci->usbcmd & USBCMD_ASE)) { + ehci->usbsts &= ~USBSTS_ASS; + ehci->astate = EST_INACTIVE; + break; + } + + /* If the doorbell is set, the guest wants to make a change to the + * schedule. The host controller needs to release cached data. + * (section 4.8.2) + */ + if (ehci->usbcmd & USBCMD_IAAD) { + DPRINTF("ASYNC: doorbell request acknowledged\n"); + ehci->usbcmd &= ~USBCMD_IAAD; + ehci_set_interrupt(ehci, USBSTS_IAA); + break; + } + + /* make sure guest has acknowledged */ + /* TO-DO: is this really needed? */ + if (ehci->usbsts & USBSTS_IAA) { + DPRINTF("IAA status bit still set.\n"); + break; + } + + DPRINTF_ST("ASYNC: waiting for listhead, starting at %08x\n", + ehci->asynclistaddr); + /* check that address register has been set */ + if (ehci->asynclistaddr == 0) { + break; + } + + state = EST_WAITLISTHEAD; + /* fall through */ + + case EST_FETCHENTRY: + /* fall through */ + + case EST_EXECUTING: + get_dwords(NLPTR_GET(ehci->qhaddr), (uint32_t *) &qh, + sizeof(EHCIqh) >> 2); + ehci->astate = ehci_advance_state(ehci, 1, state); + break; + + default: + /* this should only be due to a developer mistake */ + fprintf(stderr, "ehci: Bad asynchronous state %d. " + "Resetting to active\n", ehci->astate); + ehci->astate = EST_ACTIVE; + } +} + +static void ehci_advance_periodic_state(EHCIState *ehci) +{ + uint32_t entry; + uint32_t list; + + // 4.6 + + switch(ehci->pstate) { + case EST_INACTIVE: + if ( !(ehci->frindex & 7) && (ehci->usbcmd & USBCMD_PSE)) { + DPRINTF("PERIODIC going active\n"); + ehci->usbsts |= USBSTS_PSS; + ehci->pstate = EST_ACTIVE; + // No break, fall through to ACTIVE + } else + break; + + case EST_ACTIVE: + if ( !(ehci->frindex & 7) && !(ehci->usbcmd & USBCMD_PSE)) { + DPRINTF("PERIODIC going inactive\n"); + ehci->usbsts &= ~USBSTS_PSS; + ehci->pstate = EST_INACTIVE; + break; + } + + list = ehci->periodiclistbase & 0xfffff000; + /* check that register has been set */ + if (list == 0) { + break; + } + list |= ((ehci->frindex & 0x1ff8) >> 1); + + cpu_physical_memory_rw(list, (uint8_t *) &entry, sizeof entry, 0); + entry = le32_to_cpu(entry); + + DPRINTF("PERIODIC state adv fr=%d. [%08X] -> %08X\n", + ehci->frindex / 8, list, entry); + ehci->fetch_addr = entry; + ehci->pstate = ehci_advance_state(ehci, 0, EST_FETCHENTRY); + break; + + case EST_EXECUTING: + DPRINTF("PERIODIC state adv for executing\n"); + ehci->pstate = ehci_advance_state(ehci, 0, EST_EXECUTING); + break; + + default: + /* this should only be due to a developer mistake */ + fprintf(stderr, "ehci: Bad periodic state %d. " + "Resetting to active\n", ehci->pstate); + ehci->pstate = EST_ACTIVE; + } +} + +static void ehci_frame_timer(void *opaque) +{ + EHCIState *ehci = opaque; + int64_t expire_time, t_now; + int usec_elapsed; + int frames; + int usec_now; + int i; + int skipped_frames = 0; + + + t_now = qemu_get_clock_ns(vm_clock); + expire_time = t_now + (get_ticks_per_sec() / FRAME_TIMER_FREQ); + if (expire_time == t_now) { + expire_time++; + } + + usec_now = t_now / 1000; + usec_elapsed = usec_now - ehci->last_run_usec; + frames = usec_elapsed / FRAME_TIMER_USEC; + ehci->frame_end_usec = usec_now + FRAME_TIMER_USEC - 10; + + for (i = 0; i < frames; i++) { + if ( !(ehci->usbsts & USBSTS_HALT)) { + if (ehci->isoch_pause <= 0) { + ehci->frindex += 8; + } + + if (ehci->frindex > 0x00001fff) { + ehci->frindex = 0; + ehci_set_interrupt(ehci, USBSTS_FLR); + } + + ehci->sofv = (ehci->frindex - 1) >> 3; + ehci->sofv &= 0x000003ff; + } + + if (frames - i > 10) { + skipped_frames++; + } else { + // TODO could this cause periodic frames to get skipped if async + // active? + if (ehci->astate != EST_EXECUTING) { + ehci_advance_periodic_state(ehci); + } + } + + ehci->last_run_usec += FRAME_TIMER_USEC; + } + +#if 0 + if (skipped_frames) { + DPRINTF("WARNING - EHCI skipped %d frames\n", skipped_frames); + } +#endif + + /* Async is not inside loop since it executes everything it can once + * called + */ + if (ehci->pstate != EST_EXECUTING) { + ehci_advance_async_state(ehci); + } + + qemu_mod_timer(ehci->frame_timer, expire_time); +} + +static CPUReadMemoryFunc *ehci_readfn[3]={ + ehci_mem_readb, + ehci_mem_readw, + ehci_mem_readl +}; + +static CPUWriteMemoryFunc *ehci_writefn[3]={ + ehci_mem_writeb, + ehci_mem_writew, + ehci_mem_writel +}; + +static void ehci_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + EHCIState *s =(EHCIState *)pci_dev; + + DPRINTF("ehci_map: region %d, addr %08" PRIx64 ", size %" PRId64 ", s->mem %08X\n", + region_num, addr, size, s->mem); + s->mem_base = addr; + cpu_register_physical_memory(addr, size, s->mem); +} + +static int usb_ehci_initfn(PCIDevice *dev); + +static USBPortOps ehci_port_ops = { + .attach = ehci_attach, + .detach = ehci_detach, + .complete = ehci_async_complete_packet, +}; + +static PCIDeviceInfo ehci_info = { + .qdev.name = "usb-ehci", + .qdev.size = sizeof(EHCIState), + .init = usb_ehci_initfn, +}; + +static int usb_ehci_initfn(PCIDevice *dev) +{ + EHCIState *s = DO_UPCAST(EHCIState, dev, dev); + uint8_t *pci_conf = s->dev.config; + int i; + + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_INTEL_82801D); + pci_set_byte(&pci_conf[PCI_REVISION_ID], 0x10); + pci_set_byte(&pci_conf[PCI_CLASS_PROG], 0x20); + pci_config_set_class(pci_conf, PCI_CLASS_SERIAL_USB); + pci_set_byte(&pci_conf[PCI_HEADER_TYPE], PCI_HEADER_TYPE_NORMAL); + + /* capabilities pointer */ + pci_set_byte(&pci_conf[PCI_CAPABILITY_LIST], 0x00); + //pci_set_byte(&pci_conf[PCI_CAPABILITY_LIST], 0x50); + + pci_set_byte(&pci_conf[PCI_INTERRUPT_PIN], 4); // interrupt pin 3 + pci_set_byte(&pci_conf[PCI_MIN_GNT], 0); + pci_set_byte(&pci_conf[PCI_MAX_LAT], 0); + + // pci_conf[0x50] = 0x01; // power management caps + + pci_set_byte(&pci_conf[0x60], 0x20); // spec release number (2.1.4) + pci_set_byte(&pci_conf[0x61], 0x20); // frame length adjustment (2.1.5) + pci_set_word(&pci_conf[0x62], 0x00); // port wake up capability (2.1.6) + + pci_conf[0x64] = 0x00; + pci_conf[0x65] = 0x00; + pci_conf[0x66] = 0x00; + pci_conf[0x67] = 0x00; + pci_conf[0x68] = 0x01; + pci_conf[0x69] = 0x00; + pci_conf[0x6a] = 0x00; + pci_conf[0x6b] = 0x00; // USBLEGSUP + pci_conf[0x6c] = 0x00; + pci_conf[0x6d] = 0x00; + pci_conf[0x6e] = 0x00; + pci_conf[0x6f] = 0xc0; // USBLEFCTLSTS + + // 2.2 host controller interface version + s->mmio[0x00] = (uint8_t) OPREGBASE; + s->mmio[0x01] = 0x00; + s->mmio[0x02] = 0x00; + s->mmio[0x03] = 0x01; // HC version + s->mmio[0x04] = NB_PORTS; // Number of downstream ports + s->mmio[0x05] = 0x00; // No companion ports at present + s->mmio[0x06] = 0x00; + s->mmio[0x07] = 0x00; + s->mmio[0x08] = 0x80; // We can cache whole frame, not 64-bit capable + s->mmio[0x09] = 0x68; // EECP + s->mmio[0x0a] = 0x00; + s->mmio[0x0b] = 0x00; + + s->irq = s->dev.irq[3]; + + usb_bus_new(&s->bus, &s->dev.qdev); + for(i = 0; i < NB_PORTS; i++) { + usb_register_port(&s->bus, &s->ports[i], s, i, &ehci_port_ops, + USB_SPEED_MASK_HIGH); + usb_port_location(&s->ports[i], NULL, i+1); + s->ports[i].dev = 0; + } + + s->frame_timer = qemu_new_timer_ns(vm_clock, ehci_frame_timer, s); + + qemu_register_reset(ehci_reset, s); + + s->mem = cpu_register_io_memory(ehci_readfn, ehci_writefn, s, + DEVICE_LITTLE_ENDIAN); + + pci_register_bar(&s->dev, 0, MMIO_SIZE, PCI_BASE_ADDRESS_SPACE_MEMORY, + ehci_map); + + fprintf(stderr, "*** EHCI support is under development ***\n"); + + return 0; +} + +static void ehci_register(void) +{ + pci_qdev_register(&ehci_info); +} +device_init(ehci_register); + +/* + * vim: expandtab ts=4 + */ diff --git a/hw/usb-hid.c b/hw/usb-hid.c index 89c293c466..53b261c3b9 100644 --- a/hw/usb-hid.c +++ b/hw/usb-hid.c @@ -211,6 +211,7 @@ static const USBDescDevice desc_device_mouse = { .iConfiguration = STR_CONFIG_MOUSE, .bmAttributes = 0xa0, .bMaxPower = 50, + .nif = 1, .ifs = &desc_iface_mouse, }, }, @@ -227,6 +228,7 @@ static const USBDescDevice desc_device_tablet = { .iConfiguration = STR_CONFIG_TABLET, .bmAttributes = 0xa0, .bMaxPower = 50, + .nif = 1, .ifs = &desc_iface_tablet, }, }, @@ -243,6 +245,7 @@ static const USBDescDevice desc_device_keyboard = { .iConfiguration = STR_CONFIG_KEYBOARD, .bmAttributes = 0xa0, .bMaxPower = 50, + .nif = 1, .ifs = &desc_iface_keyboard, }, }, @@ -724,13 +727,13 @@ static void usb_hid_set_next_idle(USBHIDState *s, int64_t curtime) s->next_idle_clock = curtime + (get_ticks_per_sec() * s->idle * 4) / 1000; } -static int usb_hid_handle_control(USBDevice *dev, int request, int value, - int index, int length, uint8_t *data) +static int usb_hid_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) { USBHIDState *s = (USBHIDState *)dev; int ret; - ret = usb_desc_handle_control(dev, request, value, index, length, data); + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); if (ret >= 0) { return ret; } diff --git a/hw/usb-hub.c b/hw/usb-hub.c index e0588f891d..6e2a35839d 100644 --- a/hw/usb-hub.c +++ b/hw/usb-hub.c @@ -119,6 +119,7 @@ static const USBDescDevice desc_device_hub = { .bNumInterfaces = 1, .bConfigurationValue = 1, .bmAttributes = 0xe0, + .nif = 1, .ifs = &desc_iface_hub, }, }, @@ -284,13 +285,13 @@ static void usb_hub_handle_reset(USBDevice *dev) /* XXX: do it */ } -static int usb_hub_handle_control(USBDevice *dev, int request, int value, - int index, int length, uint8_t *data) +static int usb_hub_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) { USBHubState *s = (USBHubState *)dev; int ret; - ret = usb_desc_handle_control(dev, request, value, index, length, data); + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); if (ret >= 0) { return ret; } @@ -494,7 +495,7 @@ static int usb_hub_broadcast_packet(USBHubState *s, USBPacket *p) port = &s->ports[i]; dev = port->port.dev; if (dev && (port->wPortStatus & PORT_STAT_ENABLE)) { - ret = dev->info->handle_packet(dev, p); + ret = usb_handle_packet(dev, p); if (ret != USB_RET_NODEV) { return ret; } diff --git a/hw/usb-msd.c b/hw/usb-msd.c index bd1c3a415f..c59797b27e 100644 --- a/hw/usb-msd.c +++ b/hw/usb-msd.c @@ -48,6 +48,7 @@ typedef struct { uint32_t data_len; uint32_t residue; uint32_t tag; + SCSIRequest *req; SCSIBus bus; BlockConf conf; SCSIDevice *scsi_dev; @@ -119,6 +120,7 @@ static const USBDescDevice desc_device_full = { .bConfigurationValue = 1, .iConfiguration = STR_CONFIG_FULL, .bmAttributes = 0xc0, + .nif = 1, .ifs = &desc_iface_full, }, }, @@ -153,6 +155,7 @@ static const USBDescDevice desc_device_high = { .bConfigurationValue = 1, .iConfiguration = STR_CONFIG_HIGH, .bmAttributes = 0xc0, + .nif = 1, .ifs = &desc_iface_high, }, }, @@ -189,11 +192,7 @@ static void usb_msd_copy_data(MSDState *s) s->scsi_buf += len; s->data_len -= len; if (s->scsi_len == 0 || s->data_len == 0) { - if (s->mode == USB_MSDM_DATAIN) { - s->scsi_dev->info->read_data(s->scsi_dev, s->tag); - } else if (s->mode == USB_MSDM_DATAOUT) { - s->scsi_dev->info->write_data(s->scsi_dev, s->tag); - } + scsi_req_continue(s->req); } } @@ -211,47 +210,21 @@ static void usb_msd_send_status(MSDState *s, USBPacket *p) memcpy(p->data, &csw, len); } -static void usb_msd_command_complete(SCSIBus *bus, int reason, uint32_t tag, - uint32_t arg) +static void usb_msd_transfer_data(SCSIRequest *req, uint32_t len) { - MSDState *s = DO_UPCAST(MSDState, dev.qdev, bus->qbus.parent); + MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent); USBPacket *p = s->packet; - if (tag != s->tag) { - fprintf(stderr, "usb-msd: Unexpected SCSI Tag 0x%x\n", tag); + if (req->tag != s->tag) { + fprintf(stderr, "usb-msd: Unexpected SCSI Tag 0x%x\n", req->tag); } - if (reason == SCSI_REASON_DONE) { - DPRINTF("Command complete %d\n", arg); - s->residue = s->data_len; - s->result = arg != 0; - if (s->packet) { - if (s->data_len == 0 && s->mode == USB_MSDM_DATAOUT) { - /* A deferred packet with no write data remaining must be - the status read packet. */ - usb_msd_send_status(s, p); - s->mode = USB_MSDM_CBW; - } else { - if (s->data_len) { - s->data_len -= s->usb_len; - if (s->mode == USB_MSDM_DATAIN) - memset(s->usb_buf, 0, s->usb_len); - s->usb_len = 0; - } - if (s->data_len == 0) - s->mode = USB_MSDM_CSW; - } - s->packet = NULL; - usb_packet_complete(&s->dev, p); - } else if (s->data_len == 0) { - s->mode = USB_MSDM_CSW; - } - return; - } - s->scsi_len = arg; - s->scsi_buf = s->scsi_dev->info->get_buf(s->scsi_dev, tag); + + assert((s->mode == USB_MSDM_DATAOUT) == (req->cmd.mode == SCSI_XFER_TO_DEV)); + s->scsi_len = len; + s->scsi_buf = scsi_req_get_buf(req); if (p) { usb_msd_copy_data(s); - if (s->usb_len == 0) { + if (s->packet && s->usb_len == 0) { /* Set s->packet to NULL before calling usb_packet_complete because another request may be issued before usb_packet_complete returns. */ @@ -262,6 +235,56 @@ static void usb_msd_command_complete(SCSIBus *bus, int reason, uint32_t tag, } } +static void usb_msd_command_complete(SCSIRequest *req, uint32_t status) +{ + MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent); + USBPacket *p = s->packet; + + if (req->tag != s->tag) { + fprintf(stderr, "usb-msd: Unexpected SCSI Tag 0x%x\n", req->tag); + } + DPRINTF("Command complete %d\n", status); + s->residue = s->data_len; + s->result = status != 0; + if (s->packet) { + if (s->data_len == 0 && s->mode == USB_MSDM_DATAOUT) { + /* A deferred packet with no write data remaining must be + the status read packet. */ + usb_msd_send_status(s, p); + s->mode = USB_MSDM_CBW; + } else { + if (s->data_len) { + s->data_len -= s->usb_len; + if (s->mode == USB_MSDM_DATAIN) { + memset(s->usb_buf, 0, s->usb_len); + } + s->usb_len = 0; + } + if (s->data_len == 0) { + s->mode = USB_MSDM_CSW; + } + } + s->packet = NULL; + usb_packet_complete(&s->dev, p); + } else if (s->data_len == 0) { + s->mode = USB_MSDM_CSW; + } + scsi_req_unref(req); + s->req = NULL; +} + +static void usb_msd_request_cancelled(SCSIRequest *req) +{ + MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent); + + if (req == s->req) { + scsi_req_unref(s->req); + s->req = NULL; + s->packet = NULL; + s->scsi_len = 0; + } +} + static void usb_msd_handle_reset(USBDevice *dev) { MSDState *s = (MSDState *)dev; @@ -270,13 +293,13 @@ static void usb_msd_handle_reset(USBDevice *dev) s->mode = USB_MSDM_CBW; } -static int usb_msd_handle_control(USBDevice *dev, int request, int value, - int index, int length, uint8_t *data) +static int usb_msd_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) { MSDState *s = (MSDState *)dev; int ret; - ret = usb_desc_handle_control(dev, request, value, index, length, data); + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); if (ret >= 0) { return ret; } @@ -313,12 +336,10 @@ static int usb_msd_handle_control(USBDevice *dev, int request, int value, return ret; } -static void usb_msd_cancel_io(USBPacket *p, void *opaque) +static void usb_msd_cancel_io(USBDevice *dev, USBPacket *p) { - MSDState *s = opaque; - s->scsi_dev->info->cancel_io(s->scsi_dev, s->tag); - s->packet = NULL; - s->scsi_len = 0; + MSDState *s = DO_UPCAST(MSDState, dev, dev); + scsi_req_cancel(s->req); } static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) @@ -365,15 +386,12 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) s->tag, cbw.flags, cbw.cmd_len, s->data_len); s->residue = 0; s->scsi_len = 0; - s->scsi_dev->info->send_command(s->scsi_dev, s->tag, cbw.cmd, 0); + s->req = scsi_req_new(s->scsi_dev, s->tag, 0); + scsi_req_enqueue(s->req, cbw.cmd); /* ??? Should check that USB and SCSI data transfer directions match. */ - if (s->residue == 0) { - if (s->mode == USB_MSDM_DATAIN) { - s->scsi_dev->info->read_data(s->scsi_dev, s->tag); - } else if (s->mode == USB_MSDM_DATAOUT) { - s->scsi_dev->info->write_data(s->scsi_dev, s->tag); - } + if (s->mode != USB_MSDM_CSW && s->residue == 0) { + scsi_req_continue(s->req); } ret = len; break; @@ -396,7 +414,6 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) } if (s->usb_len) { DPRINTF("Deferring packet %p\n", p); - usb_defer_packet(p, usb_msd_cancel_io, s); s->packet = p; ret = USB_RET_ASYNC; } else { @@ -419,7 +436,6 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) if (s->data_len != 0 || len < 13) goto fail; /* Waiting for SCSI write to complete. */ - usb_defer_packet(p, usb_msd_cancel_io, s); s->packet = p; ret = USB_RET_ASYNC; break; @@ -453,7 +469,6 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) } if (s->usb_len) { DPRINTF("Deferring packet %p\n", p); - usb_defer_packet(p, usb_msd_cancel_io, s); s->packet = p; ret = USB_RET_ASYNC; } else { @@ -487,6 +502,12 @@ static void usb_msd_password_cb(void *opaque, int err) qdev_unplug(&s->dev.qdev); } +static const struct SCSIBusOps usb_msd_scsi_ops = { + .transfer_data = usb_msd_transfer_data, + .complete = usb_msd_command_complete, + .cancel = usb_msd_request_cancelled +}; + static int usb_msd_initfn(USBDevice *dev) { MSDState *s = DO_UPCAST(MSDState, dev, dev); @@ -516,7 +537,7 @@ static int usb_msd_initfn(USBDevice *dev) } usb_desc_init(dev); - scsi_bus_new(&s->bus, &s->dev.qdev, 0, 1, usb_msd_command_complete); + scsi_bus_new(&s->bus, &s->dev.qdev, 0, 1, &usb_msd_scsi_ops); s->scsi_dev = scsi_bus_legacy_add_drive(&s->bus, bs, 0, !!s->removable); if (!s->scsi_dev) { return -1; @@ -602,6 +623,7 @@ static struct USBDeviceInfo msd_info = { .usb_desc = &desc, .init = usb_msd_initfn, .handle_packet = usb_generic_handle_packet, + .cancel_packet = usb_msd_cancel_io, .handle_attach = usb_desc_attach, .handle_reset = usb_msd_handle_reset, .handle_control = usb_msd_handle_control, diff --git a/hw/usb-musb.c b/hw/usb-musb.c index 38986d3689..6037193db8 100644 --- a/hw/usb-musb.c +++ b/hw/usb-musb.c @@ -601,7 +601,7 @@ static void musb_packet(MUSBState *s, MUSBEndPoint *ep, ep->packey[dir].dir = dir; if (s->port.dev) - ret = s->port.dev->info->handle_packet(s->port.dev, &ep->packey[dir].p); + ret = usb_handle_packet(s->port.dev, &ep->packey[dir].p); else ret = USB_RET_NODEV; diff --git a/hw/usb-net.c b/hw/usb-net.c index bf51bb3890..9be709f7cf 100644 --- a/hw/usb-net.c +++ b/hw/usb-net.c @@ -1042,13 +1042,13 @@ static void usb_net_handle_reset(USBDevice *dev) { } -static int usb_net_handle_control(USBDevice *dev, int request, int value, - int index, int length, uint8_t *data) +static int usb_net_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) { USBNetState *s = (USBNetState *) dev; int ret; - ret = usb_desc_handle_control(dev, request, value, index, length, data); + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); if (ret >= 0) { return ret; } diff --git a/hw/usb-ohci.c b/hw/usb-ohci.c index 32913ebb06..8b966f7907 100644 --- a/hw/usb-ohci.c +++ b/hw/usb-ohci.c @@ -748,7 +748,7 @@ static int ohci_service_iso_td(OHCIState *ohci, struct ohci_ed *ed, ohci->usb_packet.devep = OHCI_BM(ed->flags, ED_EN); ohci->usb_packet.data = ohci->usb_buf; ohci->usb_packet.len = len; - ret = dev->info->handle_packet(dev, &ohci->usb_packet); + ret = usb_handle_packet(dev, &ohci->usb_packet); if (ret != USB_RET_NODEV) break; } @@ -944,7 +944,7 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) ohci->usb_packet.devep = OHCI_BM(ed->flags, ED_EN); ohci->usb_packet.data = ohci->usb_buf; ohci->usb_packet.len = len; - ret = dev->info->handle_packet(dev, &ohci->usb_packet); + ret = usb_handle_packet(dev, &ohci->usb_packet); if (ret != USB_RET_NODEV) break; } diff --git a/hw/usb-serial.c b/hw/usb-serial.c index 6763d52040..59cb0fb2f7 100644 --- a/hw/usb-serial.c +++ b/hw/usb-serial.c @@ -146,6 +146,7 @@ static const USBDescDevice desc_device = { .bConfigurationValue = 1, .bmAttributes = 0x80, .bMaxPower = 50, + .nif = 1, .ifs = &desc_iface0, }, }, @@ -218,14 +219,14 @@ static uint8_t usb_get_modem_lines(USBSerialState *s) return ret; } -static int usb_serial_handle_control(USBDevice *dev, int request, int value, - int index, int length, uint8_t *data) +static int usb_serial_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) { USBSerialState *s = (USBSerialState *)dev; int ret; DPRINTF("got control %x, value %x\n",request, value); - ret = usb_desc_handle_control(dev, request, value, index, length, data); + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); if (ret >= 0) { return ret; } diff --git a/hw/usb-uhci.c b/hw/usb-uhci.c index a65e0b3af6..c0de05b4ff 100644 --- a/hw/usb-uhci.c +++ b/hw/usb-uhci.c @@ -632,7 +632,7 @@ static int uhci_broadcast_packet(UHCIState *s, USBPacket *p) USBDevice *dev = port->port.dev; if (dev && (port->ctrl & UHCI_PORT_EN)) - ret = dev->info->handle_packet(dev, p); + ret = usb_handle_packet(dev, p); } DPRINTF("uhci: packet exit. ret %d len %d\n", ret, p->len); @@ -702,11 +702,15 @@ out: case USB_RET_STALL: td->ctrl |= TD_CTRL_STALL; td->ctrl &= ~TD_CTRL_ACTIVE; + s->status |= UHCI_STS_USBERR; + uhci_update_irq(s); return 1; case USB_RET_BABBLE: td->ctrl |= TD_CTRL_BABBLE | TD_CTRL_STALL; td->ctrl &= ~TD_CTRL_ACTIVE; + s->status |= UHCI_STS_USBERR; + uhci_update_irq(s); /* frame interrupted */ return -1; diff --git a/hw/usb-wacom.c b/hw/usb-wacom.c index 16be7a20cf..9d348e170e 100644 --- a/hw/usb-wacom.c +++ b/hw/usb-wacom.c @@ -108,6 +108,7 @@ static const USBDescDevice desc_device_wacom = { .bConfigurationValue = 1, .bmAttributes = 0x80, .bMaxPower = 40, + .nif = 1, .ifs = &desc_iface_wacom, }, }, @@ -249,13 +250,13 @@ static void usb_wacom_handle_reset(USBDevice *dev) s->mode = WACOM_MODE_HID; } -static int usb_wacom_handle_control(USBDevice *dev, int request, int value, - int index, int length, uint8_t *data) +static int usb_wacom_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) { USBWacomState *s = (USBWacomState *) dev; int ret; - ret = usb_desc_handle_control(dev, request, value, index, length, data); + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); if (ret >= 0) { return ret; } diff --git a/hw/usb.c b/hw/usb.c index d8c0a75c3a..4a39cbcc7d 100644 --- a/hw/usb.c +++ b/hw/usb.c @@ -63,9 +63,10 @@ void usb_wakeup(USBDevice *dev) protocol) */ -#define SETUP_STATE_IDLE 0 -#define SETUP_STATE_DATA 1 -#define SETUP_STATE_ACK 2 +#define SETUP_STATE_IDLE 0 +#define SETUP_STATE_SETUP 1 +#define SETUP_STATE_DATA 2 +#define SETUP_STATE_ACK 3 static int do_token_setup(USBDevice *s, USBPacket *p) { @@ -82,10 +83,14 @@ static int do_token_setup(USBDevice *s, USBPacket *p) request = (s->setup_buf[0] << 8) | s->setup_buf[1]; value = (s->setup_buf[3] << 8) | s->setup_buf[2]; index = (s->setup_buf[5] << 8) | s->setup_buf[4]; - + if (s->setup_buf[0] & USB_DIR_IN) { - ret = s->info->handle_control(s, request, value, index, + ret = s->info->handle_control(s, p, request, value, index, s->setup_len, s->data_buf); + if (ret == USB_RET_ASYNC) { + s->setup_state = SETUP_STATE_SETUP; + return USB_RET_ASYNC; + } if (ret < 0) return ret; @@ -123,9 +128,12 @@ static int do_token_in(USBDevice *s, USBPacket *p) switch(s->setup_state) { case SETUP_STATE_ACK: if (!(s->setup_buf[0] & USB_DIR_IN)) { - s->setup_state = SETUP_STATE_IDLE; - ret = s->info->handle_control(s, request, value, index, + ret = s->info->handle_control(s, p, request, value, index, s->setup_len, s->data_buf); + if (ret == USB_RET_ASYNC) { + return USB_RET_ASYNC; + } + s->setup_state = SETUP_STATE_IDLE; if (ret > 0) return 0; return ret; @@ -238,6 +246,36 @@ int usb_generic_handle_packet(USBDevice *s, USBPacket *p) } } +/* ctrl complete function for devices which use usb_generic_handle_packet and + may return USB_RET_ASYNC from their handle_control callback. Device code + which does this *must* call this function instead of the normal + usb_packet_complete to complete their async control packets. */ +void usb_generic_async_ctrl_complete(USBDevice *s, USBPacket *p) +{ + if (p->len < 0) { + s->setup_state = SETUP_STATE_IDLE; + } + + switch (s->setup_state) { + case SETUP_STATE_SETUP: + if (p->len < s->setup_len) { + s->setup_len = p->len; + } + s->setup_state = SETUP_STATE_DATA; + p->len = 8; + break; + + case SETUP_STATE_ACK: + s->setup_state = SETUP_STATE_IDLE; + p->len = 0; + break; + + default: + break; + } + usb_packet_complete(s, p); +} + /* XXX: fix overflow */ int set_usb_string(uint8_t *buf, const char *str) { @@ -259,9 +297,54 @@ int set_usb_string(uint8_t *buf, const char *str) void usb_send_msg(USBDevice *dev, int msg) { USBPacket p; + int ret; + memset(&p, 0, sizeof(p)); p.pid = msg; - dev->info->handle_packet(dev, &p); - + ret = usb_handle_packet(dev, &p); /* This _must_ be synchronous */ + assert(ret != USB_RET_ASYNC); +} + +/* Hand over a packet to a device for processing. Return value + USB_RET_ASYNC indicates the processing isn't finished yet, the + driver will call usb_packet_complete() when done processing it. */ +int usb_handle_packet(USBDevice *dev, USBPacket *p) +{ + int ret; + + assert(p->owner == NULL); + ret = dev->info->handle_packet(dev, p); + if (ret == USB_RET_ASYNC) { + if (p->owner == NULL) { + p->owner = dev; + } else { + /* We'll end up here when usb_handle_packet is called + * recursively due to a hub being in the chain. Nothing + * to do. Leave p->owner pointing to the device, not the + * hub. */; + } + } + return ret; +} + +/* Notify the controller that an async packet is complete. This should only + be called for packets previously deferred by returning USB_RET_ASYNC from + handle_packet. */ +void usb_packet_complete(USBDevice *dev, USBPacket *p) +{ + /* Note: p->owner != dev is possible in case dev is a hub */ + assert(p->owner != NULL); + dev->port->ops->complete(dev, p); + p->owner = NULL; +} + +/* Cancel an active packet. The packed must have been deferred by + returning USB_RET_ASYNC from handle_packet, and not yet + completed. */ +void usb_cancel_packet(USBPacket * p) +{ + assert(p->owner != NULL); + p->owner->info->cancel_packet(p->owner, p); + p->owner = NULL; } diff --git a/hw/usb.h b/hw/usb.h index 7e46141fed..98824009b9 100644 --- a/hw/usb.h +++ b/hw/usb.h @@ -124,6 +124,7 @@ #define USB_DT_ENDPOINT 0x05 #define USB_DT_DEVICE_QUALIFIER 0x06 #define USB_DT_OTHER_SPEED_CONFIG 0x07 +#define USB_DT_INTERFACE_ASSOC 0x0B #define USB_ENDPOINT_XFER_CONTROL 0 #define USB_ENDPOINT_XFER_ISOC 1 @@ -140,6 +141,7 @@ typedef struct USBDesc USBDesc; typedef struct USBDescID USBDescID; typedef struct USBDescDevice USBDescDevice; typedef struct USBDescConfig USBDescConfig; +typedef struct USBDescIfaceAssoc USBDescIfaceAssoc; typedef struct USBDescIface USBDescIface; typedef struct USBDescEndpoint USBDescEndpoint; typedef struct USBDescOther USBDescOther; @@ -192,6 +194,11 @@ struct USBDeviceInfo { int (*handle_packet)(USBDevice *dev, USBPacket *p); /* + * Called when a packet is canceled. + */ + void (*cancel_packet)(USBDevice *dev, USBPacket *p); + + /* * Called when device is destroyed. */ void (*handle_destroy)(USBDevice *dev); @@ -212,7 +219,7 @@ struct USBDeviceInfo { * * Returns length or one of the USB_RET_ codes. */ - int (*handle_control)(USBDevice *dev, int request, int value, + int (*handle_control)(USBDevice *dev, USBPacket *p, int request, int value, int index, int length, uint8_t *data); /* @@ -260,38 +267,17 @@ struct USBPacket { uint8_t *data; int len; /* Internal use by the USB layer. */ - USBCallback *cancel_cb; - void *cancel_opaque; + USBDevice *owner; }; -/* Defer completion of a USB packet. The hadle_packet routine should then - return USB_RET_ASYNC. Packets that complete immediately (before - handle_packet returns) should not call this method. */ -static inline void usb_defer_packet(USBPacket *p, USBCallback *cancel, - void * opaque) -{ - p->cancel_cb = cancel; - p->cancel_opaque = opaque; -} - -/* Notify the controller that an async packet is complete. This should only - be called for packets previously deferred with usb_defer_packet, and - should never be called from within handle_packet. */ -static inline void usb_packet_complete(USBDevice *dev, USBPacket *p) -{ - dev->port->ops->complete(dev, p); -} - -/* Cancel an active packet. The packed must have been deferred with - usb_defer_packet, and not yet completed. */ -static inline void usb_cancel_packet(USBPacket * p) -{ - p->cancel_cb(p, p->cancel_opaque); -} +int usb_handle_packet(USBDevice *dev, USBPacket *p); +void usb_packet_complete(USBDevice *dev, USBPacket *p); +void usb_cancel_packet(USBPacket * p); void usb_attach(USBPort *port, USBDevice *dev); void usb_wakeup(USBDevice *dev); int usb_generic_handle_packet(USBDevice *s, USBPacket *p); +void usb_generic_async_ctrl_complete(USBDevice *s, USBPacket *p); int set_usb_string(uint8_t *buf, const char *str); void usb_send_msg(USBDevice *dev, int msg); diff --git a/hw/virtio-console.c b/hw/virtio-console.c index de539c4eac..b076331d37 100644 --- a/hw/virtio-console.c +++ b/hw/virtio-console.c @@ -74,25 +74,26 @@ static void chr_event(void *opaque, int event) } } -static int generic_port_init(VirtConsole *vcon, VirtIOSerialPort *port) +static int virtconsole_initfn(VirtIOSerialPort *port) { + VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port); + VirtIOSerialPortInfo *info = DO_UPCAST(VirtIOSerialPortInfo, qdev, + vcon->port.dev.info); + + if (port->id == 0 && !info->is_console) { + error_report("Port number 0 on virtio-serial devices reserved for virtconsole devices for backward compatibility."); + return -1; + } + if (vcon->chr) { qemu_chr_add_handlers(vcon->chr, chr_can_read, chr_read, chr_event, vcon); - vcon->port.info->have_data = flush_buf; - vcon->port.info->guest_open = guest_open; - vcon->port.info->guest_close = guest_close; + info->have_data = flush_buf; + info->guest_open = guest_open; + info->guest_close = guest_close; } - return 0; -} -/* Virtio Console Ports */ -static int virtconsole_initfn(VirtIOSerialPort *port) -{ - VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port); - - port->is_console = true; - return generic_port_init(vcon, port); + return 0; } static int virtconsole_exitfn(VirtIOSerialPort *port) @@ -113,10 +114,10 @@ static int virtconsole_exitfn(VirtIOSerialPort *port) static VirtIOSerialPortInfo virtconsole_info = { .qdev.name = "virtconsole", .qdev.size = sizeof(VirtConsole), + .is_console = true, .init = virtconsole_initfn, .exit = virtconsole_exitfn, .qdev.props = (Property[]) { - DEFINE_PROP_UINT8("is_console", VirtConsole, port.is_console, 1), DEFINE_PROP_UINT32("nr", VirtConsole, port.id, VIRTIO_CONSOLE_BAD_ID), DEFINE_PROP_CHR("chardev", VirtConsole, chr), DEFINE_PROP_STRING("name", VirtConsole, port.name), @@ -130,26 +131,10 @@ static void virtconsole_register(void) } device_init(virtconsole_register) -/* Generic Virtio Serial Ports */ -static int virtserialport_initfn(VirtIOSerialPort *port) -{ - VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port); - - if (port->id == 0) { - /* - * Disallow a generic port at id 0, that's reserved for - * console ports. - */ - error_report("Port number 0 on virtio-serial devices reserved for virtconsole devices for backward compatibility."); - return -1; - } - return generic_port_init(vcon, port); -} - static VirtIOSerialPortInfo virtserialport_info = { .qdev.name = "virtserialport", .qdev.size = sizeof(VirtConsole), - .init = virtserialport_initfn, + .init = virtconsole_initfn, .exit = virtconsole_exitfn, .qdev.props = (Property[]) { DEFINE_PROP_UINT32("nr", VirtConsole, port.id, VIRTIO_CONSOLE_BAD_ID), diff --git a/hw/virtio-serial-bus.c b/hw/virtio-serial-bus.c index f10d48fdb0..9a12104982 100644 --- a/hw/virtio-serial-bus.c +++ b/hw/virtio-serial-bus.c @@ -39,7 +39,7 @@ struct VirtIOSerial { /* Arrays of ivqs and ovqs: one per port */ VirtQueue **ivqs, **ovqs; - VirtIOSerialBus *bus; + VirtIOSerialBus bus; DeviceState *qdev; @@ -129,9 +129,13 @@ static void discard_vq_data(VirtQueue *vq, VirtIODevice *vdev) static void do_flush_queued_data(VirtIOSerialPort *port, VirtQueue *vq, VirtIODevice *vdev) { + VirtIOSerialPortInfo *info; + assert(port); assert(virtio_queue_ready(vq)); + info = DO_UPCAST(VirtIOSerialPortInfo, qdev, port->dev.info); + while (!port->throttled) { unsigned int i; @@ -149,10 +153,10 @@ static void do_flush_queued_data(VirtIOSerialPort *port, VirtQueue *vq, ssize_t ret; buf_size = port->elem.out_sg[i].iov_len - port->iov_offset; - ret = port->info->have_data(port, - port->elem.out_sg[i].iov_base - + port->iov_offset, - buf_size); + ret = info->have_data(port, + port->elem.out_sg[i].iov_base + + port->iov_offset, + buf_size); if (ret < 0 && ret != -EAGAIN) { /* We don't handle any other type of errors here */ abort(); @@ -285,6 +289,13 @@ size_t virtio_serial_guest_ready(VirtIOSerialPort *port) return 0; } +static void flush_queued_data_bh(void *opaque) +{ + VirtIOSerialPort *port = opaque; + + flush_queued_data(port); +} + void virtio_serial_throttle_port(VirtIOSerialPort *port, bool throttle) { if (!port) { @@ -295,14 +306,14 @@ void virtio_serial_throttle_port(VirtIOSerialPort *port, bool throttle) if (throttle) { return; } - - flush_queued_data(port); + qemu_bh_schedule(port->bh); } /* Guest wants to notify us of some event */ static void handle_control_message(VirtIOSerial *vser, void *buf, size_t len) { struct VirtIOSerialPort *port; + struct VirtIOSerialPortInfo *info; struct virtio_console_control cpkt, *gcpkt; uint8_t *buffer; size_t buffer_len; @@ -321,11 +332,13 @@ static void handle_control_message(VirtIOSerial *vser, void *buf, size_t len) if (!port && cpkt.event != VIRTIO_CONSOLE_DEVICE_READY) return; + info = DO_UPCAST(VirtIOSerialPortInfo, qdev, port->dev.info); + switch(cpkt.event) { case VIRTIO_CONSOLE_DEVICE_READY: if (!cpkt.value) { error_report("virtio-serial-bus: Guest failure in adding device %s\n", - vser->bus->qbus.name); + vser->bus.qbus.name); break; } /* @@ -340,7 +353,7 @@ static void handle_control_message(VirtIOSerial *vser, void *buf, size_t len) case VIRTIO_CONSOLE_PORT_READY: if (!cpkt.value) { error_report("virtio-serial-bus: Guest failure in adding port %u for device %s\n", - port->id, vser->bus->qbus.name); + port->id, vser->bus.qbus.name); break; } /* @@ -350,7 +363,7 @@ static void handle_control_message(VirtIOSerial *vser, void *buf, size_t len) * this port is a console port so that the guest can hook it * up to hvc. */ - if (port->is_console) { + if (info->is_console) { send_control_event(port, VIRTIO_CONSOLE_CONSOLE_PORT, 1); } @@ -379,21 +392,21 @@ static void handle_control_message(VirtIOSerial *vser, void *buf, size_t len) * initialised. If some app is interested in knowing about * this event, let it know. */ - if (port->info->guest_ready) { - port->info->guest_ready(port); + if (info->guest_ready) { + info->guest_ready(port); } break; case VIRTIO_CONSOLE_PORT_OPEN: port->guest_connected = cpkt.value; - if (cpkt.value && port->info->guest_open) { + if (cpkt.value && info->guest_open) { /* Send the guest opened notification if an app is interested */ - port->info->guest_open(port); + info->guest_open(port); } - if (!cpkt.value && port->info->guest_close) { + if (!cpkt.value && info->guest_close) { /* Send the guest closed notification if an app is interested */ - port->info->guest_close(port); + info->guest_close(port); } break; } @@ -442,11 +455,13 @@ static void handle_output(VirtIODevice *vdev, VirtQueue *vq) { VirtIOSerial *vser; VirtIOSerialPort *port; + VirtIOSerialPortInfo *info; vser = DO_UPCAST(VirtIOSerial, vdev, vdev); port = find_port_by_vq(vser, vq); + info = port ? DO_UPCAST(VirtIOSerialPortInfo, qdev, port->dev.info) : NULL; - if (!port || !port->host_connected || !port->info->have_data) { + if (!port || !port->host_connected || !info->have_data) { discard_vq_data(vq, vdev); return; } @@ -467,7 +482,7 @@ static uint32_t get_features(VirtIODevice *vdev, uint32_t features) vser = DO_UPCAST(VirtIOSerial, vdev, vdev); - if (vser->bus->max_nr_ports > 1) { + if (vser->bus.max_nr_ports > 1) { features |= (1 << VIRTIO_CONSOLE_F_MULTIPORT); } return features; @@ -644,16 +659,6 @@ static struct BusInfo virtser_bus_info = { .print_dev = virtser_bus_dev_print, }; -static VirtIOSerialBus *virtser_bus_new(DeviceState *dev) -{ - VirtIOSerialBus *bus; - - bus = FROM_QBUS(VirtIOSerialBus, qbus_create(&virtser_bus_info, dev, NULL)); - bus->qbus.allow_hotplug = 1; - - return bus; -} - static void virtser_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent) { VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, qdev); @@ -726,13 +731,14 @@ static int virtser_port_qdev_init(DeviceState *qdev, DeviceInfo *base) bool plugging_port0; port->vser = bus->vser; + port->bh = qemu_bh_new(flush_queued_data_bh, port); /* * Is the first console port we're seeing? If so, put it up at * location 0. This is done for backward compatibility (old * kernel, new qemu). */ - plugging_port0 = port->is_console && !find_port_by_id(port->vser, 0); + plugging_port0 = info->is_console && !find_port_by_id(port->vser, 0); if (find_port_by_id(port->vser, port->id)) { error_report("virtio-serial-bus: A port already exists at id %u\n", @@ -759,7 +765,6 @@ static int virtser_port_qdev_init(DeviceState *qdev, DeviceInfo *base) return -1; } - port->info = info; ret = info->init(port); if (ret) { return ret; @@ -790,15 +795,18 @@ static int virtser_port_qdev_init(DeviceState *qdev, DeviceInfo *base) static int virtser_port_qdev_exit(DeviceState *qdev) { VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, qdev); + VirtIOSerialPortInfo *info = DO_UPCAST(VirtIOSerialPortInfo, qdev, + port->dev.info); VirtIOSerial *vser = port->vser; + qemu_bh_delete(port->bh); remove_port(port->vser, port->id); QTAILQ_REMOVE(&vser->ports, port, next); - if (port->info->exit) - port->info->exit(port); - + if (info->exit) { + info->exit(port); + } return 0; } @@ -835,11 +843,12 @@ VirtIODevice *virtio_serial_init(DeviceState *dev, virtio_serial_conf *conf) vser = DO_UPCAST(VirtIOSerial, vdev, vdev); /* Spawn a new virtio-serial bus on which the ports will ride as devices */ - vser->bus = virtser_bus_new(dev); - vser->bus->vser = vser; + qbus_create_inplace(&vser->bus.qbus, &virtser_bus_info, dev, NULL); + vser->bus.qbus.allow_hotplug = 1; + vser->bus.vser = vser; QTAILQ_INIT(&vser->ports); - vser->bus->max_nr_ports = conf->max_virtserial_ports; + vser->bus.max_nr_ports = conf->max_virtserial_ports; vser->ivqs = qemu_malloc(conf->max_virtserial_ports * sizeof(VirtQueue *)); vser->ovqs = qemu_malloc(conf->max_virtserial_ports * sizeof(VirtQueue *)); @@ -859,7 +868,7 @@ VirtIODevice *virtio_serial_init(DeviceState *dev, virtio_serial_conf *conf) /* control queue: guest to host */ vser->c_ovq = virtio_add_queue(vdev, 32, control_out); - for (i = 1; i < vser->bus->max_nr_ports; i++) { + for (i = 1; i < vser->bus.max_nr_ports; i++) { /* Add a per-port queue for host to guest transfers */ vser->ivqs[i] = virtio_add_queue(vdev, 128, handle_input); /* Add a per-per queue for guest to host transfers */ diff --git a/hw/virtio-serial.h b/hw/virtio-serial.h index 5eb948e3fd..36e9d222e5 100644 --- a/hw/virtio-serial.h +++ b/hw/virtio-serial.h @@ -75,7 +75,6 @@ typedef struct VirtIOSerialPortInfo VirtIOSerialPortInfo; */ struct VirtIOSerialPort { DeviceState dev; - VirtIOSerialPortInfo *info; QTAILQ_ENTRY(VirtIOSerialPort) next; @@ -119,8 +118,10 @@ struct VirtIOSerialPort { uint32_t iov_idx; uint64_t iov_offset; - /* Identify if this is a port that binds with hvc in the guest */ - uint8_t is_console; + /* + * When unthrottling we use a bottom-half to call flush_queued_data. + */ + QEMUBH *bh; /* Is the corresponding guest device open? */ bool guest_connected; @@ -132,6 +133,10 @@ struct VirtIOSerialPort { struct VirtIOSerialPortInfo { DeviceInfo qdev; + + /* Is this a device that binds with hvc in the guest? */ + bool is_console; + /* * The per-port (or per-app) init function that's called when a * new device is found on the bus. diff --git a/trace-events b/trace-events index 385cb00009..3137a1518f 100644 --- a/trace-events +++ b/trace-events @@ -205,6 +205,14 @@ disable usb_set_config(int addr, int config, int ret) "dev %d, config %d, ret %d disable usb_clear_device_feature(int addr, int feature, int ret) "dev %d, feature %d, ret %d" disable usb_set_device_feature(int addr, int feature, int ret) "dev %d, feature %d, ret %d" +# hw/scsi-bus.c +disable scsi_req_alloc(int target, int lun, int tag) "target %d lun %d tag %d" +disable scsi_req_data(int target, int lun, int tag, int len) "target %d lun %d tag %d len %d" +disable scsi_req_dequeue(int target, int lun, int tag) "target %d lun %d tag %d" +disable scsi_req_continue(int target, int lun, int tag) "target %d lun %d tag %d" +disable scsi_req_parsed(int target, int lun, int tag, int cmd, int mode, int xfer, uint64_t lba) "target %d lun %d tag %d command %d dir %d length %d lba %"PRIu64"" +disable scsi_req_parse_bad(int target, int lun, int tag, int cmd) "target %d lun %d tag %d command %d" + # vl.c disable vm_state_notify(int running, int reason) "running %d reason %d" diff --git a/usb-bsd.c b/usb-bsd.c index 50ccd489fe..9bab6e353b 100644 --- a/usb-bsd.c +++ b/usb-bsd.c @@ -126,6 +126,7 @@ static void usb_host_handle_reset(USBDevice *dev) * and return appropriate response */ static int usb_host_handle_control(USBDevice *dev, + USBPacket *p, int request, int value, int index, diff --git a/usb-linux.c b/usb-linux.c index 0ef1d2619b..baa6574ef5 100644 --- a/usb-linux.c +++ b/usb-linux.c @@ -54,15 +54,7 @@ struct usb_ctrltransfer { void *data; }; -struct usb_ctrlrequest { - uint8_t bRequestType; - uint8_t bRequest; - uint16_t wValue; - uint16_t wIndex; - uint16_t wLength; -}; - -typedef int USBScanFunc(void *opaque, int bus_num, int addr, int devpath, +typedef int USBScanFunc(void *opaque, int bus_num, int addr, char *port, int class_id, int vendor_id, int product_id, const char *product_name, int speed); @@ -79,6 +71,7 @@ typedef int USBScanFunc(void *opaque, int bus_num, int addr, int devpath, #define USBPROCBUS_PATH "/proc/bus/usb" #define PRODUCT_NAME_SZ 32 #define MAX_ENDPOINTS 15 +#define MAX_PORTLEN 16 #define USBDEVBUS_PATH "/dev/bus/usb" #define USBSYSBUS_PATH "/sys/bus/usb" @@ -96,6 +89,9 @@ static int usb_fs_type; #define ISO_URB_COUNT 3 #define INVALID_EP_TYPE 255 +/* devio.c limits single requests to 16k */ +#define MAX_USBFS_BUFFER_SIZE 16384 + typedef struct AsyncURB AsyncURB; struct endp_data { @@ -108,29 +104,10 @@ struct endp_data { int max_packet_size; }; -enum { - CTRL_STATE_IDLE = 0, - CTRL_STATE_SETUP, - CTRL_STATE_DATA, - CTRL_STATE_ACK -}; - -/* - * Control transfer state. - * Note that 'buffer' _must_ follow 'req' field because - * we need contiguous buffer when we submit control URB. - */ -struct ctrl_struct { - uint16_t len; - uint16_t offset; - uint8_t state; - struct usb_ctrlrequest req; - uint8_t buffer[8192]; -}; - struct USBAutoFilter { uint32_t bus_num; uint32_t addr; + char *port; uint32_t vendor_id; uint32_t product_id; }; @@ -146,13 +123,13 @@ typedef struct USBHostDevice { int closing; Notifier exit; - struct ctrl_struct ctrl; struct endp_data endp_table[MAX_ENDPOINTS]; + QLIST_HEAD(, AsyncURB) aurbs; /* Host side address */ int bus_num; int addr; - int devpath; + char port[MAX_PORTLEN]; struct USBAutoFilter match; QTAILQ_ENTRY(USBHostDevice) next; @@ -236,6 +213,22 @@ static int get_iso_buffer_used(USBHostDevice *s, int ep) return s->endp_table[ep - 1].iso_buffer_used; } +static void set_max_packet_size(USBHostDevice *s, int ep, uint8_t *descriptor) +{ + int raw = descriptor[4] + (descriptor[5] << 8); + int size, microframes; + + size = raw & 0x7ff; + switch ((raw >> 11) & 3) { + case 1: microframes = 2; break; + case 2: microframes = 3; break; + default: microframes = 1; break; + } + DPRINTF("husb: max packet size: 0x%x -> %d x %d\n", + raw, microframes, size); + s->endp_table[ep - 1].max_packet_size = size * microframes; +} + static int get_max_packet_size(USBHostDevice *s, int ep) { return s->endp_table[ep - 1].max_packet_size; @@ -250,45 +243,31 @@ struct AsyncURB { struct usbdevfs_urb urb; struct usbdevfs_iso_packet_desc isocpd[ISO_FRAME_DESC_PER_URB]; + USBHostDevice *hdev; + QLIST_ENTRY(AsyncURB) next; /* For regular async urbs */ USBPacket *packet; - USBHostDevice *hdev; + int more; /* large transfer, more urbs follow */ /* For buffered iso handling */ int iso_frame_idx; /* -1 means in flight */ }; -static AsyncURB *async_alloc(void) +static AsyncURB *async_alloc(USBHostDevice *s) { - return (AsyncURB *) qemu_mallocz(sizeof(AsyncURB)); + AsyncURB *aurb = qemu_mallocz(sizeof(AsyncURB)); + aurb->hdev = s; + QLIST_INSERT_HEAD(&s->aurbs, aurb, next); + return aurb; } static void async_free(AsyncURB *aurb) { + QLIST_REMOVE(aurb, next); qemu_free(aurb); } -static void async_complete_ctrl(USBHostDevice *s, USBPacket *p) -{ - switch(s->ctrl.state) { - case CTRL_STATE_SETUP: - if (p->len < s->ctrl.len) - s->ctrl.len = p->len; - s->ctrl.state = CTRL_STATE_DATA; - p->len = 8; - break; - - case CTRL_STATE_ACK: - s->ctrl.state = CTRL_STATE_IDLE; - p->len = 0; - break; - - default: - break; - } -} - static void async_complete(void *opaque) { USBHostDevice *s = opaque; @@ -332,10 +311,7 @@ static void async_complete(void *opaque) if (p) { switch (aurb->urb.status) { case 0: - p->len = aurb->urb.actual_length; - if (aurb->urb.type == USBDEVFS_URB_TYPE_CONTROL) { - async_complete_ctrl(s, p); - } + p->len += aurb->urb.actual_length; break; case -EPIPE: @@ -348,26 +324,36 @@ static void async_complete(void *opaque) break; } - usb_packet_complete(&s->dev, p); + if (aurb->urb.type == USBDEVFS_URB_TYPE_CONTROL) { + usb_generic_async_ctrl_complete(&s->dev, p); + } else if (!aurb->more) { + usb_packet_complete(&s->dev, p); + } } async_free(aurb); } } -static void async_cancel(USBPacket *unused, void *opaque) +static void usb_host_async_cancel(USBDevice *dev, USBPacket *p) { - AsyncURB *aurb = opaque; - USBHostDevice *s = aurb->hdev; + USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev); + AsyncURB *aurb; - DPRINTF("husb: async cancel. aurb %p\n", aurb); + QLIST_FOREACH(aurb, &s->aurbs, next) { + if (p != aurb->packet) { + continue; + } + + DPRINTF("husb: async cancel: packet %p, aurb %p\n", p, aurb); - /* Mark it as dead (see async_complete above) */ - aurb->packet = NULL; + /* Mark it as dead (see async_complete above) */ + aurb->packet = NULL; - int r = ioctl(s->fd, USBDEVFS_DISCARDURB, aurb); - if (r < 0) { - DPRINTF("husb: async. discard urb failed errno %d\n", errno); + int r = ioctl(s->fd, USBDEVFS_DISCARDURB, aurb); + if (r < 0) { + DPRINTF("husb: async. discard urb failed errno %d\n", errno); + } } } @@ -675,11 +661,13 @@ static int usb_host_handle_iso_data(USBHostDevice *s, USBPacket *p, int in) return len; } -static int usb_host_handle_data(USBHostDevice *s, USBPacket *p) +static int usb_host_handle_data(USBDevice *dev, USBPacket *p) { + USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev); struct usbdevfs_urb *urb; AsyncURB *aurb; - int ret; + int ret, rem; + uint8_t *pbuf; uint8_t ep; if (!is_valid(s, p->devep)) { @@ -706,37 +694,48 @@ static int usb_host_handle_data(USBHostDevice *s, USBPacket *p) return usb_host_handle_iso_data(s, p, p->pid == USB_TOKEN_IN); } - aurb = async_alloc(); - aurb->hdev = s; - aurb->packet = p; + rem = p->len; + pbuf = p->data; + p->len = 0; + while (rem) { + aurb = async_alloc(s); + aurb->packet = p; - urb = &aurb->urb; + urb = &aurb->urb; + urb->endpoint = ep; + urb->type = USBDEVFS_URB_TYPE_BULK; + urb->usercontext = s; + urb->buffer = pbuf; - urb->endpoint = ep; - urb->buffer = p->data; - urb->buffer_length = p->len; - urb->type = USBDEVFS_URB_TYPE_BULK; - urb->usercontext = s; + if (rem > MAX_USBFS_BUFFER_SIZE) { + urb->buffer_length = MAX_USBFS_BUFFER_SIZE; + aurb->more = 1; + } else { + urb->buffer_length = rem; + aurb->more = 0; + } + pbuf += urb->buffer_length; + rem -= urb->buffer_length; - ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb); + ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb); - DPRINTF("husb: data submit. ep 0x%x len %u aurb %p\n", - urb->endpoint, p->len, aurb); + DPRINTF("husb: data submit: ep 0x%x, len %u, more %d, packet %p, aurb %p\n", + urb->endpoint, urb->buffer_length, aurb->more, p, aurb); - if (ret < 0) { - DPRINTF("husb: submit failed. errno %d\n", errno); - async_free(aurb); + if (ret < 0) { + DPRINTF("husb: submit failed. errno %d\n", errno); + async_free(aurb); - switch(errno) { - case ETIMEDOUT: - return USB_RET_NAK; - case EPIPE: - default: - return USB_RET_STALL; + switch(errno) { + case ETIMEDOUT: + return USB_RET_NAK; + case EPIPE: + default: + return USB_RET_STALL; + } } } - usb_defer_packet(p, async_cancel, aurb); return USB_RET_ASYNC; } @@ -796,50 +795,43 @@ static int usb_host_set_interface(USBHostDevice *s, int iface, int alt) return 0; } -static int usb_host_handle_control(USBHostDevice *s, USBPacket *p) +static int usb_host_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) { + USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev); struct usbdevfs_urb *urb; AsyncURB *aurb; - int ret, value, index; - int buffer_len; + int ret; /* * Process certain standard device requests. * These are infrequent and are processed synchronously. */ - value = le16_to_cpu(s->ctrl.req.wValue); - index = le16_to_cpu(s->ctrl.req.wIndex); + /* Note request is (bRequestType << 8) | bRequest */ DPRINTF("husb: ctrl type 0x%x req 0x%x val 0x%x index %u len %u\n", - s->ctrl.req.bRequestType, s->ctrl.req.bRequest, value, index, - s->ctrl.len); + request >> 8, request & 0xff, value, index, length); - if (s->ctrl.req.bRequestType == 0) { - switch (s->ctrl.req.bRequest) { - case USB_REQ_SET_ADDRESS: - return usb_host_set_address(s, value); + switch (request) { + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + return usb_host_set_address(s, value); - case USB_REQ_SET_CONFIGURATION: - return usb_host_set_config(s, value & 0xff); - } - } + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + return usb_host_set_config(s, value & 0xff); - if (s->ctrl.req.bRequestType == 1 && - s->ctrl.req.bRequest == USB_REQ_SET_INTERFACE) { + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: return usb_host_set_interface(s, index, value); } /* The rest are asynchronous */ - buffer_len = 8 + s->ctrl.len; - if (buffer_len > sizeof(s->ctrl.buffer)) { - fprintf(stderr, "husb: ctrl buffer too small (%u > %zu)\n", - buffer_len, sizeof(s->ctrl.buffer)); + if (length > sizeof(dev->data_buf)) { + fprintf(stderr, "husb: ctrl buffer too small (%d > %zu)\n", + length, sizeof(dev->data_buf)); return USB_RET_STALL; } - aurb = async_alloc(); - aurb->hdev = s; + aurb = async_alloc(s); aurb->packet = p; /* @@ -853,8 +845,8 @@ static int usb_host_handle_control(USBHostDevice *s, USBPacket *p) urb->type = USBDEVFS_URB_TYPE_CONTROL; urb->endpoint = p->devep; - urb->buffer = &s->ctrl.req; - urb->buffer_length = buffer_len; + urb->buffer = &dev->setup_buf; + urb->buffer_length = length + 8; urb->usercontext = s; @@ -875,174 +867,9 @@ static int usb_host_handle_control(USBHostDevice *s, USBPacket *p) } } - usb_defer_packet(p, async_cancel, aurb); return USB_RET_ASYNC; } -static int do_token_setup(USBDevice *dev, USBPacket *p) -{ - USBHostDevice *s = (USBHostDevice *) dev; - int ret = 0; - - if (p->len != 8) { - return USB_RET_STALL; - } - - memcpy(&s->ctrl.req, p->data, 8); - s->ctrl.len = le16_to_cpu(s->ctrl.req.wLength); - s->ctrl.offset = 0; - s->ctrl.state = CTRL_STATE_SETUP; - - if (s->ctrl.req.bRequestType & USB_DIR_IN) { - ret = usb_host_handle_control(s, p); - if (ret < 0) { - return ret; - } - - if (ret < s->ctrl.len) { - s->ctrl.len = ret; - } - s->ctrl.state = CTRL_STATE_DATA; - } else { - if (s->ctrl.len == 0) { - s->ctrl.state = CTRL_STATE_ACK; - } else { - s->ctrl.state = CTRL_STATE_DATA; - } - } - - return ret; -} - -static int do_token_in(USBDevice *dev, USBPacket *p) -{ - USBHostDevice *s = (USBHostDevice *) dev; - int ret = 0; - - if (p->devep != 0) { - return usb_host_handle_data(s, p); - } - - switch(s->ctrl.state) { - case CTRL_STATE_ACK: - if (!(s->ctrl.req.bRequestType & USB_DIR_IN)) { - ret = usb_host_handle_control(s, p); - if (ret == USB_RET_ASYNC) { - return USB_RET_ASYNC; - } - s->ctrl.state = CTRL_STATE_IDLE; - return ret > 0 ? 0 : ret; - } - - return 0; - - case CTRL_STATE_DATA: - if (s->ctrl.req.bRequestType & USB_DIR_IN) { - int len = s->ctrl.len - s->ctrl.offset; - if (len > p->len) { - len = p->len; - } - memcpy(p->data, s->ctrl.buffer + s->ctrl.offset, len); - s->ctrl.offset += len; - if (s->ctrl.offset >= s->ctrl.len) { - s->ctrl.state = CTRL_STATE_ACK; - } - return len; - } - - s->ctrl.state = CTRL_STATE_IDLE; - return USB_RET_STALL; - - default: - return USB_RET_STALL; - } -} - -static int do_token_out(USBDevice *dev, USBPacket *p) -{ - USBHostDevice *s = (USBHostDevice *) dev; - - if (p->devep != 0) { - return usb_host_handle_data(s, p); - } - - switch(s->ctrl.state) { - case CTRL_STATE_ACK: - if (s->ctrl.req.bRequestType & USB_DIR_IN) { - s->ctrl.state = CTRL_STATE_IDLE; - /* transfer OK */ - } else { - /* ignore additional output */ - } - return 0; - - case CTRL_STATE_DATA: - if (!(s->ctrl.req.bRequestType & USB_DIR_IN)) { - int len = s->ctrl.len - s->ctrl.offset; - if (len > p->len) { - len = p->len; - } - memcpy(s->ctrl.buffer + s->ctrl.offset, p->data, len); - s->ctrl.offset += len; - if (s->ctrl.offset >= s->ctrl.len) { - s->ctrl.state = CTRL_STATE_ACK; - } - return len; - } - - s->ctrl.state = CTRL_STATE_IDLE; - return USB_RET_STALL; - - default: - return USB_RET_STALL; - } -} - -/* - * Packet handler. - * Called by the HC (host controller). - * - * Returns length of the transaction or one of the USB_RET_XXX codes. - */ -static int usb_host_handle_packet(USBDevice *s, USBPacket *p) -{ - switch(p->pid) { - case USB_MSG_ATTACH: - s->state = USB_STATE_ATTACHED; - return 0; - - case USB_MSG_DETACH: - s->state = USB_STATE_NOTATTACHED; - return 0; - - case USB_MSG_RESET: - s->remote_wakeup = 0; - s->addr = 0; - s->state = USB_STATE_DEFAULT; - s->info->handle_reset(s); - return 0; - } - - /* Rest of the PIDs must match our address */ - if (s->state < USB_STATE_DEFAULT || p->devaddr != s->addr) { - return USB_RET_NODEV; - } - - switch (p->pid) { - case USB_TOKEN_SETUP: - return do_token_setup(s, p); - - case USB_TOKEN_IN: - return do_token_in(s, p); - - case USB_TOKEN_OUT: - return do_token_out(s, p); - - default: - return USB_RET_STALL; - } -} - static int usb_linux_get_configuration(USBHostDevice *s) { uint8_t configuration; @@ -1053,7 +880,7 @@ static int usb_linux_get_configuration(USBHostDevice *s) char device_name[32], line[1024]; int configuration; - sprintf(device_name, "%d-%d", s->bus_num, s->devpath); + sprintf(device_name, "%d-%s", s->bus_num, s->port); if (!usb_host_read_file(line, sizeof(line), "bConfigurationValue", device_name)) { @@ -1099,7 +926,7 @@ static uint8_t usb_linux_get_alt_setting(USBHostDevice *s, char device_name[64], line[1024]; int alt_setting; - sprintf(device_name, "%d-%d:%d.%d", s->bus_num, s->devpath, + sprintf(device_name, "%d-%s:%d.%d", s->bus_num, s->port, (int)configuration, (int)interface); if (!usb_host_read_file(line, sizeof(line), "bAlternateSetting", @@ -1195,8 +1022,7 @@ static int usb_linux_update_endp_table(USBHostDevice *s) break; case 0x01: type = USBDEVFS_URB_TYPE_ISO; - s->endp_table[(devep & 0xf) - 1].max_packet_size = - descriptors[i + 4] + (descriptors[i + 5] << 8); + set_max_packet_size(s, (devep & 0xf), descriptors + i); break; case 0x02: type = USBDEVFS_URB_TYPE_BULK; @@ -1218,7 +1044,7 @@ static int usb_linux_update_endp_table(USBHostDevice *s) } static int usb_host_open(USBHostDevice *dev, int bus_num, - int addr, int devpath, const char *prod_name) + int addr, char *port, const char *prod_name) { int fd = -1, ret; struct usbdevfs_connectinfo ci; @@ -1244,7 +1070,7 @@ static int usb_host_open(USBHostDevice *dev, int bus_num, dev->bus_num = bus_num; dev->addr = addr; - dev->devpath = devpath; + strcpy(dev->port, port); dev->fd = fd; /* read the device description */ @@ -1368,7 +1194,10 @@ static struct USBDeviceInfo usb_host_dev_info = { .qdev.name = "usb-host", .qdev.size = sizeof(USBHostDevice), .init = usb_host_initfn, - .handle_packet = usb_host_handle_packet, + .handle_packet = usb_generic_handle_packet, + .cancel_packet = usb_host_async_cancel, + .handle_data = usb_host_handle_data, + .handle_control = usb_host_handle_control, .handle_reset = usb_host_handle_reset, .handle_destroy = usb_host_handle_destroy, .usbdevice_name = "host", @@ -1376,6 +1205,7 @@ static struct USBDeviceInfo usb_host_dev_info = { .qdev.props = (Property[]) { DEFINE_PROP_UINT32("hostbus", USBHostDevice, match.bus_num, 0), DEFINE_PROP_UINT32("hostaddr", USBHostDevice, match.addr, 0), + DEFINE_PROP_STRING("hostport", USBHostDevice, match.port), DEFINE_PROP_HEX32("vendorid", USBHostDevice, match.vendor_id, 0), DEFINE_PROP_HEX32("productid", USBHostDevice, match.product_id, 0), DEFINE_PROP_END_OF_LIST(), @@ -1616,8 +1446,9 @@ static int usb_host_scan_sys(void *opaque, USBScanFunc *func) { DIR *dir = NULL; char line[1024]; - int bus_num, addr, devpath, speed, class_id, product_id, vendor_id; + int bus_num, addr, speed, class_id, product_id, vendor_id; int ret = 0; + char port[MAX_PORTLEN]; char product_name[512]; struct dirent *de; @@ -1629,12 +1460,8 @@ static int usb_host_scan_sys(void *opaque, USBScanFunc *func) while ((de = readdir(dir))) { if (de->d_name[0] != '.' && !strchr(de->d_name, ':')) { - char *tmpstr = de->d_name; - if (!strncmp(de->d_name, "usb", 3)) { - tmpstr += 3; - } - if (sscanf(tmpstr, "%d-%d", &bus_num, &devpath) < 1) { - goto the_end; + if (sscanf(de->d_name, "%d-%7[0-9.]", &bus_num, port) < 2) { + continue; } if (!usb_host_read_file(line, sizeof(line), "devnum", de->d_name)) { @@ -1686,7 +1513,7 @@ static int usb_host_scan_sys(void *opaque, USBScanFunc *func) speed = USB_SPEED_FULL; } - ret = func(opaque, bus_num, addr, devpath, class_id, vendor_id, + ret = func(opaque, bus_num, addr, port, class_id, vendor_id, product_id, product_name, speed); if (ret) { goto the_end; @@ -1777,7 +1604,7 @@ static int usb_host_scan(void *opaque, USBScanFunc *func) static QEMUTimer *usb_auto_timer; -static int usb_host_auto_scan(void *opaque, int bus_num, int addr, int devpath, +static int usb_host_auto_scan(void *opaque, int bus_num, int addr, char *port, int class_id, int vendor_id, int product_id, const char *product_name, int speed) { @@ -1797,6 +1624,9 @@ static int usb_host_auto_scan(void *opaque, int bus_num, int addr, int devpath, if (f->addr > 0 && f->addr != addr) { continue; } + if (f->port != NULL && (port == NULL || strcmp(f->port, port) != 0)) { + continue; + } if (f->vendor_id > 0 && f->vendor_id != vendor_id) { continue; @@ -1813,7 +1643,7 @@ static int usb_host_auto_scan(void *opaque, int bus_num, int addr, int devpath, } DPRINTF("husb: auto open: bus_num %d addr %d\n", bus_num, addr); - usb_host_open(s, bus_num, addr, devpath, product_name); + usb_host_open(s, bus_num, addr, port, product_name); } return 0; @@ -1935,8 +1765,8 @@ static const char *usb_class_str(uint8_t class) return p->class_name; } -static void usb_info_device(Monitor *mon, int bus_num, int addr, int class_id, - int vendor_id, int product_id, +static void usb_info_device(Monitor *mon, int bus_num, int addr, char *port, + int class_id, int vendor_id, int product_id, const char *product_name, int speed) { @@ -1957,8 +1787,8 @@ static void usb_info_device(Monitor *mon, int bus_num, int addr, int class_id, break; } - monitor_printf(mon, " Device %d.%d, speed %s Mb/s\n", - bus_num, addr, speed_str); + monitor_printf(mon, " Bus %d, Addr %d, Port %s, Speed %s Mb/s\n", + bus_num, addr, port, speed_str); class_str = usb_class_str(class_id); if (class_str) { monitor_printf(mon, " %s:", class_str); @@ -1973,14 +1803,14 @@ static void usb_info_device(Monitor *mon, int bus_num, int addr, int class_id, } static int usb_host_info_device(void *opaque, int bus_num, int addr, - int devpath, int class_id, + char *path, int class_id, int vendor_id, int product_id, const char *product_name, int speed) { Monitor *mon = opaque; - usb_info_device(mon, bus_num, addr, class_id, vendor_id, product_id, + usb_info_device(mon, bus_num, addr, path, class_id, vendor_id, product_id, product_name, speed); return 0; } @@ -2022,7 +1852,7 @@ void usb_host_info(Monitor *mon) dec2str(f->addr, addr, sizeof(addr)); hex2str(f->vendor_id, vid, sizeof(vid)); hex2str(f->product_id, pid, sizeof(pid)); - monitor_printf(mon, " Device %s.%s ID %s:%s\n", - bus, addr, vid, pid); + monitor_printf(mon, " Bus %s, Addr %s, Port %s, ID %s:%s\n", + bus, addr, f->port ? f->port : "*", vid, pid); } } |