source: mainline/uspace/drv/uhci-hcd/hc.c@ a39cfb8

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

Even more refactoring and doxygen

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