diff options
author | Anthony Liguori <aliguori@us.ibm.com> | 2012-03-09 12:29:40 -0600 |
---|---|---|
committer | Anthony Liguori <aliguori@us.ibm.com> | 2012-03-09 12:29:40 -0600 |
commit | 9f1d43b133d6bac0539f0c99497fe59d561a02ea (patch) | |
tree | 79091812779a75d7080d8801a4a1a4603012ece6 /hw | |
parent | 0202181245297a9e847c05f4a18623219d95e93e (diff) | |
parent | cf21a4aef712075d313da4ea924a3376574c16e5 (diff) |
Merge remote-tracking branch 'kraxel/usb.42' into staging
* kraxel/usb.42:
xhci: fix port status
xhci: fix control xfers
usb: add shortcut for control transfers
usb-host: enable pipelineing for bulk endpoints.
usb: add pipelining option to usb endpoints
usb: queue can have async packets
uhci_fill_queue: zap debug printf
usb: add USB_RET_IOERROR
usb: return BABBLE rather then NAK when we receive too much data
usb-ehci: Cleanup itd error handling
usb-ehci: Fix and simplify nakcnt handling
usb-ehci: Remove dead nakcnt code
usb-ehci: Fix cerr tracking
usb-ehci: Any packet completion except for NAK should set the interrupt
usb-ehci: Rip the queues when the async or period schedule is halted
usb-ehci: Drop cached qhs when the doorbell gets rung
usb-ehci: always call ehci_queues_rip_unused for period queues
usb-ehci: split our qh queue into async and periodic queues
usb-ehci: Never follow table entries with the T-bit set
usb-redir: Set ep type and interface
Diffstat (limited to 'hw')
-rw-r--r-- | hw/usb-ehci.c | 204 | ||||
-rw-r--r-- | hw/usb-ohci.c | 2 | ||||
-rw-r--r-- | hw/usb-uhci.c | 2 | ||||
-rw-r--r-- | hw/usb-xhci.c | 15 | ||||
-rw-r--r-- | hw/usb.c | 73 | ||||
-rw-r--r-- | hw/usb.h | 14 |
6 files changed, 175 insertions, 135 deletions
diff --git a/hw/usb-ehci.c b/hw/usb-ehci.c index afc8ccf458..df742f7f02 100644 --- a/hw/usb-ehci.c +++ b/hw/usb-ehci.c @@ -347,7 +347,6 @@ enum async_state { struct EHCIQueue { EHCIState *ehci; QTAILQ_ENTRY(EHCIQueue) next; - bool async_schedule; uint32_t seen; uint64_t ts; @@ -367,6 +366,8 @@ struct EHCIQueue { int usb_status; }; +typedef QTAILQ_HEAD(EHCIQueueHead, EHCIQueue) EHCIQueueHead; + struct EHCIState { PCIDevice dev; USBBus bus; @@ -410,7 +411,8 @@ struct EHCIState { USBPort ports[NB_PORTS]; USBPort *companion_ports[NB_PORTS]; uint32_t usbsts_pending; - QTAILQ_HEAD(, EHCIQueue) queues; + EHCIQueueHead aqueues; + EHCIQueueHead pqueues; uint32_t a_fetch_addr; // which address to look at next uint32_t p_fetch_addr; // which address to look at next @@ -660,31 +662,34 @@ static void ehci_trace_sitd(EHCIState *s, target_phys_addr_t addr, static EHCIQueue *ehci_alloc_queue(EHCIState *ehci, int async) { + EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues; EHCIQueue *q; q = g_malloc0(sizeof(*q)); q->ehci = ehci; - q->async_schedule = async; - QTAILQ_INSERT_HEAD(&ehci->queues, q, next); + QTAILQ_INSERT_HEAD(head, q, next); trace_usb_ehci_queue_action(q, "alloc"); return q; } -static void ehci_free_queue(EHCIQueue *q) +static void ehci_free_queue(EHCIQueue *q, int async) { + EHCIQueueHead *head = async ? &q->ehci->aqueues : &q->ehci->pqueues; trace_usb_ehci_queue_action(q, "free"); if (q->async == EHCI_ASYNC_INFLIGHT) { usb_cancel_packet(&q->packet); } - QTAILQ_REMOVE(&q->ehci->queues, q, next); + QTAILQ_REMOVE(head, q, next); g_free(q); } -static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint32_t addr) +static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint32_t addr, + int async) { + EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues; EHCIQueue *q; - QTAILQ_FOREACH(q, &ehci->queues, next) { + QTAILQ_FOREACH(q, head, next) { if (addr == q->qhaddr) { return q; } @@ -692,43 +697,46 @@ static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint32_t addr) return NULL; } -static void ehci_queues_rip_unused(EHCIState *ehci) +static void ehci_queues_rip_unused(EHCIState *ehci, int async, int flush) { + EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues; EHCIQueue *q, *tmp; - QTAILQ_FOREACH_SAFE(q, &ehci->queues, next, tmp) { + QTAILQ_FOREACH_SAFE(q, head, next, tmp) { if (q->seen) { q->seen = 0; q->ts = ehci->last_run_ns; continue; } - if (ehci->last_run_ns < q->ts + 250000000) { + if (!flush && ehci->last_run_ns < q->ts + 250000000) { /* allow 0.25 sec idle */ continue; } - ehci_free_queue(q); + ehci_free_queue(q, async); } } -static void ehci_queues_rip_device(EHCIState *ehci, USBDevice *dev) +static void ehci_queues_rip_device(EHCIState *ehci, USBDevice *dev, int async) { + EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues; EHCIQueue *q, *tmp; - QTAILQ_FOREACH_SAFE(q, &ehci->queues, next, tmp) { + QTAILQ_FOREACH_SAFE(q, head, next, tmp) { if (!usb_packet_is_inflight(&q->packet) || q->packet.ep->dev != dev) { continue; } - ehci_free_queue(q); + ehci_free_queue(q, async); } } -static void ehci_queues_rip_all(EHCIState *ehci) +static void ehci_queues_rip_all(EHCIState *ehci, int async) { + EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues; EHCIQueue *q, *tmp; - QTAILQ_FOREACH_SAFE(q, &ehci->queues, next, tmp) { - ehci_free_queue(q); + QTAILQ_FOREACH_SAFE(q, head, next, tmp) { + ehci_free_queue(q, async); } } @@ -773,7 +781,8 @@ static void ehci_detach(USBPort *port) return; } - ehci_queues_rip_device(s, port->dev); + ehci_queues_rip_device(s, port->dev, 0); + ehci_queues_rip_device(s, port->dev, 1); *portsc &= ~(PORTSC_CONNECT|PORTSC_PED); *portsc |= PORTSC_CSC; @@ -793,7 +802,8 @@ static void ehci_child_detach(USBPort *port, USBDevice *child) return; } - ehci_queues_rip_device(s, child); + ehci_queues_rip_device(s, child, 0); + ehci_queues_rip_device(s, child, 1); } static void ehci_wakeup(USBPort *port) @@ -911,7 +921,8 @@ static void ehci_reset(void *opaque) usb_device_reset(devs[i]); } } - ehci_queues_rip_all(s); + ehci_queues_rip_all(s, 0); + ehci_queues_rip_all(s, 1); qemu_del_timer(s->frame_timer); } @@ -1065,7 +1076,8 @@ static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val) if (!(val & USBCMD_RUNSTOP) && (s->usbcmd & USBCMD_RUNSTOP)) { qemu_del_timer(s->frame_timer); - // TODO - should finish out some stuff before setting halt + ehci_queues_rip_all(s, 0); + ehci_queues_rip_all(s, 1); ehci_set_usbsts(s, USBSTS_HALT); } @@ -1279,8 +1291,6 @@ static void ehci_async_complete_packet(USBPort *port, USBPacket *packet) static void ehci_execute_complete(EHCIQueue *q) { - int c_err, reload; - assert(q->async != EHCI_ASYNC_INFLIGHT); q->async = EHCI_ASYNC_NONE; @@ -1288,15 +1298,11 @@ static void ehci_execute_complete(EHCIQueue *q) q->qhaddr, q->qh.next, q->qtdaddr, q->usb_status); if (q->usb_status < 0) { -err: - /* TO-DO: put this is in a function that can be invoked below as well */ - c_err = get_field(q->qh.token, QTD_TOKEN_CERR); - c_err--; - set_field(&q->qh.token, c_err, QTD_TOKEN_CERR); - switch(q->usb_status) { + case USB_RET_IOERROR: case USB_RET_NODEV: q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_XACTERR); + set_field(&q->qh.token, 0, QTD_TOKEN_CERR); ehci_record_interrupt(q->ehci, USBSTS_ERRINT); break; case USB_RET_STALL: @@ -1304,16 +1310,8 @@ err: ehci_record_interrupt(q->ehci, USBSTS_ERRINT); break; case USB_RET_NAK: - /* 4.10.3 */ - reload = get_field(q->qh.epchar, QH_EPCHAR_RL); - if ((q->pid == USB_TOKEN_IN) && reload) { - int nakcnt = get_field(q->qh.altnext_qtd, QH_ALTNEXT_NAKCNT); - nakcnt--; - set_field(&q->qh.altnext_qtd, nakcnt, QH_ALTNEXT_NAKCNT); - } else if (!reload) { - return; - } - break; + set_field(&q->qh.altnext_qtd, 0, QH_ALTNEXT_NAKCNT); + return; /* We're not done yet with this transaction */ case USB_RET_BABBLE: q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_BABBLE); ehci_record_interrupt(q->ehci, USBSTS_ERRINT); @@ -1324,15 +1322,13 @@ err: assert(0); break; } + } else if ((q->usb_status > q->tbytes) && (q->pid == USB_TOKEN_IN)) { + q->usb_status = USB_RET_BABBLE; + q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_BABBLE); + ehci_record_interrupt(q->ehci, USBSTS_ERRINT); } else { - // DPRINTF("Short packet condition\n"); // TODO check 4.12 for splits - if ((q->usb_status > q->tbytes) && (q->pid == USB_TOKEN_IN)) { - q->usb_status = USB_RET_BABBLE; - goto err; - } - if (q->tbytes && q->pid == USB_TOKEN_IN) { q->tbytes -= q->usb_status; } else { @@ -1348,7 +1344,7 @@ err: q->qh.token ^= QTD_TOKEN_DTOGGLE; q->qh.token &= ~QTD_TOKEN_ACTIVE; - if ((q->usb_status >= 0) && (q->qh.token & QTD_TOKEN_IOC)) { + if (q->qh.token & QTD_TOKEN_IOC) { ehci_record_interrupt(q->ehci, USBSTS_INT); } } @@ -1471,24 +1467,12 @@ static int ehci_process_itd(EHCIState *ehci, } qemu_sglist_destroy(&ehci->isgl); - if (ret == USB_RET_NAK) { - /* no data for us, so do a zero-length transfer */ - ret = 0; - } - - if (ret >= 0) { - if (!dir) { - /* OUT */ - set_field(&itd->transact[i], len - ret, ITD_XACT_LENGTH); - } else { - /* IN */ - set_field(&itd->transact[i], ret, ITD_XACT_LENGTH); - } - } else { + if (ret < 0) { switch (ret) { default: fprintf(stderr, "Unexpected iso usb result: %d\n", ret); /* Fall through */ + case USB_RET_IOERROR: case USB_RET_NODEV: /* 3.3.2: XACTERR is only allowed on IN transactions */ if (dir) { @@ -1500,6 +1484,19 @@ static int ehci_process_itd(EHCIState *ehci, itd->transact[i] |= ITD_XACT_BABBLE; ehci_record_interrupt(ehci, USBSTS_ERRINT); break; + case USB_RET_NAK: + /* no data for us, so do a zero-length transfer */ + ret = 0; + break; + } + } + if (ret >= 0) { + if (!dir) { + /* OUT */ + set_field(&itd->transact[i], len - ret, ITD_XACT_LENGTH); + } else { + /* IN */ + set_field(&itd->transact[i], ret, ITD_XACT_LENGTH); } } if (itd->transact[i] & ITD_XACT_IOC) { @@ -1526,7 +1523,7 @@ static int ehci_state_waitlisthead(EHCIState *ehci, int async) ehci_set_usbsts(ehci, USBSTS_REC); } - ehci_queues_rip_unused(ehci); + ehci_queues_rip_unused(ehci, async, 0); /* Find the head of the list (4.9.1.1) */ for(i = 0; i < MAX_QH; i++) { @@ -1568,8 +1565,7 @@ static int ehci_state_fetchentry(EHCIState *ehci, int async) int again = 0; uint32_t entry = ehci_get_fetch_addr(ehci, async); - if (entry < 0x1000) { - DPRINTF("fetchentry: entry invalid (0x%08x)\n", entry); + if (NLPTR_TBIT(entry)) { ehci_set_state(ehci, async, EST_ACTIVE); goto out; } @@ -1611,10 +1607,9 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async) { uint32_t entry; EHCIQueue *q; - int reload; entry = ehci_get_fetch_addr(ehci, async); - q = ehci_find_queue_by_qh(ehci, entry); + q = ehci_find_queue_by_qh(ehci, entry, async); if (NULL == q) { q = ehci_alloc_queue(ehci, async); } @@ -1669,15 +1664,11 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async) } #endif - reload = get_field(q->qh.epchar, QH_EPCHAR_RL); - if (reload) { - set_field(&q->qh.altnext_qtd, reload, QH_ALTNEXT_NAKCNT); - } - if (q->qh.token & QTD_TOKEN_HALT) { ehci_set_state(ehci, async, EST_HORIZONTALQH); - } else if ((q->qh.token & QTD_TOKEN_ACTIVE) && (q->qh.current_qtd > 0x1000)) { + } else if ((q->qh.token & QTD_TOKEN_ACTIVE) && + (NLPTR_TBIT(q->qh.current_qtd) == 0)) { q->qtdaddr = q->qh.current_qtd; ehci_set_state(ehci, async, EST_FETCHQTD); @@ -1756,7 +1747,6 @@ static int ehci_state_advqueue(EHCIQueue *q, int async) * want data and alt-next qTD is valid */ if (((q->qh.token & QTD_TOKEN_TBYTES_MASK) != 0) && - (q->qh.altnext_qtd > 0x1000) && (NLPTR_TBIT(q->qh.altnext_qtd) == 0)) { q->qtdaddr = q->qh.altnext_qtd; ehci_set_state(q->ehci, async, EST_FETCHQTD); @@ -1764,8 +1754,7 @@ static int ehci_state_advqueue(EHCIQueue *q, int async) /* * next qTD is valid */ - } else if ((q->qh.next_qtd > 0x1000) && - (NLPTR_TBIT(q->qh.next_qtd) == 0)) { + } else if (NLPTR_TBIT(q->qh.next_qtd) == 0) { q->qtdaddr = q->qh.next_qtd; ehci_set_state(q->ehci, async, EST_FETCHQTD); @@ -1834,25 +1823,11 @@ static void ehci_flush_qh(EHCIQueue *q) static int ehci_state_execute(EHCIQueue *q, int async) { int again = 0; - int reload, nakcnt; - int smask; if (ehci_qh_do_overlay(q) != 0) { return -1; } - smask = get_field(q->qh.epcap, QH_EPCAP_SMASK); - - if (!smask) { - reload = get_field(q->qh.epchar, QH_EPCHAR_RL); - nakcnt = get_field(q->qh.altnext_qtd, QH_ALTNEXT_NAKCNT); - if (reload && !nakcnt) { - ehci_set_state(q->ehci, async, 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 @@ -1894,7 +1869,6 @@ out: static int ehci_state_executing(EHCIQueue *q, int async) { int again = 0; - int reload, nakcnt; ehci_execute_complete(q); if (q->usb_status == USB_RET_ASYNC) { @@ -1914,21 +1888,8 @@ static int ehci_state_executing(EHCIQueue *q, int async) // counter decrements to 0 } - reload = get_field(q->qh.epchar, QH_EPCHAR_RL); - if (reload) { - nakcnt = get_field(q->qh.altnext_qtd, QH_ALTNEXT_NAKCNT); - if (q->usb_status == USB_RET_NAK) { - if (nakcnt) { - nakcnt--; - } - } else { - nakcnt = reload; - } - set_field(&q->qh.altnext_qtd, nakcnt, QH_ALTNEXT_NAKCNT); - } - /* 4.10.5 */ - if ((q->usb_status == USB_RET_NAK) || (q->qh.token & QTD_TOKEN_ACTIVE)) { + if (q->usb_status == USB_RET_NAK) { ehci_set_state(q->ehci, async, EST_HORIZONTALQH); } else { ehci_set_state(q->ehci, async, EST_WRITEBACK); @@ -2066,7 +2027,7 @@ static void ehci_advance_state(EHCIState *ehci, static void ehci_advance_async_state(EHCIState *ehci) { - int async = 1; + const int async = 1; switch(ehci_get_state(ehci, async)) { case EST_INACTIVE: @@ -2079,23 +2040,13 @@ static void ehci_advance_async_state(EHCIState *ehci) case EST_ACTIVE: if ( !(ehci->usbcmd & USBCMD_ASE)) { + ehci_queues_rip_all(ehci, async); ehci_clear_usbsts(ehci, USBSTS_ASS); ehci_set_state(ehci, async, 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 */ + /* make sure guest has acknowledged the doorbell interrupt */ /* TO-DO: is this really needed? */ if (ehci->usbsts & USBSTS_IAA) { DPRINTF("IAA status bit still set.\n"); @@ -2109,6 +2060,18 @@ static void ehci_advance_async_state(EHCIState *ehci) ehci_set_state(ehci, async, EST_WAITLISTHEAD); ehci_advance_state(ehci, async); + + /* 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) { + /* Remove all unseen qhs from the async qhs queue */ + ehci_queues_rip_unused(ehci, async, 1); + DPRINTF("ASYNC: doorbell request acknowledged\n"); + ehci->usbcmd &= ~USBCMD_IAAD; + ehci_set_interrupt(ehci, USBSTS_IAA); + } break; default: @@ -2123,7 +2086,7 @@ static void ehci_advance_periodic_state(EHCIState *ehci) { uint32_t entry; uint32_t list; - int async = 0; + const int async = 0; // 4.6 @@ -2138,6 +2101,7 @@ static void ehci_advance_periodic_state(EHCIState *ehci) case EST_ACTIVE: if ( !(ehci->frindex & 7) && !(ehci->usbcmd & USBCMD_PSE)) { + ehci_queues_rip_all(ehci, async); ehci_clear_usbsts(ehci, USBSTS_PSS); ehci_set_state(ehci, async, EST_INACTIVE); break; @@ -2158,6 +2122,7 @@ static void ehci_advance_periodic_state(EHCIState *ehci) ehci_set_fetch_addr(ehci, async,entry); ehci_set_state(ehci, async, EST_FETCHENTRY); ehci_advance_state(ehci, async); + ehci_queues_rip_unused(ehci, async, 0); break; default: @@ -2356,7 +2321,8 @@ static int usb_ehci_initfn(PCIDevice *dev) } s->frame_timer = qemu_new_timer_ns(vm_clock, ehci_frame_timer, s); - QTAILQ_INIT(&s->queues); + QTAILQ_INIT(&s->aqueues); + QTAILQ_INIT(&s->pqueues); qemu_register_reset(ehci_reset, s); diff --git a/hw/usb-ohci.c b/hw/usb-ohci.c index 7aa19fe781..20aaa74250 100644 --- a/hw/usb-ohci.c +++ b/hw/usb-ohci.c @@ -837,6 +837,7 @@ static int ohci_service_iso_td(OHCIState *ohci, struct ohci_ed *ed, OHCI_CC_DATAUNDERRUN); } else { switch (ret) { + case USB_RET_IOERROR: case USB_RET_NODEV: OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC, OHCI_CC_DEVICENOTRESPONDING); @@ -1052,6 +1053,7 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DATAUNDERRUN); } else { switch (ret) { + case USB_RET_IOERROR: case USB_RET_NODEV: OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DEVICENOTRESPONDING); case USB_RET_NAK: diff --git a/hw/usb-uhci.c b/hw/usb-uhci.c index 70e3881321..304b84b831 100644 --- a/hw/usb-uhci.c +++ b/hw/usb-uhci.c @@ -765,6 +765,7 @@ out: break; return 1; + case USB_RET_IOERROR: case USB_RET_NODEV: default: break; @@ -950,7 +951,6 @@ static void uhci_fill_queue(UHCIState *s, UHCI_TD *td) UHCI_TD ptd; int ret; - fprintf(stderr, "%s: -- %x\n", __func__, token); while (is_valid(plink)) { pci_dma_read(&s->dev, plink & ~0xf, &ptd, sizeof(ptd)); le32_to_cpus(&ptd.link); diff --git a/hw/usb-xhci.c b/hw/usb-xhci.c index fc5b542d99..e8f1b6e3a5 100644 --- a/hw/usb-xhci.c +++ b/hw/usb-xhci.c @@ -1470,8 +1470,8 @@ static USBDevice *xhci_find_device(XHCIPort *port, uint8_t addr) static int xhci_fire_ctl_transfer(XHCIState *xhci, XHCITransfer *xfer) { XHCITRB *trb_setup, *trb_status; - uint8_t bmRequestType, bRequest; - uint16_t wValue, wLength, wIndex; + uint8_t bmRequestType; + uint16_t wLength; XHCIPort *port; USBDevice *dev; int ret; @@ -1508,9 +1508,6 @@ static int xhci_fire_ctl_transfer(XHCIState *xhci, XHCITransfer *xfer) } bmRequestType = trb_setup->parameter; - bRequest = trb_setup->parameter >> 8; - wValue = trb_setup->parameter >> 16; - wIndex = trb_setup->parameter >> 32; wLength = trb_setup->parameter >> 48; if (xfer->data && xfer->data_alloced < wLength) { @@ -1537,12 +1534,12 @@ static int xhci_fire_ctl_transfer(XHCIState *xhci, XHCITransfer *xfer) xfer->iso_xfer = false; xhci_setup_packet(xfer, dev); + xfer->packet.parameter = trb_setup->parameter; if (!xfer->in_xfer) { xhci_xfer_data(xfer, xfer->data, wLength, 0, 1, 0); } - ret = usb_device_handle_control(dev, &xfer->packet, - (bmRequestType << 8) | bRequest, - wValue, wIndex, wLength, xfer->data); + + ret = usb_handle_packet(dev, &xfer->packet); xhci_complete_packet(xfer, ret); if (!xfer->running_async && !xfer->running_retry) { @@ -2282,7 +2279,7 @@ static void xhci_update_port(XHCIState *xhci, XHCIPort *port, int is_detach) int nr = port->port.index + 1; port->portsc = PORTSC_PP; - if (port->port.dev && !is_detach) { + if (port->port.dev && port->port.dev->attached && !is_detach) { port->portsc |= PORTSC_CCS; switch (port->port.dev->speed) { case USB_SPEED_LOW: @@ -95,6 +95,7 @@ void usb_wakeup(USBEndpoint *ep) #define SETUP_STATE_SETUP 1 #define SETUP_STATE_DATA 2 #define SETUP_STATE_ACK 3 +#define SETUP_STATE_PARAM 4 static int do_token_setup(USBDevice *s, USBPacket *p) { @@ -226,6 +227,50 @@ static int do_token_out(USBDevice *s, USBPacket *p) } } +static int do_parameter(USBDevice *s, USBPacket *p) +{ + int request, value, index; + int i, ret = 0; + + for (i = 0; i < 8; i++) { + s->setup_buf[i] = p->parameter >> (i*8); + } + + s->setup_state = SETUP_STATE_PARAM; + s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6]; + s->setup_index = 0; + + 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_len > sizeof(s->data_buf)) { + fprintf(stderr, + "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n", + s->setup_len, sizeof(s->data_buf)); + return USB_RET_STALL; + } + + if (p->pid == USB_TOKEN_OUT) { + usb_packet_copy(p, s->data_buf, s->setup_len); + } + + ret = usb_device_handle_control(s, p, request, value, index, + s->setup_len, s->data_buf); + if (ret < 0) { + return ret; + } + + if (ret < s->setup_len) { + s->setup_len = ret; + } + if (p->pid == USB_TOKEN_IN) { + usb_packet_copy(p, s->data_buf, s->setup_len); + } + + return ret; +} + /* 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 @@ -250,6 +295,16 @@ void usb_generic_async_ctrl_complete(USBDevice *s, USBPacket *p) p->result = 0; break; + case SETUP_STATE_PARAM: + if (p->result < s->setup_len) { + s->setup_len = p->result; + } + if (p->pid == USB_TOKEN_IN) { + p->result = 0; + usb_packet_copy(p, s->data_buf, s->setup_len); + } + break; + default: break; } @@ -292,6 +347,9 @@ static int usb_process_one(USBPacket *p) if (p->ep->nr == 0) { /* control pipe */ + if (p->parameter) { + return do_parameter(dev, p); + } switch (p->pid) { case USB_TOKEN_SETUP: return do_token_setup(dev, p); @@ -323,7 +381,7 @@ int usb_handle_packet(USBDevice *dev, USBPacket *p) assert(p->state == USB_PACKET_SETUP); assert(p->ep != NULL); - if (QTAILQ_EMPTY(&p->ep->queue)) { + if (QTAILQ_EMPTY(&p->ep->queue) || p->ep->pipeline) { ret = usb_process_one(p); if (ret == USB_RET_ASYNC) { usb_packet_set_state(p, USB_PACKET_ASYNC); @@ -356,6 +414,9 @@ void usb_packet_complete(USBDevice *dev, USBPacket *p) while (!QTAILQ_EMPTY(&ep->queue)) { p = QTAILQ_FIRST(&ep->queue); + if (p->state == USB_PACKET_ASYNC) { + break; + } assert(p->state == USB_PACKET_QUEUED); ret = usb_process_one(p); if (ret == USB_RET_ASYNC) { @@ -413,6 +474,7 @@ void usb_packet_setup(USBPacket *p, int pid, USBEndpoint *ep) p->pid = pid; p->ep = ep; p->result = 0; + p->parameter = 0; qemu_iovec_reset(&p->iov); usb_packet_set_state(p, USB_PACKET_SETUP); } @@ -465,6 +527,7 @@ void usb_ep_init(USBDevice *dev) dev->ep_ctl.type = USB_ENDPOINT_XFER_CONTROL; dev->ep_ctl.ifnum = 0; dev->ep_ctl.dev = dev; + dev->ep_ctl.pipeline = false; QTAILQ_INIT(&dev->ep_ctl.queue); for (ep = 0; ep < USB_MAX_ENDPOINTS; ep++) { dev->ep_in[ep].nr = ep + 1; @@ -477,6 +540,8 @@ void usb_ep_init(USBDevice *dev) dev->ep_out[ep].ifnum = 0; dev->ep_in[ep].dev = dev; dev->ep_out[ep].dev = dev; + dev->ep_in[ep].pipeline = false; + dev->ep_out[ep].pipeline = false; QTAILQ_INIT(&dev->ep_in[ep].queue); QTAILQ_INIT(&dev->ep_out[ep].queue); } @@ -590,3 +655,9 @@ int usb_ep_get_max_packet_size(USBDevice *dev, int pid, int ep) struct USBEndpoint *uep = usb_ep_get(dev, pid, ep); return uep->max_packet_size; } + +void usb_ep_set_pipeline(USBDevice *dev, int pid, int ep, bool enabled) +{ + struct USBEndpoint *uep = usb_ep_get(dev, pid, ep); + uep->pipeline = enabled; +} @@ -39,11 +39,12 @@ #define USB_TOKEN_IN 0x69 /* device -> host */ #define USB_TOKEN_OUT 0xe1 /* host -> device */ -#define USB_RET_NODEV (-1) -#define USB_RET_NAK (-2) -#define USB_RET_STALL (-3) -#define USB_RET_BABBLE (-4) -#define USB_RET_ASYNC (-5) +#define USB_RET_NODEV (-1) +#define USB_RET_NAK (-2) +#define USB_RET_STALL (-3) +#define USB_RET_BABBLE (-4) +#define USB_RET_IOERROR (-5) +#define USB_RET_ASYNC (-6) #define USB_SPEED_LOW 0 #define USB_SPEED_FULL 1 @@ -176,6 +177,7 @@ struct USBEndpoint { uint8_t type; uint8_t ifnum; int max_packet_size; + bool pipeline; USBDevice *dev; QTAILQ_HEAD(, USBPacket) queue; }; @@ -325,6 +327,7 @@ struct USBPacket { int pid; USBEndpoint *ep; QEMUIOVector iov; + uint64_t parameter; /* control transfers */ int result; /* transfer length or USB_RET_* status code */ /* Internal use by the USB layer. */ USBPacketState state; @@ -363,6 +366,7 @@ void usb_ep_set_ifnum(USBDevice *dev, int pid, int ep, uint8_t ifnum); void usb_ep_set_max_packet_size(USBDevice *dev, int pid, int ep, uint16_t raw); int usb_ep_get_max_packet_size(USBDevice *dev, int pid, int ep); +void usb_ep_set_pipeline(USBDevice *dev, int pid, int ep, bool enabled); void usb_attach(USBPort *port); void usb_detach(USBPort *port); |