source: mainline/uspace/drv/bus/usb/ehci/hc.c@ c9c0e41

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since c9c0e41 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: 14.6 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 drvusbehcihc
30 * @{
31 */
32/** @file
33 * @brief EHCI 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 <stdint.h>
43#include <str_error.h>
44
45#include <usb/debug.h>
46#include <usb/usb.h>
47#include <usb/host/utils/malloc32.h>
48
49#include "ehci_batch.h"
50
51#include "hc.h"
52
53#define EHCI_USED_INTERRUPTS \
54 (USB_INTR_IRQ_FLAG | USB_INTR_ERR_IRQ_FLAG | USB_INTR_PORT_CHANGE_FLAG | \
55 USB_INTR_ASYNC_ADVANCE_FLAG | USB_INTR_HOST_ERR_FLAG)
56
57static const irq_pio_range_t ehci_pio_ranges[] = {
58 {
59 .base = 0,
60 .size = sizeof(ehci_regs_t)
61 }
62};
63
64static const irq_cmd_t ehci_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_memory(hc_t *instance);
92
93/** Generate IRQ code.
94 * @param[out] ranges PIO ranges buffer.
95 * @param[in] hw_res Device's resources.
96 *
97 * @return Error code.
98 */
99int ehci_hc_gen_irq_code(irq_code_t *code, hcd_t *hcd, const hw_res_list_parsed_t *hw_res)
100{
101 assert(code);
102 assert(hw_res);
103
104 hc_t *instance = hcd_get_driver_data(hcd);
105
106 if (hw_res->irqs.count != 1 || hw_res->mem_ranges.count != 1)
107 return EINVAL;
108
109 addr_range_t regs = hw_res->mem_ranges.ranges[0];
110
111 if (RNGSZ(regs) < sizeof(ehci_regs_t))
112 return EOVERFLOW;
113
114 code->ranges = malloc(sizeof(ehci_pio_ranges));
115 if (code->ranges == NULL)
116 return ENOMEM;
117
118 code->cmds = malloc(sizeof(ehci_irq_commands));
119 if (code->cmds == NULL) {
120 free(code->ranges);
121 return ENOMEM;
122 }
123
124 code->rangecount = ARRAY_SIZE(ehci_pio_ranges);
125 code->cmdcount = ARRAY_SIZE(ehci_irq_commands);
126
127 memcpy(code->ranges, ehci_pio_ranges, sizeof(ehci_pio_ranges));
128 code->ranges[0].base = RNGABS(regs);
129
130 memcpy(code->cmds, ehci_irq_commands, sizeof(ehci_irq_commands));
131
132 code->cmds[0].addr = (void *) &instance->registers->usbsts;
133 code->cmds[3].addr = (void *) &instance->registers->usbsts;
134 EHCI_WR(code->cmds[1].value, EHCI_USED_INTERRUPTS);
135
136 usb_log_debug("Memory mapped regs at %p (size %zu), IRQ %d.\n",
137 RNGABSPTR(regs), RNGSZ(regs), hw_res->irqs.irqs[0]);
138
139 return hw_res->irqs.irqs[0];
140}
141
142/** Initialize EHCI hc driver structure
143 *
144 * @param[in] instance Memory place for the structure.
145 * @param[in] regs Device's I/O registers range.
146 * @param[in] interrupts True if w interrupts should be used
147 * @return Error code
148 */
149int hc_init(hc_t *instance, const hw_res_list_parsed_t *hw_res)
150{
151 assert(instance);
152 assert(hw_res);
153 if (hw_res->mem_ranges.count != 1 ||
154 hw_res->mem_ranges.ranges[0].size <
155 (sizeof(ehci_caps_regs_t) + sizeof(ehci_regs_t)))
156 return EINVAL;
157
158 int ret = pio_enable_range(&hw_res->mem_ranges.ranges[0],
159 (void **)&instance->caps);
160 if (ret != EOK) {
161 usb_log_error("HC(%p): Failed to gain access to device "
162 "registers: %s.\n", instance, str_error(ret));
163 return ret;
164 }
165
166 usb_log_info("HC(%p): Device registers at %"PRIx64" (%zuB) accessible.",
167 instance, hw_res->mem_ranges.ranges[0].address.absolute,
168 hw_res->mem_ranges.ranges[0].size);
169 instance->registers =
170 (void*)instance->caps + EHCI_RD8(instance->caps->caplength);
171 usb_log_info("HC(%p): Device control registers at %" PRIx64, instance,
172 hw_res->mem_ranges.ranges[0].address.absolute
173 + EHCI_RD8(instance->caps->caplength));
174
175 list_initialize(&instance->pending_batches);
176 fibril_mutex_initialize(&instance->guard);
177 fibril_condvar_initialize(&instance->async_doorbell);
178
179 ret = hc_init_memory(instance);
180 if (ret != EOK) {
181 usb_log_error("HC(%p): Failed to create EHCI memory structures:"
182 " %s.", instance, str_error(ret));
183 return ret;
184 }
185
186 usb_log_info("HC(%p): Initializing RH(%p).", instance, &instance->rh);
187 ehci_rh_init(
188 &instance->rh, instance->caps, instance->registers, "ehci rh");
189
190 return EOK;
191}
192
193/** Safely dispose host controller internal structures
194 *
195 * @param[in] instance Host controller structure to use.
196 */
197void hc_fini(hc_t *instance)
198{
199 assert(instance);
200 //TODO: stop the hw
201#if 0
202 endpoint_list_fini(&instance->async_list);
203 endpoint_list_fini(&instance->int_list);
204 return_page(instance->periodic_list_base);
205#endif
206};
207
208void hc_enqueue_endpoint(hc_t *instance, const endpoint_t *ep)
209{
210 assert(instance);
211 assert(ep);
212 ehci_endpoint_t *ehci_ep = ehci_endpoint_get(ep);
213 usb_log_debug("HC(%p) enqueue EP(%d:%d:%s:%s)\n", instance,
214 ep->address, ep->endpoint,
215 usb_str_transfer_type_short(ep->transfer_type),
216 usb_str_direction(ep->direction));
217 switch (ep->transfer_type)
218 {
219 case USB_TRANSFER_CONTROL:
220 case USB_TRANSFER_BULK:
221 endpoint_list_append_ep(&instance->async_list, ehci_ep);
222 break;
223 case USB_TRANSFER_INTERRUPT:
224 endpoint_list_append_ep(&instance->int_list, ehci_ep);
225 break;
226 case USB_TRANSFER_ISOCHRONOUS:
227 /* NOT SUPPORTED */
228 break;
229 }
230}
231
232void hc_dequeue_endpoint(hc_t *instance, const endpoint_t *ep)
233{
234 assert(instance);
235 assert(ep);
236 ehci_endpoint_t *ehci_ep = ehci_endpoint_get(ep);
237 usb_log_debug("HC(%p) dequeue EP(%d:%d:%s:%s)\n", instance,
238 ep->address, ep->endpoint,
239 usb_str_transfer_type_short(ep->transfer_type),
240 usb_str_direction(ep->direction));
241 switch (ep->transfer_type)
242 {
243 case USB_TRANSFER_INTERRUPT:
244 endpoint_list_remove_ep(&instance->int_list, ehci_ep);
245 /* Fall through */
246 case USB_TRANSFER_ISOCHRONOUS:
247 /* NOT SUPPORTED */
248 return;
249 case USB_TRANSFER_CONTROL:
250 case USB_TRANSFER_BULK:
251 endpoint_list_remove_ep(&instance->async_list, ehci_ep);
252 break;
253 }
254 fibril_mutex_lock(&instance->guard);
255 usb_log_debug("HC(%p): Waiting for doorbell", instance);
256 EHCI_SET(instance->registers->usbcmd, USB_CMD_IRQ_ASYNC_DOORBELL);
257 fibril_condvar_wait(&instance->async_doorbell, &instance->guard);
258 usb_log_debug2("HC(%p): Got doorbell", instance);
259 fibril_mutex_unlock(&instance->guard);
260}
261
262int ehci_hc_status(hcd_t *hcd, uint32_t *status)
263{
264 assert(hcd);
265 hc_t *instance = hcd_get_driver_data(hcd);
266 assert(instance);
267 assert(status);
268 *status = 0;
269 if (instance->registers) {
270 *status = EHCI_RD(instance->registers->usbsts);
271 EHCI_WR(instance->registers->usbsts, *status);
272 }
273 usb_log_debug2("HC(%p): Read status: %x", instance, *status);
274 return EOK;
275}
276
277/** Add USB transfer to the schedule.
278 *
279 * @param[in] hcd HCD driver structure.
280 * @param[in] batch Batch representing the transfer.
281 * @return Error code.
282 */
283int ehci_hc_schedule(hcd_t *hcd, usb_transfer_batch_t *batch)
284{
285 assert(hcd);
286 hc_t *instance = hcd_get_driver_data(hcd);
287 assert(instance);
288
289 /* Check for root hub communication */
290 if (batch->ep->address == ehci_rh_get_address(&instance->rh)) {
291 usb_log_debug("HC(%p): Scheduling BATCH(%p) for RH(%p)",
292 instance, batch, &instance->rh);
293 return ehci_rh_schedule(&instance->rh, batch);
294 }
295 ehci_transfer_batch_t *ehci_batch = ehci_transfer_batch_get(batch);
296 if (!ehci_batch)
297 return ENOMEM;
298
299 fibril_mutex_lock(&instance->guard);
300 usb_log_debug2("HC(%p): Appending BATCH(%p)", instance, batch);
301 list_append(&ehci_batch->link, &instance->pending_batches);
302 usb_log_debug("HC(%p): Committing BATCH(%p)", instance, batch);
303 ehci_transfer_batch_commit(ehci_batch);
304
305 fibril_mutex_unlock(&instance->guard);
306 return EOK;
307}
308
309/** Interrupt handling routine
310 *
311 * @param[in] hcd HCD driver structure.
312 * @param[in] status Value of the status register at the time of interrupt.
313 */
314void ehci_hc_interrupt(hcd_t *hcd, uint32_t status)
315{
316 assert(hcd);
317 hc_t *instance = hcd_get_driver_data(hcd);
318 status = EHCI_RD(status);
319 assert(instance);
320
321 usb_log_debug2("HC(%p): Interrupt: %"PRIx32, instance, status);
322 if (status & USB_STS_PORT_CHANGE_FLAG) {
323 ehci_rh_interrupt(&instance->rh);
324 }
325
326 if (status & USB_STS_IRQ_ASYNC_ADVANCE_FLAG) {
327 fibril_mutex_lock(&instance->guard);
328 usb_log_debug2("HC(%p): Signaling doorbell", instance);
329 fibril_condvar_broadcast(&instance->async_doorbell);
330 fibril_mutex_unlock(&instance->guard);
331 }
332
333 if (status & (USB_STS_IRQ_FLAG | USB_STS_ERR_IRQ_FLAG)) {
334 fibril_mutex_lock(&instance->guard);
335
336 usb_log_debug2("HC(%p): Scanning %lu pending batches", instance,
337 list_count(&instance->pending_batches));
338 list_foreach_safe(instance->pending_batches, current, next) {
339 ehci_transfer_batch_t *batch =
340 ehci_transfer_batch_from_link(current);
341
342 if (ehci_transfer_batch_is_complete(batch)) {
343 list_remove(current);
344 ehci_transfer_batch_finish_dispose(batch);
345 }
346 }
347 fibril_mutex_unlock(&instance->guard);
348 }
349
350 if (status & USB_STS_HOST_ERROR_FLAG) {
351 usb_log_fatal("HCD(%p): HOST SYSTEM ERROR!", instance);
352 //TODO do something here
353 }
354}
355
356/** EHCI hw initialization routine.
357 *
358 * @param[in] instance EHCI hc driver structure.
359 */
360int hc_start(hc_t *instance, bool interrupts)
361{
362 assert(instance);
363 usb_log_debug("HC(%p): Starting HW.", instance);
364
365 /* Turn off the HC if it's running, Reseting a running device is
366 * undefined */
367 if (!(EHCI_RD(instance->registers->usbsts) & USB_STS_HC_HALTED_FLAG)) {
368 /* disable all interrupts */
369 EHCI_WR(instance->registers->usbintr, 0);
370 /* ack all interrupts */
371 EHCI_WR(instance->registers->usbsts, 0x3f);
372 /* Stop HC hw */
373 EHCI_WR(instance->registers->usbcmd, 0);
374 /* Wait until hc is halted */
375 while ((EHCI_RD(instance->registers->usbsts) & USB_STS_HC_HALTED_FLAG) == 0) {
376 async_usleep(1);
377 }
378 usb_log_info("HC(%p): EHCI turned off.", instance);
379 } else {
380 usb_log_info("HC(%p): EHCI was not running.", instance);
381 }
382
383 /* Hw initialization sequence, see page 53 (pdf 63) */
384 EHCI_SET(instance->registers->usbcmd, USB_CMD_HC_RESET_FLAG);
385 usb_log_info("HC(%p): Waiting for HW reset.", instance);
386 while (EHCI_RD(instance->registers->usbcmd) & USB_CMD_HC_RESET_FLAG) {
387 async_usleep(1);
388 }
389 usb_log_debug("HC(%p): HW reset OK.", instance);
390
391 /* Use the lowest 4G segment */
392 EHCI_WR(instance->registers->ctrldssegment, 0);
393
394 /* Enable periodic list */
395 assert(instance->periodic_list_base);
396 uintptr_t phys_base =
397 addr_to_phys((void*)instance->periodic_list_base);
398 assert((phys_base & USB_PERIODIC_LIST_BASE_MASK) == phys_base);
399 EHCI_WR(instance->registers->periodiclistbase, phys_base);
400 EHCI_SET(instance->registers->usbcmd, USB_CMD_PERIODIC_SCHEDULE_FLAG);
401 usb_log_debug("HC(%p): Enabled periodic list.", instance);
402
403
404 /* Enable Async schedule */
405 phys_base = addr_to_phys((void*)instance->async_list.list_head);
406 assert((phys_base & USB_ASYNCLIST_MASK) == phys_base);
407 EHCI_WR(instance->registers->asynclistaddr, phys_base);
408 EHCI_SET(instance->registers->usbcmd, USB_CMD_ASYNC_SCHEDULE_FLAG);
409 usb_log_debug("HC(%p): Enabled async list.", instance);
410
411 /* Start hc and get all ports */
412 EHCI_SET(instance->registers->usbcmd, USB_CMD_RUN_FLAG);
413 EHCI_SET(instance->registers->configflag, USB_CONFIG_FLAG_FLAG);
414 usb_log_debug("HC(%p): HW started.", instance);
415
416 usb_log_debug2("HC(%p): Registers: \n"
417 "\tUSBCMD(%p): %x(0x00080000 = at least 1ms between interrupts)\n"
418 "\tUSBSTS(%p): %x(0x00001000 = HC halted)\n"
419 "\tUSBINT(%p): %x(0x0 = no interrupts).\n"
420 "\tCONFIG(%p): %x(0x0 = ports controlled by companion hc).\n",
421 instance,
422 &instance->registers->usbcmd, EHCI_RD(instance->registers->usbcmd),
423 &instance->registers->usbsts, EHCI_RD(instance->registers->usbsts),
424 &instance->registers->usbintr, EHCI_RD(instance->registers->usbintr),
425 &instance->registers->configflag, EHCI_RD(instance->registers->configflag));
426 /* Clear and Enable interrupts */
427 EHCI_WR(instance->registers->usbsts, EHCI_RD(instance->registers->usbsts));
428 EHCI_WR(instance->registers->usbintr, EHCI_USED_INTERRUPTS);
429
430 return EOK;
431}
432
433/** Initialize memory structures used by the EHCI hcd.
434 *
435 * @param[in] instance EHCI hc driver structure.
436 * @return Error code.
437 */
438int hc_init_memory(hc_t *instance)
439{
440 assert(instance);
441 usb_log_debug2("HC(%p): Initializing Async list(%p).", instance,
442 &instance->async_list);
443 int ret = endpoint_list_init(&instance->async_list, "ASYNC");
444 if (ret != EOK) {
445 usb_log_error("HC(%p): Failed to setup ASYNC list: %s",
446 instance, str_error(ret));
447 return ret;
448 }
449 /* Specs say "Software must set queue head horizontal pointer T-bits to
450 * a zero for queue heads in the asynchronous schedule" (4.4.0).
451 * So we must maintain circular buffer (all horizontal pointers
452 * have to be valid */
453 endpoint_list_chain(&instance->async_list, &instance->async_list);
454
455 usb_log_debug2("HC(%p): Initializing Interrupt list (%p).", instance,
456 &instance->int_list);
457 ret = endpoint_list_init(&instance->int_list, "INT");
458 if (ret != EOK) {
459 usb_log_error("HC(%p): Failed to setup INT list: %s",
460 instance, str_error(ret));
461 endpoint_list_fini(&instance->async_list);
462 return ret;
463 }
464
465 /* Take 1024 periodic list heads, we ignore low mem options */
466 instance->periodic_list_base = get_page();
467 if (!instance->periodic_list_base) {
468 usb_log_error("HC(%p): Failed to get ISO schedule page.",
469 instance);
470 endpoint_list_fini(&instance->async_list);
471 endpoint_list_fini(&instance->int_list);
472 return ENOMEM;
473 }
474
475 usb_log_debug2("HC(%p): Initializing Periodic list.", instance);
476 for (unsigned i = 0;
477 i < PAGE_SIZE/sizeof(instance->periodic_list_base[0]); ++i)
478 {
479 /* Disable everything for now */
480 instance->periodic_list_base[i] =
481 LINK_POINTER_QH(addr_to_phys(instance->int_list.list_head));
482 }
483 return EOK;
484}
485
486/**
487 * @}
488 */
Note: See TracBrowser for help on using the repository browser.