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

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