source: mainline/uspace/drv/bus/usb/xhci/rh.c@ fb154e13

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since fb154e13 was fb154e13, checked in by Ondřej Hlavatý <aearsis@…>, 8 years ago

xhci: revised roothub event handling

According to the xHCI specification, Port Status Change Event is
generated per port. Also, the PCD bit can be safely ignored. Added
mutual exclusion to roothub event handling to avoid duplicate device
adding.

  • Property mode set to 100644
File size: 9.4 KB
Line 
1/*
2 * Copyright (c) 2017 Michal Staruch
3 * All rights reserved.
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 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup drvusbxhci
30 * @{
31 */
32/** @file
33 * @brief The roothub structures abstraction.
34 */
35
36#include <errno.h>
37#include <str_error.h>
38#include <usb/request.h>
39#include <usb/debug.h>
40#include <usb/host/bus.h>
41#include <usb/host/ddf_helpers.h>
42#include <usb/host/dma_buffer.h>
43#include <usb/host/hcd.h>
44
45#include "debug.h"
46#include "commands.h"
47#include "endpoint.h"
48#include "hc.h"
49#include "hw_struct/trb.h"
50#include "rh.h"
51#include "transfers.h"
52
53/* This mask only lists registers, which imply port change. */
54static const uint32_t port_change_mask =
55 XHCI_REG_MASK(XHCI_PORT_CSC) |
56 XHCI_REG_MASK(XHCI_PORT_PEC) |
57 XHCI_REG_MASK(XHCI_PORT_WRC) |
58 XHCI_REG_MASK(XHCI_PORT_OCC) |
59 XHCI_REG_MASK(XHCI_PORT_PRC) |
60 XHCI_REG_MASK(XHCI_PORT_PLC) |
61 XHCI_REG_MASK(XHCI_PORT_CEC);
62
63/**
64 * Initialize the roothub subsystem.
65 */
66int xhci_rh_init(xhci_rh_t *rh, xhci_hc_t *hc)
67{
68 assert(rh);
69 assert(hc);
70
71 rh->hc = hc;
72 rh->max_ports = XHCI_REG_RD(hc->cap_regs, XHCI_CAP_MAX_PORTS);
73 rh->devices_by_port = (xhci_device_t **) calloc(rh->max_ports, sizeof(xhci_device_t *));
74
75 const int err = bus_device_init(&rh->device.base, &rh->hc->bus.base);
76 if (err)
77 return err;
78
79 /* Initialize route string */
80 rh->device.route_str = 0;
81 rh->device.tier = 0;
82
83 fibril_mutex_initialize(&rh->event_guard);
84 fibril_condvar_initialize(&rh->event_ready);
85 fibril_condvar_initialize(&rh->event_handled);
86
87 return EOK;
88}
89
90/**
91 * Finalize the RH subsystem.
92 */
93int xhci_rh_fini(xhci_rh_t *rh)
94{
95 assert(rh);
96 free(rh->devices_by_port);
97 return EOK;
98}
99
100static int rh_event_wait_timeout(xhci_rh_t *rh, uint8_t port_id, uint32_t mask, suseconds_t timeout)
101{
102 int r;
103 assert(fibril_mutex_is_locked(&rh->event_guard));
104
105 ++rh->event_readers_waiting;
106
107 do {
108 r = fibril_condvar_wait_timeout(&rh->event_ready, &rh->event_guard, timeout);
109 if (r != EOK)
110 break;
111 } while (rh->event.port_id != port_id || (rh->event.events & mask) != mask);
112
113 if (r == EOK)
114 rh->event.events &= ~mask;
115
116 --rh->event_readers_waiting;
117 if (--rh->event_readers_to_go == 0)
118 fibril_condvar_broadcast(&rh->event_handled);
119
120 return r;
121}
122
123static void rh_event_run_handlers(xhci_rh_t *rh)
124{
125 assert(fibril_mutex_is_locked(&rh->event_guard));
126 assert(rh->event_readers_to_go == 0);
127
128 rh->event_readers_to_go = rh->event_readers_waiting;
129 fibril_condvar_broadcast(&rh->event_ready);
130 while (rh->event_readers_to_go)
131 fibril_condvar_wait(&rh->event_handled, &rh->event_guard);
132}
133
134/**
135 * Create and setup a device directly connected to RH. As the xHCI is not using
136 * a virtual usbhub device for RH, this routine is called for devices directly.
137 */
138static int rh_setup_device(xhci_rh_t *rh, uint8_t port_id)
139{
140 int err;
141 assert(rh);
142
143 assert(rh->devices_by_port[port_id - 1] == NULL);
144
145 device_t *dev = hcd_ddf_fun_create(&rh->hc->base);
146 if (!dev) {
147 usb_log_error("Failed to create USB device function.");
148 return ENOMEM;
149 }
150
151 const xhci_port_speed_t *port_speed = xhci_rh_get_port_speed(rh, port_id);
152 xhci_device_t *xhci_dev = xhci_device_get(dev);
153 xhci_dev->usb3 = port_speed->major == 3;
154 xhci_dev->rh_port = port_id;
155
156 dev->hub = &rh->device.base;
157 dev->port = port_id;
158 dev->speed = port_speed->usb_speed;
159
160 if ((err = bus_device_enumerate(dev))) {
161 usb_log_error("Failed to enumerate USB device: %s", str_error(err));
162 return err;
163 }
164
165 if (!ddf_fun_get_name(dev->fun)) {
166 bus_device_set_default_name(dev);
167 }
168
169 if ((err = ddf_fun_bind(dev->fun))) {
170 usb_log_error("Failed to register device " XHCI_DEV_FMT " DDF function: %s.",
171 XHCI_DEV_ARGS(*xhci_dev), str_error(err));
172 goto err_usb_dev;
173 }
174
175 fibril_mutex_lock(&rh->device.base.guard);
176 list_append(&dev->link, &rh->device.base.devices);
177 rh->devices_by_port[port_id - 1] = xhci_dev;
178 fibril_mutex_unlock(&rh->device.base.guard);
179
180 return EOK;
181
182err_usb_dev:
183 hcd_ddf_fun_destroy(dev);
184 return err;
185}
186
187
188static int rh_port_reset_sync(xhci_rh_t *rh, uint8_t port_id)
189{
190 xhci_port_regs_t *regs = &rh->hc->op_regs->portrs[port_id - 1];
191
192 fibril_mutex_lock(&rh->event_guard);
193 XHCI_REG_SET(regs, XHCI_PORT_PR, 1);
194 const int r = rh_event_wait_timeout(rh, port_id, XHCI_REG_MASK(XHCI_PORT_PRC), 0);
195 fibril_mutex_unlock(&rh->event_guard);
196
197 return r;
198}
199
200/**
201 * Handle a device connection. USB 3+ devices are set up directly, USB 2 and
202 * below first need to have their port reset.
203 */
204static int handle_connected_device(xhci_rh_t *rh, uint8_t port_id)
205{
206 xhci_port_regs_t *regs = &rh->hc->op_regs->portrs[port_id - 1];
207
208 uint8_t link_state = XHCI_REG_RD(regs, XHCI_PORT_PLS);
209 const xhci_port_speed_t *speed = xhci_rh_get_port_speed(rh, port_id);
210
211 usb_log_info("Detected new %.4s%u.%u device on port %u.", speed->name, speed->major, speed->minor, port_id);
212
213 if (speed->major == 3) {
214 if (link_state == 0) {
215 /* USB3 is automatically advanced to enabled. */
216 return rh_setup_device(rh, port_id);
217 }
218 else if (link_state == 5) {
219 /* USB 3 failed to enable. */
220 usb_log_error("USB 3 port couldn't be enabled.");
221 return EAGAIN;
222 }
223 else {
224 usb_log_error("USB 3 port is in invalid state %u.", link_state);
225 return EINVAL;
226 }
227 }
228 else {
229 usb_log_debug("USB 2 device attached, issuing reset.");
230 const int err = rh_port_reset_sync(rh, port_id);
231 if (err)
232 return err;
233
234 rh_setup_device(rh, port_id);
235 return EOK;
236 }
237}
238
239/**
240 * Deal with a detached device.
241 */
242static int handle_disconnected_device(xhci_rh_t *rh, uint8_t port_id)
243{
244 assert(rh);
245
246 /* Find XHCI device by the port. */
247 xhci_device_t *dev = rh->devices_by_port[port_id - 1];
248 if (!dev) {
249 /* Must be extraneous call. */
250 return EOK;
251 }
252
253 usb_log_info("Device " XHCI_DEV_FMT " at port %u has been disconnected.",
254 XHCI_DEV_ARGS(*dev), port_id);
255
256 /* Mark the device as detached. */
257 fibril_mutex_lock(&rh->device.base.guard);
258 list_remove(&dev->base.link);
259 rh->devices_by_port[port_id - 1] = NULL;
260 fibril_mutex_unlock(&rh->device.base.guard);
261
262 /* Remove device from XHCI bus. */
263 bus_device_gone(&dev->base);
264
265 return EOK;
266}
267
268typedef int (*rh_event_handler_t)(xhci_rh_t *, uint8_t);
269
270typedef struct rh_event_args {
271 xhci_rh_t *rh;
272 uint8_t port_id;
273 rh_event_handler_t handler;
274} rh_event_args_t;
275
276static int rh_event_handler_fibril(void *arg) {
277 rh_event_args_t *rh_args = arg;
278 xhci_rh_t *rh = rh_args->rh;
279 uint8_t port_id = rh_args->port_id;
280 rh_event_handler_t handler = rh_args->handler;
281
282 free(rh_args);
283
284 return handler(rh, port_id);
285}
286
287static fid_t handle_in_fibril(xhci_rh_t *rh, uint8_t port_id, rh_event_handler_t handler)
288{
289 rh_event_args_t *args = malloc(sizeof(*args));
290 *args = (rh_event_args_t) {
291 .rh = rh,
292 .port_id = port_id,
293 .handler = handler,
294 };
295
296 const fid_t fid = fibril_create(rh_event_handler_fibril, args);
297 fibril_add_ready(fid);
298 return fid;
299}
300
301/**
302 * Handle all changes on specified port.
303 */
304void xhci_rh_handle_port_change(xhci_rh_t *rh, uint8_t port_id)
305{
306 fibril_mutex_lock(&rh->event_guard);
307 xhci_port_regs_t * const regs = &rh->hc->op_regs->portrs[port_id - 1];
308
309 uint32_t events = XHCI_REG_RD_FIELD(&regs->portsc, 32) & port_change_mask;
310
311 while (events) {
312 XHCI_REG_SET_FIELD(&regs->portsc, events, 32);
313
314 if (events & XHCI_REG_MASK(XHCI_PORT_CSC)) {
315 usb_log_info("Connected state changed on port %u.", port_id);
316 events &= ~XHCI_REG_MASK(XHCI_PORT_CSC);
317
318 bool connected = XHCI_REG_RD(regs, XHCI_PORT_CCS);
319 if (connected) {
320 handle_in_fibril(rh, port_id, handle_connected_device);
321 } else {
322 handle_in_fibril(rh, port_id, handle_disconnected_device);
323 }
324 }
325
326 if (events != 0) {
327 rh->event.port_id = port_id;
328 rh->event.events = events;
329 rh_event_run_handlers(rh);
330 }
331
332 if (rh->event.events != 0)
333 usb_log_debug("RH port %u change not handled: 0x%x", port_id, rh->event.events);
334
335 /* Make sure that PSCEG is 0 before exiting the loop. */
336 events = XHCI_REG_RD_FIELD(&regs->portsc, 32) & port_change_mask;
337 }
338
339 fibril_mutex_unlock(&rh->event_guard);
340}
341
342/**
343 * Get a port speed for a given port id.
344 */
345const xhci_port_speed_t *xhci_rh_get_port_speed(xhci_rh_t *rh, uint8_t port)
346{
347 xhci_port_regs_t *port_regs = &rh->hc->op_regs->portrs[port - 1];
348
349 unsigned psiv = XHCI_REG_RD(port_regs, XHCI_PORT_PS);
350 return &rh->hc->speeds[psiv];
351}
352
353/**
354 * @}
355 */
Note: See TracBrowser for help on using the repository browser.