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

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

Add more comments about qemu interrupt bug

Remove some redundant pointer casts.

  • Property mode set to 100644
File size: 15.0 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 (%zuB) 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 SETUP_TRANSFER_LIST(type, name) \
250do { \
251 int ret = transfer_list_init(&instance->transfers_##type, name); \
252 if (ret != EOK) { \
253 usb_log_error("Failed(%d) to setup %s transfer list: %s.\n", \
254 ret, name, str_error(ret)); \
255 transfer_list_fini(&instance->transfers_bulk_full); \
256 transfer_list_fini(&instance->transfers_control_full); \
257 transfer_list_fini(&instance->transfers_control_slow); \
258 transfer_list_fini(&instance->transfers_interrupt); \
259 return ret; \
260 } \
261} while (0)
262
263 SETUP_TRANSFER_LIST(bulk_full, "BULK FULL");
264 SETUP_TRANSFER_LIST(control_full, "CONTROL FULL");
265 SETUP_TRANSFER_LIST(control_slow, "CONTROL LOW");
266 SETUP_TRANSFER_LIST(interrupt, "INTERRUPT");
267#undef SETUP_TRANSFER_LIST
268 /* Connect lists into one schedule */
269 transfer_list_set_next(&instance->transfers_control_full,
270 &instance->transfers_bulk_full);
271 transfer_list_set_next(&instance->transfers_control_slow,
272 &instance->transfers_control_full);
273 transfer_list_set_next(&instance->transfers_interrupt,
274 &instance->transfers_control_slow);
275
276 /*FSBR*/
277#ifdef FSBR
278 transfer_list_set_next(&instance->transfers_bulk_full,
279 &instance->transfers_control_full);
280#endif
281
282 /* Assign pointers to be used during scheduling */
283 instance->transfers[USB_SPEED_FULL][USB_TRANSFER_INTERRUPT] =
284 &instance->transfers_interrupt;
285 instance->transfers[USB_SPEED_LOW][USB_TRANSFER_INTERRUPT] =
286 &instance->transfers_interrupt;
287 instance->transfers[USB_SPEED_FULL][USB_TRANSFER_CONTROL] =
288 &instance->transfers_control_full;
289 instance->transfers[USB_SPEED_LOW][USB_TRANSFER_CONTROL] =
290 &instance->transfers_control_slow;
291 instance->transfers[USB_SPEED_FULL][USB_TRANSFER_BULK] =
292 &instance->transfers_bulk_full;
293
294 return EOK;
295#undef CHECK_RET_CLEAR_RETURN
296}
297/*----------------------------------------------------------------------------*/
298/** Schedule batch for execution.
299 *
300 * @param[in] instance UHCI structure to use.
301 * @param[in] batch Transfer batch to schedule.
302 * @return Error code
303 *
304 * Checks for bandwidth availability and appends the batch to the proper queue.
305 */
306int hc_schedule(hc_t *instance, usb_transfer_batch_t *batch)
307{
308 assert(instance);
309 assert(batch);
310
311 transfer_list_t *list =
312 instance->transfers[batch->ep->speed][batch->ep->transfer_type];
313 assert(list);
314 transfer_list_add_batch(list, batch);
315
316 return EOK;
317}
318/*----------------------------------------------------------------------------*/
319/** Take action based on the interrupt cause.
320 *
321 * @param[in] instance UHCI structure to use.
322 * @param[in] status Value of the status register at the time of interrupt.
323 *
324 * Interrupt might indicate:
325 * - transaction completed, either by triggering IOC, SPD, or an error
326 * - some kind of device error
327 * - resume from suspend state (not implemented)
328 */
329void hc_interrupt(hc_t *instance, uint16_t status)
330{
331 assert(instance);
332 /* Lower 2 bits are transaction error and transaction complete */
333 if (status & (UHCI_STATUS_INTERRUPT | UHCI_STATUS_ERROR_INTERRUPT)) {
334 LIST_INITIALIZE(done);
335 transfer_list_remove_finished(
336 &instance->transfers_interrupt, &done);
337 transfer_list_remove_finished(
338 &instance->transfers_control_slow, &done);
339 transfer_list_remove_finished(
340 &instance->transfers_control_full, &done);
341 transfer_list_remove_finished(
342 &instance->transfers_bulk_full, &done);
343
344 while (!list_empty(&done)) {
345 link_t *item = done.next;
346 list_remove(item);
347 usb_transfer_batch_t *batch =
348 list_get_instance(item, usb_transfer_batch_t, link);
349 usb_transfer_batch_finish(batch);
350 }
351 }
352 /* Resume interrupts are not supported */
353
354 /* Bits 4 and 5 indicate hc error */
355 if (status & (UHCI_STATUS_PROCESS_ERROR | UHCI_STATUS_SYSTEM_ERROR)) {
356 usb_log_error("UHCI hardware failure!.\n");
357 ++instance->hw_failures;
358 transfer_list_abort_all(&instance->transfers_interrupt);
359 transfer_list_abort_all(&instance->transfers_control_slow);
360 transfer_list_abort_all(&instance->transfers_control_full);
361 transfer_list_abort_all(&instance->transfers_bulk_full);
362
363 if (instance->hw_failures < UHCI_ALLOWED_HW_FAIL) {
364 /* reinitialize hw, this triggers virtual disconnect*/
365 hc_init_hw(instance);
366 } else {
367 usb_log_fatal("Too many UHCI hardware failures!.\n");
368 hc_fini(instance);
369 }
370 }
371}
372/*----------------------------------------------------------------------------*/
373/** Polling function, emulates interrupts.
374 *
375 * @param[in] arg UHCI hc structure to use.
376 * @return EOK (should never return)
377 */
378int hc_interrupt_emulator(void* arg)
379{
380 usb_log_debug("Started interrupt emulator.\n");
381 hc_t *instance = arg;
382 assert(instance);
383
384 while (1) {
385 /* Read and clear status register */
386 uint16_t status = pio_read_16(&instance->registers->usbsts);
387 pio_write_16(&instance->registers->usbsts, status);
388 if (status != 0)
389 usb_log_debug2("UHCI status: %x.\n", status);
390// Qemu fails to report stalled communication
391// see https://bugs.launchpad.net/qemu/+bug/757654
392// This is a simple workaround to force queue processing every time
393 // status |= 1;
394 hc_interrupt(instance, status);
395 async_usleep(UHCI_INT_EMULATOR_TIMEOUT);
396 }
397 return EOK;
398}
399/*---------------------------------------------------------------------------*/
400/** Debug function, checks consistency of memory structures.
401 *
402 * @param[in] arg UHCI structure to use.
403 * @return EOK (should never return)
404 */
405int hc_debug_checker(void *arg)
406{
407 hc_t *instance = arg;
408 assert(instance);
409
410#define QH(queue) \
411 instance->transfers_##queue.queue_head
412
413 while (1) {
414 const uint16_t cmd = pio_read_16(&instance->registers->usbcmd);
415 const uint16_t sts = pio_read_16(&instance->registers->usbsts);
416 const uint16_t intr =
417 pio_read_16(&instance->registers->usbintr);
418
419 if (((cmd & UHCI_CMD_RUN_STOP) != 1) || (sts != 0)) {
420 usb_log_debug2("Command: %X Status: %X Intr: %x\n",
421 cmd, sts, intr);
422 }
423
424 uintptr_t frame_list =
425 pio_read_32(&instance->registers->flbaseadd) & ~0xfff;
426 if (frame_list != addr_to_phys(instance->frame_list)) {
427 usb_log_debug("Framelist address: %p vs. %p.\n",
428 (void *) frame_list,
429 (void *) addr_to_phys(instance->frame_list));
430 }
431
432 int frnum = pio_read_16(&instance->registers->frnum) & 0x3ff;
433
434 uintptr_t expected_pa = instance->frame_list[frnum]
435 & LINK_POINTER_ADDRESS_MASK;
436 uintptr_t real_pa = addr_to_phys(QH(interrupt));
437 if (expected_pa != real_pa) {
438 usb_log_debug("Interrupt QH: %p (frame %d) vs. %p.\n",
439 (void *) expected_pa, frnum, (void *) real_pa);
440 }
441
442 expected_pa = QH(interrupt)->next & LINK_POINTER_ADDRESS_MASK;
443 real_pa = addr_to_phys(QH(control_slow));
444 if (expected_pa != real_pa) {
445 usb_log_debug("Control Slow QH: %p vs. %p.\n",
446 (void *) expected_pa, (void *) real_pa);
447 }
448
449 expected_pa = QH(control_slow)->next & LINK_POINTER_ADDRESS_MASK;
450 real_pa = addr_to_phys(QH(control_full));
451 if (expected_pa != real_pa) {
452 usb_log_debug("Control Full QH: %p vs. %p.\n",
453 (void *) expected_pa, (void *) real_pa);
454 }
455
456 expected_pa = QH(control_full)->next & LINK_POINTER_ADDRESS_MASK;
457 real_pa = addr_to_phys(QH(bulk_full));
458 if (expected_pa != real_pa ) {
459 usb_log_debug("Bulk QH: %p vs. %p.\n",
460 (void *) expected_pa, (void *) real_pa);
461 }
462 async_usleep(UHCI_DEBUGER_TIMEOUT);
463 }
464 return EOK;
465#undef QH
466}
467/**
468 * @}
469 */
Note: See TracBrowser for help on using the repository browser.