summaryrefslogtreecommitdiffstats
path: root/freebsd/sys/dev/usb/usb_hub.c
diff options
context:
space:
mode:
Diffstat (limited to 'freebsd/sys/dev/usb/usb_hub.c')
-rw-r--r--freebsd/sys/dev/usb/usb_hub.c363
1 files changed, 211 insertions, 152 deletions
diff --git a/freebsd/sys/dev/usb/usb_hub.c b/freebsd/sys/dev/usb/usb_hub.c
index c20bde30..c5887797 100644
--- a/freebsd/sys/dev/usb/usb_hub.c
+++ b/freebsd/sys/dev/usb/usb_hub.c
@@ -40,7 +40,6 @@
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/bus.h>
-#include <sys/linker_set.h>
#include <sys/module.h>
#include <rtems/bsd/sys/lock.h>
#include <sys/mutex.h>
@@ -80,9 +79,8 @@
static int uhub_debug = 0;
SYSCTL_NODE(_hw_usb, OID_AUTO, uhub, CTLFLAG_RW, 0, "USB HUB");
-SYSCTL_INT(_hw_usb_uhub, OID_AUTO, debug, CTLFLAG_RW, &uhub_debug, 0,
+SYSCTL_INT(_hw_usb_uhub, OID_AUTO, debug, CTLFLAG_RW | CTLFLAG_TUN, &uhub_debug, 0,
"Debug level");
-
TUNABLE_INT("hw.usb.uhub.debug", &uhub_debug);
#endif
@@ -112,6 +110,7 @@ struct uhub_softc {
#define UHUB_PROTO(sc) ((sc)->sc_udev->ddesc.bDeviceProtocol)
#define UHUB_IS_HIGH_SPEED(sc) (UHUB_PROTO(sc) != UDPROTO_FSHUB)
#define UHUB_IS_SINGLE_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBSTT)
+#define UHUB_IS_MULTI_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBMTT)
#define UHUB_IS_SUPER_SPEED(sc) (UHUB_PROTO(sc) == UDPROTO_SSHUB)
/* prototypes for type checking: */
@@ -243,11 +242,18 @@ uhub_explore_sub(struct uhub_softc *sc, struct usb_port *up)
/* check if device should be re-enumerated */
if (child->flags.usb_mode == USB_MODE_HOST) {
- usbd_enum_lock(child);
+ uint8_t do_unlock;
+
+ do_unlock = usbd_enum_lock(child);
if (child->re_enumerate_wait) {
- err = usbd_set_config_index(child, USB_UNCONFIG_INDEX);
- if (err == 0)
- err = usbd_req_re_enumerate(child, NULL);
+ err = usbd_set_config_index(child,
+ USB_UNCONFIG_INDEX);
+ if (err != 0) {
+ DPRINTF("Unconfigure failed: "
+ "%s: Ignored.\n",
+ usbd_errstr(err));
+ }
+ err = usbd_req_re_enumerate(child, NULL);
if (err == 0)
err = usbd_set_config_index(child, 0);
if (err == 0) {
@@ -257,7 +263,8 @@ uhub_explore_sub(struct uhub_softc *sc, struct usb_port *up)
child->re_enumerate_wait = 0;
err = 0;
}
- usbd_enum_unlock(child);
+ if (do_unlock)
+ usbd_enum_unlock(child);
}
/* check if probe and attach should be done */
@@ -325,6 +332,7 @@ uhub_reattach_port(struct uhub_softc *sc, uint8_t portno)
enum usb_dev_speed speed;
enum usb_hc_mode mode;
usb_error_t err;
+ uint16_t power_mask;
uint8_t timeout;
DPRINTF("reattaching port %d\n", portno);
@@ -367,10 +375,27 @@ repeat:
}
/* check if there is no power on the port and print a warning */
- if (!(sc->sc_st.port_status & UPS_PORT_POWER)) {
+ switch (udev->speed) {
+ case USB_SPEED_HIGH:
+ case USB_SPEED_FULL:
+ case USB_SPEED_LOW:
+ power_mask = UPS_PORT_POWER;
+ break;
+ case USB_SPEED_SUPER:
+ if (udev->parent_hub == NULL)
+ power_mask = UPS_PORT_POWER;
+ else
+ power_mask = UPS_PORT_POWER_SS;
+ break;
+ default:
+ power_mask = 0;
+ break;
+ }
+ if (!(sc->sc_st.port_status & power_mask)) {
DPRINTF("WARNING: strange, connected port %d "
"has no power\n", portno);
}
+
/* check if the device is in Host Mode */
if (!(sc->sc_st.port_status & UPS_PORT_MODE_DEVICE)) {
@@ -394,7 +419,7 @@ repeat:
/* wait for maximum device power up time */
usb_pause_mtx(NULL,
- USB_MS_TO_TICKS(USB_PORT_POWERUP_DELAY));
+ USB_MS_TO_TICKS(usb_port_powerup_delay));
/* reset port, which implies enabling it */
@@ -607,12 +632,15 @@ uhub_suspend_resume_port(struct uhub_softc *sc, uint8_t portno)
}
} else {
switch (UPS_PORT_LINK_STATE_GET(sc->sc_st.port_status)) {
- case UPS_PORT_LS_U0:
- case UPS_PORT_LS_U1:
+ case UPS_PORT_LS_U3:
+ is_suspend = 1;
+ break;
+ case UPS_PORT_LS_SS_INA:
+ usbd_req_warm_reset_port(udev, NULL, portno);
is_suspend = 0;
break;
default:
- is_suspend = 1;
+ is_suspend = 0;
break;
}
}
@@ -629,8 +657,7 @@ uhub_suspend_resume_port(struct uhub_softc *sc, uint8_t portno)
*/
if (is_suspend == 0)
usb_dev_resume_peer(child);
- else if ((child->flags.usb_mode == USB_MODE_DEVICE) ||
- (usb_device_20_compatible(child) == 0))
+ else if (child->flags.usb_mode == USB_MODE_DEVICE)
usb_dev_suspend_peer(child);
}
done:
@@ -688,6 +715,7 @@ uhub_explore(struct usb_device *udev)
usb_error_t err;
uint8_t portno;
uint8_t x;
+ uint8_t do_unlock;
hub = udev->hub;
sc = hub->hubsoftc;
@@ -704,6 +732,13 @@ uhub_explore(struct usb_device *udev)
DPRINTF("Device is suspended!\n");
return (0);
}
+
+ /*
+ * Make sure we don't race against user-space applications
+ * like LibUSB:
+ */
+ do_unlock = usbd_enum_lock(udev);
+
for (x = 0; x != hub->nports; x++) {
up = hub->ports + x;
portno = x + 1;
@@ -765,7 +800,8 @@ uhub_explore(struct usb_device *udev)
break;
}
}
- if (sc->sc_st.port_change & (UPS_C_SUSPEND | UPS_C_PORT_LINK_STATE)) {
+ if (sc->sc_st.port_change & (UPS_C_SUSPEND |
+ UPS_C_PORT_LINK_STATE)) {
err = uhub_suspend_resume_port(sc, portno);
if (err) {
/* most likely the HUB is gone */
@@ -781,6 +817,9 @@ uhub_explore(struct usb_device *udev)
up->restartcnt = 0;
}
+ if (do_unlock)
+ usbd_enum_unlock(udev);
+
/* initial status checked */
sc->sc_flags |= UHUB_FLAG_DID_EXPLORE;
@@ -916,6 +955,16 @@ uhub_attach(device_t dev)
"bus powered HUB. HUB ignored\n");
goto error;
}
+
+ if (UHUB_IS_MULTI_TT(sc)) {
+ err = usbd_set_alt_interface_index(udev, 0, 1);
+ if (err) {
+ device_printf(dev, "MTT could not be enabled\n");
+ goto error;
+ }
+ device_printf(dev, "MTT enabled\n");
+ }
+
/* get HUB descriptor */
DPRINTFN(2, "Getting HUB descriptor\n");
@@ -936,7 +985,7 @@ uhub_attach(device_t dev)
/* get power delay */
pwrdly = ((hubdesc20.bPwrOn2PwrGood * UHD_PWRON_FACTOR) +
- USB_EXTRA_POWER_UP_TIME);
+ usb_extra_power_up_time);
/* get complete HUB descriptor */
if (nports >= 8) {
@@ -981,7 +1030,7 @@ uhub_attach(device_t dev)
/* get power delay */
pwrdly = ((hubdesc30.bPwrOn2PwrGood * UHD_PWRON_FACTOR) +
- USB_EXTRA_POWER_UP_TIME);
+ usb_extra_power_up_time);
/* get complete HUB descriptor */
if (nports >= 8) {
@@ -1010,7 +1059,7 @@ uhub_attach(device_t dev)
/* default number of ports */
nports = 1;
/* default power delay */
- pwrdly = ((10 * UHD_PWRON_FACTOR) + USB_EXTRA_POWER_UP_TIME);
+ pwrdly = ((10 * UHD_PWRON_FACTOR) + usb_extra_power_up_time);
break;
}
if (nports == 0) {
@@ -1025,10 +1074,6 @@ uhub_attach(device_t dev)
}
udev->hub = hub;
-#if USB_HAVE_TT_SUPPORT
- /* init FULL-speed ISOCHRONOUS schedule */
- usbd_fs_isoc_schedule_init_all(hub->fs_isoc_schedule);
-#endif
/* initialize HUB structure */
hub->hubsoftc = sc;
hub->explore = &uhub_explore;
@@ -1332,15 +1377,21 @@ uhub_child_pnpinfo_string(device_t parent, device_t child,
"devclass=0x%02x devsubclass=0x%02x "
"sernum=\"%s\" "
"release=0x%04x "
- "intclass=0x%02x intsubclass=0x%02x",
+ "mode=%s "
+ "intclass=0x%02x intsubclass=0x%02x "
+ "intprotocol=0x%02x " "%s%s",
UGETW(res.udev->ddesc.idVendor),
UGETW(res.udev->ddesc.idProduct),
res.udev->ddesc.bDeviceClass,
res.udev->ddesc.bDeviceSubClass,
usb_get_serial(res.udev),
UGETW(res.udev->ddesc.bcdDevice),
+ (res.udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device",
iface->idesc->bInterfaceClass,
- iface->idesc->bInterfaceSubClass);
+ iface->idesc->bInterfaceSubClass,
+ iface->idesc->bInterfaceProtocol,
+ iface->pnpinfo ? " " : "",
+ iface->pnpinfo ? iface->pnpinfo : "");
} else {
if (buflen) {
buf[0] = '\0';
@@ -1393,7 +1444,7 @@ static uint8_t
usb_intr_find_best_slot(usb_size_t *ptr, uint8_t start,
uint8_t end, uint8_t mask)
{
- usb_size_t min = 0 - 1;
+ usb_size_t min = (usb_size_t)-1;
usb_size_t sum;
uint8_t x;
uint8_t y;
@@ -1618,42 +1669,6 @@ usb_hs_bandwidth_free(struct usb_xfer *xfer)
}
/*------------------------------------------------------------------------*
- * usbd_fs_isoc_schedule_init_sub
- *
- * This function initialises an USB FULL speed isochronous schedule
- * entry.
- *------------------------------------------------------------------------*/
-#if USB_HAVE_TT_SUPPORT
-static void
-usbd_fs_isoc_schedule_init_sub(struct usb_fs_isoc_schedule *fss)
-{
- fss->total_bytes = (USB_FS_ISOC_UFRAME_MAX *
- USB_FS_BYTES_PER_HS_UFRAME);
- fss->frame_bytes = (USB_FS_BYTES_PER_HS_UFRAME);
- fss->frame_slot = 0;
-}
-#endif
-
-/*------------------------------------------------------------------------*
- * usbd_fs_isoc_schedule_init_all
- *
- * This function will reset the complete USB FULL speed isochronous
- * bandwidth schedule.
- *------------------------------------------------------------------------*/
-#if USB_HAVE_TT_SUPPORT
-void
-usbd_fs_isoc_schedule_init_all(struct usb_fs_isoc_schedule *fss)
-{
- struct usb_fs_isoc_schedule *fss_end = fss + USB_ISOC_TIME_MAX;
-
- while (fss != fss_end) {
- usbd_fs_isoc_schedule_init_sub(fss);
- fss++;
- }
-}
-#endif
-
-/*------------------------------------------------------------------------*
* usb_isoc_time_expand
*
* This function will expand the time counter from 7-bit to 16-bit.
@@ -1685,114 +1700,130 @@ usb_isoc_time_expand(struct usb_bus *bus, uint16_t isoc_time_curr)
}
/*------------------------------------------------------------------------*
- * usbd_fs_isoc_schedule_isoc_time_expand
+ * usbd_fs_isoc_schedule_alloc_slot
*
- * This function does multiple things. First of all it will expand the
- * passed isochronous time, which is the return value. Then it will
- * store where the current FULL speed isochronous schedule is
- * positioned in time and where the end is. See "pp_start" and
- * "pp_end" arguments.
+ * This function will allocate bandwidth for an isochronous FULL speed
+ * transaction in the FULL speed schedule.
*
* Returns:
- * Expanded version of "isoc_time".
- *
- * NOTE: This function depends on being called regularly with
- * intervals less than "USB_ISOC_TIME_MAX".
+ * <8: Success
+ * Else: Error
*------------------------------------------------------------------------*/
#if USB_HAVE_TT_SUPPORT
-uint16_t
-usbd_fs_isoc_schedule_isoc_time_expand(struct usb_device *udev,
- struct usb_fs_isoc_schedule **pp_start,
- struct usb_fs_isoc_schedule **pp_end,
- uint16_t isoc_time)
+uint8_t
+usbd_fs_isoc_schedule_alloc_slot(struct usb_xfer *isoc_xfer, uint16_t isoc_time)
{
- struct usb_fs_isoc_schedule *fss_end;
- struct usb_fs_isoc_schedule *fss_a;
- struct usb_fs_isoc_schedule *fss_b;
- struct usb_hub *hs_hub;
+ struct usb_xfer *xfer;
+ struct usb_xfer *pipe_xfer;
+ struct usb_bus *bus;
+ usb_frlength_t len;
+ usb_frlength_t data_len;
+ uint16_t delta;
+ uint16_t slot;
+ uint8_t retval;
- isoc_time = usb_isoc_time_expand(udev->bus, isoc_time);
+ data_len = 0;
+ slot = 0;
- hs_hub = udev->parent_hs_hub->hub;
+ bus = isoc_xfer->xroot->bus;
- if (hs_hub != NULL) {
+ TAILQ_FOREACH(xfer, &bus->intr_q.head, wait_entry) {
- fss_a = hs_hub->fs_isoc_schedule +
- (hs_hub->isoc_last_time % USB_ISOC_TIME_MAX);
+ /* skip self, if any */
- hs_hub->isoc_last_time = isoc_time;
+ if (xfer == isoc_xfer)
+ continue;
+
+ /* check if this USB transfer is going through the same TT */
+
+ if (xfer->xroot->udev->parent_hs_hub !=
+ isoc_xfer->xroot->udev->parent_hs_hub) {
+ continue;
+ }
+ if ((isoc_xfer->xroot->udev->parent_hs_hub->
+ ddesc.bDeviceProtocol == UDPROTO_HSHUBMTT) &&
+ (xfer->xroot->udev->hs_port_no !=
+ isoc_xfer->xroot->udev->hs_port_no)) {
+ continue;
+ }
+ if (xfer->endpoint->methods != isoc_xfer->endpoint->methods)
+ continue;
- fss_b = hs_hub->fs_isoc_schedule +
- (isoc_time % USB_ISOC_TIME_MAX);
+ /* check if isoc_time is part of this transfer */
- fss_end = hs_hub->fs_isoc_schedule + USB_ISOC_TIME_MAX;
+ delta = xfer->isoc_time_complete - isoc_time;
+ if (delta > 0 && delta <= xfer->nframes) {
+ delta = xfer->nframes - delta;
- *pp_start = hs_hub->fs_isoc_schedule;
- *pp_end = fss_end;
+ len = xfer->frlengths[delta];
+ len += 8;
+ len *= 7;
+ len /= 6;
- while (fss_a != fss_b) {
- if (fss_a == fss_end) {
- fss_a = hs_hub->fs_isoc_schedule;
+ data_len += len;
+ }
+
+ /* check double buffered transfers */
+
+ TAILQ_FOREACH(pipe_xfer, &xfer->endpoint->endpoint_q.head,
+ wait_entry) {
+
+ /* skip self, if any */
+
+ if (pipe_xfer == isoc_xfer)
continue;
+
+ /* check if isoc_time is part of this transfer */
+
+ delta = pipe_xfer->isoc_time_complete - isoc_time;
+ if (delta > 0 && delta <= pipe_xfer->nframes) {
+ delta = pipe_xfer->nframes - delta;
+
+ len = pipe_xfer->frlengths[delta];
+ len += 8;
+ len *= 7;
+ len /= 6;
+
+ data_len += len;
}
- usbd_fs_isoc_schedule_init_sub(fss_a);
- fss_a++;
}
+ }
- } else {
-
- *pp_start = NULL;
- *pp_end = NULL;
+ while (data_len >= USB_FS_BYTES_PER_HS_UFRAME) {
+ data_len -= USB_FS_BYTES_PER_HS_UFRAME;
+ slot++;
}
- return (isoc_time);
-}
-#endif
-/*------------------------------------------------------------------------*
- * usbd_fs_isoc_schedule_alloc
- *
- * This function will allocate bandwidth for an isochronous FULL speed
- * transaction in the FULL speed schedule. The microframe slot where
- * the transaction should be started is stored in the byte pointed to
- * by "pstart". The "len" argument specifies the length of the
- * transaction in bytes.
- *
- * Returns:
- * 0: Success
- * Else: Error
- *------------------------------------------------------------------------*/
-#if USB_HAVE_TT_SUPPORT
-uint8_t
-usbd_fs_isoc_schedule_alloc(struct usb_fs_isoc_schedule *fss,
- uint8_t *pstart, uint16_t len)
-{
- uint8_t slot = fss->frame_slot;
+ /* check for overflow */
+
+ if (slot >= USB_FS_ISOC_UFRAME_MAX)
+ return (255);
- /* Compute overhead and bit-stuffing */
+ retval = slot;
- len += 8;
+ delta = isoc_xfer->isoc_time_complete - isoc_time;
+ if (delta > 0 && delta <= isoc_xfer->nframes) {
+ delta = isoc_xfer->nframes - delta;
- len *= 7;
- len /= 6;
+ len = isoc_xfer->frlengths[delta];
+ len += 8;
+ len *= 7;
+ len /= 6;
- if (len > fss->total_bytes) {
- *pstart = 0; /* set some dummy value */
- return (1); /* error */
+ data_len += len;
}
- if (len > 0) {
- fss->total_bytes -= len;
+ while (data_len >= USB_FS_BYTES_PER_HS_UFRAME) {
+ data_len -= USB_FS_BYTES_PER_HS_UFRAME;
+ slot++;
+ }
- while (len >= fss->frame_bytes) {
- len -= fss->frame_bytes;
- fss->frame_bytes = USB_FS_BYTES_PER_HS_UFRAME;
- fss->frame_slot++;
- }
+ /* check for overflow */
- fss->frame_bytes -= len;
- }
- *pstart = slot;
- return (0); /* success */
+ if (slot >= USB_FS_ISOC_UFRAME_MAX)
+ return (255);
+
+ return (retval);
}
#endif
@@ -2046,7 +2077,6 @@ usb_peer_should_wakeup(struct usb_device *udev)
(udev->pwr_save.write_refs != 0) ||
((udev->pwr_save.read_refs != 0) &&
(udev->flags.usb_mode == USB_MODE_HOST) &&
- (usb_device_20_compatible(udev) != 0) &&
(usb_peer_can_wakeup(udev) == 0)));
}
@@ -2112,7 +2142,7 @@ usb_bus_powerd(struct usb_bus *bus)
/* reset counters */
- mintime = 0 - 1;
+ mintime = (usb_ticks_t)-1;
type_refs[0] = 0;
type_refs[1] = 0;
type_refs[2] = 0;
@@ -2149,7 +2179,7 @@ usb_bus_powerd(struct usb_bus *bus)
}
}
- if (mintime >= (1 * hz)) {
+ if (mintime >= (usb_ticks_t)(1 * hz)) {
/* recompute power masks */
DPRINTF("Recomputing power masks\n");
bus->hw_power_state = 0;
@@ -2226,10 +2256,18 @@ usb_dev_resume_peer(struct usb_device *udev)
DPRINTFN(0, "Resuming port failed\n");
return;
}
+ } else {
+ /* resume current port (Valid in Host and Device Mode) */
+ err = usbd_req_set_port_link_state(udev->parent_hub,
+ NULL, udev->port_no, UPS_PORT_LS_U0);
+ if (err) {
+ DPRINTFN(0, "Resuming port failed\n");
+ return;
+ }
}
/* resume settle time */
- usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_PORT_RESUME_DELAY));
+ usb_pause_mtx(NULL, USB_MS_TO_TICKS(usb_port_resume_delay));
if (bus->methods->device_resume != NULL) {
/* resume USB device on the USB controller */
@@ -2267,8 +2305,7 @@ usb_dev_resume_peer(struct usb_device *udev)
usbd_sr_unlock(udev);
/* check if peer has wakeup capability */
- if (usb_peer_can_wakeup(udev) &&
- usb_device_20_compatible(udev)) {
+ if (usb_peer_can_wakeup(udev)) {
/* clear remote wakeup */
err = usbd_req_clear_device_feature(udev,
NULL, UF_DEVICE_REMOTE_WAKEUP);
@@ -2329,8 +2366,7 @@ repeat:
}
}
- if (usb_peer_can_wakeup(udev) &&
- usb_device_20_compatible(udev)) {
+ if (usb_peer_can_wakeup(udev)) {
/*
* This request needs to be done before we set
* "udev->flags.self_suspended":
@@ -2362,8 +2398,7 @@ repeat:
USB_BUS_UNLOCK(udev->bus);
if (err != 0) {
- if (usb_peer_can_wakeup(udev) &&
- usb_device_20_compatible(udev)) {
+ if (usb_peer_can_wakeup(udev)) {
/* allow device to do remote wakeup */
err = usbd_req_clear_device_feature(udev,
NULL, UF_DEVICE_REMOTE_WAKEUP);
@@ -2385,7 +2420,7 @@ repeat:
NULL, udev->port_no, UHF_PORT_SUSPEND);
/* resume settle time */
- usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_PORT_RESUME_DELAY));
+ usb_pause_mtx(NULL, USB_MS_TO_TICKS(usb_port_resume_delay));
}
DPRINTF("Suspend was cancelled!\n");
return;
@@ -2419,6 +2454,14 @@ repeat:
DPRINTFN(0, "Suspending port failed\n");
return;
}
+ } else {
+ /* suspend current port */
+ err = usbd_req_set_port_link_state(udev->parent_hub,
+ NULL, udev->port_no, UPS_PORT_LS_U3);
+ if (err) {
+ DPRINTFN(0, "Suspending port failed\n");
+ return;
+ }
}
udev = udev->parent_hub;
@@ -2472,3 +2515,19 @@ usbd_filter_power_mode(struct usb_device *udev, uint8_t power_mode)
/* use fixed power mode given by hardware driver */
return (temp);
}
+
+/*------------------------------------------------------------------------*
+ * usbd_start_re_enumerate
+ *
+ * This function starts re-enumeration of the given USB device. This
+ * function does not need to be called BUS-locked. This function does
+ * not wait until the re-enumeration is completed.
+ *------------------------------------------------------------------------*/
+void
+usbd_start_re_enumerate(struct usb_device *udev)
+{
+ if (udev->re_enumerate_wait == 0) {
+ udev->re_enumerate_wait = 1;
+ usb_needs_explore(udev->bus, 0);
+ }
+}