/*++ Copyright (c) 1999 Microsoft Corporation :ts=4 Module Name: dblbuff.c Abstract: The module manages double buffered bulk transactions on USB. Environment: kernel mode only Notes: Revision History: 2-1-99 : created --*/ #include "wdm.h" #include "stdarg.h" #include "stdio.h" #include "usbdi.h" #include "hcdi.h" #include "uhcd.h" #if DBG extern ULONG UHCD_XferNoise; #endif VOID UHCD_StartNoDMATransfer( IN PDEVICE_OBJECT DeviceObject, IN PUHCD_ENDPOINT Endpoint ) /*++ Routine Description: starts receiving data for the endpoint Arguments: Return Value: None. --*/ { PUHCD_TD_LIST tDList; ULONG i; HW_DESCRIPTOR_PHYSICAL_ADDRESS physicalAddress; if (Endpoint->EndpointFlags & EPFLAG_NODMA_ON) { // already on, bail LOGENTRY(LOG_MISC, 'dmaO', Endpoint, 0, 0); return; } // current TD is the first TD tDList = Endpoint->SlotTDList[0]; Endpoint->CurrentTDIdx[0] = 0; physicalAddress = Endpoint->NoDMAPhysicalAddress; LOGENTRY(LOG_MISC, 'dmG1', Endpoint, physicalAddress, tDList); for (i=0; i < Endpoint->TDCount; i++) { tDList->TDs[i].Endpoint = Endpoint->EndpointAddress; tDList->TDs[i].Address = Endpoint->DeviceAddress; tDList->TDs[i].HW_Link = tDList->TDs[(i+1) % Endpoint->TDCount].PhysicalAddress; UHCD_InitializeAsyncTD(Endpoint, &tDList->TDs[i]); // for now take an interrupt every TD so we can keep up tDList->TDs[i].InterruptOnComplete = 1; tDList->TDs[i].PID = USB_IN_PID; // set data toggle; tDList->TDs[i].RetryToggle = Endpoint->DataToggle; Endpoint->DataToggle ^=1; // buffer length is packet size tDList->TDs[i].MaxLength = UHCD_SYSTEM_TO_USB_BUFFER_LENGTH(Endpoint->MaxPacketSize); // point TDs at our NoDMA buffer; tDList->TDs[i].PacketBuffer = physicalAddress + 64*i; tDList->TDs[i].ShortPacketDetect = 0; UHCD_KdPrint((2, "'**TD for BULK DBLBUFF packet (%d)\n", i)); UHCD_Debug_DumpTD(&tDList->TDs[i]); } // no T bit the controller will stop at the first in-active TD // mark the endpoint as started SET_EPFLAG(Endpoint, EPFLAG_NODMA_ON); LOGENTRY(LOG_MISC, 'dmG2', Endpoint, Endpoint->QueueHead, 0); // fire up the transfer loop // point QH at the first TD Endpoint->QueueHead->HW_VLink = Endpoint->TDList->TDs[0].PhysicalAddress; } VOID UHCD_StopNoDMATransfer( IN PDEVICE_OBJECT DeviceObject, IN PUHCD_ENDPOINT Endpoint ) /*++ Routine Description: stops receiving data for the endpoint Arguments: Return Value: None. --*/ { CLR_EPFLAG(Endpoint, EPFLAG_NODMA_ON); LOGENTRY(LOG_MISC, 'dmaX', Endpoint, Endpoint->QueueHead, 0); UHCD_KdPrint((0, "'** stopping noDMA transfer\n")); #if DBG if (!IsListEmpty(&Endpoint->PendingTransferList)) { UHCD_KdPrint((0, "'** stopping noDMA w/ pending transfers\n")); } if(Endpoint->ActiveTransfers[0] != NULL) { UHCD_KdPrint((0, "'** stopping noDMA w/ active transfers\n")); } #endif // fixup the TDs point the QH at the first TD // and mark it inactive // note that by the time the ep is actually closed more // than one frame will have elapsed so it will be safe to // remove the QH // preserve the data toglge in case the ep gets another // transfer that starts it again Endpoint->DataToggle = Endpoint->LastPacketDataToggle; Endpoint->TDList->TDs[0].Active = 0; Endpoint->QueueHead->HW_VLink = Endpoint->TDList->TDs[0].PhysicalAddress; // so that only startdmatransfer can activate it SET_T_BIT(Endpoint->QueueHead->HW_VLink); #if 0 // DEBUG ONLY // see if we have any unclaimed data in the buffer { LONG i, cnt = 0; PHW_TRANSFER_DESCRIPTOR transferDescriptor; i = Endpoint->CurrentTDIdx[0]; for (;;) { transferDescriptor = &Endpoint->TDList->TDs[i]; LOGENTRY(LOG_MISC, 'CKtd', i, transferDescriptor, Endpoint->CurrentTDIdx[0]); // // Did this TD complete? // if (transferDescriptor->Active == 0) { cnt++; i = NEXT_TD(i, Endpoint); } else { break; } // see if all TDs were processed if (i == Endpoint->CurrentTDIdx[0]) { break; } } if (cnt) { UHCD_KdPrint((0, "'** abort with %d unprocessed TDs\n", cnt)); TEST_TRAP(); } } #endif } #define RECYCLE_TD(t) \ (t)->ActualLength = 0;\ (t)->Active = 1;\ (t)->StatusField = 0; \ (t)->ErrorCounter = 3; BOOLEAN UHCD_ProcessNoDMATransfer( IN PDEVICE_OBJECT DeviceObject, IN PUHCD_ENDPOINT Endpoint, IN PHCD_URB Urb ) /*++ Routine Description: Arguments: Return Value: True if current active transfer is complete --*/ { BOOLEAN complete = FALSE; USHORT i, thisTD; USBD_STATUS usbdStatus = USBD_STATUS_SUCCESS; PUCHAR tdBuffer; PHW_TRANSFER_DESCRIPTOR transferDescriptor; PDEVICE_EXTENSION deviceExtension; ULONG remain, length; PHCD_EXTENSION urbWork = HCD_AREA(Urb).HcdExtension; BOOLEAN processed = FALSE; deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension; // see if this transfer has been canceled if (Urb->HcdUrbCommonTransfer.Status == UHCD_STATUS_PENDING_XXX) { usbdStatus = USBD_STATUS_CANCELED; LOGENTRY(LOG_MISC, 'TCAN', Endpoint, Urb, usbdStatus); complete = TRUE; goto UHCD_ProcessNoDMATransfer_Done; } // walk through the TD list starting from currentTDIdx // stop as soon as: // we fill the active client buffer OR // we find a short packet OR // we find an unprocessed TD // Start at the last TD that had not completed i = Endpoint->CurrentTDIdx[0]; for (;;) { transferDescriptor = &Endpoint->TDList->TDs[i]; LOGENTRY(LOG_MISC, 'CKtd', i, transferDescriptor, Endpoint->CurrentTDIdx[0]); // // Did this TD complete? // UHCD_KdPrint((2, "'checking DB TD %x\n", transferDescriptor)); UHCD_Debug_DumpTD(transferDescriptor); if (transferDescriptor->Active == 0) { LOG_TD('nACT', (PULONG) transferDescriptor); processed = TRUE; UHCD_KdPrint((2, "'TD %x completed\n", transferDescriptor)); UHCD_Debug_DumpTD(transferDescriptor); Endpoint->LastPacketDataToggle = (UCHAR) transferDescriptor->RetryToggle; // // Yes, TD completed figure out what to do // if (transferDescriptor->StatusField != 0) { // we got an error, map the status code and retire // this transfer // if appropriate the caller will halt transfers // on the endpoint complete = TRUE; usbdStatus = UHCD_MapTDError(deviceExtension, transferDescriptor->StatusField, UHCD_USB_TO_SYSTEM_BUFFER_LENGTH(transferDescriptor->ActualLength)); LOGENTRY(LOG_MISC, 'Stal', Endpoint, transferDescriptor->StatusField, usbdStatus); i = NEXT_TD(i, Endpoint); break; } // // No Error, process the TDs data // // should be an IN UHCD_ASSERT(transferDescriptor->PID == USB_IN_PID); // see how much room is left remain = Urb->HcdUrbCommonTransfer.TransferBufferLength - urbWork->BytesTransferred; length = UHCD_USB_TO_SYSTEM_BUFFER_LENGTH(transferDescriptor->ActualLength); tdBuffer = Endpoint->NoDMABuffer + 64*i; if (length < remain) { LOGENTRY(LOG_MISC, 'lsTD', 0, transferDescriptor, 0); // copy the data to the client buffer RtlCopyMemory((PUCHAR) urbWork->SystemAddressForMdl + urbWork->BytesTransferred, tdBuffer, length); urbWork->BytesTransferred += length; i = NEXT_TD(i, Endpoint); // check for short packet // short packets cause the transfer to complete. if (length < Endpoint->MaxPacketSize) { LOGENTRY(LOG_MISC, 'sHRT', transferDescriptor, transferDescriptor->ActualLength, transferDescriptor->MaxLength); complete = TRUE; break; } } else if (length == remain) { LOGENTRY(LOG_MISC, 'eqTD', 0, transferDescriptor, 0); // this td just fills the client buffer RtlCopyMemory((PUCHAR)urbWork->SystemAddressForMdl + urbWork->BytesTransferred, tdBuffer, length); urbWork->BytesTransferred += length; complete = TRUE; i = NEXT_TD(i, Endpoint); break; } else { TEST_TRAP(); // bugbug this is an odd case, not sure how to handle // it yet. // normally two transfers cannot span a single packet // TD data is bigger than space left in client buffer // copy what we can RtlCopyMemory(urbWork->SystemAddressForMdl, tdBuffer, remain); complete = TRUE; // save the rest of the data for the next pass break; } /* active == 0 */ } else { // this TD still active, all done LOGENTRY(LOG_MISC, 'acBK', Endpoint, transferDescriptor, 0); break; } // see if all TDs were processed if (i == Endpoint->CurrentTDIdx[0]) { UHCD_ASSERT(processed == TRUE); break; } } /* for ;; */ if (processed) { // at least one TD completed thisTD = Endpoint->CurrentTDIdx[0]; UHCD_ASSERT(Endpoint->TDList->TDs[thisTD].Active == 0); RECYCLE_TD(&Endpoint->TDList->TDs[thisTD]); thisTD = NEXT_TD(thisTD, Endpoint); // set currentTDidx to where we left off Endpoint->CurrentTDIdx[0] = i; // recycle the TDs we processed for(;;) { if (thisTD == Endpoint->CurrentTDIdx[0]) { break; } UHCD_ASSERT(Endpoint->TDList->TDs[thisTD].Active == 0); RECYCLE_TD(&Endpoint->TDList->TDs[thisTD]); thisTD = NEXT_TD(thisTD, Endpoint); } } UHCD_KdPrint((2, "'check DB TD done\n")); UHCD_ProcessNoDMATransfer_Done: if (complete) { Urb->HcdUrbCommonTransfer.Status = usbdStatus; } return complete; } NTSTATUS UHCD_InitializeNoDMAEndpoint( IN PDEVICE_OBJECT DeviceObject, IN PUHCD_ENDPOINT Endpoint ) /*++ Routine Description: Set up a No-DMA style (double buffered endpoint) Arguments: Return Value: None. --*/ { NTSTATUS ntStatus = STATUS_SUCCESS; PDEVICE_EXTENSION deviceExtension; ULONG length; UHCD_KdPrint((2, "'Init No DMA Endpoint\n")); deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension; // we always have work SET_EPFLAG(Endpoint, EPFLAG_HAVE_WORK); // // first we need a buffer to receive Data, and TDs to // point in to it // // we should have the max TDs reserved for this endpoint UHCD_ASSERT(Endpoint->TDCount == MAX_TDS_PER_ENDPOINT); // length will be largest USB packet (64) * the MAX tds per // endpoint (ends up being one page on x86) length = 64 * MAX_TDS_PER_ENDPOINT; Endpoint->NoDMABuffer = UHCD_Alloc_NoDMA_Buffer(DeviceObject, Endpoint, length); if (Endpoint->NoDMABuffer == NULL) { ntStatus = STATUS_INSUFFICIENT_RESOURCES; } return ntStatus; } NTSTATUS UHCD_UnInitializeNoDMAEndpoint( IN PDEVICE_OBJECT DeviceObject, IN PUHCD_ENDPOINT Endpoint ) /*++ Routine Description: Set up a No-DMA style (Dbl buffered endpoint) Arguments: Return Value: None. --*/ { NTSTATUS ntStatus = STATUS_SUCCESS; PDEVICE_EXTENSION deviceExtension; LOGENTRY(LOG_MISC, 'unIN', Endpoint, 0, 0); deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension; UHCD_ASSERT(!(Endpoint->EndpointFlags & EPFLAG_NODMA_ON)); if (Endpoint->NoDMABuffer) { UHCD_Free_NoDMA_Buffer(DeviceObject, Endpoint->NoDMABuffer); Endpoint->NoDMABuffer = NULL; } return STATUS_SUCCESS; } VOID UHCD_EndpointNoDMA_Abort( IN PDEVICE_OBJECT DeviceObject, IN PUHCD_ENDPOINT Endpoint ) /*++ Routine Description: Aborts all active and/or pending transfers for a NoDMA endpoint Called at DPC level Arguments: Return Value: None. --*/ { KIRQL irql; //BOOLEAN done = FALSE; PDEVICE_EXTENSION deviceExtension = DeviceObject->DeviceExtension; UHCD_ASSERT(Endpoint->EndpointFlags & (EPFLAG_ABORT_PENDING_TRANSFERS | EPFLAG_ABORT_ACTIVE_TRANSFERS)); if (Endpoint->EndpointFlags & EPFLAG_ABORT_ACTIVE_TRANSFERS) { PHCD_URB urb; urb = Endpoint->ActiveTransfers[0]; LOGENTRY(LOG_MISC, 'ABac', urb, 0, Endpoint); if (urb) { Endpoint->ActiveTransfers[0] = NULL; URB_HEADER(urb).Status = USBD_STATUS_CANCELED; UHCD_CompleteIrp(DeviceObject, HCD_AREA(urb).HcdIrp, STATUS_CANCELLED, 0, urb); CLR_EPFLAG(Endpoint, EPFLAG_ABORT_ACTIVE_TRANSFERS); } } if (Endpoint->EndpointFlags & EPFLAG_ABORT_PENDING_TRANSFERS) { BOOLEAN pendingTransfers = TRUE; LOGENTRY(LOG_MISC, 'ABpd', Endpoint, 0, 0); // dequeue all of our pending requests an complete them with status // canceled while (pendingTransfers) { PHCD_URB urb; PLIST_ENTRY listEntry; LOCK_ENDPOINT_PENDING_LIST(Endpoint, irql, 'lck3'); if (IsListEmpty(&Endpoint->PendingTransferList)) { pendingTransfers = FALSE; // clear the abort flag CLR_EPFLAG(Endpoint, EPFLAG_ABORT_PENDING_TRANSFERS); UNLOCK_ENDPOINT_PENDING_LIST(Endpoint, irql, 'ulk3'); } else { listEntry = RemoveHeadList(&Endpoint->PendingTransferList); urb = (PHCD_URB) CONTAINING_RECORD( listEntry, struct _URB_HCD_COMMON_TRANSFER, hca.HcdListEntry); LOGENTRY(LOG_MISC, 'DQXF', urb, 0, 0); UNLOCK_ENDPOINT_PENDING_LIST(Endpoint, irql, 'ulk3'); URB_HEADER(urb).Status = USBD_STATUS_CANCELED; UHCD_CompleteIrp(DeviceObject, HCD_AREA(urb).HcdIrp, STATUS_CANCELLED, 0, urb); // complete this transfer } } /* while */ } } VOID UHCD_EndpointNoDMAWorker( IN PDEVICE_OBJECT DeviceObject, IN PUHCD_ENDPOINT Endpoint ) /*++ Routine Description: Main worker function for an endpoint that does not require DMA. Note: We should never have transfers in the active list for one of these endooints. Called at DPC level Arguments: Return Value: None. --*/ { ULONG slot; BOOLEAN complete; KIRQL irql; //BOOLEAN done = FALSE; PDEVICE_EXTENSION deviceExtension = DeviceObject->DeviceExtension; //UHCD_KdPrint((2, "'enter UHCD_EndpointNoDMAWorker\n")); // // some asserts // // max request should always be one -- we will manage with just one // set of TDs UHCD_ASSERT(Endpoint->MaxRequests == 1); slot = 0; if (Endpoint->EndpointFlags & (EPFLAG_ABORT_PENDING_TRANSFERS | EPFLAG_ABORT_ACTIVE_TRANSFERS)) { // client has requested abort UHCD_KdPrint((0, "'abort no DMA ep\n")); UHCD_EndpointNoDMA_Abort(DeviceObject, Endpoint); goto UHCD_EndpointNoDMAWorker_Done; } do { // check our pending request list if (IsListEmpty(&Endpoint->PendingTransferList) && Endpoint->ActiveTransfers[slot] == NULL) { // client list is empty, // no more to do for now LOGENTRY(LOG_MISC, 'ndMT', Endpoint, 0, 0); break; } else { USBD_STATUS usbdStatus; PIRP irp; PHCD_URB urb; PHCD_EXTENSION urbWork; // no active transfer // dequeue the next pending one for processing // if the endpoint is not stalled, start up the DMA // loop if not running on the first transfer if (!(Endpoint->EndpointFlags & EPFLAG_HOST_HALTED)) { LOGENTRY(LOG_MISC, 'ndST', Endpoint, 0, 0); UHCD_StartNoDMATransfer(DeviceObject, Endpoint); } if (Endpoint->ActiveTransfers[slot] == NULL) { PLIST_ENTRY listEntry; // dequeue the next transfer LOCK_ENDPOINT_PENDING_LIST(Endpoint, irql, 'lck6'); UHCD_ASSERT(!IsListEmpty(&Endpoint->PendingTransferList)); listEntry = RemoveHeadList(&Endpoint->PendingTransferList); urb = (PHCD_URB) CONTAINING_RECORD( listEntry, struct _URB_HCD_COMMON_TRANSFER, hca.HcdListEntry); LOGENTRY(LOG_MISC, 'ndDQ', Endpoint, urb, 0); UHCD_ASSERT(urb); UNLOCK_ENDPOINT_PENDING_LIST(Endpoint, irql, 'ulk6'); urbWork = HCD_AREA(urb).HcdExtension; UHCD_ASSERT(urbWork); //#if DBG // if (UHCD_XferNoise) { // UHCD_KdPrint((0, "'db xfer len req =%d\n", urb->HcdUrbCommonTransfer.TransferBufferLength)); // } //#endif // init the work area // note: this area is zeroed if (urb->HcdUrbCommonTransfer.TransferBufferLength) { PMDL mdl; mdl = urb->HcdUrbCommonTransfer.TransferBufferMDL; if (!(mdl->MdlFlags & (MDL_MAPPED_TO_SYSTEM_VA | MDL_SOURCE_IS_NONPAGED_POOL))) { urbWork->Flags |= UHCD_MAPPED_LOCKED_PAGES; } urbWork->SystemAddressForMdl = MmGetSystemAddressForMdl(mdl); LOGENTRY(LOG_MISC, 'sMDL', Endpoint, urb, urbWork->SystemAddressForMdl); } else { urbWork->SystemAddressForMdl = NULL; TEST_TRAP(); } urbWork->Flags |= UHCD_TRANSFER_ACTIVE; Endpoint->ActiveTransfers[slot] = urb; } // // so now we have a client transfer in the active slot // UHCD_ASSERT(Endpoint->ActiveTransfers[slot] != NULL); urb = Endpoint->ActiveTransfers[slot]; irp = HCD_AREA(urb).HcdIrp; UHCD_ASSERT(irp); urbWork = HCD_AREA(urb).HcdExtension; LOGENTRY(LOG_MISC, 'ndPR', Endpoint, urb, 0); complete = UHCD_ProcessNoDMATransfer(DeviceObject, Endpoint, urb); if (complete) { usbdStatus = urb->HcdUrbCommonTransfer.Status; // active transfer is complete Endpoint->ActiveTransfers[slot] = NULL; if (USBD_ERROR(usbdStatus)) { if (USBD_HALTED(usbdStatus)) { // // error code indicates a condition that should halt // the endpoint. // // check the endpoint state bit, if the endpoint // is marked for NO_HALT then clear the halt bit // and proceed to cancel this transfer. if (Endpoint->EndpointFlags & EPFLAG_NO_HALT) { // // clear the halt bit on the usbdStatus code // usbdStatus = USBD_STATUS(usbdStatus) | USBD_STATUS_ERROR; } else { // // mark the endpoint as halted, when the client // sends a reset we'll start processing with the // next queued transfer. // SET_EPFLAG(Endpoint, EPFLAG_HOST_HALTED); LOGENTRY(LOG_MISC, 'Hhlt', Endpoint, 0, 0); // stop streaming from the endpoint, // it should be NAKing anyway UHCD_StopNoDMATransfer(DeviceObject, Endpoint); } } // // complete the original request // UHCD_ASSERT(irp != NULL); UHCD_CompleteIrp(DeviceObject, irp, STATUS_SUCCESS, 0, urb); if (Endpoint->EndpointFlags & EPFLAG_HOST_HALTED) { // // if the endpoint is halted then stop the stream now // UHCD_StopNoDMATransfer(DeviceObject, Endpoint); break; } } else { // xfer completed with success urb->HcdUrbCommonTransfer.Status = usbdStatus; urb->HcdUrbCommonTransfer.TransferBufferLength = urbWork->BytesTransferred; //#if DBG // if (UHCD_XferNoise) { // UHCD_KdPrint((0, "'xfer len cpt =%d\n", // urb->HcdUrbCommonTransfer.TransferBufferLength)); // } //#endif UHCD_ASSERT(irp != NULL); UHCD_CompleteIrp(DeviceObject, irp, STATUS_SUCCESS, 0, urb); } } /* xfer complete */ } // cuurent transfer completed, grab the next one and process it } while (complete); UHCD_EndpointNoDMAWorker_Done: return; }