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

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

usb: update copyrights

The data was generated by a script, guided manually. If you feel your
name is missing somewhere, please add it!

The semi-automated process was roughly:

1) Changes per file and author (limited to our team) were counted
2) Trivial numbers were thrown away
3) Authors were sorted by lines added to file
4) All previous copyrights were replaced by the newly generated one
5) Hunks changing only year were discarded

It seems that a lot of my copyrights were added. It is due to me being
both sticking my nose everywhere and lazy to update the copyright right
away :)

  • Property mode set to 100644
File size: 9.1 KB
Line 
1/*
2 * Copyright (c) 2018 Petr Manek, Ondrej Hlavaty, Michal Staruch, Jaroslav Jindrak
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 */
81errno_t 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 errno_t 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 */
121errno_t xhci_rh_fini(xhci_rh_t *rh)
122{
123 assert(rh);
124 xhci_rh_stop(rh);
125
126 joinable_fibril_destroy(rh->event_worker);
127 xhci_sw_ring_fini(&rh->event_ring);
128 return EOK;
129}
130
131static rh_port_t *get_rh_port(usb_port_t *port)
132{
133 assert(port);
134 return (rh_port_t *) port;
135}
136
137/**
138 * Create and setup a device directly connected to RH. As the xHCI is not using
139 * a virtual usbhub device for RH, this routine is called for devices directly.
140 */
141static errno_t rh_enumerate_device(usb_port_t *usb_port)
142{
143 errno_t err;
144 rh_port_t *port = get_rh_port(usb_port);
145
146 if (port->major <= 2) {
147 /* USB ports for lower speeds needs their port reset first. */
148 XHCI_REG_SET(port->regs, XHCI_PORT_PR, 1);
149 if ((err = usb_port_wait_for_enabled(&port->base)))
150 return err;
151 } else {
152 /* Do the Warm reset to ensure the state is clear. */
153 XHCI_REG_SET(port->regs, XHCI_PORT_WPR, 1);
154 if ((err = usb_port_wait_for_enabled(&port->base)))
155 return err;
156 }
157
158 /*
159 * We cannot know in advance, whether the speed in the status register
160 * is valid - it depends on the protocol. So we read it later, but then
161 * we have to check if the port is still enabled.
162 */
163 uint32_t status = XHCI_REG_RD_FIELD(&port->regs->portsc, 32);
164
165 bool enabled = !!(status & XHCI_REG_MASK(XHCI_PORT_PED));
166 if (!enabled)
167 return ENOENT;
168
169 unsigned psiv = (status & XHCI_REG_MASK(XHCI_PORT_PS))
170 >> XHCI_REG_SHIFT(XHCI_PORT_PS);
171 const usb_speed_t speed = port->rh->hc->speeds[psiv].usb_speed;
172
173 device_t *dev = hcd_ddf_fun_create(&port->rh->hc->base, speed);
174 if (!dev) {
175 usb_log_error("Failed to create USB device function.");
176 return ENOMEM;
177 }
178
179 dev->hub = &port->rh->device.base;
180 dev->tier = 1;
181 dev->port = port - port->rh->ports + 1;
182
183 port->device = xhci_device_get(dev);
184 port->device->rh_port = dev->port;
185
186 usb_log_debug("Enumerating new %s-speed device on port %u.",
187 usb_str_speed(dev->speed), dev->port);
188
189 if ((err = bus_device_enumerate(dev))) {
190 usb_log_error("Failed to enumerate USB device: %s", str_error(err));
191 return err;
192 }
193
194 if (!ddf_fun_get_name(dev->fun)) {
195 bus_device_set_default_name(dev);
196 }
197
198 if ((err = ddf_fun_bind(dev->fun))) {
199 usb_log_error("Failed to register device " XHCI_DEV_FMT " DDF function: %s.",
200 XHCI_DEV_ARGS(*port->device), str_error(err));
201 goto err_usb_dev;
202 }
203
204 return EOK;
205
206err_usb_dev:
207 hcd_ddf_fun_destroy(dev);
208 port->device = NULL;
209 return err;
210}
211
212/**
213 * Deal with a detached device.
214 */
215static void rh_remove_device(usb_port_t *usb_port)
216{
217 rh_port_t *port = get_rh_port(usb_port);
218
219 assert(port->device);
220 usb_log_info("Device " XHCI_DEV_FMT " at port %zu has been disconnected.",
221 XHCI_DEV_ARGS(*port->device), port - port->rh->ports + 1);
222
223 /* Remove device from XHCI bus. */
224 bus_device_gone(&port->device->base);
225
226 /* Mark the device as detached. */
227 port->device = NULL;
228}
229
230/**
231 * Handle all changes on specified port.
232 */
233static void handle_port_change(xhci_rh_t *rh, uint8_t port_id)
234{
235 rh_port_t * const port = &rh->ports[port_id - 1];
236
237 uint32_t status = XHCI_REG_RD_FIELD(&port->regs->portsc, 32);
238
239 while (status & port_events_mask) {
240 /*
241 * The PED bit in xHCI has RW1C semantics, which means that
242 * writing 1 to it will disable the port. Which means all
243 * standard mechanisms of register handling fails here.
244 */
245 XHCI_REG_WR_FIELD(&port->regs->portsc,
246 status & ~XHCI_REG_MASK(XHCI_PORT_PED), 32);
247
248 const bool connected = !!(status & XHCI_REG_MASK(XHCI_PORT_CCS));
249 const bool enabled = !!(status & XHCI_REG_MASK(XHCI_PORT_PED));
250
251 if (status & XHCI_REG_MASK(XHCI_PORT_CSC)) {
252 usb_log_info("Connected state changed on port %u.", port_id);
253 status &= ~XHCI_REG_MASK(XHCI_PORT_CSC);
254
255 if (connected) {
256 usb_port_connected(&port->base, &rh_enumerate_device);
257 } else {
258 usb_port_disabled(&port->base, &rh_remove_device);
259 }
260 }
261
262 if (status & port_reset_mask) {
263 status &= ~port_reset_mask;
264
265 if (enabled) {
266 usb_port_enabled(&port->base);
267 } else {
268 usb_port_disabled(&port->base, &rh_remove_device);
269 }
270 }
271
272 status &= port_events_mask;
273 if (status != 0)
274 usb_log_debug("RH port %u change not handled: 0x%x", port_id, status);
275
276 /* Make sure that PSCEG is 0 before exiting the loop. */
277 status = XHCI_REG_RD_FIELD(&port->regs->portsc, 32);
278 }
279}
280
281void xhci_rh_set_ports_protocol(xhci_rh_t *rh,
282 unsigned offset, unsigned count, unsigned major)
283{
284 for (unsigned i = offset; i < offset + count; i++)
285 rh->ports[i - 1].major = major;
286}
287
288void xhci_rh_start(xhci_rh_t *rh)
289{
290 xhci_sw_ring_restart(&rh->event_ring);
291 joinable_fibril_start(rh->event_worker);
292
293 /* The reset changed status of all ports, and SW originated reason does
294 * not cause an interrupt.
295 */
296 for (uint8_t i = 0; i < rh->max_ports; ++i) {
297 handle_port_change(rh, i + 1);
298
299 rh_port_t * const port = &rh->ports[i];
300
301 /*
302 * When xHCI starts, for some reasons USB 3 ports do not have
303 * the CSC bit, even though they are connected. Try to find
304 * such ports.
305 */
306 if (XHCI_REG_RD(port->regs, XHCI_PORT_CCS)
307 && port->base.state == PORT_DISABLED)
308 usb_port_connected(&port->base, &rh_enumerate_device);
309 }
310}
311
312/**
313 * Disconnect all devices on all ports. On contrary to ordinary disconnect, this
314 * function waits until the disconnection routine is over.
315 */
316void xhci_rh_stop(xhci_rh_t *rh)
317{
318 xhci_sw_ring_stop(&rh->event_ring);
319 joinable_fibril_join(rh->event_worker);
320
321 for (uint8_t i = 0; i < rh->max_ports; ++i) {
322 rh_port_t * const port = &rh->ports[i];
323 usb_port_disabled(&port->base, &rh_remove_device);
324 usb_port_fini(&port->base);
325 }
326}
327
328static int rh_worker(void *arg)
329{
330 xhci_rh_t * const rh = arg;
331
332 xhci_trb_t trb;
333 while (xhci_sw_ring_dequeue(&rh->event_ring, &trb) == EOK) {
334 uint8_t port_id = XHCI_QWORD_EXTRACT(trb.parameter, 31, 24);
335 usb_log_debug("Port status change event detected for port %u.", port_id);
336 handle_port_change(rh, port_id);
337 }
338
339 return 0;
340}
341
342/**
343 * @}
344 */
Note: See TracBrowser for help on using the repository browser.