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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 0c4c6a2 was 0c4c6a2, checked in by Petr Manek <petr.manek@…>, 8 years ago

Invoking virtual interrupt on the emulated root hub upon status change.

  • Property mode set to 100644
File size: 15.5 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/debug.h>
39#include <usb/host/utils/malloc32.h>
40#include "debug.h"
41#include "commands.h"
42#include "endpoint.h"
43#include "hc.h"
44#include "hw_struct/trb.h"
45#include "rh.h"
46
47enum {
48 HUB_STATUS_CHANGE_PIPE = 1,
49};
50
51static usbvirt_device_ops_t ops;
52
53int xhci_rh_init(xhci_rh_t *rh, xhci_hc_t *hc)
54{
55 assert(rh);
56 assert(hc);
57
58 rh->hc = hc;
59
60 usb_hub_descriptor_header_t *header = &rh->hub_descriptor.header;
61 header->length = sizeof(usb_hub_descriptor_header_t);
62 header->descriptor_type = USB_DESCTYPE_HUB;
63 header->port_count = XHCI_MAX_PORTS;
64 header->characteristics =
65 HUB_CHAR_NO_POWER_SWITCH_FLAG | HUB_CHAR_NO_OC_FLAG;
66 header->power_good_time = 10; /* XHCI section 5.4.9 says 20ms max */
67 header->max_current = 0;
68
69 return virthub_base_init(&rh->base, "xhci", &ops, rh, NULL,
70 header, HUB_STATUS_CHANGE_PIPE);
71}
72
73// TODO: Check device deallocation, we free device_ctx in hc.c, not
74// sure about the other structs.
75static int alloc_dev(xhci_hc_t *hc, uint8_t port, uint32_t route_str)
76{
77 int err;
78
79 xhci_cmd_t cmd;
80 xhci_cmd_init(&cmd);
81
82 xhci_send_enable_slot_command(hc, &cmd);
83 if ((err = xhci_cmd_wait(&cmd, 100000)) != EOK)
84 return err;
85
86 uint32_t slot_id = cmd.slot_id;
87
88 usb_log_debug2("Obtained slot ID: %u.\n", slot_id);
89 xhci_cmd_fini(&cmd);
90
91 xhci_input_ctx_t *ictx = malloc32(sizeof(xhci_input_ctx_t));
92 if (!ictx) {
93 return ENOMEM;
94 }
95
96 memset(ictx, 0, sizeof(xhci_input_ctx_t));
97
98 XHCI_INPUT_CTRL_CTX_ADD_SET(ictx->ctrl_ctx, 0);
99 XHCI_INPUT_CTRL_CTX_ADD_SET(ictx->ctrl_ctx, 1);
100
101 /* Initialize slot_ctx according to section 4.3.3 point 3. */
102 /* Attaching to root hub port, root string equals to 0. */
103 XHCI_SLOT_ROOT_HUB_PORT_SET(ictx->slot_ctx, port);
104 XHCI_SLOT_CTX_ENTRIES_SET(ictx->slot_ctx, 1);
105 XHCI_SLOT_ROUTE_STRING_SET(ictx->slot_ctx, route_str);
106
107 xhci_trb_ring_t *ep_ring = malloc32(sizeof(xhci_trb_ring_t));
108 if (!ep_ring) {
109 err = ENOMEM;
110 goto err_ictx;
111 }
112
113 err = xhci_trb_ring_init(ep_ring, hc);
114 if (err)
115 goto err_ring;
116
117 XHCI_EP_TYPE_SET(ictx->endpoint_ctx[0], EP_TYPE_CONTROL);
118 // TODO: must be changed with a command after USB descriptor is read
119 // See 4.6.5 in XHCI specification, first note
120 XHCI_EP_MAX_PACKET_SIZE_SET(ictx->endpoint_ctx[0],
121 xhci_is_usb3_port(&hc->rh, port) ? 512 : 8);
122 XHCI_EP_MAX_BURST_SIZE_SET(ictx->endpoint_ctx[0], 0);
123 /* FIXME physical pointer? */
124 XHCI_EP_TR_DPTR_SET(ictx->endpoint_ctx[0], ep_ring->dequeue);
125 XHCI_EP_DCS_SET(ictx->endpoint_ctx[0], 1);
126 XHCI_EP_INTERVAL_SET(ictx->endpoint_ctx[0], 0);
127 XHCI_EP_MAX_P_STREAMS_SET(ictx->endpoint_ctx[0], 0);
128 XHCI_EP_MULT_SET(ictx->endpoint_ctx[0], 0);
129 XHCI_EP_ERROR_COUNT_SET(ictx->endpoint_ctx[0], 3);
130
131 // TODO: What's the alignment?
132 xhci_device_ctx_t *dctx = malloc32(sizeof(xhci_device_ctx_t));
133 if (!dctx) {
134 err = ENOMEM;
135 goto err_ring;
136 }
137 memset(dctx, 0, sizeof(xhci_device_ctx_t));
138
139 hc->dcbaa[slot_id] = addr_to_phys(dctx);
140
141 memset(&hc->dcbaa_virt[slot_id], 0, sizeof(xhci_virt_device_ctx_t));
142 hc->dcbaa_virt[slot_id].dev_ctx = dctx;
143
144 xhci_cmd_init(&cmd);
145 cmd.slot_id = slot_id;
146 xhci_send_address_device_command(hc, &cmd, ictx);
147 if ((err = xhci_cmd_wait(&cmd, 100000)) != EOK)
148 goto err_dctx;
149
150 xhci_cmd_fini(&cmd);
151
152 // TODO: Issue configure endpoint commands (sec 4.3.5).
153
154 return EOK;
155
156err_dctx:
157 if (dctx) {
158 free32(dctx);
159 hc->dcbaa[slot_id] = 0;
160 memset(&hc->dcbaa_virt[slot_id], 0, sizeof(xhci_virt_device_ctx_t));
161 }
162err_ring:
163 if (ep_ring) {
164 xhci_trb_ring_fini(ep_ring);
165 free32(ep_ring);
166 }
167err_ictx:
168 free32(ictx);
169 return err;
170}
171
172static int handle_connected_device(xhci_hc_t* hc, xhci_port_regs_t* regs, uint8_t port_id)
173{
174 uint8_t link_state = XHCI_REG_RD(regs, XHCI_PORT_PLS);
175 const xhci_port_speed_t *speed = xhci_get_port_speed(&hc->rh, port_id);
176
177 usb_log_info("Detected new %.4s%u.%u device on port %u.", speed->name, speed->major, speed->minor, port_id);
178
179 if (speed->major == 3) {
180 if(link_state == 0) {
181 /* USB3 is automatically advanced to enabled. */
182 return alloc_dev(hc, port_id, 0);
183 }
184 else if (link_state == 5) {
185 /* USB 3 failed to enable. */
186 usb_log_error("USB 3 port couldn't be enabled.");
187 return EAGAIN;
188 }
189 else {
190 usb_log_error("USB 3 port is in invalid state %u.", link_state);
191 return EINVAL;
192 }
193 }
194 else {
195 usb_log_debug("USB 2 device attached, issuing reset.");
196 xhci_reset_hub_port(hc, port_id);
197 /*
198 FIXME: we need to wait for the event triggered by the reset
199 and then alloc_dev()... can't it be done directly instead of
200 going around?
201 */
202 return EOK;
203 }
204}
205
206int xhci_handle_port_status_change_event(xhci_hc_t *hc, xhci_trb_t *trb)
207{
208 int err;
209
210 uint8_t port_id = xhci_get_hub_port(trb);
211 usb_log_debug("Port status change event detected for port %u.", port_id);
212 xhci_port_regs_t* regs = &hc->op_regs->portrs[port_id - 1];
213
214 /* Port reset change */
215 if (XHCI_REG_RD(regs, XHCI_PORT_PRC)) {
216 /* Clear the flag. */
217 XHCI_REG_WR(regs, XHCI_PORT_PRC, 1);
218
219 uint8_t port_speed = XHCI_REG_RD(regs, XHCI_PORT_PS);
220 usb_log_debug2("Detected port reset on port %u, port speed id %u.", port_id, port_speed);
221 /** FIXME: only if that port is not yet initialized */
222 if ((err = alloc_dev(hc, port_id, 0)) != EOK)
223 return err;
224 }
225
226 /* Connection status change */
227 if (XHCI_REG_RD(regs, XHCI_PORT_CSC)) {
228 XHCI_REG_WR(regs, XHCI_PORT_CSC, 1);
229
230 if (XHCI_REG_RD(regs, XHCI_PORT_CCS) == 1) {
231 if ((err = handle_connected_device(hc, regs, port_id)) != EOK)
232 return err;
233 } else {
234 // TODO: Device disconnected
235 return ENOTSUP;
236 }
237 }
238
239 // Interrupt on the virtual hub status change pipe.
240 usb_target_t target = {
241 .address = virthub_base_get_address(&hc->rh.base),
242 .endpoint = HUB_STATUS_CHANGE_PIPE
243 };
244 usb_direction_t dir = USB_DIRECTION_IN;
245 usb_device_request_setup_packet_t setup;
246 uint64_t buffer[10];
247 size_t real_size = 0;
248 err = virthub_base_request(&hc->rh.base, target, dir, &setup, &buffer,
249 sizeof(buffer), &real_size);
250
251 return EOK;
252}
253
254const xhci_port_speed_t *xhci_get_port_speed(xhci_rh_t *rh, uint8_t port)
255{
256 xhci_port_regs_t *port_regs = &rh->hc->op_regs->portrs[port - 1];
257
258 unsigned psiv = XHCI_REG_RD(port_regs, XHCI_PORT_PS);
259 return &rh->speeds[psiv];
260}
261
262int xhci_get_hub_port(xhci_trb_t *trb)
263{
264 assert(trb);
265 uint8_t port_id = XHCI_QWORD_EXTRACT(trb->parameter, 31, 24);
266
267 return port_id;
268}
269
270int xhci_reset_hub_port(xhci_hc_t* hc, uint8_t port)
271{
272 usb_log_debug2("Resetting port %u.", port);
273 xhci_port_regs_t *regs = &hc->op_regs->portrs[port-1];
274 XHCI_REG_WR(regs, XHCI_PORT_PR, 1);
275
276 return EOK;
277}
278
279int xhci_rh_schedule(xhci_rh_t *rh, usb_transfer_batch_t *batch)
280{
281 assert(rh);
282 assert(batch);
283 const usb_target_t target = {{
284 .address = batch->ep->address,
285 .endpoint = batch->ep->endpoint,
286 }};
287 batch->error = virthub_base_request(&rh->base, target,
288 usb_transfer_batch_direction(batch), (void*)batch->setup_buffer,
289 batch->buffer, batch->buffer_size, &batch->transfered_size);
290 if (batch->error == ENAK) {
291 /* This is safe because only status change interrupt transfers
292 * return NAK. The assertion holds true because the batch
293 * existence prevents communication with that ep */
294 assert(rh->unfinished_interrupt_transfer == NULL);
295 rh->unfinished_interrupt_transfer = batch;
296 } else {
297 usb_transfer_batch_finish(batch, NULL);
298 usb_transfer_batch_destroy(batch);
299 }
300 return EOK;
301}
302
303int xhci_rh_interrupt(xhci_rh_t *rh)
304{
305 usb_log_debug2("Called xhci_rh_interrupt().");
306
307 /* TODO: atomic swap needed */
308 usb_transfer_batch_t *batch = rh->unfinished_interrupt_transfer;
309 rh->unfinished_interrupt_transfer = NULL;
310 if (batch) {
311 const usb_target_t target = {{
312 .address = batch->ep->address,
313 .endpoint = batch->ep->endpoint,
314 }};
315 batch->error = virthub_base_request(&rh->base, target,
316 usb_transfer_batch_direction(batch),
317 (void*)batch->setup_buffer,
318 batch->buffer, batch->buffer_size, &batch->transfered_size);
319 usb_transfer_batch_finish(batch, NULL);
320 usb_transfer_batch_destroy(batch);
321 }
322 return EOK;
323}
324
325/** Hub set feature request handler.
326 * @param device Virtual hub device
327 * @param setup_packet USB setup stage data.
328 * @param[out] data destination data buffer, size must be at least
329 * setup_packet->length bytes
330 * @param[out] act_size Sized of the valid response part of the buffer.
331 * @return Error code.
332 */
333static int req_clear_hub_feature(usbvirt_device_t *device,
334 const usb_device_request_setup_packet_t *setup_packet,
335 uint8_t *data, size_t *act_size)
336{
337 /* TODO: Implement me! */
338 usb_log_debug2("Called req_clear_hub_feature().");
339 return EOK;
340}
341
342#define XHCI_TO_USB(usb_feat, reg_set, ...) \
343 (((XHCI_REG_RD(reg_set, ##__VA_ARGS__)) ? 1 : 0) << (usb_feat))
344
345/** Port status request handler.
346 * @param device Virtual hub device
347 * @param setup_packet USB setup stage data.
348 * @param[out] data destination data buffer, size must be at least
349 * setup_packet->length bytes
350 * @param[out] act_size Sized of the valid response part of the buffer.
351 * @return Error code.
352 */
353static int req_get_port_status(usbvirt_device_t *device,
354 const usb_device_request_setup_packet_t *setup_packet,
355 uint8_t *data, size_t *act_size)
356{
357 xhci_rh_t *hub = virthub_get_data(device);
358 assert(hub);
359
360 if (!setup_packet->index || setup_packet->index > XHCI_MAX_PORTS) {
361 return ESTALL;
362 }
363
364 /* The index is 1-based. */
365 xhci_port_regs_t* regs = &hub->hc->op_regs->portrs[setup_packet->index - 1];
366
367 const uint32_t status = uint32_host2usb(
368 XHCI_TO_USB(USB_HUB_FEATURE_C_PORT_CONNECTION, regs, XHCI_PORT_CSC) |
369 XHCI_TO_USB(USB_HUB_FEATURE_C_PORT_ENABLE, regs, XHCI_PORT_PEC) |
370 XHCI_TO_USB(USB_HUB_FEATURE_C_PORT_OVER_CURRENT, regs, XHCI_PORT_OCC) |
371 XHCI_TO_USB(USB_HUB_FEATURE_C_PORT_RESET, regs, XHCI_PORT_PRC) |
372 XHCI_TO_USB(USB_HUB_FEATURE_PORT_CONNECTION, regs, XHCI_PORT_CCS) |
373 XHCI_TO_USB(USB_HUB_FEATURE_PORT_ENABLE, regs, XHCI_PORT_PED) |
374 XHCI_TO_USB(USB_HUB_FEATURE_PORT_OVER_CURRENT, regs, XHCI_PORT_OCA) |
375 XHCI_TO_USB(USB_HUB_FEATURE_PORT_RESET, regs, XHCI_PORT_PR) |
376 XHCI_TO_USB(USB_HUB_FEATURE_PORT_POWER, regs, XHCI_PORT_PP)
377 );
378
379 usb_log_debug2("RH: GetPortStatus(%hu) = %u.", setup_packet->index,
380 uint32_usb2host(status));
381
382 memcpy(data, &status, sizeof(status));
383 *act_size = sizeof(status);
384 return EOK;
385}
386
387/** Port clear feature request handler.
388 * @param device Virtual hub device
389 * @param setup_packet USB setup stage data.
390 * @param[out] data destination data buffer, size must be at least
391 * setup_packet->length bytes
392 * @param[out] act_size Sized of the valid response part of the buffer.
393 * @return Error code.
394 */
395static int req_clear_port_feature(usbvirt_device_t *device,
396 const usb_device_request_setup_packet_t *setup_packet,
397 uint8_t *data, size_t *act_size)
398{
399 /* TODO: Implement me! */
400 usb_log_debug2("Called req_clear_port_feature().");
401 return EOK;
402}
403
404/** Port set feature request handler.
405 * @param device Virtual hub device
406 * @param setup_packet USB setup stage data.
407 * @param[out] data destination data buffer, size must be at least
408 * setup_packet->length bytes
409 * @param[out] act_size Sized of the valid response part of the buffer.
410 * @return Error code.
411 */
412static int req_set_port_feature(usbvirt_device_t *device,
413 const usb_device_request_setup_packet_t *setup_packet,
414 uint8_t *data, size_t *act_size)
415{
416 /* TODO: Implement me! */
417 usb_log_debug2("Called req_set_port_feature().");
418 return EOK;
419}
420
421/** Status change handler.
422 * @param device Virtual hub device
423 * @param endpoint Endpoint number
424 * @param tr_type Transfer type
425 * @param buffer Response destination
426 * @param buffer_size Bytes available in buffer
427 * @param actual_size Size us the used part of the dest buffer.
428 *
429 * Produces status mask. Bit 0 indicates hub status change the other bits
430 * represent port status change.
431 */
432static int req_status_change_handler(usbvirt_device_t *device,
433 usb_endpoint_t endpoint, usb_transfer_type_t tr_type,
434 void *buffer, size_t buffer_size, size_t *actual_size)
435{
436 usb_log_debug2("Called req_status_change_handler().");
437 xhci_rh_t *hub = virthub_get_data(device);
438 assert(hub);
439
440 if (buffer_size < 16)
441 return ESTALL;
442
443 memset(buffer, 0, 16);
444 *actual_size = 16;
445 return ENAK;
446}
447
448int xhci_rh_fini(xhci_rh_t *rh)
449{
450 /* TODO: Implement me! */
451 usb_log_debug2("Called xhci_rh_fini().");
452 return EOK;
453}
454
455/** XHCI root hub request handlers */
456static const usbvirt_control_request_handler_t control_transfer_handlers[] = {
457 {
458 STD_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_DEVREQ_GET_DESCRIPTOR),
459 .name = "GetDescriptor",
460 .callback = virthub_base_get_hub_descriptor,
461 },
462 {
463 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_DEVREQ_GET_DESCRIPTOR),
464 .name = "GetDescriptor",
465 .callback = virthub_base_get_hub_descriptor,
466 },
467 {
468 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_GET_DESCRIPTOR),
469 .name = "GetHubDescriptor",
470 .callback = virthub_base_get_hub_descriptor,
471 },
472 {
473 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_GET_STATUS),
474 .name = "GetPortStatus",
475 .callback = req_get_port_status,
476 },
477 {
478 CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_CLEAR_FEATURE),
479 .name = "ClearHubFeature",
480 .callback = req_clear_hub_feature,
481 },
482 {
483 CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_CLEAR_FEATURE),
484 .name = "ClearPortFeature",
485 .callback = req_clear_port_feature,
486 },
487 {
488 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_GET_STATUS),
489 .name = "GetHubStatus",
490 /* XHCI root hub has no power source,
491 * over-current is reported by port */
492 .callback = virthub_base_get_null_status,
493 },
494 {
495 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_GET_STATUS),
496 .name = "GetPortStatus",
497 .callback = req_get_port_status,
498 },
499 {
500 CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_SET_FEATURE),
501 .name = "SetHubFeature",
502 .callback = req_nop,
503 },
504 {
505 CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_SET_FEATURE),
506 .name = "SetPortFeature",
507 .callback = req_set_port_feature,
508 },
509 {
510 .callback = NULL
511 }
512};
513
514/** Virtual XHCI root hub ops */
515static usbvirt_device_ops_t ops = {
516 .control = control_transfer_handlers,
517 .data_in[HUB_STATUS_CHANGE_PIPE] = req_status_change_handler,
518};
519
520
521/**
522 * @}
523 */
Note: See TracBrowser for help on using the repository browser.