summaryrefslogtreecommitdiffstats
path: root/sys/dev/usb/controller
diff options
context:
space:
mode:
authorhselasky <hselasky@FreeBSD.org>2011-03-21 21:16:25 +0000
committerhselasky <hselasky@FreeBSD.org>2011-03-21 21:16:25 +0000
commit9fa2bf8aee52318f7642bf3d3904f66fbce1ec79 (patch)
tree595e0ad285cd637ff25868834145a3ff15019acd /sys/dev/usb/controller
parenta444cd56818258c1c8aefcbb26d5c76539e66567 (diff)
downloadFreeBSD-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.c28
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,
OpenPOWER on IntegriCloud