//Create and return an interrupt queue object. struct int_queue* EHCICreateIntQueue(struct usb_device *dev, unsigned long pipe, int queuesize, int elementsize, void *buffer, int interval) { struct ehci_ctrl *ctrl = ehci_get_ctrl(dev); struct int_queue *result = NULL; uint32_t i, toggle; struct QH *list = NULL; int cmd = 0; DWORD dwFlags; /* * Interrupt transfers requiring several transactions are not supported * because bInterval is ignored. * * Also, ehci_submit_async() relies on wMaxPacketSize being a power of 2 * <= PKT_ALIGN if several qTDs are required, while the USB * specification does not constrain this for interrupt transfers. That * means that ehci_submit_async() would support interrupt transfers * requiring several transactions only as long as the transfer size does * not require more than a single qTD. */ if (elementsize > usb_maxpacket(dev, pipe)) { printf("%s: xfers requiring several transactions are not supported.\r\n", "_ehci_create_int_queue"); return NULL; } if (usb_pipetype(pipe) != PIPE_INTERRUPT) { debug("non-interrupt pipe (type=%lu)", usb_pipetype(pipe)); return NULL; } /* limit to 4 full pages worth of data - * we can safely fit them in a single TD, * no matter the alignment */ if (elementsize >= 16384) { debug("too large elements for interrupt transfers\r\n"); return NULL; } result = malloc(sizeof(*result)); if (!result) { debug("ehci intr queue: out of memory\r\n"); goto fail1; } //Create EVENT object to synchronizing the access. result->hEvent = CreateEvent(FALSE); if (NULL == result->hEvent) { goto fail1; } result->dwTimeOut = 0; result->pNext = NULL; result->pOwnerThread = KernelThreadManager.lpCurrentKernelThread; result->QueueIntHandler = _ehciQueueIntHandler; result->pUsbDev = dev; result->dwStatus = INT_QUEUE_STATUS_INITIALIZED; result->elementsize = elementsize; result->pipe = pipe; result->first = memalign(USB_DMA_MINALIGN, sizeof(struct QH) * queuesize); if (!result->first) { debug("ehci intr queue: out of memory\r\n"); goto fail2; } debug("%s: Allocate %d QH(s) at %X.\r\n", __func__,queuesize,result->first); result->current = result->first; result->last = result->first + queuesize - 1; result->tds = memalign(USB_DMA_MINALIGN, sizeof(struct qTD) * queuesize); if (!result->tds) { debug("ehci intr queue: out of memory\r\n"); goto fail3; } debug("%s: Allocate %d qTD(s) at %X.\r\n", __func__,queuesize, result->tds); memset(result->first, 0, sizeof(struct QH) * queuesize); memset(result->tds, 0, sizeof(struct qTD) * queuesize); toggle = usb_gettoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe)); for (i = 0; i < (uint32_t)queuesize; i++) { struct QH *qh = result->first + i; struct qTD *td = result->tds + i; void **buf = &qh->buffer; qh->qh_link = cpu_to_hc32((unsigned long)(qh + 1) | QH_LINK_TYPE_QH); if (i == queuesize - 1) qh->qh_link = cpu_to_hc32(QH_LINK_TERMINATE); qh->qh_overlay.qt_next = cpu_to_hc32((unsigned long)td); qh->qh_overlay.qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE); qh->qh_endpt1 = cpu_to_hc32((0 << 28) | /* No NAK reload (ehci 4.9) */ (usb_maxpacket(dev, pipe) << 16) | /* MPS */ (1 << 14) | QH_ENDPT1_EPS(ehci_encode_speed(dev->speed)) | (usb_pipeendpoint(pipe) << 8) | /* Endpoint Number */ (usb_pipedevice(pipe) << 0)); qh->qh_endpt2 = cpu_to_hc32((1 << 30) | /* 1 Tx per mframe */ (1 << 0)); /* S-mask: microframe 0 */ if (dev->speed == USB_SPEED_LOW || dev->speed == USB_SPEED_FULL) { /* C-mask: microframes 2-4 */ qh->qh_endpt2 |= cpu_to_hc32((0x1c << 8)); } ehci_update_endpt2_dev_n_port(dev, qh); td->qt_next = cpu_to_hc32(QT_NEXT_TERMINATE); td->qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE); debug("%s: communication direction is '%s'\r\n", __func__, usb_pipein(pipe) ? "in" : "out"); if (i == queuesize - 1) //Last one,set IoC bit. { td->qt_token = cpu_to_hc32( QT_TOKEN_DT(toggle) | (elementsize << 16) | (1 << 15) | //Interrupt On Completion. (3 << 10) | //CERR bits. ((usb_pipein(pipe) ? 1 : 0) << 8) | /* IN/OUT token */ 0x80); /* active */ } else { td->qt_token = cpu_to_hc32( QT_TOKEN_DT(toggle) | (elementsize << 16) | (3 << 10) | //CERR bits. ((usb_pipein(pipe) ? 1 : 0) << 8) | /* IN/OUT token */ 0x80); /* active */ } debug("%s: construct TD token = %X.\r\n", __func__, td->qt_token); td->qt_buffer[0] = cpu_to_hc32((unsigned long)buffer + i * elementsize); td->qt_buffer[1] = cpu_to_hc32((td->qt_buffer[0] + 0x1000) & ~0xfff); td->qt_buffer[2] = cpu_to_hc32((td->qt_buffer[0] + 0x2000) & ~0xfff); td->qt_buffer[3] = cpu_to_hc32((td->qt_buffer[0] + 0x3000) & ~0xfff); td->qt_buffer[4] = cpu_to_hc32((td->qt_buffer[0] + 0x4000) & ~0xfff); #ifdef __MS_VC__ //MS VC can not support sizeof(void) operation,we should //convert the buffer type to char*. *buf = (void*)((char*)buffer + i * elementsize); #else //sizeof(void) is 1 under GCC or other environment,so the //following sentence is same as above one. *buf = buffer + i * elementsize; #endif toggle ^= 1; } flush_dcache_range((unsigned long)buffer, ALIGN_END_ADDR(char, buffer, queuesize * elementsize)); flush_dcache_range((unsigned long)result->first, ALIGN_END_ADDR(struct QH, result->first, queuesize)); flush_dcache_range((unsigned long)result->tds, ALIGN_END_ADDR(struct qTD, result->tds, queuesize)); //Acquire exclusively accessing of the controller. WaitForThisObject(ctrl->hMutex); if (ctrl->periodic_schedules > 0) { if (ehci_disable_periodic(ctrl) < 0) { ReleaseMutex(ctrl->hMutex); _hx_printf("FATAL %s: periodic should never fail, but did.\r\n",__func__); goto fail3; } } __ENTER_CRITICAL_SECTION(NULL, dwFlags); /* hook up to periodic list */ list = &ctrl->periodic_queue; result->last->qh_link = list->qh_link; list->qh_link = cpu_to_hc32((unsigned long)result->first | QH_LINK_TYPE_QH); //Link interrupt queue to Controller's pending queue. if (NULL == ctrl->pIntQueueFirst) { ctrl->pIntQueueFirst = result; ctrl->pIntQueueLast = result; } else { result->pNext = ctrl->pIntQueueFirst; ctrl->pIntQueueFirst = result; } __LEAVE_CRITICAL_SECTION(NULL, dwFlags); flush_dcache_range((unsigned long)result->last, ALIGN_END_ADDR(struct QH, result->last, 1)); flush_dcache_range((unsigned long)list, ALIGN_END_ADDR(struct QH, list, 1)); if (ehci_enable_periodic(ctrl) < 0) { ReleaseMutex(ctrl->hMutex); _hx_printf("FATAL %s: periodic should never fail, but did.\r\n", __func__);; goto fail3; } ctrl->periodic_schedules++; ReleaseMutex(ctrl->hMutex); debug("Exit create_int_queue\r\n"); return result; fail3: if (result->tds) free(result->tds); fail2: if (result->first) free(result->first); //if (result) // free(result); fail1: if (result) { if (NULL != result->hEvent) { DestroyEvent(result->hEvent); } free(result); } return NULL; }
/* Do not free buffers associated with QHs, they're owned by someone else */ int EHCIDestroyIntQueue(struct usb_device *dev,struct int_queue *queue) { struct ehci_ctrl *ctrl = ehci_get_ctrl(dev); int result = -1; unsigned long timeout; struct QH *cur = NULL; DWORD dwFlags; struct int_queue* before = NULL, *current = NULL; //Remove it from the controller's pending list if it is in.It's a bit complicated since //the pending list is a one direction link list,and we also are not sure if the queue is //in list. __ENTER_CRITICAL_SECTION(NULL, dwFlags); if (NULL != ctrl->pIntQueueFirst) { if (queue == ctrl->pIntQueueFirst) { if (queue == ctrl->pIntQueueLast) { ctrl->pIntQueueFirst = NULL; ctrl->pIntQueueLast = NULL; } else { ctrl->pIntQueueFirst = queue->pNext; queue->pNext = NULL; } } else { before = ctrl->pIntQueueFirst; current = before->pNext; while (current && (current != queue)) { before = current; current = current->pNext; } if (queue == current) //Find the queue in list. { before->pNext = current->pNext; queue->pNext = NULL; if (NULL == current->pNext) //Last one. { ctrl->pIntQueueLast = before; } } } } __LEAVE_CRITICAL_SECTION(NULL, dwFlags); if (NULL != queue->pNext) { BUG(); } WaitForThisObject(ctrl->hMutex); if (ehci_disable_periodic(ctrl) < 0) { ReleaseMutex(ctrl->hMutex); debug("FATAL: periodic should never fail, but did"); goto out; } ctrl->periodic_schedules--; cur = &ctrl->periodic_queue; timeout = get_timer(0) + (500 / SYSTEM_TIME_SLICE); /* abort after 500ms */ while (!(cur->qh_link & cpu_to_hc32(QH_LINK_TERMINATE))) { debug("considering %p, with qh_link %x\r\n", cur, cur->qh_link); if (NEXT_QH(cur) == queue->first) { debug("found candidate. removing from chain\r\n"); cur->qh_link = queue->last->qh_link; flush_dcache_range((unsigned long)cur, ALIGN_END_ADDR(struct QH, cur, 1)); result = 0; break; } cur = NEXT_QH(cur); if (get_timer(0) > timeout) { ReleaseMutex(ctrl->hMutex); _hx_printf("Timeout destroying interrupt endpoint queue\r\n"); result = -1; goto out; } }
//Create a USB isochronous transfer descriptor and install it into system. __USB_ISO_DESCRIPTOR* usbCreateISODescriptor(__PHYSICAL_DEVICE* pPhyDev, int direction, int bandwidth, char* buffer, int bufflength, __U8 endpoint,__U16 maxPacketSize,__U8 multi) { __USB_ISO_DESCRIPTOR* pIsoDesc = NULL; BOOL bResult = FALSE; struct usb_device* pUsbDev = NULL; struct iTD* pitd = NULL; __COMMON_USB_CONTROLLER* pCtrl = NULL; struct ehci_ctrl* pEhciCtrl = NULL; int maxXferSize = 0, transact_leng = 0; int slot_num = 0; //How many slot the iTD should link to. int slot_space = 0; //The slot number between 2 iTDs in periodic list. int i = 0; char* buff_ptr = buffer; DWORD dwFlags; //Parameters check. if ((NULL == pPhyDev) || (NULL == buffer) || (0 == bufflength) || (0 == endpoint)) { goto __TERMINAL; } if ((bandwidth > EHCI_ISO_MAX_BANDWIDTH) || (0 == multi)) { goto __TERMINAL; } if (maxPacketSize > 1024) //EHCI spec. { goto __TERMINAL; } if ((direction != USB_TRANSFER_DIR_IN) && (direction != USB_TRANSFER_DIR_OUT)) { goto __TERMINAL; } pUsbDev = (struct usb_device*)pPhyDev->lpPrivateInfo; if (NULL == pUsbDev) { BUG(); } pCtrl = (__COMMON_USB_CONTROLLER*)pUsbDev->controller; pEhciCtrl = (struct ehci_ctrl*)pCtrl->pUsbCtrl; //8 transactions in one iTD descriptor. maxXferSize = maxPacketSize * multi * 8; if (bufflength > (int)maxXferSize) { goto __TERMINAL; } //Allocate a ISOxfer descriptor and initialize it accordingly. pIsoDesc = _hx_malloc(sizeof(__USB_ISO_DESCRIPTOR)); if (NULL == pIsoDesc) { goto __TERMINAL; } memset(pIsoDesc, 0, sizeof(__USB_ISO_DESCRIPTOR)); pIsoDesc->bandwidth = bandwidth; pIsoDesc->buffer = buffer; pIsoDesc->bufflength = bufflength; pIsoDesc->direction = direction; pIsoDesc->ISOXferIntHandler = ISOXferIntHandler; pIsoDesc->itdArray = NULL; //Will be initialized later. pIsoDesc->itdnumber = 0; //Will be initialized later. pIsoDesc->pCtrl = (__COMMON_USB_CONTROLLER*)pUsbDev->controller; pIsoDesc->hEvent = NULL; pIsoDesc->endpoint = endpoint; pIsoDesc->maxPacketSize = maxPacketSize; pIsoDesc->multi = multi; pIsoDesc->pNext = NULL; pIsoDesc->pPhyDev = pPhyDev; pIsoDesc->status = USB_ISODESC_STATUS_INITIALIZED; pIsoDesc->hEvent = CreateEvent(FALSE); if (NULL == pIsoDesc) { goto __TERMINAL; } //Create iTD and initialize it. pitd = (struct iTD*)_hx_aligned_malloc(sizeof(struct iTD), 32); if (NULL == pitd) { goto __TERMINAL; } memset(pitd, 0, sizeof(struct iTD)); pitd->lp_next |= ITD_NEXT_TERMINATE; //Terminate bit. pitd->lp_next |= ITD_NEXT_TYPE_SET(ITD_NEXT_TYPE_ITD); //Type as iTD. //pitd->transaction[0] |= ITD_TRANS_XLEN_SET(bufflength); //xlength. //pitd->transaction[0] |= ITD_TRANS_IOC_SET(1); //Set IOC bit. //pitd->transaction[0] |= ITD_TRANS_PG_SET(0); //Page 0. //pitd->transaction[0] |= ITD_TRANS_XOFFSET_SET(buffer); //Buffer offset. //pitd->pg_pointer[0] |= ITD_PGPTR_SET(buffer); //Buffer page pointer. pitd->pg_pointer[0] |= ITD_ENDPOINT_SET(endpoint); //Endpoint. pitd->pg_pointer[0] |= pUsbDev->devnum; //Device address. //Set transfer direction. if (USB_TRANSFER_DIR_IN == direction) { pitd->pg_pointer[1] |= ITD_XFERDIR_SET(1); } else { pitd->pg_pointer[1] |= ITD_XFERDIR_SET(0); } pitd->pg_pointer[1] |= ITD_MAX_PKTSZ_SET(maxPacketSize); //Max packet size. pitd->pg_pointer[2] |= ITD_MULTI_SET(multi); //multiple per (micro)frame. //Fill iTD's transaction(s) accordingly. __Fill_iTD(pIsoDesc,pitd,direction,buffer,bufflength,endpoint,maxPacketSize,multi); #if 0 //Initializes data buffer offset and pointer one by one. i = 0; buff_ptr = buffer; maxXferSize = bufflength; transact_leng = (maxPacketSize * multi > maxXferSize) ? maxXferSize : (maxPacketSize * multi); while (maxXferSize) { pitd->transaction[i] |= ITD_TRANS_XLEN_SET(bufflength); //xlength. pitd->transaction[i] |= ITD_TRANS_PG_SET(i); //Page 0. pitd->transaction[i] |= ITD_TRANS_XOFFSET_SET(buffer); //Buffer offset. pitd->pg_pointer[i] |= ITD_PGPTR_SET(buffer); //Buffer page pointer. } #endif #ifdef __DEBUG_USB_ISO _hx_printf("iso_iTD:lp_next = 0x%X,trans[0] = 0x%X,pg_ptr[0] = 0x%X,pg_ptr[1] = 0x%X,pg_ptr[2] = 0x%X.\r\n", pitd->lp_next, pitd->transaction[0], pitd->pg_pointer[0], pitd->pg_pointer[1],pitd->pg_pointer[2]); #endif flush_dcache_range((unsigned long)pitd, ALIGN_END_ADDR(struct iTD, pitd, 1)); //Link the iTD to isochronous transfer descriptor. pIsoDesc->itdArray = pitd; pIsoDesc->itdnumber = 1; //Calculate how many periodic list slot shall we use. slot_num = bandwidth / 8; if (slot_num < bufflength) { slot_num = 1; } else { slot_num = (0 == slot_num % bufflength) ? (slot_num / bufflength) : (slot_num / bufflength + 1); } if (slot_num > USB_PERIODIC_LIST_LENGTH) //Paameter checking makes sure this can not happen. { _hx_printf("%s:too many slot number[bw = %d,buff_len = %d,slot_num = %d.\r\n", __func__, bandwidth, bufflength, slot_num); goto __TERMINAL; } //Save the slot_num to descriptor,since it will be used in usbDestroyISODescriptor routine. pIsoDesc->slot_num = slot_num; slot_space = USB_PERIODIC_LIST_LENGTH / slot_num; if (0 == slot_space) { slot_space = 1; } #ifdef __DEBUG_USB_ISO _hx_printf("slot_num:%d,slot_space:%d.\r\n", slot_num, slot_space); #endif //Stop periodic schedule if enabled already. WaitForThisObject(pEhciCtrl->hMutex); if (pEhciCtrl->periodic_schedules > 0) { if (ehci_disable_periodic(pEhciCtrl) < 0) { ReleaseMutex(pEhciCtrl->hMutex); _hx_printf("FATAL %s: periodic should never fail, but did.\r\n", __func__); goto __TERMINAL; } } //Insert the iTD to periodic list,and insert the ISOXfer descriptor into EHCI controller's //list. i = 0; __ENTER_CRITICAL_SECTION(NULL, dwFlags); while (slot_num) { pitd->lp_next = pEhciCtrl->periodic_list[i]; pEhciCtrl->periodic_list[i] = ((__U32)pitd | QH_LINK_TYPE_ITD); flush_dcache_range((unsigned long)&pEhciCtrl->periodic_list[i], ALIGN_END_ADDR(uint32_t, &pEhciCtrl->periodic_list[i], 1)); i += slot_space; //Re-calculate the space between slot to make sure the iTD can be linked into //periodic list as scatterly as possible. slot_space = (USB_PERIODIC_LIST_LENGTH - i) / slot_num; if (0 == slot_space) { slot_space = 1; } slot_num--; } //Insert it into global list. if (NULL == pEhciCtrl->pIsoDescFirst) //First element. { pEhciCtrl->pIsoDescFirst = pIsoDesc; pEhciCtrl->pIsoDescLast = pIsoDesc; } else //Put it at last. { //We only support one isochronous xfer at the sametime for simpicity. BUG(); if (NULL == pEhciCtrl->pIsoDescLast) { BUG(); } pEhciCtrl->pIsoDescLast->pNext = pIsoDesc; pEhciCtrl->pIsoDescLast = pIsoDesc; } __LEAVE_CRITICAL_SECTION(NULL, dwFlags); //Restart the periodic schedule if already enabled. if (pEhciCtrl->periodic_schedules > 0) { ehci_enable_periodic(pEhciCtrl); } ReleaseMutex(pEhciCtrl->hMutex); bResult = TRUE; __TERMINAL: if (!bResult) { if (pIsoDesc) //Should release it. { if (pIsoDesc->hEvent) { DestroyEvent(pIsoDesc->hEvent); } _hx_free(pIsoDesc); } if (pitd) { _hx_free(pitd); } pIsoDesc = NULL; //Mark as failed. } return pIsoDesc; }