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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 1170ea3c was d57122c, checked in by Jakub Jermar <jakub@…>, 14 years ago

UHCI and OHCI HC's now define and use proper IRQ PIO range.

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