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

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

Make better use of macros

  • Property mode set to 100644
File size: 14.8 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 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// status |= 1; //Uncomment to work around qemu hang
333 /* Lower 2 bits are transaction error and transaction complete */
334 if (status & (UHCI_STATUS_INTERRUPT | UHCI_STATUS_ERROR_INTERRUPT)) {
335 LIST_INITIALIZE(done);
336 transfer_list_remove_finished(
337 &instance->transfers_interrupt, &done);
338 transfer_list_remove_finished(
339 &instance->transfers_control_slow, &done);
340 transfer_list_remove_finished(
341 &instance->transfers_control_full, &done);
342 transfer_list_remove_finished(
343 &instance->transfers_bulk_full, &done);
344
345 while (!list_empty(&done)) {
346 link_t *item = done.next;
347 list_remove(item);
348 usb_transfer_batch_t *batch =
349 list_get_instance(item, usb_transfer_batch_t, link);
350 usb_transfer_batch_finish(batch);
351 }
352 }
353 /* Resume interrupts are not supported */
354
355 /* Bits 4 and 5 indicate hc error */
356 if (status & (UHCI_STATUS_PROCESS_ERROR | UHCI_STATUS_SYSTEM_ERROR)) {
357 usb_log_error("UHCI hardware failure!.\n");
358 ++instance->hw_failures;
359 transfer_list_abort_all(&instance->transfers_interrupt);
360 transfer_list_abort_all(&instance->transfers_control_slow);
361 transfer_list_abort_all(&instance->transfers_control_full);
362 transfer_list_abort_all(&instance->transfers_bulk_full);
363
364 if (instance->hw_failures < UHCI_ALLOWED_HW_FAIL) {
365 /* reinitialize hw, this triggers virtual disconnect*/
366 hc_init_hw(instance);
367 } else {
368 usb_log_fatal("Too many UHCI hardware failures!.\n");
369 hc_fini(instance);
370 }
371 }
372}
373/*----------------------------------------------------------------------------*/
374/** Polling function, emulates interrupts.
375 *
376 * @param[in] arg UHCI hc structure to use.
377 * @return EOK (should never return)
378 */
379int hc_interrupt_emulator(void* arg)
380{
381 usb_log_debug("Started interrupt emulator.\n");
382 hc_t *instance = (hc_t*)arg;
383 assert(instance);
384
385 while (1) {
386 /* Readd and clear status register */
387 uint16_t status = pio_read_16(&instance->registers->usbsts);
388 pio_write_16(&instance->registers->usbsts, status);
389 if (status != 0)
390 usb_log_debug2("UHCI status: %x.\n", status);
391 hc_interrupt(instance, status);
392 async_usleep(UHCI_INT_EMULATOR_TIMEOUT);
393 }
394 return EOK;
395}
396/*---------------------------------------------------------------------------*/
397/** Debug function, checks consistency of memory structures.
398 *
399 * @param[in] arg UHCI structure to use.
400 * @return EOK (should never return)
401 */
402int hc_debug_checker(void *arg)
403{
404 hc_t *instance = (hc_t*)arg;
405 assert(instance);
406
407#define QH(queue) \
408 instance->transfers_##queue.queue_head
409
410 while (1) {
411 const uint16_t cmd = pio_read_16(&instance->registers->usbcmd);
412 const uint16_t sts = pio_read_16(&instance->registers->usbsts);
413 const uint16_t intr =
414 pio_read_16(&instance->registers->usbintr);
415
416 if (((cmd & UHCI_CMD_RUN_STOP) != 1) || (sts != 0)) {
417 usb_log_debug2("Command: %X Status: %X Intr: %x\n",
418 cmd, sts, intr);
419 }
420
421 uintptr_t frame_list =
422 pio_read_32(&instance->registers->flbaseadd) & ~0xfff;
423 if (frame_list != addr_to_phys(instance->frame_list)) {
424 usb_log_debug("Framelist address: %p vs. %p.\n",
425 frame_list, addr_to_phys(instance->frame_list));
426 }
427
428 int frnum = pio_read_16(&instance->registers->frnum) & 0x3ff;
429
430 uintptr_t expected_pa = instance->frame_list[frnum]
431 & LINK_POINTER_ADDRESS_MASK;
432 uintptr_t real_pa = addr_to_phys(QH(interrupt));
433 if (expected_pa != real_pa) {
434 usb_log_debug("Interrupt QH: %p(frame: %d) vs. %p.\n",
435 expected_pa, frnum, real_pa);
436 }
437
438 expected_pa = QH(interrupt)->next & LINK_POINTER_ADDRESS_MASK;
439 real_pa = addr_to_phys(QH(control_slow));
440 if (expected_pa != real_pa) {
441 usb_log_debug("Control Slow QH: %p vs. %p.\n",
442 expected_pa, real_pa);
443 }
444
445 expected_pa = QH(control_slow)->next & LINK_POINTER_ADDRESS_MASK;
446 real_pa = addr_to_phys(QH(control_full));
447 if (expected_pa != real_pa) {
448 usb_log_debug("Control Full QH: %p vs. %p.\n",
449 expected_pa, real_pa);
450 }
451
452 expected_pa = QH(control_full)->next & LINK_POINTER_ADDRESS_MASK;
453 real_pa = addr_to_phys(QH(bulk_full));
454 if (expected_pa != real_pa ) {
455 usb_log_debug("Bulk QH: %p vs. %p.\n",
456 expected_pa, real_pa);
457 }
458 async_usleep(UHCI_DEBUGER_TIMEOUT);
459 }
460 return EOK;
461#undef QH
462}
463/**
464 * @}
465 */
Note: See TracBrowser for help on using the repository browser.