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

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

xhci: reinitialize in case of HC error

  • Property mode set to 100644
File size: 9.2 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/dma_buffer.h>
43#include <usb/host/hcd.h>
44#include <usb/port.h>
45
46#include "debug.h"
47#include "commands.h"
48#include "endpoint.h"
49#include "hc.h"
50#include "hw_struct/trb.h"
51#include "rh.h"
52#include "transfers.h"
53
54/* This mask only lists registers, which imply port change. */
55static const uint32_t port_events_mask =
56 XHCI_REG_MASK(XHCI_PORT_CSC) |
57 XHCI_REG_MASK(XHCI_PORT_PEC) |
58 XHCI_REG_MASK(XHCI_PORT_WRC) |
59 XHCI_REG_MASK(XHCI_PORT_OCC) |
60 XHCI_REG_MASK(XHCI_PORT_PRC) |
61 XHCI_REG_MASK(XHCI_PORT_PLC) |
62 XHCI_REG_MASK(XHCI_PORT_CEC);
63
64static const uint32_t port_reset_mask =
65 XHCI_REG_MASK(XHCI_PORT_WRC) |
66 XHCI_REG_MASK(XHCI_PORT_PRC);
67
68typedef struct rh_port {
69 usb_port_t base;
70 xhci_rh_t *rh;
71 uint8_t major;
72 xhci_port_regs_t *regs;
73 xhci_device_t *device;
74} rh_port_t;
75
76static int rh_worker(void *);
77
78/**
79 * Initialize the roothub subsystem.
80 */
81int xhci_rh_init(xhci_rh_t *rh, xhci_hc_t *hc)
82{
83 assert(rh);
84 assert(hc);
85
86 rh->hc = hc;
87 rh->max_ports = XHCI_REG_RD(hc->cap_regs, XHCI_CAP_MAX_PORTS);
88 rh->ports = calloc(rh->max_ports, sizeof(rh_port_t));
89 if (!rh->ports)
90 return ENOMEM;
91
92 const int err = bus_device_init(&rh->device.base, &rh->hc->bus.base);
93 if (err) {
94 free(rh->ports);
95 return err;
96 }
97
98 rh->event_worker = joinable_fibril_create(&rh_worker, rh);
99 if (!rh->event_worker) {
100 free(rh->ports);
101 return err;
102 }
103
104 for (unsigned i = 0; i < rh->max_ports; i++) {
105 usb_port_init(&rh->ports[i].base);
106 rh->ports[i].rh = rh;
107 rh->ports[i].regs = &rh->hc->op_regs->portrs[i];
108 }
109
110 /* Initialize route string */
111 rh->device.route_str = 0;
112
113 xhci_sw_ring_init(&rh->event_ring, rh->max_ports);
114
115 return EOK;
116}
117
118/**
119 * Finalize the RH subsystem.
120 */
121int xhci_rh_fini(xhci_rh_t *rh)
122{
123 assert(rh);
124 for (unsigned i = 0; i < rh->max_ports; i++)
125 usb_port_fini(&rh->ports[i].base);
126
127 xhci_sw_ring_stop(&rh->event_ring);
128 joinable_fibril_join(rh->event_worker);
129 xhci_sw_ring_fini(&rh->event_ring);
130 return EOK;
131}
132
133static rh_port_t *get_rh_port(usb_port_t *port)
134{
135 assert(port);
136 return (rh_port_t *) port;
137}
138
139/**
140 * Create and setup a device directly connected to RH. As the xHCI is not using
141 * a virtual usbhub device for RH, this routine is called for devices directly.
142 */
143static int rh_enumerate_device(usb_port_t *usb_port)
144{
145 int err;
146 rh_port_t *port = get_rh_port(usb_port);
147
148 if (port->major <= 2) {
149 /* USB ports for lower speeds needs their port reset first. */
150 XHCI_REG_SET(port->regs, XHCI_PORT_PR, 1);
151 if ((err = usb_port_wait_for_enabled(&port->base)))
152 return err;
153 } else {
154 /* Do the Warm reset to ensure the state is clear. */
155 XHCI_REG_SET(port->regs, XHCI_PORT_WPR, 1);
156 if ((err = usb_port_wait_for_enabled(&port->base)))
157 return err;
158 }
159
160 /*
161 * We cannot know in advance, whether the speed in the status register
162 * is valid - it depends on the protocol. So we read it later, but then
163 * we have to check if the port is still enabled.
164 */
165 uint32_t status = XHCI_REG_RD_FIELD(&port->regs->portsc, 32);
166
167 bool enabled = !!(status & XHCI_REG_MASK(XHCI_PORT_PED));
168 if (!enabled)
169 return ENOENT;
170
171 unsigned psiv = (status & XHCI_REG_MASK(XHCI_PORT_PS))
172 >> XHCI_REG_SHIFT(XHCI_PORT_PS);
173 const usb_speed_t speed = port->rh->hc->speeds[psiv].usb_speed;
174
175 device_t *dev = hcd_ddf_fun_create(&port->rh->hc->base, speed);
176 if (!dev) {
177 usb_log_error("Failed to create USB device function.");
178 return ENOMEM;
179 }
180
181 dev->hub = &port->rh->device.base;
182 dev->tier = 1;
183 dev->port = port - port->rh->ports + 1;
184
185 port->device = xhci_device_get(dev);
186 port->device->rh_port = dev->port;
187
188 usb_log_debug("Enumerating new %s-speed device on port %u.",
189 usb_str_speed(dev->speed), dev->port);
190
191 if ((err = bus_device_enumerate(dev))) {
192 usb_log_error("Failed to enumerate USB device: %s", str_error(err));
193 return err;
194 }
195
196 if (!ddf_fun_get_name(dev->fun)) {
197 bus_device_set_default_name(dev);
198 }
199
200 if ((err = ddf_fun_bind(dev->fun))) {
201 usb_log_error("Failed to register device " XHCI_DEV_FMT " DDF function: %s.",
202 XHCI_DEV_ARGS(*port->device), str_error(err));
203 goto err_usb_dev;
204 }
205
206 return EOK;
207
208err_usb_dev:
209 hcd_ddf_fun_destroy(dev);
210 port->device = NULL;
211 return err;
212}
213
214/**
215 * Deal with a detached device.
216 */
217static void rh_remove_device(usb_port_t *usb_port)
218{
219 rh_port_t *port = get_rh_port(usb_port);
220
221 assert(port->device);
222 usb_log_info("Device " XHCI_DEV_FMT " at port %zu has been disconnected.",
223 XHCI_DEV_ARGS(*port->device), port - port->rh->ports + 1);
224
225 /* Remove device from XHCI bus. */
226 bus_device_gone(&port->device->base);
227
228 /* Mark the device as detached. */
229 port->device = NULL;
230}
231
232/**
233 * Handle all changes on specified port.
234 */
235static void handle_port_change(xhci_rh_t *rh, uint8_t port_id)
236{
237 rh_port_t * const port = &rh->ports[port_id - 1];
238
239 uint32_t status = XHCI_REG_RD_FIELD(&port->regs->portsc, 32);
240
241 while (status & port_events_mask) {
242 /*
243 * The PED bit in xHCI has RW1C semantics, which means that
244 * writing 1 to it will disable the port. Which means all
245 * standard mechanisms of register handling fails here.
246 */
247 XHCI_REG_WR_FIELD(&port->regs->portsc,
248 status & ~XHCI_REG_MASK(XHCI_PORT_PED), 32);
249
250 const bool connected = !!(status & XHCI_REG_MASK(XHCI_PORT_CCS));
251 const bool enabled = !!(status & XHCI_REG_MASK(XHCI_PORT_PED));
252
253 if (status & XHCI_REG_MASK(XHCI_PORT_CSC)) {
254 usb_log_info("Connected state changed on port %u.", port_id);
255 status &= ~XHCI_REG_MASK(XHCI_PORT_CSC);
256
257 if (connected) {
258 usb_port_connected(&port->base, &rh_enumerate_device);
259 } else {
260 usb_port_disabled(&port->base, &rh_remove_device);
261 }
262 }
263
264 if (status & port_reset_mask) {
265 status &= ~port_reset_mask;
266
267 if (enabled) {
268 usb_port_enabled(&port->base);
269 } else {
270 usb_port_disabled(&port->base, &rh_remove_device);
271 }
272 }
273
274 status &= port_events_mask;
275 if (status != 0)
276 usb_log_debug("RH port %u change not handled: 0x%x", port_id, status);
277
278 /* Make sure that PSCEG is 0 before exiting the loop. */
279 status = XHCI_REG_RD_FIELD(&port->regs->portsc, 32);
280 }
281}
282
283void xhci_rh_set_ports_protocol(xhci_rh_t *rh,
284 unsigned offset, unsigned count, unsigned major)
285{
286 for (unsigned i = offset; i < offset + count; i++)
287 rh->ports[i - 1].major = major;
288}
289
290void xhci_rh_start(xhci_rh_t *rh)
291{
292 xhci_sw_ring_restart(&rh->event_ring);
293 joinable_fibril_start(rh->event_worker);
294
295 /* The reset changed status of all ports, and SW originated reason does
296 * not cause an interrupt.
297 */
298 for (uint8_t i = 0; i < rh->max_ports; ++i) {
299 handle_port_change(rh, i + 1);
300
301 rh_port_t * const port = &rh->ports[i];
302
303 /*
304 * When xHCI starts, for some reasons USB 3 ports do not have
305 * the CSC bit, even though they are connected. Try to find
306 * such ports.
307 */
308 if (XHCI_REG_RD(port->regs, XHCI_PORT_CCS)
309 && port->base.state == PORT_DISABLED)
310 usb_port_connected(&port->base, &rh_enumerate_device);
311 }
312}
313
314/**
315 * Disconnect all devices on all ports. On contrary to ordinary disconnect, this
316 * function waits until the disconnection routine is over.
317 */
318void xhci_rh_stop(xhci_rh_t *rh)
319{
320 xhci_sw_ring_stop(&rh->event_ring);
321 joinable_fibril_join(rh->event_worker);
322
323 for (uint8_t i = 0; i < rh->max_ports; ++i) {
324 rh_port_t * const port = &rh->ports[i];
325 usb_port_disabled(&port->base, &rh_remove_device);
326 usb_port_fini(&port->base);
327 }
328}
329
330static int rh_worker(void *arg)
331{
332 xhci_rh_t * const rh = arg;
333
334 xhci_trb_t trb;
335 while (xhci_sw_ring_dequeue(&rh->event_ring, &trb) == EOK) {
336 uint8_t port_id = XHCI_QWORD_EXTRACT(trb.parameter, 31, 24);
337 usb_log_debug("Port status change event detected for port %u.", port_id);
338 handle_port_change(rh, port_id);
339 }
340
341 return 0;
342}
343
344/**
345 * @}
346 */
Note: See TracBrowser for help on using the repository browser.