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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 7265558 was 7265558, checked in by Jan Vesely <jano.vesely@…>, 14 years ago

libusbhost: Drop hash_table and use multiple lists instead.

This has several advantages:
Knowing that all endpoints on one address are in one list makes toggle resets more efficient.
Endpoint search is done only once for inserts and deletes.
Lists are part of the structure and not allocated separately, removing one more failure point from init path.

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