xref: /aosp_15_r20/external/coreboot/payloads/libpayload/drivers/usb/usbhub.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 <usb/usb.h>
32 #include "generic_hub.h"
33 
34 /* assume that host_to_device is overwritten if necessary */
35 #define DR_PORT gen_bmRequestType(host_to_device, class_type, other_recp)
36 /* status (and status change) bits */
37 #define PORT_CONNECTION		0x01
38 #define PORT_ENABLE		0x02
39 #define PORT_SUSPEND		0x04
40 #define PORT_OVER_CURRENT	0x08
41 #define PORT_RESET		0x10
42 #define BH_PORT_RESET		0x20
43 #define PORT_LINK_STATE		0x40
44 #define PORT_CONFIG_ERROR	0x80
45 /* feature selectors (for setting / clearing features) */
46 #define SEL_PORT_RESET		0x04
47 #define SEL_PORT_POWER		0x08
48 #define SEL_C_PORT_CONNECTION	0x10
49 #define SEL_C_PORT_ENABLE	0x11
50 #define SEL_C_PORT_SUSPEND	0x12
51 #define SEL_C_PORT_OVER_CURRENT	0x13
52 #define SEL_C_PORT_RESET	0x14
53 #define SEL_C_PORT_LINK_STATE	0x19
54 #define SEL_C_PORT_CONFIG_ERROR	0x1a
55 #define SEL_C_BH_PORT_RESET	0x1d
56 /* request type (USB 3.0 hubs only) */
57 #define SET_HUB_DEPTH		12
58 
59 static endpoint_t *
usb_hub_interrupt_ep(usbdev_t * const dev)60 usb_hub_interrupt_ep(usbdev_t *const dev)
61 {
62 	int i;
63 	for (i = 0; i < dev->num_endp; ++i) {
64 		if (dev->endpoints[i].type == INTERRUPT &&
65 				dev->endpoints[i].direction == IN)
66 			return &dev->endpoints[i];
67 	}
68 	return NULL;
69 }
70 
71 static int
usb_hub_port_status_changed(usbdev_t * const dev,const int port)72 usb_hub_port_status_changed(usbdev_t *const dev, const int port)
73 {
74 	unsigned short buf[2];
75 	int ret = get_status(dev, port, DR_PORT, sizeof(buf), buf);
76 	if (ret >= 0) {
77 		ret = buf[1] & PORT_CONNECTION;
78 		if (ret)
79 			clear_feature(dev, port, SEL_C_PORT_CONNECTION,
80 				       DR_PORT);
81 	}
82 	return ret;
83 }
84 
85 static int
usb_hub_port_connected(usbdev_t * const dev,const int port)86 usb_hub_port_connected(usbdev_t *const dev, const int port)
87 {
88 	unsigned short buf[2];
89 	int ret = get_status(dev, port, DR_PORT, sizeof(buf), buf);
90 	if (ret >= 0)
91 		ret = buf[0] & PORT_CONNECTION;
92 	return ret;
93 }
94 
95 static int
usb_hub_port_in_reset(usbdev_t * const dev,const int port)96 usb_hub_port_in_reset(usbdev_t *const dev, const int port)
97 {
98 	unsigned short buf[2];
99 	int ret = get_status(dev, port, DR_PORT, sizeof(buf), buf);
100 	if (ret >= 0)
101 		ret = buf[0] & PORT_RESET;
102 	return ret;
103 }
104 
105 static int
usb_hub_port_enabled(usbdev_t * const dev,const int port)106 usb_hub_port_enabled(usbdev_t *const dev, const int port)
107 {
108 	unsigned short buf[2];
109 	int ret = get_status(dev, port, DR_PORT, sizeof(buf), buf);
110 	if (ret >= 0)
111 		ret = buf[0] & PORT_ENABLE;
112 	return ret;
113 }
114 
115 static usb_speed
usb_hub_port_speed(usbdev_t * const dev,const int port)116 usb_hub_port_speed(usbdev_t *const dev, const int port)
117 {
118 	unsigned short buf[2];
119 	int ret = get_status(dev, port, DR_PORT, sizeof(buf), buf);
120 	if (ret >= 0 && (buf[0] & PORT_ENABLE)) {
121 		/* SuperSpeed hubs can only have SuperSpeed devices. */
122 		if (is_usb_speed_ss(dev->speed))
123 			return dev->speed;
124 
125 		/*[bit] 10  9  (USB 2.0 port status word)
126 		 *      0   0  full speed
127 		 *      0   1  low speed
128 		 *      1   0  high speed
129 		 *      1   1  invalid
130 		 */
131 		ret = (buf[0] >> 9) & 0x3;
132 		if (ret != 0x3)
133 			return ret;
134 	}
135 	return -1;
136 }
137 
138 static int
usb_hub_enable_port(usbdev_t * const dev,const int port)139 usb_hub_enable_port(usbdev_t *const dev, const int port)
140 {
141 	return set_feature(dev, port, SEL_PORT_POWER, DR_PORT);
142 }
143 
144 static int
usb_hub_start_port_reset(usbdev_t * const dev,const int port)145 usb_hub_start_port_reset(usbdev_t *const dev, const int port)
146 {
147 	return set_feature(dev, port, SEL_PORT_RESET, DR_PORT);
148 }
149 
usb_hub_set_hub_depth(usbdev_t * const dev)150 static void usb_hub_set_hub_depth(usbdev_t *const dev)
151 {
152 	dev_req_t dr = {
153 		.bmRequestType = gen_bmRequestType(host_to_device,
154 						   class_type, dev_recp),
155 		.bRequest = SET_HUB_DEPTH,
156 		.wValue = 0,
157 		.wIndex = 0,
158 		.wLength = 0,
159 	};
160 	usbdev_t *parent = dev;
161 	while (parent->hub > 0) {
162 		parent = dev->controller->devices[parent->hub];
163 		dr.wValue++;
164 	}
165 	int ret = dev->controller->control(dev, OUT, sizeof(dr), &dr, 0, NULL);
166 	if (ret < 0)
167 		usb_debug("Failed SET_HUB_DEPTH(%d) on hub %d: %d\n",
168 			  dr.wValue, dev->address, ret);
169 }
170 
171 static const generic_hub_ops_t usb_hub_ops = {
172 	.hub_status_changed	= NULL,
173 	.port_status_changed	= usb_hub_port_status_changed,
174 	.port_connected		= usb_hub_port_connected,
175 	.port_in_reset		= usb_hub_port_in_reset,
176 	.port_enabled		= usb_hub_port_enabled,
177 	.port_speed		= usb_hub_port_speed,
178 	.enable_port		= usb_hub_enable_port,
179 	.disable_port		= NULL,
180 	.start_port_reset	= usb_hub_start_port_reset,
181 	.reset_port		= generic_hub_resetport,
182 };
183 
184 /* Clear CSC if set and enumerate port if it's connected regardless of change
185    bits. Some broken hubs don't set CSC if already connected during reset. */
186 static void
usb_hub_port_initialize(usbdev_t * const dev,const int port)187 usb_hub_port_initialize(usbdev_t *const dev, const int port)
188 {
189 	unsigned short buf[2];
190 	int ret = get_status(dev, port, DR_PORT, sizeof(buf), buf);
191 	if (ret < 0)
192 		return;
193 	if (buf[1] & PORT_CONNECTION)
194 		clear_feature(dev, port, SEL_C_PORT_CONNECTION, DR_PORT);
195 	if (buf[0] & PORT_CONNECTION) {
196 		usb_debug("usbhub: Port coldplug at %d\n", port);
197 		generic_hub_scanport(dev, port);
198 	}
199 }
200 
201 static int
usb_hub_handle_port_change(usbdev_t * const dev,const int port)202 usb_hub_handle_port_change(usbdev_t *const dev, const int port)
203 {
204 	static const struct {
205 		unsigned short change_bit;
206 		unsigned short clear_sel;
207 	} change_bits[] = {
208 		{ PORT_CONNECTION,	SEL_C_PORT_CONNECTION	},
209 		{ PORT_ENABLE,		SEL_C_PORT_ENABLE	},
210 		{ PORT_SUSPEND,		SEL_C_PORT_SUSPEND	},
211 		{ PORT_OVER_CURRENT,	SEL_C_PORT_OVER_CURRENT	},
212 		{ PORT_RESET,		SEL_C_PORT_RESET	},
213 		{ BH_PORT_RESET,	SEL_C_BH_PORT_RESET	},
214 		{ PORT_LINK_STATE,	SEL_C_PORT_LINK_STATE	},
215 		{ PORT_CONFIG_ERROR,	SEL_C_PORT_CONFIG_ERROR	},
216 	};
217 	int ret = 0;
218 	unsigned int i;
219 	unsigned short checked_bits = 0;
220 	unsigned short buf[2] = { 0, 0 };
221 
222 	ret = get_status(dev, port, DR_PORT, sizeof(buf), buf);
223 	if (ret < 0)
224 		return ret;
225 
226 	/*
227 	 * Second word holds the change bits. The interrupt transfer shows
228 	 * a logical or of these bits, so we have to clear them all.
229 	 */
230 	for (i = 0; i < ARRAY_SIZE(change_bits); ++i) {
231 		if (buf[1] & change_bits[i].change_bit)
232 			clear_feature(dev, port, change_bits[i].clear_sel, DR_PORT);
233 		checked_bits |= change_bits[i].change_bit;
234 	}
235 	if (buf[1] & ~checked_bits)
236 		usb_debug("usbhub: Spurious change bit at port %d\n", port);
237 
238 	/* Now, handle connection changes. */
239 	if (buf[1] & PORT_CONNECTION) {
240 		usb_debug("usbhub: Port change at %d\n", port);
241 		ret = generic_hub_scanport(dev, port);
242 	}
243 	return ret;
244 }
245 
246 static void
usb_hub_poll(usbdev_t * const dev)247 usb_hub_poll(usbdev_t *const dev)
248 {
249 	unsigned int port, i;
250 	u8 buf[32] = { 0 };
251 	const u8 *ibuf;
252 
253 	/* First, gather all change bits from finished interrupt transfers. */
254 	const size_t port_bytes = MIN(ARRAY_SIZE(buf),
255 			div_round_up(GEN_HUB(dev)->num_ports + 1, 8));
256 	while ((ibuf = dev->controller->poll_intr_queue(GEN_HUB(dev)->data))) {
257 		for (i = 0; i < port_bytes; ++i)
258 			buf[i] |= ibuf[i];
259 	}
260 
261 	for (port = 1; port <= GEN_HUB(dev)->num_ports; ++port) {
262 		/* ports start at bit1; bit0 is hub status change */
263 		if (buf[port / 8] & (1 << (port % 8))) {
264 			if (usb_hub_handle_port_change(dev, port) < 0)
265 				return;
266 		}
267 	}
268 }
269 
270 static void
usb_hub_destroy(usbdev_t * const dev)271 usb_hub_destroy(usbdev_t *const dev)
272 {
273 	endpoint_t *const intr_ep = usb_hub_interrupt_ep(dev);
274 	dev->controller->destroy_intr_queue(intr_ep, GEN_HUB(dev)->data);
275 	generic_hub_destroy(dev);
276 }
277 
278 void
usb_hub_init(usbdev_t * const dev)279 usb_hub_init(usbdev_t *const dev)
280 {
281 	endpoint_t *const intr_ep = usb_hub_interrupt_ep(dev);
282 	if (!intr_ep) {
283 		usb_debug("usbhub: ERROR: No interrupt-in endpoint found\n");
284 		return;
285 	}
286 
287 	/* Get number of ports from hub descriptor */
288 	int type = is_usb_speed_ss(dev->speed) ? 0x2a : 0x29; /* similar enough */
289 	hub_descriptor_t desc;	/* won't fit the whole thing, we don't care */
290 	if (get_descriptor(dev, gen_bmRequestType(device_to_host, class_type,
291 		dev_recp), type, 0, &desc, sizeof(desc)) != sizeof(desc)) {
292 		usb_debug("get_descriptor(HUB) failed\n");
293 		usb_detach_device(dev->controller, dev->address);
294 		return;
295 	}
296 
297 	if (is_usb_speed_ss(dev->speed))
298 		usb_hub_set_hub_depth(dev);
299 
300 	/*
301 	 * Register interrupt transfer:
302 	 *   one bit per port + one bit for the hub,
303 	 *   20 transfers in the queue, like our HID driver,
304 	 *   one transfer per 256ms
305 	 */
306 	void *const intrq = dev->controller->create_intr_queue(
307 			intr_ep, intr_ep->maxpacketsize, 20, 256);
308 	if (!intrq) {
309 		usb_detach_device(dev->controller, dev->address);
310 		return;
311 	}
312 
313 	/*
314 	 * Limit the number of ports by the max packet size of
315 	 * the interrupt endpoint. This shouldn't be necessary
316 	 * but prevents a potential overflow in usb_hub_poll().
317 	 */
318 	const unsigned int num_ports =
319 		MIN(desc.bNbrPorts, intr_ep->maxpacketsize * 8 - 1);
320 	if (generic_hub_init(dev, num_ports, &usb_hub_ops)) {
321 		dev->controller->destroy_intr_queue(intr_ep, intrq);
322 		usb_detach_device(dev->controller, dev->address);
323 		return;
324 	}
325 
326 	int port;
327 	for (port = 1; port <= num_ports; ++port)
328 		usb_hub_port_initialize(dev, port);
329 
330 	GEN_HUB(dev)->data = intrq;
331 	dev->poll = usb_hub_poll;
332 	dev->destroy = usb_hub_destroy;
333 }
334