diff options
Diffstat (limited to 'sys/dev/usb/controller/xhci.c')
| -rw-r--r-- | sys/dev/usb/controller/xhci.c | 1041 |
1 files changed, 552 insertions, 489 deletions
diff --git a/sys/dev/usb/controller/xhci.c b/sys/dev/usb/controller/xhci.c index 3dad0985b39d..c385631f22d7 100644 --- a/sys/dev/usb/controller/xhci.c +++ b/sys/dev/usb/controller/xhci.c @@ -132,28 +132,6 @@ SYSCTL_INT(_hw_usb_xhci, OID_AUTO, ctlstep, CTLFLAG_RWTUN, #define XHCI_INTR_ENDPT 1 -struct xhci_std_temp { - struct xhci_softc *sc; - struct usb_page_cache *pc; - struct xhci_td *td; - struct xhci_td *td_next; - uint32_t len; - uint32_t offset; - uint32_t max_packet_size; - uint32_t average; - uint32_t isoc_frame; - uint16_t isoc_delta; - uint8_t shortpkt; - uint8_t multishort; - uint8_t last_frame; - uint8_t trb_type; - uint8_t direction; - uint8_t tbc; - uint8_t tlbpc; - uint8_t step_td; - uint8_t do_isoc_sync; -}; - static void xhci_do_poll(struct usb_bus *); static void xhci_device_done(struct usb_xfer *, usb_error_t); static void xhci_get_xecp(struct xhci_softc *); @@ -997,6 +975,8 @@ xhci_check_transfer(struct xhci_softc *sc, struct xhci_trb *trb) continue; td = xfer->td_transfer_cache; + if (td == NULL) + continue; DPRINTFN(5, "Checking if 0x%016llx == (0x%016llx .. 0x%016llx)\n", (long long)td_event, @@ -1419,7 +1399,7 @@ xhci_cmd_set_address(struct xhci_softc *sc, uint64_t input_ctx, trb.dwTrb3 = htole32(temp); - return (xhci_do_command(sc, &trb, 500 /* ms */)); + return (xhci_do_command(sc, &trb, 1000 /* ms */)); } static usb_error_t @@ -1757,555 +1737,635 @@ xhci_do_poll(struct usb_bus *bus) USB_BUS_UNLOCK(&sc->sc_bus); } +/* + * Fill the link TRB at td->td_trb[td->ntrb]. + * + * td_next: the TD to link to (qwTrb0 is set to its physical address), or + * NULL when this is the end of the static chain (xhci_transfer_insert + * will later overwrite qwTrb0 with the ring-return address). + * chain: when true, CHAIN_BIT is set on this link TRB so the hardware + * continues into td_next without a TD boundary; this is used when + * one transfer frame spans multiple xhci_td objects. When false + * the link TRB ends the hardware TD. + * + * NOTE: link TRBs between two frames must not use chain=true; in particular + * isochronous frames must never be chained, see xhci_setup_isoc(). + */ static void -xhci_setup_generic_chain_sub(struct xhci_std_temp *temp) +xhci_td_fill_link(struct xhci_td *td, struct xhci_td *td_next, bool chain) { - struct usb_page_search buf_res; - struct xhci_td *td; - struct xhci_td *td_next; - struct xhci_td *td_alt_next; - struct xhci_td *td_first; - uint32_t buf_offset; - uint32_t average; - uint32_t len_old; - uint32_t npkt_off; + struct xhci_trb *trb; uint32_t dword; - uint8_t shortpkt_old; - uint8_t precompute; - uint8_t x; - - td_alt_next = NULL; - buf_offset = 0; - shortpkt_old = temp->shortpkt; - len_old = temp->len; - npkt_off = 0; - precompute = 1; -restart: - - td = temp->td; - td_next = td_first = temp->td_next; - - while (1) { - if (temp->len == 0) { - if (temp->shortpkt) - break; + trb = &td->td_trb[td->ntrb]; + trb->qwTrb0 = td_next != NULL ? htole64(td_next->td_self) : 0; + trb->dwTrb2 = 0; + dword = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK) | XHCI_TRB_3_CYCLE_BIT | + XHCI_TRB_3_IOC_BIT; + if (chain) + dword |= XHCI_TRB_3_CHAIN_BIT; + trb->dwTrb3 = htole32(dword); +} - /* send a Zero Length Packet, ZLP, last */ +/* + * Build Normal TRBs for one transfer frame. + * + * A single xhci_td holds TRBs for at most XHCI_TD_PAYLOAD_MAX bytes. + * Larger frames are split across consecutive TDs, pre-allocated by + * xhci_xfer_setup(), whose link TRBs carry CHAIN_BIT so that the hardware + * treats the whole frame as one TD. Returns the last TD used; the caller + * must take its obj_next to reach the TD for the next frame. + * + * cache: DMA page cache containing the data. + * offset: byte offset within cache (non-zero for isochronous frames that + * share a single frbuffer[0]). + * len: number of bytes in this frame (0 = zero-length packet). + * mps: maximum packet size of the endpoint. + * td: first transfer descriptor to fill. + * is_in: true for an IN endpoint (ISP_BIT is set on data TRBs). + * step_td: if true, leave CYCLE_BIT clear on the first TRB so that + * xhci_activate_transfer() can start it later (bulk IN stepping). + * last: true if this is the last frame of the transfer. + */ +static struct xhci_td * +xhci_setup_normal_trbs(struct usb_page_cache *cache, uint32_t offset, + uint32_t len, uint32_t mps, struct xhci_td *td, bool is_in, bool step_td, + bool last) +{ + struct xhci_trb *trb; + struct usb_page_search search; + struct xhci_td *td_first; + struct xhci_td *td_alt_next; + uint32_t cur_len, td_end, seg_len, npkt, dword; + bool is_final; + int i; - temp->shortpkt = 1; - average = 0; + td_first = td; + cur_len = 0; - } else { - average = temp->average; + for (;;) { + /* Number of bytes described by the current TD */ + td->len = len - cur_len; + if (td->len > XHCI_TD_PAYLOAD_MAX) + td->len = XHCI_TD_PAYLOAD_MAX; + td_end = cur_len + td->len; - if (temp->len < average) { - if (temp->len % temp->max_packet_size) { - temp->shortpkt = 1; - } - average = temp->len; + i = 0; + do { + trb = &td->td_trb[i]; + if (len > 0) { + usbd_get_page(cache, offset + cur_len, &search); + seg_len = search.length; + if (cur_len + seg_len > td_end) + seg_len = td_end - cur_len; + if (seg_len > XHCI_TD_PAGE_SIZE) + seg_len = XHCI_TD_PAGE_SIZE; + cur_len += seg_len; + } else { + /* Zero-length packet: no data buffer */ + memset(&search, 0, sizeof(search)); + seg_len = 0; } - } - - if (td_next == NULL) - panic("%s: out of XHCI transfer descriptors!", __FUNCTION__); - /* get next TD */ - - td = td_next; - td_next = td->obj_next; - - /* check if we are pre-computing */ + /* TD size counts the packets left in the frame */ + npkt = howmany(len - cur_len, mps); + if (npkt > 31) + npkt = 31; - if (precompute) { - /* update remaining length */ + trb->qwTrb0 = htole64(search.physaddr); + trb->dwTrb2 = htole32(XHCI_TRB_2_BYTES_SET(seg_len) | + XHCI_TRB_2_TDSZ_SET(npkt)); + dword = XHCI_TRB_3_CHAIN_BIT | + XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_NORMAL); + if (is_in) + dword |= XHCI_TRB_3_ISP_BIT; + /* First TRB of the frame: omit CYCLE if stepping */ + if (td != td_first || i > 0 || !step_td) + dword |= XHCI_TRB_3_CYCLE_BIT; + trb->dwTrb3 = htole32(dword); + i++; + } while (cur_len < td_end); - temp->len -= average; + is_final = (cur_len == len); - continue; + /* + * Last data TRB of each TD: add IOC so that the interrupt + * handler gets an event to advance td_transfer_cache. On + * the frame's final data TRB additionally remove CHAIN and + * clear the TD size. + */ + trb->dwTrb3 |= htole32(XHCI_TRB_3_IOC_BIT); + if (is_final) { + trb->dwTrb3 &= ~htole32(XHCI_TRB_3_CHAIN_BIT); + trb->dwTrb2 &= ~htole32(XHCI_TRB_2_TDSZ_SET(31)); } - /* fill out current TD */ - td->len = average; + td->ntrb = i; td->remainder = 0; td->status = 0; - /* update remaining length */ - - temp->len -= average; - - /* reset TRB index */ - - x = 0; - - if (temp->trb_type == XHCI_TRB_TYPE_SETUP_STAGE) { - /* immediate data */ - - if (average > 8) - average = 8; + /* + * Intermediate link TRBs keep CHAIN_BIT set so that the + * frame continues into the next TD without a TD boundary. + */ + xhci_td_fill_link(td, (is_final && last) ? NULL : td->obj_next, + !is_final); - td->td_trb[0].qwTrb0 = 0; + if (is_final) + break; - usbd_copy_out(temp->pc, temp->offset + buf_offset, - (uint8_t *)(uintptr_t)&td->td_trb[0].qwTrb0, - average); + if (td->obj_next == NULL) + panic("%s: out of XHCI transfer descriptors!", + __FUNCTION__); + td = td->obj_next; + } - dword = XHCI_TRB_2_BYTES_SET(8) | - XHCI_TRB_2_TDSZ_SET(0) | - XHCI_TRB_2_IRQ_SET(0); + /* + * All TDs of one frame must share the same alt_next: + * xhci_generic_done_sub() uses an alt_next change to detect the end + * of a frame, and a short packet must skip ahead to the next frame, + * not to the next TD of the same frame. + */ + td_alt_next = last ? NULL : td->obj_next; + for (;;) { + td_first->alt_next = td_alt_next; + usb_pc_cpu_flush(td_first->page_cache); + if (td_first == td) + break; + td_first = td_first->obj_next; + } - td->td_trb[0].dwTrb2 = htole32(dword); + return (td); +} - dword = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_SETUP_STAGE) | - XHCI_TRB_3_IDT_BIT | XHCI_TRB_3_CYCLE_BIT; +/* + * Build TRBs for a control transfer. Each stage (Setup, Data, Status) + * occupies its own xhci_td so that xhci_generic_done_sub() can account + * for each frame independently. Returns the last TD used. + */ +static struct xhci_td * +xhci_setup_ctrl(struct usb_xfer *xfer, struct xhci_td *td) +{ + struct xhci_softc *sc = XHCI_BUS2SC(xfer->xroot->bus); + struct xhci_trb *trb; + struct usb_page_search search; + uint32_t dword, len, cur_len, seg_len, npkt; + int x, i; + bool is_in, use_data_stage, step_td, is_last; - /* check wLength */ - if (td->td_trb[0].qwTrb0 & - htole64(XHCI_TRB_0_WLENGTH_MASK)) { - if (td->td_trb[0].qwTrb0 & - htole64(XHCI_TRB_0_DIR_IN_MASK)) - dword |= XHCI_TRB_3_TRT_IN; - else - dword |= XHCI_TRB_3_TRT_OUT; - } + is_in = !!(xfer->endpointno & UE_DIR_IN); + use_data_stage = !xfer->flags_int.control_did_data; - td->td_trb[0].dwTrb3 = htole32(dword); -#ifdef USB_DEBUG - xhci_dump_trb(&td->td_trb[x]); -#endif - x++; + /* ---- Setup stage ---- */ + if (xfer->flags_int.control_hdr) { + /* setup_only: no data or status to follow */ + bool setup_only = (xfer->nframes == 1) && + xfer->flags_int.control_act; - } else do { - uint32_t npkt; + trb = &td->td_trb[0]; + usbd_copy_out(&xfer->frbuffers[0], 0, + (uint8_t *)(uintptr_t)&trb->qwTrb0, 8); + trb->dwTrb2 = htole32( + XHCI_TRB_2_BYTES_SET(8) | XHCI_TRB_2_TDSZ_SET(0)); + /* + * IOC: the Setup stage is a complete one-TRB TD; without IOC + * the controller generates no Transfer Event for it and + * td_transfer_cache never advances past the Setup stage, so the + * control transfer (and thus enumeration) hangs. + */ + dword = XHCI_TRB_3_CYCLE_BIT | XHCI_TRB_3_IDT_BIT | + XHCI_TRB_3_IOC_BIT | + XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_SETUP_STAGE); + /* TRT field: set only when wLength != 0 (XHCI 1.2 §4.11.2.2) */ + if (trb->qwTrb0 & htole64(XHCI_TRB_0_WLENGTH_MASK)) + dword |= (trb->qwTrb0 & + htole64(XHCI_TRB_0_DIR_IN_MASK)) ? + XHCI_TRB_3_TRT_IN : + XHCI_TRB_3_TRT_OUT; + trb->dwTrb3 = htole32(dword); - /* fill out buffer pointers */ + td->ntrb = 1; + td->len = 8; + td->remainder = 0; + td->status = 0; + td->alt_next = setup_only ? NULL : td->obj_next; - if (average == 0) { - memset(&buf_res, 0, sizeof(buf_res)); - } else { - usbd_get_page(temp->pc, temp->offset + - buf_offset, &buf_res); + xhci_td_fill_link(td, setup_only ? NULL : td->obj_next, false); + usb_pc_cpu_flush(td->page_cache); - /* get length to end of page */ - if (buf_res.length > average) - buf_res.length = average; + if (setup_only) + return (td); + td = td->obj_next; + } - /* check for maximum length */ - if (buf_res.length > XHCI_TD_PAGE_SIZE) - buf_res.length = XHCI_TD_PAGE_SIZE; + /* ---- Data stages (frame indices 1 .. nframes-1) ---- */ + for (x = 1; x < xfer->nframes; x++) { + len = xfer->frlengths[x]; + is_last = (x == xfer->nframes - 1) && + xfer->flags_int.control_act; - npkt_off += buf_res.length; - } + cur_len = 0; + i = 0; - /* set up npkt */ - npkt = howmany(len_old - npkt_off, - temp->max_packet_size); + do { + trb = &td->td_trb[i]; + usbd_get_page(&xfer->frbuffers[x], cur_len, &search); + seg_len = search.length; + if (cur_len + seg_len > len) + seg_len = len - cur_len; + if (seg_len > XHCI_TD_PAGE_SIZE) + seg_len = XHCI_TD_PAGE_SIZE; + cur_len += seg_len; - if (npkt == 0) - npkt = 1; - else if (npkt > 31) + npkt = howmany(len - cur_len, xfer->max_packet_size); + if (npkt > 31) npkt = 31; - /* fill out TRB's */ - td->td_trb[x].qwTrb0 = - htole64((uint64_t)buf_res.physaddr); + trb->qwTrb0 = htole64(search.physaddr); + trb->dwTrb2 = htole32(XHCI_TRB_2_BYTES_SET(seg_len) | + XHCI_TRB_2_TDSZ_SET(npkt)); - dword = - XHCI_TRB_2_BYTES_SET(buf_res.length) | - XHCI_TRB_2_TDSZ_SET(npkt) | - XHCI_TRB_2_IRQ_SET(0); - - td->td_trb[x].dwTrb2 = htole32(dword); - - switch (temp->trb_type) { - case XHCI_TRB_TYPE_ISOCH: - dword = XHCI_TRB_3_CHAIN_BIT | XHCI_TRB_3_CYCLE_BIT | - XHCI_TRB_3_TBC_SET(temp->tbc) | - XHCI_TRB_3_TLBPC_SET(temp->tlbpc); - if (td != td_first) { - dword |= XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_NORMAL); - } else if (temp->do_isoc_sync != 0) { - temp->do_isoc_sync = 0; - /* wait until "isoc_frame" */ - dword |= XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_ISOCH) | - XHCI_TRB_3_FRID_SET(temp->isoc_frame / 8); - } else { - /* start data transfer at next interval */ - dword |= XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_ISOCH) | - XHCI_TRB_3_ISO_SIA_BIT; - } - if (temp->direction == UE_DIR_IN) - dword |= XHCI_TRB_3_ISP_BIT; - break; - case XHCI_TRB_TYPE_DATA_STAGE: - dword = XHCI_TRB_3_CHAIN_BIT | XHCI_TRB_3_CYCLE_BIT | - XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_DATA_STAGE); - if (temp->direction == UE_DIR_IN) - dword |= XHCI_TRB_3_DIR_IN | XHCI_TRB_3_ISP_BIT; - /* - * Section 3.2.9 in the XHCI - * specification about control - * transfers says that we should use a - * normal-TRB if there are more TRBs - * extending the data-stage - * TRB. Update the "trb_type". - */ - temp->trb_type = XHCI_TRB_TYPE_NORMAL; - break; - case XHCI_TRB_TYPE_STATUS_STAGE: - dword = XHCI_TRB_3_CHAIN_BIT | XHCI_TRB_3_CYCLE_BIT | - XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_STATUS_STAGE); - if (temp->direction == UE_DIR_IN) - dword |= XHCI_TRB_3_DIR_IN; - break; - default: /* XHCI_TRB_TYPE_NORMAL */ - dword = XHCI_TRB_3_CHAIN_BIT | XHCI_TRB_3_CYCLE_BIT | + /* + * XHCI 1.2 §4.11.2.2: first TRB of the data + * phase must be Data Stage type; subsequent + * TRBs (same or later data frame) use Normal. + */ + if (use_data_stage) { + dword = XHCI_TRB_3_CYCLE_BIT | + XHCI_TRB_3_CHAIN_BIT | + XHCI_TRB_3_TYPE_SET( + XHCI_TRB_TYPE_DATA_STAGE); + if (is_in) + dword |= XHCI_TRB_3_DIR_IN | + XHCI_TRB_3_ISP_BIT; + use_data_stage = false; + } else { + dword = XHCI_TRB_3_CYCLE_BIT | + XHCI_TRB_3_CHAIN_BIT | XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_NORMAL); - if (temp->direction == UE_DIR_IN) + if (is_in) dword |= XHCI_TRB_3_ISP_BIT; - break; } - td->td_trb[x].dwTrb3 = htole32(dword); - - average -= buf_res.length; - buf_offset += buf_res.length; -#ifdef USB_DEBUG - xhci_dump_trb(&td->td_trb[x]); -#endif - x++; - - } while (average != 0); - - td->td_trb[x-1].dwTrb3 |= htole32(XHCI_TRB_3_IOC_BIT); - - /* store number of data TRB's */ - - td->ntrb = x; - - DPRINTF("NTRB=%u\n", x); - - /* fill out link TRB */ + trb->dwTrb3 = htole32(dword); + i++; + } while (cur_len < len); + /* Fix last data TRB */ + trb->dwTrb3 &= ~htole32(XHCI_TRB_3_CHAIN_BIT); + trb->dwTrb3 |= htole32(XHCI_TRB_3_IOC_BIT); + trb->dwTrb2 &= ~htole32(XHCI_TRB_2_TDSZ_SET(31)); - if (td_next != NULL) { - /* link the current TD with the next one */ - td->td_trb[x].qwTrb0 = htole64((uint64_t)td_next->td_self); - DPRINTF("LINK=0x%08llx\n", (long long)td_next->td_self); - } else { - /* this field will get updated later */ - DPRINTF("NOLINK\n"); - } - - dword = XHCI_TRB_2_IRQ_SET(0); - - td->td_trb[x].dwTrb2 = htole32(dword); - - dword = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK) | - XHCI_TRB_3_CYCLE_BIT | XHCI_TRB_3_IOC_BIT | - /* - * CHAIN-BIT: Ensure that a multi-TRB IN-endpoint - * frame only receives a single short packet event - * by setting the CHAIN bit in the LINK field. In - * addition some XHCI controllers have problems - * sending a ZLP unless the CHAIN-BIT is set in - * the LINK TRB. - */ - XHCI_TRB_3_CHAIN_BIT; - - td->td_trb[x].dwTrb3 = htole32(dword); + td->ntrb = i; + td->len = len; + td->remainder = 0; + td->status = 0; + td->alt_next = is_last ? NULL : td->obj_next; - td->alt_next = td_alt_next; -#ifdef USB_DEBUG - xhci_dump_trb(&td->td_trb[x]); -#endif + xhci_td_fill_link(td, is_last ? NULL : td->obj_next, false); usb_pc_cpu_flush(td->page_cache); - } - - if (precompute) { - precompute = 0; - - /* set up alt next pointer, if any */ - if (temp->last_frame) { - td_alt_next = NULL; - } else { - /* we use this field internally */ - td_alt_next = td_next; - } - /* restore */ - temp->shortpkt = shortpkt_old; - temp->len = len_old; - goto restart; + if (is_last) + return (td); + td = td->obj_next; } + /* ---- Status stage ---- */ + /* - * Remove cycle bit from the first TRB if we are - * stepping them: + * Some XHCI controllers will not delay the status stage until the + * next SOF, causing control transfer failures. When ctlstep is + * set we leave CYCLE_BIT clear; xhci_activate_transfer() enables it + * after the data stage has completed. */ - if (temp->step_td != 0) { - td_first->td_trb[0].dwTrb3 &= ~htole32(XHCI_TRB_3_CYCLE_BIT); - usb_pc_cpu_flush(td_first->page_cache); - } + step_td = (xhcictlstep || sc->sc_ctlstep) && (xfer->nframes != 0); - /* clear TD SIZE to zero, hence this is the last TRB */ - /* remove chain bit because this is the last data TRB in the chain */ - td->td_trb[td->ntrb - 1].dwTrb2 &= ~htole32(XHCI_TRB_2_TDSZ_SET(31)); - td->td_trb[td->ntrb - 1].dwTrb3 &= ~htole32(XHCI_TRB_3_CHAIN_BIT); - /* remove CHAIN-BIT from last LINK TRB */ - td->td_trb[td->ntrb].dwTrb3 &= ~htole32(XHCI_TRB_3_CHAIN_BIT); + trb = &td->td_trb[0]; + trb->qwTrb0 = 0; + trb->dwTrb2 = htole32(XHCI_TRB_2_BYTES_SET(0) | XHCI_TRB_2_TDSZ_SET(0)); + dword = XHCI_TRB_3_IOC_BIT | + XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_STATUS_STAGE); + /* Status direction is opposite to the data direction */ + if (!is_in) + dword |= XHCI_TRB_3_DIR_IN; + if (!step_td) + dword |= XHCI_TRB_3_CYCLE_BIT; + trb->dwTrb3 = htole32(dword); + td->ntrb = 1; + td->len = 0; + td->remainder = 0; + td->status = 0; + td->alt_next = NULL; + + xhci_td_fill_link(td, NULL, false); usb_pc_cpu_flush(td->page_cache); - temp->td = td; - temp->td_next = td_next; + return (td); } -static void -xhci_setup_generic_chain(struct usb_xfer *xfer) +/* + * Build TRBs for a bulk (or interrupt) transfer. + * Each frame gets its own xhci_td. Returns the last TD used. + */ +static struct xhci_td * +xhci_setup_bulk(struct usb_xfer *xfer, struct xhci_td *td) { - struct xhci_std_temp temp; - struct xhci_td *td; - uint32_t x; - uint32_t y; - uint8_t mult; - - temp.do_isoc_sync = 0; - temp.step_td = 0; - temp.tbc = 0; - temp.tlbpc = 0; - temp.average = xfer->max_hc_frame_size; - temp.max_packet_size = xfer->max_packet_size; - temp.sc = XHCI_BUS2SC(xfer->xroot->bus); - temp.pc = NULL; - temp.last_frame = 0; - temp.offset = 0; - temp.multishort = xfer->flags_int.isochronous_xfr || - xfer->flags_int.control_xfr || - xfer->flags_int.short_frames_ok; - - /* toggle the DMA set we are using */ - xfer->flags_int.curr_dma_set ^= 1; - - /* get next DMA set */ - td = xfer->td_start[xfer->flags_int.curr_dma_set]; - - temp.td = NULL; - temp.td_next = td; - - xfer->td_transfer_first = td; - xfer->td_transfer_cache = td; - - if (xfer->flags_int.isochronous_xfr) { - uint8_t shift; - - /* compute multiplier for ISOCHRONOUS transfers */ - mult = xfer->endpoint->ecomp ? - UE_GET_SS_ISO_MULT(xfer->endpoint->ecomp->bmAttributes) - : 0; - /* check for USB 2.0 multiplier */ - if (mult == 0) { - mult = (xfer->endpoint->edesc-> - wMaxPacketSize[1] >> 3) & 3; - } - /* range check */ - if (mult > 2) - mult = 3; - else - mult++; - - x = XREAD4(temp.sc, runt, XHCI_MFINDEX); + bool is_in = !!(xfer->endpointno & UE_DIR_IN); + bool multishort = xfer->flags_int.short_frames_ok; + struct xhci_td *td_last; + int i; - DPRINTF("MFINDEX=0x%08x IST=0x%x\n", x, temp.sc->sc_ist); + td_last = td; + for (i = 0; i < xfer->nframes; i++) { + bool is_last = (i == xfer->nframes - 1); + /* + * Bulk IN: skip CYCLE on the first TRB of non-first frames + * (unless short frames are allowed) so that the host + * controller does not start the next frame before software + * calls xhci_activate_transfer(). + */ + bool step_td = is_in && (i != 0) && !multishort; - switch (usbd_get_speed(xfer->xroot->udev)) { - case USB_SPEED_FULL: - shift = 3; - temp.isoc_delta = 8; /* 1ms */ - break; - default: - shift = usbd_xfer_get_fps_shift(xfer); - temp.isoc_delta = 1U << shift; - break; - } + td = xhci_setup_normal_trbs(&xfer->frbuffers[i], 0, + xfer->frlengths[i], xfer->max_packet_size, td, is_in, + step_td, is_last); + td_last = td; + td = td->obj_next; + } - /* Compute isochronous scheduling threshold. */ - if (temp.sc->sc_ist & 8) - y = (temp.sc->sc_ist & 7) << 3; - else - y = (temp.sc->sc_ist & 7); + /* + * If force_short_xfer is set and the last frame length is a non-zero + * multiple of the max packet size, the hardware will not generate a + * short packet naturally, so we must append a zero-length TD. + * + * The last regular-frame TD was set up with qwTrb0=0 in its link TRB + * (the placeholder that xhci_transfer_insert would normally overwrite + * for the final TD). Since the ZLP TD is now the true final TD, that + * link TRB must be patched: qwTrb0 must point to the ZLP TD, and + * CHAIN_BIT must be set. Some xHCI controllers refuse to send a ZLP + * if the preceding link TRB does not have CHAIN_BIT set. + */ + if (xfer->flags.force_short_xfer && xfer->nframes > 0) { + uint32_t last_len = xfer->frlengths[xfer->nframes - 1]; - /* Range check the IST. */ - if (y < 8) { - y = 0; - } else if (y > 15) { - DPRINTFN(3, "IST(%d) is too big!\n", temp.sc->sc_ist); - /* - * The USB stack minimum isochronous transfer - * size is typically 2x2 ms of payload. If the - * IST makes is above 15 microframes, we have - * an effective scheduling delay of more than - * or equal to 2 milliseconds, which is too - * much. - */ - y = 7; - } else { + if (last_len > 0 && (last_len % xfer->max_packet_size) == 0) { + xhci_setup_normal_trbs(NULL, 0, 0, + xfer->max_packet_size, td, is_in, false, true); /* - * Subtract one millisecond, because the - * generic code adds that to the latency. + * Fix the previous TD's link TRB: point to the ZLP TD + * and set CHAIN_BIT. The address fix is necessary + * because xhci_transfer_insert only patches td_last's + * link TRB. The CHAIN_BIT is required because some + * xHCI controllers will not emit a ZLP unless the + * preceding link TRB has CHAIN_BIT set. */ - y -= 8; - } - - if (usbd_xfer_get_isochronous_start_frame( - xfer, x, y, 8, XHCI_MFINDEX_GET(-1), &temp.isoc_frame)) { - /* Start isochronous transfer at specified time. */ - temp.do_isoc_sync = 1; - - DPRINTFN(3, "start next=%d\n", temp.isoc_frame); + td_last->td_trb[td_last->ntrb].qwTrb0 = + htole64(td->td_self); + td_last->td_trb[td_last->ntrb].dwTrb3 |= htole32( + XHCI_TRB_3_CHAIN_BIT); + usb_pc_cpu_flush(td_last->page_cache); + td_last = td; } + } + return (td_last); +} - x = 0; - temp.trb_type = XHCI_TRB_TYPE_ISOCH; +/* + * Build TRBs for an isochronous transfer. + * + * All isochronous frame data lives in a single contiguous DMA buffer + * (frbuffers[0]); frlengths[x] gives the size of each frame. Each frame + * maps to one xhci_td whose first TRB is of type ISOCH and whose remaining + * TRBs (if data spans multiple pages) are of type NORMAL. + * + * Returns the last TD used. + */ +static struct xhci_td * +xhci_setup_isoc(struct usb_xfer *xfer, struct xhci_td *td) +{ + struct xhci_softc *sc = XHCI_BUS2SC(xfer->xroot->bus); + struct xhci_trb *trb; + struct usb_page_search search; + uint32_t dword, len, cur_len, buf_offset, seg_len, npkt; + uint32_t mfindex, isoc_frame, isoc_delta; + uint8_t mult, tdpc, tbc, tlbpc, shift, ist, y; + int x, i; + bool is_in = !!(xfer->endpointno & UE_DIR_IN); + bool do_isoc_sync = false; + struct xhci_td *td_last; - } else if (xfer->flags_int.control_xfr) { - /* check if we should prepend a setup message */ + /* Compute burst multiplier (SuperSpeed or USB 2.0 high-bandwidth) */ + mult = xfer->endpoint->ecomp ? + UE_GET_SS_ISO_MULT(xfer->endpoint->ecomp->bmAttributes) : + 0; + if (mult == 0) + mult = (xfer->endpoint->edesc->wMaxPacketSize[1] >> 3) & 3; + if (mult > 2) + mult = 3; + else + mult++; - if (xfer->flags_int.control_hdr) { - temp.len = xfer->frlengths[0]; - temp.pc = xfer->frbuffers + 0; - temp.shortpkt = temp.len ? 1 : 0; - temp.trb_type = XHCI_TRB_TYPE_SETUP_STAGE; - temp.direction = 0; + mfindex = XREAD4(sc, runt, XHCI_MFINDEX); + DPRINTF("MFINDEX=0x%08x IST=0x%x\n", mfindex, sc->sc_ist); - /* check for last frame */ - if (xfer->nframes == 1) { - /* no STATUS stage yet, SETUP is last */ - if (xfer->flags_int.control_act) - temp.last_frame = 1; - } + switch (usbd_get_speed(xfer->xroot->udev)) { + case USB_SPEED_FULL: + shift = 3; + isoc_delta = 8; /* 1 ms = 8 microframes */ + break; + default: + shift = usbd_xfer_get_fps_shift(xfer); + isoc_delta = 1U << shift; + break; + } - xhci_setup_generic_chain_sub(&temp); - } - x = 1; - mult = 1; - temp.isoc_delta = 0; - temp.isoc_frame = 0; - temp.trb_type = xfer->flags_int.control_did_data ? - XHCI_TRB_TYPE_NORMAL : XHCI_TRB_TYPE_DATA_STAGE; + /* Compute isochronous scheduling threshold (XHCI 1.2 §4.14.2) */ + ist = sc->sc_ist; + if (ist & 8) + y = (ist & 7) << 3; + else + y = (ist & 7); + if (y < 8) { + y = 0; + } else if (y > 15) { + DPRINTFN(3, "IST(%d) is too big!\n", ist); + /* + * The USB stack minimum isochronous transfer size is typically + * 2x2 ms of payload. An IST above 15 microframes gives a + * scheduling delay >= 2 ms, which is too much. + */ + y = 7; } else { - x = 0; - mult = 1; - temp.isoc_delta = 0; - temp.isoc_frame = 0; - temp.trb_type = XHCI_TRB_TYPE_NORMAL; + /* Subtract one millisecond added by the generic layer */ + y -= 8; } - if (x != xfer->nframes) { - /* set up page_cache pointer */ - temp.pc = xfer->frbuffers + x; - /* set endpoint direction */ - temp.direction = UE_GET_DIR(xfer->endpointno); + if (usbd_xfer_get_isochronous_start_frame(xfer, mfindex, y, 8, + XHCI_MFINDEX_GET(-1), &isoc_frame)) { + /* Synchronise to a specific frame number */ + do_isoc_sync = true; + DPRINTFN(3, "start next=%d\n", isoc_frame); } - while (x != xfer->nframes) { - /* DATA0 / DATA1 message */ + buf_offset = 0; + td_last = td; - temp.len = xfer->frlengths[x]; - temp.step_td = ((xfer->endpointno & UE_DIR_IN) && - x != 0 && temp.multishort == 0); + for (x = 0; x < xfer->nframes; x++) { + bool is_last = (x == xfer->nframes - 1); + struct xhci_td *td_next = is_last ? NULL : td->obj_next; - x++; + len = xfer->frlengths[x]; + if (len > xfer->max_frame_size) + len = xfer->max_frame_size; - if (x == xfer->nframes) { - if (xfer->flags_int.control_xfr) { - /* no STATUS stage yet, DATA is last */ - if (xfer->flags_int.control_act) - temp.last_frame = 1; - } else { - temp.last_frame = 1; - } + /* Compute TBC and TLBPC (XHCI 1.2 §4.11.2.3) */ + if (len == 0) { + tbc = 0; + tlbpc = mult - 1; + } else { + tdpc = howmany(len, xfer->max_packet_size); + tbc = howmany(tdpc, mult) - 1; + tlbpc = tdpc % mult; + if (tlbpc == 0) + tlbpc = mult - 1; + else + tlbpc--; } - if (temp.len == 0) { - /* make sure that we send an USB packet */ - - temp.shortpkt = 0; - - temp.tbc = 0; - temp.tlbpc = mult - 1; - - } else if (xfer->flags_int.isochronous_xfr) { - uint8_t tdpc; - - /* - * Isochronous transfers don't have short - * packet termination: - */ - - temp.shortpkt = 1; - - /* isochronous transfers have a transfer limit */ - - if (temp.len > xfer->max_frame_size) - temp.len = xfer->max_frame_size; - - /* compute TD packet count */ - tdpc = howmany(temp.len, xfer->max_packet_size); - temp.tbc = howmany(tdpc, mult) - 1; - temp.tlbpc = (tdpc % mult); + cur_len = 0; + i = 0; - if (temp.tlbpc == 0) - temp.tlbpc = mult - 1; - else - temp.tlbpc--; + if (len == 0) { + /* Zero-length isochronous frame */ + trb = &td->td_trb[0]; + trb->qwTrb0 = 0; + trb->dwTrb2 = htole32( + XHCI_TRB_2_BYTES_SET(0) | XHCI_TRB_2_TDSZ_SET(0)); + dword = XHCI_TRB_3_CYCLE_BIT | XHCI_TRB_3_IOC_BIT | + XHCI_TRB_3_TBC_SET(tbc) | + XHCI_TRB_3_TLBPC_SET(tlbpc) | + XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_ISOCH); + if (do_isoc_sync) { + do_isoc_sync = false; + dword |= XHCI_TRB_3_FRID_SET(isoc_frame / 8); + } else { + dword |= XHCI_TRB_3_ISO_SIA_BIT; + } + if (is_in) + dword |= XHCI_TRB_3_ISP_BIT; + trb->dwTrb3 = htole32(dword); + i = 1; } else { - /* regular data transfer */ + while (cur_len < len) { + trb = &td->td_trb[i]; + usbd_get_page(&xfer->frbuffers[0], + buf_offset + cur_len, &search); + seg_len = search.length; + if (cur_len + seg_len > len) + seg_len = len - cur_len; + if (seg_len > XHCI_TD_PAGE_SIZE) + seg_len = XHCI_TD_PAGE_SIZE; + cur_len += seg_len; - temp.shortpkt = xfer->flags.force_short_xfer ? 0 : 1; - } + npkt = howmany(len - cur_len, + xfer->max_packet_size); + if (npkt > 31) + npkt = 31; - xhci_setup_generic_chain_sub(&temp); + trb->qwTrb0 = htole64(search.physaddr); + trb->dwTrb2 = htole32( + XHCI_TRB_2_BYTES_SET(seg_len) | + XHCI_TRB_2_TDSZ_SET(npkt)); - if (xfer->flags_int.isochronous_xfr) { - temp.offset += xfer->frlengths[x - 1]; - temp.isoc_frame += temp.isoc_delta; - } else { - /* get next Page Cache pointer */ - temp.pc = xfer->frbuffers + x; + /* + * TBC and TLBPC are placed in the same bit + * positions for both ISOCH and NORMAL TRBs + * within an isochronous frame. + */ + dword = XHCI_TRB_3_CYCLE_BIT | + XHCI_TRB_3_CHAIN_BIT | + XHCI_TRB_3_TBC_SET(tbc) | + XHCI_TRB_3_TLBPC_SET(tlbpc); + if (i == 0) { + /* First TRB: ISOCH type */ + if (do_isoc_sync) { + do_isoc_sync = false; + dword |= + XHCI_TRB_3_TYPE_SET( + XHCI_TRB_TYPE_ISOCH) | + XHCI_TRB_3_FRID_SET( + isoc_frame / 8); + } else { + dword |= + XHCI_TRB_3_TYPE_SET( + XHCI_TRB_TYPE_ISOCH) | + XHCI_TRB_3_ISO_SIA_BIT; + } + } else { + /* Subsequent TRBs: NORMAL type */ + dword |= XHCI_TRB_3_TYPE_SET( + XHCI_TRB_TYPE_NORMAL); + } + if (is_in) + dword |= XHCI_TRB_3_ISP_BIT; + trb->dwTrb3 = htole32(dword); + i++; + } + /* Fix last data TRB */ + trb->dwTrb3 &= ~htole32(XHCI_TRB_3_CHAIN_BIT); + trb->dwTrb3 |= htole32(XHCI_TRB_3_IOC_BIT); + trb->dwTrb2 &= ~htole32(XHCI_TRB_2_TDSZ_SET(31)); } - } - /* check if we should append a status stage */ + td->ntrb = i; + td->len = len; + td->remainder = 0; + td->status = 0; + /* For isochronous, alt_next always follows to the next frame */ + td->alt_next = is_last ? NULL : td->obj_next; - if (xfer->flags_int.control_xfr && - !xfer->flags_int.control_act) { /* - * Send a DATA1 message and invert the current - * endpoint direction. + * Isochronous frames must NOT have CHAIN set on their link + * TRBs. The old xhci_setup_generic_chain_sub always removed + * CHAIN from the link TRB at the end of each per-frame call. + * With CHAIN set, some controllers treat consecutive ISOCH TDs + * as a single chained TD and generate only one Transfer Event + * for the whole transfer instead of one per frame, which causes + * td_transfer_cache to stall on the first frame and time out. + * Pass chain=false unconditionally so qwTrb0 is still written + * with td_next's address while CHAIN is suppressed. */ - if (xhcictlstep || temp.sc->sc_ctlstep) { - /* - * Some XHCI controllers will not delay the - * status stage until the next SOF. Force this - * behaviour to avoid failed control - * transfers. - */ - temp.step_td = (xfer->nframes != 0); - } else { - temp.step_td = 0; - } - temp.direction = UE_GET_DIR(xfer->endpointno) ^ UE_DIR_IN; - temp.len = 0; - temp.pc = NULL; - temp.shortpkt = 0; - temp.last_frame = 1; - temp.trb_type = XHCI_TRB_TYPE_STATUS_STAGE; + xhci_td_fill_link(td, td_next, false); + usb_pc_cpu_flush(td->page_cache); - xhci_setup_generic_chain_sub(&temp); + td_last = td; + td = td->obj_next; + buf_offset += xfer->frlengths[x]; + isoc_frame += isoc_delta; } - td = temp.td; + return (td_last); +} + +static void +xhci_setup_generic_chain(struct usb_xfer *xfer) +{ + struct xhci_td *td; + + /* Toggle the DMA set we are using */ + xfer->flags_int.curr_dma_set ^= 1; + + /* Get the first TD of this DMA set */ + td = xfer->td_start[xfer->flags_int.curr_dma_set]; + + xfer->td_transfer_first = td; + xfer->td_transfer_cache = td; - /* must have at least one frame! */ + if (xfer->flags_int.isochronous_xfr) + td = xhci_setup_isoc(xfer, td); + else if (xfer->flags_int.control_xfr) + td = xhci_setup_ctrl(xfer, td); + else + td = xhci_setup_bulk(xfer, td); xfer->td_transfer_last = td; @@ -3184,8 +3244,7 @@ xhci_device_generic_start(struct usb_xfer *xfer) usbd_transfer_timeout_ms(xfer, &xhci_timeout, xfer->timeout); } -static const struct usb_pipe_methods xhci_device_generic_methods = -{ +static const struct usb_pipe_methods xhci_device_generic_methods = { .open = xhci_device_generic_open, .close = xhci_device_generic_close, .enter = xhci_device_generic_enter, @@ -3898,10 +3957,8 @@ xhci_configure_reset_endpoint(struct usb_xfer *xfer) */ switch (xhci_get_endpoint_state(udev, epno)) { case XHCI_EPCTX_0_EPSTATE_DISABLED: - drop = 0; - break; case XHCI_EPCTX_0_EPSTATE_STOPPED: - drop = 1; + drop = 0; break; case XHCI_EPCTX_0_EPSTATE_HALTED: err = xhci_cmd_reset_ep(sc, 0, epno, index); @@ -3910,9 +3967,15 @@ xhci_configure_reset_endpoint(struct usb_xfer *xfer) DPRINTF("Could not reset endpoint %u\n", epno); break; default: - drop = 1; + /* + * xHCI spec 4.6.8: + * The Drop and Add operation resets the toggle bit, which can + * cause a toggle mismatch between the device and host. As a + * result, xHCI may refuse to receive or process the packet. + */ err = xhci_cmd_stop_ep(sc, 0, epno, index); - if (err != 0) + drop = (err != 0); + if (drop) DPRINTF("Could not stop endpoint %u\n", epno); break; } |
