svn commit: r261106 - in stable/9/sys/dev/usb: . controller
Hans Petter Selasky
hselasky at FreeBSD.org
Fri Jan 24 07:57:23 UTC 2014
Author: hselasky
Date: Fri Jan 24 07:57:21 2014
New Revision: 261106
URL: http://svnweb.freebsd.org/changeset/base/261106
Log:
MFC r260588 and r260589:
- Separate I/O errors from reception of STALL PID.
- Implement better error recovery for Transaction Translators, TTs,
found in High Speed USB HUBs which translate from High Speed USB into
FULL or LOW speed USB. In some rare cases SPLIT transactions might get
lost, which might leave the TT in an unknown state. Whenever we detect
such an error try to issue either a clear TT buffer request, or if
that is not possible reset the whole TT.
Modified:
stable/9/sys/dev/usb/controller/ehci.c
stable/9/sys/dev/usb/controller/uhci.c
stable/9/sys/dev/usb/usb_device.c
stable/9/sys/dev/usb/usb_device.h
stable/9/sys/dev/usb/usb_hub.c
stable/9/sys/dev/usb/usb_hub.h
stable/9/sys/dev/usb/usb_request.c
stable/9/sys/dev/usb/usb_transfer.c
Directory Properties:
stable/9/sys/ (props changed)
stable/9/sys/dev/ (props changed)
Modified: stable/9/sys/dev/usb/controller/ehci.c
==============================================================================
--- stable/9/sys/dev/usb/controller/ehci.c Fri Jan 24 07:48:52 2014 (r261105)
+++ stable/9/sys/dev/usb/controller/ehci.c Fri Jan 24 07:57:21 2014 (r261106)
@@ -1195,9 +1195,16 @@ ehci_non_isoc_done_sub(struct usb_xfer *
(status & EHCI_QTD_PINGSTATE) ? "[PING]" : "");
}
#endif
-
- return ((status & EHCI_QTD_HALTED) ?
- USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION);
+ if (status & EHCI_QTD_HALTED) {
+ if ((xfer->xroot->udev->parent_hs_hub != NULL) ||
+ (xfer->xroot->udev->address != 0)) {
+ /* try to separate I/O errors from STALL */
+ if (EHCI_QTD_GET_CERR(status) == 0)
+ return (USB_ERR_IOERROR);
+ }
+ return (USB_ERR_STALLED);
+ }
+ return (USB_ERR_NORMAL_COMPLETION);
}
static void
Modified: stable/9/sys/dev/usb/controller/uhci.c
==============================================================================
--- stable/9/sys/dev/usb/controller/uhci.c Fri Jan 24 07:48:52 2014 (r261105)
+++ stable/9/sys/dev/usb/controller/uhci.c Fri Jan 24 07:57:21 2014 (r261106)
@@ -1176,8 +1176,13 @@ uhci_non_isoc_done_sub(struct usb_xfer *
(status & UHCI_TD_SPD) ? "[SPD]" : "");
}
#endif
- return (status & UHCI_TD_STALLED) ?
- USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION;
+ if (status & UHCI_TD_STALLED) {
+ /* try to separate I/O errors from STALL */
+ if (UHCI_TD_GET_ERRCNT(status) == 0)
+ return (USB_ERR_IOERROR);
+ return (USB_ERR_STALLED);
+ }
+ return (USB_ERR_NORMAL_COMPLETION);
}
static void
Modified: stable/9/sys/dev/usb/usb_device.c
==============================================================================
--- stable/9/sys/dev/usb/usb_device.c Fri Jan 24 07:48:52 2014 (r261105)
+++ stable/9/sys/dev/usb/usb_device.c Fri Jan 24 07:57:21 2014 (r261106)
@@ -94,7 +94,7 @@ static void usb_init_attach_arg(struct u
struct usb_attach_arg *);
static void usb_suspend_resume_sub(struct usb_device *, device_t,
uint8_t);
-static void usbd_clear_stall_proc(struct usb_proc_msg *_pm);
+static usb_proc_callback_t usbd_clear_stall_proc;
static usb_error_t usb_config_parse(struct usb_device *, uint8_t, uint8_t);
static void usbd_set_device_strings(struct usb_device *);
#if USB_HAVE_DEVCTL
@@ -1444,7 +1444,7 @@ usb_suspend_resume(struct usb_device *ud
static void
usbd_clear_stall_proc(struct usb_proc_msg *_pm)
{
- struct usb_clear_stall_msg *pm = (void *)_pm;
+ struct usb_udev_msg *pm = (void *)_pm;
struct usb_device *udev = pm->udev;
/* Change lock */
Modified: stable/9/sys/dev/usb/usb_device.h
==============================================================================
--- stable/9/sys/dev/usb/usb_device.h Fri Jan 24 07:48:52 2014 (r261105)
+++ stable/9/sys/dev/usb/usb_device.h Fri Jan 24 07:57:21 2014 (r261106)
@@ -53,7 +53,7 @@ struct usb_symlink; /* UGEN */
#define USB_UNCFG_FLAG_NONE 0x00
#define USB_UNCFG_FLAG_FREE_EP0 0x02 /* endpoint zero is freed */
-struct usb_clear_stall_msg {
+struct usb_udev_msg {
struct usb_proc_msg hdr;
struct usb_device *udev;
};
@@ -179,8 +179,8 @@ union usb_device_scratch {
* these structures for every USB device.
*/
struct usb_device {
- struct usb_clear_stall_msg cs_msg[2]; /* generic clear stall
- * messages */
+ /* generic clear stall message */
+ struct usb_udev_msg cs_msg[2];
struct sx enum_sx;
struct sx sr_sx;
struct mtx device_mtx;
@@ -304,4 +304,10 @@ void usbd_sr_lock(struct usb_device *);
void usbd_sr_unlock(struct usb_device *);
uint8_t usbd_enum_is_locked(struct usb_device *);
+#if USB_HAVE_TT_SUPPORT
+void uhub_tt_buffer_reset_async_locked(struct usb_device *, struct usb_endpoint *);
+#endif
+
+uint8_t uhub_count_active_host_ports(struct usb_device *, enum usb_dev_speed);
+
#endif /* _USB_DEVICE_H_ */
Modified: stable/9/sys/dev/usb/usb_hub.c
==============================================================================
--- stable/9/sys/dev/usb/usb_hub.c Fri Jan 24 07:48:52 2014 (r261105)
+++ stable/9/sys/dev/usb/usb_hub.c Fri Jan 24 07:57:21 2014 (r261106)
@@ -70,7 +70,13 @@
#include <dev/usb/usb_bus.h>
#define UHUB_INTR_INTERVAL 250 /* ms */
-#define UHUB_N_TRANSFER 1
+enum {
+ UHUB_INTR_TRANSFER,
+#if USB_HAVE_TT_SUPPORT
+ UHUB_RESET_TT_TRANSFER,
+#endif
+ UHUB_N_TRANSFER,
+};
#ifdef USB_DEBUG
static int uhub_debug = 0;
@@ -123,6 +129,9 @@ static bus_child_location_str_t uhub_chi
static bus_child_pnpinfo_str_t uhub_child_pnpinfo_string;
static usb_callback_t uhub_intr_callback;
+#if USB_HAVE_TT_SUPPORT
+static usb_callback_t uhub_reset_tt_callback;
+#endif
static void usb_dev_resume_peer(struct usb_device *udev);
static void usb_dev_suspend_peer(struct usb_device *udev);
@@ -130,7 +139,7 @@ static uint8_t usb_peer_should_wakeup(st
static const struct usb_config uhub_config[UHUB_N_TRANSFER] = {
- [0] = {
+ [UHUB_INTR_TRANSFER] = {
.type = UE_INTERRUPT,
.endpoint = UE_ADDR_ANY,
.direction = UE_DIR_ANY,
@@ -140,6 +149,17 @@ static const struct usb_config uhub_conf
.callback = &uhub_intr_callback,
.interval = UHUB_INTR_INTERVAL,
},
+#if USB_HAVE_TT_SUPPORT
+ [UHUB_RESET_TT_TRANSFER] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &uhub_reset_tt_callback,
+ .timeout = 1000, /* 1 second */
+ .usb_mode = USB_MODE_HOST,
+ },
+#endif
};
/*
@@ -209,6 +229,199 @@ uhub_intr_callback(struct usb_xfer *xfer
}
/*------------------------------------------------------------------------*
+ * uhub_reset_tt_proc
+ *
+ * This function starts the TT reset USB request
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_TT_SUPPORT
+static void
+uhub_reset_tt_proc(struct usb_proc_msg *_pm)
+{
+ struct usb_udev_msg *pm = (void *)_pm;
+ struct usb_device *udev = pm->udev;
+ struct usb_hub *hub;
+ struct uhub_softc *sc;
+
+ hub = udev->hub;
+ if (hub == NULL)
+ return;
+ sc = hub->hubsoftc;
+ if (sc == NULL)
+ return;
+
+ /* Change lock */
+ USB_BUS_UNLOCK(udev->bus);
+ mtx_lock(&sc->sc_mtx);
+ /* Start transfer */
+ usbd_transfer_start(sc->sc_xfer[UHUB_RESET_TT_TRANSFER]);
+ /* Change lock */
+ mtx_unlock(&sc->sc_mtx);
+ USB_BUS_LOCK(udev->bus);
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * uhub_tt_buffer_reset_async_locked
+ *
+ * This function queues a TT reset for the given USB device and endpoint.
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_TT_SUPPORT
+void
+uhub_tt_buffer_reset_async_locked(struct usb_device *child, struct usb_endpoint *ep)
+{
+ struct usb_device_request req;
+ struct usb_device *udev;
+ struct usb_hub *hub;
+ struct usb_port *up;
+ uint16_t wValue;
+ uint8_t port;
+
+ if (child == NULL || ep == NULL)
+ return;
+
+ udev = child->parent_hs_hub;
+ port = child->hs_port_no;
+
+ if (udev == NULL)
+ return;
+
+ hub = udev->hub;
+ if ((hub == NULL) ||
+ (udev->speed != USB_SPEED_HIGH) ||
+ (child->speed != USB_SPEED_LOW &&
+ child->speed != USB_SPEED_FULL) ||
+ (child->flags.usb_mode != USB_MODE_HOST) ||
+ (port == 0) || (ep->edesc == NULL)) {
+ /* not applicable */
+ return;
+ }
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ up = hub->ports + port - 1;
+
+ if (udev->ddesc.bDeviceClass == UDCLASS_HUB &&
+ udev->ddesc.bDeviceProtocol == UDPROTO_HSHUBSTT)
+ port = 1;
+
+ /* if we already received a clear buffer request, reset the whole TT */
+ if (up->req_reset_tt.bRequest != 0) {
+ req.bmRequestType = UT_WRITE_CLASS_OTHER;
+ req.bRequest = UR_RESET_TT;
+ USETW(req.wValue, 0);
+ req.wIndex[0] = port;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+ } else {
+ wValue = (ep->edesc->bEndpointAddress & 0xF) |
+ ((child->address & 0x7F) << 4) |
+ ((ep->edesc->bEndpointAddress & 0x80) << 8) |
+ ((ep->edesc->bmAttributes & 3) << 12);
+
+ req.bmRequestType = UT_WRITE_CLASS_OTHER;
+ req.bRequest = UR_CLEAR_TT_BUFFER;
+ USETW(req.wValue, wValue);
+ req.wIndex[0] = port;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+ }
+ up->req_reset_tt = req;
+ /* get reset transfer started */
+ usb_proc_msignal(&udev->bus->non_giant_callback_proc,
+ &hub->tt_msg[0], &hub->tt_msg[1]);
+}
+#endif
+
+#if USB_HAVE_TT_SUPPORT
+static void
+uhub_reset_tt_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhub_softc *sc;
+ struct usb_device *udev;
+ struct usb_port *up;
+ uint8_t x;
+
+ DPRINTF("TT buffer reset\n");
+
+ sc = usbd_xfer_softc(xfer);
+ udev = sc->sc_udev;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ case USB_ST_SETUP:
+tr_setup:
+ USB_BUS_LOCK(udev->bus);
+ /* find first port which needs a TT reset */
+ for (x = 0; x != udev->hub->nports; x++) {
+ up = udev->hub->ports + x;
+
+ if (up->req_reset_tt.bRequest == 0)
+ continue;
+
+ /* copy in the transfer */
+ usbd_copy_in(xfer->frbuffers, 0, &up->req_reset_tt,
+ sizeof(up->req_reset_tt));
+ /* reset buffer */
+ memset(&up->req_reset_tt, 0, sizeof(up->req_reset_tt));
+
+ /* set length */
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(up->req_reset_tt));
+ xfer->nframes = 1;
+ USB_BUS_UNLOCK(udev->bus);
+
+ usbd_transfer_submit(xfer);
+ return;
+ }
+ USB_BUS_UNLOCK(udev->bus);
+ break;
+
+ default:
+ if (error == USB_ERR_CANCELLED)
+ break;
+
+ DPRINTF("TT buffer reset failed (%s)\n", usbd_errstr(error));
+ goto tr_setup;
+ }
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * uhub_count_active_host_ports
+ *
+ * This function counts the number of active ports at the given speed.
+ *------------------------------------------------------------------------*/
+uint8_t
+uhub_count_active_host_ports(struct usb_device *udev, enum usb_dev_speed speed)
+{
+ struct uhub_softc *sc;
+ struct usb_device *child;
+ struct usb_hub *hub;
+ struct usb_port *up;
+ uint8_t retval = 0;
+ uint8_t x;
+
+ if (udev == NULL)
+ goto done;
+ hub = udev->hub;
+ if (hub == NULL)
+ goto done;
+ sc = hub->hubsoftc;
+ if (sc == NULL)
+ goto done;
+
+ for (x = 0; x != hub->nports; x++) {
+ up = hub->ports + x;
+ child = usb_bus_port_get_device(udev->bus, up);
+ if (child != NULL &&
+ child->flags.usb_mode == USB_MODE_HOST &&
+ child->speed == speed)
+ retval++;
+ }
+done:
+ return (retval);
+}
+
+/*------------------------------------------------------------------------*
* uhub_explore_sub - subroutine
*
* Return values:
@@ -1105,7 +1318,12 @@ uhub_attach(device_t dev)
hub->explore = &uhub_explore;
hub->nports = nports;
hub->hubudev = udev;
-
+#if USB_HAVE_TT_SUPPORT
+ hub->tt_msg[0].hdr.pm_callback = &uhub_reset_tt_proc;
+ hub->tt_msg[0].udev = udev;
+ hub->tt_msg[1].hdr.pm_callback = &uhub_reset_tt_proc;
+ hub->tt_msg[1].udev = udev;
+#endif
/* if self powered hub, give ports maximum current */
if (udev->flags.self_powered) {
hub->portpower = USB_MAX_POWER;
@@ -1207,11 +1425,9 @@ uhub_attach(device_t dev)
/* Start the interrupt endpoint, if any */
- if (sc->sc_xfer[0] != NULL) {
- mtx_lock(&sc->sc_mtx);
- usbd_transfer_start(sc->sc_xfer[0]);
- mtx_unlock(&sc->sc_mtx);
- }
+ mtx_lock(&sc->sc_mtx);
+ usbd_transfer_start(sc->sc_xfer[UHUB_INTR_TRANSFER]);
+ mtx_unlock(&sc->sc_mtx);
/* Enable automatic power save on all USB HUBs */
@@ -1241,6 +1457,7 @@ uhub_detach(device_t dev)
{
struct uhub_softc *sc = device_get_softc(dev);
struct usb_hub *hub = sc->sc_udev->hub;
+ struct usb_bus *bus = sc->sc_udev->bus;
struct usb_device *child;
uint8_t x;
@@ -1253,7 +1470,7 @@ uhub_detach(device_t dev)
/* Detach all ports */
for (x = 0; x != hub->nports; x++) {
- child = usb_bus_port_get_device(sc->sc_udev->bus, hub->ports + x);
+ child = usb_bus_port_get_device(bus, hub->ports + x);
if (child == NULL) {
continue;
@@ -1265,6 +1482,13 @@ uhub_detach(device_t dev)
usb_free_device(child, 0);
}
+#if USB_HAVE_TT_SUPPORT
+ /* Make sure our TT messages are not queued anywhere */
+ USB_BUS_LOCK(bus);
+ usb_proc_mwait(&bus->non_giant_callback_proc,
+ &hub->tt_msg[0], &hub->tt_msg[1]);
+ USB_BUS_UNLOCK(bus);
+#endif
free(hub, M_USBDEV);
sc->sc_udev->hub = NULL;
Modified: stable/9/sys/dev/usb/usb_hub.h
==============================================================================
--- stable/9/sys/dev/usb/usb_hub.h Fri Jan 24 07:48:52 2014 (r261105)
+++ stable/9/sys/dev/usb/usb_hub.h Fri Jan 24 07:57:21 2014 (r261106)
@@ -35,6 +35,9 @@ struct usb_port {
#define USB_RESTART_MAX 5
uint8_t device_index; /* zero means not valid */
enum usb_hc_mode usb_mode; /* host or device mode */
+#if USB_HAVE_TT_SUPPORT
+ struct usb_device_request req_reset_tt __aligned(4);
+#endif
};
/*
@@ -44,6 +47,9 @@ struct usb_hub {
struct usb_device *hubudev; /* the HUB device */
usb_error_t (*explore) (struct usb_device *hub);
void *hubsoftc;
+#if USB_HAVE_TT_SUPPORT
+ struct usb_udev_msg tt_msg[2];
+#endif
usb_size_t uframe_usage[USB_HS_MICRO_FRAMES_MAX];
uint16_t portpower; /* mA per USB port */
uint8_t isoc_last_time;
Modified: stable/9/sys/dev/usb/usb_request.c
==============================================================================
--- stable/9/sys/dev/usb/usb_request.c Fri Jan 24 07:48:52 2014 (r261105)
+++ stable/9/sys/dev/usb/usb_request.c Fri Jan 24 07:57:21 2014 (r261106)
@@ -714,6 +714,17 @@ done:
if ((mtx != NULL) && (mtx != &Giant))
mtx_lock(mtx);
+ switch (err) {
+ case USB_ERR_NORMAL_COMPLETION:
+ case USB_ERR_SHORT_XFER:
+ case USB_ERR_STALLED:
+ case USB_ERR_CANCELLED:
+ break;
+ default:
+ DPRINTF("I/O error - waiting a bit for TT cleanup\n");
+ usb_pause_mtx(mtx, hz / 16);
+ break;
+ }
return ((usb_error_t)err);
}
@@ -1965,6 +1976,7 @@ usbd_req_re_enumerate(struct usb_device
return (USB_ERR_INVAL);
}
retry:
+#if USB_HAVE_TT_SUPPORT
/*
* Try to reset the High Speed parent HUB of a LOW- or FULL-
* speed device, if any.
@@ -1972,15 +1984,24 @@ retry:
if (udev->parent_hs_hub != NULL &&
udev->speed != USB_SPEED_HIGH) {
DPRINTF("Trying to reset parent High Speed TT.\n");
- err = usbd_req_reset_tt(udev->parent_hs_hub, NULL,
- udev->hs_port_no);
+ if (udev->parent_hs_hub == parent_hub &&
+ (uhub_count_active_host_ports(parent_hub, USB_SPEED_LOW) +
+ uhub_count_active_host_ports(parent_hub, USB_SPEED_FULL)) == 1) {
+ /* we can reset the whole TT */
+ err = usbd_req_reset_tt(parent_hub, NULL,
+ udev->hs_port_no);
+ } else {
+ /* only reset a particular device and endpoint */
+ err = usbd_req_clear_tt_buffer(udev->parent_hs_hub, NULL,
+ udev->hs_port_no, old_addr, UE_CONTROL, 0);
+ }
if (err) {
DPRINTF("Resetting parent High "
"Speed TT failed (%s).\n",
usbd_errstr(err));
}
}
-
+#endif
/* Try to warm reset first */
if (parent_hub->speed == USB_SPEED_SUPER)
usbd_req_warm_reset_port(parent_hub, mtx, udev->port_no);
Modified: stable/9/sys/dev/usb/usb_transfer.c
==============================================================================
--- stable/9/sys/dev/usb/usb_transfer.c Fri Jan 24 07:48:52 2014 (r261105)
+++ stable/9/sys/dev/usb/usb_transfer.c Fri Jan 24 07:57:21 2014 (r261106)
@@ -2366,7 +2366,9 @@ usbd_transfer_enqueue(struct usb_xfer_qu
void
usbd_transfer_done(struct usb_xfer *xfer, usb_error_t error)
{
- USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+ struct usb_xfer_root *info = xfer->xroot;
+
+ USB_BUS_LOCK_ASSERT(info->bus, MA_OWNED);
DPRINTF("err=%s\n", usbd_errstr(error));
@@ -2380,10 +2382,10 @@ usbd_transfer_done(struct usb_xfer *xfer
xfer->flags_int.control_act = 0;
return;
}
- /* only set transfer error if not already set */
- if (!xfer->error) {
+ /* only set transfer error, if not already set */
+ if (xfer->error == USB_ERR_NORMAL_COMPLETION)
xfer->error = error;
- }
+
/* stop any callouts */
usb_callout_stop(&xfer->timeout_handle);
@@ -2395,14 +2397,14 @@ usbd_transfer_done(struct usb_xfer *xfer
usbd_transfer_dequeue(xfer);
#if USB_HAVE_BUSDMA
- if (mtx_owned(xfer->xroot->xfer_mtx)) {
+ if (mtx_owned(info->xfer_mtx)) {
struct usb_xfer_queue *pq;
/*
* If the private USB lock is not locked, then we assume
* that the BUS-DMA load stage has been passed:
*/
- pq = &xfer->xroot->dma_q;
+ pq = &info->dma_q;
if (pq->curr == xfer) {
/* start the next BUS-DMA load, if any */
@@ -2412,10 +2414,10 @@ usbd_transfer_done(struct usb_xfer *xfer
#endif
/* keep some statistics */
if (xfer->error) {
- xfer->xroot->bus->stats_err.uds_requests
+ info->bus->stats_err.uds_requests
[xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE]++;
} else {
- xfer->xroot->bus->stats_ok.uds_requests
+ info->bus->stats_ok.uds_requests
[xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE]++;
}
@@ -2781,6 +2783,22 @@ usbd_callback_wrapper_sub(struct usb_xfe
/* end of control transfer, if any */
xfer->flags_int.control_act = 0;
+#if USB_HAVE_TT_SUPPORT
+ switch (xfer->error) {
+ case USB_ERR_NORMAL_COMPLETION:
+ case USB_ERR_SHORT_XFER:
+ case USB_ERR_STALLED:
+ case USB_ERR_CANCELLED:
+ /* nothing to do */
+ break;
+ default:
+ /* try to reset the TT, if any */
+ USB_BUS_LOCK(bus);
+ uhub_tt_buffer_reset_async_locked(xfer->xroot->udev, xfer->endpoint);
+ USB_BUS_UNLOCK(bus);
+ break;
+ }
+#endif
/* check if we should block the execution queue */
if ((xfer->error != USB_ERR_CANCELLED) &&
(xfer->flags.pipe_bof)) {
More information about the svn-src-stable-9
mailing list