/** Initialize UHCI hc memory structures. * * @param[in] instance UHCI structure to use. * @return Error code * @note Should be called only once on any structure. * * Structures: * - transfer lists (queue heads need to be accessible by the hw) * - frame list page (needs to be one UHCI hw accessible 4K page) */ int hc_init_mem_structures(hc_t *instance) { assert(instance); /* Init USB frame list page */ instance->frame_list = get_page(); if (!instance->frame_list) { return ENOMEM; } usb_log_debug("Initialized frame list at %p.\n", instance->frame_list); /* Init transfer lists */ int ret = hc_init_transfer_lists(instance); if (ret != EOK) { usb_log_error("Failed to initialize transfer lists.\n"); return_page(instance->frame_list); return ENOMEM; } usb_log_debug("Initialized transfer lists.\n"); /* Set all frames to point to the first queue head */ const uint32_t queue = LINK_POINTER_QH( addr_to_phys(instance->transfers_interrupt.queue_head)); for (unsigned i = 0; i < UHCI_FRAME_LIST_COUNT; ++i) { instance->frame_list[i] = queue; } return EOK; }
/** * Initialize EHCI TD. * @param instance TD structure to initialize. * @param next Next TD in ED list. * @param direction Used to determine PID, BOTH means setup PID. * @param buffer Pointer to the first byte of transferred data. * @param size Size of the buffer. * @param toggle Toggle bit value, use 0/1 to set explicitly, * any other value means that ED toggle will be used. */ void td_init(td_t *instance, const td_t *next, usb_direction_t direction, const void *buffer, size_t size, int toggle, bool ioc) { assert(instance); memset(instance, 0, sizeof(td_t)); /* Set PID and Total size */ assert((size & TD_STATUS_TOTAL_MASK) == size); EHCI_MEM32_WR(instance->status, ((dir[direction] & TD_STATUS_PID_MASK) << TD_STATUS_PID_SHIFT) | ((size & TD_STATUS_TOTAL_MASK) << TD_STATUS_TOTAL_SHIFT) | (ioc ? TD_STATUS_IOC_FLAG : 0) ); if (toggle == 0 || toggle == 1) { EHCI_MEM32_SET(instance->status, toggle ? TD_STATUS_TOGGLE_FLAG : 0); } if (buffer != NULL) { assert(size != 0); for (unsigned i = 0; (i < ARRAY_SIZE(instance->buffer_pointer)) && size; ++i) { const uintptr_t page = (addr_to_phys(buffer) & TD_BUFFER_POINTER_MASK); const size_t offset = ((uintptr_t)buffer & TD_BUFFER_POINTER_OFFSET_MASK); assert(offset == 0 || i == 0); size -= min((4096 - offset), size); buffer += min((4096 - offset), size); EHCI_MEM32_WR(instance->buffer_pointer[i], page | offset); } } EHCI_MEM32_WR(instance->next, next ? LINK_POINTER_TD(addr_to_phys(next)) : LINK_POINTER_TERM); EHCI_MEM32_WR(instance->alternate, LINK_POINTER_TERM); EHCI_MEM32_SET(instance->status, TD_STATUS_ACTIVE_FLAG); write_barrier(); }
/** Initialize transfer list structures. * * @param[in] instance Memory place to use. * @param[in] name Name of the new list. * @return Error code * * Allocates memory for internal ed_t structure. */ int endpoint_list_init(endpoint_list_t *instance, const char *name) { assert(instance); instance->name = name; instance->list_head = malloc32(sizeof(ed_t)); if (!instance->list_head) { usb_log_error("Failed to allocate list head.\n"); return ENOMEM; } instance->list_head_pa = addr_to_phys(instance->list_head); usb_log_debug2("Transfer list %s setup with ED: %p(0x%0" PRIx32 ")).\n", name, instance->list_head, instance->list_head_pa); ed_init(instance->list_head, NULL, NULL); list_initialize(&instance->endpoint_list); fibril_mutex_initialize(&instance->guard); return EOK; }
/** * Initialize ED. * * @param instance ED structure to initialize. * @param ep Driver endpoint to use. * @param td TD to put in the list. * * If @param ep is NULL, dummy ED is initialized with only skip flag set. */ void ed_init(ed_t *instance, const endpoint_t *ep, const td_t *td) { assert(instance); memset(instance, 0, sizeof(*instance)); if (ep == NULL) { /* Mark as dead, used for dummy EDs at the beginning of * endpoint lists. */ OHCI_MEM32_WR(instance->status, ED_STATUS_K_FLAG); return; } /* Non-dummy ED must have corresponding EP and TD assigned */ assert(td); assert(ep); assert(ep->direction < ARRAY_SIZE(dir)); /* Status: address, endpoint nr, direction mask and max packet size. */ OHCI_MEM32_WR(instance->status, ((ep->address & ED_STATUS_FA_MASK) << ED_STATUS_FA_SHIFT) | ((ep->endpoint & ED_STATUS_EN_MASK) << ED_STATUS_EN_SHIFT) | ((dir[ep->direction] & ED_STATUS_D_MASK) << ED_STATUS_D_SHIFT) | ((ep->max_packet_size & ED_STATUS_MPS_MASK) << ED_STATUS_MPS_SHIFT)); /* Low speed flag */ if (ep->speed == USB_SPEED_LOW) OHCI_MEM32_SET(instance->status, ED_STATUS_S_FLAG); /* Isochronous format flag */ // TODO: We need iTD instead of TD for iso transfers if (ep->transfer_type == USB_TRANSFER_ISOCHRONOUS) OHCI_MEM32_SET(instance->status, ED_STATUS_F_FLAG); /* Set TD to the list */ const uintptr_t pa = addr_to_phys(td); OHCI_MEM32_WR(instance->td_head, pa & ED_TDHEAD_PTR_MASK); OHCI_MEM32_WR(instance->td_tail, pa & ED_TDTAIL_PTR_MASK); /* Set toggle bit */ if (ep->toggle) OHCI_MEM32_SET(instance->td_head, ED_TDHEAD_TOGGLE_CARRY); }
/** Remove endpoint from the list and queue. * * @param[in] instance List to use. * @param[in] endpoint Endpoint to remove. */ void endpoint_list_remove_ep(endpoint_list_t *instance, ohci_endpoint_t *ep) { assert(instance); assert(instance->list_head); assert(ep); assert(ep->ed); fibril_mutex_lock(&instance->guard); usb_log_debug2("Queue %s: removing endpoint(%p).\n", instance->name, ep); const char *qpos = NULL; ed_t *prev_ed; /* Remove from the hardware queue */ if (list_first(&instance->endpoint_list) == &ep->link) { /* I'm the first one here */ prev_ed = instance->list_head; qpos = "FIRST"; } else { ohci_endpoint_t *prev = list_get_instance(ep->link.prev, ohci_endpoint_t, link); prev_ed = prev->ed; qpos = "NOT FIRST"; } assert(ed_next(prev_ed) == addr_to_phys(ep->ed)); prev_ed->next = ep->ed->next; /* Make sure ED is updated */ write_barrier(); usb_log_debug("HCD EP(%p) removed (%s) from %s, next %x.\n", ep, qpos, instance->name, ep->ed->next); /* Remove from the endpoint list */ list_remove(&ep->link); fibril_mutex_unlock(&instance->guard); }
/** Initialize UHCI hc hw resources. * * @param[in] instance UHCI structure to use. * For magic values see UHCI Design Guide */ void hc_init_hw(const hc_t *instance) { assert(instance); uhci_regs_t *registers = instance->registers; /* Reset everything, who knows what touched it before us */ pio_write_16(®isters->usbcmd, UHCI_CMD_GLOBAL_RESET); async_usleep(50000); /* 50ms according to USB spec(root hub reset) */ pio_write_16(®isters->usbcmd, 0); /* Reset hc, all states and counters. Hope that hw is not broken */ pio_write_16(®isters->usbcmd, UHCI_CMD_HCRESET); do { async_usleep(10); } while ((pio_read_16(®isters->usbcmd) & UHCI_CMD_HCRESET) != 0); /* Set frame to exactly 1ms */ pio_write_8(®isters->sofmod, 64); /* Set frame list pointer */ const uint32_t pa = addr_to_phys(instance->frame_list); pio_write_32(®isters->flbaseadd, pa); if (instance->hw_interrupts) { /* Enable all interrupts, but resume interrupt */ pio_write_16(&instance->registers->usbintr, UHCI_INTR_ALLOW_INTERRUPTS); } const uint16_t cmd = pio_read_16(®isters->usbcmd); if (cmd != 0) usb_log_warning("Previous command value: %x.\n", cmd); /* Start the hc with large(64B) packet FSBR */ pio_write_16(®isters->usbcmd, UHCI_CMD_RUN_STOP | UHCI_CMD_MAX_PACKET | UHCI_CMD_CONFIGURE); }
/** Debug function, checks consistency of memory structures. * * @param[in] arg UHCI structure to use. * @return EOK (should never return) */ int hc_debug_checker(void *arg) { hc_t *instance = arg; assert(instance); #define QH(queue) \ instance->transfers_##queue.queue_head while (1) { const uint16_t cmd = pio_read_16(&instance->registers->usbcmd); const uint16_t sts = pio_read_16(&instance->registers->usbsts); const uint16_t intr = pio_read_16(&instance->registers->usbintr); if (((cmd & UHCI_CMD_RUN_STOP) != 1) || (sts != 0)) { usb_log_debug2("Command: %X Status: %X Intr: %x\n", cmd, sts, intr); } const uintptr_t frame_list = pio_read_32(&instance->registers->flbaseadd) & ~0xfff; if (frame_list != addr_to_phys(instance->frame_list)) { usb_log_debug("Framelist address: %p vs. %p.\n", (void *) frame_list, (void *) addr_to_phys(instance->frame_list)); } int frnum = pio_read_16(&instance->registers->frnum) & 0x3ff; uintptr_t expected_pa = instance->frame_list[frnum] & LINK_POINTER_ADDRESS_MASK; uintptr_t real_pa = addr_to_phys(QH(interrupt)); if (expected_pa != real_pa) { usb_log_debug("Interrupt QH: %p (frame %d) vs. %p.\n", (void *) expected_pa, frnum, (void *) real_pa); } expected_pa = QH(interrupt)->next & LINK_POINTER_ADDRESS_MASK; real_pa = addr_to_phys(QH(control_slow)); if (expected_pa != real_pa) { usb_log_debug("Control Slow QH: %p vs. %p.\n", (void *) expected_pa, (void *) real_pa); } expected_pa = QH(control_slow)->next & LINK_POINTER_ADDRESS_MASK; real_pa = addr_to_phys(QH(control_full)); if (expected_pa != real_pa) { usb_log_debug("Control Full QH: %p vs. %p.\n", (void *) expected_pa, (void *) real_pa); } expected_pa = QH(control_full)->next & LINK_POINTER_ADDRESS_MASK; real_pa = addr_to_phys(QH(bulk_full)); if (expected_pa != real_pa ) { usb_log_debug("Bulk QH: %p vs. %p.\n", (void *) expected_pa, (void *) real_pa); } async_usleep(UHCI_DEBUGER_TIMEOUT); } return EOK; #undef QH }