// ------------------------------------------------------------------------------------------------ static void ehci_dev_intr(USB_Device* dev, USB_Transfer* t) { EHCI_Controller* hc = (EHCI_Controller*)dev->hc; // Determine transfer properties uint speed = dev->speed; uint addr = dev->addr; uint max_size = dev->max_packet_size; uint endp = dev->endp.desc.addr & 0xf; // Create queue of transfer descriptors EHCI_TD* td = ehci_alloc_td(hc); if (!td) { t->success = false; t->complete = true; return; } EHCI_TD* head = td; EHCI_TD* prev = 0; // Data in/out packets uint toggle = dev->endp.toggle; uint packet_type = USB_PACKET_IN; uint packet_size = t->len; ehci_td_init(td, prev, toggle, packet_type, packet_size, t->data); // Initialize queue head EHCI_QH* qh = ehci_alloc_qh(hc); ehci_qh_init(qh, t, head, dev->parent, true, speed, addr, endp, max_size); // Schedule queue ehci_insert_qh(hc, qh); }
// ------------------------------------------------------------------------------------------------ void ehci_init(uint id, PCI_DeviceInfo* info) { if (!(((info->class_code << 8) | info->subclass) == PCI_SERIAL_USB && info->prog_intf == PCI_SERIAL_USB_EHCI)) { return; } if (sizeof(EHCI_QH) != 128) { console_print("Unexpected EHCI_QH size: %d\n", sizeof(EHCI_QH)); return; } console_print ("Initializing EHCI\n"); // Base I/O Address u32 bar0 = pci_in32(id, PCI_CONFIG_BAR0); if (bar0 & 0x1) { // Only Memory Mapped I/O supported return; } if (bar0 & 0x4) { // TODO - support 64-bit pointer return; } bar0 &= ~0xf; // clear low 4 bits // Controller initialization EHCI_Controller* hc = vm_alloc(sizeof(EHCI_Controller)); hc->cap_regs = (EHCI_Cap_Regs*)(uintptr_t)bar0; hc->op_regs = (EHCI_Op_Regs*)(uintptr_t)(bar0 + hc->cap_regs->cap_length); hc->qh_pool = (EHCI_QH*)vm_alloc(sizeof(EHCI_QH) * MAX_QH); hc->td_pool = (EHCI_TD*)vm_alloc(sizeof(EHCI_TD) * MAX_TD); memset(hc->qh_pool, 0, sizeof(EHCI_QH) * MAX_QH); memset(hc->td_pool, 0, sizeof(EHCI_TD) * MAX_TD); // Asynchronous queue setup EHCI_QH* qh = ehci_alloc_qh(hc); qh->qhlp = (u32)(uintptr_t)qh | PTR_QH; qh->ch = QH_CH_H; qh->caps = (1 << QH_CAP_MULT_SHIFT); qh->cur_link = 0; qh->next_link = PTR_TERMINATE; qh->alt_link = 0; qh->token = 0; for (uint i = 0; i < 5; ++i) { qh->buffer[i] = 0; qh->ext_buffer[i] = 0; } qh->transfer = 0; qh->qh_link.prev = &qh->qh_link; qh->qh_link.next = &qh->qh_link; hc->async_qh = qh; // Check extended capabilities uint eecp = (hc->cap_regs->hcc_params & HCCPARAMS_EECP_MASK) >> HCCPARAMS_EECP_SHIFT; if (eecp >= 0x40) { // Disable BIOS legacy support uint legsup = pci_in32(id, eecp + USBLEGSUP); if (legsup & USBLEGSUP_HC_BIOS) { pci_out32(id, eecp + USBLEGSUP, legsup | USBLEGSUP_HC_OS); for (;;) { legsup = pci_in32(id, eecp + USBLEGSUP); if (~legsup & USBLEGSUP_HC_BIOS && legsup & USBLEGSUP_HC_OS) { break; } } } } // Disable interrupts hc->op_regs->usb_intr = 0; // Setup frame list hc->op_regs->frame_index = 0; hc->op_regs->periodic_list_base = (u32)(uintptr_t)0; hc->op_regs->async_list_addr = (u32)(uintptr_t)hc->async_qh; hc->op_regs->ctrl_ds_segment = 0; // Clear status hc->op_regs->usb_sts = 0x3f; // Enable controller hc->op_regs->usb_cmd = (8 << CMD_ITC_SHIFT) | CMD_ASE | CMD_RS; while (hc->op_regs->usb_sts & STS_HCHALTED) // TODO - remove after dynamic port detection ; // Configure all devices to be managed by the EHCI hc->op_regs->config_flag = 1; pit_wait(5); // TODO - remove after dynamic port detection // Probe devices ehci_probe(hc); // Register controller USB_Controller* controller = (USB_Controller*)vm_alloc(sizeof(USB_Controller)); controller->next = g_usb_controller_list; controller->hc = hc; controller->poll = ehci_controller_poll; g_usb_controller_list = controller; }
// ------------------------------------------------------------------------------------------------ static void ehci_dev_control(USB_Device* dev, USB_Transfer* t) { EHCI_Controller* hc = (EHCI_Controller*)dev->hc; USB_DevReq* req = t->req; // Determine transfer properties uint speed = dev->speed; uint addr = dev->addr; uint max_size = dev->max_packet_size; uint type = req->type; uint len = req->len; // Create queue of transfer descriptors EHCI_TD* td = ehci_alloc_td(hc); if (!td) { return; } EHCI_TD* head = td; EHCI_TD* prev = 0; // Setup packet uint toggle = 0; uint packet_type = USB_PACKET_SETUP; uint packet_size = sizeof(USB_DevReq); ehci_td_init(td, prev, toggle, packet_type, packet_size, req); prev = td; // Data in/out packets packet_type = type & RT_DEV_TO_HOST ? USB_PACKET_IN : USB_PACKET_OUT; u8* it = (u8*)t->data; u8* end = it + len; while (it < end) { td = ehci_alloc_td(hc); if (!td) { return; } toggle ^= 1; packet_size = end - it; if (packet_size > max_size) { packet_size = max_size; } ehci_td_init(td, prev, toggle, packet_type, packet_size, it); it += packet_size; prev = td; } // Status packet td = ehci_alloc_td(hc); if (!td) { return; } toggle = 1; packet_type = type & RT_DEV_TO_HOST ? USB_PACKET_OUT : USB_PACKET_IN; ehci_td_init(td, prev, toggle, packet_type, 0, 0); // Initialize queue head EHCI_QH* qh = ehci_alloc_qh(hc); ehci_qh_init(qh, t, head, dev->parent, false, speed, addr, 0, max_size); // Wait until queue has been processed ehci_insert_qh(hc, qh); ehci_qh_wait(hc, qh); }
/* * ehci_hcdi_pipe_open: * * Member of HCD Ops structure and called during client specific pipe open * Add the pipe to the data structure representing the device and allocate * bandwidth for the pipe if it is a interrupt or isochronous endpoint. */ int ehci_hcdi_pipe_open( usba_pipe_handle_data_t *ph, usb_flags_t flags) { ehci_state_t *ehcip = ehci_obtain_state( ph->p_usba_device->usb_root_hub_dip); usb_ep_descr_t *epdt = &ph->p_ep; int rval, error = USB_SUCCESS; int kmflag = (flags & USB_FLAGS_SLEEP) ? KM_SLEEP : KM_NOSLEEP; uchar_t smask = 0; uchar_t cmask = 0; uint_t pnode = 0; ehci_pipe_private_t *pp; USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_open: addr = 0x%x, ep%d", ph->p_usba_device->usb_addr, epdt->bEndpointAddress & USB_EP_NUM_MASK); mutex_enter(&ehcip->ehci_int_mutex); rval = ehci_state_is_operational(ehcip); mutex_exit(&ehcip->ehci_int_mutex); if (rval != USB_SUCCESS) { return (rval); } /* * Check and handle root hub pipe open. */ if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) { mutex_enter(&ehcip->ehci_int_mutex); error = ehci_handle_root_hub_pipe_open(ph, flags); mutex_exit(&ehcip->ehci_int_mutex); return (error); } /* * Opening of other pipes excluding root hub pipe are * handled below. Check whether pipe is already opened. */ if (ph->p_hcd_private) { USB_DPRINTF_L2(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_open: Pipe is already opened"); return (USB_FAILURE); } /* * A portion of the bandwidth is reserved for the non-periodic * transfers, i.e control and bulk transfers in each of one * millisecond frame period & usually it will be 20% of frame * period. Hence there is no need to check for the available * bandwidth before adding the control or bulk endpoints. * * There is a need to check for the available bandwidth before * adding the periodic transfers, i.e interrupt & isochronous, * since all these periodic transfers are guaranteed transfers. * Usually 80% of the total frame time is reserved for periodic * transfers. */ if (EHCI_PERIODIC_ENDPOINT(epdt)) { mutex_enter(&ehcip->ehci_int_mutex); mutex_enter(&ph->p_mutex); error = ehci_allocate_bandwidth(ehcip, ph, &pnode, &smask, &cmask); if (error != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_open: Bandwidth allocation failed"); mutex_exit(&ph->p_mutex); mutex_exit(&ehcip->ehci_int_mutex); return (error); } mutex_exit(&ph->p_mutex); mutex_exit(&ehcip->ehci_int_mutex); } /* Create the HCD pipe private structure */ pp = kmem_zalloc(sizeof (ehci_pipe_private_t), kmflag); /* * Return failure if ehci pipe private * structure allocation fails. */ if (pp == NULL) { mutex_enter(&ehcip->ehci_int_mutex); /* Deallocate bandwidth */ if (EHCI_PERIODIC_ENDPOINT(epdt)) { mutex_enter(&ph->p_mutex); ehci_deallocate_bandwidth(ehcip, ph, pnode, smask, cmask); mutex_exit(&ph->p_mutex); } mutex_exit(&ehcip->ehci_int_mutex); return (USB_NO_RESOURCES); } mutex_enter(&ehcip->ehci_int_mutex); /* Save periodic nodes */ pp->pp_pnode = pnode; /* Save start and complete split mask values */ pp->pp_smask = smask; pp->pp_cmask = cmask; /* Create prototype for xfer completion condition variable */ cv_init(&pp->pp_xfer_cmpl_cv, NULL, CV_DRIVER, NULL); /* Set the state of pipe as idle */ pp->pp_state = EHCI_PIPE_STATE_IDLE; /* Store a pointer to the pipe handle */ pp->pp_pipe_handle = ph; mutex_enter(&ph->p_mutex); /* Store the pointer in the pipe handle */ ph->p_hcd_private = (usb_opaque_t)pp; /* Store a copy of the pipe policy */ bcopy(&ph->p_policy, &pp->pp_policy, sizeof (usb_pipe_policy_t)); mutex_exit(&ph->p_mutex); /* Allocate the host controller endpoint descriptor */ pp->pp_qh = ehci_alloc_qh(ehcip, ph, NULL); /* Initialize the halting flag */ pp->pp_halt_state = EHCI_HALT_STATE_FREE; /* Create prototype for halt completion condition variable */ cv_init(&pp->pp_halt_cmpl_cv, NULL, CV_DRIVER, NULL); /* Isoch does not use QH, so ignore this */ if ((pp->pp_qh == NULL) && !(EHCI_ISOC_ENDPOINT(epdt))) { ASSERT(pp->pp_qh == NULL); USB_DPRINTF_L2(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_open: QH allocation failed"); mutex_enter(&ph->p_mutex); /* Deallocate bandwidth */ if (EHCI_PERIODIC_ENDPOINT(epdt)) { ehci_deallocate_bandwidth(ehcip, ph, pnode, smask, cmask); } /* Destroy the xfer completion condition variable */ cv_destroy(&pp->pp_xfer_cmpl_cv); /* * Deallocate the hcd private portion * of the pipe handle. */ kmem_free(ph->p_hcd_private, sizeof (ehci_pipe_private_t)); /* * Set the private structure in the * pipe handle equal to NULL. */ ph->p_hcd_private = NULL; mutex_exit(&ph->p_mutex); mutex_exit(&ehcip->ehci_int_mutex); return (USB_NO_RESOURCES); } /* * Isoch does not use QH so no need to * restore data toggle or insert QH */ if (!(EHCI_ISOC_ENDPOINT(epdt))) { /* Restore the data toggle information */ ehci_restore_data_toggle(ehcip, ph); } /* * Insert the endpoint onto the host controller's * appropriate endpoint list. The host controller * will not schedule this endpoint and will not have * any QTD's to process. It will also update the pipe count. */ ehci_insert_qh(ehcip, ph); USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_open: ph = 0x%p", (void *)ph); ehcip->ehci_open_pipe_count++; mutex_exit(&ehcip->ehci_int_mutex); return (USB_SUCCESS); }