source: mainline/uspace/drv/bus/usb/uhci/hc.c@ a06fd64

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since a06fd64 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: 15.2 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 drvusbuhcihc
30 * @{
31 */
32/** @file
33 * @brief UHCI Host controller driver routines
34 */
35
36#include <adt/list.h>
37#include <assert.h>
38#include <async.h>
39#include <ddi.h>
40#include <device/hw_res_parsed.h>
41#include <fibril.h>
42#include <errno.h>
43#include <macros.h>
44#include <mem.h>
45#include <stdlib.h>
46#include <stdint.h>
47#include <str_error.h>
48
49#include <usb/debug.h>
50#include <usb/usb.h>
51#include <usb/host/utils/malloc32.h>
52
53#include "uhci_batch.h"
54#include "hc.h"
55
56#define UHCI_INTR_ALLOW_INTERRUPTS \
57 (UHCI_INTR_CRC | UHCI_INTR_COMPLETE | UHCI_INTR_SHORT_PACKET)
58#define UHCI_STATUS_USED_INTERRUPTS \
59 (UHCI_STATUS_INTERRUPT | UHCI_STATUS_ERROR_INTERRUPT)
60
61static const irq_pio_range_t uhci_irq_pio_ranges[] = {
62 {
63 .base = 0,
64 .size = sizeof(uhci_regs_t)
65 }
66};
67
68static const irq_cmd_t uhci_irq_commands[] = {
69 {
70 .cmd = CMD_PIO_READ_16,
71 .dstarg = 1,
72 .addr = NULL
73 },
74 {
75 .cmd = CMD_AND,
76 .srcarg = 1,
77 .dstarg = 2,
78 .value = UHCI_STATUS_USED_INTERRUPTS | UHCI_STATUS_NM_INTERRUPTS
79 },
80 {
81 .cmd = CMD_PREDICATE,
82 .srcarg = 2,
83 .value = 2
84 },
85 {
86 .cmd = CMD_PIO_WRITE_A_16,
87 .srcarg = 1,
88 .addr = NULL
89 },
90 {
91 .cmd = CMD_ACCEPT
92 }
93};
94
95static void hc_init_hw(const hc_t *instance);
96static int hc_init_mem_structures(hc_t *instance);
97static int hc_init_transfer_lists(hc_t *instance);
98
99static int hc_debug_checker(void *arg);
100
101
102/** Generate IRQ code.
103 * @param[out] code IRQ code structure.
104 * @param[in] hw_res Device's resources.
105 *
106 * @return Error code.
107 */
108int uhci_hc_gen_irq_code(irq_code_t *code, hcd_t *hcd, const hw_res_list_parsed_t *hw_res)
109{
110 assert(code);
111 assert(hw_res);
112
113 if (hw_res->irqs.count != 1 || hw_res->io_ranges.count != 1)
114 return EINVAL;
115 const addr_range_t regs = hw_res->io_ranges.ranges[0];
116
117 if (RNGSZ(regs) < sizeof(uhci_regs_t))
118 return EOVERFLOW;
119
120 code->ranges = malloc(sizeof(uhci_irq_pio_ranges));
121 if (code->ranges == NULL)
122 return ENOMEM;
123
124 code->cmds = malloc(sizeof(uhci_irq_commands));
125 if (code->cmds == NULL) {
126 free(code->ranges);
127 return ENOMEM;
128 }
129
130 code->rangecount = ARRAY_SIZE(uhci_irq_pio_ranges);
131 code->cmdcount = ARRAY_SIZE(uhci_irq_commands);
132
133 memcpy(code->ranges, uhci_irq_pio_ranges, sizeof(uhci_irq_pio_ranges));
134 code->ranges[0].base = RNGABS(regs);
135
136 memcpy(code->cmds, uhci_irq_commands, sizeof(uhci_irq_commands));
137 uhci_regs_t *registers = (uhci_regs_t *) RNGABSPTR(regs);
138 code->cmds[0].addr = (void*)&registers->usbsts;
139 code->cmds[3].addr = (void*)&registers->usbsts;
140
141 usb_log_debug("I/O regs at %p (size %zu), IRQ %d.\n",
142 RNGABSPTR(regs), RNGSZ(regs), hw_res->irqs.irqs[0]);
143
144 return hw_res->irqs.irqs[0];
145}
146
147/** Take action based on the interrupt cause.
148 *
149 * @param[in] hcd HCD structure to use.
150 * @param[in] status Value of the status register at the time of interrupt.
151 *
152 * Interrupt might indicate:
153 * - transaction completed, either by triggering IOC, SPD, or an error
154 * - some kind of device error
155 * - resume from suspend state (not implemented)
156 */
157void uhci_hc_interrupt(hcd_t *hcd, uint32_t status)
158{
159 assert(hcd);
160 hc_t *instance = hcd_get_driver_data(hcd);
161 assert(instance);
162 /* Lower 2 bits are transaction error and transaction complete */
163 if (status & (UHCI_STATUS_INTERRUPT | UHCI_STATUS_ERROR_INTERRUPT)) {
164 LIST_INITIALIZE(done);
165 transfer_list_remove_finished(
166 &instance->transfers_interrupt, &done);
167 transfer_list_remove_finished(
168 &instance->transfers_control_slow, &done);
169 transfer_list_remove_finished(
170 &instance->transfers_control_full, &done);
171 transfer_list_remove_finished(
172 &instance->transfers_bulk_full, &done);
173
174 list_foreach_safe(done, current, next) {
175 list_remove(current);
176 uhci_transfer_batch_t *batch =
177 uhci_transfer_batch_from_link(current);
178 uhci_transfer_batch_finish_dispose(batch);
179 }
180 }
181 /* Resume interrupts are not supported */
182 if (status & UHCI_STATUS_RESUME) {
183 usb_log_error("Resume interrupt!\n");
184 }
185
186 /* Bits 4 and 5 indicate hc error */
187 if (status & (UHCI_STATUS_PROCESS_ERROR | UHCI_STATUS_SYSTEM_ERROR)) {
188 usb_log_error("UHCI hardware failure!.\n");
189 ++instance->hw_failures;
190 transfer_list_abort_all(&instance->transfers_interrupt);
191 transfer_list_abort_all(&instance->transfers_control_slow);
192 transfer_list_abort_all(&instance->transfers_control_full);
193 transfer_list_abort_all(&instance->transfers_bulk_full);
194
195 if (instance->hw_failures < UHCI_ALLOWED_HW_FAIL) {
196 /* reinitialize hw, this triggers virtual disconnect*/
197 hc_init_hw(instance);
198 } else {
199 usb_log_fatal("Too many UHCI hardware failures!.\n");
200 hc_fini(instance);
201 }
202 }
203}
204
205/** Initialize UHCI hc driver structure
206 *
207 * @param[in] instance Memory place to initialize.
208 * @param[in] regs Range of device's I/O control registers.
209 * @param[in] interrupts True if hw interrupts should be used.
210 * @return Error code.
211 * @note Should be called only once on any structure.
212 *
213 * Initializes memory structures, starts up hw, and launches debugger and
214 * interrupt fibrils.
215 */
216int hc_init(hc_t *instance, const hw_res_list_parsed_t *hw_res)
217{
218 assert(instance);
219 assert(hw_res);
220 if (hw_res->io_ranges.count != 1 ||
221 hw_res->io_ranges.ranges[0].size < sizeof(uhci_regs_t))
222 return EINVAL;
223
224 instance->hw_failures = 0;
225
226 /* allow access to hc control registers */
227 int ret = pio_enable_range(&hw_res->io_ranges.ranges[0],
228 (void **) &instance->registers);
229 if (ret != EOK) {
230 usb_log_error("Failed to gain access to registers: %s.\n",
231 str_error(ret));
232 return ret;
233 }
234
235 usb_log_debug("Device registers at %" PRIx64 " (%zuB) accessible.\n",
236 hw_res->io_ranges.ranges[0].address.absolute,
237 hw_res->io_ranges.ranges[0].size);
238
239 ret = hc_init_mem_structures(instance);
240 if (ret != EOK) {
241 usb_log_error("Failed to init UHCI memory structures: %s.\n",
242 str_error(ret));
243 // TODO: we should disable pio here
244 return ret;
245 }
246
247 return EOK;
248}
249
250void hc_start(hc_t *instance)
251{
252 hc_init_hw(instance);
253 (void)hc_debug_checker;
254
255 uhci_rh_init(&instance->rh, instance->registers->ports, "uhci");
256}
257
258/** Safely dispose host controller internal structures
259 *
260 * @param[in] instance Host controller structure to use.
261 */
262void hc_fini(hc_t *instance)
263{
264 assert(instance);
265 //TODO Implement
266}
267
268/** Initialize UHCI hc hw resources.
269 *
270 * @param[in] instance UHCI structure to use.
271 * For magic values see UHCI Design Guide
272 */
273void hc_init_hw(const hc_t *instance)
274{
275 assert(instance);
276 uhci_regs_t *registers = instance->registers;
277
278 /* Reset everything, who knows what touched it before us */
279 pio_write_16(&registers->usbcmd, UHCI_CMD_GLOBAL_RESET);
280 async_usleep(50000); /* 50ms according to USB spec(root hub reset) */
281 pio_write_16(&registers->usbcmd, 0);
282
283 /* Reset hc, all states and counters. Hope that hw is not broken */
284 pio_write_16(&registers->usbcmd, UHCI_CMD_HCRESET);
285 do { async_usleep(10); }
286 while ((pio_read_16(&registers->usbcmd) & UHCI_CMD_HCRESET) != 0);
287
288 /* Set frame to exactly 1ms */
289 pio_write_8(&registers->sofmod, 64);
290
291 /* Set frame list pointer */
292 const uint32_t pa = addr_to_phys(instance->frame_list);
293 pio_write_32(&registers->flbaseadd, pa);
294
295 if (instance->hw_interrupts) {
296 /* Enable all interrupts, but resume interrupt */
297 pio_write_16(&instance->registers->usbintr,
298 UHCI_INTR_ALLOW_INTERRUPTS);
299 }
300
301 const uint16_t cmd = pio_read_16(&registers->usbcmd);
302 if (cmd != 0)
303 usb_log_warning("Previous command value: %x.\n", cmd);
304
305 /* Start the hc with large(64B) packet FSBR */
306 pio_write_16(&registers->usbcmd,
307 UHCI_CMD_RUN_STOP | UHCI_CMD_MAX_PACKET | UHCI_CMD_CONFIGURE);
308}
309
310/** Initialize UHCI hc memory structures.
311 *
312 * @param[in] instance UHCI structure to use.
313 * @return Error code
314 * @note Should be called only once on any structure.
315 *
316 * Structures:
317 * - transfer lists (queue heads need to be accessible by the hw)
318 * - frame list page (needs to be one UHCI hw accessible 4K page)
319 */
320int hc_init_mem_structures(hc_t *instance)
321{
322 assert(instance);
323
324 /* Init USB frame list page */
325 instance->frame_list = get_page();
326 if (!instance->frame_list) {
327 return ENOMEM;
328 }
329 usb_log_debug("Initialized frame list at %p.\n", instance->frame_list);
330
331 /* Init transfer lists */
332 int ret = hc_init_transfer_lists(instance);
333 if (ret != EOK) {
334 usb_log_error("Failed to initialize transfer lists.\n");
335 return_page(instance->frame_list);
336 return ENOMEM;
337 }
338 usb_log_debug("Initialized transfer lists.\n");
339
340
341 /* Set all frames to point to the first queue head */
342 const uint32_t queue = LINK_POINTER_QH(
343 addr_to_phys(instance->transfers_interrupt.queue_head));
344
345 for (unsigned i = 0; i < UHCI_FRAME_LIST_COUNT; ++i) {
346 instance->frame_list[i] = queue;
347 }
348
349 return EOK;
350}
351
352/** Initialize UHCI hc transfer lists.
353 *
354 * @param[in] instance UHCI structure to use.
355 * @return Error code
356 * @note Should be called only once on any structure.
357 *
358 * Initializes transfer lists and sets them in one chain to support proper
359 * USB scheduling. Sets pointer table for quick access.
360 */
361int hc_init_transfer_lists(hc_t *instance)
362{
363 assert(instance);
364#define SETUP_TRANSFER_LIST(type, name) \
365do { \
366 int ret = transfer_list_init(&instance->transfers_##type, name); \
367 if (ret != EOK) { \
368 usb_log_error("Failed to setup %s transfer list: %s.\n", \
369 name, str_error(ret)); \
370 transfer_list_fini(&instance->transfers_bulk_full); \
371 transfer_list_fini(&instance->transfers_control_full); \
372 transfer_list_fini(&instance->transfers_control_slow); \
373 transfer_list_fini(&instance->transfers_interrupt); \
374 return ret; \
375 } \
376} while (0)
377
378 SETUP_TRANSFER_LIST(bulk_full, "BULK FULL");
379 SETUP_TRANSFER_LIST(control_full, "CONTROL FULL");
380 SETUP_TRANSFER_LIST(control_slow, "CONTROL LOW");
381 SETUP_TRANSFER_LIST(interrupt, "INTERRUPT");
382#undef SETUP_TRANSFER_LIST
383 /* Connect lists into one schedule */
384 transfer_list_set_next(&instance->transfers_control_full,
385 &instance->transfers_bulk_full);
386 transfer_list_set_next(&instance->transfers_control_slow,
387 &instance->transfers_control_full);
388 transfer_list_set_next(&instance->transfers_interrupt,
389 &instance->transfers_control_slow);
390
391 /*FSBR, This feature is not needed (adds no benefit) and is supposedly
392 * buggy on certain hw, enable at your own risk. */
393#ifdef FSBR
394 transfer_list_set_next(&instance->transfers_bulk_full,
395 &instance->transfers_control_full);
396#endif
397
398 /* Assign pointers to be used during scheduling */
399 instance->transfers[USB_SPEED_FULL][USB_TRANSFER_INTERRUPT] =
400 &instance->transfers_interrupt;
401 instance->transfers[USB_SPEED_LOW][USB_TRANSFER_INTERRUPT] =
402 &instance->transfers_interrupt;
403 instance->transfers[USB_SPEED_FULL][USB_TRANSFER_CONTROL] =
404 &instance->transfers_control_full;
405 instance->transfers[USB_SPEED_LOW][USB_TRANSFER_CONTROL] =
406 &instance->transfers_control_slow;
407 instance->transfers[USB_SPEED_FULL][USB_TRANSFER_BULK] =
408 &instance->transfers_bulk_full;
409
410 return EOK;
411}
412
413int uhci_hc_status(hcd_t *hcd, uint32_t *status)
414{
415 assert(hcd);
416 assert(status);
417 hc_t *instance = hcd_get_driver_data(hcd);
418 assert(instance);
419
420 *status = 0;
421 if (instance->registers) {
422 uint16_t s = pio_read_16(&instance->registers->usbsts);
423 pio_write_16(&instance->registers->usbsts, s);
424 *status = s;
425 }
426 return EOK;
427}
428
429/** Schedule batch for execution.
430 *
431 * @param[in] instance UHCI structure to use.
432 * @param[in] batch Transfer batch to schedule.
433 * @return Error code
434 *
435 * Checks for bandwidth availability and appends the batch to the proper queue.
436 */
437int uhci_hc_schedule(hcd_t *hcd, usb_transfer_batch_t *batch)
438{
439 assert(hcd);
440 hc_t *instance = hcd_get_driver_data(hcd);
441 assert(instance);
442 assert(batch);
443
444 if (batch->ep->address == uhci_rh_get_address(&instance->rh))
445 return uhci_rh_schedule(&instance->rh, batch);
446
447 uhci_transfer_batch_t *uhci_batch = uhci_transfer_batch_get(batch);
448 if (!uhci_batch) {
449 usb_log_error("Failed to create UHCI transfer structures.\n");
450 return ENOMEM;
451 }
452
453 transfer_list_t *list =
454 instance->transfers[batch->ep->speed][batch->ep->transfer_type];
455 assert(list);
456 transfer_list_add_batch(list, uhci_batch);
457
458 return EOK;
459}
460
461/** Debug function, checks consistency of memory structures.
462 *
463 * @param[in] arg UHCI structure to use.
464 * @return EOK (should never return)
465 */
466int hc_debug_checker(void *arg)
467{
468 hc_t *instance = arg;
469 assert(instance);
470
471#define QH(queue) \
472 instance->transfers_##queue.queue_head
473
474 while (1) {
475 const uint16_t cmd = pio_read_16(&instance->registers->usbcmd);
476 const uint16_t sts = pio_read_16(&instance->registers->usbsts);
477 const uint16_t intr =
478 pio_read_16(&instance->registers->usbintr);
479
480 if (((cmd & UHCI_CMD_RUN_STOP) != 1) || (sts != 0)) {
481 usb_log_debug2("Command: %X Status: %X Intr: %x\n",
482 cmd, sts, intr);
483 }
484
485 const uintptr_t frame_list =
486 pio_read_32(&instance->registers->flbaseadd) & ~0xfff;
487 if (frame_list != addr_to_phys(instance->frame_list)) {
488 usb_log_debug("Framelist address: %p vs. %p.\n",
489 (void *) frame_list,
490 (void *) addr_to_phys(instance->frame_list));
491 }
492
493 int frnum = pio_read_16(&instance->registers->frnum) & 0x3ff;
494
495 uintptr_t expected_pa = instance->frame_list[frnum]
496 & LINK_POINTER_ADDRESS_MASK;
497 uintptr_t real_pa = addr_to_phys(QH(interrupt));
498 if (expected_pa != real_pa) {
499 usb_log_debug("Interrupt QH: %p (frame %d) vs. %p.\n",
500 (void *) expected_pa, frnum, (void *) real_pa);
501 }
502
503 expected_pa = QH(interrupt)->next & LINK_POINTER_ADDRESS_MASK;
504 real_pa = addr_to_phys(QH(control_slow));
505 if (expected_pa != real_pa) {
506 usb_log_debug("Control Slow QH: %p vs. %p.\n",
507 (void *) expected_pa, (void *) real_pa);
508 }
509
510 expected_pa = QH(control_slow)->next & LINK_POINTER_ADDRESS_MASK;
511 real_pa = addr_to_phys(QH(control_full));
512 if (expected_pa != real_pa) {
513 usb_log_debug("Control Full QH: %p vs. %p.\n",
514 (void *) expected_pa, (void *) real_pa);
515 }
516
517 expected_pa = QH(control_full)->next & LINK_POINTER_ADDRESS_MASK;
518 real_pa = addr_to_phys(QH(bulk_full));
519 if (expected_pa != real_pa ) {
520 usb_log_debug("Bulk QH: %p vs. %p.\n",
521 (void *) expected_pa, (void *) real_pa);
522 }
523 async_usleep(UHCI_DEBUGER_TIMEOUT);
524 }
525 return EOK;
526#undef QH
527}
528/**
529 * @}
530 */
Note: See TracBrowser for help on using the repository browser.