diff options
author | hselasky <hselasky@FreeBSD.org> | 2011-03-21 21:16:25 +0000 |
---|---|---|
committer | hselasky <hselasky@FreeBSD.org> | 2011-03-21 21:16:25 +0000 |
commit | 9fa2bf8aee52318f7642bf3d3904f66fbce1ec79 (patch) | |
tree | 595e0ad285cd637ff25868834145a3ff15019acd /sys/dev/usb/controller | |
parent | a444cd56818258c1c8aefcbb26d5c76539e66567 (diff) | |
download | FreeBSD-src-9fa2bf8aee52318f7642bf3d3904f66fbce1ec79.zip FreeBSD-src-9fa2bf8aee52318f7642bf3d3904f66fbce1ec79.tar.gz |
- Bugfix: Fix a EHCI hardware race, where the hardware computed data toggle
value is updated after that we read it in the queue-head. This patch can
fix problems with BULK timeouts. The issue was found on a Nvidia chipset.
MFC after: 14 days
Approved by: thompsa (mentor)
Diffstat (limited to 'sys/dev/usb/controller')
-rw-r--r-- | sys/dev/usb/controller/ehci.c | 28 |
1 files changed, 25 insertions, 3 deletions
diff --git a/sys/dev/usb/controller/ehci.c b/sys/dev/usb/controller/ehci.c index e631e55..812e422 100644 --- a/sys/dev/usb/controller/ehci.c +++ b/sys/dev/usb/controller/ehci.c @@ -1180,6 +1180,26 @@ _ehci_remove_qh(ehci_qh_t *sqh, ehci_qh_t *last) return (last); } +static void +ehci_data_toggle_update(struct usb_xfer *xfer, uint16_t actlen, uint16_t xlen) +{ + uint8_t full = (actlen == xlen); + uint8_t dt; + + /* count number of full packets */ + dt = (actlen / xfer->max_packet_size) & 1; + + /* cumpute remainder */ + actlen = actlen % xfer->max_packet_size; + + if (actlen > 0) + dt ^= 1; /* short packet at the end */ + else if (!full) + dt ^= 1; /* zero length packet at the end */ + + xfer->endpoint->toggle_next ^= dt; +} + static usb_error_t ehci_non_isoc_done_sub(struct usb_xfer *xfer) { @@ -1213,7 +1233,10 @@ ehci_non_isoc_done_sub(struct usb_xfer *xfer) status |= EHCI_QTD_HALTED; } else if (xfer->aframes != xfer->nframes) { xfer->frlengths[xfer->aframes] += td->len - len; + /* manually update data toggle */ + ehci_data_toggle_update(xfer, td->len - len, td->len); } + /* Check for last transfer */ if (((void *)td) == xfer->td_transfer_last) { td = NULL; @@ -1295,9 +1318,6 @@ ehci_non_isoc_done(struct usb_xfer *xfer) status = hc32toh(sc, qh->qh_qtd.qtd_status); - xfer->endpoint->toggle_next = - (status & EHCI_QTD_TOGGLE_MASK) ? 1 : 0; - /* reset scanner */ xfer->td_transfer_cache = xfer->td_transfer_first; @@ -1876,6 +1896,8 @@ ehci_setup_standard_chain(struct usb_xfer *xfer, ehci_qh_t **qh_last) if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { + xfer->endpoint->toggle_next = 0; + temp.qtd_status &= htohc32(temp.sc, EHCI_QTD_SET_CERR(3)); temp.qtd_status |= htohc32(temp.sc, |