source: mainline/uspace/drv/bus/usb/ohci/hc.c@ 8b415cc

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

usbhost: refactor the initialization

Before that, drivers had to setup MMIO range multiple times, or even parse hw
resources themselves again. The former init method was split in half - init and
start. Init shall allocate and initialize inner structures, start shall start
the HC.

In the XHCI it is demonstrated how to isolate inner HC implementation from the
fact this driver is using libusbhost. It adds some boilerplate code, but
I think it leads to cleaner design.

  • Property mode set to 100644
File size: 16.7 KB
Line 
1/*
2 * Copyright (c) 2011 Jan Vesely
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 drvusbohcihc
30 * @{
31 */
32/** @file
33 * @brief OHCI Host controller driver routines
34 */
35
36#include <assert.h>
37#include <async.h>
38#include <errno.h>
39#include <macros.h>
40#include <mem.h>
41#include <stdlib.h>
42#include <str_error.h>
43#include <stddef.h>
44#include <stdint.h>
45
46#include <usb/debug.h>
47#include <usb/usb.h>
48
49#include "ohci_endpoint.h"
50#include "ohci_batch.h"
51
52#include "hc.h"
53
54#define OHCI_USED_INTERRUPTS \
55 (I_SO | I_WDH | I_UE | I_RHSC)
56
57static const irq_pio_range_t ohci_pio_ranges[] = {
58 {
59 .base = 0,
60 .size = sizeof(ohci_regs_t)
61 }
62};
63
64static const irq_cmd_t ohci_irq_commands[] = {
65 {
66 .cmd = CMD_PIO_READ_32,
67 .dstarg = 1,
68 .addr = NULL
69 },
70 {
71 .cmd = CMD_AND,
72 .srcarg = 1,
73 .dstarg = 2,
74 .value = 0
75 },
76 {
77 .cmd = CMD_PREDICATE,
78 .srcarg = 2,
79 .value = 2
80 },
81 {
82 .cmd = CMD_PIO_WRITE_A_32,
83 .srcarg = 1,
84 .addr = NULL
85 },
86 {
87 .cmd = CMD_ACCEPT
88 }
89};
90
91static int hc_init_transfer_lists(hc_t *instance);
92static int hc_init_memory(hc_t *instance);
93
94/** Generate IRQ code.
95 * @param[out] ranges PIO ranges buffer.
96 * @param[in] ranges_size Size of the ranges buffer (bytes).
97 * @param[out] cmds Commands buffer.
98 * @param[in] cmds_size Size of the commands buffer (bytes).
99 * @param[in] hw_res Device's resources.
100 *
101 * @return Error code.
102 */
103int ohci_hc_gen_irq_code(irq_code_t *code, hcd_t *hcd, const hw_res_list_parsed_t *hw_res)
104{
105 assert(code);
106 assert(hw_res);
107
108 if (hw_res->irqs.count != 1 || hw_res->mem_ranges.count != 1)
109 return EINVAL;
110
111 const addr_range_t regs = hw_res->mem_ranges.ranges[0];
112
113 if (RNGSZ(regs) < sizeof(ohci_regs_t))
114 return EOVERFLOW;
115
116 code->ranges = malloc(sizeof(ohci_pio_ranges));
117 if (code->ranges == NULL)
118 return ENOMEM;
119
120 code->cmds = malloc(sizeof(ohci_irq_commands));
121 if (code->cmds == NULL) {
122 free(code->ranges);
123 return ENOMEM;
124 }
125
126 code->rangecount = ARRAY_SIZE(ohci_pio_ranges);
127 code->cmdcount = ARRAY_SIZE(ohci_irq_commands);
128
129 memcpy(code->ranges, ohci_pio_ranges, sizeof(ohci_pio_ranges));
130 code->ranges[0].base = RNGABS(regs);
131
132 memcpy(code->cmds, ohci_irq_commands, sizeof(ohci_irq_commands));
133 ohci_regs_t *registers = (ohci_regs_t *) RNGABSPTR(regs);
134 code->cmds[0].addr = (void *) &registers->interrupt_status;
135 code->cmds[3].addr = (void *) &registers->interrupt_status;
136 OHCI_WR(code->cmds[1].value, OHCI_USED_INTERRUPTS);
137
138 usb_log_debug("Memory mapped regs at %p (size %zu), IRQ %d.\n",
139 RNGABSPTR(regs), RNGSZ(regs), hw_res->irqs.irqs[0]);
140
141 return hw_res->irqs.irqs[0];
142}
143
144/** Initialize OHCI hc driver structure
145 *
146 * @param[in] instance Memory place for the structure.
147 * @param[in] regs Device's resources
148 * @param[in] interrupts True if w interrupts should be used
149 * @return Error code
150 */
151int hc_init(hc_t *instance, const hw_res_list_parsed_t *hw_res)
152{
153 assert(instance);
154 assert(hw_res);
155 if (hw_res->mem_ranges.count != 1 ||
156 hw_res->mem_ranges.ranges[0].size < sizeof(ohci_regs_t))
157 return EINVAL;
158
159 int ret = pio_enable_range(&hw_res->mem_ranges.ranges[0],
160 (void **) &instance->registers);
161 if (ret != EOK) {
162 usb_log_error("Failed to gain access to registers: %s.\n",
163 str_error(ret));
164 return ret;
165 }
166 usb_log_debug("Device registers at %" PRIx64 " (%zuB) accessible.\n",
167 hw_res->mem_ranges.ranges[0].address.absolute,
168 hw_res->mem_ranges.ranges[0].size);
169
170 list_initialize(&instance->pending_batches);
171 fibril_mutex_initialize(&instance->guard);
172
173 ret = hc_init_memory(instance);
174 if (ret != EOK) {
175 usb_log_error("Failed to create OHCI memory structures: %s.\n",
176 str_error(ret));
177 // TODO: We should disable pio access here
178 return ret;
179 }
180
181 return EOK;
182}
183
184/** Safely dispose host controller internal structures
185 *
186 * @param[in] instance Host controller structure to use.
187 */
188void hc_fini(hc_t *instance)
189{
190 assert(instance);
191 /* TODO: implement*/
192};
193
194void hc_enqueue_endpoint(hc_t *instance, const endpoint_t *ep)
195{
196 assert(instance);
197 assert(ep);
198
199 endpoint_list_t *list = &instance->lists[ep->transfer_type];
200 ohci_endpoint_t *ohci_ep = ohci_endpoint_get(ep);
201 assert(list);
202 assert(ohci_ep);
203
204 /* Enqueue ep */
205 switch (ep->transfer_type) {
206 case USB_TRANSFER_CONTROL:
207 OHCI_CLR(instance->registers->control, C_CLE);
208 endpoint_list_add_ep(list, ohci_ep);
209 OHCI_WR(instance->registers->control_current, 0);
210 OHCI_SET(instance->registers->control, C_CLE);
211 break;
212 case USB_TRANSFER_BULK:
213 OHCI_CLR(instance->registers->control, C_BLE);
214 endpoint_list_add_ep(list, ohci_ep);
215 OHCI_WR(instance->registers->bulk_current, 0);
216 OHCI_SET(instance->registers->control, C_BLE);
217 break;
218 case USB_TRANSFER_ISOCHRONOUS:
219 case USB_TRANSFER_INTERRUPT:
220 OHCI_CLR(instance->registers->control, C_PLE | C_IE);
221 endpoint_list_add_ep(list, ohci_ep);
222 OHCI_SET(instance->registers->control, C_PLE | C_IE);
223 break;
224 }
225}
226
227void hc_dequeue_endpoint(hc_t *instance, const endpoint_t *ep)
228{
229 assert(instance);
230 assert(ep);
231
232 /* Dequeue ep */
233 endpoint_list_t *list = &instance->lists[ep->transfer_type];
234 ohci_endpoint_t *ohci_ep = ohci_endpoint_get(ep);
235
236 assert(list);
237 assert(ohci_ep);
238 switch (ep->transfer_type) {
239 case USB_TRANSFER_CONTROL:
240 OHCI_CLR(instance->registers->control, C_CLE);
241 endpoint_list_remove_ep(list, ohci_ep);
242 OHCI_WR(instance->registers->control_current, 0);
243 OHCI_SET(instance->registers->control, C_CLE);
244 break;
245 case USB_TRANSFER_BULK:
246 OHCI_CLR(instance->registers->control, C_BLE);
247 endpoint_list_remove_ep(list, ohci_ep);
248 OHCI_WR(instance->registers->bulk_current, 0);
249 OHCI_SET(instance->registers->control, C_BLE);
250 break;
251 case USB_TRANSFER_ISOCHRONOUS:
252 case USB_TRANSFER_INTERRUPT:
253 OHCI_CLR(instance->registers->control, C_PLE | C_IE);
254 endpoint_list_remove_ep(list, ohci_ep);
255 OHCI_SET(instance->registers->control, C_PLE | C_IE);
256 break;
257 default:
258 break;
259 }
260}
261
262int ohci_hc_status(hcd_t *hcd, uint32_t *status)
263{
264 assert(hcd);
265 assert(status);
266 hc_t *instance = hcd_get_driver_data(hcd);
267 assert(instance);
268
269 if (instance->registers){
270 *status = OHCI_RD(instance->registers->interrupt_status);
271 OHCI_WR(instance->registers->interrupt_status, *status);
272 }
273 return EOK;
274}
275
276/** Add USB transfer to the schedule.
277 *
278 * @param[in] hcd HCD driver structure.
279 * @param[in] batch Batch representing the transfer.
280 * @return Error code.
281 */
282int ohci_hc_schedule(hcd_t *hcd, usb_transfer_batch_t *batch)
283{
284 assert(hcd);
285 hc_t *instance = hcd_get_driver_data(hcd);
286 assert(instance);
287
288 /* Check for root hub communication */
289 if (batch->ep->address == ohci_rh_get_address(&instance->rh)) {
290 usb_log_debug("OHCI root hub request.\n");
291 return ohci_rh_schedule(&instance->rh, batch);
292 }
293 ohci_transfer_batch_t *ohci_batch = ohci_transfer_batch_get(batch);
294 if (!ohci_batch)
295 return ENOMEM;
296
297 fibril_mutex_lock(&instance->guard);
298 list_append(&ohci_batch->link, &instance->pending_batches);
299 ohci_transfer_batch_commit(ohci_batch);
300
301 /* Control and bulk schedules need a kick to start working */
302 switch (batch->ep->transfer_type)
303 {
304 case USB_TRANSFER_CONTROL:
305 OHCI_SET(instance->registers->command_status, CS_CLF);
306 break;
307 case USB_TRANSFER_BULK:
308 OHCI_SET(instance->registers->command_status, CS_BLF);
309 break;
310 default:
311 break;
312 }
313 fibril_mutex_unlock(&instance->guard);
314 return EOK;
315}
316
317/** Interrupt handling routine
318 *
319 * @param[in] hcd HCD driver structure.
320 * @param[in] status Value of the status register at the time of interrupt.
321 */
322void ohci_hc_interrupt(hcd_t *hcd, uint32_t status)
323{
324 assert(hcd);
325 hc_t *instance = hcd_get_driver_data(hcd);
326 status = OHCI_RD(status);
327 assert(instance);
328 if ((status & ~I_SF) == 0) /* ignore sof status */
329 return;
330 usb_log_debug2("OHCI(%p) interrupt: %x.\n", instance, status);
331 if (status & I_RHSC)
332 ohci_rh_interrupt(&instance->rh);
333
334 if (status & I_WDH) {
335 fibril_mutex_lock(&instance->guard);
336 usb_log_debug2("HCCA: %p-%#" PRIx32 " (%p).\n", instance->hcca,
337 OHCI_RD(instance->registers->hcca),
338 (void *) addr_to_phys(instance->hcca));
339 usb_log_debug2("Periodic current: %#" PRIx32 ".\n",
340 OHCI_RD(instance->registers->periodic_current));
341
342 link_t *current = list_first(&instance->pending_batches);
343 while (current && current != &instance->pending_batches.head) {
344 link_t *next = current->next;
345 ohci_transfer_batch_t *batch =
346 ohci_transfer_batch_from_link(current);
347
348 if (ohci_transfer_batch_is_complete(batch)) {
349 list_remove(current);
350 ohci_transfer_batch_finish_dispose(batch);
351 }
352
353 current = next;
354 }
355 fibril_mutex_unlock(&instance->guard);
356 }
357
358 if (status & I_UE) {
359 usb_log_fatal("Error like no other!\n");
360 hc_start(instance);
361 }
362
363}
364
365/** Turn off any (BIOS)driver that might be in control of the device.
366 *
367 * This function implements routines described in chapter 5.1.1.3 of the OHCI
368 * specification (page 40, pdf page 54).
369 *
370 * @param[in] instance OHCI hc driver structure.
371 */
372void hc_gain_control(hc_t *instance)
373{
374 assert(instance);
375
376 usb_log_debug("Requesting OHCI control.\n");
377 if (OHCI_RD(instance->registers->revision) & R_LEGACY_FLAG) {
378 /* Turn off legacy emulation, it should be enough to zero
379 * the lowest bit, but it caused problems. Thus clear all
380 * except GateA20 (causes restart on some hw).
381 * See page 145 of the specs for details.
382 */
383 volatile uint32_t *ohci_emulation_reg =
384 (uint32_t*)((char*)instance->registers + LEGACY_REGS_OFFSET);
385 usb_log_debug("OHCI legacy register %p: %x.\n",
386 ohci_emulation_reg, OHCI_RD(*ohci_emulation_reg));
387 /* Zero everything but A20State */
388 // TODO: should we ack interrupts before doing this?
389 OHCI_CLR(*ohci_emulation_reg, ~0x100);
390 usb_log_debug(
391 "OHCI legacy register (should be 0 or 0x100) %p: %x.\n",
392 ohci_emulation_reg, OHCI_RD(*ohci_emulation_reg));
393 }
394
395 /* Interrupt routing enabled => smm driver is active */
396 if (OHCI_RD(instance->registers->control) & C_IR) {
397 usb_log_debug("SMM driver: request ownership change.\n");
398 // TODO: should we ack interrupts before doing this?
399 OHCI_SET(instance->registers->command_status, CS_OCR);
400 /* Hope that SMM actually knows its stuff or we can hang here */
401 while (OHCI_RD(instance->registers->control) & C_IR) {
402 async_usleep(1000);
403 }
404 usb_log_info("SMM driver: Ownership taken.\n");
405 C_HCFS_SET(instance->registers->control, C_HCFS_RESET);
406 async_usleep(50000);
407 return;
408 }
409
410 const unsigned hc_status = C_HCFS_GET(instance->registers->control);
411 /* Interrupt routing disabled && status != USB_RESET => BIOS active */
412 if (hc_status != C_HCFS_RESET) {
413 usb_log_debug("BIOS driver found.\n");
414 if (hc_status == C_HCFS_OPERATIONAL) {
415 usb_log_info("BIOS driver: HC operational.\n");
416 return;
417 }
418 /* HC is suspended assert resume for 20ms */
419 C_HCFS_SET(instance->registers->control, C_HCFS_RESUME);
420 async_usleep(20000);
421 usb_log_info("BIOS driver: HC resumed.\n");
422 return;
423 }
424
425 /* HC is in reset (hw startup) => no other driver
426 * maintain reset for at least the time specified in USB spec (50 ms)*/
427 usb_log_debug("Host controller found in reset state.\n");
428 async_usleep(50000);
429}
430
431/** OHCI hw initialization routine.
432 *
433 * @param[in] instance OHCI hc driver structure.
434 */
435void hc_start(hc_t *instance)
436{
437 ohci_rh_init(&instance->rh, instance->registers, "ohci rh");
438
439 /* OHCI guide page 42 */
440 assert(instance);
441 usb_log_debug2("Started hc initialization routine.\n");
442
443 /* Save contents of fm_interval register */
444 const uint32_t fm_interval = OHCI_RD(instance->registers->fm_interval);
445 usb_log_debug2("Old value of HcFmInterval: %x.\n", fm_interval);
446
447 /* Reset hc */
448 usb_log_debug2("HC reset.\n");
449 size_t time = 0;
450 OHCI_WR(instance->registers->command_status, CS_HCR);
451 while (OHCI_RD(instance->registers->command_status) & CS_HCR) {
452 async_usleep(10);
453 time += 10;
454 }
455 usb_log_debug2("HC reset complete in %zu us.\n", time);
456
457 /* Restore fm_interval */
458 OHCI_WR(instance->registers->fm_interval, fm_interval);
459 assert((OHCI_RD(instance->registers->command_status) & CS_HCR) == 0);
460
461 /* hc is now in suspend state */
462 usb_log_debug2("HC should be in suspend state(%x).\n",
463 OHCI_RD(instance->registers->control));
464
465 /* Use HCCA */
466 OHCI_WR(instance->registers->hcca, addr_to_phys(instance->hcca));
467
468 /* Use queues */
469 OHCI_WR(instance->registers->bulk_head,
470 instance->lists[USB_TRANSFER_BULK].list_head_pa);
471 usb_log_debug2("Bulk HEAD set to: %p (%#" PRIx32 ").\n",
472 instance->lists[USB_TRANSFER_BULK].list_head,
473 instance->lists[USB_TRANSFER_BULK].list_head_pa);
474
475 OHCI_WR(instance->registers->control_head,
476 instance->lists[USB_TRANSFER_CONTROL].list_head_pa);
477 usb_log_debug2("Control HEAD set to: %p (%#" PRIx32 ").\n",
478 instance->lists[USB_TRANSFER_CONTROL].list_head,
479 instance->lists[USB_TRANSFER_CONTROL].list_head_pa);
480
481 /* Enable queues */
482 OHCI_SET(instance->registers->control, (C_PLE | C_IE | C_CLE | C_BLE));
483 usb_log_debug("Queues enabled(%x).\n",
484 OHCI_RD(instance->registers->control));
485
486 /* Enable interrupts */
487 if (instance->hw_interrupts) {
488 OHCI_WR(instance->registers->interrupt_enable,
489 OHCI_USED_INTERRUPTS);
490 usb_log_debug("Enabled interrupts: %x.\n",
491 OHCI_RD(instance->registers->interrupt_enable));
492 OHCI_WR(instance->registers->interrupt_enable, I_MI);
493 }
494
495 /* Set periodic start to 90% */
496 const uint32_t frame_length =
497 (fm_interval >> FMI_FI_SHIFT) & FMI_FI_MASK;
498 OHCI_WR(instance->registers->periodic_start,
499 ((frame_length / 10) * 9) & PS_MASK << PS_SHIFT);
500 usb_log_debug2("All periodic start set to: %x(%u - 90%% of %d).\n",
501 OHCI_RD(instance->registers->periodic_start),
502 OHCI_RD(instance->registers->periodic_start), frame_length);
503 C_HCFS_SET(instance->registers->control, C_HCFS_OPERATIONAL);
504 usb_log_debug("OHCI HC up and running (ctl_reg=0x%x).\n",
505 OHCI_RD(instance->registers->control));
506}
507
508/** Initialize schedule queues
509 *
510 * @param[in] instance OHCI hc driver structure
511 * @return Error code
512 */
513int hc_init_transfer_lists(hc_t *instance)
514{
515 assert(instance);
516#define SETUP_ENDPOINT_LIST(type) \
517do { \
518 const char *name = usb_str_transfer_type(type); \
519 const int ret = endpoint_list_init(&instance->lists[type], name); \
520 if (ret != EOK) { \
521 usb_log_error("Failed to setup %s endpoint list: %s.\n", \
522 name, str_error(ret)); \
523 endpoint_list_fini(&instance->lists[USB_TRANSFER_ISOCHRONOUS]);\
524 endpoint_list_fini(&instance->lists[USB_TRANSFER_INTERRUPT]); \
525 endpoint_list_fini(&instance->lists[USB_TRANSFER_CONTROL]); \
526 endpoint_list_fini(&instance->lists[USB_TRANSFER_BULK]); \
527 return ret; \
528 } \
529} while (0)
530
531 SETUP_ENDPOINT_LIST(USB_TRANSFER_ISOCHRONOUS);
532 SETUP_ENDPOINT_LIST(USB_TRANSFER_INTERRUPT);
533 SETUP_ENDPOINT_LIST(USB_TRANSFER_CONTROL);
534 SETUP_ENDPOINT_LIST(USB_TRANSFER_BULK);
535#undef SETUP_ENDPOINT_LIST
536 endpoint_list_set_next(&instance->lists[USB_TRANSFER_INTERRUPT],
537 &instance->lists[USB_TRANSFER_ISOCHRONOUS]);
538
539 return EOK;
540}
541
542/** Initialize memory structures used by the OHCI hcd.
543 *
544 * @param[in] instance OHCI hc driver structure.
545 * @return Error code.
546 */
547int hc_init_memory(hc_t *instance)
548{
549 assert(instance);
550
551 memset(&instance->rh, 0, sizeof(instance->rh));
552 /* Init queues */
553 const int ret = hc_init_transfer_lists(instance);
554 if (ret != EOK) {
555 return ret;
556 }
557
558 /*Init HCCA */
559 instance->hcca = hcca_get();
560 if (instance->hcca == NULL)
561 return ENOMEM;
562 usb_log_debug2("OHCI HCCA initialized at %p.\n", instance->hcca);
563
564 for (unsigned i = 0; i < HCCA_INT_EP_COUNT; ++i) {
565 hcca_set_int_ep(instance->hcca, i,
566 instance->lists[USB_TRANSFER_INTERRUPT].list_head_pa);
567 }
568 usb_log_debug2("Interrupt HEADs set to: %p (%#" PRIx32 ").\n",
569 instance->lists[USB_TRANSFER_INTERRUPT].list_head,
570 instance->lists[USB_TRANSFER_INTERRUPT].list_head_pa);
571
572 return EOK;
573}
574
575/**
576 * @}
577 */
Note: See TracBrowser for help on using the repository browser.