xref: /aosp_15_r20/external/coreboot/payloads/libpayload/drivers/usb/generic_hub.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /*
2  *
3  * Copyright (C) 2013 secunet Security Networks AG
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. The name of the author may not be used to endorse or promote products
14  *    derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 //#define USB_DEBUG
30 
31 #include <stdlib.h>
32 #include <usb/usb.h>
33 #include "generic_hub.h"
34 
35 void
generic_hub_destroy(usbdev_t * const dev)36 generic_hub_destroy(usbdev_t *const dev)
37 {
38 	generic_hub_t *const hub = GEN_HUB(dev);
39 	if (!hub)
40 		return;
41 
42 	/* First, detach all devices behind this hub */
43 	int port;
44 	for (port = 1; port <= hub->num_ports; ++port) {
45 		if (hub->ports[port] >= 0) {
46 			usb_debug("generic_hub: Detachment at port %d\n", port);
47 			usb_detach_device(dev->controller, hub->ports[port]);
48 			hub->ports[port] = NO_DEV;
49 		}
50 	}
51 
52 	/* Disable all ports */
53 	if (hub->ops->disable_port) {
54 		for (port = 1; port <= hub->num_ports; ++port)
55 			hub->ops->disable_port(dev, port);
56 	}
57 
58 	free(hub->ports);
59 	free(hub);
60 }
61 
62 static int
generic_hub_debounce(usbdev_t * const dev,const int port)63 generic_hub_debounce(usbdev_t *const dev, const int port)
64 {
65 	generic_hub_t *const hub = GEN_HUB(dev);
66 
67 	const int step_ms	= 1;	/* linux uses 25ms, we're busy anyway */
68 	const int at_least_ms	= 100;	/* 100ms as in usb20 spec 9.1.2 */
69 	const int timeout_ms	= 1500;	/* linux uses this value */
70 
71 	int total_ms = 0;
72 	int stable_ms = 0;
73 	while (stable_ms < at_least_ms && total_ms < timeout_ms) {
74 		mdelay(step_ms);
75 
76 		const int changed = hub->ops->port_status_changed(dev, port);
77 		const int connected = hub->ops->port_connected(dev, port);
78 		if (changed < 0 || connected < 0)
79 			return -1;
80 
81 		if (!changed && connected) {
82 			stable_ms += step_ms;
83 		} else {
84 			usb_debug("generic_hub: Unstable connection at %d\n",
85 				  port);
86 			stable_ms = 0;
87 		}
88 		total_ms += step_ms;
89 	}
90 	if (total_ms >= timeout_ms)
91 		usb_debug("generic_hub: Debouncing timed out at %d\n", port);
92 	return 0; /* ignore timeouts, try to always go on */
93 }
94 
95 int
generic_hub_wait_for_port(usbdev_t * const dev,const int port,const int wait_for,int (* const port_op)(usbdev_t *,int),int timeout_steps,const int step_us)96 generic_hub_wait_for_port(usbdev_t *const dev, const int port,
97 			  const int wait_for,
98 			  int (*const port_op)(usbdev_t *, int),
99 			  int timeout_steps, const int step_us)
100 {
101 	int state;
102 	do {
103 		state = port_op(dev, port);
104 		if (state < 0)
105 			return -1;
106 		else if (!!state == wait_for)
107 			return timeout_steps;
108 		udelay(step_us);
109 		--timeout_steps;
110 	} while (timeout_steps);
111 	return 0;
112 }
113 
114 int
generic_hub_resetport(usbdev_t * const dev,const int port)115 generic_hub_resetport(usbdev_t *const dev, const int port)
116 {
117 	generic_hub_t *const hub = GEN_HUB(dev);
118 
119 	if (hub->ops->start_port_reset(dev, port) < 0)
120 		return -1;
121 
122 	/* wait for 10ms (usb20 spec 11.5.1.5: reset should take 10 to 20ms) */
123 	mdelay(10);
124 
125 	/* now wait 12ms for the hub to finish the reset */
126 	const int ret = generic_hub_wait_for_port(
127 			/* time out after 120 * 100us = 12ms */
128 			dev, port, 0, hub->ops->port_in_reset, 120, 100);
129 	if (ret < 0)
130 		return -1;
131 	else if (!ret)
132 		usb_debug("generic_hub: Reset timed out at port %d\n", port);
133 
134 	return 0; /* ignore timeouts, try to always go on */
135 }
136 
137 static int
generic_hub_detach_dev(usbdev_t * const dev,const int port)138 generic_hub_detach_dev(usbdev_t *const dev, const int port)
139 {
140 	generic_hub_t *const hub = GEN_HUB(dev);
141 
142 	usb_detach_device(dev->controller, hub->ports[port]);
143 	hub->ports[port] = NO_DEV;
144 
145 	return 0;
146 }
147 
148 static int
generic_hub_attach_dev(usbdev_t * const dev,const int port)149 generic_hub_attach_dev(usbdev_t *const dev, const int port)
150 {
151 	generic_hub_t *const hub = GEN_HUB(dev);
152 
153 	if (generic_hub_debounce(dev, port) < 0)
154 		return -1;
155 
156 	if (hub->ops->reset_port) {
157 		if (hub->ops->reset_port(dev, port) < 0)
158 			return -1;
159 
160 		if (!hub->ops->port_connected(dev, port)) {
161 			usb_debug(
162 				"generic_hub: Port %d disconnected after "
163 				"reset. Possibly upgraded, rescan required.\n",
164 				port);
165 			return 0;
166 		}
167 
168 		/* after reset the port will be enabled automatically */
169 		const int ret = generic_hub_wait_for_port(
170 			/* time out after 1,000 * 10us = 10ms */
171 			dev, port, 1, hub->ops->port_enabled, 1000, 10);
172 		if (ret < 0)
173 			return -1;
174 		else if (!ret)
175 			usb_debug("generic_hub: Port %d still "
176 				  "disabled after 10ms\n", port);
177 	}
178 
179 	const usb_speed speed = hub->ops->port_speed(dev, port);
180 	if (speed >= 0) {
181 		usb_debug("generic_hub: Success at port %d\n", port);
182 		if (hub->ops->reset_port)
183 			mdelay(10); /* Reset recovery time
184 				       (usb20 spec 7.1.7.5) */
185 		hub->ports[port] = usb_attach_device(
186 				dev->controller, dev->address, port, speed);
187 	}
188 	return 0;
189 }
190 
191 int
generic_hub_scanport(usbdev_t * const dev,const int port)192 generic_hub_scanport(usbdev_t *const dev, const int port)
193 {
194 	generic_hub_t *const hub = GEN_HUB(dev);
195 
196 	if (hub->ports[port] >= 0) {
197 		usb_debug("generic_hub: Detachment at port %d\n", port);
198 
199 		const int ret = generic_hub_detach_dev(dev, port);
200 		if (ret < 0)
201 			return ret;
202 	}
203 
204 	if (hub->ops->port_connected(dev, port)) {
205 		usb_debug("generic_hub: Attachment at port %d\n", port);
206 
207 		return generic_hub_attach_dev(dev, port);
208 	}
209 
210 	return 0;
211 }
212 
213 static void
generic_hub_poll(usbdev_t * const dev)214 generic_hub_poll(usbdev_t *const dev)
215 {
216 	generic_hub_t *const hub = GEN_HUB(dev);
217 	if (!hub)
218 		return;
219 
220 	if (!(dev->quirks & USB_QUIRK_HUB_NO_USBSTS_PCD) &&
221 	    hub->ops->hub_status_changed &&
222 	    hub->ops->hub_status_changed(dev) != 1) {
223 		return;
224 	}
225 
226 	int port;
227 	for (port = 1; port <= hub->num_ports; ++port) {
228 		const int ret = hub->ops->port_status_changed(dev, port);
229 		if (ret < 0) {
230 			return;
231 		} else if (ret == 1) {
232 			usb_debug("generic_hub: Port change at %d\n", port);
233 			if (generic_hub_scanport(dev, port) < 0)
234 				return;
235 		}
236 	}
237 }
238 
239 int
generic_hub_init(usbdev_t * const dev,const int num_ports,const generic_hub_ops_t * const ops)240 generic_hub_init(usbdev_t *const dev, const int num_ports,
241 		 const generic_hub_ops_t *const ops)
242 {
243 	int port;
244 
245 	dev->destroy = generic_hub_destroy;
246 	dev->poll = generic_hub_poll;
247 	dev->data = malloc(sizeof(generic_hub_t));
248 	if (!dev->data) {
249 		usb_debug("generic_hub: ERROR: Out of memory\n");
250 		return -1;
251 	}
252 
253 	generic_hub_t *const hub = GEN_HUB(dev);
254 	hub->num_ports = num_ports;
255 	hub->ports = malloc(sizeof(*hub->ports) * (num_ports + 1));
256 	hub->ops = ops;
257 	if (!hub->ports) {
258 		usb_debug("generic_hub: ERROR: Out of memory\n");
259 		free(dev->data);
260 		dev->data = NULL;
261 		return -1;
262 	}
263 	for (port = 1; port <= num_ports; ++port)
264 		hub->ports[port] = NO_DEV;
265 
266 	/* Enable all ports */
267 	if (ops->enable_port) {
268 		for (port = 1; port <= num_ports; ++port)
269 			ops->enable_port(dev, port);
270 		/* wait once for all ports */
271 		mdelay(20);
272 	}
273 
274 	return 0;
275 }
276