summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorhselasky <hselasky@FreeBSD.org>2013-12-06 08:42:41 +0000
committerhselasky <hselasky@FreeBSD.org>2013-12-06 08:42:41 +0000
commit04d9725876f488c8cfcbbf47fa59835c70be59a7 (patch)
tree2fc3a84f7c496faad8c43d30f82377896847c373
parent681c6ef3ecabd11a13f4d291b100c0d036aa650f (diff)
downloadFreeBSD-src-04d9725876f488c8cfcbbf47fa59835c70be59a7.zip
FreeBSD-src-04d9725876f488c8cfcbbf47fa59835c70be59a7.tar.gz
Improve the XHCI command timeout recovery handling code.
MFC after: 1 week
-rw-r--r--sys/dev/usb/controller/usb_controller.c53
-rw-r--r--sys/dev/usb/controller/xhci.c97
-rw-r--r--sys/dev/usb/usb_bus.h1
-rw-r--r--sys/dev/usb/usb_controller.h1
4 files changed, 134 insertions, 18 deletions
diff --git a/sys/dev/usb/controller/usb_controller.c b/sys/dev/usb/controller/usb_controller.c
index 0abb42f..a1cbbb8 100644
--- a/sys/dev/usb/controller/usb_controller.c
+++ b/sys/dev/usb/controller/usb_controller.c
@@ -296,6 +296,28 @@ usb_resume(device_t dev)
}
/*------------------------------------------------------------------------*
+ * usb_bus_reset_async_locked
+ *------------------------------------------------------------------------*/
+void
+usb_bus_reset_async_locked(struct usb_bus *bus)
+{
+ USB_BUS_LOCK_ASSERT(bus, MA_OWNED);
+
+ DPRINTF("\n");
+
+ if (bus->reset_msg[0].hdr.pm_qentry.tqe_prev != NULL ||
+ bus->reset_msg[1].hdr.pm_qentry.tqe_prev != NULL) {
+ DPRINTF("Reset already pending\n");
+ return;
+ }
+
+ device_printf(bus->parent, "Resetting controller\n");
+
+ usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus),
+ &bus->reset_msg[0], &bus->reset_msg[1]);
+}
+
+/*------------------------------------------------------------------------*
* usb_shutdown
*------------------------------------------------------------------------*/
static int
@@ -429,6 +451,8 @@ usb_bus_suspend(struct usb_proc_msg *pm)
usb_error_t err;
uint8_t do_unlock;
+ DPRINTF("\n");
+
bus = ((struct usb_bus_msg *)pm)->bus;
udev = bus->devices[USB_ROOT_HUB_ADDR];
@@ -484,6 +508,8 @@ usb_bus_resume(struct usb_proc_msg *pm)
usb_error_t err;
uint8_t do_unlock;
+ DPRINTF("\n");
+
bus = ((struct usb_bus_msg *)pm)->bus;
udev = bus->devices[USB_ROOT_HUB_ADDR];
@@ -533,6 +559,28 @@ usb_bus_resume(struct usb_proc_msg *pm)
}
/*------------------------------------------------------------------------*
+ * usb_bus_reset
+ *
+ * This function is used to reset the USB contoller.
+ *------------------------------------------------------------------------*/
+static void
+usb_bus_reset(struct usb_proc_msg *pm)
+{
+ struct usb_bus *bus;
+
+ DPRINTF("\n");
+
+ bus = ((struct usb_bus_msg *)pm)->bus;
+
+ if (bus->bdev == NULL || bus->no_explore != 0)
+ return;
+
+ /* a suspend and resume will reset the USB controller */
+ usb_bus_suspend(pm);
+ usb_bus_resume(pm);
+}
+
+/*------------------------------------------------------------------------*
* usb_bus_shutdown
*
* This function is used to shutdown the USB contoller.
@@ -750,6 +798,11 @@ usb_attach_sub(device_t dev, struct usb_bus *bus)
bus->resume_msg[1].hdr.pm_callback = &usb_bus_resume;
bus->resume_msg[1].bus = bus;
+ bus->reset_msg[0].hdr.pm_callback = &usb_bus_reset;
+ bus->reset_msg[0].bus = bus;
+ bus->reset_msg[1].hdr.pm_callback = &usb_bus_reset;
+ bus->reset_msg[1].bus = bus;
+
bus->shutdown_msg[0].hdr.pm_callback = &usb_bus_shutdown;
bus->shutdown_msg[0].bus = bus;
bus->shutdown_msg[1].hdr.pm_callback = &usb_bus_shutdown;
diff --git a/sys/dev/usb/controller/xhci.c b/sys/dev/usb/controller/xhci.c
index 7402995..75f3d5e 100644
--- a/sys/dev/usb/controller/xhci.c
+++ b/sys/dev/usb/controller/xhci.c
@@ -278,6 +278,69 @@ xhci_ctx_get_le64(struct xhci_softc *sc, volatile uint64_t *ptr)
}
#endif
+static int
+xhci_reset_command_queue_locked(struct xhci_softc *sc)
+{
+ struct usb_page_search buf_res;
+ struct xhci_hw_root *phwr;
+ uint64_t addr;
+ uint32_t temp;
+
+ DPRINTF("\n");
+
+ temp = XREAD4(sc, oper, XHCI_CRCR_LO);
+ if (temp & XHCI_CRCR_LO_CRR) {
+ DPRINTF("Command ring running\n");
+ temp &= ~(XHCI_CRCR_LO_CS | XHCI_CRCR_LO_CA);
+
+ /*
+ * Try to abort the last command as per section
+ * 4.6.1.2 "Aborting a Command" of the XHCI
+ * specification:
+ */
+
+ /* stop and cancel */
+ XWRITE4(sc, oper, XHCI_CRCR_LO, temp | XHCI_CRCR_LO_CS);
+ XWRITE4(sc, oper, XHCI_CRCR_HI, 0);
+
+ XWRITE4(sc, oper, XHCI_CRCR_LO, temp | XHCI_CRCR_LO_CA);
+ XWRITE4(sc, oper, XHCI_CRCR_HI, 0);
+
+ /* wait 250ms */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 4);
+
+ /* check if command ring is still running */
+ temp = XREAD4(sc, oper, XHCI_CRCR_LO);
+ if (temp & XHCI_CRCR_LO_CRR) {
+ DPRINTF("Comand ring still running\n");
+ return (USB_ERR_IOERROR);
+ }
+ }
+
+ /* reset command ring */
+ sc->sc_command_ccs = 1;
+ sc->sc_command_idx = 0;
+
+ usbd_get_page(&sc->sc_hw.root_pc, 0, &buf_res);
+
+ /* setup command ring control base address */
+ addr = buf_res.physaddr;
+ phwr = buf_res.buffer;
+ addr += (uintptr_t)&((struct xhci_hw_root *)0)->hwr_commands[0];
+
+ DPRINTF("CRCR=0x%016llx\n", (unsigned long long)addr);
+
+ memset(phwr->hwr_commands, 0, sizeof(phwr->hwr_commands));
+ phwr->hwr_commands[XHCI_MAX_COMMANDS - 1].qwTrb0 = htole64(addr);
+
+ usb_pc_cpu_flush(&sc->sc_hw.root_pc);
+
+ XWRITE4(sc, oper, XHCI_CRCR_LO, ((uint32_t)addr) | XHCI_CRCR_LO_RCS);
+ XWRITE4(sc, oper, XHCI_CRCR_HI, (uint32_t)(addr >> 32));
+
+ return (0);
+}
+
usb_error_t
xhci_start_controller(struct xhci_softc *sc)
{
@@ -1059,6 +1122,7 @@ xhci_do_command(struct xhci_softc *sc, struct xhci_trb *trb,
uint32_t temp;
uint8_t i;
uint8_t j;
+ uint8_t timeout = 0;
int err;
XHCI_CMD_ASSERT_LOCKED(sc);
@@ -1072,7 +1136,7 @@ xhci_do_command(struct xhci_softc *sc, struct xhci_trb *trb,
/* Queue command */
USB_BUS_LOCK(&sc->sc_bus);
-
+retry:
i = sc->sc_command_idx;
j = sc->sc_command_ccs;
@@ -1143,25 +1207,22 @@ xhci_do_command(struct xhci_softc *sc, struct xhci_trb *trb,
err = 0;
}
if (err != 0) {
- DPRINTFN(0, "Command timeout!\n");
-
+ DPRINTF("Command timeout!\n");
/*
- * Try to abort the last command as per section
- * 4.6.1.2 "Aborting a Command" of the XHCI
- * specification:
+ * After some weeks of continuous operation, it has
+ * been observed that the ASMedia Technology, ASM1042
+ * SuperSpeed USB Host Controller can suddenly stop
+ * accepting commands via the command queue. Try to
+ * first reset the command queue. If that fails do a
+ * host controller reset.
*/
- temp = XREAD4(sc, oper, XHCI_CRCR_LO);
- XWRITE4(sc, oper, XHCI_CRCR_LO, temp | XHCI_CRCR_LO_CA);
-
- /* wait for abort event, if any */
- err = cv_timedwait(&sc->sc_cmd_cv, &sc->sc_bus.bus_mtx, hz / 16);
-
- if (err != 0 && xhci_interrupt_poll(sc) != 0) {
- DPRINTF("Command was completed when polling\n");
- err = 0;
- }
- if (err != 0) {
- DPRINTF("Command abort timeout!\n");
+ if (timeout == 0 &&
+ xhci_reset_command_queue_locked(sc) == 0) {
+ timeout = 1;
+ goto retry;
+ } else {
+ DPRINTF("Controller reset!\n");
+ usb_bus_reset_async_locked(&sc->sc_bus);
}
err = USB_ERR_TIMEOUT;
trb->dwTrb2 = 0;
diff --git a/sys/dev/usb/usb_bus.h b/sys/dev/usb/usb_bus.h
index 2898eee..9836058 100644
--- a/sys/dev/usb/usb_bus.h
+++ b/sys/dev/usb/usb_bus.h
@@ -81,6 +81,7 @@ struct usb_bus {
struct usb_bus_msg attach_msg[2];
struct usb_bus_msg suspend_msg[2];
struct usb_bus_msg resume_msg[2];
+ struct usb_bus_msg reset_msg[2];
struct usb_bus_msg shutdown_msg[2];
/*
* This mutex protects the USB hardware:
diff --git a/sys/dev/usb/usb_controller.h b/sys/dev/usb/usb_controller.h
index 3ee6f04..6a2f6b0 100644
--- a/sys/dev/usb/usb_controller.h
+++ b/sys/dev/usb/usb_controller.h
@@ -191,6 +191,7 @@ void usb_bus_mem_flush_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb);
uint8_t usb_bus_mem_alloc_all(struct usb_bus *bus, bus_dma_tag_t dmat, usb_bus_mem_cb_t *cb);
void usb_bus_mem_free_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb);
uint16_t usb_isoc_time_expand(struct usb_bus *bus, uint16_t isoc_time_curr);
+void usb_bus_reset_async_locked(struct usb_bus *bus);
#if USB_HAVE_TT_SUPPORT
uint8_t usbd_fs_isoc_schedule_alloc_slot(struct usb_xfer *isoc_xfer, uint16_t isoc_time);
#endif
OpenPOWER on IntegriCloud