mirror of
https://github.com/freebsd/freebsd-src
synced 2024-10-19 23:03:55 +00:00
EHCI Hardware BUG workaround
The EHCI HW can use the qtd_next field instead of qtd_altnext when a short packet is received. This contradicts what is stated in the EHCI datasheet. Also the total-bytes field in the status field of the following TD gets corrupted upon reception of a short packet! We work this around in software by not queueing more than one job/TD at a time of up to 16Kbytes! The bug has been seen on multiple INTEL based EHCI chips. Other vendors have not been tested yet. - Applications using /dev/usb/X.Y.Z, where Z is non-zero are affected, but not applications using LibUSB v0.1, v1.2 and v2.0. - Mass Storage (umass) is affected. Submitted by: Hans Petter Selasky MFC after: 3 days
This commit is contained in:
parent
ad1e5416ab
commit
c8a14b9124
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=197682
|
@ -131,6 +131,7 @@ struct ehci_std_temp {
|
|||
uint8_t auto_data_toggle;
|
||||
uint8_t setup_alt_next;
|
||||
uint8_t last_frame;
|
||||
uint8_t can_use_next;
|
||||
};
|
||||
|
||||
void
|
||||
|
@ -1207,11 +1208,6 @@ ehci_non_isoc_done_sub(struct usb_xfer *xfer)
|
|||
|
||||
xfer->td_transfer_cache = td;
|
||||
|
||||
/* update data toggle */
|
||||
|
||||
xfer->endpoint->toggle_next =
|
||||
(status & EHCI_QTD_TOGGLE_MASK) ? 1 : 0;
|
||||
|
||||
#if USB_DEBUG
|
||||
if (status & EHCI_QTD_STATERRS) {
|
||||
DPRINTFN(11, "error, addr=%d, endpt=0x%02x, frame=0x%02x"
|
||||
|
@ -1235,6 +1231,9 @@ ehci_non_isoc_done_sub(struct usb_xfer *xfer)
|
|||
static void
|
||||
ehci_non_isoc_done(struct usb_xfer *xfer)
|
||||
{
|
||||
ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
|
||||
ehci_qh_t *qh;
|
||||
uint32_t status;
|
||||
usb_error_t err = 0;
|
||||
|
||||
DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n",
|
||||
|
@ -1248,6 +1247,17 @@ ehci_non_isoc_done(struct usb_xfer *xfer)
|
|||
}
|
||||
#endif
|
||||
|
||||
/* extract data toggle directly from the QH's overlay area */
|
||||
|
||||
qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
|
||||
|
||||
usb_pc_cpu_invalidate(qh->page_cache);
|
||||
|
||||
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;
|
||||
|
@ -1348,6 +1358,7 @@ ehci_check_transfer(struct usb_xfer *xfer)
|
|||
}
|
||||
} else {
|
||||
ehci_qtd_t *td;
|
||||
ehci_qh_t *qh;
|
||||
|
||||
/* non-isochronous transfer */
|
||||
|
||||
|
@ -1357,16 +1368,35 @@ ehci_check_transfer(struct usb_xfer *xfer)
|
|||
*/
|
||||
td = xfer->td_transfer_cache;
|
||||
|
||||
qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
|
||||
|
||||
usb_pc_cpu_invalidate(qh->page_cache);
|
||||
|
||||
status = hc32toh(sc, qh->qh_qtd.qtd_status);
|
||||
if (status & EHCI_QTD_ACTIVE) {
|
||||
/* transfer is pending */
|
||||
goto done;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
usb_pc_cpu_invalidate(td->page_cache);
|
||||
status = hc32toh(sc, td->qtd_status);
|
||||
|
||||
/*
|
||||
* if there is an active TD the transfer isn't done
|
||||
* Check if there is an active TD which
|
||||
* indicates that the transfer isn't done.
|
||||
*/
|
||||
if (status & EHCI_QTD_ACTIVE) {
|
||||
/* update cache */
|
||||
xfer->td_transfer_cache = td;
|
||||
if (xfer->td_transfer_cache != td) {
|
||||
xfer->td_transfer_cache = td;
|
||||
if (qh->qh_qtd.qtd_next &
|
||||
htohc32(sc, EHCI_LINK_TERMINATE)) {
|
||||
/* XXX - manually advance to next frame */
|
||||
qh->qh_qtd.qtd_next = td->qtd_self;
|
||||
usb_pc_cpu_flush(td->page_cache);
|
||||
}
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
/*
|
||||
|
@ -1545,7 +1575,6 @@ ehci_setup_standard_chain_sub(struct ehci_std_temp *temp)
|
|||
ehci_qtd_t *td;
|
||||
ehci_qtd_t *td_next;
|
||||
ehci_qtd_t *td_alt_next;
|
||||
uint32_t qtd_altnext;
|
||||
uint32_t buf_offset;
|
||||
uint32_t average;
|
||||
uint32_t len_old;
|
||||
|
@ -1554,7 +1583,6 @@ ehci_setup_standard_chain_sub(struct ehci_std_temp *temp)
|
|||
uint8_t precompute;
|
||||
|
||||
terminate = htohc32(temp->sc, EHCI_LINK_TERMINATE);
|
||||
qtd_altnext = terminate;
|
||||
td_alt_next = NULL;
|
||||
buf_offset = 0;
|
||||
shortpkt_old = temp->shortpkt;
|
||||
|
@ -1612,7 +1640,8 @@ ehci_setup_standard_chain_sub(struct ehci_std_temp *temp)
|
|||
|
||||
td->qtd_status =
|
||||
temp->qtd_status |
|
||||
htohc32(temp->sc, EHCI_QTD_SET_BYTES(average));
|
||||
htohc32(temp->sc, EHCI_QTD_IOC |
|
||||
EHCI_QTD_SET_BYTES(average));
|
||||
|
||||
if (average == 0) {
|
||||
|
||||
|
@ -1687,11 +1716,23 @@ ehci_setup_standard_chain_sub(struct ehci_std_temp *temp)
|
|||
td->qtd_buffer_hi[x] = 0;
|
||||
}
|
||||
|
||||
if (td_next) {
|
||||
/* link the current TD with the next one */
|
||||
td->qtd_next = td_next->qtd_self;
|
||||
if (temp->can_use_next) {
|
||||
if (td_next) {
|
||||
/* link the current TD with the next one */
|
||||
td->qtd_next = td_next->qtd_self;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* BUG WARNING: The EHCI HW can use the
|
||||
* qtd_next field instead of qtd_altnext when
|
||||
* a short packet is received! We work this
|
||||
* around in software by not queueing more
|
||||
* than one job/TD at a time!
|
||||
*/
|
||||
td->qtd_next = terminate;
|
||||
}
|
||||
td->qtd_altnext = qtd_altnext;
|
||||
|
||||
td->qtd_altnext = terminate;
|
||||
td->alt_next = td_alt_next;
|
||||
|
||||
usb_pc_cpu_flush(td->page_cache);
|
||||
|
@ -1703,15 +1744,9 @@ ehci_setup_standard_chain_sub(struct ehci_std_temp *temp)
|
|||
/* setup alt next pointer, if any */
|
||||
if (temp->last_frame) {
|
||||
td_alt_next = NULL;
|
||||
qtd_altnext = terminate;
|
||||
} else {
|
||||
/* we use this field internally */
|
||||
td_alt_next = td_next;
|
||||
if (temp->setup_alt_next) {
|
||||
qtd_altnext = td_next->qtd_self;
|
||||
} else {
|
||||
qtd_altnext = terminate;
|
||||
}
|
||||
}
|
||||
|
||||
/* restore */
|
||||
|
@ -1756,6 +1791,8 @@ ehci_setup_standard_chain(struct usb_xfer *xfer, ehci_qh_t **qh_last)
|
|||
temp.qtd_status = 0;
|
||||
temp.last_frame = 0;
|
||||
temp.setup_alt_next = xfer->flags_int.short_frames_ok;
|
||||
temp.can_use_next = (xfer->flags_int.control_xfr ||
|
||||
(UE_GET_DIR(xfer->endpointno) == UE_DIR_OUT));
|
||||
|
||||
if (xfer->flags_int.control_xfr) {
|
||||
if (xfer->endpoint->toggle_next) {
|
||||
|
@ -1889,7 +1926,6 @@ ehci_setup_standard_chain(struct usb_xfer *xfer, ehci_qh_t **qh_last)
|
|||
/* the last TD terminates the transfer: */
|
||||
td->qtd_next = htohc32(temp.sc, EHCI_LINK_TERMINATE);
|
||||
td->qtd_altnext = htohc32(temp.sc, EHCI_LINK_TERMINATE);
|
||||
td->qtd_status |= htohc32(temp.sc, EHCI_QTD_IOC);
|
||||
|
||||
usb_pc_cpu_flush(td->page_cache);
|
||||
|
||||
|
|
Loading…
Reference in a new issue