1184610Salfred/* $FreeBSD: stable/10/sys/dev/usb/usb_hub.c 359318 2020-03-26 05:38:33Z hselasky $ */
2184610Salfred/*-
3184610Salfred * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
4184610Salfred * Copyright (c) 1998 Lennart Augustsson. All rights reserved.
5213435Shselasky * Copyright (c) 2008-2010 Hans Petter Selasky. All rights reserved.
6184610Salfred *
7184610Salfred * Redistribution and use in source and binary forms, with or without
8184610Salfred * modification, are permitted provided that the following conditions
9184610Salfred * are met:
10184610Salfred * 1. Redistributions of source code must retain the above copyright
11184610Salfred *    notice, this list of conditions and the following disclaimer.
12184610Salfred * 2. Redistributions in binary form must reproduce the above copyright
13184610Salfred *    notice, this list of conditions and the following disclaimer in the
14184610Salfred *    documentation and/or other materials provided with the distribution.
15184610Salfred *
16184610Salfred * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17184610Salfred * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18184610Salfred * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19184610Salfred * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20184610Salfred * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21184610Salfred * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22184610Salfred * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23184610Salfred * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24184610Salfred * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25184610Salfred * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26184610Salfred * SUCH DAMAGE.
27184610Salfred */
28184610Salfred
29184610Salfred/*
30190754Sthompsa * USB spec: http://www.usb.org/developers/docs/usbspec.zip
31184610Salfred */
32184610Salfred
33246122Shselasky#ifdef USB_GLOBAL_INCLUDE_FILE
34246122Shselasky#include USB_GLOBAL_INCLUDE_FILE
35246122Shselasky#else
36194677Sthompsa#include <sys/stdint.h>
37194677Sthompsa#include <sys/stddef.h>
38194677Sthompsa#include <sys/param.h>
39194677Sthompsa#include <sys/queue.h>
40194677Sthompsa#include <sys/types.h>
41194677Sthompsa#include <sys/systm.h>
42194677Sthompsa#include <sys/kernel.h>
43194677Sthompsa#include <sys/bus.h>
44194677Sthompsa#include <sys/module.h>
45194677Sthompsa#include <sys/lock.h>
46194677Sthompsa#include <sys/mutex.h>
47194677Sthompsa#include <sys/condvar.h>
48194677Sthompsa#include <sys/sysctl.h>
49194677Sthompsa#include <sys/sx.h>
50194677Sthompsa#include <sys/unistd.h>
51194677Sthompsa#include <sys/callout.h>
52194677Sthompsa#include <sys/malloc.h>
53194677Sthompsa#include <sys/priv.h>
54194677Sthompsa
55188942Sthompsa#include <dev/usb/usb.h>
56194677Sthompsa#include <dev/usb/usbdi.h>
57212136Sthompsa#include <dev/usb/usbdi_util.h>
58184610Salfred
59184610Salfred#define	USB_DEBUG_VAR uhub_debug
60184610Salfred
61188942Sthompsa#include <dev/usb/usb_core.h>
62188942Sthompsa#include <dev/usb/usb_process.h>
63188942Sthompsa#include <dev/usb/usb_device.h>
64188942Sthompsa#include <dev/usb/usb_request.h>
65188942Sthompsa#include <dev/usb/usb_debug.h>
66188942Sthompsa#include <dev/usb/usb_hub.h>
67188942Sthompsa#include <dev/usb/usb_util.h>
68188942Sthompsa#include <dev/usb/usb_busdma.h>
69188942Sthompsa#include <dev/usb/usb_transfer.h>
70188942Sthompsa#include <dev/usb/usb_dynamic.h>
71184610Salfred
72188942Sthompsa#include <dev/usb/usb_controller.h>
73188942Sthompsa#include <dev/usb/usb_bus.h>
74246122Shselasky#endif			/* USB_GLOBAL_INCLUDE_FILE */
75184610Salfred
76184610Salfred#define	UHUB_INTR_INTERVAL 250		/* ms */
77261105Shselaskyenum {
78261105Shselasky	UHUB_INTR_TRANSFER,
79261105Shselasky#if USB_HAVE_TT_SUPPORT
80261105Shselasky	UHUB_RESET_TT_TRANSFER,
81261105Shselasky#endif
82261105Shselasky	UHUB_N_TRANSFER,
83261105Shselasky};
84184610Salfred
85194677Sthompsa#ifdef USB_DEBUG
86184610Salfredstatic int uhub_debug = 0;
87184610Salfred
88227309Sedstatic SYSCTL_NODE(_hw_usb, OID_AUTO, uhub, CTLFLAG_RW, 0, "USB HUB");
89242126ShselaskySYSCTL_INT(_hw_usb_uhub, OID_AUTO, debug, CTLFLAG_RW | CTLFLAG_TUN, &uhub_debug, 0,
90184610Salfred    "Debug level");
91199675SthompsaTUNABLE_INT("hw.usb.uhub.debug", &uhub_debug);
92184610Salfred#endif
93184610Salfred
94190735Sthompsa#if USB_HAVE_POWERD
95194228Sthompsastatic int usb_power_timeout = 30;	/* seconds */
96186730Salfred
97192502SthompsaSYSCTL_INT(_hw_usb, OID_AUTO, power_timeout, CTLFLAG_RW,
98194228Sthompsa    &usb_power_timeout, 0, "USB power timeout");
99190735Sthompsa#endif
100186730Salfred
101273729Shselasky#if USB_HAVE_DISABLE_ENUM
102273729Shselaskystatic int usb_disable_enumeration = 0;
103273729ShselaskySYSCTL_INT(_hw_usb, OID_AUTO, disable_enumeration, CTLFLAG_RWTUN,
104324798Shselasky    &usb_disable_enumeration, 0, "Set to disable all USB device enumeration. "
105324798Shselasky	"This can secure against USB devices turning evil, "
106324798Shselasky	"for example a USB memory stick becoming a USB keyboard.");
107273729ShselaskyTUNABLE_INT("hw.usb.disable_enumeration", &usb_disable_enumeration);
108273729Shselasky
109273729Shselaskystatic int usb_disable_port_power = 0;
110273729ShselaskySYSCTL_INT(_hw_usb, OID_AUTO, disable_port_power, CTLFLAG_RWTUN,
111273729Shselasky    &usb_disable_port_power, 0, "Set to disable all USB port power.");
112273729ShselaskyTUNABLE_INT("hw.usb.disable_port_power", &usb_disable_port_power);
113273729Shselasky#endif
114273729Shselasky
115184610Salfredstruct uhub_current_state {
116184610Salfred	uint16_t port_change;
117184610Salfred	uint16_t port_status;
118184610Salfred};
119184610Salfred
120184610Salfredstruct uhub_softc {
121184610Salfred	struct uhub_current_state sc_st;/* current state */
122250207Shselasky#if (USB_HAVE_FIXED_PORT != 0)
123250207Shselasky	struct usb_hub sc_hub;
124250207Shselasky#endif
125184610Salfred	device_t sc_dev;		/* base device */
126196498Salfred	struct mtx sc_mtx;		/* our mutex */
127192984Sthompsa	struct usb_device *sc_udev;	/* USB device */
128192984Sthompsa	struct usb_xfer *sc_xfer[UHUB_N_TRANSFER];	/* interrupt xfer */
129273729Shselasky#if USB_HAVE_DISABLE_ENUM
130273729Shselasky	int sc_disable_enumeration;
131273729Shselasky	int sc_disable_port_power;
132273729Shselasky#endif
133343136Shselasky	uint8_t sc_usb_port_errors;	/* error counter */
134343136Shselasky#define	UHUB_USB_PORT_ERRORS_MAX 4
135184610Salfred	uint8_t	sc_flags;
136185087Salfred#define	UHUB_FLAG_DID_EXPLORE 0x01
137184610Salfred};
138184610Salfred
139184610Salfred#define	UHUB_PROTO(sc) ((sc)->sc_udev->ddesc.bDeviceProtocol)
140184610Salfred#define	UHUB_IS_HIGH_SPEED(sc) (UHUB_PROTO(sc) != UDPROTO_FSHUB)
141184610Salfred#define	UHUB_IS_SINGLE_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBSTT)
142234803Shselasky#define	UHUB_IS_MULTI_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBMTT)
143213435Shselasky#define	UHUB_IS_SUPER_SPEED(sc) (UHUB_PROTO(sc) == UDPROTO_SSHUB)
144184610Salfred
145184610Salfred/* prototypes for type checking: */
146184610Salfred
147184610Salfredstatic device_probe_t uhub_probe;
148184610Salfredstatic device_attach_t uhub_attach;
149184610Salfredstatic device_detach_t uhub_detach;
150186730Salfredstatic device_suspend_t uhub_suspend;
151186730Salfredstatic device_resume_t uhub_resume;
152184610Salfred
153184610Salfredstatic bus_driver_added_t uhub_driver_added;
154184610Salfredstatic bus_child_location_str_t uhub_child_location_string;
155184610Salfredstatic bus_child_pnpinfo_str_t uhub_child_pnpinfo_string;
156184610Salfred
157193045Sthompsastatic usb_callback_t uhub_intr_callback;
158261105Shselasky#if USB_HAVE_TT_SUPPORT
159261105Shselaskystatic usb_callback_t uhub_reset_tt_callback;
160261105Shselasky#endif
161184610Salfred
162194228Sthompsastatic void usb_dev_resume_peer(struct usb_device *udev);
163194228Sthompsastatic void usb_dev_suspend_peer(struct usb_device *udev);
164208008Sthompsastatic uint8_t usb_peer_should_wakeup(struct usb_device *udev);
165186730Salfred
166192984Sthompsastatic const struct usb_config uhub_config[UHUB_N_TRANSFER] = {
167184610Salfred
168261105Shselasky	[UHUB_INTR_TRANSFER] = {
169184610Salfred		.type = UE_INTERRUPT,
170184610Salfred		.endpoint = UE_ADDR_ANY,
171184610Salfred		.direction = UE_DIR_ANY,
172190734Sthompsa		.timeout = 0,
173190734Sthompsa		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
174190734Sthompsa		.bufsize = 0,	/* use wMaxPacketSize */
175190734Sthompsa		.callback = &uhub_intr_callback,
176190734Sthompsa		.interval = UHUB_INTR_INTERVAL,
177184610Salfred	},
178261105Shselasky#if USB_HAVE_TT_SUPPORT
179261105Shselasky	[UHUB_RESET_TT_TRANSFER] = {
180261105Shselasky		.type = UE_CONTROL,
181261105Shselasky		.endpoint = 0x00,	/* Control pipe */
182261105Shselasky		.direction = UE_DIR_ANY,
183261105Shselasky		.bufsize = sizeof(struct usb_device_request),
184261105Shselasky		.callback = &uhub_reset_tt_callback,
185261105Shselasky		.timeout = 1000,	/* 1 second */
186261105Shselasky		.usb_mode = USB_MODE_HOST,
187261105Shselasky	},
188261105Shselasky#endif
189184610Salfred};
190184610Salfred
191184610Salfred/*
192184610Salfred * driver instance for "hub" connected to "usb"
193184610Salfred * and "hub" connected to "hub"
194184610Salfred */
195184610Salfredstatic devclass_t uhub_devclass;
196184610Salfred
197190735Sthompsastatic device_method_t uhub_methods[] = {
198190735Sthompsa	DEVMETHOD(device_probe, uhub_probe),
199190735Sthompsa	DEVMETHOD(device_attach, uhub_attach),
200190735Sthompsa	DEVMETHOD(device_detach, uhub_detach),
201184610Salfred
202190735Sthompsa	DEVMETHOD(device_suspend, uhub_suspend),
203190735Sthompsa	DEVMETHOD(device_resume, uhub_resume),
204184610Salfred
205190735Sthompsa	DEVMETHOD(bus_child_location_str, uhub_child_location_string),
206190735Sthompsa	DEVMETHOD(bus_child_pnpinfo_str, uhub_child_pnpinfo_string),
207190735Sthompsa	DEVMETHOD(bus_driver_added, uhub_driver_added),
208241128Shselasky	DEVMETHOD_END
209190735Sthompsa};
210190735Sthompsa
211190735Sthompsastatic driver_t uhub_driver = {
212190735Sthompsa	.name = "uhub",
213190735Sthompsa	.methods = uhub_methods,
214184610Salfred	.size = sizeof(struct uhub_softc)
215184610Salfred};
216184610Salfred
217189275SthompsaDRIVER_MODULE(uhub, usbus, uhub_driver, uhub_devclass, 0, 0);
218189275SthompsaDRIVER_MODULE(uhub, uhub, uhub_driver, uhub_devclass, NULL, 0);
219212122SthompsaMODULE_VERSION(uhub, 1);
220184610Salfred
221184610Salfredstatic void
222194677Sthompsauhub_intr_callback(struct usb_xfer *xfer, usb_error_t error)
223184610Salfred{
224194677Sthompsa	struct uhub_softc *sc = usbd_xfer_softc(xfer);
225184610Salfred
226184610Salfred	switch (USB_GET_STATE(xfer)) {
227184610Salfred	case USB_ST_TRANSFERRED:
228184610Salfred		DPRINTFN(2, "\n");
229184610Salfred		/*
230184610Salfred		 * This is an indication that some port
231184610Salfred		 * has changed status. Notify the bus
232184610Salfred		 * event handler thread that we need
233184610Salfred		 * to be explored again:
234184610Salfred		 */
235194228Sthompsa		usb_needs_explore(sc->sc_udev->bus, 0);
236184610Salfred
237184610Salfred	case USB_ST_SETUP:
238194677Sthompsa		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
239194228Sthompsa		usbd_transfer_submit(xfer);
240187178Sthompsa		break;
241184610Salfred
242184610Salfred	default:			/* Error */
243184610Salfred		if (xfer->error != USB_ERR_CANCELLED) {
244187178Sthompsa			/*
245187178Sthompsa			 * Do a clear-stall. The "stall_pipe" flag
246187178Sthompsa			 * will get cleared before next callback by
247187178Sthompsa			 * the USB stack.
248187178Sthompsa			 */
249194677Sthompsa			usbd_xfer_set_stall(xfer);
250194677Sthompsa			usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
251194228Sthompsa			usbd_transfer_submit(xfer);
252184610Salfred		}
253187178Sthompsa		break;
254184610Salfred	}
255184610Salfred}
256184610Salfred
257184610Salfred/*------------------------------------------------------------------------*
258261105Shselasky *      uhub_reset_tt_proc
259261105Shselasky *
260261105Shselasky * This function starts the TT reset USB request
261261105Shselasky *------------------------------------------------------------------------*/
262261105Shselasky#if USB_HAVE_TT_SUPPORT
263261105Shselaskystatic void
264261105Shselaskyuhub_reset_tt_proc(struct usb_proc_msg *_pm)
265261105Shselasky{
266261105Shselasky	struct usb_udev_msg *pm = (void *)_pm;
267261105Shselasky	struct usb_device *udev = pm->udev;
268261105Shselasky	struct usb_hub *hub;
269261105Shselasky	struct uhub_softc *sc;
270261105Shselasky
271261105Shselasky	hub = udev->hub;
272261105Shselasky	if (hub == NULL)
273261105Shselasky		return;
274261105Shselasky	sc = hub->hubsoftc;
275261105Shselasky	if (sc == NULL)
276261105Shselasky		return;
277261105Shselasky
278261105Shselasky	/* Change lock */
279261105Shselasky	USB_BUS_UNLOCK(udev->bus);
280261105Shselasky	mtx_lock(&sc->sc_mtx);
281261105Shselasky	/* Start transfer */
282261105Shselasky	usbd_transfer_start(sc->sc_xfer[UHUB_RESET_TT_TRANSFER]);
283261105Shselasky	/* Change lock */
284261105Shselasky	mtx_unlock(&sc->sc_mtx);
285261105Shselasky	USB_BUS_LOCK(udev->bus);
286261105Shselasky}
287261105Shselasky#endif
288261105Shselasky
289261105Shselasky/*------------------------------------------------------------------------*
290261105Shselasky *      uhub_tt_buffer_reset_async_locked
291261105Shselasky *
292261105Shselasky * This function queues a TT reset for the given USB device and endpoint.
293261105Shselasky *------------------------------------------------------------------------*/
294261105Shselasky#if USB_HAVE_TT_SUPPORT
295261105Shselaskyvoid
296261105Shselaskyuhub_tt_buffer_reset_async_locked(struct usb_device *child, struct usb_endpoint *ep)
297261105Shselasky{
298261105Shselasky	struct usb_device_request req;
299261105Shselasky	struct usb_device *udev;
300261105Shselasky	struct usb_hub *hub;
301261105Shselasky	struct usb_port *up;
302261105Shselasky	uint16_t wValue;
303261105Shselasky	uint8_t port;
304261105Shselasky
305261105Shselasky	if (child == NULL || ep == NULL)
306261105Shselasky		return;
307261105Shselasky
308261105Shselasky	udev = child->parent_hs_hub;
309261105Shselasky	port = child->hs_port_no;
310261105Shselasky
311261105Shselasky	if (udev == NULL)
312261105Shselasky		return;
313261105Shselasky
314261105Shselasky	hub = udev->hub;
315261105Shselasky	if ((hub == NULL) ||
316261105Shselasky	    (udev->speed != USB_SPEED_HIGH) ||
317261105Shselasky	    (child->speed != USB_SPEED_LOW &&
318261105Shselasky	     child->speed != USB_SPEED_FULL) ||
319261105Shselasky	    (child->flags.usb_mode != USB_MODE_HOST) ||
320261105Shselasky	    (port == 0) || (ep->edesc == NULL)) {
321261105Shselasky		/* not applicable */
322261105Shselasky		return;
323261105Shselasky	}
324261105Shselasky
325261105Shselasky	USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
326261105Shselasky
327261105Shselasky	up = hub->ports + port - 1;
328261105Shselasky
329261105Shselasky	if (udev->ddesc.bDeviceClass == UDCLASS_HUB &&
330261105Shselasky	    udev->ddesc.bDeviceProtocol == UDPROTO_HSHUBSTT)
331261105Shselasky		port = 1;
332261105Shselasky
333261105Shselasky	/* if we already received a clear buffer request, reset the whole TT */
334261105Shselasky	if (up->req_reset_tt.bRequest != 0) {
335261105Shselasky		req.bmRequestType = UT_WRITE_CLASS_OTHER;
336261105Shselasky		req.bRequest = UR_RESET_TT;
337261105Shselasky		USETW(req.wValue, 0);
338261105Shselasky		req.wIndex[0] = port;
339261105Shselasky		req.wIndex[1] = 0;
340261105Shselasky		USETW(req.wLength, 0);
341261105Shselasky	} else {
342261105Shselasky		wValue = (ep->edesc->bEndpointAddress & 0xF) |
343261105Shselasky		      ((child->address & 0x7F) << 4) |
344261105Shselasky		      ((ep->edesc->bEndpointAddress & 0x80) << 8) |
345261105Shselasky		      ((ep->edesc->bmAttributes & 3) << 12);
346261105Shselasky
347261105Shselasky		req.bmRequestType = UT_WRITE_CLASS_OTHER;
348261105Shselasky		req.bRequest = UR_CLEAR_TT_BUFFER;
349261105Shselasky		USETW(req.wValue, wValue);
350261105Shselasky		req.wIndex[0] = port;
351261105Shselasky		req.wIndex[1] = 0;
352261105Shselasky		USETW(req.wLength, 0);
353261105Shselasky	}
354261105Shselasky	up->req_reset_tt = req;
355261105Shselasky	/* get reset transfer started */
356287274Shselasky	usb_proc_msignal(USB_BUS_TT_PROC(udev->bus),
357261105Shselasky	    &hub->tt_msg[0], &hub->tt_msg[1]);
358261105Shselasky}
359261105Shselasky#endif
360261105Shselasky
361261105Shselasky#if USB_HAVE_TT_SUPPORT
362261105Shselaskystatic void
363261105Shselaskyuhub_reset_tt_callback(struct usb_xfer *xfer, usb_error_t error)
364261105Shselasky{
365261105Shselasky	struct uhub_softc *sc;
366261105Shselasky	struct usb_device *udev;
367261105Shselasky	struct usb_port *up;
368261105Shselasky	uint8_t x;
369261105Shselasky
370261105Shselasky	DPRINTF("TT buffer reset\n");
371261105Shselasky
372261105Shselasky	sc = usbd_xfer_softc(xfer);
373261105Shselasky	udev = sc->sc_udev;
374261105Shselasky
375261105Shselasky	switch (USB_GET_STATE(xfer)) {
376261105Shselasky	case USB_ST_TRANSFERRED:
377261105Shselasky	case USB_ST_SETUP:
378261105Shselaskytr_setup:
379261105Shselasky		USB_BUS_LOCK(udev->bus);
380261105Shselasky		/* find first port which needs a TT reset */
381261105Shselasky		for (x = 0; x != udev->hub->nports; x++) {
382261105Shselasky			up = udev->hub->ports + x;
383261105Shselasky
384261105Shselasky			if (up->req_reset_tt.bRequest == 0)
385261105Shselasky				continue;
386261105Shselasky
387261105Shselasky			/* copy in the transfer */
388261105Shselasky			usbd_copy_in(xfer->frbuffers, 0, &up->req_reset_tt,
389261105Shselasky			    sizeof(up->req_reset_tt));
390261105Shselasky			/* reset buffer */
391261105Shselasky			memset(&up->req_reset_tt, 0, sizeof(up->req_reset_tt));
392261105Shselasky
393261105Shselasky			/* set length */
394261105Shselasky			usbd_xfer_set_frame_len(xfer, 0, sizeof(up->req_reset_tt));
395261105Shselasky			xfer->nframes = 1;
396261105Shselasky			USB_BUS_UNLOCK(udev->bus);
397261105Shselasky
398261105Shselasky			usbd_transfer_submit(xfer);
399261105Shselasky			return;
400261105Shselasky		}
401261105Shselasky		USB_BUS_UNLOCK(udev->bus);
402261105Shselasky		break;
403261105Shselasky
404261105Shselasky	default:
405261105Shselasky		if (error == USB_ERR_CANCELLED)
406261105Shselasky			break;
407261105Shselasky
408261105Shselasky		DPRINTF("TT buffer reset failed (%s)\n", usbd_errstr(error));
409261105Shselasky		goto tr_setup;
410261105Shselasky	}
411261105Shselasky}
412261105Shselasky#endif
413261105Shselasky
414261105Shselasky/*------------------------------------------------------------------------*
415261105Shselasky *      uhub_count_active_host_ports
416261105Shselasky *
417261105Shselasky * This function counts the number of active ports at the given speed.
418261105Shselasky *------------------------------------------------------------------------*/
419261105Shselaskyuint8_t
420261105Shselaskyuhub_count_active_host_ports(struct usb_device *udev, enum usb_dev_speed speed)
421261105Shselasky{
422261105Shselasky	struct uhub_softc *sc;
423261105Shselasky	struct usb_device *child;
424261105Shselasky	struct usb_hub *hub;
425261105Shselasky	struct usb_port *up;
426261105Shselasky	uint8_t retval = 0;
427261105Shselasky	uint8_t x;
428261105Shselasky
429261105Shselasky	if (udev == NULL)
430261105Shselasky		goto done;
431261105Shselasky	hub = udev->hub;
432261105Shselasky	if (hub == NULL)
433261105Shselasky		goto done;
434261105Shselasky	sc = hub->hubsoftc;
435261105Shselasky	if (sc == NULL)
436261105Shselasky		goto done;
437261105Shselasky
438261105Shselasky	for (x = 0; x != hub->nports; x++) {
439261105Shselasky		up = hub->ports + x;
440261105Shselasky		child = usb_bus_port_get_device(udev->bus, up);
441261105Shselasky		if (child != NULL &&
442261105Shselasky		    child->flags.usb_mode == USB_MODE_HOST &&
443261105Shselasky		    child->speed == speed)
444261105Shselasky			retval++;
445261105Shselasky	}
446261105Shselaskydone:
447261105Shselasky	return (retval);
448261105Shselasky}
449261105Shselasky
450267347Shselaskyvoid
451267347Shselaskyuhub_explore_handle_re_enumerate(struct usb_device *child)
452267347Shselasky{
453267347Shselasky	uint8_t do_unlock;
454267347Shselasky	usb_error_t err;
455267347Shselasky
456267347Shselasky	/* check if device should be re-enumerated */
457267347Shselasky	if (child->flags.usb_mode != USB_MODE_HOST)
458267347Shselasky		return;
459267347Shselasky
460267347Shselasky	do_unlock = usbd_enum_lock(child);
461267347Shselasky	switch (child->re_enumerate_wait) {
462267347Shselasky	case USB_RE_ENUM_START:
463267347Shselasky		err = usbd_set_config_index(child,
464267347Shselasky		    USB_UNCONFIG_INDEX);
465267347Shselasky		if (err != 0) {
466267347Shselasky			DPRINTF("Unconfigure failed: %s: Ignored.\n",
467267347Shselasky			    usbd_errstr(err));
468267347Shselasky		}
469267347Shselasky		if (child->parent_hub == NULL) {
470267347Shselasky			/* the root HUB cannot be re-enumerated */
471267347Shselasky			DPRINTFN(6, "cannot reset root HUB\n");
472267347Shselasky			err = 0;
473267347Shselasky		} else {
474267347Shselasky			err = usbd_req_re_enumerate(child, NULL);
475267347Shselasky		}
476267347Shselasky		if (err == 0)
477267347Shselasky			err = usbd_set_config_index(child, 0);
478267347Shselasky		if (err == 0) {
479267347Shselasky			err = usb_probe_and_attach(child,
480267347Shselasky			    USB_IFACE_INDEX_ANY);
481267347Shselasky		}
482267347Shselasky		child->re_enumerate_wait = USB_RE_ENUM_DONE;
483267347Shselasky		break;
484267347Shselasky
485267347Shselasky	case USB_RE_ENUM_PWR_OFF:
486267347Shselasky		/* get the device unconfigured */
487267347Shselasky		err = usbd_set_config_index(child,
488267347Shselasky		    USB_UNCONFIG_INDEX);
489267347Shselasky		if (err) {
490267347Shselasky			DPRINTFN(0, "Could not unconfigure "
491267347Shselasky			    "device (ignored)\n");
492267347Shselasky		}
493267347Shselasky		if (child->parent_hub == NULL) {
494267347Shselasky			/* the root HUB cannot be re-enumerated */
495267347Shselasky			DPRINTFN(6, "cannot set port feature\n");
496267347Shselasky			err = 0;
497267347Shselasky		} else {
498267347Shselasky			/* clear port enable */
499267347Shselasky			err = usbd_req_clear_port_feature(child->parent_hub,
500267347Shselasky			    NULL, child->port_no, UHF_PORT_ENABLE);
501267347Shselasky			if (err) {
502267347Shselasky				DPRINTFN(0, "Could not disable port "
503267347Shselasky				    "(ignored)\n");
504267347Shselasky			}
505267347Shselasky		}
506267347Shselasky		child->re_enumerate_wait = USB_RE_ENUM_DONE;
507267347Shselasky		break;
508267347Shselasky
509267347Shselasky	case USB_RE_ENUM_SET_CONFIG:
510267347Shselasky		err = usbd_set_config_index(child,
511267347Shselasky		    child->next_config_index);
512267347Shselasky		if (err != 0) {
513267347Shselasky			DPRINTF("Configure failed: %s: Ignored.\n",
514267347Shselasky			    usbd_errstr(err));
515267347Shselasky		} else {
516267347Shselasky			err = usb_probe_and_attach(child,
517267347Shselasky			    USB_IFACE_INDEX_ANY);
518267347Shselasky		}
519267347Shselasky		child->re_enumerate_wait = USB_RE_ENUM_DONE;
520267347Shselasky		break;
521267347Shselasky
522267347Shselasky	default:
523267347Shselasky		child->re_enumerate_wait = USB_RE_ENUM_DONE;
524267347Shselasky		break;
525267347Shselasky	}
526267347Shselasky	if (do_unlock)
527267347Shselasky		usbd_enum_unlock(child);
528267347Shselasky}
529267347Shselasky
530261105Shselasky/*------------------------------------------------------------------------*
531184610Salfred *	uhub_explore_sub - subroutine
532184610Salfred *
533184610Salfred * Return values:
534184610Salfred *    0: Success
535184610Salfred * Else: A control transaction failed
536184610Salfred *------------------------------------------------------------------------*/
537193045Sthompsastatic usb_error_t
538192984Sthompsauhub_explore_sub(struct uhub_softc *sc, struct usb_port *up)
539184610Salfred{
540192984Sthompsa	struct usb_bus *bus;
541192984Sthompsa	struct usb_device *child;
542184610Salfred	uint8_t refcount;
543193045Sthompsa	usb_error_t err;
544184610Salfred
545184610Salfred	bus = sc->sc_udev->bus;
546184610Salfred	err = 0;
547184610Salfred
548184610Salfred	/* get driver added refcount from USB bus */
549184610Salfred	refcount = bus->driver_added_refcount;
550184610Salfred
551184610Salfred	/* get device assosiated with the given port */
552194228Sthompsa	child = usb_bus_port_get_device(bus, up);
553184610Salfred	if (child == NULL) {
554184610Salfred		/* nothing to do */
555184610Salfred		goto done;
556184610Salfred	}
557213435Shselasky
558267347Shselasky	uhub_explore_handle_re_enumerate(child);
559213432Shselasky
560184610Salfred	/* check if probe and attach should be done */
561184610Salfred
562184610Salfred	if (child->driver_added_refcount != refcount) {
563184610Salfred		child->driver_added_refcount = refcount;
564194228Sthompsa		err = usb_probe_and_attach(child,
565184610Salfred		    USB_IFACE_INDEX_ANY);
566184610Salfred		if (err) {
567184610Salfred			goto done;
568184610Salfred		}
569184610Salfred	}
570184610Salfred	/* start control transfer, if device mode */
571184610Salfred
572213435Shselasky	if (child->flags.usb_mode == USB_MODE_DEVICE)
573207080Sthompsa		usbd_ctrl_transfer_setup(child);
574213435Shselasky
575184610Salfred	/* if a HUB becomes present, do a recursive HUB explore */
576184610Salfred
577213435Shselasky	if (child->hub)
578184610Salfred		err = (child->hub->explore) (child);
579213435Shselasky
580184610Salfreddone:
581184610Salfred	return (err);
582184610Salfred}
583184610Salfred
584184610Salfred/*------------------------------------------------------------------------*
585184610Salfred *	uhub_read_port_status - factored out code
586184610Salfred *------------------------------------------------------------------------*/
587193045Sthompsastatic usb_error_t
588184610Salfreduhub_read_port_status(struct uhub_softc *sc, uint8_t portno)
589184610Salfred{
590192984Sthompsa	struct usb_port_status ps;
591193045Sthompsa	usb_error_t err;
592184610Salfred
593343136Shselasky	if (sc->sc_usb_port_errors >= UHUB_USB_PORT_ERRORS_MAX) {
594343136Shselasky		DPRINTFN(4, "port %d, HUB looks dead, too many errors\n", portno);
595343136Shselasky		sc->sc_st.port_status = 0;
596343136Shselasky		sc->sc_st.port_change = 0;
597343136Shselasky		return (USB_ERR_TIMEOUT);
598343136Shselasky	}
599343136Shselasky
600194228Sthompsa	err = usbd_req_get_port_status(
601188986Sthompsa	    sc->sc_udev, NULL, &ps, portno);
602184610Salfred
603343136Shselasky	if (err == 0) {
604343136Shselasky		sc->sc_st.port_status = UGETW(ps.wPortStatus);
605343136Shselasky		sc->sc_st.port_change = UGETW(ps.wPortChange);
606343136Shselasky		sc->sc_usb_port_errors = 0;
607343136Shselasky	} else {
608343136Shselasky		sc->sc_st.port_status = 0;
609343136Shselasky		sc->sc_st.port_change = 0;
610343136Shselasky		sc->sc_usb_port_errors++;
611343136Shselasky	}
612184610Salfred
613184610Salfred	/* debugging print */
614184610Salfred
615184610Salfred	DPRINTFN(4, "port %d, wPortStatus=0x%04x, "
616184610Salfred	    "wPortChange=0x%04x, err=%s\n",
617184610Salfred	    portno, sc->sc_st.port_status,
618194228Sthompsa	    sc->sc_st.port_change, usbd_errstr(err));
619184610Salfred	return (err);
620184610Salfred}
621184610Salfred
622184610Salfred/*------------------------------------------------------------------------*
623184610Salfred *	uhub_reattach_port
624184610Salfred *
625184610Salfred * Returns:
626184610Salfred *    0: Success
627184610Salfred * Else: A control transaction failed
628184610Salfred *------------------------------------------------------------------------*/
629193045Sthompsastatic usb_error_t
630184610Salfreduhub_reattach_port(struct uhub_softc *sc, uint8_t portno)
631184610Salfred{
632192984Sthompsa	struct usb_device *child;
633192984Sthompsa	struct usb_device *udev;
634192500Sthompsa	enum usb_dev_speed speed;
635192500Sthompsa	enum usb_hc_mode mode;
636193045Sthompsa	usb_error_t err;
637230050Shselasky	uint16_t power_mask;
638184610Salfred	uint8_t timeout;
639184610Salfred
640184610Salfred	DPRINTF("reattaching port %d\n", portno);
641184610Salfred
642184610Salfred	timeout = 0;
643184610Salfred	udev = sc->sc_udev;
644194228Sthompsa	child = usb_bus_port_get_device(udev->bus,
645184610Salfred	    udev->hub->ports + portno - 1);
646184610Salfred
647184610Salfredrepeat:
648184610Salfred
649184610Salfred	/* first clear the port connection change bit */
650184610Salfred
651194228Sthompsa	err = usbd_req_clear_port_feature(udev, NULL,
652186730Salfred	    portno, UHF_C_PORT_CONNECTION);
653184610Salfred
654273729Shselasky	if (err)
655184610Salfred		goto error;
656273729Shselasky
657197553Sthompsa	/* check if there is a child */
658184610Salfred
659197553Sthompsa	if (child != NULL) {
660197553Sthompsa		/*
661197553Sthompsa		 * Free USB device and all subdevices, if any.
662197553Sthompsa		 */
663197553Sthompsa		usb_free_device(child, 0);
664184610Salfred		child = NULL;
665184610Salfred	}
666184610Salfred	/* get fresh status */
667184610Salfred
668184610Salfred	err = uhub_read_port_status(sc, portno);
669273729Shselasky	if (err)
670184610Salfred		goto error;
671273729Shselasky
672273729Shselasky#if USB_HAVE_DISABLE_ENUM
673273729Shselasky	/* check if we should skip enumeration from this USB HUB */
674273729Shselasky	if (usb_disable_enumeration != 0 ||
675273729Shselasky	    sc->sc_disable_enumeration != 0) {
676273729Shselasky		DPRINTF("Enumeration is disabled!\n");
677273729Shselasky		goto error;
678184610Salfred	}
679273729Shselasky#endif
680184610Salfred	/* check if nothing is connected to the port */
681184610Salfred
682273729Shselasky	if (!(sc->sc_st.port_status & UPS_CURRENT_CONNECT_STATUS))
683184610Salfred		goto error;
684273729Shselasky
685184610Salfred	/* check if there is no power on the port and print a warning */
686184610Salfred
687230032Shselasky	switch (udev->speed) {
688230032Shselasky	case USB_SPEED_HIGH:
689230032Shselasky	case USB_SPEED_FULL:
690230032Shselasky	case USB_SPEED_LOW:
691230050Shselasky		power_mask = UPS_PORT_POWER;
692230032Shselasky		break;
693230032Shselasky	case USB_SPEED_SUPER:
694230050Shselasky		if (udev->parent_hub == NULL)
695358875Shselasky			power_mask = 0;	/* XXX undefined */
696230050Shselasky		else
697230050Shselasky			power_mask = UPS_PORT_POWER_SS;
698230032Shselasky		break;
699230032Shselasky	default:
700230050Shselasky		power_mask = 0;
701230032Shselasky		break;
702184610Salfred	}
703358875Shselasky	if ((sc->sc_st.port_status & power_mask) != power_mask) {
704230050Shselasky		DPRINTF("WARNING: strange, connected port %d "
705230050Shselasky		    "has no power\n", portno);
706230050Shselasky	}
707230032Shselasky
708184610Salfred	/* check if the device is in Host Mode */
709184610Salfred
710184610Salfred	if (!(sc->sc_st.port_status & UPS_PORT_MODE_DEVICE)) {
711184610Salfred
712184610Salfred		DPRINTF("Port %d is in Host Mode\n", portno);
713184610Salfred
714186730Salfred		if (sc->sc_st.port_status & UPS_SUSPEND) {
715213435Shselasky			/*
716213435Shselasky			 * NOTE: Should not get here in SuperSpeed
717213435Shselasky			 * mode, because the HUB should report this
718213435Shselasky			 * bit as zero.
719213435Shselasky			 */
720186730Salfred			DPRINTF("Port %d was still "
721186730Salfred			    "suspended, clearing.\n", portno);
722213435Shselasky			err = usbd_req_clear_port_feature(udev,
723188986Sthompsa			    NULL, portno, UHF_PORT_SUSPEND);
724186730Salfred		}
725213435Shselasky
726184610Salfred		/* USB Host Mode */
727184610Salfred
728184610Salfred		/* wait for maximum device power up time */
729184610Salfred
730194228Sthompsa		usb_pause_mtx(NULL,
731241987Shselasky		    USB_MS_TO_TICKS(usb_port_powerup_delay));
732184610Salfred
733184610Salfred		/* reset port, which implies enabling it */
734184610Salfred
735194228Sthompsa		err = usbd_req_reset_port(udev, NULL, portno);
736184610Salfred
737184610Salfred		if (err) {
738184610Salfred			DPRINTFN(0, "port %d reset "
739184610Salfred			    "failed, error=%s\n",
740194228Sthompsa			    portno, usbd_errstr(err));
741184610Salfred			goto error;
742184610Salfred		}
743184610Salfred		/* get port status again, it might have changed during reset */
744184610Salfred
745184610Salfred		err = uhub_read_port_status(sc, portno);
746184610Salfred		if (err) {
747184610Salfred			goto error;
748184610Salfred		}
749184610Salfred		/* check if something changed during port reset */
750184610Salfred
751184610Salfred		if ((sc->sc_st.port_change & UPS_C_CONNECT_STATUS) ||
752184610Salfred		    (!(sc->sc_st.port_status & UPS_CURRENT_CONNECT_STATUS))) {
753184610Salfred			if (timeout) {
754184610Salfred				DPRINTFN(0, "giving up port reset "
755199816Sthompsa				    "- device vanished\n");
756184610Salfred				goto error;
757184610Salfred			}
758184610Salfred			timeout = 1;
759184610Salfred			goto repeat;
760184610Salfred		}
761184610Salfred	} else {
762184610Salfred		DPRINTF("Port %d is in Device Mode\n", portno);
763184610Salfred	}
764184610Salfred
765184610Salfred	/*
766184610Salfred	 * Figure out the device speed
767184610Salfred	 */
768187180Sthompsa	switch (udev->speed) {
769187180Sthompsa	case USB_SPEED_HIGH:
770187180Sthompsa		if (sc->sc_st.port_status & UPS_HIGH_SPEED)
771187180Sthompsa			speed = USB_SPEED_HIGH;
772187180Sthompsa		else if (sc->sc_st.port_status & UPS_LOW_SPEED)
773187180Sthompsa			speed = USB_SPEED_LOW;
774187180Sthompsa		else
775187180Sthompsa			speed = USB_SPEED_FULL;
776187180Sthompsa		break;
777187180Sthompsa	case USB_SPEED_FULL:
778187180Sthompsa		if (sc->sc_st.port_status & UPS_LOW_SPEED)
779187180Sthompsa			speed = USB_SPEED_LOW;
780187180Sthompsa		else
781187180Sthompsa			speed = USB_SPEED_FULL;
782187180Sthompsa		break;
783187180Sthompsa	case USB_SPEED_LOW:
784187180Sthompsa		speed = USB_SPEED_LOW;
785187180Sthompsa		break;
786213435Shselasky	case USB_SPEED_SUPER:
787213435Shselasky		if (udev->parent_hub == NULL) {
788213435Shselasky			/* Root HUB - special case */
789213435Shselasky			switch (sc->sc_st.port_status & UPS_OTHER_SPEED) {
790213435Shselasky			case 0:
791213435Shselasky				speed = USB_SPEED_FULL;
792213435Shselasky				break;
793213435Shselasky			case UPS_LOW_SPEED:
794213435Shselasky				speed = USB_SPEED_LOW;
795213435Shselasky				break;
796213435Shselasky			case UPS_HIGH_SPEED:
797213435Shselasky				speed = USB_SPEED_HIGH;
798213435Shselasky				break;
799213435Shselasky			default:
800213435Shselasky				speed = USB_SPEED_SUPER;
801213435Shselasky				break;
802213435Shselasky			}
803213435Shselasky		} else {
804213435Shselasky			speed = USB_SPEED_SUPER;
805213435Shselasky		}
806213435Shselasky		break;
807187180Sthompsa	default:
808187180Sthompsa		/* same speed like parent */
809187180Sthompsa		speed = udev->speed;
810187180Sthompsa		break;
811187180Sthompsa	}
812213435Shselasky	if (speed == USB_SPEED_SUPER) {
813213435Shselasky		err = usbd_req_set_hub_u1_timeout(udev, NULL,
814213435Shselasky		    portno, 128 - (2 * udev->depth));
815213435Shselasky		if (err) {
816213435Shselasky			DPRINTFN(0, "port %d U1 timeout "
817213435Shselasky			    "failed, error=%s\n",
818213435Shselasky			    portno, usbd_errstr(err));
819213435Shselasky		}
820213435Shselasky		err = usbd_req_set_hub_u2_timeout(udev, NULL,
821213435Shselasky		    portno, 128 - (2 * udev->depth));
822213435Shselasky		if (err) {
823213435Shselasky			DPRINTFN(0, "port %d U2 timeout "
824213435Shselasky			    "failed, error=%s\n",
825213435Shselasky			    portno, usbd_errstr(err));
826213435Shselasky		}
827213435Shselasky	}
828213435Shselasky
829184610Salfred	/*
830184610Salfred	 * Figure out the device mode
831184610Salfred	 *
832184610Salfred	 * NOTE: This part is currently FreeBSD specific.
833184610Salfred	 */
834241082Shselasky	if (udev->parent_hub != NULL) {
835241082Shselasky		/* inherit mode from the parent HUB */
836241082Shselasky		mode = udev->parent_hub->flags.usb_mode;
837241082Shselasky	} else if (sc->sc_st.port_status & UPS_PORT_MODE_DEVICE)
838192499Sthompsa		mode = USB_MODE_DEVICE;
839187180Sthompsa	else
840192499Sthompsa		mode = USB_MODE_HOST;
841184610Salfred
842184610Salfred	/* need to create a new child */
843194228Sthompsa	child = usb_alloc_device(sc->sc_dev, udev->bus, udev,
844192499Sthompsa	    udev->depth + 1, portno - 1, portno, speed, mode);
845184610Salfred	if (child == NULL) {
846199816Sthompsa		DPRINTFN(0, "could not allocate new device\n");
847184610Salfred		goto error;
848184610Salfred	}
849184610Salfred	return (0);			/* success */
850184610Salfred
851184610Salfrederror:
852197553Sthompsa	if (child != NULL) {
853197553Sthompsa		/*
854197553Sthompsa		 * Free USB device and all subdevices, if any.
855197553Sthompsa		 */
856197553Sthompsa		usb_free_device(child, 0);
857184610Salfred		child = NULL;
858184610Salfred	}
859184610Salfred	if (err == 0) {
860184610Salfred		if (sc->sc_st.port_status & UPS_PORT_ENABLED) {
861194228Sthompsa			err = usbd_req_clear_port_feature(
862188986Sthompsa			    sc->sc_udev, NULL,
863184610Salfred			    portno, UHF_PORT_ENABLE);
864184610Salfred		}
865184610Salfred	}
866184610Salfred	if (err) {
867184610Salfred		DPRINTFN(0, "device problem (%s), "
868194228Sthompsa		    "disabling port %d\n", usbd_errstr(err), portno);
869184610Salfred	}
870184610Salfred	return (err);
871184610Salfred}
872184610Salfred
873184610Salfred/*------------------------------------------------------------------------*
874213435Shselasky *	usb_device_20_compatible
875213435Shselasky *
876213435Shselasky * Returns:
877213435Shselasky *    0: HUB does not support suspend and resume
878213435Shselasky * Else: HUB supports suspend and resume
879213435Shselasky *------------------------------------------------------------------------*/
880213435Shselaskystatic uint8_t
881213435Shselaskyusb_device_20_compatible(struct usb_device *udev)
882213435Shselasky{
883213435Shselasky	if (udev == NULL)
884213435Shselasky		return (0);
885213435Shselasky	switch (udev->speed) {
886213435Shselasky	case USB_SPEED_LOW:
887213435Shselasky	case USB_SPEED_FULL:
888213435Shselasky	case USB_SPEED_HIGH:
889213435Shselasky		return (1);
890213435Shselasky	default:
891213435Shselasky		return (0);
892213435Shselasky	}
893213435Shselasky}
894213435Shselasky
895213435Shselasky/*------------------------------------------------------------------------*
896184610Salfred *	uhub_suspend_resume_port
897184610Salfred *
898184610Salfred * Returns:
899184610Salfred *    0: Success
900184610Salfred * Else: A control transaction failed
901184610Salfred *------------------------------------------------------------------------*/
902193045Sthompsastatic usb_error_t
903184610Salfreduhub_suspend_resume_port(struct uhub_softc *sc, uint8_t portno)
904184610Salfred{
905192984Sthompsa	struct usb_device *child;
906192984Sthompsa	struct usb_device *udev;
907184610Salfred	uint8_t is_suspend;
908193045Sthompsa	usb_error_t err;
909184610Salfred
910184610Salfred	DPRINTF("port %d\n", portno);
911184610Salfred
912184610Salfred	udev = sc->sc_udev;
913194228Sthompsa	child = usb_bus_port_get_device(udev->bus,
914184610Salfred	    udev->hub->ports + portno - 1);
915184610Salfred
916184610Salfred	/* first clear the port suspend change bit */
917184610Salfred
918213435Shselasky	if (usb_device_20_compatible(udev)) {
919213435Shselasky		err = usbd_req_clear_port_feature(udev, NULL,
920213435Shselasky		    portno, UHF_C_PORT_SUSPEND);
921213435Shselasky	} else {
922213435Shselasky		err = usbd_req_clear_port_feature(udev, NULL,
923213435Shselasky		    portno, UHF_C_PORT_LINK_STATE);
924213435Shselasky	}
925213435Shselasky
926184610Salfred	if (err) {
927186730Salfred		DPRINTF("clearing suspend failed.\n");
928184610Salfred		goto done;
929184610Salfred	}
930184610Salfred	/* get fresh status */
931184610Salfred
932184610Salfred	err = uhub_read_port_status(sc, portno);
933184610Salfred	if (err) {
934186730Salfred		DPRINTF("reading port status failed.\n");
935184610Salfred		goto done;
936184610Salfred	}
937213435Shselasky	/* convert current state */
938184610Salfred
939213435Shselasky	if (usb_device_20_compatible(udev)) {
940213435Shselasky		if (sc->sc_st.port_status & UPS_SUSPEND) {
941213435Shselasky			is_suspend = 1;
942213435Shselasky		} else {
943213435Shselasky			is_suspend = 0;
944213435Shselasky		}
945184610Salfred	} else {
946213435Shselasky		switch (UPS_PORT_LINK_STATE_GET(sc->sc_st.port_status)) {
947230091Shselasky		case UPS_PORT_LS_U3:
948230091Shselasky			is_suspend = 1;
949230091Shselasky			break;
950230091Shselasky		case UPS_PORT_LS_SS_INA:
951230091Shselasky			usbd_req_warm_reset_port(udev, NULL, portno);
952213435Shselasky			is_suspend = 0;
953213435Shselasky			break;
954213435Shselasky		default:
955230091Shselasky			is_suspend = 0;
956213435Shselasky			break;
957213435Shselasky		}
958184610Salfred	}
959186730Salfred
960186730Salfred	DPRINTF("suspended=%u\n", is_suspend);
961186730Salfred
962184610Salfred	/* do the suspend or resume */
963184610Salfred
964184610Salfred	if (child) {
965186730Salfred		/*
966186730Salfred		 * This code handle two cases: 1) Host Mode - we can only
967186730Salfred		 * receive resume here 2) Device Mode - we can receive
968186730Salfred		 * suspend and resume here
969186730Salfred		 */
970186730Salfred		if (is_suspend == 0)
971194228Sthompsa			usb_dev_resume_peer(child);
972230032Shselasky		else if (child->flags.usb_mode == USB_MODE_DEVICE)
973194228Sthompsa			usb_dev_suspend_peer(child);
974184610Salfred	}
975184610Salfreddone:
976184610Salfred	return (err);
977184610Salfred}
978184610Salfred
979184610Salfred/*------------------------------------------------------------------------*
980190735Sthompsa *	uhub_root_interrupt
981190735Sthompsa *
982190735Sthompsa * This function is called when a Root HUB interrupt has
983190735Sthompsa * happened. "ptr" and "len" makes up the Root HUB interrupt
984190735Sthompsa * packet. This function is called having the "bus_mtx" locked.
985190735Sthompsa *------------------------------------------------------------------------*/
986190735Sthompsavoid
987192984Sthompsauhub_root_intr(struct usb_bus *bus, const uint8_t *ptr, uint8_t len)
988190735Sthompsa{
989190735Sthompsa	USB_BUS_LOCK_ASSERT(bus, MA_OWNED);
990190735Sthompsa
991194228Sthompsa	usb_needs_explore(bus, 0);
992190735Sthompsa}
993190735Sthompsa
994213435Shselaskystatic uint8_t
995213435Shselaskyuhub_is_too_deep(struct usb_device *udev)
996213435Shselasky{
997213435Shselasky	switch (udev->speed) {
998213435Shselasky	case USB_SPEED_FULL:
999213435Shselasky	case USB_SPEED_LOW:
1000213435Shselasky	case USB_SPEED_HIGH:
1001213435Shselasky		if (udev->depth > USB_HUB_MAX_DEPTH)
1002213435Shselasky			return (1);
1003213435Shselasky		break;
1004213435Shselasky	case USB_SPEED_SUPER:
1005213435Shselasky		if (udev->depth > USB_SS_HUB_DEPTH_MAX)
1006213435Shselasky			return (1);
1007213435Shselasky		break;
1008213435Shselasky	default:
1009213435Shselasky		break;
1010213435Shselasky	}
1011213435Shselasky	return (0);
1012213435Shselasky}
1013213435Shselasky
1014190735Sthompsa/*------------------------------------------------------------------------*
1015184610Salfred *	uhub_explore
1016184610Salfred *
1017184610Salfred * Returns:
1018184610Salfred *     0: Success
1019184610Salfred *  Else: Failure
1020184610Salfred *------------------------------------------------------------------------*/
1021193045Sthompsastatic usb_error_t
1022192984Sthompsauhub_explore(struct usb_device *udev)
1023184610Salfred{
1024192984Sthompsa	struct usb_hub *hub;
1025184610Salfred	struct uhub_softc *sc;
1026192984Sthompsa	struct usb_port *up;
1027193045Sthompsa	usb_error_t err;
1028184610Salfred	uint8_t portno;
1029184610Salfred	uint8_t x;
1030246759Shselasky	uint8_t do_unlock;
1031184610Salfred
1032184610Salfred	hub = udev->hub;
1033184610Salfred	sc = hub->hubsoftc;
1034184610Salfred
1035184610Salfred	DPRINTFN(11, "udev=%p addr=%d\n", udev, udev->address);
1036184610Salfred
1037213435Shselasky	/* ignore devices that are too deep */
1038213435Shselasky	if (uhub_is_too_deep(udev))
1039184610Salfred		return (USB_ERR_TOO_DEEP);
1040191824Sthompsa
1041213435Shselasky	/* check if device is suspended */
1042191824Sthompsa	if (udev->flags.self_suspended) {
1043186730Salfred		/* need to wait until the child signals resume */
1044186730Salfred		DPRINTF("Device is suspended!\n");
1045186730Salfred		return (0);
1046186730Salfred	}
1047228758Shselasky
1048228758Shselasky	/*
1049228758Shselasky	 * Make sure we don't race against user-space applications
1050228758Shselasky	 * like LibUSB:
1051228758Shselasky	 */
1052246759Shselasky	do_unlock = usbd_enum_lock(udev);
1053228758Shselasky
1054184610Salfred	for (x = 0; x != hub->nports; x++) {
1055184610Salfred		up = hub->ports + x;
1056184610Salfred		portno = x + 1;
1057184610Salfred
1058184610Salfred		err = uhub_read_port_status(sc, portno);
1059184610Salfred		if (err) {
1060184610Salfred			/* most likely the HUB is gone */
1061184610Salfred			break;
1062184610Salfred		}
1063186730Salfred		if (sc->sc_st.port_change & UPS_C_OVERCURRENT_INDICATOR) {
1064186730Salfred			DPRINTF("Overcurrent on port %u.\n", portno);
1065194228Sthompsa			err = usbd_req_clear_port_feature(
1066188986Sthompsa			    udev, NULL, portno, UHF_C_PORT_OVER_CURRENT);
1067186730Salfred			if (err) {
1068186730Salfred				/* most likely the HUB is gone */
1069186730Salfred				break;
1070186730Salfred			}
1071186730Salfred		}
1072185087Salfred		if (!(sc->sc_flags & UHUB_FLAG_DID_EXPLORE)) {
1073185087Salfred			/*
1074185087Salfred			 * Fake a connect status change so that the
1075185087Salfred			 * status gets checked initially!
1076185087Salfred			 */
1077185087Salfred			sc->sc_st.port_change |=
1078185087Salfred			    UPS_C_CONNECT_STATUS;
1079185087Salfred		}
1080184610Salfred		if (sc->sc_st.port_change & UPS_C_PORT_ENABLED) {
1081194228Sthompsa			err = usbd_req_clear_port_feature(
1082188986Sthompsa			    udev, NULL, portno, UHF_C_PORT_ENABLE);
1083184610Salfred			if (err) {
1084184610Salfred				/* most likely the HUB is gone */
1085184610Salfred				break;
1086184610Salfred			}
1087184610Salfred			if (sc->sc_st.port_change & UPS_C_CONNECT_STATUS) {
1088184610Salfred				/*
1089184610Salfred				 * Ignore the port error if the device
1090184610Salfred				 * has vanished !
1091184610Salfred				 */
1092184610Salfred			} else if (sc->sc_st.port_status & UPS_PORT_ENABLED) {
1093184610Salfred				DPRINTFN(0, "illegal enable change, "
1094184610Salfred				    "port %d\n", portno);
1095184610Salfred			} else {
1096184610Salfred
1097184610Salfred				if (up->restartcnt == USB_RESTART_MAX) {
1098184610Salfred					/* XXX could try another speed ? */
1099184610Salfred					DPRINTFN(0, "port error, giving up "
1100184610Salfred					    "port %d\n", portno);
1101184610Salfred				} else {
1102185087Salfred					sc->sc_st.port_change |=
1103185087Salfred					    UPS_C_CONNECT_STATUS;
1104184610Salfred					up->restartcnt++;
1105184610Salfred				}
1106184610Salfred			}
1107184610Salfred		}
1108184610Salfred		if (sc->sc_st.port_change & UPS_C_CONNECT_STATUS) {
1109184610Salfred			err = uhub_reattach_port(sc, portno);
1110184610Salfred			if (err) {
1111184610Salfred				/* most likely the HUB is gone */
1112184610Salfred				break;
1113184610Salfred			}
1114184610Salfred		}
1115230091Shselasky		if (sc->sc_st.port_change & (UPS_C_SUSPEND |
1116230091Shselasky		    UPS_C_PORT_LINK_STATE)) {
1117184610Salfred			err = uhub_suspend_resume_port(sc, portno);
1118184610Salfred			if (err) {
1119184610Salfred				/* most likely the HUB is gone */
1120184610Salfred				break;
1121184610Salfred			}
1122184610Salfred		}
1123184610Salfred		err = uhub_explore_sub(sc, up);
1124184610Salfred		if (err) {
1125184610Salfred			/* no device(s) present */
1126184610Salfred			continue;
1127184610Salfred		}
1128184610Salfred		/* explore succeeded - reset restart counter */
1129184610Salfred		up->restartcnt = 0;
1130184610Salfred	}
1131185087Salfred
1132246759Shselasky	if (do_unlock)
1133246759Shselasky		usbd_enum_unlock(udev);
1134228758Shselasky
1135185087Salfred	/* initial status checked */
1136185087Salfred	sc->sc_flags |= UHUB_FLAG_DID_EXPLORE;
1137185087Salfred
1138185087Salfred	/* return success */
1139184610Salfred	return (USB_ERR_NORMAL_COMPLETION);
1140184610Salfred}
1141184610Salfred
1142184610Salfredstatic int
1143184610Salfreduhub_probe(device_t dev)
1144184610Salfred{
1145192984Sthompsa	struct usb_attach_arg *uaa = device_get_ivars(dev);
1146184610Salfred
1147213435Shselasky	if (uaa->usb_mode != USB_MODE_HOST)
1148184610Salfred		return (ENXIO);
1149213435Shselasky
1150184610Salfred	/*
1151213435Shselasky	 * The subclass for USB HUBs is currently ignored because it
1152213435Shselasky	 * is 0 for some and 1 for others.
1153184610Salfred	 */
1154213435Shselasky	if (uaa->info.bConfigIndex == 0 &&
1155213435Shselasky	    uaa->info.bDeviceClass == UDCLASS_HUB)
1156184610Salfred		return (0);
1157213435Shselasky
1158184610Salfred	return (ENXIO);
1159184610Salfred}
1160184610Salfred
1161213435Shselasky/* NOTE: The information returned by this function can be wrong. */
1162213435Shselaskyusb_error_t
1163213435Shselaskyuhub_query_info(struct usb_device *udev, uint8_t *pnports, uint8_t *ptt)
1164213435Shselasky{
1165213435Shselasky	struct usb_hub_descriptor hubdesc20;
1166213435Shselasky	struct usb_hub_ss_descriptor hubdesc30;
1167213435Shselasky	usb_error_t err;
1168213435Shselasky	uint8_t nports;
1169213435Shselasky	uint8_t tt;
1170213435Shselasky
1171213435Shselasky	if (udev->ddesc.bDeviceClass != UDCLASS_HUB)
1172213435Shselasky		return (USB_ERR_INVAL);
1173213435Shselasky
1174213435Shselasky	nports = 0;
1175213435Shselasky	tt = 0;
1176213435Shselasky
1177213435Shselasky	switch (udev->speed) {
1178213435Shselasky	case USB_SPEED_LOW:
1179213435Shselasky	case USB_SPEED_FULL:
1180213435Shselasky	case USB_SPEED_HIGH:
1181213435Shselasky		/* assuming that there is one port */
1182213435Shselasky		err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, 1);
1183213435Shselasky		if (err) {
1184213435Shselasky			DPRINTFN(0, "getting USB 2.0 HUB descriptor failed,"
1185213435Shselasky			    "error=%s\n", usbd_errstr(err));
1186213435Shselasky			break;
1187213435Shselasky		}
1188213435Shselasky		nports = hubdesc20.bNbrPorts;
1189213435Shselasky		if (nports > 127)
1190213435Shselasky			nports = 127;
1191213435Shselasky
1192213435Shselasky		if (udev->speed == USB_SPEED_HIGH)
1193213435Shselasky			tt = (UGETW(hubdesc20.wHubCharacteristics) >> 5) & 3;
1194213435Shselasky		break;
1195213435Shselasky
1196213435Shselasky	case USB_SPEED_SUPER:
1197213435Shselasky		err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, 1);
1198213435Shselasky		if (err) {
1199213435Shselasky			DPRINTFN(0, "Getting USB 3.0 HUB descriptor failed,"
1200213435Shselasky			    "error=%s\n", usbd_errstr(err));
1201213435Shselasky			break;
1202213435Shselasky		}
1203213435Shselasky		nports = hubdesc30.bNbrPorts;
1204213435Shselasky		if (nports > 16)
1205213435Shselasky			nports = 16;
1206213435Shselasky		break;
1207213435Shselasky
1208213435Shselasky	default:
1209213435Shselasky		err = USB_ERR_INVAL;
1210213435Shselasky		break;
1211213435Shselasky	}
1212213435Shselasky
1213213435Shselasky	if (pnports != NULL)
1214213435Shselasky		*pnports = nports;
1215213435Shselasky
1216213435Shselasky	if (ptt != NULL)
1217213435Shselasky		*ptt = tt;
1218213435Shselasky
1219213435Shselasky	return (err);
1220213435Shselasky}
1221213435Shselasky
1222184610Salfredstatic int
1223184610Salfreduhub_attach(device_t dev)
1224184610Salfred{
1225184610Salfred	struct uhub_softc *sc = device_get_softc(dev);
1226192984Sthompsa	struct usb_attach_arg *uaa = device_get_ivars(dev);
1227192984Sthompsa	struct usb_device *udev = uaa->device;
1228192984Sthompsa	struct usb_device *parent_hub = udev->parent_hub;
1229192984Sthompsa	struct usb_hub *hub;
1230213435Shselasky	struct usb_hub_descriptor hubdesc20;
1231213435Shselasky	struct usb_hub_ss_descriptor hubdesc30;
1232273729Shselasky#if USB_HAVE_DISABLE_ENUM
1233273729Shselasky	struct sysctl_ctx_list *sysctl_ctx;
1234273729Shselasky	struct sysctl_oid *sysctl_tree;
1235273729Shselasky#endif
1236184610Salfred	uint16_t pwrdly;
1237250207Shselasky	uint16_t nports;
1238184610Salfred	uint8_t x;
1239184610Salfred	uint8_t portno;
1240184610Salfred	uint8_t removable;
1241184610Salfred	uint8_t iface_index;
1242193045Sthompsa	usb_error_t err;
1243184610Salfred
1244184610Salfred	sc->sc_udev = udev;
1245184610Salfred	sc->sc_dev = dev;
1246184610Salfred
1247196498Salfred	mtx_init(&sc->sc_mtx, "USB HUB mutex", NULL, MTX_DEF);
1248196498Salfred
1249194228Sthompsa	device_set_usb_desc(dev);
1250184610Salfred
1251184610Salfred	DPRINTFN(2, "depth=%d selfpowered=%d, parent=%p, "
1252184610Salfred	    "parent->selfpowered=%d\n",
1253184610Salfred	    udev->depth,
1254184610Salfred	    udev->flags.self_powered,
1255184610Salfred	    parent_hub,
1256184610Salfred	    parent_hub ?
1257184610Salfred	    parent_hub->flags.self_powered : 0);
1258184610Salfred
1259213435Shselasky	if (uhub_is_too_deep(udev)) {
1260213435Shselasky		DPRINTFN(0, "HUB at depth %d, "
1261213435Shselasky		    "exceeds maximum. HUB ignored\n", (int)udev->depth);
1262184610Salfred		goto error;
1263184610Salfred	}
1264213435Shselasky
1265184610Salfred	if (!udev->flags.self_powered && parent_hub &&
1266213435Shselasky	    !parent_hub->flags.self_powered) {
1267213435Shselasky		DPRINTFN(0, "Bus powered HUB connected to "
1268199816Sthompsa		    "bus powered HUB. HUB ignored\n");
1269184610Salfred		goto error;
1270184610Salfred	}
1271234803Shselasky
1272234803Shselasky	if (UHUB_IS_MULTI_TT(sc)) {
1273234803Shselasky		err = usbd_set_alt_interface_index(udev, 0, 1);
1274234803Shselasky		if (err) {
1275234803Shselasky			device_printf(dev, "MTT could not be enabled\n");
1276234803Shselasky			goto error;
1277234803Shselasky		}
1278234803Shselasky		device_printf(dev, "MTT enabled\n");
1279234803Shselasky	}
1280234803Shselasky
1281184610Salfred	/* get HUB descriptor */
1282184610Salfred
1283213435Shselasky	DPRINTFN(2, "Getting HUB descriptor\n");
1284184610Salfred
1285213435Shselasky	switch (udev->speed) {
1286213435Shselasky	case USB_SPEED_LOW:
1287213435Shselasky	case USB_SPEED_FULL:
1288213435Shselasky	case USB_SPEED_HIGH:
1289213435Shselasky		/* assuming that there is one port */
1290213435Shselasky		err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, 1);
1291213435Shselasky		if (err) {
1292213435Shselasky			DPRINTFN(0, "getting USB 2.0 HUB descriptor failed,"
1293213435Shselasky			    "error=%s\n", usbd_errstr(err));
1294213435Shselasky			goto error;
1295213435Shselasky		}
1296213435Shselasky		/* get number of ports */
1297213435Shselasky		nports = hubdesc20.bNbrPorts;
1298184610Salfred
1299213435Shselasky		/* get power delay */
1300213435Shselasky		pwrdly = ((hubdesc20.bPwrOn2PwrGood * UHD_PWRON_FACTOR) +
1301241987Shselasky		    usb_extra_power_up_time);
1302184610Salfred
1303184610Salfred		/* get complete HUB descriptor */
1304213435Shselasky		if (nports >= 8) {
1305213435Shselasky			/* check number of ports */
1306213435Shselasky			if (nports > 127) {
1307213435Shselasky				DPRINTFN(0, "Invalid number of USB 2.0 ports,"
1308213435Shselasky				    "error=%s\n", usbd_errstr(err));
1309213435Shselasky				goto error;
1310213435Shselasky			}
1311213435Shselasky			/* get complete HUB descriptor */
1312213435Shselasky			err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, nports);
1313213435Shselasky
1314213435Shselasky			if (err) {
1315213435Shselasky				DPRINTFN(0, "Getting USB 2.0 HUB descriptor failed,"
1316213435Shselasky				    "error=%s\n", usbd_errstr(err));
1317213435Shselasky				goto error;
1318213435Shselasky			}
1319213435Shselasky			if (hubdesc20.bNbrPorts != nports) {
1320213435Shselasky				DPRINTFN(0, "Number of ports changed\n");
1321213435Shselasky				goto error;
1322213435Shselasky			}
1323213435Shselasky		}
1324213435Shselasky		break;
1325213435Shselasky	case USB_SPEED_SUPER:
1326213435Shselasky		if (udev->parent_hub != NULL) {
1327213435Shselasky			err = usbd_req_set_hub_depth(udev, NULL,
1328213435Shselasky			    udev->depth - 1);
1329213435Shselasky			if (err) {
1330213435Shselasky				DPRINTFN(0, "Setting USB 3.0 HUB depth failed,"
1331213435Shselasky				    "error=%s\n", usbd_errstr(err));
1332213435Shselasky				goto error;
1333213435Shselasky			}
1334213435Shselasky		}
1335213435Shselasky		err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, 1);
1336213435Shselasky		if (err) {
1337213435Shselasky			DPRINTFN(0, "Getting USB 3.0 HUB descriptor failed,"
1338213435Shselasky			    "error=%s\n", usbd_errstr(err));
1339213435Shselasky			goto error;
1340213435Shselasky		}
1341213435Shselasky		/* get number of ports */
1342213435Shselasky		nports = hubdesc30.bNbrPorts;
1343213435Shselasky
1344213435Shselasky		/* get power delay */
1345213435Shselasky		pwrdly = ((hubdesc30.bPwrOn2PwrGood * UHD_PWRON_FACTOR) +
1346241987Shselasky		    usb_extra_power_up_time);
1347213435Shselasky
1348213435Shselasky		/* get complete HUB descriptor */
1349213435Shselasky		if (nports >= 8) {
1350213435Shselasky			/* check number of ports */
1351213435Shselasky			if (nports > ((udev->parent_hub != NULL) ? 15 : 127)) {
1352213435Shselasky				DPRINTFN(0, "Invalid number of USB 3.0 ports,"
1353213435Shselasky				    "error=%s\n", usbd_errstr(err));
1354213435Shselasky				goto error;
1355213435Shselasky			}
1356213435Shselasky			/* get complete HUB descriptor */
1357213435Shselasky			err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, nports);
1358213435Shselasky
1359213435Shselasky			if (err) {
1360213435Shselasky				DPRINTFN(0, "Getting USB 2.0 HUB descriptor failed,"
1361213435Shselasky				    "error=%s\n", usbd_errstr(err));
1362213435Shselasky				goto error;
1363213435Shselasky			}
1364213435Shselasky			if (hubdesc30.bNbrPorts != nports) {
1365213435Shselasky				DPRINTFN(0, "Number of ports changed\n");
1366213435Shselasky				goto error;
1367213435Shselasky			}
1368213435Shselasky		}
1369213435Shselasky		break;
1370213435Shselasky	default:
1371213435Shselasky		DPRINTF("Assuming HUB has only one port\n");
1372213435Shselasky		/* default number of ports */
1373213435Shselasky		nports = 1;
1374213435Shselasky		/* default power delay */
1375241987Shselasky		pwrdly = ((10 * UHD_PWRON_FACTOR) + usb_extra_power_up_time);
1376213435Shselasky		break;
1377184610Salfred	}
1378184610Salfred	if (nports == 0) {
1379199816Sthompsa		DPRINTFN(0, "portless HUB\n");
1380184610Salfred		goto error;
1381184610Salfred	}
1382250207Shselasky	if (nports > USB_MAX_PORTS) {
1383250207Shselasky		DPRINTF("Port limit exceeded\n");
1384250207Shselasky		goto error;
1385250207Shselasky	}
1386250207Shselasky#if (USB_HAVE_FIXED_PORT == 0)
1387184610Salfred	hub = malloc(sizeof(hub[0]) + (sizeof(hub->ports[0]) * nports),
1388184610Salfred	    M_USBDEV, M_WAITOK | M_ZERO);
1389184610Salfred
1390250207Shselasky	if (hub == NULL)
1391184610Salfred		goto error;
1392250207Shselasky#else
1393250207Shselasky	hub = &sc->sc_hub;
1394250207Shselasky#endif
1395184610Salfred	udev->hub = hub;
1396184610Salfred
1397184610Salfred	/* initialize HUB structure */
1398184610Salfred	hub->hubsoftc = sc;
1399184610Salfred	hub->explore = &uhub_explore;
1400213435Shselasky	hub->nports = nports;
1401184610Salfred	hub->hubudev = udev;
1402261105Shselasky#if USB_HAVE_TT_SUPPORT
1403261105Shselasky	hub->tt_msg[0].hdr.pm_callback = &uhub_reset_tt_proc;
1404261105Shselasky	hub->tt_msg[0].udev = udev;
1405261105Shselasky	hub->tt_msg[1].hdr.pm_callback = &uhub_reset_tt_proc;
1406261105Shselasky	hub->tt_msg[1].udev = udev;
1407261105Shselasky#endif
1408184610Salfred	/* if self powered hub, give ports maximum current */
1409184610Salfred	if (udev->flags.self_powered) {
1410184610Salfred		hub->portpower = USB_MAX_POWER;
1411184610Salfred	} else {
1412184610Salfred		hub->portpower = USB_MIN_POWER;
1413184610Salfred	}
1414184610Salfred
1415184610Salfred	/* set up interrupt pipe */
1416184610Salfred	iface_index = 0;
1417190735Sthompsa	if (udev->parent_hub == NULL) {
1418190735Sthompsa		/* root HUB is special */
1419190735Sthompsa		err = 0;
1420190735Sthompsa	} else {
1421190735Sthompsa		/* normal HUB */
1422194228Sthompsa		err = usbd_transfer_setup(udev, &iface_index, sc->sc_xfer,
1423196498Salfred		    uhub_config, UHUB_N_TRANSFER, sc, &sc->sc_mtx);
1424190735Sthompsa	}
1425184610Salfred	if (err) {
1426184610Salfred		DPRINTFN(0, "cannot setup interrupt transfer, "
1427199816Sthompsa		    "errstr=%s\n", usbd_errstr(err));
1428184610Salfred		goto error;
1429184610Salfred	}
1430184610Salfred	/* wait with power off for a while */
1431194228Sthompsa	usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_POWER_DOWN_TIME));
1432184610Salfred
1433273729Shselasky#if USB_HAVE_DISABLE_ENUM
1434273729Shselasky	/* Add device sysctls */
1435273729Shselasky
1436273729Shselasky	sysctl_ctx = device_get_sysctl_ctx(dev);
1437273729Shselasky	sysctl_tree = device_get_sysctl_tree(dev);
1438273729Shselasky
1439273729Shselasky	if (sysctl_ctx != NULL && sysctl_tree != NULL) {
1440273729Shselasky		char path[128];
1441273729Shselasky
1442273729Shselasky		snprintf(path, sizeof(path), "dev.uhub.%d.disable_enumeration",
1443273729Shselasky		    device_get_unit(dev));
1444273729Shselasky		TUNABLE_INT_FETCH(path, &sc->sc_disable_enumeration);
1445273729Shselasky
1446273729Shselasky		(void) SYSCTL_ADD_INT(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree),
1447273729Shselasky		    OID_AUTO, "disable_enumeration", CTLFLAG_RWTUN,
1448273729Shselasky		    &sc->sc_disable_enumeration, 0,
1449273729Shselasky		    "Set to disable enumeration on this USB HUB.");
1450273729Shselasky
1451273729Shselasky		snprintf(path, sizeof(path), "dev.uhub.%d.disable_port_power",
1452273729Shselasky		    device_get_unit(dev));
1453273729Shselasky		TUNABLE_INT_FETCH(path, &sc->sc_disable_port_power);
1454273729Shselasky
1455273729Shselasky		(void) SYSCTL_ADD_INT(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree),
1456273729Shselasky		    OID_AUTO, "disable_port_power", CTLFLAG_RWTUN,
1457273729Shselasky		    &sc->sc_disable_port_power, 0,
1458273729Shselasky		    "Set to disable USB port power on this USB HUB.");
1459273729Shselasky	}
1460273729Shselasky#endif
1461184610Salfred	/*
1462184610Salfred	 * To have the best chance of success we do things in the exact same
1463184610Salfred	 * order as Windoze98.  This should not be necessary, but some
1464184610Salfred	 * devices do not follow the USB specs to the letter.
1465184610Salfred	 *
1466184610Salfred	 * These are the events on the bus when a hub is attached:
1467184610Salfred	 *  Get device and config descriptors (see attach code)
1468184610Salfred	 *  Get hub descriptor (see above)
1469184610Salfred	 *  For all ports
1470184610Salfred	 *     turn on power
1471184610Salfred	 *     wait for power to become stable
1472184610Salfred	 * (all below happens in explore code)
1473184610Salfred	 *  For all ports
1474184610Salfred	 *     clear C_PORT_CONNECTION
1475184610Salfred	 *  For all ports
1476184610Salfred	 *     get port status
1477184610Salfred	 *     if device connected
1478184610Salfred	 *        wait 100 ms
1479184610Salfred	 *        turn on reset
1480184610Salfred	 *        wait
1481184610Salfred	 *        clear C_PORT_RESET
1482184610Salfred	 *        get port status
1483184610Salfred	 *        proceed with device attachment
1484184610Salfred	 */
1485184610Salfred
1486184610Salfred	/* XXX should check for none, individual, or ganged power? */
1487184610Salfred
1488184610Salfred	removable = 0;
1489184610Salfred
1490184610Salfred	for (x = 0; x != nports; x++) {
1491184610Salfred		/* set up data structures */
1492192984Sthompsa		struct usb_port *up = hub->ports + x;
1493184610Salfred
1494184610Salfred		up->device_index = 0;
1495184610Salfred		up->restartcnt = 0;
1496184610Salfred		portno = x + 1;
1497184610Salfred
1498184610Salfred		/* check if port is removable */
1499213435Shselasky		switch (udev->speed) {
1500213435Shselasky		case USB_SPEED_LOW:
1501213435Shselasky		case USB_SPEED_FULL:
1502213435Shselasky		case USB_SPEED_HIGH:
1503213435Shselasky			if (!UHD_NOT_REMOV(&hubdesc20, portno))
1504213435Shselasky				removable++;
1505213435Shselasky			break;
1506213435Shselasky		case USB_SPEED_SUPER:
1507213435Shselasky			if (!UHD_NOT_REMOV(&hubdesc30, portno))
1508213435Shselasky				removable++;
1509213435Shselasky			break;
1510213435Shselasky		default:
1511213435Shselasky			DPRINTF("Assuming removable port\n");
1512184610Salfred			removable++;
1513213435Shselasky			break;
1514184610Salfred		}
1515273729Shselasky		if (err == 0) {
1516273729Shselasky#if USB_HAVE_DISABLE_ENUM
1517273729Shselasky			/* check if we should disable USB port power or not */
1518273729Shselasky			if (usb_disable_port_power != 0 ||
1519273729Shselasky			    sc->sc_disable_port_power != 0) {
1520273729Shselasky				/* turn the power off */
1521275230Shselasky				DPRINTFN(2, "Turning port %d power off\n", portno);
1522273729Shselasky				err = usbd_req_clear_port_feature(udev, NULL,
1523273729Shselasky				    portno, UHF_PORT_POWER);
1524273729Shselasky			} else {
1525273729Shselasky#endif
1526273729Shselasky				/* turn the power on */
1527275230Shselasky				DPRINTFN(2, "Turning port %d power on\n", portno);
1528273729Shselasky				err = usbd_req_set_port_feature(udev, NULL,
1529273729Shselasky				    portno, UHF_PORT_POWER);
1530273729Shselasky#if USB_HAVE_DISABLE_ENUM
1531273729Shselasky			}
1532273729Shselasky#endif
1533184610Salfred		}
1534273729Shselasky		if (err != 0) {
1535273729Shselasky			DPRINTFN(0, "port %d power on or off failed, %s\n",
1536194228Sthompsa			    portno, usbd_errstr(err));
1537184610Salfred		}
1538184610Salfred		DPRINTF("turn on port %d power\n",
1539184610Salfred		    portno);
1540184610Salfred
1541184610Salfred		/* wait for stable power */
1542194228Sthompsa		usb_pause_mtx(NULL, USB_MS_TO_TICKS(pwrdly));
1543184610Salfred	}
1544184610Salfred
1545184610Salfred	device_printf(dev, "%d port%s with %d "
1546184610Salfred	    "removable, %s powered\n", nports, (nports != 1) ? "s" : "",
1547184610Salfred	    removable, udev->flags.self_powered ? "self" : "bus");
1548184610Salfred
1549190735Sthompsa	/* Start the interrupt endpoint, if any */
1550184610Salfred
1551261105Shselasky	mtx_lock(&sc->sc_mtx);
1552261105Shselasky	usbd_transfer_start(sc->sc_xfer[UHUB_INTR_TRANSFER]);
1553261105Shselasky	mtx_unlock(&sc->sc_mtx);
1554184610Salfred
1555186730Salfred	/* Enable automatic power save on all USB HUBs */
1556186730Salfred
1557194228Sthompsa	usbd_set_power_mode(udev, USB_POWER_MODE_SAVE);
1558186730Salfred
1559184610Salfred	return (0);
1560184610Salfred
1561184610Salfrederror:
1562194228Sthompsa	usbd_transfer_unsetup(sc->sc_xfer, UHUB_N_TRANSFER);
1563184610Salfred
1564250207Shselasky#if (USB_HAVE_FIXED_PORT == 0)
1565250207Shselasky	free(udev->hub, M_USBDEV);
1566250207Shselasky#endif
1567250207Shselasky	udev->hub = NULL;
1568196498Salfred
1569196498Salfred	mtx_destroy(&sc->sc_mtx);
1570196498Salfred
1571184610Salfred	return (ENXIO);
1572184610Salfred}
1573184610Salfred
1574184610Salfred/*
1575184610Salfred * Called from process context when the hub is gone.
1576184610Salfred * Detach all devices on active ports.
1577184610Salfred */
1578184610Salfredstatic int
1579184610Salfreduhub_detach(device_t dev)
1580184610Salfred{
1581184610Salfred	struct uhub_softc *sc = device_get_softc(dev);
1582192984Sthompsa	struct usb_hub *hub = sc->sc_udev->hub;
1583261105Shselasky	struct usb_bus *bus = sc->sc_udev->bus;
1584192984Sthompsa	struct usb_device *child;
1585184610Salfred	uint8_t x;
1586184610Salfred
1587213435Shselasky	if (hub == NULL)		/* must be partially working */
1588184610Salfred		return (0);
1589197553Sthompsa
1590197553Sthompsa	/* Make sure interrupt transfer is gone. */
1591197553Sthompsa	usbd_transfer_unsetup(sc->sc_xfer, UHUB_N_TRANSFER);
1592197553Sthompsa
1593197553Sthompsa	/* Detach all ports */
1594184610Salfred	for (x = 0; x != hub->nports; x++) {
1595184610Salfred
1596261105Shselasky		child = usb_bus_port_get_device(bus, hub->ports + x);
1597184610Salfred
1598184610Salfred		if (child == NULL) {
1599184610Salfred			continue;
1600184610Salfred		}
1601197553Sthompsa
1602184610Salfred		/*
1603197553Sthompsa		 * Free USB device and all subdevices, if any.
1604184610Salfred		 */
1605197553Sthompsa		usb_free_device(child, 0);
1606184610Salfred	}
1607184610Salfred
1608261105Shselasky#if USB_HAVE_TT_SUPPORT
1609261105Shselasky	/* Make sure our TT messages are not queued anywhere */
1610261105Shselasky	USB_BUS_LOCK(bus);
1611287274Shselasky	usb_proc_mwait(USB_BUS_TT_PROC(bus),
1612261105Shselasky	    &hub->tt_msg[0], &hub->tt_msg[1]);
1613261105Shselasky	USB_BUS_UNLOCK(bus);
1614261105Shselasky#endif
1615261105Shselasky
1616250207Shselasky#if (USB_HAVE_FIXED_PORT == 0)
1617184610Salfred	free(hub, M_USBDEV);
1618250207Shselasky#endif
1619184610Salfred	sc->sc_udev->hub = NULL;
1620196498Salfred
1621196498Salfred	mtx_destroy(&sc->sc_mtx);
1622196498Salfred
1623184610Salfred	return (0);
1624184610Salfred}
1625184610Salfred
1626186730Salfredstatic int
1627186730Salfreduhub_suspend(device_t dev)
1628186730Salfred{
1629186730Salfred	DPRINTF("\n");
1630186730Salfred	/* Sub-devices are not suspended here! */
1631186730Salfred	return (0);
1632186730Salfred}
1633186730Salfred
1634186730Salfredstatic int
1635186730Salfreduhub_resume(device_t dev)
1636186730Salfred{
1637186730Salfred	DPRINTF("\n");
1638186730Salfred	/* Sub-devices are not resumed here! */
1639186730Salfred	return (0);
1640186730Salfred}
1641186730Salfred
1642184610Salfredstatic void
1643184610Salfreduhub_driver_added(device_t dev, driver_t *driver)
1644184610Salfred{
1645194228Sthompsa	usb_needs_explore_all();
1646184610Salfred}
1647184610Salfred
1648184610Salfredstruct hub_result {
1649192984Sthompsa	struct usb_device *udev;
1650184610Salfred	uint8_t	portno;
1651184610Salfred	uint8_t	iface_index;
1652184610Salfred};
1653184610Salfred
1654184610Salfredstatic void
1655192984Sthompsauhub_find_iface_index(struct usb_hub *hub, device_t child,
1656184610Salfred    struct hub_result *res)
1657184610Salfred{
1658192984Sthompsa	struct usb_interface *iface;
1659192984Sthompsa	struct usb_device *udev;
1660184610Salfred	uint8_t nports;
1661184610Salfred	uint8_t x;
1662184610Salfred	uint8_t i;
1663184610Salfred
1664184610Salfred	nports = hub->nports;
1665184610Salfred	for (x = 0; x != nports; x++) {
1666194228Sthompsa		udev = usb_bus_port_get_device(hub->hubudev->bus,
1667184610Salfred		    hub->ports + x);
1668184610Salfred		if (!udev) {
1669184610Salfred			continue;
1670184610Salfred		}
1671184610Salfred		for (i = 0; i != USB_IFACE_MAX; i++) {
1672194228Sthompsa			iface = usbd_get_iface(udev, i);
1673184610Salfred			if (iface &&
1674184610Salfred			    (iface->subdev == child)) {
1675184610Salfred				res->iface_index = i;
1676184610Salfred				res->udev = udev;
1677184610Salfred				res->portno = x + 1;
1678184610Salfred				return;
1679184610Salfred			}
1680184610Salfred		}
1681184610Salfred	}
1682184610Salfred	res->iface_index = 0;
1683184610Salfred	res->udev = NULL;
1684184610Salfred	res->portno = 0;
1685184610Salfred}
1686184610Salfred
1687184610Salfredstatic int
1688184610Salfreduhub_child_location_string(device_t parent, device_t child,
1689184610Salfred    char *buf, size_t buflen)
1690184610Salfred{
1691197553Sthompsa	struct uhub_softc *sc;
1692197553Sthompsa	struct usb_hub *hub;
1693184610Salfred	struct hub_result res;
1694184610Salfred
1695197553Sthompsa	if (!device_is_attached(parent)) {
1696197553Sthompsa		if (buflen)
1697197553Sthompsa			buf[0] = 0;
1698197553Sthompsa		return (0);
1699197553Sthompsa	}
1700197553Sthompsa
1701197553Sthompsa	sc = device_get_softc(parent);
1702197553Sthompsa	hub = sc->sc_udev->hub;
1703197553Sthompsa
1704196403Sjhb	mtx_lock(&Giant);
1705184610Salfred	uhub_find_iface_index(hub, child, &res);
1706184610Salfred	if (!res.udev) {
1707184610Salfred		DPRINTF("device not on hub\n");
1708184610Salfred		if (buflen) {
1709184610Salfred			buf[0] = '\0';
1710184610Salfred		}
1711184610Salfred		goto done;
1712184610Salfred	}
1713279636Shselasky	snprintf(buf, buflen, "bus=%u hubaddr=%u port=%u devaddr=%u"
1714279636Shselasky	    " interface=%u"
1715279636Shselasky#if USB_HAVE_UGEN
1716279636Shselasky	    " ugen=%s"
1717279636Shselasky#endif
1718279636Shselasky	    , device_get_unit(res.udev->bus->bdev)
1719279636Shselasky	    , (res.udev->parent_hub != NULL) ?
1720279636Shselasky	    res.udev->parent_hub->device_index : 0
1721279636Shselasky	    , res.portno, res.udev->device_index, res.iface_index
1722279636Shselasky#if USB_HAVE_UGEN
1723279636Shselasky	    , res.udev->ugen_name
1724279636Shselasky#endif
1725279636Shselasky	    );
1726184610Salfreddone:
1727196403Sjhb	mtx_unlock(&Giant);
1728184610Salfred
1729184610Salfred	return (0);
1730184610Salfred}
1731184610Salfred
1732184610Salfredstatic int
1733184610Salfreduhub_child_pnpinfo_string(device_t parent, device_t child,
1734184610Salfred    char *buf, size_t buflen)
1735184610Salfred{
1736197553Sthompsa	struct uhub_softc *sc;
1737197553Sthompsa	struct usb_hub *hub;
1738192984Sthompsa	struct usb_interface *iface;
1739184610Salfred	struct hub_result res;
1740184610Salfred
1741197553Sthompsa	if (!device_is_attached(parent)) {
1742197553Sthompsa		if (buflen)
1743197553Sthompsa			buf[0] = 0;
1744197553Sthompsa		return (0);
1745197553Sthompsa	}
1746197553Sthompsa
1747197553Sthompsa	sc = device_get_softc(parent);
1748197553Sthompsa	hub = sc->sc_udev->hub;
1749197553Sthompsa
1750196403Sjhb	mtx_lock(&Giant);
1751184610Salfred	uhub_find_iface_index(hub, child, &res);
1752184610Salfred	if (!res.udev) {
1753184610Salfred		DPRINTF("device not on hub\n");
1754184610Salfred		if (buflen) {
1755184610Salfred			buf[0] = '\0';
1756184610Salfred		}
1757184610Salfred		goto done;
1758184610Salfred	}
1759194228Sthompsa	iface = usbd_get_iface(res.udev, res.iface_index);
1760184610Salfred	if (iface && iface->idesc) {
1761184610Salfred		snprintf(buf, buflen, "vendor=0x%04x product=0x%04x "
1762184610Salfred		    "devclass=0x%02x devsubclass=0x%02x "
1763184610Salfred		    "sernum=\"%s\" "
1764195963Salfred		    "release=0x%04x "
1765223489Shselasky		    "mode=%s "
1766223489Shselasky		    "intclass=0x%02x intsubclass=0x%02x "
1767279636Shselasky		    "intprotocol=0x%02x" "%s%s",
1768184610Salfred		    UGETW(res.udev->ddesc.idVendor),
1769184610Salfred		    UGETW(res.udev->ddesc.idProduct),
1770184610Salfred		    res.udev->ddesc.bDeviceClass,
1771184610Salfred		    res.udev->ddesc.bDeviceSubClass,
1772212136Sthompsa		    usb_get_serial(res.udev),
1773195963Salfred		    UGETW(res.udev->ddesc.bcdDevice),
1774223489Shselasky		    (res.udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device",
1775184610Salfred		    iface->idesc->bInterfaceClass,
1776214429Shselasky		    iface->idesc->bInterfaceSubClass,
1777223489Shselasky		    iface->idesc->bInterfaceProtocol,
1778214429Shselasky		    iface->pnpinfo ? " " : "",
1779214429Shselasky		    iface->pnpinfo ? iface->pnpinfo : "");
1780184610Salfred	} else {
1781184610Salfred		if (buflen) {
1782184610Salfred			buf[0] = '\0';
1783184610Salfred		}
1784184610Salfred		goto done;
1785184610Salfred	}
1786184610Salfreddone:
1787196403Sjhb	mtx_unlock(&Giant);
1788184610Salfred
1789184610Salfred	return (0);
1790184610Salfred}
1791184610Salfred
1792184610Salfred/*
1793184610Salfred * The USB Transaction Translator:
1794184610Salfred * ===============================
1795184610Salfred *
1796184610Salfred * When doing LOW- and FULL-speed USB transfers accross a HIGH-speed
1797184610Salfred * USB HUB, bandwidth must be allocated for ISOCHRONOUS and INTERRUPT
1798184610Salfred * USB transfers. To utilize bandwidth dynamically the "scatter and
1799184610Salfred * gather" principle must be applied. This means that bandwidth must
1800184610Salfred * be divided into equal parts of bandwidth. With regard to USB all
1801184610Salfred * data is transferred in smaller packets with length
1802184610Salfred * "wMaxPacketSize". The problem however is that "wMaxPacketSize" is
1803184610Salfred * not a constant!
1804184610Salfred *
1805184610Salfred * The bandwidth scheduler which I have implemented will simply pack
1806184610Salfred * the USB transfers back to back until there is no more space in the
1807184610Salfred * schedule. Out of the 8 microframes which the USB 2.0 standard
1808184610Salfred * provides, only 6 are available for non-HIGH-speed devices. I have
1809184610Salfred * reserved the first 4 microframes for ISOCHRONOUS transfers. The
1810184610Salfred * last 2 microframes I have reserved for INTERRUPT transfers. Without
1811184610Salfred * this division, it is very difficult to allocate and free bandwidth
1812184610Salfred * dynamically.
1813184610Salfred *
1814184610Salfred * NOTE about the Transaction Translator in USB HUBs:
1815184610Salfred *
1816184610Salfred * USB HUBs have a very simple Transaction Translator, that will
1817184610Salfred * simply pipeline all the SPLIT transactions. That means that the
1818184610Salfred * transactions will be executed in the order they are queued!
1819184610Salfred *
1820184610Salfred */
1821184610Salfred
1822184610Salfred/*------------------------------------------------------------------------*
1823194228Sthompsa *	usb_intr_find_best_slot
1824184610Salfred *
1825184610Salfred * Return value:
1826184610Salfred *   The best Transaction Translation slot for an interrupt endpoint.
1827184610Salfred *------------------------------------------------------------------------*/
1828184610Salfredstatic uint8_t
1829199672Sthompsausb_intr_find_best_slot(usb_size_t *ptr, uint8_t start,
1830199672Sthompsa    uint8_t end, uint8_t mask)
1831184610Salfred{
1832233774Shselasky	usb_size_t min = (usb_size_t)-1;
1833199672Sthompsa	usb_size_t sum;
1834184610Salfred	uint8_t x;
1835184610Salfred	uint8_t y;
1836199672Sthompsa	uint8_t z;
1837184610Salfred
1838184610Salfred	y = 0;
1839184610Salfred
1840184610Salfred	/* find the last slot with lesser used bandwidth */
1841184610Salfred
1842184610Salfred	for (x = start; x < end; x++) {
1843199672Sthompsa
1844199672Sthompsa		sum = 0;
1845199672Sthompsa
1846199672Sthompsa		/* compute sum of bandwidth */
1847199672Sthompsa		for (z = x; z < end; z++) {
1848199672Sthompsa			if (mask & (1U << (z - x)))
1849199672Sthompsa				sum += ptr[z];
1850199672Sthompsa		}
1851199672Sthompsa
1852199672Sthompsa		/* check if the current multi-slot is more optimal */
1853199672Sthompsa		if (min >= sum) {
1854199672Sthompsa			min = sum;
1855184610Salfred			y = x;
1856184610Salfred		}
1857199672Sthompsa
1858199672Sthompsa		/* check if the mask is about to be shifted out */
1859199672Sthompsa		if (mask & (1U << (end - 1 - x)))
1860199672Sthompsa			break;
1861184610Salfred	}
1862184610Salfred	return (y);
1863184610Salfred}
1864184610Salfred
1865184610Salfred/*------------------------------------------------------------------------*
1866199672Sthompsa *	usb_hs_bandwidth_adjust
1867184610Salfred *
1868184610Salfred * This function will update the bandwith usage for the microframe
1869184610Salfred * having index "slot" by "len" bytes. "len" can be negative.  If the
1870184610Salfred * "slot" argument is greater or equal to "USB_HS_MICRO_FRAMES_MAX"
1871184610Salfred * the "slot" argument will be replaced by the slot having least used
1872199672Sthompsa * bandwidth. The "mask" argument is used for multi-slot allocations.
1873184610Salfred *
1874184610Salfred * Returns:
1875199672Sthompsa *    The slot in which the bandwidth update was done: 0..7
1876184610Salfred *------------------------------------------------------------------------*/
1877199672Sthompsastatic uint8_t
1878199672Sthompsausb_hs_bandwidth_adjust(struct usb_device *udev, int16_t len,
1879199672Sthompsa    uint8_t slot, uint8_t mask)
1880184610Salfred{
1881192984Sthompsa	struct usb_bus *bus = udev->bus;
1882192984Sthompsa	struct usb_hub *hub;
1883192500Sthompsa	enum usb_dev_speed speed;
1884199672Sthompsa	uint8_t x;
1885184610Salfred
1886184824Sthompsa	USB_BUS_LOCK_ASSERT(bus, MA_OWNED);
1887184610Salfred
1888194228Sthompsa	speed = usbd_get_speed(udev);
1889187180Sthompsa
1890187180Sthompsa	switch (speed) {
1891187180Sthompsa	case USB_SPEED_LOW:
1892187180Sthompsa	case USB_SPEED_FULL:
1893187180Sthompsa		if (speed == USB_SPEED_LOW) {
1894184610Salfred			len *= 8;
1895184610Salfred		}
1896184610Salfred		/*
1897184610Salfred	         * The Host Controller Driver should have
1898184610Salfred	         * performed checks so that the lookup
1899184610Salfred	         * below does not result in a NULL pointer
1900184610Salfred	         * access.
1901184610Salfred	         */
1902184610Salfred
1903191395Sthompsa		hub = udev->parent_hs_hub->hub;
1904184610Salfred		if (slot >= USB_HS_MICRO_FRAMES_MAX) {
1905194228Sthompsa			slot = usb_intr_find_best_slot(hub->uframe_usage,
1906199672Sthompsa			    USB_FS_ISOC_UFRAME_MAX, 6, mask);
1907184610Salfred		}
1908199672Sthompsa		for (x = slot; x < 8; x++) {
1909199672Sthompsa			if (mask & (1U << (x - slot))) {
1910199672Sthompsa				hub->uframe_usage[x] += len;
1911199672Sthompsa				bus->uframe_usage[x] += len;
1912199672Sthompsa			}
1913199672Sthompsa		}
1914187180Sthompsa		break;
1915187180Sthompsa	default:
1916187180Sthompsa		if (slot >= USB_HS_MICRO_FRAMES_MAX) {
1917194228Sthompsa			slot = usb_intr_find_best_slot(bus->uframe_usage, 0,
1918199672Sthompsa			    USB_HS_MICRO_FRAMES_MAX, mask);
1919187180Sthompsa		}
1920199672Sthompsa		for (x = slot; x < 8; x++) {
1921199672Sthompsa			if (mask & (1U << (x - slot))) {
1922199672Sthompsa				bus->uframe_usage[x] += len;
1923199672Sthompsa			}
1924199672Sthompsa		}
1925187180Sthompsa		break;
1926184610Salfred	}
1927184610Salfred	return (slot);
1928184610Salfred}
1929184610Salfred
1930184610Salfred/*------------------------------------------------------------------------*
1931199672Sthompsa *	usb_hs_bandwidth_alloc
1932199672Sthompsa *
1933199672Sthompsa * This function is a wrapper function for "usb_hs_bandwidth_adjust()".
1934199672Sthompsa *------------------------------------------------------------------------*/
1935199672Sthompsavoid
1936199672Sthompsausb_hs_bandwidth_alloc(struct usb_xfer *xfer)
1937199672Sthompsa{
1938199672Sthompsa	struct usb_device *udev;
1939199672Sthompsa	uint8_t slot;
1940199672Sthompsa	uint8_t mask;
1941199672Sthompsa	uint8_t speed;
1942199672Sthompsa
1943199672Sthompsa	udev = xfer->xroot->udev;
1944199672Sthompsa
1945199672Sthompsa	if (udev->flags.usb_mode != USB_MODE_HOST)
1946199672Sthompsa		return;		/* not supported */
1947199672Sthompsa
1948199672Sthompsa	xfer->endpoint->refcount_bw++;
1949199672Sthompsa	if (xfer->endpoint->refcount_bw != 1)
1950199672Sthompsa		return;		/* already allocated */
1951199672Sthompsa
1952199672Sthompsa	speed = usbd_get_speed(udev);
1953199672Sthompsa
1954199672Sthompsa	switch (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) {
1955199672Sthompsa	case UE_INTERRUPT:
1956199672Sthompsa		/* allocate a microframe slot */
1957199672Sthompsa
1958199672Sthompsa		mask = 0x01;
1959199672Sthompsa		slot = usb_hs_bandwidth_adjust(udev,
1960199672Sthompsa		    xfer->max_frame_size, USB_HS_MICRO_FRAMES_MAX, mask);
1961199672Sthompsa
1962199672Sthompsa		xfer->endpoint->usb_uframe = slot;
1963199672Sthompsa		xfer->endpoint->usb_smask = mask << slot;
1964199672Sthompsa
1965199672Sthompsa		if ((speed != USB_SPEED_FULL) &&
1966199672Sthompsa		    (speed != USB_SPEED_LOW)) {
1967199672Sthompsa			xfer->endpoint->usb_cmask = 0x00 ;
1968199672Sthompsa		} else {
1969199672Sthompsa			xfer->endpoint->usb_cmask = (-(0x04 << slot)) & 0xFE;
1970199672Sthompsa		}
1971199672Sthompsa		break;
1972199672Sthompsa
1973199672Sthompsa	case UE_ISOCHRONOUS:
1974199672Sthompsa		switch (usbd_xfer_get_fps_shift(xfer)) {
1975199672Sthompsa		case 0:
1976199672Sthompsa			mask = 0xFF;
1977199672Sthompsa			break;
1978199672Sthompsa		case 1:
1979199672Sthompsa			mask = 0x55;
1980199672Sthompsa			break;
1981199672Sthompsa		case 2:
1982199672Sthompsa			mask = 0x11;
1983199672Sthompsa			break;
1984199672Sthompsa		default:
1985199672Sthompsa			mask = 0x01;
1986199672Sthompsa			break;
1987199672Sthompsa		}
1988199672Sthompsa
1989199672Sthompsa		/* allocate a microframe multi-slot */
1990199672Sthompsa
1991199672Sthompsa		slot = usb_hs_bandwidth_adjust(udev,
1992199672Sthompsa		    xfer->max_frame_size, USB_HS_MICRO_FRAMES_MAX, mask);
1993199672Sthompsa
1994199672Sthompsa		xfer->endpoint->usb_uframe = slot;
1995199672Sthompsa		xfer->endpoint->usb_cmask = 0;
1996199672Sthompsa		xfer->endpoint->usb_smask = mask << slot;
1997199672Sthompsa		break;
1998199672Sthompsa
1999199672Sthompsa	default:
2000199672Sthompsa		xfer->endpoint->usb_uframe = 0;
2001199672Sthompsa		xfer->endpoint->usb_cmask = 0;
2002199672Sthompsa		xfer->endpoint->usb_smask = 0;
2003199672Sthompsa		break;
2004199672Sthompsa	}
2005199672Sthompsa
2006199672Sthompsa	DPRINTFN(11, "slot=%d, mask=0x%02x\n",
2007199672Sthompsa	    xfer->endpoint->usb_uframe,
2008199672Sthompsa	    xfer->endpoint->usb_smask >> xfer->endpoint->usb_uframe);
2009199672Sthompsa}
2010199672Sthompsa
2011199672Sthompsa/*------------------------------------------------------------------------*
2012199672Sthompsa *	usb_hs_bandwidth_free
2013199672Sthompsa *
2014199672Sthompsa * This function is a wrapper function for "usb_hs_bandwidth_adjust()".
2015199672Sthompsa *------------------------------------------------------------------------*/
2016199672Sthompsavoid
2017199672Sthompsausb_hs_bandwidth_free(struct usb_xfer *xfer)
2018199672Sthompsa{
2019199672Sthompsa	struct usb_device *udev;
2020199672Sthompsa	uint8_t slot;
2021199672Sthompsa	uint8_t mask;
2022199672Sthompsa
2023199672Sthompsa	udev = xfer->xroot->udev;
2024199672Sthompsa
2025199672Sthompsa	if (udev->flags.usb_mode != USB_MODE_HOST)
2026199672Sthompsa		return;		/* not supported */
2027199672Sthompsa
2028199672Sthompsa	xfer->endpoint->refcount_bw--;
2029199672Sthompsa	if (xfer->endpoint->refcount_bw != 0)
2030199672Sthompsa		return;		/* still allocated */
2031199672Sthompsa
2032199672Sthompsa	switch (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) {
2033199672Sthompsa	case UE_INTERRUPT:
2034199672Sthompsa	case UE_ISOCHRONOUS:
2035199672Sthompsa
2036199672Sthompsa		slot = xfer->endpoint->usb_uframe;
2037199672Sthompsa		mask = xfer->endpoint->usb_smask;
2038199672Sthompsa
2039199672Sthompsa		/* free microframe slot(s): */
2040199672Sthompsa		usb_hs_bandwidth_adjust(udev,
2041199672Sthompsa		    -xfer->max_frame_size, slot, mask >> slot);
2042199672Sthompsa
2043199672Sthompsa		DPRINTFN(11, "slot=%d, mask=0x%02x\n",
2044199672Sthompsa		    slot, mask >> slot);
2045199672Sthompsa
2046199672Sthompsa		xfer->endpoint->usb_uframe = 0;
2047199672Sthompsa		xfer->endpoint->usb_cmask = 0;
2048199672Sthompsa		xfer->endpoint->usb_smask = 0;
2049199672Sthompsa		break;
2050199672Sthompsa
2051199672Sthompsa	default:
2052199672Sthompsa		break;
2053199672Sthompsa	}
2054199672Sthompsa}
2055199672Sthompsa
2056199672Sthompsa/*------------------------------------------------------------------------*
2057194228Sthompsa *	usb_isoc_time_expand
2058184610Salfred *
2059184610Salfred * This function will expand the time counter from 7-bit to 16-bit.
2060184610Salfred *
2061184610Salfred * Returns:
2062184610Salfred *   16-bit isochronous time counter.
2063184610Salfred *------------------------------------------------------------------------*/
2064184610Salfreduint16_t
2065194228Sthompsausb_isoc_time_expand(struct usb_bus *bus, uint16_t isoc_time_curr)
2066184610Salfred{
2067184610Salfred	uint16_t rem;
2068184610Salfred
2069184824Sthompsa	USB_BUS_LOCK_ASSERT(bus, MA_OWNED);
2070184610Salfred
2071184610Salfred	rem = bus->isoc_time_last & (USB_ISOC_TIME_MAX - 1);
2072184610Salfred
2073184610Salfred	isoc_time_curr &= (USB_ISOC_TIME_MAX - 1);
2074184610Salfred
2075184610Salfred	if (isoc_time_curr < rem) {
2076184610Salfred		/* the time counter wrapped around */
2077184610Salfred		bus->isoc_time_last += USB_ISOC_TIME_MAX;
2078184610Salfred	}
2079184610Salfred	/* update the remainder */
2080184610Salfred
2081184610Salfred	bus->isoc_time_last &= ~(USB_ISOC_TIME_MAX - 1);
2082184610Salfred	bus->isoc_time_last |= isoc_time_curr;
2083184610Salfred
2084184610Salfred	return (bus->isoc_time_last);
2085184610Salfred}
2086184610Salfred
2087184610Salfred/*------------------------------------------------------------------------*
2088234803Shselasky *	usbd_fs_isoc_schedule_alloc_slot
2089184610Salfred *
2090234803Shselasky * This function will allocate bandwidth for an isochronous FULL speed
2091234803Shselasky * transaction in the FULL speed schedule.
2092184610Salfred *
2093184610Salfred * Returns:
2094234803Shselasky *    <8: Success
2095234803Shselasky * Else: Error
2096184610Salfred *------------------------------------------------------------------------*/
2097190734Sthompsa#if USB_HAVE_TT_SUPPORT
2098234803Shselaskyuint8_t
2099234803Shselaskyusbd_fs_isoc_schedule_alloc_slot(struct usb_xfer *isoc_xfer, uint16_t isoc_time)
2100184610Salfred{
2101234803Shselasky	struct usb_xfer *xfer;
2102234803Shselasky	struct usb_xfer *pipe_xfer;
2103234803Shselasky	struct usb_bus *bus;
2104234803Shselasky	usb_frlength_t len;
2105234803Shselasky	usb_frlength_t data_len;
2106234803Shselasky	uint16_t delta;
2107234803Shselasky	uint16_t slot;
2108234803Shselasky	uint8_t retval;
2109184610Salfred
2110234803Shselasky	data_len = 0;
2111234803Shselasky	slot = 0;
2112184610Salfred
2113234803Shselasky	bus = isoc_xfer->xroot->bus;
2114184610Salfred
2115234803Shselasky	TAILQ_FOREACH(xfer, &bus->intr_q.head, wait_entry) {
2116184610Salfred
2117234803Shselasky		/* skip self, if any */
2118184610Salfred
2119234803Shselasky		if (xfer == isoc_xfer)
2120234803Shselasky			continue;
2121184610Salfred
2122234803Shselasky		/* check if this USB transfer is going through the same TT */
2123184610Salfred
2124234803Shselasky		if (xfer->xroot->udev->parent_hs_hub !=
2125234803Shselasky		    isoc_xfer->xroot->udev->parent_hs_hub) {
2126234803Shselasky			continue;
2127234803Shselasky		}
2128234803Shselasky		if ((isoc_xfer->xroot->udev->parent_hs_hub->
2129234803Shselasky		    ddesc.bDeviceProtocol == UDPROTO_HSHUBMTT) &&
2130234803Shselasky		    (xfer->xroot->udev->hs_port_no !=
2131234803Shselasky		    isoc_xfer->xroot->udev->hs_port_no)) {
2132234803Shselasky			continue;
2133234803Shselasky		}
2134234803Shselasky		if (xfer->endpoint->methods != isoc_xfer->endpoint->methods)
2135234803Shselasky			continue;
2136184610Salfred
2137234803Shselasky		/* check if isoc_time is part of this transfer */
2138184610Salfred
2139234803Shselasky		delta = xfer->isoc_time_complete - isoc_time;
2140234803Shselasky		if (delta > 0 && delta <= xfer->nframes) {
2141234803Shselasky			delta = xfer->nframes - delta;
2142234803Shselasky
2143234803Shselasky			len = xfer->frlengths[delta];
2144234803Shselasky			len += 8;
2145234803Shselasky			len *= 7;
2146234803Shselasky			len /= 6;
2147234803Shselasky
2148234803Shselasky			data_len += len;
2149234803Shselasky		}
2150234803Shselasky
2151239214Shselasky		/*
2152239214Shselasky		 * Check double buffered transfers. Only stream ID
2153239214Shselasky		 * equal to zero is valid here!
2154239214Shselasky		 */
2155239214Shselasky		TAILQ_FOREACH(pipe_xfer, &xfer->endpoint->endpoint_q[0].head,
2156234803Shselasky		    wait_entry) {
2157234803Shselasky
2158234803Shselasky			/* skip self, if any */
2159234803Shselasky
2160234803Shselasky			if (pipe_xfer == isoc_xfer)
2161184610Salfred				continue;
2162234803Shselasky
2163234803Shselasky			/* check if isoc_time is part of this transfer */
2164234803Shselasky
2165234803Shselasky			delta = pipe_xfer->isoc_time_complete - isoc_time;
2166234803Shselasky			if (delta > 0 && delta <= pipe_xfer->nframes) {
2167234803Shselasky				delta = pipe_xfer->nframes - delta;
2168234803Shselasky
2169234803Shselasky				len = pipe_xfer->frlengths[delta];
2170234803Shselasky				len += 8;
2171234803Shselasky				len *= 7;
2172234803Shselasky				len /= 6;
2173234803Shselasky
2174234803Shselasky				data_len += len;
2175184610Salfred			}
2176184610Salfred		}
2177234803Shselasky	}
2178184610Salfred
2179234803Shselasky	while (data_len >= USB_FS_BYTES_PER_HS_UFRAME) {
2180234803Shselasky		data_len -= USB_FS_BYTES_PER_HS_UFRAME;
2181234803Shselasky		slot++;
2182184610Salfred	}
2183184610Salfred
2184234803Shselasky	/* check for overflow */
2185184610Salfred
2186234803Shselasky	if (slot >= USB_FS_ISOC_UFRAME_MAX)
2187234803Shselasky		return (255);
2188184610Salfred
2189234803Shselasky	retval = slot;
2190184610Salfred
2191234803Shselasky	delta = isoc_xfer->isoc_time_complete - isoc_time;
2192234803Shselasky	if (delta > 0 && delta <= isoc_xfer->nframes) {
2193234803Shselasky		delta = isoc_xfer->nframes - delta;
2194184610Salfred
2195234803Shselasky		len = isoc_xfer->frlengths[delta];
2196234803Shselasky		len += 8;
2197234803Shselasky		len *= 7;
2198234803Shselasky		len /= 6;
2199234803Shselasky
2200234803Shselasky		data_len += len;
2201184610Salfred	}
2202184610Salfred
2203234803Shselasky	while (data_len >= USB_FS_BYTES_PER_HS_UFRAME) {
2204234803Shselasky		data_len -= USB_FS_BYTES_PER_HS_UFRAME;
2205234803Shselasky		slot++;
2206234803Shselasky	}
2207184610Salfred
2208234803Shselasky	/* check for overflow */
2209184610Salfred
2210234803Shselasky	if (slot >= USB_FS_ISOC_UFRAME_MAX)
2211234803Shselasky		return (255);
2212234803Shselasky
2213234803Shselasky	return (retval);
2214184610Salfred}
2215190734Sthompsa#endif
2216184610Salfred
2217184610Salfred/*------------------------------------------------------------------------*
2218194228Sthompsa *	usb_bus_port_get_device
2219184610Salfred *
2220184610Salfred * This function is NULL safe.
2221184610Salfred *------------------------------------------------------------------------*/
2222192984Sthompsastruct usb_device *
2223194228Sthompsausb_bus_port_get_device(struct usb_bus *bus, struct usb_port *up)
2224184610Salfred{
2225184610Salfred	if ((bus == NULL) || (up == NULL)) {
2226184610Salfred		/* be NULL safe */
2227184610Salfred		return (NULL);
2228184610Salfred	}
2229184610Salfred	if (up->device_index == 0) {
2230184610Salfred		/* nothing to do */
2231184610Salfred		return (NULL);
2232184610Salfred	}
2233184610Salfred	return (bus->devices[up->device_index]);
2234184610Salfred}
2235184610Salfred
2236184610Salfred/*------------------------------------------------------------------------*
2237194228Sthompsa *	usb_bus_port_set_device
2238184610Salfred *
2239184610Salfred * This function is NULL safe.
2240184610Salfred *------------------------------------------------------------------------*/
2241184610Salfredvoid
2242194228Sthompsausb_bus_port_set_device(struct usb_bus *bus, struct usb_port *up,
2243192984Sthompsa    struct usb_device *udev, uint8_t device_index)
2244184610Salfred{
2245184610Salfred	if (bus == NULL) {
2246184610Salfred		/* be NULL safe */
2247184610Salfred		return;
2248184610Salfred	}
2249184610Salfred	/*
2250184610Salfred	 * There is only one case where we don't
2251184610Salfred	 * have an USB port, and that is the Root Hub!
2252184610Salfred         */
2253184610Salfred	if (up) {
2254184610Salfred		if (udev) {
2255184610Salfred			up->device_index = device_index;
2256184610Salfred		} else {
2257184610Salfred			device_index = up->device_index;
2258184610Salfred			up->device_index = 0;
2259184610Salfred		}
2260184610Salfred	}
2261184610Salfred	/*
2262184610Salfred	 * Make relationships to our new device
2263184610Salfred	 */
2264184610Salfred	if (device_index != 0) {
2265189599Sthompsa#if USB_HAVE_UGEN
2266194228Sthompsa		mtx_lock(&usb_ref_lock);
2267189599Sthompsa#endif
2268184610Salfred		bus->devices[device_index] = udev;
2269189599Sthompsa#if USB_HAVE_UGEN
2270194228Sthompsa		mtx_unlock(&usb_ref_lock);
2271189599Sthompsa#endif
2272184610Salfred	}
2273184610Salfred	/*
2274184610Salfred	 * Debug print
2275184610Salfred	 */
2276184610Salfred	DPRINTFN(2, "bus %p devices[%u] = %p\n", bus, device_index, udev);
2277184610Salfred}
2278184610Salfred
2279184610Salfred/*------------------------------------------------------------------------*
2280194228Sthompsa *	usb_needs_explore
2281184610Salfred *
2282184610Salfred * This functions is called when the USB event thread needs to run.
2283184610Salfred *------------------------------------------------------------------------*/
2284184610Salfredvoid
2285194228Sthompsausb_needs_explore(struct usb_bus *bus, uint8_t do_probe)
2286184610Salfred{
2287190735Sthompsa	uint8_t do_unlock;
2288190735Sthompsa
2289184610Salfred	DPRINTF("\n");
2290184610Salfred
2291311799Shselasky	if (cold != 0) {
2292311799Shselasky		DPRINTF("Cold\n");
2293311799Shselasky		return;
2294311799Shselasky	}
2295311799Shselasky
2296184610Salfred	if (bus == NULL) {
2297184610Salfred		DPRINTF("No bus pointer!\n");
2298184610Salfred		return;
2299184610Salfred	}
2300190735Sthompsa	if ((bus->devices == NULL) ||
2301190735Sthompsa	    (bus->devices[USB_ROOT_HUB_ADDR] == NULL)) {
2302190735Sthompsa		DPRINTF("No root HUB\n");
2303190735Sthompsa		return;
2304190735Sthompsa	}
2305190735Sthompsa	if (mtx_owned(&bus->bus_mtx)) {
2306190735Sthompsa		do_unlock = 0;
2307190735Sthompsa	} else {
2308190735Sthompsa		USB_BUS_LOCK(bus);
2309190735Sthompsa		do_unlock = 1;
2310190735Sthompsa	}
2311184610Salfred	if (do_probe) {
2312184610Salfred		bus->do_probe = 1;
2313184610Salfred	}
2314246363Shselasky	if (usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus),
2315184610Salfred	    &bus->explore_msg[0], &bus->explore_msg[1])) {
2316184610Salfred		/* ignore */
2317184610Salfred	}
2318190735Sthompsa	if (do_unlock) {
2319190735Sthompsa		USB_BUS_UNLOCK(bus);
2320190735Sthompsa	}
2321184610Salfred}
2322184610Salfred
2323184610Salfred/*------------------------------------------------------------------------*
2324194228Sthompsa *	usb_needs_explore_all
2325184610Salfred *
2326184610Salfred * This function is called whenever a new driver is loaded and will
2327184610Salfred * cause that all USB busses are re-explored.
2328184610Salfred *------------------------------------------------------------------------*/
2329184610Salfredvoid
2330194228Sthompsausb_needs_explore_all(void)
2331184610Salfred{
2332192984Sthompsa	struct usb_bus *bus;
2333184610Salfred	devclass_t dc;
2334184610Salfred	device_t dev;
2335184610Salfred	int max;
2336184610Salfred
2337184610Salfred	DPRINTFN(3, "\n");
2338184610Salfred
2339194228Sthompsa	dc = usb_devclass_ptr;
2340184610Salfred	if (dc == NULL) {
2341184610Salfred		DPRINTFN(0, "no devclass\n");
2342184610Salfred		return;
2343184610Salfred	}
2344184610Salfred	/*
2345184610Salfred	 * Explore all USB busses in parallell.
2346184610Salfred	 */
2347184610Salfred	max = devclass_get_maxunit(dc);
2348184610Salfred	while (max >= 0) {
2349184610Salfred		dev = devclass_get_device(dc, max);
2350184610Salfred		if (dev) {
2351184610Salfred			bus = device_get_softc(dev);
2352184610Salfred			if (bus) {
2353194228Sthompsa				usb_needs_explore(bus, 1);
2354184610Salfred			}
2355184610Salfred		}
2356184610Salfred		max--;
2357184610Salfred	}
2358184610Salfred}
2359186730Salfred
2360186730Salfred/*------------------------------------------------------------------------*
2361311799Shselasky *	usb_needs_explore_init
2362311799Shselasky *
2363311799Shselasky * This function will ensure that the USB controllers are not enumerated
2364311799Shselasky * until the "cold" variable is cleared.
2365311799Shselasky *------------------------------------------------------------------------*/
2366311799Shselaskystatic void
2367311799Shselaskyusb_needs_explore_init(void *arg)
2368311799Shselasky{
2369311799Shselasky	/*
2370311799Shselasky	 * The cold variable should be cleared prior to this function
2371311799Shselasky	 * being called:
2372311799Shselasky	 */
2373311799Shselasky	if (cold == 0)
2374311799Shselasky		usb_needs_explore_all();
2375311799Shselasky	else
2376311799Shselasky		DPRINTFN(-1, "Cold variable is still set!\n");
2377311799Shselasky}
2378311799ShselaskySYSINIT(usb_needs_explore_init, SI_SUB_KICK_SCHEDULER, SI_ORDER_SECOND, usb_needs_explore_init, NULL);
2379311799Shselasky
2380311799Shselasky/*------------------------------------------------------------------------*
2381194228Sthompsa *	usb_bus_power_update
2382186730Salfred *
2383186730Salfred * This function will ensure that all USB devices on the given bus are
2384186730Salfred * properly suspended or resumed according to the device transfer
2385186730Salfred * state.
2386186730Salfred *------------------------------------------------------------------------*/
2387190734Sthompsa#if USB_HAVE_POWERD
2388186730Salfredvoid
2389194228Sthompsausb_bus_power_update(struct usb_bus *bus)
2390186730Salfred{
2391194228Sthompsa	usb_needs_explore(bus, 0 /* no probe */ );
2392186730Salfred}
2393190734Sthompsa#endif
2394186730Salfred
2395186730Salfred/*------------------------------------------------------------------------*
2396194228Sthompsa *	usbd_transfer_power_ref
2397186730Salfred *
2398186730Salfred * This function will modify the power save reference counts and
2399186730Salfred * wakeup the USB device associated with the given USB transfer, if
2400186730Salfred * needed.
2401186730Salfred *------------------------------------------------------------------------*/
2402190734Sthompsa#if USB_HAVE_POWERD
2403186730Salfredvoid
2404194228Sthompsausbd_transfer_power_ref(struct usb_xfer *xfer, int val)
2405186730Salfred{
2406193045Sthompsa	static const usb_power_mask_t power_mask[4] = {
2407186730Salfred		[UE_CONTROL] = USB_HW_POWER_CONTROL,
2408186730Salfred		[UE_BULK] = USB_HW_POWER_BULK,
2409186730Salfred		[UE_INTERRUPT] = USB_HW_POWER_INTERRUPT,
2410186730Salfred		[UE_ISOCHRONOUS] = USB_HW_POWER_ISOC,
2411186730Salfred	};
2412192984Sthompsa	struct usb_device *udev;
2413186730Salfred	uint8_t needs_explore;
2414186730Salfred	uint8_t needs_hw_power;
2415186730Salfred	uint8_t xfer_type;
2416186730Salfred
2417187173Sthompsa	udev = xfer->xroot->udev;
2418186730Salfred
2419186730Salfred	if (udev->device_index == USB_ROOT_HUB_ADDR) {
2420186730Salfred		/* no power save for root HUB */
2421186730Salfred		return;
2422186730Salfred	}
2423186730Salfred	USB_BUS_LOCK(udev->bus);
2424186730Salfred
2425193644Sthompsa	xfer_type = xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE;
2426186730Salfred
2427186730Salfred	udev->pwr_save.last_xfer_time = ticks;
2428186730Salfred	udev->pwr_save.type_refs[xfer_type] += val;
2429186730Salfred
2430186730Salfred	if (xfer->flags_int.control_xfr) {
2431186730Salfred		udev->pwr_save.read_refs += val;
2432192499Sthompsa		if (xfer->flags_int.usb_mode == USB_MODE_HOST) {
2433186730Salfred			/*
2434208008Sthompsa			 * It is not allowed to suspend during a
2435208008Sthompsa			 * control transfer:
2436186730Salfred			 */
2437186730Salfred			udev->pwr_save.write_refs += val;
2438186730Salfred		}
2439186730Salfred	} else if (USB_GET_DATA_ISREAD(xfer)) {
2440186730Salfred		udev->pwr_save.read_refs += val;
2441186730Salfred	} else {
2442186730Salfred		udev->pwr_save.write_refs += val;
2443186730Salfred	}
2444186730Salfred
2445208008Sthompsa	if (val > 0) {
2446208008Sthompsa		if (udev->flags.self_suspended)
2447208008Sthompsa			needs_explore = usb_peer_should_wakeup(udev);
2448208008Sthompsa		else
2449208008Sthompsa			needs_explore = 0;
2450186730Salfred
2451208008Sthompsa		if (!(udev->bus->hw_power_state & power_mask[xfer_type])) {
2452208008Sthompsa			DPRINTF("Adding type %u to power state\n", xfer_type);
2453208008Sthompsa			udev->bus->hw_power_state |= power_mask[xfer_type];
2454208008Sthompsa			needs_hw_power = 1;
2455208008Sthompsa		} else {
2456208008Sthompsa			needs_hw_power = 0;
2457208008Sthompsa		}
2458186730Salfred	} else {
2459208008Sthompsa		needs_explore = 0;
2460186730Salfred		needs_hw_power = 0;
2461186730Salfred	}
2462186730Salfred
2463186730Salfred	USB_BUS_UNLOCK(udev->bus);
2464186730Salfred
2465186730Salfred	if (needs_explore) {
2466186730Salfred		DPRINTF("update\n");
2467194228Sthompsa		usb_bus_power_update(udev->bus);
2468186730Salfred	} else if (needs_hw_power) {
2469186730Salfred		DPRINTF("needs power\n");
2470186730Salfred		if (udev->bus->methods->set_hw_power != NULL) {
2471186730Salfred			(udev->bus->methods->set_hw_power) (udev->bus);
2472186730Salfred		}
2473186730Salfred	}
2474186730Salfred}
2475190734Sthompsa#endif
2476186730Salfred
2477186730Salfred/*------------------------------------------------------------------------*
2478208008Sthompsa *	usb_peer_should_wakeup
2479208008Sthompsa *
2480208008Sthompsa * This function returns non-zero if the current device should wake up.
2481208008Sthompsa *------------------------------------------------------------------------*/
2482208008Sthompsastatic uint8_t
2483208008Sthompsausb_peer_should_wakeup(struct usb_device *udev)
2484208008Sthompsa{
2485255488Shselasky	return (((udev->power_mode == USB_POWER_MODE_ON) &&
2486255488Shselasky	    (udev->flags.usb_mode == USB_MODE_HOST)) ||
2487213432Shselasky	    (udev->driver_added_refcount != udev->bus->driver_added_refcount) ||
2488257375Shselasky	    (udev->re_enumerate_wait != USB_RE_ENUM_DONE) ||
2489208008Sthompsa	    (udev->pwr_save.type_refs[UE_ISOCHRONOUS] != 0) ||
2490208008Sthompsa	    (udev->pwr_save.write_refs != 0) ||
2491208008Sthompsa	    ((udev->pwr_save.read_refs != 0) &&
2492208008Sthompsa	    (udev->flags.usb_mode == USB_MODE_HOST) &&
2493208008Sthompsa	    (usb_peer_can_wakeup(udev) == 0)));
2494208008Sthompsa}
2495208008Sthompsa
2496208008Sthompsa/*------------------------------------------------------------------------*
2497194228Sthompsa *	usb_bus_powerd
2498186730Salfred *
2499186730Salfred * This function implements the USB power daemon and is called
2500186730Salfred * regularly from the USB explore thread.
2501186730Salfred *------------------------------------------------------------------------*/
2502190734Sthompsa#if USB_HAVE_POWERD
2503186730Salfredvoid
2504194228Sthompsausb_bus_powerd(struct usb_bus *bus)
2505186730Salfred{
2506192984Sthompsa	struct usb_device *udev;
2507193045Sthompsa	usb_ticks_t temp;
2508193045Sthompsa	usb_ticks_t limit;
2509193045Sthompsa	usb_ticks_t mintime;
2510193074Sthompsa	usb_size_t type_refs[5];
2511186730Salfred	uint8_t x;
2512186730Salfred
2513194228Sthompsa	limit = usb_power_timeout;
2514186730Salfred	if (limit == 0)
2515186730Salfred		limit = hz;
2516186730Salfred	else if (limit > 255)
2517186730Salfred		limit = 255 * hz;
2518186730Salfred	else
2519186730Salfred		limit = limit * hz;
2520186730Salfred
2521186730Salfred	DPRINTF("bus=%p\n", bus);
2522186730Salfred
2523186730Salfred	USB_BUS_LOCK(bus);
2524186730Salfred
2525186730Salfred	/*
2526186730Salfred	 * The root HUB device is never suspended
2527186730Salfred	 * and we simply skip it.
2528186730Salfred	 */
2529187171Sthompsa	for (x = USB_ROOT_HUB_ADDR + 1;
2530187171Sthompsa	    x != bus->devices_max; x++) {
2531186730Salfred
2532186730Salfred		udev = bus->devices[x];
2533186730Salfred		if (udev == NULL)
2534186730Salfred			continue;
2535186730Salfred
2536186730Salfred		temp = ticks - udev->pwr_save.last_xfer_time;
2537186730Salfred
2538208008Sthompsa		if (usb_peer_should_wakeup(udev)) {
2539186730Salfred			/* check if we are suspended */
2540191824Sthompsa			if (udev->flags.self_suspended != 0) {
2541186730Salfred				USB_BUS_UNLOCK(bus);
2542194228Sthompsa				usb_dev_resume_peer(udev);
2543186730Salfred				USB_BUS_LOCK(bus);
2544186730Salfred			}
2545208008Sthompsa		} else if ((temp >= limit) &&
2546208008Sthompsa		    (udev->flags.usb_mode == USB_MODE_HOST) &&
2547208008Sthompsa		    (udev->flags.self_suspended == 0)) {
2548208008Sthompsa			/* try to do suspend */
2549186730Salfred
2550208008Sthompsa			USB_BUS_UNLOCK(bus);
2551208008Sthompsa			usb_dev_suspend_peer(udev);
2552208008Sthompsa			USB_BUS_LOCK(bus);
2553186730Salfred		}
2554186730Salfred	}
2555186730Salfred
2556186730Salfred	/* reset counters */
2557186730Salfred
2558233774Shselasky	mintime = (usb_ticks_t)-1;
2559186730Salfred	type_refs[0] = 0;
2560186730Salfred	type_refs[1] = 0;
2561186730Salfred	type_refs[2] = 0;
2562186730Salfred	type_refs[3] = 0;
2563187730Sthompsa	type_refs[4] = 0;
2564186730Salfred
2565186730Salfred	/* Re-loop all the devices to get the actual state */
2566186730Salfred
2567187171Sthompsa	for (x = USB_ROOT_HUB_ADDR + 1;
2568187171Sthompsa	    x != bus->devices_max; x++) {
2569186730Salfred
2570186730Salfred		udev = bus->devices[x];
2571186730Salfred		if (udev == NULL)
2572186730Salfred			continue;
2573186730Salfred
2574187730Sthompsa		/* we found a non-Root-Hub USB device */
2575187730Sthompsa		type_refs[4] += 1;
2576187730Sthompsa
2577186730Salfred		/* "last_xfer_time" can be updated by a resume */
2578186730Salfred		temp = ticks - udev->pwr_save.last_xfer_time;
2579186730Salfred
2580186730Salfred		/*
2581186730Salfred		 * Compute minimum time since last transfer for the complete
2582186730Salfred		 * bus:
2583186730Salfred		 */
2584186730Salfred		if (temp < mintime)
2585186730Salfred			mintime = temp;
2586186730Salfred
2587191824Sthompsa		if (udev->flags.self_suspended == 0) {
2588186730Salfred			type_refs[0] += udev->pwr_save.type_refs[0];
2589186730Salfred			type_refs[1] += udev->pwr_save.type_refs[1];
2590186730Salfred			type_refs[2] += udev->pwr_save.type_refs[2];
2591186730Salfred			type_refs[3] += udev->pwr_save.type_refs[3];
2592186730Salfred		}
2593186730Salfred	}
2594186730Salfred
2595233774Shselasky	if (mintime >= (usb_ticks_t)(1 * hz)) {
2596186730Salfred		/* recompute power masks */
2597186730Salfred		DPRINTF("Recomputing power masks\n");
2598186730Salfred		bus->hw_power_state = 0;
2599186730Salfred		if (type_refs[UE_CONTROL] != 0)
2600186730Salfred			bus->hw_power_state |= USB_HW_POWER_CONTROL;
2601186730Salfred		if (type_refs[UE_BULK] != 0)
2602186730Salfred			bus->hw_power_state |= USB_HW_POWER_BULK;
2603186730Salfred		if (type_refs[UE_INTERRUPT] != 0)
2604186730Salfred			bus->hw_power_state |= USB_HW_POWER_INTERRUPT;
2605186730Salfred		if (type_refs[UE_ISOCHRONOUS] != 0)
2606186730Salfred			bus->hw_power_state |= USB_HW_POWER_ISOC;
2607187730Sthompsa		if (type_refs[4] != 0)
2608187730Sthompsa			bus->hw_power_state |= USB_HW_POWER_NON_ROOT_HUB;
2609186730Salfred	}
2610186730Salfred	USB_BUS_UNLOCK(bus);
2611186730Salfred
2612186730Salfred	if (bus->methods->set_hw_power != NULL) {
2613186730Salfred		/* always update hardware power! */
2614186730Salfred		(bus->methods->set_hw_power) (bus);
2615186730Salfred	}
2616186730Salfred	return;
2617186730Salfred}
2618190734Sthompsa#endif
2619186730Salfred
2620359318Shselaskystatic usb_error_t
2621359318Shselaskyusbd_device_30_remote_wakeup(struct usb_device *udev, uint8_t bRequest)
2622359318Shselasky{
2623359318Shselasky	struct usb_device_request req = {};
2624359318Shselasky
2625359318Shselasky	req.bmRequestType = UT_WRITE_INTERFACE;
2626359318Shselasky	req.bRequest = bRequest;
2627359318Shselasky	USETW(req.wValue, USB_INTERFACE_FUNC_SUSPEND);
2628359318Shselasky	USETW(req.wIndex, USB_INTERFACE_FUNC_SUSPEND_LP |
2629359318Shselasky	    USB_INTERFACE_FUNC_SUSPEND_RW);
2630359318Shselasky
2631359318Shselasky	return (usbd_do_request(udev, NULL, &req, 0));
2632359318Shselasky}
2633359318Shselasky
2634359318Shselaskystatic usb_error_t
2635359318Shselaskyusbd_clear_dev_wakeup(struct usb_device *udev)
2636359318Shselasky{
2637359318Shselasky	usb_error_t err;
2638359318Shselasky
2639359318Shselasky	if (usb_device_20_compatible(udev)) {
2640359318Shselasky		err = usbd_req_clear_device_feature(udev,
2641359318Shselasky		    NULL, UF_DEVICE_REMOTE_WAKEUP);
2642359318Shselasky	} else {
2643359318Shselasky		err = usbd_device_30_remote_wakeup(udev,
2644359318Shselasky		    UR_CLEAR_FEATURE);
2645359318Shselasky	}
2646359318Shselasky	return (err);
2647359318Shselasky}
2648359318Shselasky
2649359318Shselaskystatic usb_error_t
2650359318Shselaskyusbd_set_dev_wakeup(struct usb_device *udev)
2651359318Shselasky{
2652359318Shselasky	usb_error_t err;
2653359318Shselasky
2654359318Shselasky	if (usb_device_20_compatible(udev)) {
2655359318Shselasky		err = usbd_req_set_device_feature(udev,
2656359318Shselasky		    NULL, UF_DEVICE_REMOTE_WAKEUP);
2657359318Shselasky	} else {
2658359318Shselasky		err = usbd_device_30_remote_wakeup(udev,
2659359318Shselasky		    UR_SET_FEATURE);
2660359318Shselasky	}
2661359318Shselasky	return (err);
2662359318Shselasky}
2663359318Shselasky
2664186730Salfred/*------------------------------------------------------------------------*
2665194228Sthompsa *	usb_dev_resume_peer
2666186730Salfred *
2667186730Salfred * This function will resume an USB peer and do the required USB
2668186730Salfred * signalling to get an USB device out of the suspended state.
2669186730Salfred *------------------------------------------------------------------------*/
2670186730Salfredstatic void
2671194228Sthompsausb_dev_resume_peer(struct usb_device *udev)
2672186730Salfred{
2673192984Sthompsa	struct usb_bus *bus;
2674186730Salfred	int err;
2675186730Salfred
2676186730Salfred	/* be NULL safe */
2677186730Salfred	if (udev == NULL)
2678186730Salfred		return;
2679186730Salfred
2680186730Salfred	/* check if already resumed */
2681191824Sthompsa	if (udev->flags.self_suspended == 0)
2682186730Salfred		return;
2683186730Salfred
2684186730Salfred	/* we need a parent HUB to do resume */
2685186730Salfred	if (udev->parent_hub == NULL)
2686186730Salfred		return;
2687186730Salfred
2688186730Salfred	DPRINTF("udev=%p\n", udev);
2689186730Salfred
2690192499Sthompsa	if ((udev->flags.usb_mode == USB_MODE_DEVICE) &&
2691186730Salfred	    (udev->flags.remote_wakeup == 0)) {
2692186730Salfred		/*
2693186730Salfred		 * If the host did not set the remote wakeup feature, we can
2694186730Salfred		 * not wake it up either!
2695186730Salfred		 */
2696186730Salfred		DPRINTF("remote wakeup is not set!\n");
2697186730Salfred		return;
2698186730Salfred	}
2699186730Salfred	/* get bus pointer */
2700186730Salfred	bus = udev->bus;
2701186730Salfred
2702186730Salfred	/* resume parent hub first */
2703194228Sthompsa	usb_dev_resume_peer(udev->parent_hub);
2704186730Salfred
2705208008Sthompsa	/* reduce chance of instant resume failure by waiting a little bit */
2706208008Sthompsa	usb_pause_mtx(NULL, USB_MS_TO_TICKS(20));
2707208008Sthompsa
2708213435Shselasky	if (usb_device_20_compatible(udev)) {
2709213435Shselasky		/* resume current port (Valid in Host and Device Mode) */
2710213435Shselasky		err = usbd_req_clear_port_feature(udev->parent_hub,
2711213435Shselasky		    NULL, udev->port_no, UHF_PORT_SUSPEND);
2712213435Shselasky		if (err) {
2713213435Shselasky			DPRINTFN(0, "Resuming port failed\n");
2714213435Shselasky			return;
2715213435Shselasky		}
2716230032Shselasky	} else {
2717230032Shselasky		/* resume current port (Valid in Host and Device Mode) */
2718230032Shselasky		err = usbd_req_set_port_link_state(udev->parent_hub,
2719230032Shselasky		    NULL, udev->port_no, UPS_PORT_LS_U0);
2720230032Shselasky		if (err) {
2721230032Shselasky			DPRINTFN(0, "Resuming port failed\n");
2722230032Shselasky			return;
2723230032Shselasky		}
2724186730Salfred	}
2725213435Shselasky
2726186730Salfred	/* resume settle time */
2727241987Shselasky	usb_pause_mtx(NULL, USB_MS_TO_TICKS(usb_port_resume_delay));
2728186730Salfred
2729186730Salfred	if (bus->methods->device_resume != NULL) {
2730186730Salfred		/* resume USB device on the USB controller */
2731186730Salfred		(bus->methods->device_resume) (udev);
2732186730Salfred	}
2733186730Salfred	USB_BUS_LOCK(bus);
2734186730Salfred	/* set that this device is now resumed */
2735191824Sthompsa	udev->flags.self_suspended = 0;
2736190734Sthompsa#if USB_HAVE_POWERD
2737186730Salfred	/* make sure that we don't go into suspend right away */
2738186730Salfred	udev->pwr_save.last_xfer_time = ticks;
2739186730Salfred
2740186730Salfred	/* make sure the needed power masks are on */
2741186730Salfred	if (udev->pwr_save.type_refs[UE_CONTROL] != 0)
2742186730Salfred		bus->hw_power_state |= USB_HW_POWER_CONTROL;
2743186730Salfred	if (udev->pwr_save.type_refs[UE_BULK] != 0)
2744186730Salfred		bus->hw_power_state |= USB_HW_POWER_BULK;
2745186730Salfred	if (udev->pwr_save.type_refs[UE_INTERRUPT] != 0)
2746186730Salfred		bus->hw_power_state |= USB_HW_POWER_INTERRUPT;
2747186730Salfred	if (udev->pwr_save.type_refs[UE_ISOCHRONOUS] != 0)
2748186730Salfred		bus->hw_power_state |= USB_HW_POWER_ISOC;
2749190734Sthompsa#endif
2750186730Salfred	USB_BUS_UNLOCK(bus);
2751186730Salfred
2752186730Salfred	if (bus->methods->set_hw_power != NULL) {
2753186730Salfred		/* always update hardware power! */
2754186730Salfred		(bus->methods->set_hw_power) (bus);
2755186730Salfred	}
2756196498Salfred
2757208008Sthompsa	usbd_sr_lock(udev);
2758196498Salfred
2759186730Salfred	/* notify all sub-devices about resume */
2760194228Sthompsa	err = usb_suspend_resume(udev, 0);
2761186730Salfred
2762208008Sthompsa	usbd_sr_unlock(udev);
2763196498Salfred
2764186730Salfred	/* check if peer has wakeup capability */
2765230032Shselasky	if (usb_peer_can_wakeup(udev)) {
2766186730Salfred		/* clear remote wakeup */
2767359318Shselasky		err = usbd_clear_dev_wakeup(udev);
2768186730Salfred		if (err) {
2769186730Salfred			DPRINTFN(0, "Clearing device "
2770199816Sthompsa			    "remote wakeup failed: %s\n",
2771194228Sthompsa			    usbd_errstr(err));
2772186730Salfred		}
2773186730Salfred	}
2774186730Salfred}
2775186730Salfred
2776186730Salfred/*------------------------------------------------------------------------*
2777194228Sthompsa *	usb_dev_suspend_peer
2778186730Salfred *
2779186730Salfred * This function will suspend an USB peer and do the required USB
2780186730Salfred * signalling to get an USB device into the suspended state.
2781186730Salfred *------------------------------------------------------------------------*/
2782186730Salfredstatic void
2783194228Sthompsausb_dev_suspend_peer(struct usb_device *udev)
2784186730Salfred{
2785192984Sthompsa	struct usb_device *child;
2786186730Salfred	int err;
2787186730Salfred	uint8_t x;
2788186730Salfred	uint8_t nports;
2789186730Salfred
2790186730Salfredrepeat:
2791186730Salfred	/* be NULL safe */
2792186730Salfred	if (udev == NULL)
2793186730Salfred		return;
2794186730Salfred
2795186730Salfred	/* check if already suspended */
2796191824Sthompsa	if (udev->flags.self_suspended)
2797186730Salfred		return;
2798186730Salfred
2799186730Salfred	/* we need a parent HUB to do suspend */
2800186730Salfred	if (udev->parent_hub == NULL)
2801186730Salfred		return;
2802186730Salfred
2803186730Salfred	DPRINTF("udev=%p\n", udev);
2804186730Salfred
2805191396Sthompsa	/* check if the current device is a HUB */
2806191396Sthompsa	if (udev->hub != NULL) {
2807191396Sthompsa		nports = udev->hub->nports;
2808186730Salfred
2809191396Sthompsa		/* check if all devices on the HUB are suspended */
2810186730Salfred		for (x = 0; x != nports; x++) {
2811194228Sthompsa			child = usb_bus_port_get_device(udev->bus,
2812191396Sthompsa			    udev->hub->ports + x);
2813186730Salfred
2814186730Salfred			if (child == NULL)
2815186730Salfred				continue;
2816186730Salfred
2817191824Sthompsa			if (child->flags.self_suspended)
2818186730Salfred				continue;
2819186730Salfred
2820191396Sthompsa			DPRINTFN(1, "Port %u is busy on the HUB!\n", x + 1);
2821191396Sthompsa			return;
2822186730Salfred		}
2823186730Salfred	}
2824186730Salfred
2825230032Shselasky	if (usb_peer_can_wakeup(udev)) {
2826213435Shselasky		/*
2827213435Shselasky		 * This request needs to be done before we set
2828213435Shselasky		 * "udev->flags.self_suspended":
2829213435Shselasky		 */
2830213435Shselasky
2831213435Shselasky		/* allow device to do remote wakeup */
2832359318Shselasky		err = usbd_set_dev_wakeup(udev);
2833213435Shselasky		if (err) {
2834213435Shselasky			DPRINTFN(0, "Setting device "
2835213435Shselasky			    "remote wakeup failed\n");
2836213435Shselasky		}
2837213435Shselasky	}
2838213435Shselasky
2839208008Sthompsa	USB_BUS_LOCK(udev->bus);
2840208008Sthompsa	/*
2841208008Sthompsa	 * Checking for suspend condition and setting suspended bit
2842208008Sthompsa	 * must be atomic!
2843208008Sthompsa	 */
2844208008Sthompsa	err = usb_peer_should_wakeup(udev);
2845208008Sthompsa	if (err == 0) {
2846208008Sthompsa		/*
2847208008Sthompsa		 * Set that this device is suspended. This variable
2848208008Sthompsa		 * must be set before calling USB controller suspend
2849208008Sthompsa		 * callbacks.
2850208008Sthompsa		 */
2851208008Sthompsa		udev->flags.self_suspended = 1;
2852208008Sthompsa	}
2853208008Sthompsa	USB_BUS_UNLOCK(udev->bus);
2854196498Salfred
2855208008Sthompsa	if (err != 0) {
2856230032Shselasky		if (usb_peer_can_wakeup(udev)) {
2857213435Shselasky			/* allow device to do remote wakeup */
2858359318Shselasky			err = usbd_clear_dev_wakeup(udev);
2859213435Shselasky			if (err) {
2860213435Shselasky				DPRINTFN(0, "Setting device "
2861213435Shselasky				    "remote wakeup failed\n");
2862213435Shselasky			}
2863213435Shselasky		}
2864213435Shselasky
2865208008Sthompsa		if (udev->flags.usb_mode == USB_MODE_DEVICE) {
2866208008Sthompsa			/* resume parent HUB first */
2867208008Sthompsa			usb_dev_resume_peer(udev->parent_hub);
2868208008Sthompsa
2869208008Sthompsa			/* reduce chance of instant resume failure by waiting a little bit */
2870208008Sthompsa			usb_pause_mtx(NULL, USB_MS_TO_TICKS(20));
2871208008Sthompsa
2872208008Sthompsa			/* resume current port (Valid in Host and Device Mode) */
2873208008Sthompsa			err = usbd_req_clear_port_feature(udev->parent_hub,
2874208008Sthompsa			    NULL, udev->port_no, UHF_PORT_SUSPEND);
2875208008Sthompsa
2876208008Sthompsa			/* resume settle time */
2877241987Shselasky			usb_pause_mtx(NULL, USB_MS_TO_TICKS(usb_port_resume_delay));
2878208008Sthompsa		}
2879208008Sthompsa		DPRINTF("Suspend was cancelled!\n");
2880208008Sthompsa		return;
2881208008Sthompsa	}
2882208008Sthompsa
2883208008Sthompsa	usbd_sr_lock(udev);
2884208008Sthompsa
2885186730Salfred	/* notify all sub-devices about suspend */
2886194228Sthompsa	err = usb_suspend_resume(udev, 1);
2887186730Salfred
2888208008Sthompsa	usbd_sr_unlock(udev);
2889196498Salfred
2890186730Salfred	if (udev->bus->methods->device_suspend != NULL) {
2891193045Sthompsa		usb_timeout_t temp;
2892186730Salfred
2893186730Salfred		/* suspend device on the USB controller */
2894186730Salfred		(udev->bus->methods->device_suspend) (udev);
2895186730Salfred
2896186730Salfred		/* do DMA delay */
2897212134Sthompsa		temp = usbd_get_dma_delay(udev);
2898212134Sthompsa		if (temp != 0)
2899212134Sthompsa			usb_pause_mtx(NULL, USB_MS_TO_TICKS(temp));
2900186730Salfred
2901186730Salfred	}
2902213435Shselasky
2903213435Shselasky	if (usb_device_20_compatible(udev)) {
2904213435Shselasky		/* suspend current port */
2905213435Shselasky		err = usbd_req_set_port_feature(udev->parent_hub,
2906213435Shselasky		    NULL, udev->port_no, UHF_PORT_SUSPEND);
2907213435Shselasky		if (err) {
2908213435Shselasky			DPRINTFN(0, "Suspending port failed\n");
2909213435Shselasky			return;
2910213435Shselasky		}
2911230032Shselasky	} else {
2912230032Shselasky		/* suspend current port */
2913230032Shselasky		err = usbd_req_set_port_link_state(udev->parent_hub,
2914230032Shselasky		    NULL, udev->port_no, UPS_PORT_LS_U3);
2915230032Shselasky		if (err) {
2916230032Shselasky			DPRINTFN(0, "Suspending port failed\n");
2917230032Shselasky			return;
2918230032Shselasky		}
2919186730Salfred	}
2920191396Sthompsa
2921191396Sthompsa	udev = udev->parent_hub;
2922191396Sthompsa	goto repeat;
2923186730Salfred}
2924186730Salfred
2925186730Salfred/*------------------------------------------------------------------------*
2926194228Sthompsa *	usbd_set_power_mode
2927186730Salfred *
2928186730Salfred * This function will set the power mode, see USB_POWER_MODE_XXX for a
2929186730Salfred * USB device.
2930186730Salfred *------------------------------------------------------------------------*/
2931186730Salfredvoid
2932194228Sthompsausbd_set_power_mode(struct usb_device *udev, uint8_t power_mode)
2933186730Salfred{
2934186730Salfred	/* filter input argument */
2935187164Sthompsa	if ((power_mode != USB_POWER_MODE_ON) &&
2936212135Sthompsa	    (power_mode != USB_POWER_MODE_OFF))
2937186730Salfred		power_mode = USB_POWER_MODE_SAVE;
2938212135Sthompsa
2939212135Sthompsa	power_mode = usbd_filter_power_mode(udev, power_mode);
2940212135Sthompsa
2941186730Salfred	udev->power_mode = power_mode;	/* update copy of power mode */
2942186730Salfred
2943190734Sthompsa#if USB_HAVE_POWERD
2944194228Sthompsa	usb_bus_power_update(udev->bus);
2945257375Shselasky#else
2946257375Shselasky	usb_needs_explore(udev->bus, 0 /* no probe */ );
2947190734Sthompsa#endif
2948186730Salfred}
2949212135Sthompsa
2950212135Sthompsa/*------------------------------------------------------------------------*
2951212135Sthompsa *	usbd_filter_power_mode
2952212135Sthompsa *
2953212135Sthompsa * This function filters the power mode based on hardware requirements.
2954212135Sthompsa *------------------------------------------------------------------------*/
2955212135Sthompsauint8_t
2956212135Sthompsausbd_filter_power_mode(struct usb_device *udev, uint8_t power_mode)
2957212135Sthompsa{
2958212135Sthompsa	struct usb_bus_methods *mtod;
2959212135Sthompsa	int8_t temp;
2960212135Sthompsa
2961212135Sthompsa	mtod = udev->bus->methods;
2962212135Sthompsa	temp = -1;
2963212135Sthompsa
2964212135Sthompsa	if (mtod->get_power_mode != NULL)
2965212135Sthompsa		(mtod->get_power_mode) (udev, &temp);
2966212135Sthompsa
2967212135Sthompsa	/* check if we should not filter */
2968212135Sthompsa	if (temp < 0)
2969212135Sthompsa		return (power_mode);
2970212135Sthompsa
2971212135Sthompsa	/* use fixed power mode given by hardware driver */
2972212135Sthompsa	return (temp);
2973212135Sthompsa}
2974222786Shselasky
2975222786Shselasky/*------------------------------------------------------------------------*
2976222786Shselasky *	usbd_start_re_enumerate
2977222786Shselasky *
2978222786Shselasky * This function starts re-enumeration of the given USB device. This
2979222786Shselasky * function does not need to be called BUS-locked. This function does
2980222786Shselasky * not wait until the re-enumeration is completed.
2981222786Shselasky *------------------------------------------------------------------------*/
2982222786Shselaskyvoid
2983222786Shselaskyusbd_start_re_enumerate(struct usb_device *udev)
2984222786Shselasky{
2985257375Shselasky	if (udev->re_enumerate_wait == USB_RE_ENUM_DONE) {
2986257375Shselasky		udev->re_enumerate_wait = USB_RE_ENUM_START;
2987222786Shselasky		usb_needs_explore(udev->bus, 0);
2988222786Shselasky	}
2989222786Shselasky}
2990267347Shselasky
2991267347Shselasky/*-----------------------------------------------------------------------*
2992267347Shselasky *	usbd_start_set_config
2993267347Shselasky *
2994267347Shselasky * This function starts setting a USB configuration. This function
2995267347Shselasky * does not need to be called BUS-locked. This function does not wait
2996267347Shselasky * until the set USB configuratino is completed.
2997267347Shselasky *------------------------------------------------------------------------*/
2998267347Shselaskyusb_error_t
2999267347Shselaskyusbd_start_set_config(struct usb_device *udev, uint8_t index)
3000267347Shselasky{
3001267347Shselasky	if (udev->re_enumerate_wait == USB_RE_ENUM_DONE) {
3002267347Shselasky		if (udev->curr_config_index == index) {
3003267347Shselasky			/* no change needed */
3004267347Shselasky			return (0);
3005267347Shselasky		}
3006267347Shselasky		udev->next_config_index = index;
3007267347Shselasky		udev->re_enumerate_wait = USB_RE_ENUM_SET_CONFIG;
3008267347Shselasky		usb_needs_explore(udev->bus, 0);
3009267347Shselasky		return (0);
3010267347Shselasky	} else if (udev->re_enumerate_wait == USB_RE_ENUM_SET_CONFIG) {
3011267347Shselasky		if (udev->next_config_index == index) {
3012267347Shselasky			/* no change needed */
3013267347Shselasky			return (0);
3014267347Shselasky		}
3015267347Shselasky	}
3016267347Shselasky	return (USB_ERR_PENDING_REQUESTS);
3017267347Shselasky}
3018