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

Last change on this file was 7c3fb9b, checked in by Jiri Svoboda <jiri@…>, 7 years ago

Fix block comment formatting (ccheck).

  • Property mode set to 100644
File size: 9.2 KB
RevLine 
[7bd99bf]1/*
[e0a5d4c]2 * Copyright (c) 2018 Petr Manek, Ondrej Hlavaty, Michal Staruch, Jaroslav Jindrak
[7bd99bf]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 */
[d967aa1]35
[7bd99bf]36#include <errno.h>
37#include <str_error.h>
[2770b66]38#include <usb/request.h>
[7bd99bf]39#include <usb/debug.h>
[20eaa82]40#include <usb/host/bus.h>
[867b375]41#include <usb/host/ddf_helpers.h>
[3b60ea0]42#include <usb/dma_buffer.h>
[2770b66]43#include <usb/host/hcd.h>
[a9fcd73]44#include <usb/port.h>
[867b375]45
[7bd99bf]46#include "debug.h"
[174788f]47#include "commands.h"
[370a1c8]48#include "endpoint.h"
[7bd99bf]49#include "hc.h"
50#include "hw_struct/trb.h"
[c8bb7090]51#include "rh.h"
[e9e24f2]52#include "transfers.h"
[7bd99bf]53
[9876e34]54/* This mask only lists registers, which imply port change. */
[9b56e528]55static const uint32_t port_events_mask =
[3bacee1]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);
[9876e34]63
[58f4c0f]64static const uint32_t port_reset_mask =
[3bacee1]65 XHCI_REG_MASK(XHCI_PORT_WRC) |
66 XHCI_REG_MASK(XHCI_PORT_PRC);
[58f4c0f]67
[a9fcd73]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
[047fbc8]76static int rh_worker(void *);
77
[eb928c4]78/**
79 * Initialize the roothub subsystem.
80 */
[45457265]81errno_t xhci_rh_init(xhci_rh_t *rh, xhci_hc_t *hc)
[d32d51d]82{
[5c5c9407]83 assert(rh);
[816335c]84 assert(hc);
85
86 rh->hc = hc;
[9876e34]87 rh->max_ports = XHCI_REG_RD(hc->cap_regs, XHCI_CAP_MAX_PORTS);
[a9fcd73]88 rh->ports = calloc(rh->max_ports, sizeof(rh_port_t));
89 if (!rh->ports)
90 return ENOMEM;
[5c5c9407]91
[45457265]92 const errno_t err = bus_device_init(&rh->device.base, &rh->hc->bus.base);
[a9fcd73]93 if (err) {
94 free(rh->ports);
[2cf28b9]95 return err;
[a9fcd73]96 }
97
[73a5857]98 rh->event_worker = joinable_fibril_create(&rh_worker, rh);
99 if (!rh->event_worker) {
[047fbc8]100 free(rh->ports);
101 return err;
102 }
103
[a9fcd73]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 }
[2cf28b9]109
110 /* Initialize route string */
111 rh->device.route_str = 0;
112
[047fbc8]113 xhci_sw_ring_init(&rh->event_ring, rh->max_ports);
114
[2cf28b9]115 return EOK;
[d32d51d]116}
117
[eb928c4]118/**
119 * Finalize the RH subsystem.
120 */
[45457265]121errno_t xhci_rh_fini(xhci_rh_t *rh)
[eb928c4]122{
123 assert(rh);
[17d34a8]124 xhci_rh_stop(rh);
[047fbc8]125
[17d34a8]126 joinable_fibril_destroy(rh->event_worker);
[047fbc8]127 xhci_sw_ring_fini(&rh->event_ring);
[eb928c4]128 return EOK;
129}
130
[a9fcd73]131static rh_port_t *get_rh_port(usb_port_t *port)
[49e62998]132{
[a9fcd73]133 assert(port);
134 return (rh_port_t *) port;
[49e62998]135}
136
[eb928c4]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.
[20eaa82]140 */
[45457265]141static errno_t rh_enumerate_device(usb_port_t *usb_port)
[867b375]142{
[45457265]143 errno_t err;
[a9fcd73]144 rh_port_t *port = get_rh_port(usb_port);
[2cf28b9]145
[a9fcd73]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;
[58f4c0f]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;
[9b56e528]156 }
157
[0f79283b]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
[3bacee1]169 unsigned psiv = (status & XHCI_REG_MASK(XHCI_PORT_PS)) >>
170 XHCI_REG_SHIFT(XHCI_PORT_PS);
[0f79283b]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);
[20eaa82]174 if (!dev) {
175 usb_log_error("Failed to create USB device function.");
176 return ENOMEM;
177 }
178
[a9fcd73]179 dev->hub = &port->rh->device.base;
[2aaba7e]180 dev->tier = 1;
[a9fcd73]181 dev->port = port - port->rh->ports + 1;
[eeca8a6]182
[a9fcd73]183 port->device = xhci_device_get(dev);
184 port->device->rh_port = dev->port;
[0206d35]185
[8033f89]186 usb_log_debug("Enumerating new %s-speed device on port %u.",
187 usb_str_speed(dev->speed), dev->port);
[36e8a0c8]188
[eb928c4]189 if ((err = bus_device_enumerate(dev))) {
[20eaa82]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)) {
[6832245]195 bus_device_set_default_name(dev);
[20eaa82]196 }
197
198 if ((err = ddf_fun_bind(dev->fun))) {
[9620a54]199 usb_log_error("Failed to register device " XHCI_DEV_FMT " DDF function: %s.",
[a9fcd73]200 XHCI_DEV_ARGS(*port->device), str_error(err));
[20eaa82]201 goto err_usb_dev;
202 }
203
204 return EOK;
205
206err_usb_dev:
[32fb6bce]207 hcd_ddf_fun_destroy(dev);
[a9fcd73]208 port->device = NULL;
[20eaa82]209 return err;
[867b375]210}
211
[eb928c4]212/**
213 * Deal with a detached device.
[f45c78f]214 */
[a9fcd73]215static void rh_remove_device(usb_port_t *usb_port)
[f45c78f]216{
[a9fcd73]217 rh_port_t *port = get_rh_port(usb_port);
[a4e26882]218
[a9fcd73]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);
[2cf28b9]222
[31cca4f3]223 /* Remove device from XHCI bus. */
[a9fcd73]224 bus_device_gone(&port->device->base);
[49e62998]225
[a9fcd73]226 /* Mark the device as detached. */
227 port->device = NULL;
[49e62998]228}
229
[eb928c4]230/**
[fb154e13]231 * Handle all changes on specified port.
[eb928c4]232 */
[047fbc8]233static void handle_port_change(xhci_rh_t *rh, uint8_t port_id)
[d32d51d]234{
[3bacee1]235 rh_port_t *const port = &rh->ports[port_id - 1];
[07c08ea]236
[a9fcd73]237 uint32_t status = XHCI_REG_RD_FIELD(&port->regs->portsc, 32);
[5c5c9407]238
[a9fcd73]239 while (status & port_events_mask) {
[9b56e528]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 */
[8033f89]245 XHCI_REG_WR_FIELD(&port->regs->portsc,
246 status & ~XHCI_REG_MASK(XHCI_PORT_PED), 32);
[5c5c9407]247
[58f4c0f]248 const bool connected = !!(status & XHCI_REG_MASK(XHCI_PORT_CCS));
249 const bool enabled = !!(status & XHCI_REG_MASK(XHCI_PORT_PED));
250
[a9fcd73]251 if (status & XHCI_REG_MASK(XHCI_PORT_CSC)) {
[fb154e13]252 usb_log_info("Connected state changed on port %u.", port_id);
[a9fcd73]253 status &= ~XHCI_REG_MASK(XHCI_PORT_CSC);
[5c5c9407]254
[f45c78f]255 if (connected) {
[a9fcd73]256 usb_port_connected(&port->base, &rh_enumerate_device);
[f45c78f]257 } else {
[a9fcd73]258 usb_port_disabled(&port->base, &rh_remove_device);
[f45c78f]259 }
[dcf0597]260 }
[5c5c9407]261
[58f4c0f]262 if (status & port_reset_mask) {
263 status &= ~port_reset_mask;
[fb154e13]264
[a9fcd73]265 if (enabled) {
[0f79283b]266 usb_port_enabled(&port->base);
[a9fcd73]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);
[a35b458]275
[fb154e13]276 /* Make sure that PSCEG is 0 before exiting the loop. */
[a9fcd73]277 status = XHCI_REG_RD_FIELD(&port->regs->portsc, 32);
[916991b]278 }
[07c08ea]279}
280
[8033f89]281void xhci_rh_set_ports_protocol(xhci_rh_t *rh,
[3bacee1]282 unsigned offset, unsigned count, unsigned major)
[07c08ea]283{
[a9fcd73]284 for (unsigned i = offset; i < offset + count; i++)
285 rh->ports[i - 1].major = major;
[07c08ea]286}
287
[19f0048]288void xhci_rh_start(xhci_rh_t *rh)
[05770666]289{
[19f0048]290 xhci_sw_ring_restart(&rh->event_ring);
291 joinable_fibril_start(rh->event_worker);
292
[7c3fb9b]293 /*
294 * The reset changed status of all ports, and SW originated reason does
[05770666]295 * not cause an interrupt.
296 */
[09187c6e]297 for (uint8_t i = 0; i < rh->max_ports; ++i) {
[047fbc8]298 handle_port_change(rh, i + 1);
[05770666]299
[3bacee1]300 rh_port_t *const port = &rh->ports[i];
[05770666]301
302 /*
303 * When xHCI starts, for some reasons USB 3 ports do not have
304 * the CSC bit, even though they are connected. Try to find
305 * such ports.
306 */
[3bacee1]307 if (XHCI_REG_RD(port->regs, XHCI_PORT_CCS) &&
308 port->base.state == PORT_DISABLED)
[05770666]309 usb_port_connected(&port->base, &rh_enumerate_device);
310 }
311}
312
[19f0048]313/**
314 * Disconnect all devices on all ports. On contrary to ordinary disconnect, this
315 * function waits until the disconnection routine is over.
316 */
317void xhci_rh_stop(xhci_rh_t *rh)
318{
319 xhci_sw_ring_stop(&rh->event_ring);
320 joinable_fibril_join(rh->event_worker);
321
322 for (uint8_t i = 0; i < rh->max_ports; ++i) {
[3bacee1]323 rh_port_t *const port = &rh->ports[i];
[19f0048]324 usb_port_disabled(&port->base, &rh_remove_device);
325 usb_port_fini(&port->base);
326 }
327}
328
[047fbc8]329static int rh_worker(void *arg)
330{
[3bacee1]331 xhci_rh_t *const rh = arg;
[047fbc8]332
333 xhci_trb_t trb;
334 while (xhci_sw_ring_dequeue(&rh->event_ring, &trb) == EOK) {
335 uint8_t port_id = XHCI_QWORD_EXTRACT(trb.parameter, 31, 24);
336 usb_log_debug("Port status change event detected for port %u.", port_id);
337 handle_port_change(rh, port_id);
338 }
339
[73a5857]340 return 0;
[047fbc8]341}
342
[c8bb7090]343/**
344 * @}
345 */
Note: See TracBrowser for help on using the repository browser.