xref: /dragonfly/sys/bus/u4b/usb_hub.c (revision 2b3f93ea)
145f67c02SMarkus Pfeiffer /* $FreeBSD: head/sys/dev/usb/usb_hub.c 276701 2015-01-05 15:04:17Z hselasky $ */
212bd3c8bSSascha Wildner /*-
312bd3c8bSSascha Wildner  * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
412bd3c8bSSascha Wildner  * Copyright (c) 1998 Lennart Augustsson. All rights reserved.
512bd3c8bSSascha Wildner  * Copyright (c) 2008-2010 Hans Petter Selasky. All rights reserved.
612bd3c8bSSascha Wildner  *
712bd3c8bSSascha Wildner  * Redistribution and use in source and binary forms, with or without
812bd3c8bSSascha Wildner  * modification, are permitted provided that the following conditions
912bd3c8bSSascha Wildner  * are met:
1012bd3c8bSSascha Wildner  * 1. Redistributions of source code must retain the above copyright
1112bd3c8bSSascha Wildner  *    notice, this list of conditions and the following disclaimer.
1212bd3c8bSSascha Wildner  * 2. Redistributions in binary form must reproduce the above copyright
1312bd3c8bSSascha Wildner  *    notice, this list of conditions and the following disclaimer in the
1412bd3c8bSSascha Wildner  *    documentation and/or other materials provided with the distribution.
1512bd3c8bSSascha Wildner  *
1612bd3c8bSSascha Wildner  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1712bd3c8bSSascha Wildner  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1812bd3c8bSSascha Wildner  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1912bd3c8bSSascha Wildner  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2012bd3c8bSSascha Wildner  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2112bd3c8bSSascha Wildner  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2212bd3c8bSSascha Wildner  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2312bd3c8bSSascha Wildner  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2412bd3c8bSSascha Wildner  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2512bd3c8bSSascha Wildner  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2612bd3c8bSSascha Wildner  * SUCH DAMAGE.
2712bd3c8bSSascha Wildner  */
2812bd3c8bSSascha Wildner 
2912bd3c8bSSascha Wildner /*
3012bd3c8bSSascha Wildner  * USB spec: http://www.usb.org/developers/docs/usbspec.zip
3112bd3c8bSSascha Wildner  */
3212bd3c8bSSascha Wildner 
3312bd3c8bSSascha Wildner #include <sys/stdint.h>
3412bd3c8bSSascha Wildner #include <sys/param.h>
3512bd3c8bSSascha Wildner #include <sys/queue.h>
3612bd3c8bSSascha Wildner #include <sys/types.h>
3712bd3c8bSSascha Wildner #include <sys/systm.h>
3812bd3c8bSSascha Wildner #include <sys/kernel.h>
3912bd3c8bSSascha Wildner #include <sys/bus.h>
4012bd3c8bSSascha Wildner #include <sys/module.h>
4112bd3c8bSSascha Wildner #include <sys/lock.h>
4212bd3c8bSSascha Wildner #include <sys/condvar.h>
4312bd3c8bSSascha Wildner #include <sys/sysctl.h>
4412bd3c8bSSascha Wildner #include <sys/unistd.h>
4512bd3c8bSSascha Wildner #include <sys/callout.h>
4612bd3c8bSSascha Wildner #include <sys/malloc.h>
47*2b3f93eaSMatthew Dillon #include <sys/caps.h>
4812bd3c8bSSascha Wildner 
49722d05c3SSascha Wildner #include <bus/u4b/usb.h>
50722d05c3SSascha Wildner #include <bus/u4b/usb_ioctl.h>
51722d05c3SSascha Wildner #include <bus/u4b/usbdi.h>
52722d05c3SSascha Wildner #include <bus/u4b/usbdi_util.h>
5312bd3c8bSSascha Wildner 
5412bd3c8bSSascha Wildner #define	USB_DEBUG_VAR uhub_debug
5512bd3c8bSSascha Wildner 
56722d05c3SSascha Wildner #include <bus/u4b/usb_core.h>
57722d05c3SSascha Wildner #include <bus/u4b/usb_process.h>
58722d05c3SSascha Wildner #include <bus/u4b/usb_device.h>
59722d05c3SSascha Wildner #include <bus/u4b/usb_request.h>
60722d05c3SSascha Wildner #include <bus/u4b/usb_debug.h>
61722d05c3SSascha Wildner #include <bus/u4b/usb_hub.h>
62722d05c3SSascha Wildner #include <bus/u4b/usb_util.h>
63722d05c3SSascha Wildner #include <bus/u4b/usb_busdma.h>
64722d05c3SSascha Wildner #include <bus/u4b/usb_transfer.h>
65722d05c3SSascha Wildner #include <bus/u4b/usb_dynamic.h>
6612bd3c8bSSascha Wildner 
67722d05c3SSascha Wildner #include <bus/u4b/usb_controller.h>
68722d05c3SSascha Wildner #include <bus/u4b/usb_bus.h>
6912bd3c8bSSascha Wildner 
7012bd3c8bSSascha Wildner #define	UHUB_INTR_INTERVAL 250		/* ms */
7145f67c02SMarkus Pfeiffer enum {
7245f67c02SMarkus Pfeiffer 	UHUB_INTR_TRANSFER,
7345f67c02SMarkus Pfeiffer #if USB_HAVE_TT_SUPPORT
7445f67c02SMarkus Pfeiffer 	UHUB_RESET_TT_TRANSFER,
7545f67c02SMarkus Pfeiffer #endif
7645f67c02SMarkus Pfeiffer 	UHUB_N_TRANSFER,
7745f67c02SMarkus Pfeiffer };
7812bd3c8bSSascha Wildner 
7912bd3c8bSSascha Wildner #ifdef USB_DEBUG
8012bd3c8bSSascha Wildner static int uhub_debug = 0;
8112bd3c8bSSascha Wildner 
8212bd3c8bSSascha Wildner static SYSCTL_NODE(_hw_usb, OID_AUTO, uhub, CTLFLAG_RW, 0, "USB HUB");
8312bd3c8bSSascha Wildner SYSCTL_INT(_hw_usb_uhub, OID_AUTO, debug, CTLFLAG_RW, &uhub_debug, 0,
8412bd3c8bSSascha Wildner     "Debug level");
8512bd3c8bSSascha Wildner 
8612bd3c8bSSascha Wildner TUNABLE_INT("hw.usb.uhub.debug", &uhub_debug);
8712bd3c8bSSascha Wildner #endif
8812bd3c8bSSascha Wildner 
8912bd3c8bSSascha Wildner #if USB_HAVE_POWERD
9012bd3c8bSSascha Wildner static int usb_power_timeout = 30;	/* seconds */
9112bd3c8bSSascha Wildner 
9212bd3c8bSSascha Wildner SYSCTL_INT(_hw_usb, OID_AUTO, power_timeout, CTLFLAG_RW,
9312bd3c8bSSascha Wildner     &usb_power_timeout, 0, "USB power timeout");
94dd681da6SMatthew Dillon TUNABLE_INT("hw.usb.power_timeout", &usb_power_timeout);
9512bd3c8bSSascha Wildner #endif
9612bd3c8bSSascha Wildner 
9745f67c02SMarkus Pfeiffer #if USB_HAVE_DISABLE_ENUM
9845f67c02SMarkus Pfeiffer static int usb_disable_enumeration = 0;
995ea2ce29SSascha Wildner SYSCTL_INT(_hw_usb, OID_AUTO, disable_enumeration, CTLFLAG_RW,
10045f67c02SMarkus Pfeiffer     &usb_disable_enumeration, 0, "Set to disable all USB device enumeration.");
10160e94610SSascha Wildner TUNABLE_INT("hw.usb.disable_enumeration", &usb_disable_enumeration);
10245f67c02SMarkus Pfeiffer 
10345f67c02SMarkus Pfeiffer static int usb_disable_port_power = 0;
1045ea2ce29SSascha Wildner SYSCTL_INT(_hw_usb, OID_AUTO, disable_port_power, CTLFLAG_RW,
10545f67c02SMarkus Pfeiffer     &usb_disable_port_power, 0, "Set to disable all USB port power.");
10660e94610SSascha Wildner TUNABLE_INT("hw.usb.disable_port_power", &usb_disable_port_power);
10745f67c02SMarkus Pfeiffer #endif
10845f67c02SMarkus Pfeiffer 
10912bd3c8bSSascha Wildner struct uhub_current_state {
11012bd3c8bSSascha Wildner 	uint16_t port_change;
11112bd3c8bSSascha Wildner 	uint16_t port_status;
11212bd3c8bSSascha Wildner };
11312bd3c8bSSascha Wildner 
11412bd3c8bSSascha Wildner struct uhub_softc {
11512bd3c8bSSascha Wildner 	struct uhub_current_state sc_st;/* current state */
11657bed822SMarkus Pfeiffer #if (USB_HAVE_FIXED_PORT != 0)
11757bed822SMarkus Pfeiffer 	struct usb_hub sc_hub;
11857bed822SMarkus Pfeiffer #endif
11912bd3c8bSSascha Wildner 	device_t sc_dev;		/* base device */
120722d05c3SSascha Wildner 	struct lock sc_lock;		/* our mutex */
12112bd3c8bSSascha Wildner 	struct usb_device *sc_udev;	/* USB device */
12212bd3c8bSSascha Wildner 	struct usb_xfer *sc_xfer[UHUB_N_TRANSFER];	/* interrupt xfer */
12345f67c02SMarkus Pfeiffer #if USB_HAVE_DISABLE_ENUM
12445f67c02SMarkus Pfeiffer 	int sc_disable_enumeration;
12545f67c02SMarkus Pfeiffer 	int sc_disable_port_power;
12645f67c02SMarkus Pfeiffer #endif
12712bd3c8bSSascha Wildner 	uint8_t	sc_flags;
12812bd3c8bSSascha Wildner #define	UHUB_FLAG_DID_EXPLORE 0x01
12912bd3c8bSSascha Wildner };
13012bd3c8bSSascha Wildner 
13112bd3c8bSSascha Wildner #define	UHUB_PROTO(sc) ((sc)->sc_udev->ddesc.bDeviceProtocol)
13212bd3c8bSSascha Wildner #define	UHUB_IS_HIGH_SPEED(sc) (UHUB_PROTO(sc) != UDPROTO_FSHUB)
13312bd3c8bSSascha Wildner #define	UHUB_IS_SINGLE_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBSTT)
1345e41ab93SMarkus Pfeiffer #define	UHUB_IS_MULTI_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBMTT)
13512bd3c8bSSascha Wildner #define	UHUB_IS_SUPER_SPEED(sc) (UHUB_PROTO(sc) == UDPROTO_SSHUB)
13612bd3c8bSSascha Wildner 
13712bd3c8bSSascha Wildner /* prototypes for type checking: */
13812bd3c8bSSascha Wildner 
13912bd3c8bSSascha Wildner static device_probe_t uhub_probe;
14012bd3c8bSSascha Wildner static device_attach_t uhub_attach;
14112bd3c8bSSascha Wildner static device_detach_t uhub_detach;
14212bd3c8bSSascha Wildner static device_suspend_t uhub_suspend;
14312bd3c8bSSascha Wildner static device_resume_t uhub_resume;
14412bd3c8bSSascha Wildner 
14512bd3c8bSSascha Wildner static bus_driver_added_t uhub_driver_added;
14612bd3c8bSSascha Wildner static bus_child_location_str_t uhub_child_location_string;
14712bd3c8bSSascha Wildner static bus_child_pnpinfo_str_t uhub_child_pnpinfo_string;
14812bd3c8bSSascha Wildner 
14912bd3c8bSSascha Wildner static usb_callback_t uhub_intr_callback;
15045f67c02SMarkus Pfeiffer #if USB_HAVE_TT_SUPPORT
15145f67c02SMarkus Pfeiffer static usb_callback_t uhub_reset_tt_callback;
15245f67c02SMarkus Pfeiffer #endif
15312bd3c8bSSascha Wildner 
15412bd3c8bSSascha Wildner static void usb_dev_resume_peer(struct usb_device *udev);
15512bd3c8bSSascha Wildner static void usb_dev_suspend_peer(struct usb_device *udev);
15612bd3c8bSSascha Wildner static uint8_t usb_peer_should_wakeup(struct usb_device *udev);
15712bd3c8bSSascha Wildner 
15812bd3c8bSSascha Wildner static const struct usb_config uhub_config[UHUB_N_TRANSFER] = {
15912bd3c8bSSascha Wildner 
16045f67c02SMarkus Pfeiffer 	[UHUB_INTR_TRANSFER] = {
16112bd3c8bSSascha Wildner 		.type = UE_INTERRUPT,
16212bd3c8bSSascha Wildner 		.endpoint = UE_ADDR_ANY,
16312bd3c8bSSascha Wildner 		.direction = UE_DIR_ANY,
16412bd3c8bSSascha Wildner 		.timeout = 0,
16512bd3c8bSSascha Wildner 		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
16612bd3c8bSSascha Wildner 		.bufsize = 0,	/* use wMaxPacketSize */
16712bd3c8bSSascha Wildner 		.callback = &uhub_intr_callback,
16812bd3c8bSSascha Wildner 		.interval = UHUB_INTR_INTERVAL,
16912bd3c8bSSascha Wildner 	},
17045f67c02SMarkus Pfeiffer #if USB_HAVE_TT_SUPPORT
17145f67c02SMarkus Pfeiffer 	[UHUB_RESET_TT_TRANSFER] = {
17245f67c02SMarkus Pfeiffer 		.type = UE_CONTROL,
17345f67c02SMarkus Pfeiffer 		.endpoint = 0x00,	/* Control pipe */
17445f67c02SMarkus Pfeiffer 		.direction = UE_DIR_ANY,
17545f67c02SMarkus Pfeiffer 		.bufsize = sizeof(struct usb_device_request),
17645f67c02SMarkus Pfeiffer 		.callback = &uhub_reset_tt_callback,
17745f67c02SMarkus Pfeiffer 		.timeout = 1000,	/* 1 second */
17845f67c02SMarkus Pfeiffer 		.usb_mode = USB_MODE_HOST,
17945f67c02SMarkus Pfeiffer 	},
18045f67c02SMarkus Pfeiffer #endif
18112bd3c8bSSascha Wildner };
18212bd3c8bSSascha Wildner 
18312bd3c8bSSascha Wildner /*
18412bd3c8bSSascha Wildner  * driver instance for "hub" connected to "usb"
18512bd3c8bSSascha Wildner  * and "hub" connected to "hub"
18612bd3c8bSSascha Wildner  */
18712bd3c8bSSascha Wildner static devclass_t uhub_devclass;
18812bd3c8bSSascha Wildner 
18912bd3c8bSSascha Wildner static device_method_t uhub_methods[] = {
19012bd3c8bSSascha Wildner 	DEVMETHOD(device_probe, uhub_probe),
19112bd3c8bSSascha Wildner 	DEVMETHOD(device_attach, uhub_attach),
19212bd3c8bSSascha Wildner 	DEVMETHOD(device_detach, uhub_detach),
19312bd3c8bSSascha Wildner 
19412bd3c8bSSascha Wildner 	DEVMETHOD(device_suspend, uhub_suspend),
19512bd3c8bSSascha Wildner 	DEVMETHOD(device_resume, uhub_resume),
19612bd3c8bSSascha Wildner 
19712bd3c8bSSascha Wildner 	DEVMETHOD(bus_child_location_str, uhub_child_location_string),
19812bd3c8bSSascha Wildner 	DEVMETHOD(bus_child_pnpinfo_str, uhub_child_pnpinfo_string),
19912bd3c8bSSascha Wildner 	DEVMETHOD(bus_driver_added, uhub_driver_added),
200d3c9c58eSSascha Wildner 	DEVMETHOD_END
20112bd3c8bSSascha Wildner };
20212bd3c8bSSascha Wildner 
20312bd3c8bSSascha Wildner static driver_t uhub_driver = {
20412bd3c8bSSascha Wildner 	.name = "uhub",
20512bd3c8bSSascha Wildner 	.methods = uhub_methods,
20612bd3c8bSSascha Wildner 	.size = sizeof(struct uhub_softc)
20712bd3c8bSSascha Wildner };
20812bd3c8bSSascha Wildner 
2093a25be87SSascha Wildner DRIVER_MODULE(uhub, usbus, uhub_driver, uhub_devclass, NULL, NULL);
2103a25be87SSascha Wildner DRIVER_MODULE(uhub, uhub, uhub_driver, uhub_devclass, NULL, NULL);
21112bd3c8bSSascha Wildner MODULE_VERSION(uhub, 1);
21212bd3c8bSSascha Wildner 
21312bd3c8bSSascha Wildner static void
uhub_intr_callback(struct usb_xfer * xfer,usb_error_t error)21412bd3c8bSSascha Wildner uhub_intr_callback(struct usb_xfer *xfer, usb_error_t error)
21512bd3c8bSSascha Wildner {
21612bd3c8bSSascha Wildner 	struct uhub_softc *sc = usbd_xfer_softc(xfer);
21712bd3c8bSSascha Wildner 
21812bd3c8bSSascha Wildner 	switch (USB_GET_STATE(xfer)) {
21912bd3c8bSSascha Wildner 	case USB_ST_TRANSFERRED:
22012bd3c8bSSascha Wildner 		DPRINTFN(2, "\n");
22112bd3c8bSSascha Wildner 		/*
22212bd3c8bSSascha Wildner 		 * This is an indication that some port
22312bd3c8bSSascha Wildner 		 * has changed status. Notify the bus
22412bd3c8bSSascha Wildner 		 * event handler thread that we need
22512bd3c8bSSascha Wildner 		 * to be explored again:
22612bd3c8bSSascha Wildner 		 */
22712bd3c8bSSascha Wildner 		usb_needs_explore(sc->sc_udev->bus, 0);
22812bd3c8bSSascha Wildner 
22912bd3c8bSSascha Wildner 	case USB_ST_SETUP:
23012bd3c8bSSascha Wildner 		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
23112bd3c8bSSascha Wildner 		usbd_transfer_submit(xfer);
23212bd3c8bSSascha Wildner 		break;
23312bd3c8bSSascha Wildner 
23412bd3c8bSSascha Wildner 	default:			/* Error */
23512bd3c8bSSascha Wildner 		if (xfer->error != USB_ERR_CANCELLED) {
23612bd3c8bSSascha Wildner 			/*
23712bd3c8bSSascha Wildner 			 * Do a clear-stall. The "stall_pipe" flag
23812bd3c8bSSascha Wildner 			 * will get cleared before next callback by
23912bd3c8bSSascha Wildner 			 * the USB stack.
24012bd3c8bSSascha Wildner 			 */
24112bd3c8bSSascha Wildner 			usbd_xfer_set_stall(xfer);
24212bd3c8bSSascha Wildner 			usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
24312bd3c8bSSascha Wildner 			usbd_transfer_submit(xfer);
24412bd3c8bSSascha Wildner 		}
24512bd3c8bSSascha Wildner 		break;
24612bd3c8bSSascha Wildner 	}
24712bd3c8bSSascha Wildner }
24812bd3c8bSSascha Wildner 
24912bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
25045f67c02SMarkus Pfeiffer  *      uhub_reset_tt_proc
25145f67c02SMarkus Pfeiffer  *
25245f67c02SMarkus Pfeiffer  * This function starts the TT reset USB request
25345f67c02SMarkus Pfeiffer  *------------------------------------------------------------------------*/
25445f67c02SMarkus Pfeiffer #if USB_HAVE_TT_SUPPORT
25545f67c02SMarkus Pfeiffer static void
uhub_reset_tt_proc(struct usb_proc_msg * _pm)25645f67c02SMarkus Pfeiffer uhub_reset_tt_proc(struct usb_proc_msg *_pm)
25745f67c02SMarkus Pfeiffer {
25845f67c02SMarkus Pfeiffer 	struct usb_udev_msg *pm = (void *)_pm;
25945f67c02SMarkus Pfeiffer 	struct usb_device *udev = pm->udev;
26045f67c02SMarkus Pfeiffer 	struct usb_hub *hub;
26145f67c02SMarkus Pfeiffer 	struct uhub_softc *sc;
26245f67c02SMarkus Pfeiffer 
26345f67c02SMarkus Pfeiffer 	hub = udev->hub;
26445f67c02SMarkus Pfeiffer 	if (hub == NULL)
26545f67c02SMarkus Pfeiffer 		return;
26645f67c02SMarkus Pfeiffer 	sc = hub->hubsoftc;
26745f67c02SMarkus Pfeiffer 	if (sc == NULL)
26845f67c02SMarkus Pfeiffer 		return;
26945f67c02SMarkus Pfeiffer 
27045f67c02SMarkus Pfeiffer 	/* Change lock */
27145f67c02SMarkus Pfeiffer 	USB_BUS_UNLOCK(udev->bus);
27245f67c02SMarkus Pfeiffer 	lockmgr(&sc->sc_lock, LK_EXCLUSIVE);
27345f67c02SMarkus Pfeiffer 	/* Start transfer */
27445f67c02SMarkus Pfeiffer 	usbd_transfer_start(sc->sc_xfer[UHUB_RESET_TT_TRANSFER]);
27545f67c02SMarkus Pfeiffer 	/* Change lock */
27645f67c02SMarkus Pfeiffer 	lockmgr(&sc->sc_lock, LK_RELEASE);
27745f67c02SMarkus Pfeiffer 	USB_BUS_LOCK(udev->bus);
27845f67c02SMarkus Pfeiffer }
27945f67c02SMarkus Pfeiffer #endif
28045f67c02SMarkus Pfeiffer 
28145f67c02SMarkus Pfeiffer /*------------------------------------------------------------------------*
28245f67c02SMarkus Pfeiffer  *      uhub_tt_buffer_reset_async_locked
28345f67c02SMarkus Pfeiffer  *
28445f67c02SMarkus Pfeiffer  * This function queues a TT reset for the given USB device and endpoint.
28545f67c02SMarkus Pfeiffer  *------------------------------------------------------------------------*/
28645f67c02SMarkus Pfeiffer #if USB_HAVE_TT_SUPPORT
28745f67c02SMarkus Pfeiffer void
uhub_tt_buffer_reset_async_locked(struct usb_device * child,struct usb_endpoint * ep)28845f67c02SMarkus Pfeiffer uhub_tt_buffer_reset_async_locked(struct usb_device *child, struct usb_endpoint *ep)
28945f67c02SMarkus Pfeiffer {
29045f67c02SMarkus Pfeiffer 	struct usb_device_request req;
29145f67c02SMarkus Pfeiffer 	struct usb_device *udev;
29245f67c02SMarkus Pfeiffer 	struct usb_hub *hub;
29345f67c02SMarkus Pfeiffer 	struct usb_port *up;
29445f67c02SMarkus Pfeiffer 	uint16_t wValue;
29545f67c02SMarkus Pfeiffer 	uint8_t port;
29645f67c02SMarkus Pfeiffer 
29745f67c02SMarkus Pfeiffer 	if (child == NULL || ep == NULL)
29845f67c02SMarkus Pfeiffer 		return;
29945f67c02SMarkus Pfeiffer 
30045f67c02SMarkus Pfeiffer 	udev = child->parent_hs_hub;
30145f67c02SMarkus Pfeiffer 	port = child->hs_port_no;
30245f67c02SMarkus Pfeiffer 
30345f67c02SMarkus Pfeiffer 	if (udev == NULL)
30445f67c02SMarkus Pfeiffer 		return;
30545f67c02SMarkus Pfeiffer 
30645f67c02SMarkus Pfeiffer 	hub = udev->hub;
30745f67c02SMarkus Pfeiffer 	if ((hub == NULL) ||
30845f67c02SMarkus Pfeiffer 	    (udev->speed != USB_SPEED_HIGH) ||
30945f67c02SMarkus Pfeiffer 	    (child->speed != USB_SPEED_LOW &&
31045f67c02SMarkus Pfeiffer 	     child->speed != USB_SPEED_FULL) ||
31145f67c02SMarkus Pfeiffer 	    (child->flags.usb_mode != USB_MODE_HOST) ||
31245f67c02SMarkus Pfeiffer 	    (port == 0) || (ep->edesc == NULL)) {
31345f67c02SMarkus Pfeiffer 		/* not applicable */
31445f67c02SMarkus Pfeiffer 		return;
31545f67c02SMarkus Pfeiffer 	}
31645f67c02SMarkus Pfeiffer 
31745f67c02SMarkus Pfeiffer 	USB_BUS_LOCK_ASSERT(udev->bus);
31845f67c02SMarkus Pfeiffer 
31945f67c02SMarkus Pfeiffer 	up = hub->ports + port - 1;
32045f67c02SMarkus Pfeiffer 
32145f67c02SMarkus Pfeiffer 	if (udev->ddesc.bDeviceClass == UDCLASS_HUB &&
32245f67c02SMarkus Pfeiffer 	    udev->ddesc.bDeviceProtocol == UDPROTO_HSHUBSTT)
32345f67c02SMarkus Pfeiffer 		port = 1;
32445f67c02SMarkus Pfeiffer 
32545f67c02SMarkus Pfeiffer 	/* if we already received a clear buffer request, reset the whole TT */
32645f67c02SMarkus Pfeiffer 	if (up->req_reset_tt.bRequest != 0) {
32745f67c02SMarkus Pfeiffer 		req.bmRequestType = UT_WRITE_CLASS_OTHER;
32845f67c02SMarkus Pfeiffer 		req.bRequest = UR_RESET_TT;
32945f67c02SMarkus Pfeiffer 		USETW(req.wValue, 0);
33045f67c02SMarkus Pfeiffer 		req.wIndex[0] = port;
33145f67c02SMarkus Pfeiffer 		req.wIndex[1] = 0;
33245f67c02SMarkus Pfeiffer 		USETW(req.wLength, 0);
33345f67c02SMarkus Pfeiffer 	} else {
33445f67c02SMarkus Pfeiffer 		wValue = (ep->edesc->bEndpointAddress & 0xF) |
33545f67c02SMarkus Pfeiffer 		      ((child->address & 0x7F) << 4) |
33645f67c02SMarkus Pfeiffer 		      ((ep->edesc->bEndpointAddress & 0x80) << 8) |
33745f67c02SMarkus Pfeiffer 		      ((ep->edesc->bmAttributes & 3) << 12);
33845f67c02SMarkus Pfeiffer 
33945f67c02SMarkus Pfeiffer 		req.bmRequestType = UT_WRITE_CLASS_OTHER;
34045f67c02SMarkus Pfeiffer 		req.bRequest = UR_CLEAR_TT_BUFFER;
34145f67c02SMarkus Pfeiffer 		USETW(req.wValue, wValue);
34245f67c02SMarkus Pfeiffer 		req.wIndex[0] = port;
34345f67c02SMarkus Pfeiffer 		req.wIndex[1] = 0;
34445f67c02SMarkus Pfeiffer 		USETW(req.wLength, 0);
34545f67c02SMarkus Pfeiffer 	}
34645f67c02SMarkus Pfeiffer 	up->req_reset_tt = req;
34745f67c02SMarkus Pfeiffer 	/* get reset transfer started */
34845f67c02SMarkus Pfeiffer 	usb_proc_msignal(USB_BUS_NON_GIANT_PROC(udev->bus),
34945f67c02SMarkus Pfeiffer 	    &hub->tt_msg[0], &hub->tt_msg[1]);
35045f67c02SMarkus Pfeiffer }
35145f67c02SMarkus Pfeiffer #endif
35245f67c02SMarkus Pfeiffer 
35345f67c02SMarkus Pfeiffer #if USB_HAVE_TT_SUPPORT
35445f67c02SMarkus Pfeiffer static void
uhub_reset_tt_callback(struct usb_xfer * xfer,usb_error_t error)35545f67c02SMarkus Pfeiffer uhub_reset_tt_callback(struct usb_xfer *xfer, usb_error_t error)
35645f67c02SMarkus Pfeiffer {
35745f67c02SMarkus Pfeiffer 	struct uhub_softc *sc;
35845f67c02SMarkus Pfeiffer 	struct usb_device *udev;
35945f67c02SMarkus Pfeiffer 	struct usb_port *up;
36045f67c02SMarkus Pfeiffer 	uint8_t x;
36145f67c02SMarkus Pfeiffer 
36245f67c02SMarkus Pfeiffer 	DPRINTF("TT buffer reset\n");
36345f67c02SMarkus Pfeiffer 
36445f67c02SMarkus Pfeiffer 	sc = usbd_xfer_softc(xfer);
36545f67c02SMarkus Pfeiffer 	udev = sc->sc_udev;
36645f67c02SMarkus Pfeiffer 
36745f67c02SMarkus Pfeiffer 	switch (USB_GET_STATE(xfer)) {
36845f67c02SMarkus Pfeiffer 	case USB_ST_TRANSFERRED:
36945f67c02SMarkus Pfeiffer 	case USB_ST_SETUP:
37045f67c02SMarkus Pfeiffer tr_setup:
37145f67c02SMarkus Pfeiffer 		USB_BUS_LOCK(udev->bus);
37245f67c02SMarkus Pfeiffer 		/* find first port which needs a TT reset */
37345f67c02SMarkus Pfeiffer 		for (x = 0; x != udev->hub->nports; x++) {
37445f67c02SMarkus Pfeiffer 			up = udev->hub->ports + x;
37545f67c02SMarkus Pfeiffer 
37645f67c02SMarkus Pfeiffer 			if (up->req_reset_tt.bRequest == 0)
37745f67c02SMarkus Pfeiffer 				continue;
37845f67c02SMarkus Pfeiffer 
37945f67c02SMarkus Pfeiffer 			/* copy in the transfer */
38045f67c02SMarkus Pfeiffer 			usbd_copy_in(xfer->frbuffers, 0, &up->req_reset_tt,
38145f67c02SMarkus Pfeiffer 			    sizeof(up->req_reset_tt));
38245f67c02SMarkus Pfeiffer 			/* reset buffer */
38345f67c02SMarkus Pfeiffer 			memset(&up->req_reset_tt, 0, sizeof(up->req_reset_tt));
38445f67c02SMarkus Pfeiffer 
38545f67c02SMarkus Pfeiffer 			/* set length */
38645f67c02SMarkus Pfeiffer 			usbd_xfer_set_frame_len(xfer, 0, sizeof(up->req_reset_tt));
38745f67c02SMarkus Pfeiffer 			xfer->nframes = 1;
38845f67c02SMarkus Pfeiffer 			USB_BUS_UNLOCK(udev->bus);
38945f67c02SMarkus Pfeiffer 
39045f67c02SMarkus Pfeiffer 			usbd_transfer_submit(xfer);
39145f67c02SMarkus Pfeiffer 			return;
39245f67c02SMarkus Pfeiffer 		}
39345f67c02SMarkus Pfeiffer 		USB_BUS_UNLOCK(udev->bus);
39445f67c02SMarkus Pfeiffer 		break;
39545f67c02SMarkus Pfeiffer 
39645f67c02SMarkus Pfeiffer 	default:
39745f67c02SMarkus Pfeiffer 		if (error == USB_ERR_CANCELLED)
39845f67c02SMarkus Pfeiffer 			break;
39945f67c02SMarkus Pfeiffer 
40045f67c02SMarkus Pfeiffer 		DPRINTF("TT buffer reset failed (%s)\n", usbd_errstr(error));
40145f67c02SMarkus Pfeiffer 		goto tr_setup;
40245f67c02SMarkus Pfeiffer 	}
40345f67c02SMarkus Pfeiffer }
40445f67c02SMarkus Pfeiffer #endif
40545f67c02SMarkus Pfeiffer 
40645f67c02SMarkus Pfeiffer /*------------------------------------------------------------------------*
40745f67c02SMarkus Pfeiffer  *      uhub_count_active_host_ports
40845f67c02SMarkus Pfeiffer  *
40945f67c02SMarkus Pfeiffer  * This function counts the number of active ports at the given speed.
41045f67c02SMarkus Pfeiffer  *------------------------------------------------------------------------*/
41145f67c02SMarkus Pfeiffer uint8_t
uhub_count_active_host_ports(struct usb_device * udev,enum usb_dev_speed speed)41245f67c02SMarkus Pfeiffer uhub_count_active_host_ports(struct usb_device *udev, enum usb_dev_speed speed)
41345f67c02SMarkus Pfeiffer {
41445f67c02SMarkus Pfeiffer 	struct uhub_softc *sc;
41545f67c02SMarkus Pfeiffer 	struct usb_device *child;
41645f67c02SMarkus Pfeiffer 	struct usb_hub *hub;
41745f67c02SMarkus Pfeiffer 	struct usb_port *up;
41845f67c02SMarkus Pfeiffer 	uint8_t retval = 0;
41945f67c02SMarkus Pfeiffer 	uint8_t x;
42045f67c02SMarkus Pfeiffer 
42145f67c02SMarkus Pfeiffer 	if (udev == NULL)
42245f67c02SMarkus Pfeiffer 		goto done;
42345f67c02SMarkus Pfeiffer 	hub = udev->hub;
42445f67c02SMarkus Pfeiffer 	if (hub == NULL)
42545f67c02SMarkus Pfeiffer 		goto done;
42645f67c02SMarkus Pfeiffer 	sc = hub->hubsoftc;
42745f67c02SMarkus Pfeiffer 	if (sc == NULL)
42845f67c02SMarkus Pfeiffer 		goto done;
42945f67c02SMarkus Pfeiffer 
43045f67c02SMarkus Pfeiffer 	for (x = 0; x != hub->nports; x++) {
43145f67c02SMarkus Pfeiffer 		up = hub->ports + x;
43245f67c02SMarkus Pfeiffer 		child = usb_bus_port_get_device(udev->bus, up);
43345f67c02SMarkus Pfeiffer 		if (child != NULL &&
43445f67c02SMarkus Pfeiffer 		    child->flags.usb_mode == USB_MODE_HOST &&
43545f67c02SMarkus Pfeiffer 		    child->speed == speed)
43645f67c02SMarkus Pfeiffer 			retval++;
43745f67c02SMarkus Pfeiffer 	}
43845f67c02SMarkus Pfeiffer done:
43945f67c02SMarkus Pfeiffer 	return (retval);
44045f67c02SMarkus Pfeiffer }
44145f67c02SMarkus Pfeiffer 
44245f67c02SMarkus Pfeiffer void
uhub_explore_handle_re_enumerate(struct usb_device * child)44345f67c02SMarkus Pfeiffer uhub_explore_handle_re_enumerate(struct usb_device *child)
44445f67c02SMarkus Pfeiffer {
44545f67c02SMarkus Pfeiffer 	uint8_t do_unlock;
44645f67c02SMarkus Pfeiffer 	usb_error_t err;
44745f67c02SMarkus Pfeiffer 
44845f67c02SMarkus Pfeiffer 	/* check if device should be re-enumerated */
44945f67c02SMarkus Pfeiffer 	if (child->flags.usb_mode != USB_MODE_HOST)
45045f67c02SMarkus Pfeiffer 		return;
45145f67c02SMarkus Pfeiffer 
45245f67c02SMarkus Pfeiffer 	do_unlock = usbd_enum_lock(child);
45345f67c02SMarkus Pfeiffer 	switch (child->re_enumerate_wait) {
45445f67c02SMarkus Pfeiffer 	case USB_RE_ENUM_START:
45545f67c02SMarkus Pfeiffer 		err = usbd_set_config_index(child,
45645f67c02SMarkus Pfeiffer 		    USB_UNCONFIG_INDEX);
45745f67c02SMarkus Pfeiffer 		if (err != 0) {
45845f67c02SMarkus Pfeiffer 			DPRINTF("Unconfigure failed: %s: Ignored.\n",
45945f67c02SMarkus Pfeiffer 			    usbd_errstr(err));
46045f67c02SMarkus Pfeiffer 		}
46145f67c02SMarkus Pfeiffer 		if (child->parent_hub == NULL) {
46245f67c02SMarkus Pfeiffer 			/* the root HUB cannot be re-enumerated */
46345f67c02SMarkus Pfeiffer 			DPRINTFN(6, "cannot reset root HUB\n");
46445f67c02SMarkus Pfeiffer 			err = 0;
46545f67c02SMarkus Pfeiffer 		} else {
46645f67c02SMarkus Pfeiffer 			err = usbd_req_re_enumerate(child, NULL);
46745f67c02SMarkus Pfeiffer 		}
46845f67c02SMarkus Pfeiffer 		if (err == 0)
46945f67c02SMarkus Pfeiffer 			err = usbd_set_config_index(child, 0);
47045f67c02SMarkus Pfeiffer 		if (err == 0) {
47145f67c02SMarkus Pfeiffer 			err = usb_probe_and_attach(child,
47245f67c02SMarkus Pfeiffer 			    USB_IFACE_INDEX_ANY);
47345f67c02SMarkus Pfeiffer 		}
47445f67c02SMarkus Pfeiffer 		child->re_enumerate_wait = USB_RE_ENUM_DONE;
47545f67c02SMarkus Pfeiffer 		break;
47645f67c02SMarkus Pfeiffer 
47745f67c02SMarkus Pfeiffer 	case USB_RE_ENUM_PWR_OFF:
47845f67c02SMarkus Pfeiffer 		/* get the device unconfigured */
47945f67c02SMarkus Pfeiffer 		err = usbd_set_config_index(child,
48045f67c02SMarkus Pfeiffer 		    USB_UNCONFIG_INDEX);
48145f67c02SMarkus Pfeiffer 		if (err) {
48245f67c02SMarkus Pfeiffer 			DPRINTFN(0, "Could not unconfigure "
48345f67c02SMarkus Pfeiffer 			    "device (ignored)\n");
48445f67c02SMarkus Pfeiffer 		}
48545f67c02SMarkus Pfeiffer 		if (child->parent_hub == NULL) {
48645f67c02SMarkus Pfeiffer 			/* the root HUB cannot be re-enumerated */
48745f67c02SMarkus Pfeiffer 			DPRINTFN(6, "cannot set port feature\n");
48845f67c02SMarkus Pfeiffer 			err = 0;
48945f67c02SMarkus Pfeiffer 		} else {
49045f67c02SMarkus Pfeiffer 			/* clear port enable */
49145f67c02SMarkus Pfeiffer 			err = usbd_req_clear_port_feature(child->parent_hub,
49245f67c02SMarkus Pfeiffer 			    NULL, child->port_no, UHF_PORT_ENABLE);
49345f67c02SMarkus Pfeiffer 			if (err) {
49445f67c02SMarkus Pfeiffer 				DPRINTFN(0, "Could not disable port "
49545f67c02SMarkus Pfeiffer 				    "(ignored)\n");
49645f67c02SMarkus Pfeiffer 			}
49745f67c02SMarkus Pfeiffer 		}
49845f67c02SMarkus Pfeiffer 		child->re_enumerate_wait = USB_RE_ENUM_DONE;
49945f67c02SMarkus Pfeiffer 		break;
50045f67c02SMarkus Pfeiffer 
50145f67c02SMarkus Pfeiffer 	case USB_RE_ENUM_SET_CONFIG:
50245f67c02SMarkus Pfeiffer 		err = usbd_set_config_index(child,
50345f67c02SMarkus Pfeiffer 		    child->next_config_index);
50445f67c02SMarkus Pfeiffer 		if (err != 0) {
50545f67c02SMarkus Pfeiffer 			DPRINTF("Configure failed: %s: Ignored.\n",
50645f67c02SMarkus Pfeiffer 			    usbd_errstr(err));
50745f67c02SMarkus Pfeiffer 		} else {
50845f67c02SMarkus Pfeiffer 			err = usb_probe_and_attach(child,
50945f67c02SMarkus Pfeiffer 			    USB_IFACE_INDEX_ANY);
51045f67c02SMarkus Pfeiffer 		}
51145f67c02SMarkus Pfeiffer 		child->re_enumerate_wait = USB_RE_ENUM_DONE;
51245f67c02SMarkus Pfeiffer 		break;
51345f67c02SMarkus Pfeiffer 
51445f67c02SMarkus Pfeiffer 	default:
51545f67c02SMarkus Pfeiffer 		child->re_enumerate_wait = USB_RE_ENUM_DONE;
51645f67c02SMarkus Pfeiffer 		break;
51745f67c02SMarkus Pfeiffer 	}
51845f67c02SMarkus Pfeiffer 	if (do_unlock)
51945f67c02SMarkus Pfeiffer 		usbd_enum_unlock(child);
52045f67c02SMarkus Pfeiffer }
52145f67c02SMarkus Pfeiffer 
52245f67c02SMarkus Pfeiffer /*------------------------------------------------------------------------*
52312bd3c8bSSascha Wildner  *	uhub_explore_sub - subroutine
52412bd3c8bSSascha Wildner  *
52512bd3c8bSSascha Wildner  * Return values:
52612bd3c8bSSascha Wildner  *    0: Success
52712bd3c8bSSascha Wildner  * Else: A control transaction failed
52812bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
52912bd3c8bSSascha Wildner static usb_error_t
uhub_explore_sub(struct uhub_softc * sc,struct usb_port * up)53012bd3c8bSSascha Wildner uhub_explore_sub(struct uhub_softc *sc, struct usb_port *up)
53112bd3c8bSSascha Wildner {
53212bd3c8bSSascha Wildner 	struct usb_bus *bus;
53312bd3c8bSSascha Wildner 	struct usb_device *child;
53412bd3c8bSSascha Wildner 	uint8_t refcount;
53512bd3c8bSSascha Wildner 	usb_error_t err;
53612bd3c8bSSascha Wildner 
53712bd3c8bSSascha Wildner 	bus = sc->sc_udev->bus;
53812bd3c8bSSascha Wildner 	err = 0;
53912bd3c8bSSascha Wildner 
54012bd3c8bSSascha Wildner 	/* get driver added refcount from USB bus */
54112bd3c8bSSascha Wildner 	refcount = bus->driver_added_refcount;
54212bd3c8bSSascha Wildner 
54312bd3c8bSSascha Wildner 	/* get device assosiated with the given port */
54412bd3c8bSSascha Wildner 	child = usb_bus_port_get_device(bus, up);
54512bd3c8bSSascha Wildner 	if (child == NULL) {
54612bd3c8bSSascha Wildner 		/* nothing to do */
54712bd3c8bSSascha Wildner 		goto done;
54812bd3c8bSSascha Wildner 	}
54912bd3c8bSSascha Wildner 
55045f67c02SMarkus Pfeiffer 	uhub_explore_handle_re_enumerate(child);
55112bd3c8bSSascha Wildner 
55212bd3c8bSSascha Wildner 	/* check if probe and attach should be done */
55312bd3c8bSSascha Wildner 
55412bd3c8bSSascha Wildner 	if (child->driver_added_refcount != refcount) {
55512bd3c8bSSascha Wildner 		child->driver_added_refcount = refcount;
55612bd3c8bSSascha Wildner 		err = usb_probe_and_attach(child,
55712bd3c8bSSascha Wildner 		    USB_IFACE_INDEX_ANY);
55812bd3c8bSSascha Wildner 		if (err) {
55912bd3c8bSSascha Wildner 			goto done;
56012bd3c8bSSascha Wildner 		}
56112bd3c8bSSascha Wildner 	}
56212bd3c8bSSascha Wildner 	/* start control transfer, if device mode */
56312bd3c8bSSascha Wildner 
56412bd3c8bSSascha Wildner 	if (child->flags.usb_mode == USB_MODE_DEVICE)
56512bd3c8bSSascha Wildner 		usbd_ctrl_transfer_setup(child);
56612bd3c8bSSascha Wildner 
56712bd3c8bSSascha Wildner 	/* if a HUB becomes present, do a recursive HUB explore */
56812bd3c8bSSascha Wildner 
56912bd3c8bSSascha Wildner 	if (child->hub)
57012bd3c8bSSascha Wildner 		err = (child->hub->explore) (child);
57112bd3c8bSSascha Wildner 
57212bd3c8bSSascha Wildner done:
57312bd3c8bSSascha Wildner 	return (err);
57412bd3c8bSSascha Wildner }
57512bd3c8bSSascha Wildner 
57612bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
57712bd3c8bSSascha Wildner  *	uhub_read_port_status - factored out code
57812bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
57912bd3c8bSSascha Wildner static usb_error_t
uhub_read_port_status(struct uhub_softc * sc,uint8_t portno)58012bd3c8bSSascha Wildner uhub_read_port_status(struct uhub_softc *sc, uint8_t portno)
58112bd3c8bSSascha Wildner {
58212bd3c8bSSascha Wildner 	struct usb_port_status ps;
58312bd3c8bSSascha Wildner 	usb_error_t err;
58412bd3c8bSSascha Wildner 
58512bd3c8bSSascha Wildner 	err = usbd_req_get_port_status(
58612bd3c8bSSascha Wildner 	    sc->sc_udev, NULL, &ps, portno);
58712bd3c8bSSascha Wildner 
58812bd3c8bSSascha Wildner 	/* update status regardless of error */
58912bd3c8bSSascha Wildner 
59012bd3c8bSSascha Wildner 	sc->sc_st.port_status = UGETW(ps.wPortStatus);
59112bd3c8bSSascha Wildner 	sc->sc_st.port_change = UGETW(ps.wPortChange);
59212bd3c8bSSascha Wildner 
59312bd3c8bSSascha Wildner 	/* debugging print */
59412bd3c8bSSascha Wildner 
59512bd3c8bSSascha Wildner 	DPRINTFN(4, "port %d, wPortStatus=0x%04x, "
59612bd3c8bSSascha Wildner 	    "wPortChange=0x%04x, err=%s\n",
59712bd3c8bSSascha Wildner 	    portno, sc->sc_st.port_status,
59812bd3c8bSSascha Wildner 	    sc->sc_st.port_change, usbd_errstr(err));
59912bd3c8bSSascha Wildner 	return (err);
60012bd3c8bSSascha Wildner }
60112bd3c8bSSascha Wildner 
60212bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
60312bd3c8bSSascha Wildner  *	uhub_reattach_port
60412bd3c8bSSascha Wildner  *
60512bd3c8bSSascha Wildner  * Returns:
60612bd3c8bSSascha Wildner  *    0: Success
60712bd3c8bSSascha Wildner  * Else: A control transaction failed
60812bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
60912bd3c8bSSascha Wildner static usb_error_t
uhub_reattach_port(struct uhub_softc * sc,uint8_t portno)61012bd3c8bSSascha Wildner uhub_reattach_port(struct uhub_softc *sc, uint8_t portno)
61112bd3c8bSSascha Wildner {
61212bd3c8bSSascha Wildner 	struct usb_device *child;
61312bd3c8bSSascha Wildner 	struct usb_device *udev;
61412bd3c8bSSascha Wildner 	enum usb_dev_speed speed;
61512bd3c8bSSascha Wildner 	enum usb_hc_mode mode;
61612bd3c8bSSascha Wildner 	usb_error_t err;
61712bd3c8bSSascha Wildner 	uint16_t power_mask;
61812bd3c8bSSascha Wildner 	uint8_t timeout;
61912bd3c8bSSascha Wildner 
62012bd3c8bSSascha Wildner 	DPRINTF("reattaching port %d\n", portno);
62112bd3c8bSSascha Wildner 
62212bd3c8bSSascha Wildner 	timeout = 0;
62312bd3c8bSSascha Wildner 	udev = sc->sc_udev;
62412bd3c8bSSascha Wildner 	child = usb_bus_port_get_device(udev->bus,
62512bd3c8bSSascha Wildner 	    udev->hub->ports + portno - 1);
62612bd3c8bSSascha Wildner 
62712bd3c8bSSascha Wildner repeat:
62812bd3c8bSSascha Wildner 
62912bd3c8bSSascha Wildner 	/* first clear the port connection change bit */
63012bd3c8bSSascha Wildner 
63112bd3c8bSSascha Wildner 	err = usbd_req_clear_port_feature(udev, NULL,
63212bd3c8bSSascha Wildner 	    portno, UHF_C_PORT_CONNECTION);
63312bd3c8bSSascha Wildner 
63412bd3c8bSSascha Wildner 	if (err) {
63512bd3c8bSSascha Wildner 		goto error;
63612bd3c8bSSascha Wildner 	}
63712bd3c8bSSascha Wildner 	/* check if there is a child */
63812bd3c8bSSascha Wildner 
63912bd3c8bSSascha Wildner 	if (child != NULL) {
64012bd3c8bSSascha Wildner 		/*
64112bd3c8bSSascha Wildner 		 * Free USB device and all subdevices, if any.
64212bd3c8bSSascha Wildner 		 */
64312bd3c8bSSascha Wildner 		usb_free_device(child, 0);
64412bd3c8bSSascha Wildner 		child = NULL;
64512bd3c8bSSascha Wildner 	}
64612bd3c8bSSascha Wildner 	/* get fresh status */
64712bd3c8bSSascha Wildner 
64812bd3c8bSSascha Wildner 	err = uhub_read_port_status(sc, portno);
64945f67c02SMarkus Pfeiffer 	if (err)
65045f67c02SMarkus Pfeiffer 		goto error;
65145f67c02SMarkus Pfeiffer 
65245f67c02SMarkus Pfeiffer #if USB_HAVE_DISABLE_ENUM
65345f67c02SMarkus Pfeiffer 	/* check if we should skip enumeration from this USB HUB */
65445f67c02SMarkus Pfeiffer 	if (usb_disable_enumeration != 0 ||
65545f67c02SMarkus Pfeiffer 	    sc->sc_disable_enumeration != 0) {
65645f67c02SMarkus Pfeiffer 		DPRINTF("Enumeration is disabled!\n");
65712bd3c8bSSascha Wildner 		goto error;
65812bd3c8bSSascha Wildner 	}
65945f67c02SMarkus Pfeiffer #endif
66012bd3c8bSSascha Wildner 	/* check if nothing is connected to the port */
66112bd3c8bSSascha Wildner 
66212bd3c8bSSascha Wildner 	if (!(sc->sc_st.port_status & UPS_CURRENT_CONNECT_STATUS)) {
66312bd3c8bSSascha Wildner 		goto error;
66412bd3c8bSSascha Wildner 	}
66512bd3c8bSSascha Wildner 	/* check if there is no power on the port and print a warning */
66612bd3c8bSSascha Wildner 
66712bd3c8bSSascha Wildner 	switch (udev->speed) {
66812bd3c8bSSascha Wildner 	case USB_SPEED_HIGH:
66912bd3c8bSSascha Wildner 	case USB_SPEED_FULL:
67012bd3c8bSSascha Wildner 	case USB_SPEED_LOW:
67112bd3c8bSSascha Wildner 		power_mask = UPS_PORT_POWER;
67212bd3c8bSSascha Wildner 		break;
67312bd3c8bSSascha Wildner 	case USB_SPEED_SUPER:
67412bd3c8bSSascha Wildner 		if (udev->parent_hub == NULL)
67512bd3c8bSSascha Wildner 			power_mask = UPS_PORT_POWER;
67612bd3c8bSSascha Wildner 		else
67712bd3c8bSSascha Wildner 			power_mask = UPS_PORT_POWER_SS;
67812bd3c8bSSascha Wildner 		break;
67912bd3c8bSSascha Wildner 	default:
68012bd3c8bSSascha Wildner 		power_mask = 0;
68112bd3c8bSSascha Wildner 		break;
68212bd3c8bSSascha Wildner 	}
68312bd3c8bSSascha Wildner 	if (!(sc->sc_st.port_status & power_mask)) {
68412bd3c8bSSascha Wildner 		DPRINTF("WARNING: strange, connected port %d "
68512bd3c8bSSascha Wildner 		    "has no power\n", portno);
68612bd3c8bSSascha Wildner 	}
68712bd3c8bSSascha Wildner 
68812bd3c8bSSascha Wildner 	/* check if the device is in Host Mode */
68912bd3c8bSSascha Wildner 
69012bd3c8bSSascha Wildner 	if (!(sc->sc_st.port_status & UPS_PORT_MODE_DEVICE)) {
69112bd3c8bSSascha Wildner 
69212bd3c8bSSascha Wildner 		DPRINTF("Port %d is in Host Mode\n", portno);
69312bd3c8bSSascha Wildner 
69412bd3c8bSSascha Wildner 		if (sc->sc_st.port_status & UPS_SUSPEND) {
69512bd3c8bSSascha Wildner 			/*
69612bd3c8bSSascha Wildner 			 * NOTE: Should not get here in SuperSpeed
69712bd3c8bSSascha Wildner 			 * mode, because the HUB should report this
69812bd3c8bSSascha Wildner 			 * bit as zero.
69912bd3c8bSSascha Wildner 			 */
70012bd3c8bSSascha Wildner 			DPRINTF("Port %d was still "
70112bd3c8bSSascha Wildner 			    "suspended, clearing.\n", portno);
70212bd3c8bSSascha Wildner 			err = usbd_req_clear_port_feature(udev,
70312bd3c8bSSascha Wildner 			    NULL, portno, UHF_PORT_SUSPEND);
70412bd3c8bSSascha Wildner 		}
70512bd3c8bSSascha Wildner 
70612bd3c8bSSascha Wildner 		/* USB Host Mode */
70712bd3c8bSSascha Wildner 
70812bd3c8bSSascha Wildner 		/* wait for maximum device power up time */
70912bd3c8bSSascha Wildner 
71012bd3c8bSSascha Wildner 		usb_pause_mtx(NULL,
7115e41ab93SMarkus Pfeiffer 		    USB_MS_TO_TICKS(usb_port_powerup_delay));
71212bd3c8bSSascha Wildner 
71312bd3c8bSSascha Wildner 		/* reset port, which implies enabling it */
71412bd3c8bSSascha Wildner 
71512bd3c8bSSascha Wildner 		err = usbd_req_reset_port(udev, NULL, portno);
71612bd3c8bSSascha Wildner 
71712bd3c8bSSascha Wildner 		if (err) {
71812bd3c8bSSascha Wildner 			DPRINTFN(0, "port %d reset "
71912bd3c8bSSascha Wildner 			    "failed, error=%s\n",
72012bd3c8bSSascha Wildner 			    portno, usbd_errstr(err));
72112bd3c8bSSascha Wildner 			goto error;
72212bd3c8bSSascha Wildner 		}
72312bd3c8bSSascha Wildner 		/* get port status again, it might have changed during reset */
72412bd3c8bSSascha Wildner 
72512bd3c8bSSascha Wildner 		err = uhub_read_port_status(sc, portno);
72612bd3c8bSSascha Wildner 		if (err) {
72712bd3c8bSSascha Wildner 			goto error;
72812bd3c8bSSascha Wildner 		}
72912bd3c8bSSascha Wildner 		/* check if something changed during port reset */
73012bd3c8bSSascha Wildner 
73112bd3c8bSSascha Wildner 		if ((sc->sc_st.port_change & UPS_C_CONNECT_STATUS) ||
73212bd3c8bSSascha Wildner 		    (!(sc->sc_st.port_status & UPS_CURRENT_CONNECT_STATUS))) {
73312bd3c8bSSascha Wildner 			if (timeout) {
73412bd3c8bSSascha Wildner 				DPRINTFN(0, "giving up port reset "
73512bd3c8bSSascha Wildner 				    "- device vanished\n");
73612bd3c8bSSascha Wildner 				goto error;
73712bd3c8bSSascha Wildner 			}
73812bd3c8bSSascha Wildner 			timeout = 1;
73912bd3c8bSSascha Wildner 			goto repeat;
74012bd3c8bSSascha Wildner 		}
74112bd3c8bSSascha Wildner 	} else {
74212bd3c8bSSascha Wildner 		DPRINTF("Port %d is in Device Mode\n", portno);
74312bd3c8bSSascha Wildner 	}
74412bd3c8bSSascha Wildner 
74512bd3c8bSSascha Wildner 	/*
74612bd3c8bSSascha Wildner 	 * Figure out the device speed
74712bd3c8bSSascha Wildner 	 */
74812bd3c8bSSascha Wildner 	switch (udev->speed) {
74912bd3c8bSSascha Wildner 	case USB_SPEED_HIGH:
75012bd3c8bSSascha Wildner 		if (sc->sc_st.port_status & UPS_HIGH_SPEED)
75112bd3c8bSSascha Wildner 			speed = USB_SPEED_HIGH;
75212bd3c8bSSascha Wildner 		else if (sc->sc_st.port_status & UPS_LOW_SPEED)
75312bd3c8bSSascha Wildner 			speed = USB_SPEED_LOW;
75412bd3c8bSSascha Wildner 		else
75512bd3c8bSSascha Wildner 			speed = USB_SPEED_FULL;
75612bd3c8bSSascha Wildner 		break;
75712bd3c8bSSascha Wildner 	case USB_SPEED_FULL:
75812bd3c8bSSascha Wildner 		if (sc->sc_st.port_status & UPS_LOW_SPEED)
75912bd3c8bSSascha Wildner 			speed = USB_SPEED_LOW;
76012bd3c8bSSascha Wildner 		else
76112bd3c8bSSascha Wildner 			speed = USB_SPEED_FULL;
76212bd3c8bSSascha Wildner 		break;
76312bd3c8bSSascha Wildner 	case USB_SPEED_LOW:
76412bd3c8bSSascha Wildner 		speed = USB_SPEED_LOW;
76512bd3c8bSSascha Wildner 		break;
76612bd3c8bSSascha Wildner 	case USB_SPEED_SUPER:
76712bd3c8bSSascha Wildner 		if (udev->parent_hub == NULL) {
76812bd3c8bSSascha Wildner 			/* Root HUB - special case */
76912bd3c8bSSascha Wildner 			switch (sc->sc_st.port_status & UPS_OTHER_SPEED) {
77012bd3c8bSSascha Wildner 			case 0:
77112bd3c8bSSascha Wildner 				speed = USB_SPEED_FULL;
77212bd3c8bSSascha Wildner 				break;
77312bd3c8bSSascha Wildner 			case UPS_LOW_SPEED:
77412bd3c8bSSascha Wildner 				speed = USB_SPEED_LOW;
77512bd3c8bSSascha Wildner 				break;
77612bd3c8bSSascha Wildner 			case UPS_HIGH_SPEED:
77712bd3c8bSSascha Wildner 				speed = USB_SPEED_HIGH;
77812bd3c8bSSascha Wildner 				break;
77912bd3c8bSSascha Wildner 			default:
78012bd3c8bSSascha Wildner 				speed = USB_SPEED_SUPER;
78112bd3c8bSSascha Wildner 				break;
78212bd3c8bSSascha Wildner 			}
78312bd3c8bSSascha Wildner 		} else {
78412bd3c8bSSascha Wildner 			speed = USB_SPEED_SUPER;
78512bd3c8bSSascha Wildner 		}
78612bd3c8bSSascha Wildner 		break;
78712bd3c8bSSascha Wildner 	default:
78812bd3c8bSSascha Wildner 		/* same speed like parent */
78912bd3c8bSSascha Wildner 		speed = udev->speed;
79012bd3c8bSSascha Wildner 		break;
79112bd3c8bSSascha Wildner 	}
79212bd3c8bSSascha Wildner 	if (speed == USB_SPEED_SUPER) {
79312bd3c8bSSascha Wildner 		err = usbd_req_set_hub_u1_timeout(udev, NULL,
79412bd3c8bSSascha Wildner 		    portno, 128 - (2 * udev->depth));
79512bd3c8bSSascha Wildner 		if (err) {
79612bd3c8bSSascha Wildner 			DPRINTFN(0, "port %d U1 timeout "
79712bd3c8bSSascha Wildner 			    "failed, error=%s\n",
79812bd3c8bSSascha Wildner 			    portno, usbd_errstr(err));
79912bd3c8bSSascha Wildner 		}
80012bd3c8bSSascha Wildner 		err = usbd_req_set_hub_u2_timeout(udev, NULL,
80112bd3c8bSSascha Wildner 		    portno, 128 - (2 * udev->depth));
80212bd3c8bSSascha Wildner 		if (err) {
80312bd3c8bSSascha Wildner 			DPRINTFN(0, "port %d U2 timeout "
80412bd3c8bSSascha Wildner 			    "failed, error=%s\n",
80512bd3c8bSSascha Wildner 			    portno, usbd_errstr(err));
80612bd3c8bSSascha Wildner 		}
80712bd3c8bSSascha Wildner 	}
80812bd3c8bSSascha Wildner 
80912bd3c8bSSascha Wildner 	/*
81012bd3c8bSSascha Wildner 	 * Figure out the device mode
81112bd3c8bSSascha Wildner 	 *
81212bd3c8bSSascha Wildner 	 * NOTE: This part is currently FreeBSD specific.
81312bd3c8bSSascha Wildner 	 */
8145e41ab93SMarkus Pfeiffer 	if (udev->parent_hub != NULL) {
8155e41ab93SMarkus Pfeiffer 		/* inherit mode from the parent HUB */
8165e41ab93SMarkus Pfeiffer 		mode = udev->parent_hub->flags.usb_mode;
8175e41ab93SMarkus Pfeiffer 	} else if (sc->sc_st.port_status & UPS_PORT_MODE_DEVICE)
81812bd3c8bSSascha Wildner 		mode = USB_MODE_DEVICE;
81912bd3c8bSSascha Wildner 	else
82012bd3c8bSSascha Wildner 		mode = USB_MODE_HOST;
8218922de18SMarkus Pfeiffer 
82212bd3c8bSSascha Wildner 	/* need to create a new child */
82312bd3c8bSSascha Wildner 	child = usb_alloc_device(sc->sc_dev, udev->bus, udev,
82412bd3c8bSSascha Wildner 	    udev->depth + 1, portno - 1, portno, speed, mode);
82512bd3c8bSSascha Wildner 	if (child == NULL) {
82612bd3c8bSSascha Wildner 		DPRINTFN(0, "could not allocate new device\n");
82712bd3c8bSSascha Wildner 		goto error;
82812bd3c8bSSascha Wildner 	}
82912bd3c8bSSascha Wildner 	return (0);			/* success */
83012bd3c8bSSascha Wildner 
83112bd3c8bSSascha Wildner error:
83212bd3c8bSSascha Wildner 	if (child != NULL) {
83312bd3c8bSSascha Wildner 		/*
83412bd3c8bSSascha Wildner 		 * Free USB device and all subdevices, if any.
83512bd3c8bSSascha Wildner 		 */
83612bd3c8bSSascha Wildner 		usb_free_device(child, 0);
83712bd3c8bSSascha Wildner 		child = NULL;
83812bd3c8bSSascha Wildner 	}
83912bd3c8bSSascha Wildner 	if (err == 0) {
84012bd3c8bSSascha Wildner 		if (sc->sc_st.port_status & UPS_PORT_ENABLED) {
84112bd3c8bSSascha Wildner 			err = usbd_req_clear_port_feature(
84212bd3c8bSSascha Wildner 			    sc->sc_udev, NULL,
84312bd3c8bSSascha Wildner 			    portno, UHF_PORT_ENABLE);
84412bd3c8bSSascha Wildner 		}
84512bd3c8bSSascha Wildner 	}
84612bd3c8bSSascha Wildner 	if (err) {
84712bd3c8bSSascha Wildner 		DPRINTFN(0, "device problem (%s), "
84812bd3c8bSSascha Wildner 		    "disabling port %d\n", usbd_errstr(err), portno);
84912bd3c8bSSascha Wildner 	}
85012bd3c8bSSascha Wildner 	return (err);
85112bd3c8bSSascha Wildner }
85212bd3c8bSSascha Wildner 
85312bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
85412bd3c8bSSascha Wildner  *	usb_device_20_compatible
85512bd3c8bSSascha Wildner  *
85612bd3c8bSSascha Wildner  * Returns:
85712bd3c8bSSascha Wildner  *    0: HUB does not support suspend and resume
85812bd3c8bSSascha Wildner  * Else: HUB supports suspend and resume
85912bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
86012bd3c8bSSascha Wildner static uint8_t
usb_device_20_compatible(struct usb_device * udev)86112bd3c8bSSascha Wildner usb_device_20_compatible(struct usb_device *udev)
86212bd3c8bSSascha Wildner {
86312bd3c8bSSascha Wildner 	if (udev == NULL)
86412bd3c8bSSascha Wildner 		return (0);
86512bd3c8bSSascha Wildner 	switch (udev->speed) {
86612bd3c8bSSascha Wildner 	case USB_SPEED_LOW:
86712bd3c8bSSascha Wildner 	case USB_SPEED_FULL:
86812bd3c8bSSascha Wildner 	case USB_SPEED_HIGH:
86912bd3c8bSSascha Wildner 		return (1);
87012bd3c8bSSascha Wildner 	default:
87112bd3c8bSSascha Wildner 		return (0);
87212bd3c8bSSascha Wildner 	}
87312bd3c8bSSascha Wildner }
87412bd3c8bSSascha Wildner 
87512bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
87612bd3c8bSSascha Wildner  *	uhub_suspend_resume_port
87712bd3c8bSSascha Wildner  *
87812bd3c8bSSascha Wildner  * Returns:
87912bd3c8bSSascha Wildner  *    0: Success
88012bd3c8bSSascha Wildner  * Else: A control transaction failed
88112bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
88212bd3c8bSSascha Wildner static usb_error_t
uhub_suspend_resume_port(struct uhub_softc * sc,uint8_t portno)88312bd3c8bSSascha Wildner uhub_suspend_resume_port(struct uhub_softc *sc, uint8_t portno)
88412bd3c8bSSascha Wildner {
88512bd3c8bSSascha Wildner 	struct usb_device *child;
88612bd3c8bSSascha Wildner 	struct usb_device *udev;
88712bd3c8bSSascha Wildner 	uint8_t is_suspend;
88812bd3c8bSSascha Wildner 	usb_error_t err;
88912bd3c8bSSascha Wildner 
89012bd3c8bSSascha Wildner 	DPRINTF("port %d\n", portno);
89112bd3c8bSSascha Wildner 
89212bd3c8bSSascha Wildner 	udev = sc->sc_udev;
89312bd3c8bSSascha Wildner 	child = usb_bus_port_get_device(udev->bus,
89412bd3c8bSSascha Wildner 	    udev->hub->ports + portno - 1);
89512bd3c8bSSascha Wildner 
89612bd3c8bSSascha Wildner 	/* first clear the port suspend change bit */
89712bd3c8bSSascha Wildner 
89812bd3c8bSSascha Wildner 	if (usb_device_20_compatible(udev)) {
89912bd3c8bSSascha Wildner 		err = usbd_req_clear_port_feature(udev, NULL,
90012bd3c8bSSascha Wildner 		    portno, UHF_C_PORT_SUSPEND);
90112bd3c8bSSascha Wildner 	} else {
90212bd3c8bSSascha Wildner 		err = usbd_req_clear_port_feature(udev, NULL,
90312bd3c8bSSascha Wildner 		    portno, UHF_C_PORT_LINK_STATE);
90412bd3c8bSSascha Wildner 	}
90512bd3c8bSSascha Wildner 
90612bd3c8bSSascha Wildner 	if (err) {
90712bd3c8bSSascha Wildner 		DPRINTF("clearing suspend failed.\n");
90812bd3c8bSSascha Wildner 		goto done;
90912bd3c8bSSascha Wildner 	}
91012bd3c8bSSascha Wildner 	/* get fresh status */
91112bd3c8bSSascha Wildner 
91212bd3c8bSSascha Wildner 	err = uhub_read_port_status(sc, portno);
91312bd3c8bSSascha Wildner 	if (err) {
91412bd3c8bSSascha Wildner 		DPRINTF("reading port status failed.\n");
91512bd3c8bSSascha Wildner 		goto done;
91612bd3c8bSSascha Wildner 	}
91712bd3c8bSSascha Wildner 	/* convert current state */
91812bd3c8bSSascha Wildner 
91912bd3c8bSSascha Wildner 	if (usb_device_20_compatible(udev)) {
92012bd3c8bSSascha Wildner 		if (sc->sc_st.port_status & UPS_SUSPEND) {
92112bd3c8bSSascha Wildner 			is_suspend = 1;
92212bd3c8bSSascha Wildner 		} else {
92312bd3c8bSSascha Wildner 			is_suspend = 0;
92412bd3c8bSSascha Wildner 		}
92512bd3c8bSSascha Wildner 	} else {
92612bd3c8bSSascha Wildner 		switch (UPS_PORT_LINK_STATE_GET(sc->sc_st.port_status)) {
92712bd3c8bSSascha Wildner 		case UPS_PORT_LS_U3:
92812bd3c8bSSascha Wildner 			is_suspend = 1;
92912bd3c8bSSascha Wildner 			break;
93012bd3c8bSSascha Wildner 		case UPS_PORT_LS_SS_INA:
93112bd3c8bSSascha Wildner 			usbd_req_warm_reset_port(udev, NULL, portno);
93212bd3c8bSSascha Wildner 			is_suspend = 0;
93312bd3c8bSSascha Wildner 			break;
93412bd3c8bSSascha Wildner 		default:
93512bd3c8bSSascha Wildner 			is_suspend = 0;
93612bd3c8bSSascha Wildner 			break;
93712bd3c8bSSascha Wildner 		}
93812bd3c8bSSascha Wildner 	}
93912bd3c8bSSascha Wildner 
94012bd3c8bSSascha Wildner 	DPRINTF("suspended=%u\n", is_suspend);
94112bd3c8bSSascha Wildner 
94212bd3c8bSSascha Wildner 	/* do the suspend or resume */
94312bd3c8bSSascha Wildner 
94412bd3c8bSSascha Wildner 	if (child) {
94512bd3c8bSSascha Wildner 		/*
94612bd3c8bSSascha Wildner 		 * This code handle two cases: 1) Host Mode - we can only
94712bd3c8bSSascha Wildner 		 * receive resume here 2) Device Mode - we can receive
94812bd3c8bSSascha Wildner 		 * suspend and resume here
94912bd3c8bSSascha Wildner 		 */
95012bd3c8bSSascha Wildner 		if (is_suspend == 0)
95112bd3c8bSSascha Wildner 			usb_dev_resume_peer(child);
95212bd3c8bSSascha Wildner 		else if (child->flags.usb_mode == USB_MODE_DEVICE)
95312bd3c8bSSascha Wildner 			usb_dev_suspend_peer(child);
95412bd3c8bSSascha Wildner 	}
95512bd3c8bSSascha Wildner done:
95612bd3c8bSSascha Wildner 	return (err);
95712bd3c8bSSascha Wildner }
95812bd3c8bSSascha Wildner 
95912bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
96012bd3c8bSSascha Wildner  *	uhub_root_interrupt
96112bd3c8bSSascha Wildner  *
96212bd3c8bSSascha Wildner  * This function is called when a Root HUB interrupt has
96312bd3c8bSSascha Wildner  * happened. "ptr" and "len" makes up the Root HUB interrupt
96412bd3c8bSSascha Wildner  * packet. This function is called having the "bus_mtx" locked.
96512bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
96612bd3c8bSSascha Wildner void
uhub_root_intr(struct usb_bus * bus,const uint8_t * ptr,uint8_t len)96712bd3c8bSSascha Wildner uhub_root_intr(struct usb_bus *bus, const uint8_t *ptr, uint8_t len)
96812bd3c8bSSascha Wildner {
969722d05c3SSascha Wildner 	USB_BUS_LOCK_ASSERT(bus);
97012bd3c8bSSascha Wildner 
97112bd3c8bSSascha Wildner 	usb_needs_explore(bus, 0);
97212bd3c8bSSascha Wildner }
97312bd3c8bSSascha Wildner 
97412bd3c8bSSascha Wildner static uint8_t
uhub_is_too_deep(struct usb_device * udev)97512bd3c8bSSascha Wildner uhub_is_too_deep(struct usb_device *udev)
97612bd3c8bSSascha Wildner {
97712bd3c8bSSascha Wildner 	switch (udev->speed) {
97812bd3c8bSSascha Wildner 	case USB_SPEED_FULL:
97912bd3c8bSSascha Wildner 	case USB_SPEED_LOW:
98012bd3c8bSSascha Wildner 	case USB_SPEED_HIGH:
98112bd3c8bSSascha Wildner 		if (udev->depth > USB_HUB_MAX_DEPTH)
98212bd3c8bSSascha Wildner 			return (1);
98312bd3c8bSSascha Wildner 		break;
98412bd3c8bSSascha Wildner 	case USB_SPEED_SUPER:
98512bd3c8bSSascha Wildner 		if (udev->depth > USB_SS_HUB_DEPTH_MAX)
98612bd3c8bSSascha Wildner 			return (1);
98712bd3c8bSSascha Wildner 		break;
98812bd3c8bSSascha Wildner 	default:
98912bd3c8bSSascha Wildner 		break;
99012bd3c8bSSascha Wildner 	}
99112bd3c8bSSascha Wildner 	return (0);
99212bd3c8bSSascha Wildner }
99312bd3c8bSSascha Wildner 
99412bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
99512bd3c8bSSascha Wildner  *	uhub_explore
99612bd3c8bSSascha Wildner  *
99712bd3c8bSSascha Wildner  * Returns:
99812bd3c8bSSascha Wildner  *     0: Success
99912bd3c8bSSascha Wildner  *  Else: Failure
100012bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
100112bd3c8bSSascha Wildner static usb_error_t
uhub_explore(struct usb_device * udev)100212bd3c8bSSascha Wildner uhub_explore(struct usb_device *udev)
100312bd3c8bSSascha Wildner {
100412bd3c8bSSascha Wildner 	struct usb_hub *hub;
100512bd3c8bSSascha Wildner 	struct uhub_softc *sc;
100612bd3c8bSSascha Wildner 	struct usb_port *up;
100712bd3c8bSSascha Wildner 	usb_error_t err;
100812bd3c8bSSascha Wildner 	uint8_t portno;
100912bd3c8bSSascha Wildner 	uint8_t x;
101057bed822SMarkus Pfeiffer 	uint8_t do_unlock;
101112bd3c8bSSascha Wildner 
101212bd3c8bSSascha Wildner 	hub = udev->hub;
101312bd3c8bSSascha Wildner 	sc = hub->hubsoftc;
101412bd3c8bSSascha Wildner 
101512bd3c8bSSascha Wildner 	DPRINTFN(11, "udev=%p addr=%d\n", udev, udev->address);
101612bd3c8bSSascha Wildner 
101712bd3c8bSSascha Wildner 	/* ignore devices that are too deep */
101812bd3c8bSSascha Wildner 	if (uhub_is_too_deep(udev))
101912bd3c8bSSascha Wildner 		return (USB_ERR_TOO_DEEP);
102012bd3c8bSSascha Wildner 
102112bd3c8bSSascha Wildner 	/* check if device is suspended */
102212bd3c8bSSascha Wildner 	if (udev->flags.self_suspended) {
102312bd3c8bSSascha Wildner 		/* need to wait until the child signals resume */
102412bd3c8bSSascha Wildner 		DPRINTF("Device is suspended!\n");
102512bd3c8bSSascha Wildner 		return (0);
102612bd3c8bSSascha Wildner 	}
102712bd3c8bSSascha Wildner 
102812bd3c8bSSascha Wildner 	/*
102912bd3c8bSSascha Wildner 	 * Make sure we don't race against user-space applications
103012bd3c8bSSascha Wildner 	 * like LibUSB:
103112bd3c8bSSascha Wildner 	 */
103257bed822SMarkus Pfeiffer 	do_unlock = usbd_enum_lock(udev);
103312bd3c8bSSascha Wildner 
103412bd3c8bSSascha Wildner 	for (x = 0; x != hub->nports; x++) {
103512bd3c8bSSascha Wildner 		up = hub->ports + x;
103612bd3c8bSSascha Wildner 		portno = x + 1;
103712bd3c8bSSascha Wildner 
103812bd3c8bSSascha Wildner 		err = uhub_read_port_status(sc, portno);
103912bd3c8bSSascha Wildner 		if (err) {
104012bd3c8bSSascha Wildner 			/* most likely the HUB is gone */
104112bd3c8bSSascha Wildner 			break;
104212bd3c8bSSascha Wildner 		}
104312bd3c8bSSascha Wildner 		if (sc->sc_st.port_change & UPS_C_OVERCURRENT_INDICATOR) {
104412bd3c8bSSascha Wildner 			DPRINTF("Overcurrent on port %u.\n", portno);
104512bd3c8bSSascha Wildner 			err = usbd_req_clear_port_feature(
104612bd3c8bSSascha Wildner 			    udev, NULL, portno, UHF_C_PORT_OVER_CURRENT);
104712bd3c8bSSascha Wildner 			if (err) {
104812bd3c8bSSascha Wildner 				/* most likely the HUB is gone */
104912bd3c8bSSascha Wildner 				break;
105012bd3c8bSSascha Wildner 			}
105112bd3c8bSSascha Wildner 		}
105212bd3c8bSSascha Wildner 		if (!(sc->sc_flags & UHUB_FLAG_DID_EXPLORE)) {
105312bd3c8bSSascha Wildner 			/*
105412bd3c8bSSascha Wildner 			 * Fake a connect status change so that the
105512bd3c8bSSascha Wildner 			 * status gets checked initially!
105612bd3c8bSSascha Wildner 			 */
105712bd3c8bSSascha Wildner 			sc->sc_st.port_change |=
105812bd3c8bSSascha Wildner 			    UPS_C_CONNECT_STATUS;
105912bd3c8bSSascha Wildner 		}
106012bd3c8bSSascha Wildner 		if (sc->sc_st.port_change & UPS_C_PORT_ENABLED) {
106112bd3c8bSSascha Wildner 			err = usbd_req_clear_port_feature(
106212bd3c8bSSascha Wildner 			    udev, NULL, portno, UHF_C_PORT_ENABLE);
106312bd3c8bSSascha Wildner 			if (err) {
106412bd3c8bSSascha Wildner 				/* most likely the HUB is gone */
106512bd3c8bSSascha Wildner 				break;
106612bd3c8bSSascha Wildner 			}
106712bd3c8bSSascha Wildner 			if (sc->sc_st.port_change & UPS_C_CONNECT_STATUS) {
106812bd3c8bSSascha Wildner 				/*
106912bd3c8bSSascha Wildner 				 * Ignore the port error if the device
107012bd3c8bSSascha Wildner 				 * has vanished !
107112bd3c8bSSascha Wildner 				 */
107212bd3c8bSSascha Wildner 			} else if (sc->sc_st.port_status & UPS_PORT_ENABLED) {
107312bd3c8bSSascha Wildner 				DPRINTFN(0, "illegal enable change, "
107412bd3c8bSSascha Wildner 				    "port %d\n", portno);
107512bd3c8bSSascha Wildner 			} else {
107612bd3c8bSSascha Wildner 
107712bd3c8bSSascha Wildner 				if (up->restartcnt == USB_RESTART_MAX) {
107812bd3c8bSSascha Wildner 					/* XXX could try another speed ? */
107912bd3c8bSSascha Wildner 					DPRINTFN(0, "port error, giving up "
108012bd3c8bSSascha Wildner 					    "port %d\n", portno);
108112bd3c8bSSascha Wildner 				} else {
108212bd3c8bSSascha Wildner 					sc->sc_st.port_change |=
108312bd3c8bSSascha Wildner 					    UPS_C_CONNECT_STATUS;
108412bd3c8bSSascha Wildner 					up->restartcnt++;
108512bd3c8bSSascha Wildner 				}
108612bd3c8bSSascha Wildner 			}
108712bd3c8bSSascha Wildner 		}
108812bd3c8bSSascha Wildner 		if (sc->sc_st.port_change & UPS_C_CONNECT_STATUS) {
108912bd3c8bSSascha Wildner 			err = uhub_reattach_port(sc, portno);
109012bd3c8bSSascha Wildner 			if (err) {
109112bd3c8bSSascha Wildner 				/* most likely the HUB is gone */
109212bd3c8bSSascha Wildner 				break;
109312bd3c8bSSascha Wildner 			}
109412bd3c8bSSascha Wildner 		}
109512bd3c8bSSascha Wildner 		if (sc->sc_st.port_change & (UPS_C_SUSPEND |
109612bd3c8bSSascha Wildner 		    UPS_C_PORT_LINK_STATE)) {
109712bd3c8bSSascha Wildner 			err = uhub_suspend_resume_port(sc, portno);
109812bd3c8bSSascha Wildner 			if (err) {
109912bd3c8bSSascha Wildner 				/* most likely the HUB is gone */
110012bd3c8bSSascha Wildner 				break;
110112bd3c8bSSascha Wildner 			}
110212bd3c8bSSascha Wildner 		}
110312bd3c8bSSascha Wildner 		err = uhub_explore_sub(sc, up);
110412bd3c8bSSascha Wildner 		if (err) {
110512bd3c8bSSascha Wildner 			/* no device(s) present */
110612bd3c8bSSascha Wildner 			continue;
110712bd3c8bSSascha Wildner 		}
110812bd3c8bSSascha Wildner 		/* explore succeeded - reset restart counter */
110912bd3c8bSSascha Wildner 		up->restartcnt = 0;
111012bd3c8bSSascha Wildner 	}
111112bd3c8bSSascha Wildner 
111257bed822SMarkus Pfeiffer 	if (do_unlock)
111312bd3c8bSSascha Wildner 		usbd_enum_unlock(udev);
111412bd3c8bSSascha Wildner 
111512bd3c8bSSascha Wildner 	/* initial status checked */
111612bd3c8bSSascha Wildner 	sc->sc_flags |= UHUB_FLAG_DID_EXPLORE;
111712bd3c8bSSascha Wildner 
111812bd3c8bSSascha Wildner 	/* return success */
111912bd3c8bSSascha Wildner 	return (USB_ERR_NORMAL_COMPLETION);
112012bd3c8bSSascha Wildner }
112112bd3c8bSSascha Wildner 
112212bd3c8bSSascha Wildner static int
uhub_probe(device_t dev)112312bd3c8bSSascha Wildner uhub_probe(device_t dev)
112412bd3c8bSSascha Wildner {
112512bd3c8bSSascha Wildner 	struct usb_attach_arg *uaa = device_get_ivars(dev);
112612bd3c8bSSascha Wildner 
112712bd3c8bSSascha Wildner 	if (uaa->usb_mode != USB_MODE_HOST)
112812bd3c8bSSascha Wildner 		return (ENXIO);
112912bd3c8bSSascha Wildner 
113012bd3c8bSSascha Wildner 	/*
113112bd3c8bSSascha Wildner 	 * The subclass for USB HUBs is currently ignored because it
113212bd3c8bSSascha Wildner 	 * is 0 for some and 1 for others.
113312bd3c8bSSascha Wildner 	 */
113412bd3c8bSSascha Wildner 	if (uaa->info.bConfigIndex == 0 &&
113512bd3c8bSSascha Wildner 	    uaa->info.bDeviceClass == UDCLASS_HUB)
113612bd3c8bSSascha Wildner 		return (0);
113712bd3c8bSSascha Wildner 
113812bd3c8bSSascha Wildner 	return (ENXIO);
113912bd3c8bSSascha Wildner }
114012bd3c8bSSascha Wildner 
114112bd3c8bSSascha Wildner /* NOTE: The information returned by this function can be wrong. */
114212bd3c8bSSascha Wildner usb_error_t
uhub_query_info(struct usb_device * udev,uint8_t * pnports,uint8_t * ptt)114312bd3c8bSSascha Wildner uhub_query_info(struct usb_device *udev, uint8_t *pnports, uint8_t *ptt)
114412bd3c8bSSascha Wildner {
114512bd3c8bSSascha Wildner 	struct usb_hub_descriptor hubdesc20;
114612bd3c8bSSascha Wildner 	struct usb_hub_ss_descriptor hubdesc30;
114712bd3c8bSSascha Wildner 	usb_error_t err;
114812bd3c8bSSascha Wildner 	uint8_t nports;
114912bd3c8bSSascha Wildner 	uint8_t tt;
115012bd3c8bSSascha Wildner 
115112bd3c8bSSascha Wildner 	if (udev->ddesc.bDeviceClass != UDCLASS_HUB)
115212bd3c8bSSascha Wildner 		return (USB_ERR_INVAL);
115312bd3c8bSSascha Wildner 
115412bd3c8bSSascha Wildner 	nports = 0;
115512bd3c8bSSascha Wildner 	tt = 0;
115612bd3c8bSSascha Wildner 
115712bd3c8bSSascha Wildner 	switch (udev->speed) {
115812bd3c8bSSascha Wildner 	case USB_SPEED_LOW:
115912bd3c8bSSascha Wildner 	case USB_SPEED_FULL:
116012bd3c8bSSascha Wildner 	case USB_SPEED_HIGH:
116112bd3c8bSSascha Wildner 		/* assuming that there is one port */
116212bd3c8bSSascha Wildner 		err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, 1);
116312bd3c8bSSascha Wildner 		if (err) {
116412bd3c8bSSascha Wildner 			DPRINTFN(0, "getting USB 2.0 HUB descriptor failed,"
116512bd3c8bSSascha Wildner 			    "error=%s\n", usbd_errstr(err));
116612bd3c8bSSascha Wildner 			break;
116712bd3c8bSSascha Wildner 		}
116812bd3c8bSSascha Wildner 		nports = hubdesc20.bNbrPorts;
116912bd3c8bSSascha Wildner 		if (nports > 127)
117012bd3c8bSSascha Wildner 			nports = 127;
117112bd3c8bSSascha Wildner 
117212bd3c8bSSascha Wildner 		if (udev->speed == USB_SPEED_HIGH)
117312bd3c8bSSascha Wildner 			tt = (UGETW(hubdesc20.wHubCharacteristics) >> 5) & 3;
117412bd3c8bSSascha Wildner 		break;
117512bd3c8bSSascha Wildner 
117612bd3c8bSSascha Wildner 	case USB_SPEED_SUPER:
117712bd3c8bSSascha Wildner 		err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, 1);
117812bd3c8bSSascha Wildner 		if (err) {
117912bd3c8bSSascha Wildner 			DPRINTFN(0, "Getting USB 3.0 HUB descriptor failed,"
118012bd3c8bSSascha Wildner 			    "error=%s\n", usbd_errstr(err));
118112bd3c8bSSascha Wildner 			break;
118212bd3c8bSSascha Wildner 		}
118312bd3c8bSSascha Wildner 		nports = hubdesc30.bNbrPorts;
118412bd3c8bSSascha Wildner 		if (nports > 16)
118512bd3c8bSSascha Wildner 			nports = 16;
118612bd3c8bSSascha Wildner 		break;
118712bd3c8bSSascha Wildner 
118812bd3c8bSSascha Wildner 	default:
118912bd3c8bSSascha Wildner 		err = USB_ERR_INVAL;
119012bd3c8bSSascha Wildner 		break;
119112bd3c8bSSascha Wildner 	}
119212bd3c8bSSascha Wildner 
119312bd3c8bSSascha Wildner 	if (pnports != NULL)
119412bd3c8bSSascha Wildner 		*pnports = nports;
119512bd3c8bSSascha Wildner 
119612bd3c8bSSascha Wildner 	if (ptt != NULL)
119712bd3c8bSSascha Wildner 		*ptt = tt;
119812bd3c8bSSascha Wildner 
119912bd3c8bSSascha Wildner 	return (err);
120012bd3c8bSSascha Wildner }
120112bd3c8bSSascha Wildner 
120212bd3c8bSSascha Wildner static int
uhub_attach(device_t dev)120312bd3c8bSSascha Wildner uhub_attach(device_t dev)
120412bd3c8bSSascha Wildner {
120512bd3c8bSSascha Wildner 	struct uhub_softc *sc = device_get_softc(dev);
120612bd3c8bSSascha Wildner 	struct usb_attach_arg *uaa = device_get_ivars(dev);
120712bd3c8bSSascha Wildner 	struct usb_device *udev = uaa->device;
120812bd3c8bSSascha Wildner 	struct usb_device *parent_hub = udev->parent_hub;
120912bd3c8bSSascha Wildner 	struct usb_hub *hub;
121012bd3c8bSSascha Wildner 	struct usb_hub_descriptor hubdesc20;
121112bd3c8bSSascha Wildner 	struct usb_hub_ss_descriptor hubdesc30;
121245f67c02SMarkus Pfeiffer #if USB_HAVE_DISABLE_ENUM
121345f67c02SMarkus Pfeiffer 	struct sysctl_ctx_list *sysctl_ctx;
121445f67c02SMarkus Pfeiffer 	struct sysctl_oid *sysctl_tree;
121545f67c02SMarkus Pfeiffer #endif
121612bd3c8bSSascha Wildner 	uint16_t pwrdly;
121757bed822SMarkus Pfeiffer 	uint16_t nports;
121812bd3c8bSSascha Wildner 	uint8_t x;
121912bd3c8bSSascha Wildner 	uint8_t portno;
122012bd3c8bSSascha Wildner 	uint8_t removable;
122112bd3c8bSSascha Wildner 	uint8_t iface_index;
122212bd3c8bSSascha Wildner 	usb_error_t err;
122312bd3c8bSSascha Wildner 
122412bd3c8bSSascha Wildner 	sc->sc_udev = udev;
122512bd3c8bSSascha Wildner 	sc->sc_dev = dev;
122612bd3c8bSSascha Wildner 
1227722d05c3SSascha Wildner 	lockinit(&sc->sc_lock, "USB HUB mutex", 0, 0);
122812bd3c8bSSascha Wildner 
122912bd3c8bSSascha Wildner 	device_set_usb_desc(dev);
123012bd3c8bSSascha Wildner 
123112bd3c8bSSascha Wildner 	DPRINTFN(2, "depth=%d selfpowered=%d, parent=%p, "
123212bd3c8bSSascha Wildner 	    "parent->selfpowered=%d\n",
123312bd3c8bSSascha Wildner 	    udev->depth,
123412bd3c8bSSascha Wildner 	    udev->flags.self_powered,
123512bd3c8bSSascha Wildner 	    parent_hub,
123612bd3c8bSSascha Wildner 	    parent_hub ?
123712bd3c8bSSascha Wildner 	    parent_hub->flags.self_powered : 0);
123812bd3c8bSSascha Wildner 
123912bd3c8bSSascha Wildner 	if (uhub_is_too_deep(udev)) {
124012bd3c8bSSascha Wildner 		DPRINTFN(0, "HUB at depth %d, "
124112bd3c8bSSascha Wildner 		    "exceeds maximum. HUB ignored\n", (int)udev->depth);
124212bd3c8bSSascha Wildner 		goto error;
124312bd3c8bSSascha Wildner 	}
124412bd3c8bSSascha Wildner 
124512bd3c8bSSascha Wildner 	if (!udev->flags.self_powered && parent_hub &&
124612bd3c8bSSascha Wildner 	    !parent_hub->flags.self_powered) {
124712bd3c8bSSascha Wildner 		DPRINTFN(0, "Bus powered HUB connected to "
124812bd3c8bSSascha Wildner 		    "bus powered HUB. HUB ignored\n");
124912bd3c8bSSascha Wildner 		goto error;
125012bd3c8bSSascha Wildner 	}
12515e41ab93SMarkus Pfeiffer 
12525e41ab93SMarkus Pfeiffer 	if (UHUB_IS_MULTI_TT(sc)) {
12535e41ab93SMarkus Pfeiffer 		err = usbd_set_alt_interface_index(udev, 0, 1);
12545e41ab93SMarkus Pfeiffer 		if (err) {
12555e41ab93SMarkus Pfeiffer 			device_printf(dev, "MTT could not be enabled\n");
12565e41ab93SMarkus Pfeiffer 			goto error;
12575e41ab93SMarkus Pfeiffer 		}
12585e41ab93SMarkus Pfeiffer 		device_printf(dev, "MTT enabled\n");
12595e41ab93SMarkus Pfeiffer 	}
12605e41ab93SMarkus Pfeiffer 
126112bd3c8bSSascha Wildner 	/* get HUB descriptor */
126212bd3c8bSSascha Wildner 
126312bd3c8bSSascha Wildner 	DPRINTFN(2, "Getting HUB descriptor\n");
126412bd3c8bSSascha Wildner 
126512bd3c8bSSascha Wildner 	switch (udev->speed) {
126612bd3c8bSSascha Wildner 	case USB_SPEED_LOW:
126712bd3c8bSSascha Wildner 	case USB_SPEED_FULL:
126812bd3c8bSSascha Wildner 	case USB_SPEED_HIGH:
126912bd3c8bSSascha Wildner 		/* assuming that there is one port */
127012bd3c8bSSascha Wildner 		err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, 1);
127112bd3c8bSSascha Wildner 		if (err) {
127212bd3c8bSSascha Wildner 			DPRINTFN(0, "getting USB 2.0 HUB descriptor failed,"
127312bd3c8bSSascha Wildner 			    "error=%s\n", usbd_errstr(err));
127412bd3c8bSSascha Wildner 			goto error;
127512bd3c8bSSascha Wildner 		}
127612bd3c8bSSascha Wildner 		/* get number of ports */
127712bd3c8bSSascha Wildner 		nports = hubdesc20.bNbrPorts;
127812bd3c8bSSascha Wildner 
127912bd3c8bSSascha Wildner 		/* get power delay */
128012bd3c8bSSascha Wildner 		pwrdly = ((hubdesc20.bPwrOn2PwrGood * UHD_PWRON_FACTOR) +
12815e41ab93SMarkus Pfeiffer 		    usb_extra_power_up_time);
128212bd3c8bSSascha Wildner 
128312bd3c8bSSascha Wildner 		/* get complete HUB descriptor */
128412bd3c8bSSascha Wildner 		if (nports >= 8) {
128512bd3c8bSSascha Wildner 			/* check number of ports */
128612bd3c8bSSascha Wildner 			if (nports > 127) {
128712bd3c8bSSascha Wildner 				DPRINTFN(0, "Invalid number of USB 2.0 ports,"
128812bd3c8bSSascha Wildner 				    "error=%s\n", usbd_errstr(err));
128912bd3c8bSSascha Wildner 				goto error;
129012bd3c8bSSascha Wildner 			}
129112bd3c8bSSascha Wildner 			/* get complete HUB descriptor */
129212bd3c8bSSascha Wildner 			err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, nports);
129312bd3c8bSSascha Wildner 
129412bd3c8bSSascha Wildner 			if (err) {
129512bd3c8bSSascha Wildner 				DPRINTFN(0, "Getting USB 2.0 HUB descriptor failed,"
129612bd3c8bSSascha Wildner 				    "error=%s\n", usbd_errstr(err));
129712bd3c8bSSascha Wildner 				goto error;
129812bd3c8bSSascha Wildner 			}
129912bd3c8bSSascha Wildner 			if (hubdesc20.bNbrPorts != nports) {
130012bd3c8bSSascha Wildner 				DPRINTFN(0, "Number of ports changed\n");
130112bd3c8bSSascha Wildner 				goto error;
130212bd3c8bSSascha Wildner 			}
130312bd3c8bSSascha Wildner 		}
130412bd3c8bSSascha Wildner 		break;
130512bd3c8bSSascha Wildner 	case USB_SPEED_SUPER:
130612bd3c8bSSascha Wildner 		if (udev->parent_hub != NULL) {
130712bd3c8bSSascha Wildner 			err = usbd_req_set_hub_depth(udev, NULL,
130812bd3c8bSSascha Wildner 			    udev->depth - 1);
130912bd3c8bSSascha Wildner 			if (err) {
131012bd3c8bSSascha Wildner 				DPRINTFN(0, "Setting USB 3.0 HUB depth failed,"
131112bd3c8bSSascha Wildner 				    "error=%s\n", usbd_errstr(err));
131212bd3c8bSSascha Wildner 				goto error;
131312bd3c8bSSascha Wildner 			}
131412bd3c8bSSascha Wildner 		}
131512bd3c8bSSascha Wildner 		err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, 1);
131612bd3c8bSSascha Wildner 		if (err) {
131712bd3c8bSSascha Wildner 			DPRINTFN(0, "Getting USB 3.0 HUB descriptor failed,"
131812bd3c8bSSascha Wildner 			    "error=%s\n", usbd_errstr(err));
131912bd3c8bSSascha Wildner 			goto error;
132012bd3c8bSSascha Wildner 		}
132112bd3c8bSSascha Wildner 		/* get number of ports */
132212bd3c8bSSascha Wildner 		nports = hubdesc30.bNbrPorts;
132312bd3c8bSSascha Wildner 
132412bd3c8bSSascha Wildner 		/* get power delay */
132512bd3c8bSSascha Wildner 		pwrdly = ((hubdesc30.bPwrOn2PwrGood * UHD_PWRON_FACTOR) +
13265e41ab93SMarkus Pfeiffer 		    usb_extra_power_up_time);
132712bd3c8bSSascha Wildner 
132812bd3c8bSSascha Wildner 		/* get complete HUB descriptor */
132912bd3c8bSSascha Wildner 		if (nports >= 8) {
133012bd3c8bSSascha Wildner 			/* check number of ports */
133112bd3c8bSSascha Wildner 			if (nports > ((udev->parent_hub != NULL) ? 15 : 127)) {
133212bd3c8bSSascha Wildner 				DPRINTFN(0, "Invalid number of USB 3.0 ports,"
133312bd3c8bSSascha Wildner 				    "error=%s\n", usbd_errstr(err));
133412bd3c8bSSascha Wildner 				goto error;
133512bd3c8bSSascha Wildner 			}
133612bd3c8bSSascha Wildner 			/* get complete HUB descriptor */
133712bd3c8bSSascha Wildner 			err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, nports);
133812bd3c8bSSascha Wildner 
133912bd3c8bSSascha Wildner 			if (err) {
134012bd3c8bSSascha Wildner 				DPRINTFN(0, "Getting USB 2.0 HUB descriptor failed,"
134112bd3c8bSSascha Wildner 				    "error=%s\n", usbd_errstr(err));
134212bd3c8bSSascha Wildner 				goto error;
134312bd3c8bSSascha Wildner 			}
134412bd3c8bSSascha Wildner 			if (hubdesc30.bNbrPorts != nports) {
134512bd3c8bSSascha Wildner 				DPRINTFN(0, "Number of ports changed\n");
134612bd3c8bSSascha Wildner 				goto error;
134712bd3c8bSSascha Wildner 			}
134812bd3c8bSSascha Wildner 		}
134912bd3c8bSSascha Wildner 		break;
135012bd3c8bSSascha Wildner 	default:
135112bd3c8bSSascha Wildner 		DPRINTF("Assuming HUB has only one port\n");
135212bd3c8bSSascha Wildner 		/* default number of ports */
135312bd3c8bSSascha Wildner 		nports = 1;
135412bd3c8bSSascha Wildner 		/* default power delay */
13555e41ab93SMarkus Pfeiffer 		pwrdly = ((10 * UHD_PWRON_FACTOR) + usb_extra_power_up_time);
135612bd3c8bSSascha Wildner 		break;
135712bd3c8bSSascha Wildner 	}
135812bd3c8bSSascha Wildner 	if (nports == 0) {
135912bd3c8bSSascha Wildner 		DPRINTFN(0, "portless HUB\n");
136012bd3c8bSSascha Wildner 		goto error;
136112bd3c8bSSascha Wildner 	}
136257bed822SMarkus Pfeiffer 	if (nports > USB_MAX_PORTS) {
136357bed822SMarkus Pfeiffer 		DPRINTF("Port limit exceeded\n");
136457bed822SMarkus Pfeiffer 		goto error;
136557bed822SMarkus Pfeiffer 	}
136657bed822SMarkus Pfeiffer #if (USB_HAVE_FIXED_PORT == 0)
1367722d05c3SSascha Wildner 	hub = kmalloc(sizeof(hub[0]) + (sizeof(hub->ports[0]) * nports),
136812bd3c8bSSascha Wildner 	    M_USBDEV, M_WAITOK | M_ZERO);
136912bd3c8bSSascha Wildner 
137057bed822SMarkus Pfeiffer 	if (hub == NULL)
137157bed822SMarkus Pfeiffer 		goto error;
137257bed822SMarkus Pfeiffer #else
137357bed822SMarkus Pfeiffer 	hub = &sc->sc_hub;
137457bed822SMarkus Pfeiffer #endif
137512bd3c8bSSascha Wildner 	udev->hub = hub;
137612bd3c8bSSascha Wildner 
137712bd3c8bSSascha Wildner 	/* initialize HUB structure */
137812bd3c8bSSascha Wildner 	hub->hubsoftc = sc;
137912bd3c8bSSascha Wildner 	hub->explore = &uhub_explore;
138012bd3c8bSSascha Wildner 	hub->nports = nports;
138112bd3c8bSSascha Wildner 	hub->hubudev = udev;
138245f67c02SMarkus Pfeiffer #if USB_HAVE_TT_SUPPORT
138345f67c02SMarkus Pfeiffer 	hub->tt_msg[0].hdr.pm_callback = &uhub_reset_tt_proc;
138445f67c02SMarkus Pfeiffer 	hub->tt_msg[0].udev = udev;
138545f67c02SMarkus Pfeiffer 	hub->tt_msg[1].hdr.pm_callback = &uhub_reset_tt_proc;
138645f67c02SMarkus Pfeiffer 	hub->tt_msg[1].udev = udev;
138745f67c02SMarkus Pfeiffer #endif
138812bd3c8bSSascha Wildner 	/* if self powered hub, give ports maximum current */
138912bd3c8bSSascha Wildner 	if (udev->flags.self_powered) {
139012bd3c8bSSascha Wildner 		hub->portpower = USB_MAX_POWER;
139112bd3c8bSSascha Wildner 	} else {
139212bd3c8bSSascha Wildner 		hub->portpower = USB_MIN_POWER;
139312bd3c8bSSascha Wildner 	}
139412bd3c8bSSascha Wildner 
139512bd3c8bSSascha Wildner 	/* set up interrupt pipe */
139612bd3c8bSSascha Wildner 	iface_index = 0;
139712bd3c8bSSascha Wildner 	if (udev->parent_hub == NULL) {
139812bd3c8bSSascha Wildner 		/* root HUB is special */
139912bd3c8bSSascha Wildner 		err = 0;
140012bd3c8bSSascha Wildner 	} else {
140112bd3c8bSSascha Wildner 		/* normal HUB */
140212bd3c8bSSascha Wildner 		err = usbd_transfer_setup(udev, &iface_index, sc->sc_xfer,
1403722d05c3SSascha Wildner 		    uhub_config, UHUB_N_TRANSFER, sc, &sc->sc_lock);
140412bd3c8bSSascha Wildner 	}
140512bd3c8bSSascha Wildner 	if (err) {
140612bd3c8bSSascha Wildner 		DPRINTFN(0, "cannot setup interrupt transfer, "
140712bd3c8bSSascha Wildner 		    "errstr=%s\n", usbd_errstr(err));
140812bd3c8bSSascha Wildner 		goto error;
140912bd3c8bSSascha Wildner 	}
141012bd3c8bSSascha Wildner 	/* wait with power off for a while */
141112bd3c8bSSascha Wildner 	usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_POWER_DOWN_TIME));
141212bd3c8bSSascha Wildner 
141345f67c02SMarkus Pfeiffer #if USB_HAVE_DISABLE_ENUM
141445f67c02SMarkus Pfeiffer 	/* Add device sysctls */
141545f67c02SMarkus Pfeiffer 
141645f67c02SMarkus Pfeiffer 	sysctl_ctx = device_get_sysctl_ctx(dev);
141745f67c02SMarkus Pfeiffer 	sysctl_tree = device_get_sysctl_tree(dev);
141845f67c02SMarkus Pfeiffer 
141945f67c02SMarkus Pfeiffer 	if (sysctl_ctx != NULL && sysctl_tree != NULL) {
142045f67c02SMarkus Pfeiffer 		(void) SYSCTL_ADD_INT(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree),
14215ea2ce29SSascha Wildner 		    OID_AUTO, "disable_enumeration", CTLFLAG_RW,
142245f67c02SMarkus Pfeiffer 		    &sc->sc_disable_enumeration, 0,
142345f67c02SMarkus Pfeiffer 		    "Set to disable enumeration on this USB HUB.");
142445f67c02SMarkus Pfeiffer 
142545f67c02SMarkus Pfeiffer 		(void) SYSCTL_ADD_INT(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree),
14265ea2ce29SSascha Wildner 		    OID_AUTO, "disable_port_power", CTLFLAG_RW,
142745f67c02SMarkus Pfeiffer 		    &sc->sc_disable_port_power, 0,
142845f67c02SMarkus Pfeiffer 		    "Set to disable USB port power on this USB HUB.");
142945f67c02SMarkus Pfeiffer 	}
143045f67c02SMarkus Pfeiffer #endif
143112bd3c8bSSascha Wildner 	/*
143212bd3c8bSSascha Wildner 	 * To have the best chance of success we do things in the exact same
143312bd3c8bSSascha Wildner 	 * order as Windoze98.  This should not be necessary, but some
143412bd3c8bSSascha Wildner 	 * devices do not follow the USB specs to the letter.
143512bd3c8bSSascha Wildner 	 *
143612bd3c8bSSascha Wildner 	 * These are the events on the bus when a hub is attached:
143712bd3c8bSSascha Wildner 	 *  Get device and config descriptors (see attach code)
143812bd3c8bSSascha Wildner 	 *  Get hub descriptor (see above)
143912bd3c8bSSascha Wildner 	 *  For all ports
144012bd3c8bSSascha Wildner 	 *     turn on power
144112bd3c8bSSascha Wildner 	 *     wait for power to become stable
144212bd3c8bSSascha Wildner 	 * (all below happens in explore code)
144312bd3c8bSSascha Wildner 	 *  For all ports
144412bd3c8bSSascha Wildner 	 *     clear C_PORT_CONNECTION
144512bd3c8bSSascha Wildner 	 *  For all ports
144612bd3c8bSSascha Wildner 	 *     get port status
144712bd3c8bSSascha Wildner 	 *     if device connected
144812bd3c8bSSascha Wildner 	 *        wait 100 ms
144912bd3c8bSSascha Wildner 	 *        turn on reset
145012bd3c8bSSascha Wildner 	 *        wait
145112bd3c8bSSascha Wildner 	 *        clear C_PORT_RESET
145212bd3c8bSSascha Wildner 	 *        get port status
145312bd3c8bSSascha Wildner 	 *        proceed with device attachment
145412bd3c8bSSascha Wildner 	 */
145512bd3c8bSSascha Wildner 
145612bd3c8bSSascha Wildner 	/* XXX should check for none, individual, or ganged power? */
145712bd3c8bSSascha Wildner 
145812bd3c8bSSascha Wildner 	removable = 0;
145912bd3c8bSSascha Wildner 
146012bd3c8bSSascha Wildner 	for (x = 0; x != nports; x++) {
146112bd3c8bSSascha Wildner 		/* set up data structures */
146212bd3c8bSSascha Wildner 		struct usb_port *up = hub->ports + x;
146312bd3c8bSSascha Wildner 
146412bd3c8bSSascha Wildner 		up->device_index = 0;
146512bd3c8bSSascha Wildner 		up->restartcnt = 0;
146612bd3c8bSSascha Wildner 		portno = x + 1;
146712bd3c8bSSascha Wildner 
146812bd3c8bSSascha Wildner 		/* check if port is removable */
146912bd3c8bSSascha Wildner 		switch (udev->speed) {
147012bd3c8bSSascha Wildner 		case USB_SPEED_LOW:
147112bd3c8bSSascha Wildner 		case USB_SPEED_FULL:
147212bd3c8bSSascha Wildner 		case USB_SPEED_HIGH:
147312bd3c8bSSascha Wildner 			if (!UHD_NOT_REMOV(&hubdesc20, portno))
147412bd3c8bSSascha Wildner 				removable++;
147512bd3c8bSSascha Wildner 			break;
147612bd3c8bSSascha Wildner 		case USB_SPEED_SUPER:
147712bd3c8bSSascha Wildner 			if (!UHD_NOT_REMOV(&hubdesc30, portno))
147812bd3c8bSSascha Wildner 				removable++;
147912bd3c8bSSascha Wildner 			break;
148012bd3c8bSSascha Wildner 		default:
148112bd3c8bSSascha Wildner 			DPRINTF("Assuming removable port\n");
148212bd3c8bSSascha Wildner 			removable++;
148312bd3c8bSSascha Wildner 			break;
148412bd3c8bSSascha Wildner 		}
148545f67c02SMarkus Pfeiffer 		if (err == 0) {
148645f67c02SMarkus Pfeiffer #if USB_HAVE_DISABLE_ENUM
148745f67c02SMarkus Pfeiffer 			/* check if we should disable USB port power or not */
148845f67c02SMarkus Pfeiffer 			if (usb_disable_port_power != 0 ||
148945f67c02SMarkus Pfeiffer 			    sc->sc_disable_port_power != 0) {
149045f67c02SMarkus Pfeiffer 				/* turn the power off */
149145f67c02SMarkus Pfeiffer 				DPRINTFN(2, "Turning port %d power off\n", portno);
149245f67c02SMarkus Pfeiffer 				err = usbd_req_clear_port_feature(udev, NULL,
149345f67c02SMarkus Pfeiffer 				    portno, UHF_PORT_POWER);
149445f67c02SMarkus Pfeiffer 			} else {
149545f67c02SMarkus Pfeiffer #endif
149612bd3c8bSSascha Wildner 				/* turn the power on */
149745f67c02SMarkus Pfeiffer 				DPRINTFN(2, "Turning port %d power on\n", portno);
149812bd3c8bSSascha Wildner 				err = usbd_req_set_port_feature(udev, NULL,
149912bd3c8bSSascha Wildner 				    portno, UHF_PORT_POWER);
150045f67c02SMarkus Pfeiffer #if USB_HAVE_DISABLE_ENUM
150112bd3c8bSSascha Wildner 			}
150245f67c02SMarkus Pfeiffer #endif
150345f67c02SMarkus Pfeiffer 		}
150445f67c02SMarkus Pfeiffer 		if (err != 0) {
150545f67c02SMarkus Pfeiffer 			DPRINTFN(0, "port %d power on or off failed, %s\n",
150612bd3c8bSSascha Wildner 			    portno, usbd_errstr(err));
150712bd3c8bSSascha Wildner 		}
150812bd3c8bSSascha Wildner 		DPRINTF("turn on port %d power\n",
150912bd3c8bSSascha Wildner 		    portno);
151012bd3c8bSSascha Wildner 
151112bd3c8bSSascha Wildner 		/* wait for stable power */
151212bd3c8bSSascha Wildner 		usb_pause_mtx(NULL, USB_MS_TO_TICKS(pwrdly));
151312bd3c8bSSascha Wildner 	}
151412bd3c8bSSascha Wildner 
151512bd3c8bSSascha Wildner 	device_printf(dev, "%d port%s with %d "
151612bd3c8bSSascha Wildner 	    "removable, %s powered\n", nports, (nports != 1) ? "s" : "",
151712bd3c8bSSascha Wildner 	    removable, udev->flags.self_powered ? "self" : "bus");
151812bd3c8bSSascha Wildner 
151912bd3c8bSSascha Wildner 	/* Start the interrupt endpoint, if any */
152012bd3c8bSSascha Wildner 
152112bd3c8bSSascha Wildner 	if (sc->sc_xfer[0] != NULL) {
1522722d05c3SSascha Wildner 		lockmgr(&sc->sc_lock, LK_EXCLUSIVE);
152312bd3c8bSSascha Wildner 		usbd_transfer_start(sc->sc_xfer[0]);
1524722d05c3SSascha Wildner 		lockmgr(&sc->sc_lock, LK_RELEASE);
152512bd3c8bSSascha Wildner 	}
152612bd3c8bSSascha Wildner 
152712bd3c8bSSascha Wildner 	/* Enable automatic power save on all USB HUBs */
152812bd3c8bSSascha Wildner 
152912bd3c8bSSascha Wildner 	usbd_set_power_mode(udev, USB_POWER_MODE_SAVE);
153012bd3c8bSSascha Wildner 
153112bd3c8bSSascha Wildner 	return (0);
153212bd3c8bSSascha Wildner 
153312bd3c8bSSascha Wildner error:
153412bd3c8bSSascha Wildner 	usbd_transfer_unsetup(sc->sc_xfer, UHUB_N_TRANSFER);
153512bd3c8bSSascha Wildner 
153612bd3c8bSSascha Wildner 	if (udev->hub) {
153757bed822SMarkus Pfeiffer #if (USB_HAVE_FIXED_PORT == 0)
1538722d05c3SSascha Wildner 		kfree(udev->hub, M_USBDEV);
153957bed822SMarkus Pfeiffer #endif
154012bd3c8bSSascha Wildner 		udev->hub = NULL;
154112bd3c8bSSascha Wildner 	}
1542722d05c3SSascha Wildner 	lockuninit(&sc->sc_lock);
154312bd3c8bSSascha Wildner 
154412bd3c8bSSascha Wildner 	return (ENXIO);
154512bd3c8bSSascha Wildner }
154612bd3c8bSSascha Wildner 
154712bd3c8bSSascha Wildner /*
154812bd3c8bSSascha Wildner  * Called from process context when the hub is gone.
154912bd3c8bSSascha Wildner  * Detach all devices on active ports.
155012bd3c8bSSascha Wildner  */
155112bd3c8bSSascha Wildner static int
uhub_detach(device_t dev)155212bd3c8bSSascha Wildner uhub_detach(device_t dev)
155312bd3c8bSSascha Wildner {
155412bd3c8bSSascha Wildner 	struct uhub_softc *sc = device_get_softc(dev);
155512bd3c8bSSascha Wildner 	struct usb_hub *hub = sc->sc_udev->hub;
155645f67c02SMarkus Pfeiffer 	struct usb_bus *bus = sc->sc_udev->bus;
155712bd3c8bSSascha Wildner 	struct usb_device *child;
155812bd3c8bSSascha Wildner 	uint8_t x;
155912bd3c8bSSascha Wildner 
156012bd3c8bSSascha Wildner 	if (hub == NULL)		/* must be partially working */
156112bd3c8bSSascha Wildner 		return (0);
156212bd3c8bSSascha Wildner 
156312bd3c8bSSascha Wildner 	/* Make sure interrupt transfer is gone. */
156412bd3c8bSSascha Wildner 	usbd_transfer_unsetup(sc->sc_xfer, UHUB_N_TRANSFER);
156512bd3c8bSSascha Wildner 
156612bd3c8bSSascha Wildner 	/* Detach all ports */
156712bd3c8bSSascha Wildner 	for (x = 0; x != hub->nports; x++) {
156812bd3c8bSSascha Wildner 
156945f67c02SMarkus Pfeiffer 		child = usb_bus_port_get_device(bus, hub->ports + x);
157012bd3c8bSSascha Wildner 
157112bd3c8bSSascha Wildner 		if (child == NULL) {
157212bd3c8bSSascha Wildner 			continue;
157312bd3c8bSSascha Wildner 		}
157412bd3c8bSSascha Wildner 
157512bd3c8bSSascha Wildner 		/*
157612bd3c8bSSascha Wildner 		 * Free USB device and all subdevices, if any.
157712bd3c8bSSascha Wildner 		 */
157812bd3c8bSSascha Wildner 		usb_free_device(child, 0);
157912bd3c8bSSascha Wildner 	}
158012bd3c8bSSascha Wildner 
158145f67c02SMarkus Pfeiffer #if USB_HAVE_TT_SUPPORT
158245f67c02SMarkus Pfeiffer 	/* Make sure our TT messages are not queued anywhere */
158345f67c02SMarkus Pfeiffer 	USB_BUS_LOCK(bus);
158445f67c02SMarkus Pfeiffer 	usb_proc_mwait(USB_BUS_NON_GIANT_PROC(bus),
158545f67c02SMarkus Pfeiffer 	    &hub->tt_msg[0], &hub->tt_msg[1]);
158645f67c02SMarkus Pfeiffer 	USB_BUS_UNLOCK(bus);
158745f67c02SMarkus Pfeiffer #endif
158845f67c02SMarkus Pfeiffer 
158957bed822SMarkus Pfeiffer #if (USB_HAVE_FIXED_PORT == 0)
1590722d05c3SSascha Wildner 	kfree(hub, M_USBDEV);
159157bed822SMarkus Pfeiffer #endif
159212bd3c8bSSascha Wildner 	sc->sc_udev->hub = NULL;
159312bd3c8bSSascha Wildner 
1594722d05c3SSascha Wildner 	lockuninit(&sc->sc_lock);
159512bd3c8bSSascha Wildner 
159612bd3c8bSSascha Wildner 	return (0);
159712bd3c8bSSascha Wildner }
159812bd3c8bSSascha Wildner 
159912bd3c8bSSascha Wildner static int
uhub_suspend(device_t dev)160012bd3c8bSSascha Wildner uhub_suspend(device_t dev)
160112bd3c8bSSascha Wildner {
160212bd3c8bSSascha Wildner 	DPRINTF("\n");
160312bd3c8bSSascha Wildner 	/* Sub-devices are not suspended here! */
160412bd3c8bSSascha Wildner 	return (0);
160512bd3c8bSSascha Wildner }
160612bd3c8bSSascha Wildner 
160712bd3c8bSSascha Wildner static int
uhub_resume(device_t dev)160812bd3c8bSSascha Wildner uhub_resume(device_t dev)
160912bd3c8bSSascha Wildner {
161012bd3c8bSSascha Wildner 	DPRINTF("\n");
161112bd3c8bSSascha Wildner 	/* Sub-devices are not resumed here! */
161212bd3c8bSSascha Wildner 	return (0);
161312bd3c8bSSascha Wildner }
161412bd3c8bSSascha Wildner 
161512bd3c8bSSascha Wildner static void
uhub_driver_added(device_t dev,driver_t * driver)161612bd3c8bSSascha Wildner uhub_driver_added(device_t dev, driver_t *driver)
161712bd3c8bSSascha Wildner {
161812bd3c8bSSascha Wildner 	usb_needs_explore_all();
161912bd3c8bSSascha Wildner }
162012bd3c8bSSascha Wildner 
162112bd3c8bSSascha Wildner struct hub_result {
162212bd3c8bSSascha Wildner 	struct usb_device *udev;
162312bd3c8bSSascha Wildner 	uint8_t	portno;
162412bd3c8bSSascha Wildner 	uint8_t	iface_index;
162512bd3c8bSSascha Wildner };
162612bd3c8bSSascha Wildner 
162712bd3c8bSSascha Wildner static void
uhub_find_iface_index(struct usb_hub * hub,device_t child,struct hub_result * res)162812bd3c8bSSascha Wildner uhub_find_iface_index(struct usb_hub *hub, device_t child,
162912bd3c8bSSascha Wildner     struct hub_result *res)
163012bd3c8bSSascha Wildner {
163112bd3c8bSSascha Wildner 	struct usb_interface *iface;
163212bd3c8bSSascha Wildner 	struct usb_device *udev;
163312bd3c8bSSascha Wildner 	uint8_t nports;
163412bd3c8bSSascha Wildner 	uint8_t x;
163512bd3c8bSSascha Wildner 	uint8_t i;
163612bd3c8bSSascha Wildner 
163712bd3c8bSSascha Wildner 	nports = hub->nports;
163812bd3c8bSSascha Wildner 	for (x = 0; x != nports; x++) {
163912bd3c8bSSascha Wildner 		udev = usb_bus_port_get_device(hub->hubudev->bus,
164012bd3c8bSSascha Wildner 		    hub->ports + x);
164112bd3c8bSSascha Wildner 		if (!udev) {
164212bd3c8bSSascha Wildner 			continue;
164312bd3c8bSSascha Wildner 		}
164412bd3c8bSSascha Wildner 		for (i = 0; i != USB_IFACE_MAX; i++) {
164512bd3c8bSSascha Wildner 			iface = usbd_get_iface(udev, i);
164612bd3c8bSSascha Wildner 			if (iface &&
164712bd3c8bSSascha Wildner 			    (iface->subdev == child)) {
164812bd3c8bSSascha Wildner 				res->iface_index = i;
164912bd3c8bSSascha Wildner 				res->udev = udev;
165012bd3c8bSSascha Wildner 				res->portno = x + 1;
165112bd3c8bSSascha Wildner 				return;
165212bd3c8bSSascha Wildner 			}
165312bd3c8bSSascha Wildner 		}
165412bd3c8bSSascha Wildner 	}
165512bd3c8bSSascha Wildner 	res->iface_index = 0;
165612bd3c8bSSascha Wildner 	res->udev = NULL;
165712bd3c8bSSascha Wildner 	res->portno = 0;
165812bd3c8bSSascha Wildner }
165912bd3c8bSSascha Wildner 
166012bd3c8bSSascha Wildner static int
uhub_child_location_string(device_t parent,device_t child,char * buf,size_t buflen)166112bd3c8bSSascha Wildner uhub_child_location_string(device_t parent, device_t child,
166212bd3c8bSSascha Wildner     char *buf, size_t buflen)
166312bd3c8bSSascha Wildner {
166412bd3c8bSSascha Wildner 	struct uhub_softc *sc;
166512bd3c8bSSascha Wildner 	struct usb_hub *hub;
166612bd3c8bSSascha Wildner 	struct hub_result res;
166712bd3c8bSSascha Wildner 
166812bd3c8bSSascha Wildner 	if (!device_is_attached(parent)) {
166912bd3c8bSSascha Wildner 		if (buflen)
167012bd3c8bSSascha Wildner 			buf[0] = 0;
167112bd3c8bSSascha Wildner 		return (0);
167212bd3c8bSSascha Wildner 	}
167312bd3c8bSSascha Wildner 
167412bd3c8bSSascha Wildner 	sc = device_get_softc(parent);
167512bd3c8bSSascha Wildner 	hub = sc->sc_udev->hub;
167612bd3c8bSSascha Wildner 
167712bd3c8bSSascha Wildner 	uhub_find_iface_index(hub, child, &res);
167812bd3c8bSSascha Wildner 	if (!res.udev) {
167912bd3c8bSSascha Wildner 		DPRINTF("device not on hub\n");
168012bd3c8bSSascha Wildner 		if (buflen) {
168112bd3c8bSSascha Wildner 			buf[0] = '\0';
168212bd3c8bSSascha Wildner 		}
168312bd3c8bSSascha Wildner 		goto done;
168412bd3c8bSSascha Wildner 	}
1685dd681da6SMatthew Dillon 	ksnprintf(buf, buflen, "bus=%u hubaddr=%u port=%u devaddr=%u"
1686dd681da6SMatthew Dillon 	    " interface=%u"
1687dd681da6SMatthew Dillon #if USB_HAVE_UGEN
1688dd681da6SMatthew Dillon 	    " ugen=%s"
1689dd681da6SMatthew Dillon #endif
1690dd681da6SMatthew Dillon 	    , device_get_unit(res.udev->bus->bdev)
1691dd681da6SMatthew Dillon 	    , (res.udev->parent_hub != NULL) ?
1692dd681da6SMatthew Dillon 	    res.udev->parent_hub->device_index : 0
1693dd681da6SMatthew Dillon 	    , res.portno, res.udev->device_index, res.iface_index
1694dd681da6SMatthew Dillon #if USB_HAVE_UGEN
1695dd681da6SMatthew Dillon 	    , res.udev->ugen_name
1696dd681da6SMatthew Dillon #endif
1697dd681da6SMatthew Dillon 	    );
169812bd3c8bSSascha Wildner done:
169912bd3c8bSSascha Wildner 	return (0);
170012bd3c8bSSascha Wildner }
170112bd3c8bSSascha Wildner 
170212bd3c8bSSascha Wildner static int
uhub_child_pnpinfo_string(device_t parent,device_t child,char * buf,size_t buflen)170312bd3c8bSSascha Wildner uhub_child_pnpinfo_string(device_t parent, device_t child,
170412bd3c8bSSascha Wildner     char *buf, size_t buflen)
170512bd3c8bSSascha Wildner {
170612bd3c8bSSascha Wildner 	struct uhub_softc *sc;
170712bd3c8bSSascha Wildner 	struct usb_hub *hub;
170812bd3c8bSSascha Wildner 	struct usb_interface *iface;
170912bd3c8bSSascha Wildner 	struct hub_result res;
171012bd3c8bSSascha Wildner 
171112bd3c8bSSascha Wildner 	if (!device_is_attached(parent)) {
171212bd3c8bSSascha Wildner 		if (buflen)
171312bd3c8bSSascha Wildner 			buf[0] = 0;
171412bd3c8bSSascha Wildner 		return (0);
171512bd3c8bSSascha Wildner 	}
171612bd3c8bSSascha Wildner 
171712bd3c8bSSascha Wildner 	sc = device_get_softc(parent);
171812bd3c8bSSascha Wildner 	hub = sc->sc_udev->hub;
171912bd3c8bSSascha Wildner 
172012bd3c8bSSascha Wildner 	uhub_find_iface_index(hub, child, &res);
172112bd3c8bSSascha Wildner 	if (!res.udev) {
172212bd3c8bSSascha Wildner 		DPRINTF("device not on hub\n");
172312bd3c8bSSascha Wildner 		if (buflen) {
172412bd3c8bSSascha Wildner 			buf[0] = '\0';
172512bd3c8bSSascha Wildner 		}
172612bd3c8bSSascha Wildner 		goto done;
172712bd3c8bSSascha Wildner 	}
172812bd3c8bSSascha Wildner 	iface = usbd_get_iface(res.udev, res.iface_index);
172912bd3c8bSSascha Wildner 	if (iface && iface->idesc) {
1730722d05c3SSascha Wildner 		ksnprintf(buf, buflen, "vendor=0x%04x product=0x%04x "
173112bd3c8bSSascha Wildner 		    "devclass=0x%02x devsubclass=0x%02x "
173212bd3c8bSSascha Wildner 		    "sernum=\"%s\" "
173312bd3c8bSSascha Wildner 		    "release=0x%04x "
173412bd3c8bSSascha Wildner 		    "mode=%s "
173512bd3c8bSSascha Wildner 		    "intclass=0x%02x intsubclass=0x%02x "
173612bd3c8bSSascha Wildner 		    "intprotocol=0x%02x" "%s%s",
173712bd3c8bSSascha Wildner 		    UGETW(res.udev->ddesc.idVendor),
173812bd3c8bSSascha Wildner 		    UGETW(res.udev->ddesc.idProduct),
173912bd3c8bSSascha Wildner 		    res.udev->ddesc.bDeviceClass,
174012bd3c8bSSascha Wildner 		    res.udev->ddesc.bDeviceSubClass,
174112bd3c8bSSascha Wildner 		    usb_get_serial(res.udev),
174212bd3c8bSSascha Wildner 		    UGETW(res.udev->ddesc.bcdDevice),
174312bd3c8bSSascha Wildner 		    (res.udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device",
174412bd3c8bSSascha Wildner 		    iface->idesc->bInterfaceClass,
174512bd3c8bSSascha Wildner 		    iface->idesc->bInterfaceSubClass,
174612bd3c8bSSascha Wildner 		    iface->idesc->bInterfaceProtocol,
174712bd3c8bSSascha Wildner 		    iface->pnpinfo ? " " : "",
174812bd3c8bSSascha Wildner 		    iface->pnpinfo ? iface->pnpinfo : "");
174912bd3c8bSSascha Wildner 	} else {
175012bd3c8bSSascha Wildner 		if (buflen) {
175112bd3c8bSSascha Wildner 			buf[0] = '\0';
175212bd3c8bSSascha Wildner 		}
175312bd3c8bSSascha Wildner 		goto done;
175412bd3c8bSSascha Wildner 	}
175512bd3c8bSSascha Wildner done:
175612bd3c8bSSascha Wildner 	return (0);
175712bd3c8bSSascha Wildner }
175812bd3c8bSSascha Wildner 
175912bd3c8bSSascha Wildner /*
176012bd3c8bSSascha Wildner  * The USB Transaction Translator:
176112bd3c8bSSascha Wildner  * ===============================
176212bd3c8bSSascha Wildner  *
176312bd3c8bSSascha Wildner  * When doing LOW- and FULL-speed USB transfers accross a HIGH-speed
176412bd3c8bSSascha Wildner  * USB HUB, bandwidth must be allocated for ISOCHRONOUS and INTERRUPT
176512bd3c8bSSascha Wildner  * USB transfers. To utilize bandwidth dynamically the "scatter and
176612bd3c8bSSascha Wildner  * gather" principle must be applied. This means that bandwidth must
176712bd3c8bSSascha Wildner  * be divided into equal parts of bandwidth. With regard to USB all
176812bd3c8bSSascha Wildner  * data is transferred in smaller packets with length
176912bd3c8bSSascha Wildner  * "wMaxPacketSize". The problem however is that "wMaxPacketSize" is
177012bd3c8bSSascha Wildner  * not a constant!
177112bd3c8bSSascha Wildner  *
177212bd3c8bSSascha Wildner  * The bandwidth scheduler which I have implemented will simply pack
177312bd3c8bSSascha Wildner  * the USB transfers back to back until there is no more space in the
177412bd3c8bSSascha Wildner  * schedule. Out of the 8 microframes which the USB 2.0 standard
177512bd3c8bSSascha Wildner  * provides, only 6 are available for non-HIGH-speed devices. I have
177612bd3c8bSSascha Wildner  * reserved the first 4 microframes for ISOCHRONOUS transfers. The
177712bd3c8bSSascha Wildner  * last 2 microframes I have reserved for INTERRUPT transfers. Without
177812bd3c8bSSascha Wildner  * this division, it is very difficult to allocate and free bandwidth
177912bd3c8bSSascha Wildner  * dynamically.
178012bd3c8bSSascha Wildner  *
178112bd3c8bSSascha Wildner  * NOTE about the Transaction Translator in USB HUBs:
178212bd3c8bSSascha Wildner  *
178312bd3c8bSSascha Wildner  * USB HUBs have a very simple Transaction Translator, that will
178412bd3c8bSSascha Wildner  * simply pipeline all the SPLIT transactions. That means that the
178512bd3c8bSSascha Wildner  * transactions will be executed in the order they are queued!
178612bd3c8bSSascha Wildner  *
178712bd3c8bSSascha Wildner  */
178812bd3c8bSSascha Wildner 
178912bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
179012bd3c8bSSascha Wildner  *	usb_intr_find_best_slot
179112bd3c8bSSascha Wildner  *
179212bd3c8bSSascha Wildner  * Return value:
179312bd3c8bSSascha Wildner  *   The best Transaction Translation slot for an interrupt endpoint.
179412bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
179512bd3c8bSSascha Wildner static uint8_t
usb_intr_find_best_slot(usb_size_t * ptr,uint8_t start,uint8_t end,uint8_t mask)179612bd3c8bSSascha Wildner usb_intr_find_best_slot(usb_size_t *ptr, uint8_t start,
179712bd3c8bSSascha Wildner     uint8_t end, uint8_t mask)
179812bd3c8bSSascha Wildner {
17995e41ab93SMarkus Pfeiffer 	usb_size_t min = (usb_size_t)-1;
180012bd3c8bSSascha Wildner 	usb_size_t sum;
180112bd3c8bSSascha Wildner 	uint8_t x;
180212bd3c8bSSascha Wildner 	uint8_t y;
180312bd3c8bSSascha Wildner 	uint8_t z;
180412bd3c8bSSascha Wildner 
180512bd3c8bSSascha Wildner 	y = 0;
180612bd3c8bSSascha Wildner 
180712bd3c8bSSascha Wildner 	/* find the last slot with lesser used bandwidth */
180812bd3c8bSSascha Wildner 
180912bd3c8bSSascha Wildner 	for (x = start; x < end; x++) {
181012bd3c8bSSascha Wildner 
181112bd3c8bSSascha Wildner 		sum = 0;
181212bd3c8bSSascha Wildner 
181312bd3c8bSSascha Wildner 		/* compute sum of bandwidth */
181412bd3c8bSSascha Wildner 		for (z = x; z < end; z++) {
181512bd3c8bSSascha Wildner 			if (mask & (1U << (z - x)))
181612bd3c8bSSascha Wildner 				sum += ptr[z];
181712bd3c8bSSascha Wildner 		}
181812bd3c8bSSascha Wildner 
181912bd3c8bSSascha Wildner 		/* check if the current multi-slot is more optimal */
182012bd3c8bSSascha Wildner 		if (min >= sum) {
182112bd3c8bSSascha Wildner 			min = sum;
182212bd3c8bSSascha Wildner 			y = x;
182312bd3c8bSSascha Wildner 		}
182412bd3c8bSSascha Wildner 
182512bd3c8bSSascha Wildner 		/* check if the mask is about to be shifted out */
182612bd3c8bSSascha Wildner 		if (mask & (1U << (end - 1 - x)))
182712bd3c8bSSascha Wildner 			break;
182812bd3c8bSSascha Wildner 	}
182912bd3c8bSSascha Wildner 	return (y);
183012bd3c8bSSascha Wildner }
183112bd3c8bSSascha Wildner 
183212bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
183312bd3c8bSSascha Wildner  *	usb_hs_bandwidth_adjust
183412bd3c8bSSascha Wildner  *
183512bd3c8bSSascha Wildner  * This function will update the bandwith usage for the microframe
183612bd3c8bSSascha Wildner  * having index "slot" by "len" bytes. "len" can be negative.  If the
183712bd3c8bSSascha Wildner  * "slot" argument is greater or equal to "USB_HS_MICRO_FRAMES_MAX"
183812bd3c8bSSascha Wildner  * the "slot" argument will be replaced by the slot having least used
183912bd3c8bSSascha Wildner  * bandwidth. The "mask" argument is used for multi-slot allocations.
184012bd3c8bSSascha Wildner  *
184112bd3c8bSSascha Wildner  * Returns:
184212bd3c8bSSascha Wildner  *    The slot in which the bandwidth update was done: 0..7
184312bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
184412bd3c8bSSascha Wildner static uint8_t
usb_hs_bandwidth_adjust(struct usb_device * udev,int16_t len,uint8_t slot,uint8_t mask)184512bd3c8bSSascha Wildner usb_hs_bandwidth_adjust(struct usb_device *udev, int16_t len,
184612bd3c8bSSascha Wildner     uint8_t slot, uint8_t mask)
184712bd3c8bSSascha Wildner {
184812bd3c8bSSascha Wildner 	struct usb_bus *bus = udev->bus;
184912bd3c8bSSascha Wildner 	struct usb_hub *hub;
185012bd3c8bSSascha Wildner 	enum usb_dev_speed speed;
185112bd3c8bSSascha Wildner 	uint8_t x;
185212bd3c8bSSascha Wildner 
1853722d05c3SSascha Wildner 	USB_BUS_LOCK_ASSERT(bus);
185463da4a34SSascha Wildner 
185512bd3c8bSSascha Wildner 	speed = usbd_get_speed(udev);
185612bd3c8bSSascha Wildner 
185712bd3c8bSSascha Wildner 	switch (speed) {
185812bd3c8bSSascha Wildner 	case USB_SPEED_LOW:
185912bd3c8bSSascha Wildner 	case USB_SPEED_FULL:
186012bd3c8bSSascha Wildner 		if (speed == USB_SPEED_LOW) {
186112bd3c8bSSascha Wildner 			len *= 8;
186212bd3c8bSSascha Wildner 		}
186312bd3c8bSSascha Wildner 		/*
186412bd3c8bSSascha Wildner 	         * The Host Controller Driver should have
186512bd3c8bSSascha Wildner 	         * performed checks so that the lookup
186612bd3c8bSSascha Wildner 	         * below does not result in a NULL pointer
186712bd3c8bSSascha Wildner 	         * access.
186812bd3c8bSSascha Wildner 	         */
186912bd3c8bSSascha Wildner 
187012bd3c8bSSascha Wildner 		hub = udev->parent_hs_hub->hub;
187112bd3c8bSSascha Wildner 		if (slot >= USB_HS_MICRO_FRAMES_MAX) {
187212bd3c8bSSascha Wildner 			slot = usb_intr_find_best_slot(hub->uframe_usage,
187312bd3c8bSSascha Wildner 			    USB_FS_ISOC_UFRAME_MAX, 6, mask);
187412bd3c8bSSascha Wildner 		}
187512bd3c8bSSascha Wildner 		for (x = slot; x < 8; x++) {
187612bd3c8bSSascha Wildner 			if (mask & (1U << (x - slot))) {
187712bd3c8bSSascha Wildner 				hub->uframe_usage[x] += len;
187812bd3c8bSSascha Wildner 				bus->uframe_usage[x] += len;
187912bd3c8bSSascha Wildner 			}
188012bd3c8bSSascha Wildner 		}
188112bd3c8bSSascha Wildner 		break;
188212bd3c8bSSascha Wildner 	default:
188312bd3c8bSSascha Wildner 		if (slot >= USB_HS_MICRO_FRAMES_MAX) {
188412bd3c8bSSascha Wildner 			slot = usb_intr_find_best_slot(bus->uframe_usage, 0,
188512bd3c8bSSascha Wildner 			    USB_HS_MICRO_FRAMES_MAX, mask);
188612bd3c8bSSascha Wildner 		}
188712bd3c8bSSascha Wildner 		for (x = slot; x < 8; x++) {
188812bd3c8bSSascha Wildner 			if (mask & (1U << (x - slot))) {
188912bd3c8bSSascha Wildner 				bus->uframe_usage[x] += len;
189012bd3c8bSSascha Wildner 			}
189112bd3c8bSSascha Wildner 		}
189212bd3c8bSSascha Wildner 		break;
189312bd3c8bSSascha Wildner 	}
189412bd3c8bSSascha Wildner 	return (slot);
189512bd3c8bSSascha Wildner }
189612bd3c8bSSascha Wildner 
189712bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
189812bd3c8bSSascha Wildner  *	usb_hs_bandwidth_alloc
189912bd3c8bSSascha Wildner  *
190012bd3c8bSSascha Wildner  * This function is a wrapper function for "usb_hs_bandwidth_adjust()".
190112bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
190212bd3c8bSSascha Wildner void
usb_hs_bandwidth_alloc(struct usb_xfer * xfer)190312bd3c8bSSascha Wildner usb_hs_bandwidth_alloc(struct usb_xfer *xfer)
190412bd3c8bSSascha Wildner {
190512bd3c8bSSascha Wildner 	struct usb_device *udev;
190612bd3c8bSSascha Wildner 	uint8_t slot;
190712bd3c8bSSascha Wildner 	uint8_t mask;
190812bd3c8bSSascha Wildner 	uint8_t speed;
190912bd3c8bSSascha Wildner 
191012bd3c8bSSascha Wildner 	udev = xfer->xroot->udev;
191112bd3c8bSSascha Wildner 
191212bd3c8bSSascha Wildner 	if (udev->flags.usb_mode != USB_MODE_HOST)
191312bd3c8bSSascha Wildner 		return;		/* not supported */
191412bd3c8bSSascha Wildner 
191512bd3c8bSSascha Wildner 	xfer->endpoint->refcount_bw++;
191612bd3c8bSSascha Wildner 	if (xfer->endpoint->refcount_bw != 1)
191712bd3c8bSSascha Wildner 		return;		/* already allocated */
191812bd3c8bSSascha Wildner 
191912bd3c8bSSascha Wildner 	speed = usbd_get_speed(udev);
192012bd3c8bSSascha Wildner 
192112bd3c8bSSascha Wildner 	switch (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) {
192212bd3c8bSSascha Wildner 	case UE_INTERRUPT:
192312bd3c8bSSascha Wildner 		/* allocate a microframe slot */
192412bd3c8bSSascha Wildner 
192512bd3c8bSSascha Wildner 		mask = 0x01;
192612bd3c8bSSascha Wildner 		slot = usb_hs_bandwidth_adjust(udev,
192712bd3c8bSSascha Wildner 		    xfer->max_frame_size, USB_HS_MICRO_FRAMES_MAX, mask);
192812bd3c8bSSascha Wildner 
192912bd3c8bSSascha Wildner 		xfer->endpoint->usb_uframe = slot;
193012bd3c8bSSascha Wildner 		xfer->endpoint->usb_smask = mask << slot;
193112bd3c8bSSascha Wildner 
193212bd3c8bSSascha Wildner 		if ((speed != USB_SPEED_FULL) &&
193312bd3c8bSSascha Wildner 		    (speed != USB_SPEED_LOW)) {
193412bd3c8bSSascha Wildner 			xfer->endpoint->usb_cmask = 0x00 ;
193512bd3c8bSSascha Wildner 		} else {
193612bd3c8bSSascha Wildner 			xfer->endpoint->usb_cmask = (-(0x04 << slot)) & 0xFE;
193712bd3c8bSSascha Wildner 		}
193812bd3c8bSSascha Wildner 		break;
193912bd3c8bSSascha Wildner 
194012bd3c8bSSascha Wildner 	case UE_ISOCHRONOUS:
194112bd3c8bSSascha Wildner 		switch (usbd_xfer_get_fps_shift(xfer)) {
194212bd3c8bSSascha Wildner 		case 0:
194312bd3c8bSSascha Wildner 			mask = 0xFF;
194412bd3c8bSSascha Wildner 			break;
194512bd3c8bSSascha Wildner 		case 1:
194612bd3c8bSSascha Wildner 			mask = 0x55;
194712bd3c8bSSascha Wildner 			break;
194812bd3c8bSSascha Wildner 		case 2:
194912bd3c8bSSascha Wildner 			mask = 0x11;
195012bd3c8bSSascha Wildner 			break;
195112bd3c8bSSascha Wildner 		default:
195212bd3c8bSSascha Wildner 			mask = 0x01;
195312bd3c8bSSascha Wildner 			break;
195412bd3c8bSSascha Wildner 		}
195512bd3c8bSSascha Wildner 
195612bd3c8bSSascha Wildner 		/* allocate a microframe multi-slot */
195712bd3c8bSSascha Wildner 
195812bd3c8bSSascha Wildner 		slot = usb_hs_bandwidth_adjust(udev,
195912bd3c8bSSascha Wildner 		    xfer->max_frame_size, USB_HS_MICRO_FRAMES_MAX, mask);
196012bd3c8bSSascha Wildner 
196112bd3c8bSSascha Wildner 		xfer->endpoint->usb_uframe = slot;
196212bd3c8bSSascha Wildner 		xfer->endpoint->usb_cmask = 0;
196312bd3c8bSSascha Wildner 		xfer->endpoint->usb_smask = mask << slot;
196412bd3c8bSSascha Wildner 		break;
196512bd3c8bSSascha Wildner 
196612bd3c8bSSascha Wildner 	default:
196712bd3c8bSSascha Wildner 		xfer->endpoint->usb_uframe = 0;
196812bd3c8bSSascha Wildner 		xfer->endpoint->usb_cmask = 0;
196912bd3c8bSSascha Wildner 		xfer->endpoint->usb_smask = 0;
197012bd3c8bSSascha Wildner 		break;
197112bd3c8bSSascha Wildner 	}
197212bd3c8bSSascha Wildner 
197312bd3c8bSSascha Wildner 	DPRINTFN(11, "slot=%d, mask=0x%02x\n",
197412bd3c8bSSascha Wildner 	    xfer->endpoint->usb_uframe,
197512bd3c8bSSascha Wildner 	    xfer->endpoint->usb_smask >> xfer->endpoint->usb_uframe);
197612bd3c8bSSascha Wildner }
197712bd3c8bSSascha Wildner 
197812bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
197912bd3c8bSSascha Wildner  *	usb_hs_bandwidth_free
198012bd3c8bSSascha Wildner  *
198112bd3c8bSSascha Wildner  * This function is a wrapper function for "usb_hs_bandwidth_adjust()".
198212bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
198312bd3c8bSSascha Wildner void
usb_hs_bandwidth_free(struct usb_xfer * xfer)198412bd3c8bSSascha Wildner usb_hs_bandwidth_free(struct usb_xfer *xfer)
198512bd3c8bSSascha Wildner {
198612bd3c8bSSascha Wildner 	struct usb_device *udev;
198712bd3c8bSSascha Wildner 	uint8_t slot;
198812bd3c8bSSascha Wildner 	uint8_t mask;
198912bd3c8bSSascha Wildner 
199012bd3c8bSSascha Wildner 	udev = xfer->xroot->udev;
199112bd3c8bSSascha Wildner 
199212bd3c8bSSascha Wildner 	if (udev->flags.usb_mode != USB_MODE_HOST)
199312bd3c8bSSascha Wildner 		return;		/* not supported */
199412bd3c8bSSascha Wildner 
199512bd3c8bSSascha Wildner 	xfer->endpoint->refcount_bw--;
199612bd3c8bSSascha Wildner 	if (xfer->endpoint->refcount_bw != 0)
199712bd3c8bSSascha Wildner 		return;		/* still allocated */
199812bd3c8bSSascha Wildner 
199912bd3c8bSSascha Wildner 	switch (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) {
200012bd3c8bSSascha Wildner 	case UE_INTERRUPT:
200112bd3c8bSSascha Wildner 	case UE_ISOCHRONOUS:
200212bd3c8bSSascha Wildner 
200312bd3c8bSSascha Wildner 		slot = xfer->endpoint->usb_uframe;
200412bd3c8bSSascha Wildner 		mask = xfer->endpoint->usb_smask;
200512bd3c8bSSascha Wildner 
200612bd3c8bSSascha Wildner 		/* free microframe slot(s): */
200712bd3c8bSSascha Wildner 		usb_hs_bandwidth_adjust(udev,
200812bd3c8bSSascha Wildner 		    -xfer->max_frame_size, slot, mask >> slot);
200912bd3c8bSSascha Wildner 
201012bd3c8bSSascha Wildner 		DPRINTFN(11, "slot=%d, mask=0x%02x\n",
201112bd3c8bSSascha Wildner 		    slot, mask >> slot);
201212bd3c8bSSascha Wildner 
201312bd3c8bSSascha Wildner 		xfer->endpoint->usb_uframe = 0;
201412bd3c8bSSascha Wildner 		xfer->endpoint->usb_cmask = 0;
201512bd3c8bSSascha Wildner 		xfer->endpoint->usb_smask = 0;
201612bd3c8bSSascha Wildner 		break;
201712bd3c8bSSascha Wildner 
201812bd3c8bSSascha Wildner 	default:
201912bd3c8bSSascha Wildner 		break;
202012bd3c8bSSascha Wildner 	}
202112bd3c8bSSascha Wildner }
202212bd3c8bSSascha Wildner 
202312bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
202412bd3c8bSSascha Wildner  *	usb_isoc_time_expand
202512bd3c8bSSascha Wildner  *
202612bd3c8bSSascha Wildner  * This function will expand the time counter from 7-bit to 16-bit.
202712bd3c8bSSascha Wildner  *
202812bd3c8bSSascha Wildner  * Returns:
202912bd3c8bSSascha Wildner  *   16-bit isochronous time counter.
203012bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
203112bd3c8bSSascha Wildner uint16_t
usb_isoc_time_expand(struct usb_bus * bus,uint16_t isoc_time_curr)203212bd3c8bSSascha Wildner usb_isoc_time_expand(struct usb_bus *bus, uint16_t isoc_time_curr)
203312bd3c8bSSascha Wildner {
203412bd3c8bSSascha Wildner 	uint16_t rem;
203512bd3c8bSSascha Wildner 
2036722d05c3SSascha Wildner 	USB_BUS_LOCK_ASSERT(bus);
203712bd3c8bSSascha Wildner 
203812bd3c8bSSascha Wildner 	rem = bus->isoc_time_last & (USB_ISOC_TIME_MAX - 1);
203912bd3c8bSSascha Wildner 
204012bd3c8bSSascha Wildner 	isoc_time_curr &= (USB_ISOC_TIME_MAX - 1);
204112bd3c8bSSascha Wildner 
204212bd3c8bSSascha Wildner 	if (isoc_time_curr < rem) {
204312bd3c8bSSascha Wildner 		/* the time counter wrapped around */
204412bd3c8bSSascha Wildner 		bus->isoc_time_last += USB_ISOC_TIME_MAX;
204512bd3c8bSSascha Wildner 	}
204612bd3c8bSSascha Wildner 	/* update the remainder */
204712bd3c8bSSascha Wildner 
204812bd3c8bSSascha Wildner 	bus->isoc_time_last &= ~(USB_ISOC_TIME_MAX - 1);
204912bd3c8bSSascha Wildner 	bus->isoc_time_last |= isoc_time_curr;
205012bd3c8bSSascha Wildner 
205112bd3c8bSSascha Wildner 	return (bus->isoc_time_last);
205212bd3c8bSSascha Wildner }
205312bd3c8bSSascha Wildner 
205412bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
20555e41ab93SMarkus Pfeiffer  *	usbd_fs_isoc_schedule_alloc_slot
205612bd3c8bSSascha Wildner  *
205712bd3c8bSSascha Wildner  * This function will allocate bandwidth for an isochronous FULL speed
20585e41ab93SMarkus Pfeiffer  * transaction in the FULL speed schedule.
205912bd3c8bSSascha Wildner  *
206012bd3c8bSSascha Wildner  * Returns:
20615e41ab93SMarkus Pfeiffer  *    <8: Success
206212bd3c8bSSascha Wildner  * Else: Error
206312bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
206412bd3c8bSSascha Wildner #if USB_HAVE_TT_SUPPORT
206512bd3c8bSSascha Wildner uint8_t
usbd_fs_isoc_schedule_alloc_slot(struct usb_xfer * isoc_xfer,uint16_t isoc_time)20665e41ab93SMarkus Pfeiffer usbd_fs_isoc_schedule_alloc_slot(struct usb_xfer *isoc_xfer, uint16_t isoc_time)
206712bd3c8bSSascha Wildner {
20685e41ab93SMarkus Pfeiffer 	struct usb_xfer *xfer;
20695e41ab93SMarkus Pfeiffer 	struct usb_xfer *pipe_xfer;
20705e41ab93SMarkus Pfeiffer 	struct usb_bus *bus;
20715e41ab93SMarkus Pfeiffer 	usb_frlength_t len;
20725e41ab93SMarkus Pfeiffer 	usb_frlength_t data_len;
20735e41ab93SMarkus Pfeiffer 	uint16_t delta;
20745e41ab93SMarkus Pfeiffer 	uint16_t slot;
20755e41ab93SMarkus Pfeiffer 	uint8_t retval;
207612bd3c8bSSascha Wildner 
20775e41ab93SMarkus Pfeiffer 	data_len = 0;
20785e41ab93SMarkus Pfeiffer 	slot = 0;
207912bd3c8bSSascha Wildner 
20805e41ab93SMarkus Pfeiffer 	bus = isoc_xfer->xroot->bus;
20815e41ab93SMarkus Pfeiffer 
20825e41ab93SMarkus Pfeiffer 	TAILQ_FOREACH(xfer, &bus->intr_q.head, wait_entry) {
20835e41ab93SMarkus Pfeiffer 
20845e41ab93SMarkus Pfeiffer 		/* skip self, if any */
20855e41ab93SMarkus Pfeiffer 
20865e41ab93SMarkus Pfeiffer 		if (xfer == isoc_xfer)
20875e41ab93SMarkus Pfeiffer 			continue;
20885e41ab93SMarkus Pfeiffer 
20895e41ab93SMarkus Pfeiffer 		/* check if this USB transfer is going through the same TT */
20905e41ab93SMarkus Pfeiffer 
20915e41ab93SMarkus Pfeiffer 		if (xfer->xroot->udev->parent_hs_hub !=
20925e41ab93SMarkus Pfeiffer 		    isoc_xfer->xroot->udev->parent_hs_hub) {
20935e41ab93SMarkus Pfeiffer 			continue;
20945e41ab93SMarkus Pfeiffer 		}
20955e41ab93SMarkus Pfeiffer 		if ((isoc_xfer->xroot->udev->parent_hs_hub->
20965e41ab93SMarkus Pfeiffer 		    ddesc.bDeviceProtocol == UDPROTO_HSHUBMTT) &&
20975e41ab93SMarkus Pfeiffer 		    (xfer->xroot->udev->hs_port_no !=
20985e41ab93SMarkus Pfeiffer 		    isoc_xfer->xroot->udev->hs_port_no)) {
20995e41ab93SMarkus Pfeiffer 			continue;
21005e41ab93SMarkus Pfeiffer 		}
21015e41ab93SMarkus Pfeiffer 		if (xfer->endpoint->methods != isoc_xfer->endpoint->methods)
21025e41ab93SMarkus Pfeiffer 			continue;
21035e41ab93SMarkus Pfeiffer 
21045e41ab93SMarkus Pfeiffer 		/* check if isoc_time is part of this transfer */
21055e41ab93SMarkus Pfeiffer 
21065e41ab93SMarkus Pfeiffer 		delta = xfer->isoc_time_complete - isoc_time;
21075e41ab93SMarkus Pfeiffer 		if (delta > 0 && delta <= xfer->nframes) {
21085e41ab93SMarkus Pfeiffer 			delta = xfer->nframes - delta;
21095e41ab93SMarkus Pfeiffer 
21105e41ab93SMarkus Pfeiffer 			len = xfer->frlengths[delta];
211112bd3c8bSSascha Wildner 			len += 8;
211212bd3c8bSSascha Wildner 			len *= 7;
211312bd3c8bSSascha Wildner 			len /= 6;
211412bd3c8bSSascha Wildner 
21155e41ab93SMarkus Pfeiffer 			data_len += len;
211612bd3c8bSSascha Wildner 		}
211712bd3c8bSSascha Wildner 
21185e41ab93SMarkus Pfeiffer 		/*
21195e41ab93SMarkus Pfeiffer 		 * Check double buffered transfers. Only stream ID
21205e41ab93SMarkus Pfeiffer 		 * equal to zero is valid here!
21215e41ab93SMarkus Pfeiffer 		 */
21225e41ab93SMarkus Pfeiffer 		TAILQ_FOREACH(pipe_xfer, &xfer->endpoint->endpoint_q[0].head,
21235e41ab93SMarkus Pfeiffer 		    wait_entry) {
21245e41ab93SMarkus Pfeiffer 
21255e41ab93SMarkus Pfeiffer 			/* skip self, if any */
21265e41ab93SMarkus Pfeiffer 
21275e41ab93SMarkus Pfeiffer 			if (pipe_xfer == isoc_xfer)
21285e41ab93SMarkus Pfeiffer 				continue;
21295e41ab93SMarkus Pfeiffer 
21305e41ab93SMarkus Pfeiffer 			/* check if isoc_time is part of this transfer */
21315e41ab93SMarkus Pfeiffer 
21325e41ab93SMarkus Pfeiffer 			delta = pipe_xfer->isoc_time_complete - isoc_time;
21335e41ab93SMarkus Pfeiffer 			if (delta > 0 && delta <= pipe_xfer->nframes) {
21345e41ab93SMarkus Pfeiffer 				delta = pipe_xfer->nframes - delta;
21355e41ab93SMarkus Pfeiffer 
21365e41ab93SMarkus Pfeiffer 				len = pipe_xfer->frlengths[delta];
21375e41ab93SMarkus Pfeiffer 				len += 8;
21385e41ab93SMarkus Pfeiffer 				len *= 7;
21395e41ab93SMarkus Pfeiffer 				len /= 6;
21405e41ab93SMarkus Pfeiffer 
21415e41ab93SMarkus Pfeiffer 				data_len += len;
214212bd3c8bSSascha Wildner 			}
21435e41ab93SMarkus Pfeiffer 		}
21445e41ab93SMarkus Pfeiffer 	}
21455e41ab93SMarkus Pfeiffer 
21465e41ab93SMarkus Pfeiffer 	while (data_len >= USB_FS_BYTES_PER_HS_UFRAME) {
21475e41ab93SMarkus Pfeiffer 		data_len -= USB_FS_BYTES_PER_HS_UFRAME;
21485e41ab93SMarkus Pfeiffer 		slot++;
21495e41ab93SMarkus Pfeiffer 	}
21505e41ab93SMarkus Pfeiffer 
21515e41ab93SMarkus Pfeiffer 	/* check for overflow */
21525e41ab93SMarkus Pfeiffer 
21535e41ab93SMarkus Pfeiffer 	if (slot >= USB_FS_ISOC_UFRAME_MAX)
21545e41ab93SMarkus Pfeiffer 		return (255);
21555e41ab93SMarkus Pfeiffer 
21565e41ab93SMarkus Pfeiffer 	retval = slot;
21575e41ab93SMarkus Pfeiffer 
21585e41ab93SMarkus Pfeiffer 	delta = isoc_xfer->isoc_time_complete - isoc_time;
21595e41ab93SMarkus Pfeiffer 	if (delta > 0 && delta <= isoc_xfer->nframes) {
21605e41ab93SMarkus Pfeiffer 		delta = isoc_xfer->nframes - delta;
21615e41ab93SMarkus Pfeiffer 
21625e41ab93SMarkus Pfeiffer 		len = isoc_xfer->frlengths[delta];
21635e41ab93SMarkus Pfeiffer 		len += 8;
21645e41ab93SMarkus Pfeiffer 		len *= 7;
21655e41ab93SMarkus Pfeiffer 		len /= 6;
21665e41ab93SMarkus Pfeiffer 
21675e41ab93SMarkus Pfeiffer 		data_len += len;
21685e41ab93SMarkus Pfeiffer 	}
21695e41ab93SMarkus Pfeiffer 
21705e41ab93SMarkus Pfeiffer 	while (data_len >= USB_FS_BYTES_PER_HS_UFRAME) {
21715e41ab93SMarkus Pfeiffer 		data_len -= USB_FS_BYTES_PER_HS_UFRAME;
21725e41ab93SMarkus Pfeiffer 		slot++;
21735e41ab93SMarkus Pfeiffer 	}
21745e41ab93SMarkus Pfeiffer 
21755e41ab93SMarkus Pfeiffer 	/* check for overflow */
21765e41ab93SMarkus Pfeiffer 
21775e41ab93SMarkus Pfeiffer 	if (slot >= USB_FS_ISOC_UFRAME_MAX)
21785e41ab93SMarkus Pfeiffer 		return (255);
21795e41ab93SMarkus Pfeiffer 
21805e41ab93SMarkus Pfeiffer 	return (retval);
218112bd3c8bSSascha Wildner }
218212bd3c8bSSascha Wildner #endif
218312bd3c8bSSascha Wildner 
218412bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
218512bd3c8bSSascha Wildner  *	usb_bus_port_get_device
218612bd3c8bSSascha Wildner  *
218712bd3c8bSSascha Wildner  * This function is NULL safe.
218812bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
218912bd3c8bSSascha Wildner struct usb_device *
usb_bus_port_get_device(struct usb_bus * bus,struct usb_port * up)219012bd3c8bSSascha Wildner usb_bus_port_get_device(struct usb_bus *bus, struct usb_port *up)
219112bd3c8bSSascha Wildner {
219212bd3c8bSSascha Wildner 	if ((bus == NULL) || (up == NULL)) {
219312bd3c8bSSascha Wildner 		/* be NULL safe */
219412bd3c8bSSascha Wildner 		return (NULL);
219512bd3c8bSSascha Wildner 	}
219612bd3c8bSSascha Wildner 	if (up->device_index == 0) {
219712bd3c8bSSascha Wildner 		/* nothing to do */
219812bd3c8bSSascha Wildner 		return (NULL);
219912bd3c8bSSascha Wildner 	}
220012bd3c8bSSascha Wildner 	return (bus->devices[up->device_index]);
220112bd3c8bSSascha Wildner }
220212bd3c8bSSascha Wildner 
220312bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
220412bd3c8bSSascha Wildner  *	usb_bus_port_set_device
220512bd3c8bSSascha Wildner  *
220612bd3c8bSSascha Wildner  * This function is NULL safe.
220712bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
220812bd3c8bSSascha Wildner void
usb_bus_port_set_device(struct usb_bus * bus,struct usb_port * up,struct usb_device * udev,uint8_t device_index)220912bd3c8bSSascha Wildner usb_bus_port_set_device(struct usb_bus *bus, struct usb_port *up,
221012bd3c8bSSascha Wildner     struct usb_device *udev, uint8_t device_index)
221112bd3c8bSSascha Wildner {
221212bd3c8bSSascha Wildner 	if (bus == NULL) {
221312bd3c8bSSascha Wildner 		/* be NULL safe */
221412bd3c8bSSascha Wildner 		return;
221512bd3c8bSSascha Wildner 	}
221612bd3c8bSSascha Wildner 	/*
221712bd3c8bSSascha Wildner 	 * There is only one case where we don't
221812bd3c8bSSascha Wildner 	 * have an USB port, and that is the Root Hub!
221912bd3c8bSSascha Wildner          */
222012bd3c8bSSascha Wildner 	if (up) {
222112bd3c8bSSascha Wildner 		if (udev) {
222212bd3c8bSSascha Wildner 			up->device_index = device_index;
222312bd3c8bSSascha Wildner 		} else {
222412bd3c8bSSascha Wildner 			device_index = up->device_index;
222512bd3c8bSSascha Wildner 			up->device_index = 0;
222612bd3c8bSSascha Wildner 		}
222712bd3c8bSSascha Wildner 	}
222812bd3c8bSSascha Wildner 	/*
222912bd3c8bSSascha Wildner 	 * Make relationships to our new device
223012bd3c8bSSascha Wildner 	 */
223112bd3c8bSSascha Wildner 	if (device_index != 0) {
223212bd3c8bSSascha Wildner #if USB_HAVE_UGEN
2233722d05c3SSascha Wildner 		lockmgr(&usb_ref_lock, LK_EXCLUSIVE);
223412bd3c8bSSascha Wildner #endif
223512bd3c8bSSascha Wildner 		bus->devices[device_index] = udev;
223612bd3c8bSSascha Wildner #if USB_HAVE_UGEN
2237722d05c3SSascha Wildner 		lockmgr(&usb_ref_lock, LK_RELEASE);
223812bd3c8bSSascha Wildner #endif
223912bd3c8bSSascha Wildner 	}
224012bd3c8bSSascha Wildner 	/*
224112bd3c8bSSascha Wildner 	 * Debug print
224212bd3c8bSSascha Wildner 	 */
224312bd3c8bSSascha Wildner 	DPRINTFN(2, "bus %p devices[%u] = %p\n", bus, device_index, udev);
224412bd3c8bSSascha Wildner }
224512bd3c8bSSascha Wildner 
224612bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
224712bd3c8bSSascha Wildner  *	usb_needs_explore
224812bd3c8bSSascha Wildner  *
224912bd3c8bSSascha Wildner  * This functions is called when the USB event thread needs to run.
225012bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
225112bd3c8bSSascha Wildner void
usb_needs_explore(struct usb_bus * bus,uint8_t do_probe)225212bd3c8bSSascha Wildner usb_needs_explore(struct usb_bus *bus, uint8_t do_probe)
225312bd3c8bSSascha Wildner {
225412bd3c8bSSascha Wildner 	uint8_t do_unlock;
225512bd3c8bSSascha Wildner 
225612bd3c8bSSascha Wildner 	DPRINTF("\n");
225712bd3c8bSSascha Wildner 
225812bd3c8bSSascha Wildner 	if (bus == NULL) {
225912bd3c8bSSascha Wildner 		DPRINTF("No bus pointer!\n");
226012bd3c8bSSascha Wildner 		return;
226112bd3c8bSSascha Wildner 	}
226212bd3c8bSSascha Wildner 	if ((bus->devices == NULL) ||
226312bd3c8bSSascha Wildner 	    (bus->devices[USB_ROOT_HUB_ADDR] == NULL)) {
226412bd3c8bSSascha Wildner 		DPRINTF("No root HUB\n");
226512bd3c8bSSascha Wildner 		return;
226612bd3c8bSSascha Wildner 	}
22673a76bbe8SSascha Wildner 	if (lockowned(&bus->bus_lock) != 0) {
226812bd3c8bSSascha Wildner 		do_unlock = 0;
226912bd3c8bSSascha Wildner 	} else {
227012bd3c8bSSascha Wildner 		USB_BUS_LOCK(bus);
227112bd3c8bSSascha Wildner 		do_unlock = 1;
227212bd3c8bSSascha Wildner 	}
227312bd3c8bSSascha Wildner 	if (do_probe) {
227412bd3c8bSSascha Wildner 		bus->do_probe = 1;
227512bd3c8bSSascha Wildner 	}
227657bed822SMarkus Pfeiffer 	if (usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus),
227712bd3c8bSSascha Wildner 	    &bus->explore_msg[0], &bus->explore_msg[1])) {
227812bd3c8bSSascha Wildner 		/* ignore */
227912bd3c8bSSascha Wildner 	}
228012bd3c8bSSascha Wildner 	if (do_unlock) {
228112bd3c8bSSascha Wildner 		USB_BUS_UNLOCK(bus);
228212bd3c8bSSascha Wildner 	}
228312bd3c8bSSascha Wildner }
228412bd3c8bSSascha Wildner 
228512bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
228612bd3c8bSSascha Wildner  *	usb_needs_explore_all
228712bd3c8bSSascha Wildner  *
228812bd3c8bSSascha Wildner  * This function is called whenever a new driver is loaded and will
228912bd3c8bSSascha Wildner  * cause that all USB busses are re-explored.
229012bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
229112bd3c8bSSascha Wildner void
usb_needs_explore_all(void)229212bd3c8bSSascha Wildner usb_needs_explore_all(void)
229312bd3c8bSSascha Wildner {
229412bd3c8bSSascha Wildner 	struct usb_bus *bus;
229512bd3c8bSSascha Wildner 	devclass_t dc;
229612bd3c8bSSascha Wildner 	device_t dev;
229712bd3c8bSSascha Wildner 	int max;
229812bd3c8bSSascha Wildner 
229912bd3c8bSSascha Wildner 	DPRINTFN(3, "\n");
230012bd3c8bSSascha Wildner 
230112bd3c8bSSascha Wildner 	dc = usb_devclass_ptr;
230212bd3c8bSSascha Wildner 	if (dc == NULL) {
230312bd3c8bSSascha Wildner 		DPRINTFN(0, "no devclass\n");
230412bd3c8bSSascha Wildner 		return;
230512bd3c8bSSascha Wildner 	}
230612bd3c8bSSascha Wildner 	/*
230712bd3c8bSSascha Wildner 	 * Explore all USB busses in parallell.
230812bd3c8bSSascha Wildner 	 */
230912bd3c8bSSascha Wildner 	max = devclass_get_maxunit(dc);
231012bd3c8bSSascha Wildner 	while (max >= 0) {
231112bd3c8bSSascha Wildner 		dev = devclass_get_device(dc, max);
231212bd3c8bSSascha Wildner 		if (dev) {
231312bd3c8bSSascha Wildner 			bus = device_get_softc(dev);
231412bd3c8bSSascha Wildner 			if (bus) {
231512bd3c8bSSascha Wildner 				usb_needs_explore(bus, 1);
231612bd3c8bSSascha Wildner 			}
231712bd3c8bSSascha Wildner 		}
231812bd3c8bSSascha Wildner 		max--;
231912bd3c8bSSascha Wildner 	}
232012bd3c8bSSascha Wildner }
232112bd3c8bSSascha Wildner 
232212bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
232312bd3c8bSSascha Wildner  *	usb_bus_power_update
232412bd3c8bSSascha Wildner  *
232512bd3c8bSSascha Wildner  * This function will ensure that all USB devices on the given bus are
232612bd3c8bSSascha Wildner  * properly suspended or resumed according to the device transfer
232712bd3c8bSSascha Wildner  * state.
232812bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
232912bd3c8bSSascha Wildner #if USB_HAVE_POWERD
233012bd3c8bSSascha Wildner void
usb_bus_power_update(struct usb_bus * bus)233112bd3c8bSSascha Wildner usb_bus_power_update(struct usb_bus *bus)
233212bd3c8bSSascha Wildner {
233312bd3c8bSSascha Wildner 	usb_needs_explore(bus, 0 /* no probe */ );
233412bd3c8bSSascha Wildner }
233512bd3c8bSSascha Wildner #endif
233612bd3c8bSSascha Wildner 
233712bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
233812bd3c8bSSascha Wildner  *	usbd_transfer_power_ref
233912bd3c8bSSascha Wildner  *
234012bd3c8bSSascha Wildner  * This function will modify the power save reference counts and
234112bd3c8bSSascha Wildner  * wakeup the USB device associated with the given USB transfer, if
234212bd3c8bSSascha Wildner  * needed.
234312bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
234412bd3c8bSSascha Wildner #if USB_HAVE_POWERD
234512bd3c8bSSascha Wildner void
usbd_transfer_power_ref(struct usb_xfer * xfer,int val)234612bd3c8bSSascha Wildner usbd_transfer_power_ref(struct usb_xfer *xfer, int val)
234712bd3c8bSSascha Wildner {
234812bd3c8bSSascha Wildner 	static const usb_power_mask_t power_mask[4] = {
234912bd3c8bSSascha Wildner 		[UE_CONTROL] = USB_HW_POWER_CONTROL,
235012bd3c8bSSascha Wildner 		[UE_BULK] = USB_HW_POWER_BULK,
235112bd3c8bSSascha Wildner 		[UE_INTERRUPT] = USB_HW_POWER_INTERRUPT,
235212bd3c8bSSascha Wildner 		[UE_ISOCHRONOUS] = USB_HW_POWER_ISOC,
235312bd3c8bSSascha Wildner 	};
235412bd3c8bSSascha Wildner 	struct usb_device *udev;
235512bd3c8bSSascha Wildner 	uint8_t needs_explore;
235612bd3c8bSSascha Wildner 	uint8_t needs_hw_power;
235712bd3c8bSSascha Wildner 	uint8_t xfer_type;
235812bd3c8bSSascha Wildner 
235912bd3c8bSSascha Wildner 	udev = xfer->xroot->udev;
236012bd3c8bSSascha Wildner 
236112bd3c8bSSascha Wildner 	if (udev->device_index == USB_ROOT_HUB_ADDR) {
236212bd3c8bSSascha Wildner 		/* no power save for root HUB */
236312bd3c8bSSascha Wildner 		return;
236412bd3c8bSSascha Wildner 	}
236512bd3c8bSSascha Wildner 	USB_BUS_LOCK(udev->bus);
236612bd3c8bSSascha Wildner 
236712bd3c8bSSascha Wildner 	xfer_type = xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE;
236812bd3c8bSSascha Wildner 
236912bd3c8bSSascha Wildner 	udev->pwr_save.last_xfer_time = ticks;
237012bd3c8bSSascha Wildner 	udev->pwr_save.type_refs[xfer_type] += val;
237112bd3c8bSSascha Wildner 
237212bd3c8bSSascha Wildner 	if (xfer->flags_int.control_xfr) {
237312bd3c8bSSascha Wildner 		udev->pwr_save.read_refs += val;
237412bd3c8bSSascha Wildner 		if (xfer->flags_int.usb_mode == USB_MODE_HOST) {
237512bd3c8bSSascha Wildner 			/*
237612bd3c8bSSascha Wildner 			 * It is not allowed to suspend during a
237712bd3c8bSSascha Wildner 			 * control transfer:
237812bd3c8bSSascha Wildner 			 */
237912bd3c8bSSascha Wildner 			udev->pwr_save.write_refs += val;
238012bd3c8bSSascha Wildner 		}
238112bd3c8bSSascha Wildner 	} else if (USB_GET_DATA_ISREAD(xfer)) {
238212bd3c8bSSascha Wildner 		udev->pwr_save.read_refs += val;
238312bd3c8bSSascha Wildner 	} else {
238412bd3c8bSSascha Wildner 		udev->pwr_save.write_refs += val;
238512bd3c8bSSascha Wildner 	}
238612bd3c8bSSascha Wildner 
238712bd3c8bSSascha Wildner 	if (val > 0) {
238812bd3c8bSSascha Wildner 		if (udev->flags.self_suspended)
238912bd3c8bSSascha Wildner 			needs_explore = usb_peer_should_wakeup(udev);
239012bd3c8bSSascha Wildner 		else
239112bd3c8bSSascha Wildner 			needs_explore = 0;
239212bd3c8bSSascha Wildner 
239312bd3c8bSSascha Wildner 		if (!(udev->bus->hw_power_state & power_mask[xfer_type])) {
239412bd3c8bSSascha Wildner 			DPRINTF("Adding type %u to power state\n", xfer_type);
239512bd3c8bSSascha Wildner 			udev->bus->hw_power_state |= power_mask[xfer_type];
239612bd3c8bSSascha Wildner 			needs_hw_power = 1;
239712bd3c8bSSascha Wildner 		} else {
239812bd3c8bSSascha Wildner 			needs_hw_power = 0;
239912bd3c8bSSascha Wildner 		}
240012bd3c8bSSascha Wildner 	} else {
240112bd3c8bSSascha Wildner 		needs_explore = 0;
240212bd3c8bSSascha Wildner 		needs_hw_power = 0;
240312bd3c8bSSascha Wildner 	}
240412bd3c8bSSascha Wildner 
240512bd3c8bSSascha Wildner 	USB_BUS_UNLOCK(udev->bus);
240612bd3c8bSSascha Wildner 
240712bd3c8bSSascha Wildner 	if (needs_explore) {
240812bd3c8bSSascha Wildner 		DPRINTF("update\n");
240912bd3c8bSSascha Wildner 		usb_bus_power_update(udev->bus);
241012bd3c8bSSascha Wildner 	} else if (needs_hw_power) {
241112bd3c8bSSascha Wildner 		DPRINTF("needs power\n");
241212bd3c8bSSascha Wildner 		if (udev->bus->methods->set_hw_power != NULL) {
241312bd3c8bSSascha Wildner 			(udev->bus->methods->set_hw_power) (udev->bus);
241412bd3c8bSSascha Wildner 		}
241512bd3c8bSSascha Wildner 	}
241612bd3c8bSSascha Wildner }
241712bd3c8bSSascha Wildner #endif
241812bd3c8bSSascha Wildner 
241912bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
242012bd3c8bSSascha Wildner  *	usb_peer_should_wakeup
242112bd3c8bSSascha Wildner  *
242212bd3c8bSSascha Wildner  * This function returns non-zero if the current device should wake up.
242312bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
242412bd3c8bSSascha Wildner static uint8_t
usb_peer_should_wakeup(struct usb_device * udev)242512bd3c8bSSascha Wildner usb_peer_should_wakeup(struct usb_device *udev)
242612bd3c8bSSascha Wildner {
24278922de18SMarkus Pfeiffer 	return (((udev->power_mode == USB_POWER_MODE_ON) &&
24288922de18SMarkus Pfeiffer 	    (udev->flags.usb_mode == USB_MODE_HOST)) ||
242912bd3c8bSSascha Wildner 	    (udev->driver_added_refcount != udev->bus->driver_added_refcount) ||
24308922de18SMarkus Pfeiffer 	    (udev->re_enumerate_wait != USB_RE_ENUM_DONE) ||
243112bd3c8bSSascha Wildner 	    (udev->pwr_save.type_refs[UE_ISOCHRONOUS] != 0) ||
243212bd3c8bSSascha Wildner 	    (udev->pwr_save.write_refs != 0) ||
243312bd3c8bSSascha Wildner 	    ((udev->pwr_save.read_refs != 0) &&
243412bd3c8bSSascha Wildner 	    (udev->flags.usb_mode == USB_MODE_HOST) &&
243512bd3c8bSSascha Wildner 	    (usb_peer_can_wakeup(udev) == 0)));
243612bd3c8bSSascha Wildner }
243712bd3c8bSSascha Wildner 
243812bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
243912bd3c8bSSascha Wildner  *	usb_bus_powerd
244012bd3c8bSSascha Wildner  *
244112bd3c8bSSascha Wildner  * This function implements the USB power daemon and is called
244212bd3c8bSSascha Wildner  * regularly from the USB explore thread.
244312bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
244412bd3c8bSSascha Wildner #if USB_HAVE_POWERD
244512bd3c8bSSascha Wildner void
usb_bus_powerd(struct usb_bus * bus)244612bd3c8bSSascha Wildner usb_bus_powerd(struct usb_bus *bus)
244712bd3c8bSSascha Wildner {
244812bd3c8bSSascha Wildner 	struct usb_device *udev;
244912bd3c8bSSascha Wildner 	usb_ticks_t temp;
245012bd3c8bSSascha Wildner 	usb_ticks_t limit;
245112bd3c8bSSascha Wildner 	usb_ticks_t mintime;
245212bd3c8bSSascha Wildner 	usb_size_t type_refs[5];
245312bd3c8bSSascha Wildner 	uint8_t x;
245412bd3c8bSSascha Wildner 
245512bd3c8bSSascha Wildner 	limit = usb_power_timeout;
245612bd3c8bSSascha Wildner 	if (limit == 0)
245712bd3c8bSSascha Wildner 		limit = hz;
245812bd3c8bSSascha Wildner 	else if (limit > 255)
245912bd3c8bSSascha Wildner 		limit = 255 * hz;
246012bd3c8bSSascha Wildner 	else
246112bd3c8bSSascha Wildner 		limit = limit * hz;
246212bd3c8bSSascha Wildner 
246312bd3c8bSSascha Wildner 	DPRINTF("bus=%p\n", bus);
246412bd3c8bSSascha Wildner 
246512bd3c8bSSascha Wildner 	USB_BUS_LOCK(bus);
246612bd3c8bSSascha Wildner 
246712bd3c8bSSascha Wildner 	/*
246812bd3c8bSSascha Wildner 	 * The root HUB device is never suspended
246912bd3c8bSSascha Wildner 	 * and we simply skip it.
247012bd3c8bSSascha Wildner 	 */
247112bd3c8bSSascha Wildner 	for (x = USB_ROOT_HUB_ADDR + 1;
247212bd3c8bSSascha Wildner 	    x != bus->devices_max; x++) {
247312bd3c8bSSascha Wildner 
247412bd3c8bSSascha Wildner 		udev = bus->devices[x];
247512bd3c8bSSascha Wildner 		if (udev == NULL)
247612bd3c8bSSascha Wildner 			continue;
247712bd3c8bSSascha Wildner 
247812bd3c8bSSascha Wildner 		temp = ticks - udev->pwr_save.last_xfer_time;
247912bd3c8bSSascha Wildner 
248012bd3c8bSSascha Wildner 		if (usb_peer_should_wakeup(udev)) {
248112bd3c8bSSascha Wildner 			/* check if we are suspended */
248212bd3c8bSSascha Wildner 			if (udev->flags.self_suspended != 0) {
248312bd3c8bSSascha Wildner 				USB_BUS_UNLOCK(bus);
248412bd3c8bSSascha Wildner 				usb_dev_resume_peer(udev);
248512bd3c8bSSascha Wildner 				USB_BUS_LOCK(bus);
248612bd3c8bSSascha Wildner 			}
248712bd3c8bSSascha Wildner 		} else if ((temp >= limit) &&
248812bd3c8bSSascha Wildner 		    (udev->flags.usb_mode == USB_MODE_HOST) &&
248912bd3c8bSSascha Wildner 		    (udev->flags.self_suspended == 0)) {
249012bd3c8bSSascha Wildner 			/* try to do suspend */
249112bd3c8bSSascha Wildner 
249212bd3c8bSSascha Wildner 			USB_BUS_UNLOCK(bus);
249312bd3c8bSSascha Wildner 			usb_dev_suspend_peer(udev);
249412bd3c8bSSascha Wildner 			USB_BUS_LOCK(bus);
249512bd3c8bSSascha Wildner 		}
249612bd3c8bSSascha Wildner 	}
249712bd3c8bSSascha Wildner 
249812bd3c8bSSascha Wildner 	/* reset counters */
249912bd3c8bSSascha Wildner 
25005e41ab93SMarkus Pfeiffer 	mintime = (usb_ticks_t)-1;
250112bd3c8bSSascha Wildner 	type_refs[0] = 0;
250212bd3c8bSSascha Wildner 	type_refs[1] = 0;
250312bd3c8bSSascha Wildner 	type_refs[2] = 0;
250412bd3c8bSSascha Wildner 	type_refs[3] = 0;
250512bd3c8bSSascha Wildner 	type_refs[4] = 0;
250612bd3c8bSSascha Wildner 
250712bd3c8bSSascha Wildner 	/* Re-loop all the devices to get the actual state */
250812bd3c8bSSascha Wildner 
250912bd3c8bSSascha Wildner 	for (x = USB_ROOT_HUB_ADDR + 1;
251012bd3c8bSSascha Wildner 	    x != bus->devices_max; x++) {
251112bd3c8bSSascha Wildner 
251212bd3c8bSSascha Wildner 		udev = bus->devices[x];
251312bd3c8bSSascha Wildner 		if (udev == NULL)
251412bd3c8bSSascha Wildner 			continue;
251512bd3c8bSSascha Wildner 
251612bd3c8bSSascha Wildner 		/* we found a non-Root-Hub USB device */
251712bd3c8bSSascha Wildner 		type_refs[4] += 1;
251812bd3c8bSSascha Wildner 
251912bd3c8bSSascha Wildner 		/* "last_xfer_time" can be updated by a resume */
252012bd3c8bSSascha Wildner 		temp = ticks - udev->pwr_save.last_xfer_time;
252112bd3c8bSSascha Wildner 
252212bd3c8bSSascha Wildner 		/*
252312bd3c8bSSascha Wildner 		 * Compute minimum time since last transfer for the complete
252412bd3c8bSSascha Wildner 		 * bus:
252512bd3c8bSSascha Wildner 		 */
252612bd3c8bSSascha Wildner 		if (temp < mintime)
252712bd3c8bSSascha Wildner 			mintime = temp;
252812bd3c8bSSascha Wildner 
252912bd3c8bSSascha Wildner 		if (udev->flags.self_suspended == 0) {
253012bd3c8bSSascha Wildner 			type_refs[0] += udev->pwr_save.type_refs[0];
253112bd3c8bSSascha Wildner 			type_refs[1] += udev->pwr_save.type_refs[1];
253212bd3c8bSSascha Wildner 			type_refs[2] += udev->pwr_save.type_refs[2];
253312bd3c8bSSascha Wildner 			type_refs[3] += udev->pwr_save.type_refs[3];
253412bd3c8bSSascha Wildner 		}
253512bd3c8bSSascha Wildner 	}
253612bd3c8bSSascha Wildner 
25375e41ab93SMarkus Pfeiffer 	if (mintime >= (usb_ticks_t)(1 * hz)) {
253812bd3c8bSSascha Wildner 		/* recompute power masks */
253912bd3c8bSSascha Wildner 		DPRINTF("Recomputing power masks\n");
254012bd3c8bSSascha Wildner 		bus->hw_power_state = 0;
254112bd3c8bSSascha Wildner 		if (type_refs[UE_CONTROL] != 0)
254212bd3c8bSSascha Wildner 			bus->hw_power_state |= USB_HW_POWER_CONTROL;
254312bd3c8bSSascha Wildner 		if (type_refs[UE_BULK] != 0)
254412bd3c8bSSascha Wildner 			bus->hw_power_state |= USB_HW_POWER_BULK;
254512bd3c8bSSascha Wildner 		if (type_refs[UE_INTERRUPT] != 0)
254612bd3c8bSSascha Wildner 			bus->hw_power_state |= USB_HW_POWER_INTERRUPT;
254712bd3c8bSSascha Wildner 		if (type_refs[UE_ISOCHRONOUS] != 0)
254812bd3c8bSSascha Wildner 			bus->hw_power_state |= USB_HW_POWER_ISOC;
254912bd3c8bSSascha Wildner 		if (type_refs[4] != 0)
255012bd3c8bSSascha Wildner 			bus->hw_power_state |= USB_HW_POWER_NON_ROOT_HUB;
255112bd3c8bSSascha Wildner 	}
255212bd3c8bSSascha Wildner 	USB_BUS_UNLOCK(bus);
255312bd3c8bSSascha Wildner 
255412bd3c8bSSascha Wildner 	if (bus->methods->set_hw_power != NULL) {
255512bd3c8bSSascha Wildner 		/* always update hardware power! */
255612bd3c8bSSascha Wildner 		(bus->methods->set_hw_power) (bus);
255712bd3c8bSSascha Wildner 	}
255812bd3c8bSSascha Wildner 	return;
255912bd3c8bSSascha Wildner }
256012bd3c8bSSascha Wildner #endif
256112bd3c8bSSascha Wildner 
256212bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
256312bd3c8bSSascha Wildner  *	usb_dev_resume_peer
256412bd3c8bSSascha Wildner  *
256512bd3c8bSSascha Wildner  * This function will resume an USB peer and do the required USB
256612bd3c8bSSascha Wildner  * signalling to get an USB device out of the suspended state.
256712bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
256812bd3c8bSSascha Wildner static void
usb_dev_resume_peer(struct usb_device * udev)256912bd3c8bSSascha Wildner usb_dev_resume_peer(struct usb_device *udev)
257012bd3c8bSSascha Wildner {
257112bd3c8bSSascha Wildner 	struct usb_bus *bus;
257212bd3c8bSSascha Wildner 	int err;
257312bd3c8bSSascha Wildner 
257412bd3c8bSSascha Wildner 	/* be NULL safe */
257512bd3c8bSSascha Wildner 	if (udev == NULL)
257612bd3c8bSSascha Wildner 		return;
257712bd3c8bSSascha Wildner 
257812bd3c8bSSascha Wildner 	/* check if already resumed */
257912bd3c8bSSascha Wildner 	if (udev->flags.self_suspended == 0)
258012bd3c8bSSascha Wildner 		return;
258112bd3c8bSSascha Wildner 
258212bd3c8bSSascha Wildner 	/* we need a parent HUB to do resume */
258312bd3c8bSSascha Wildner 	if (udev->parent_hub == NULL)
258412bd3c8bSSascha Wildner 		return;
258512bd3c8bSSascha Wildner 
258612bd3c8bSSascha Wildner 	DPRINTF("udev=%p\n", udev);
258712bd3c8bSSascha Wildner 
258812bd3c8bSSascha Wildner 	if ((udev->flags.usb_mode == USB_MODE_DEVICE) &&
258912bd3c8bSSascha Wildner 	    (udev->flags.remote_wakeup == 0)) {
259012bd3c8bSSascha Wildner 		/*
259112bd3c8bSSascha Wildner 		 * If the host did not set the remote wakeup feature, we can
259212bd3c8bSSascha Wildner 		 * not wake it up either!
259312bd3c8bSSascha Wildner 		 */
259412bd3c8bSSascha Wildner 		DPRINTF("remote wakeup is not set!\n");
259512bd3c8bSSascha Wildner 		return;
259612bd3c8bSSascha Wildner 	}
259712bd3c8bSSascha Wildner 	/* get bus pointer */
259812bd3c8bSSascha Wildner 	bus = udev->bus;
259912bd3c8bSSascha Wildner 
260012bd3c8bSSascha Wildner 	/* resume parent hub first */
260112bd3c8bSSascha Wildner 	usb_dev_resume_peer(udev->parent_hub);
260212bd3c8bSSascha Wildner 
260312bd3c8bSSascha Wildner 	/* reduce chance of instant resume failure by waiting a little bit */
260412bd3c8bSSascha Wildner 	usb_pause_mtx(NULL, USB_MS_TO_TICKS(20));
260512bd3c8bSSascha Wildner 
260612bd3c8bSSascha Wildner 	if (usb_device_20_compatible(udev)) {
260712bd3c8bSSascha Wildner 		/* resume current port (Valid in Host and Device Mode) */
260812bd3c8bSSascha Wildner 		err = usbd_req_clear_port_feature(udev->parent_hub,
260912bd3c8bSSascha Wildner 		    NULL, udev->port_no, UHF_PORT_SUSPEND);
261012bd3c8bSSascha Wildner 		if (err) {
261112bd3c8bSSascha Wildner 			DPRINTFN(0, "Resuming port failed\n");
261212bd3c8bSSascha Wildner 			return;
261312bd3c8bSSascha Wildner 		}
261412bd3c8bSSascha Wildner 	} else {
261512bd3c8bSSascha Wildner 		/* resume current port (Valid in Host and Device Mode) */
261612bd3c8bSSascha Wildner 		err = usbd_req_set_port_link_state(udev->parent_hub,
261712bd3c8bSSascha Wildner 		    NULL, udev->port_no, UPS_PORT_LS_U0);
261812bd3c8bSSascha Wildner 		if (err) {
261912bd3c8bSSascha Wildner 			DPRINTFN(0, "Resuming port failed\n");
262012bd3c8bSSascha Wildner 			return;
262112bd3c8bSSascha Wildner 		}
262212bd3c8bSSascha Wildner 	}
262312bd3c8bSSascha Wildner 
262412bd3c8bSSascha Wildner 	/* resume settle time */
26255e41ab93SMarkus Pfeiffer 	usb_pause_mtx(NULL, USB_MS_TO_TICKS(usb_port_resume_delay));
262612bd3c8bSSascha Wildner 
262712bd3c8bSSascha Wildner 	if (bus->methods->device_resume != NULL) {
262812bd3c8bSSascha Wildner 		/* resume USB device on the USB controller */
262912bd3c8bSSascha Wildner 		(bus->methods->device_resume) (udev);
263012bd3c8bSSascha Wildner 	}
263112bd3c8bSSascha Wildner 	USB_BUS_LOCK(bus);
263212bd3c8bSSascha Wildner 	/* set that this device is now resumed */
263312bd3c8bSSascha Wildner 	udev->flags.self_suspended = 0;
263412bd3c8bSSascha Wildner #if USB_HAVE_POWERD
263512bd3c8bSSascha Wildner 	/* make sure that we don't go into suspend right away */
263612bd3c8bSSascha Wildner 	udev->pwr_save.last_xfer_time = ticks;
263712bd3c8bSSascha Wildner 
263812bd3c8bSSascha Wildner 	/* make sure the needed power masks are on */
263912bd3c8bSSascha Wildner 	if (udev->pwr_save.type_refs[UE_CONTROL] != 0)
264012bd3c8bSSascha Wildner 		bus->hw_power_state |= USB_HW_POWER_CONTROL;
264112bd3c8bSSascha Wildner 	if (udev->pwr_save.type_refs[UE_BULK] != 0)
264212bd3c8bSSascha Wildner 		bus->hw_power_state |= USB_HW_POWER_BULK;
264312bd3c8bSSascha Wildner 	if (udev->pwr_save.type_refs[UE_INTERRUPT] != 0)
264412bd3c8bSSascha Wildner 		bus->hw_power_state |= USB_HW_POWER_INTERRUPT;
264512bd3c8bSSascha Wildner 	if (udev->pwr_save.type_refs[UE_ISOCHRONOUS] != 0)
264612bd3c8bSSascha Wildner 		bus->hw_power_state |= USB_HW_POWER_ISOC;
264712bd3c8bSSascha Wildner #endif
264812bd3c8bSSascha Wildner 	USB_BUS_UNLOCK(bus);
264912bd3c8bSSascha Wildner 
265012bd3c8bSSascha Wildner 	if (bus->methods->set_hw_power != NULL) {
265112bd3c8bSSascha Wildner 		/* always update hardware power! */
265212bd3c8bSSascha Wildner 		(bus->methods->set_hw_power) (bus);
265312bd3c8bSSascha Wildner 	}
265412bd3c8bSSascha Wildner 
265512bd3c8bSSascha Wildner 	usbd_sr_lock(udev);
265612bd3c8bSSascha Wildner 
265712bd3c8bSSascha Wildner 	/* notify all sub-devices about resume */
265812bd3c8bSSascha Wildner 	err = usb_suspend_resume(udev, 0);
265912bd3c8bSSascha Wildner 
266012bd3c8bSSascha Wildner 	usbd_sr_unlock(udev);
266112bd3c8bSSascha Wildner 
266212bd3c8bSSascha Wildner 	/* check if peer has wakeup capability */
266312bd3c8bSSascha Wildner 	if (usb_peer_can_wakeup(udev)) {
266412bd3c8bSSascha Wildner 		/* clear remote wakeup */
266512bd3c8bSSascha Wildner 		err = usbd_req_clear_device_feature(udev,
266612bd3c8bSSascha Wildner 		    NULL, UF_DEVICE_REMOTE_WAKEUP);
266712bd3c8bSSascha Wildner 		if (err) {
266812bd3c8bSSascha Wildner 			DPRINTFN(0, "Clearing device "
266912bd3c8bSSascha Wildner 			    "remote wakeup failed: %s\n",
267012bd3c8bSSascha Wildner 			    usbd_errstr(err));
267112bd3c8bSSascha Wildner 		}
267212bd3c8bSSascha Wildner 	}
267312bd3c8bSSascha Wildner }
267412bd3c8bSSascha Wildner 
267512bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
267612bd3c8bSSascha Wildner  *	usb_dev_suspend_peer
267712bd3c8bSSascha Wildner  *
267812bd3c8bSSascha Wildner  * This function will suspend an USB peer and do the required USB
267912bd3c8bSSascha Wildner  * signalling to get an USB device into the suspended state.
268012bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
268112bd3c8bSSascha Wildner static void
usb_dev_suspend_peer(struct usb_device * udev)268212bd3c8bSSascha Wildner usb_dev_suspend_peer(struct usb_device *udev)
268312bd3c8bSSascha Wildner {
268412bd3c8bSSascha Wildner 	struct usb_device *child;
268512bd3c8bSSascha Wildner 	int err;
268612bd3c8bSSascha Wildner 	uint8_t x;
268712bd3c8bSSascha Wildner 	uint8_t nports;
268812bd3c8bSSascha Wildner 
268912bd3c8bSSascha Wildner repeat:
269012bd3c8bSSascha Wildner 	/* be NULL safe */
269112bd3c8bSSascha Wildner 	if (udev == NULL)
269212bd3c8bSSascha Wildner 		return;
269312bd3c8bSSascha Wildner 
269412bd3c8bSSascha Wildner 	/* check if already suspended */
269512bd3c8bSSascha Wildner 	if (udev->flags.self_suspended)
269612bd3c8bSSascha Wildner 		return;
269712bd3c8bSSascha Wildner 
269812bd3c8bSSascha Wildner 	/* we need a parent HUB to do suspend */
269912bd3c8bSSascha Wildner 	if (udev->parent_hub == NULL)
270012bd3c8bSSascha Wildner 		return;
270112bd3c8bSSascha Wildner 
270212bd3c8bSSascha Wildner 	DPRINTF("udev=%p\n", udev);
270312bd3c8bSSascha Wildner 
270412bd3c8bSSascha Wildner 	/* check if the current device is a HUB */
270512bd3c8bSSascha Wildner 	if (udev->hub != NULL) {
270612bd3c8bSSascha Wildner 		nports = udev->hub->nports;
270712bd3c8bSSascha Wildner 
270812bd3c8bSSascha Wildner 		/* check if all devices on the HUB are suspended */
270912bd3c8bSSascha Wildner 		for (x = 0; x != nports; x++) {
271012bd3c8bSSascha Wildner 			child = usb_bus_port_get_device(udev->bus,
271112bd3c8bSSascha Wildner 			    udev->hub->ports + x);
271212bd3c8bSSascha Wildner 
271312bd3c8bSSascha Wildner 			if (child == NULL)
271412bd3c8bSSascha Wildner 				continue;
271512bd3c8bSSascha Wildner 
271612bd3c8bSSascha Wildner 			if (child->flags.self_suspended)
271712bd3c8bSSascha Wildner 				continue;
271812bd3c8bSSascha Wildner 
271912bd3c8bSSascha Wildner 			DPRINTFN(1, "Port %u is busy on the HUB!\n", x + 1);
272012bd3c8bSSascha Wildner 			return;
272112bd3c8bSSascha Wildner 		}
272212bd3c8bSSascha Wildner 	}
272312bd3c8bSSascha Wildner 
272412bd3c8bSSascha Wildner 	if (usb_peer_can_wakeup(udev)) {
272512bd3c8bSSascha Wildner 		/*
272612bd3c8bSSascha Wildner 		 * This request needs to be done before we set
272712bd3c8bSSascha Wildner 		 * "udev->flags.self_suspended":
272812bd3c8bSSascha Wildner 		 */
272912bd3c8bSSascha Wildner 
273012bd3c8bSSascha Wildner 		/* allow device to do remote wakeup */
273112bd3c8bSSascha Wildner 		err = usbd_req_set_device_feature(udev,
273212bd3c8bSSascha Wildner 		    NULL, UF_DEVICE_REMOTE_WAKEUP);
273312bd3c8bSSascha Wildner 		if (err) {
273412bd3c8bSSascha Wildner 			DPRINTFN(0, "Setting device "
273512bd3c8bSSascha Wildner 			    "remote wakeup failed\n");
273612bd3c8bSSascha Wildner 		}
273712bd3c8bSSascha Wildner 	}
273812bd3c8bSSascha Wildner 
273912bd3c8bSSascha Wildner 	USB_BUS_LOCK(udev->bus);
274012bd3c8bSSascha Wildner 	/*
274112bd3c8bSSascha Wildner 	 * Checking for suspend condition and setting suspended bit
274212bd3c8bSSascha Wildner 	 * must be atomic!
274312bd3c8bSSascha Wildner 	 */
274412bd3c8bSSascha Wildner 	err = usb_peer_should_wakeup(udev);
274512bd3c8bSSascha Wildner 	if (err == 0) {
274612bd3c8bSSascha Wildner 		/*
274712bd3c8bSSascha Wildner 		 * Set that this device is suspended. This variable
274812bd3c8bSSascha Wildner 		 * must be set before calling USB controller suspend
274912bd3c8bSSascha Wildner 		 * callbacks.
275012bd3c8bSSascha Wildner 		 */
275112bd3c8bSSascha Wildner 		udev->flags.self_suspended = 1;
275212bd3c8bSSascha Wildner 	}
275312bd3c8bSSascha Wildner 	USB_BUS_UNLOCK(udev->bus);
275412bd3c8bSSascha Wildner 
275512bd3c8bSSascha Wildner 	if (err != 0) {
275612bd3c8bSSascha Wildner 		if (usb_peer_can_wakeup(udev)) {
275712bd3c8bSSascha Wildner 			/* allow device to do remote wakeup */
275812bd3c8bSSascha Wildner 			err = usbd_req_clear_device_feature(udev,
275912bd3c8bSSascha Wildner 			    NULL, UF_DEVICE_REMOTE_WAKEUP);
276012bd3c8bSSascha Wildner 			if (err) {
276112bd3c8bSSascha Wildner 				DPRINTFN(0, "Setting device "
276212bd3c8bSSascha Wildner 				    "remote wakeup failed\n");
276312bd3c8bSSascha Wildner 			}
276412bd3c8bSSascha Wildner 		}
276512bd3c8bSSascha Wildner 
276612bd3c8bSSascha Wildner 		if (udev->flags.usb_mode == USB_MODE_DEVICE) {
276712bd3c8bSSascha Wildner 			/* resume parent HUB first */
276812bd3c8bSSascha Wildner 			usb_dev_resume_peer(udev->parent_hub);
276912bd3c8bSSascha Wildner 
277012bd3c8bSSascha Wildner 			/* reduce chance of instant resume failure by waiting a little bit */
277112bd3c8bSSascha Wildner 			usb_pause_mtx(NULL, USB_MS_TO_TICKS(20));
277212bd3c8bSSascha Wildner 
277312bd3c8bSSascha Wildner 			/* resume current port (Valid in Host and Device Mode) */
277412bd3c8bSSascha Wildner 			err = usbd_req_clear_port_feature(udev->parent_hub,
277512bd3c8bSSascha Wildner 			    NULL, udev->port_no, UHF_PORT_SUSPEND);
277612bd3c8bSSascha Wildner 
277712bd3c8bSSascha Wildner 			/* resume settle time */
27785e41ab93SMarkus Pfeiffer 			usb_pause_mtx(NULL, USB_MS_TO_TICKS(usb_port_resume_delay));
277912bd3c8bSSascha Wildner 		}
278012bd3c8bSSascha Wildner 		DPRINTF("Suspend was cancelled!\n");
278112bd3c8bSSascha Wildner 		return;
278212bd3c8bSSascha Wildner 	}
278312bd3c8bSSascha Wildner 
278412bd3c8bSSascha Wildner 	usbd_sr_lock(udev);
278512bd3c8bSSascha Wildner 
278612bd3c8bSSascha Wildner 	/* notify all sub-devices about suspend */
278712bd3c8bSSascha Wildner 	err = usb_suspend_resume(udev, 1);
278812bd3c8bSSascha Wildner 
278912bd3c8bSSascha Wildner 	usbd_sr_unlock(udev);
279012bd3c8bSSascha Wildner 
279112bd3c8bSSascha Wildner 	if (udev->bus->methods->device_suspend != NULL) {
279212bd3c8bSSascha Wildner 		usb_timeout_t temp;
279312bd3c8bSSascha Wildner 
279412bd3c8bSSascha Wildner 		/* suspend device on the USB controller */
279512bd3c8bSSascha Wildner 		(udev->bus->methods->device_suspend) (udev);
279612bd3c8bSSascha Wildner 
279712bd3c8bSSascha Wildner 		/* do DMA delay */
279812bd3c8bSSascha Wildner 		temp = usbd_get_dma_delay(udev);
279912bd3c8bSSascha Wildner 		if (temp != 0)
280012bd3c8bSSascha Wildner 			usb_pause_mtx(NULL, USB_MS_TO_TICKS(temp));
280112bd3c8bSSascha Wildner 
280212bd3c8bSSascha Wildner 	}
280312bd3c8bSSascha Wildner 
280412bd3c8bSSascha Wildner 	if (usb_device_20_compatible(udev)) {
280512bd3c8bSSascha Wildner 		/* suspend current port */
280612bd3c8bSSascha Wildner 		err = usbd_req_set_port_feature(udev->parent_hub,
280712bd3c8bSSascha Wildner 		    NULL, udev->port_no, UHF_PORT_SUSPEND);
280812bd3c8bSSascha Wildner 		if (err) {
280912bd3c8bSSascha Wildner 			DPRINTFN(0, "Suspending port failed\n");
281012bd3c8bSSascha Wildner 			return;
281112bd3c8bSSascha Wildner 		}
281212bd3c8bSSascha Wildner 	} else {
281312bd3c8bSSascha Wildner 		/* suspend current port */
281412bd3c8bSSascha Wildner 		err = usbd_req_set_port_link_state(udev->parent_hub,
281512bd3c8bSSascha Wildner 		    NULL, udev->port_no, UPS_PORT_LS_U3);
281612bd3c8bSSascha Wildner 		if (err) {
281712bd3c8bSSascha Wildner 			DPRINTFN(0, "Suspending port failed\n");
281812bd3c8bSSascha Wildner 			return;
281912bd3c8bSSascha Wildner 		}
282012bd3c8bSSascha Wildner 	}
282112bd3c8bSSascha Wildner 
282212bd3c8bSSascha Wildner 	udev = udev->parent_hub;
282312bd3c8bSSascha Wildner 	goto repeat;
282412bd3c8bSSascha Wildner }
282512bd3c8bSSascha Wildner 
282612bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
282712bd3c8bSSascha Wildner  *	usbd_set_power_mode
282812bd3c8bSSascha Wildner  *
282912bd3c8bSSascha Wildner  * This function will set the power mode, see USB_POWER_MODE_XXX for a
283012bd3c8bSSascha Wildner  * USB device.
283112bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
283212bd3c8bSSascha Wildner void
usbd_set_power_mode(struct usb_device * udev,uint8_t power_mode)283312bd3c8bSSascha Wildner usbd_set_power_mode(struct usb_device *udev, uint8_t power_mode)
283412bd3c8bSSascha Wildner {
283512bd3c8bSSascha Wildner 	/* filter input argument */
283612bd3c8bSSascha Wildner 	if ((power_mode != USB_POWER_MODE_ON) &&
283712bd3c8bSSascha Wildner 	    (power_mode != USB_POWER_MODE_OFF))
283812bd3c8bSSascha Wildner 		power_mode = USB_POWER_MODE_SAVE;
283912bd3c8bSSascha Wildner 
284012bd3c8bSSascha Wildner 	power_mode = usbd_filter_power_mode(udev, power_mode);
284112bd3c8bSSascha Wildner 
284212bd3c8bSSascha Wildner 	udev->power_mode = power_mode;	/* update copy of power mode */
284312bd3c8bSSascha Wildner 
284412bd3c8bSSascha Wildner #if USB_HAVE_POWERD
284512bd3c8bSSascha Wildner 	usb_bus_power_update(udev->bus);
28468922de18SMarkus Pfeiffer #else
28478922de18SMarkus Pfeiffer 	usb_needs_explore(udev->bus, 0 /* no probe */ );
284812bd3c8bSSascha Wildner #endif
284912bd3c8bSSascha Wildner }
285012bd3c8bSSascha Wildner 
285112bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
285212bd3c8bSSascha Wildner  *	usbd_filter_power_mode
285312bd3c8bSSascha Wildner  *
285412bd3c8bSSascha Wildner  * This function filters the power mode based on hardware requirements.
285512bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
285612bd3c8bSSascha Wildner uint8_t
usbd_filter_power_mode(struct usb_device * udev,uint8_t power_mode)285712bd3c8bSSascha Wildner usbd_filter_power_mode(struct usb_device *udev, uint8_t power_mode)
285812bd3c8bSSascha Wildner {
28598922de18SMarkus Pfeiffer 	const struct usb_bus_methods *mtod;
286012bd3c8bSSascha Wildner 	int8_t temp;
286112bd3c8bSSascha Wildner 
286212bd3c8bSSascha Wildner 	mtod = udev->bus->methods;
286312bd3c8bSSascha Wildner 	temp = -1;
286412bd3c8bSSascha Wildner 
286512bd3c8bSSascha Wildner 	if (mtod->get_power_mode != NULL)
286612bd3c8bSSascha Wildner 		(mtod->get_power_mode) (udev, &temp);
286712bd3c8bSSascha Wildner 
286812bd3c8bSSascha Wildner 	/* check if we should not filter */
286912bd3c8bSSascha Wildner 	if (temp < 0)
287012bd3c8bSSascha Wildner 		return (power_mode);
287112bd3c8bSSascha Wildner 
287212bd3c8bSSascha Wildner 	/* use fixed power mode given by hardware driver */
287312bd3c8bSSascha Wildner 	return (temp);
287412bd3c8bSSascha Wildner }
287512bd3c8bSSascha Wildner 
287612bd3c8bSSascha Wildner /*------------------------------------------------------------------------*
287712bd3c8bSSascha Wildner  *	usbd_start_re_enumerate
287812bd3c8bSSascha Wildner  *
287912bd3c8bSSascha Wildner  * This function starts re-enumeration of the given USB device. This
288012bd3c8bSSascha Wildner  * function does not need to be called BUS-locked. This function does
288112bd3c8bSSascha Wildner  * not wait until the re-enumeration is completed.
288212bd3c8bSSascha Wildner  *------------------------------------------------------------------------*/
288312bd3c8bSSascha Wildner void
usbd_start_re_enumerate(struct usb_device * udev)288412bd3c8bSSascha Wildner usbd_start_re_enumerate(struct usb_device *udev)
288512bd3c8bSSascha Wildner {
28868922de18SMarkus Pfeiffer 	if (udev->re_enumerate_wait == USB_RE_ENUM_DONE) {
28878922de18SMarkus Pfeiffer 		udev->re_enumerate_wait = USB_RE_ENUM_START;
288812bd3c8bSSascha Wildner 		usb_needs_explore(udev->bus, 0);
288912bd3c8bSSascha Wildner 	}
289012bd3c8bSSascha Wildner }
289145f67c02SMarkus Pfeiffer 
289245f67c02SMarkus Pfeiffer /*-----------------------------------------------------------------------*
289345f67c02SMarkus Pfeiffer  *	usbd_start_set_config
289445f67c02SMarkus Pfeiffer  *
289545f67c02SMarkus Pfeiffer  * This function starts setting a USB configuration. This function
289645f67c02SMarkus Pfeiffer  * does not need to be called BUS-locked. This function does not wait
289745f67c02SMarkus Pfeiffer  * until the set USB configuratino is completed.
289845f67c02SMarkus Pfeiffer  *------------------------------------------------------------------------*/
289945f67c02SMarkus Pfeiffer usb_error_t
usbd_start_set_config(struct usb_device * udev,uint8_t index)290045f67c02SMarkus Pfeiffer usbd_start_set_config(struct usb_device *udev, uint8_t index)
290145f67c02SMarkus Pfeiffer {
290245f67c02SMarkus Pfeiffer 	if (udev->re_enumerate_wait == USB_RE_ENUM_DONE) {
290345f67c02SMarkus Pfeiffer 		if (udev->curr_config_index == index) {
290445f67c02SMarkus Pfeiffer 			/* no change needed */
290545f67c02SMarkus Pfeiffer 			return (0);
290645f67c02SMarkus Pfeiffer 		}
290745f67c02SMarkus Pfeiffer 		udev->next_config_index = index;
290845f67c02SMarkus Pfeiffer 		udev->re_enumerate_wait = USB_RE_ENUM_SET_CONFIG;
290945f67c02SMarkus Pfeiffer 		usb_needs_explore(udev->bus, 0);
291045f67c02SMarkus Pfeiffer 		return (0);
291145f67c02SMarkus Pfeiffer 	} else if (udev->re_enumerate_wait == USB_RE_ENUM_SET_CONFIG) {
291245f67c02SMarkus Pfeiffer 		if (udev->next_config_index == index) {
291345f67c02SMarkus Pfeiffer 			/* no change needed */
291445f67c02SMarkus Pfeiffer 			return (0);
291545f67c02SMarkus Pfeiffer 		}
291645f67c02SMarkus Pfeiffer 	}
291745f67c02SMarkus Pfeiffer 	return (USB_ERR_PENDING_REQUESTS);
291845f67c02SMarkus Pfeiffer }
2919