/* * PROJECT: ReactOS Universal Serial Bus Bulk Enhanced Host Controller Interface * LICENSE: GPL - See COPYING in the top level directory * FILE: drivers/usb/usbehci/usb_queue.cpp * PURPOSE: USB EHCI device driver. * PROGRAMMERS: * Michael Martin (michael.martin@reactos.org) * Johannes Anderwald (johannes.anderwald@reactos.org) */ #include "usbehci.h" #include "hardware.h" class CUSBQueue : public IUSBQueue { public: STDMETHODIMP QueryInterface( REFIID InterfaceId, PVOID* Interface); STDMETHODIMP_(ULONG) AddRef() { InterlockedIncrement(&m_Ref); return m_Ref; } STDMETHODIMP_(ULONG) Release() { InterlockedDecrement(&m_Ref); if (!m_Ref) { delete this; return 0; } return m_Ref; } NTSTATUS Initialize(IN PUSBHARDWAREDEVICE Hardware, PDMA_ADAPTER AdapterObject, IN OPTIONAL PKSPIN_LOCK Lock); ULONG GetPendingRequestCount(); NTSTATUS AddUSBRequest(PURB Urb); NTSTATUS AddUSBRequest(IUSBRequest * Request); NTSTATUS CancelRequests(); NTSTATUS CreateUSBRequest(IUSBRequest **OutRequest); // constructor / destructor CUSBQueue(IUnknown *OuterUnknown){} virtual ~CUSBQueue(){} protected: LONG m_Ref; KSPIN_LOCK m_Lock; PDMA_ADAPTER m_Adapter; PQUEUE_HEAD AsyncListQueueHead; PQUEUE_HEAD PendingListQueueHead; // queue head manipulation functions VOID LinkQueueHead(PQUEUE_HEAD HeadQueueHead, PQUEUE_HEAD NewQueueHead); VOID UnlinkQueueHead(PQUEUE_HEAD QueueHead); VOID LinkQueueHeadChain(PQUEUE_HEAD HeadQueueHead, PQUEUE_HEAD NewQueueHead); PQUEUE_HEAD UnlinkQueueHeadChain(PQUEUE_HEAD HeadQueueHead, ULONG Count); // called for each completed queue head NTSTATUS QueueHeadCompletion(PQUEUE_HEAD QueueHead, NTSTATUS Status); // called when the completion queue is cleaned up VOID QueueHeadCleanup(PQUEUE_HEAD QueueHead); }; //================================================================================================= // COM // NTSTATUS STDMETHODCALLTYPE CUSBQueue::QueryInterface( IN REFIID refiid, OUT PVOID* Output) { if (IsEqualGUIDAligned(refiid, IID_IUnknown)) { *Output = PVOID(PUNKNOWN(this)); PUNKNOWN(*Output)->AddRef(); return STATUS_SUCCESS; } return STATUS_UNSUCCESSFUL; } NTSTATUS CUSBQueue::Initialize( IN PUSBHARDWAREDEVICE Hardware, PDMA_ADAPTER AdapterObject, IN OPTIONAL PKSPIN_LOCK Lock) { NTSTATUS Status = STATUS_SUCCESS; DPRINT1("CUSBQueue::Initialize()\n"); ASSERT(Hardware); // // initialize device lock // KeInitializeSpinLock(&m_Lock); // // Get the AsyncQueueHead // AsyncListQueueHead = (PQUEUE_HEAD)Hardware->GetAsyncListRegister(); // // Create the PendingListQueueHead from NONPAGEDPOOL. It will never be linked into the Asynclist Schedule // PendingListQueueHead = (PQUEUE_HEAD)ExAllocatePoolWithTag(NonPagedPool, sizeof(QUEUE_HEAD), TAG_USBEHCI); if (!PendingListQueueHead) { DPRINT1("Pool Allocation failed!\n"); return STATUS_INSUFFICIENT_RESOURCES; } // // Initialize the List Head // InitializeListHead(&PendingListQueueHead->LinkedQueueHeads); return Status; } ULONG CUSBQueue::GetPendingRequestCount() { // // Loop through the pending list and iterrate one for each QueueHead that // has a IRP to complete. // return 0; } NTSTATUS CUSBQueue::AddUSBRequest( IUSBRequest * Request) { PQUEUE_HEAD QueueHead; ASSERT(Request != NULL); Request->GetQueueHead(&QueueHead); // // Add it to the pending list // LinkQueueHead(PendingListQueueHead, QueueHead); // // add extra reference which is released when the request is completed // Request->AddRef(); return STATUS_SUCCESS; } NTSTATUS CUSBQueue::AddUSBRequest( PURB Urb) { UNIMPLEMENTED return STATUS_NOT_IMPLEMENTED; } NTSTATUS CUSBQueue::CancelRequests() { UNIMPLEMENTED return STATUS_NOT_IMPLEMENTED; } NTSTATUS CUSBQueue::CreateUSBRequest( IUSBRequest **OutRequest) { PUSBREQUEST UsbRequest; NTSTATUS Status; *OutRequest = NULL; Status = InternalCreateUSBRequest(&UsbRequest); if (NT_SUCCESS(Status)) { *OutRequest = UsbRequest; } return Status; } // // LinkQueueHead - Links one QueueHead to the end of HeadQueueHead list, updating HorizontalLinkPointer. // VOID CUSBQueue::LinkQueueHead( PQUEUE_HEAD HeadQueueHead, PQUEUE_HEAD NewQueueHead) { PQUEUE_HEAD LastQueueHead, NextQueueHead; PLIST_ENTRY Entry; ASSERT(HeadQueueHead); ASSERT(NewQueueHead); // // Link the LIST_ENTRYs // InsertTailList(&HeadQueueHead->LinkedQueueHeads, &NewQueueHead->LinkedQueueHeads); // // Update HLP for Previous QueueHead, which should be the last in list. // Entry = NewQueueHead->LinkedQueueHeads.Blink; LastQueueHead = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads); LastQueueHead->HorizontalLinkPointer = (NewQueueHead->PhysicalAddr | QH_TYPE_QH); // // Update HLP for NewQueueHead to point to next, which should be the HeadQueueHead // Entry = NewQueueHead->LinkedQueueHeads.Flink; NextQueueHead = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads); ASSERT(NextQueueHead == HeadQueueHead); NewQueueHead->HorizontalLinkPointer = NextQueueHead->PhysicalAddr; } // // UnlinkQueueHead - Unlinks one QueueHead, updating HorizontalLinkPointer. // VOID CUSBQueue::UnlinkQueueHead( PQUEUE_HEAD QueueHead) { PQUEUE_HEAD PreviousQH, NextQH; PLIST_ENTRY Entry; Entry = QueueHead->LinkedQueueHeads.Blink; PreviousQH = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads); Entry = QueueHead->LinkedQueueHeads.Flink; NextQH = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads); ASSERT(QueueHead->HorizontalLinkPointer == (NextQH->PhysicalAddr | QH_TYPE_QH)); PreviousQH->HorizontalLinkPointer = NextQH->PhysicalAddr | QH_TYPE_QH; RemoveEntryList(&QueueHead->LinkedQueueHeads); } // // LinkQueueHeadChain - Links a list of QueueHeads to the HeadQueueHead list, updating HorizontalLinkPointer. // VOID CUSBQueue::LinkQueueHeadChain( PQUEUE_HEAD HeadQueueHead, PQUEUE_HEAD NewQueueHead) { PQUEUE_HEAD LastQueueHead; PLIST_ENTRY Entry; ASSERT(HeadQueueHead); ASSERT(NewQueueHead); // // Find the last QueueHead in NewQueueHead // Entry = NewQueueHead->LinkedQueueHeads.Blink; ASSERT(Entry != NewQueueHead->LinkedQueueHeads.Flink); LastQueueHead = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads); // // Set the LinkPointer and Flink // LastQueueHead->HorizontalLinkPointer = HeadQueueHead->PhysicalAddr | QH_TYPE_QH; LastQueueHead->LinkedQueueHeads.Flink = &HeadQueueHead->LinkedQueueHeads; // // Fine the last QueueHead in HeadQueueHead // Entry = HeadQueueHead->LinkedQueueHeads.Blink; HeadQueueHead->LinkedQueueHeads.Blink = &LastQueueHead->LinkedQueueHeads; LastQueueHead = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads); LastQueueHead->LinkedQueueHeads.Flink = &NewQueueHead->LinkedQueueHeads; LastQueueHead->HorizontalLinkPointer = NewQueueHead->PhysicalAddr | QH_TYPE_QH; } // // UnlinkQueueHeadChain - Unlinks a list number of QueueHeads from HeadQueueHead list, updating HorizontalLinkPointer. // returns the chain of QueueHeads removed from HeadQueueHead. // PQUEUE_HEAD CUSBQueue::UnlinkQueueHeadChain( PQUEUE_HEAD HeadQueueHead, ULONG Count) { PQUEUE_HEAD LastQueueHead, FirstQueueHead; PLIST_ENTRY Entry; ULONG Index; // // Find the last QueueHead in NewQueueHead // Entry = &HeadQueueHead->LinkedQueueHeads; FirstQueueHead = CONTAINING_RECORD(Entry->Flink, QUEUE_HEAD, LinkedQueueHeads); for (Index = 0; Index < Count; Index++) { Entry = Entry->Flink; if (Entry == &HeadQueueHead->LinkedQueueHeads) { DPRINT1("Warnnig; Only %d QueueHeads in HeadQueueHead\n", Index); Count = Index + 1; break; } } LastQueueHead = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads); HeadQueueHead->LinkedQueueHeads.Flink = LastQueueHead->LinkedQueueHeads.Flink; if (Count + 1 == Index) { HeadQueueHead->LinkedQueueHeads.Blink = &HeadQueueHead->LinkedQueueHeads; } else HeadQueueHead->LinkedQueueHeads.Blink = LastQueueHead->LinkedQueueHeads.Flink; FirstQueueHead->LinkedQueueHeads.Blink = &LastQueueHead->LinkedQueueHeads; LastQueueHead->LinkedQueueHeads.Flink = &FirstQueueHead->LinkedQueueHeads; LastQueueHead->HorizontalLinkPointer = TERMINATE_POINTER; return FirstQueueHead; } NTSTATUS CUSBQueue::QueueHeadCompletion( PQUEUE_HEAD CurrentQH, NTSTATUS Status) { IUSBRequest *Request; USBD_STATUS UrbStatus; PQUEUE_HEAD NewQueueHead; // // this function is called when a queue head has been completed // PC_ASSERT(CurrentQH->Token.Bits.Active == 0); // // get contained usb request // Request = (IUSBRequest*)CurrentQH->Request; // // sanity check // PC_ASSERT(Request); // // check if the queue head was completed with errors // if (CurrentQH->Token.Bits.Halted) { if (CurrentQH->Token.Bits.DataBufferError) { // // data buffer error // UrbStatus = USBD_STATUS_DATA_BUFFER_ERROR; } else if (CurrentQH->Token.Bits.BabbleDetected) { // // babble detected // UrbStatus = USBD_STATUS_BABBLE_DETECTED; } else { // // stall pid // UrbStatus = USBD_STATUS_STALL_PID; } } else { // // well done ;) // UrbStatus = USBD_STATUS_SUCCESS; } // // notify request that a queue head has been completed // Request->CompletionCallback(Status, UrbStatus, CurrentQH); // // now unlink the queue head // FIXME: implement chained queue heads // UnlinkQueueHead(CurrentQH); // // check if the request is complete // if (Request->IsRequestComplete() == FALSE) { // // request is still in complete // get new queue head // Status = Request->GetQueueHead(&NewQueueHead); // // add to pending list // LinkQueueHead(PendingListQueueHead, NewQueueHead); } else { // // FIXME: put queue head into completed queue head list // } // // done // return STATUS_SUCCESS; } VOID CUSBQueue::QueueHeadCleanup( PQUEUE_HEAD CurrentQH) { IUSBRequest * Request; BOOLEAN ShouldReleaseWhenDone; // // sanity checks // PC_ASSERT(CurrentQH->Token.Bits.Active == 0); PC_ASSERT(CurrentQH->Request); // // get request // Request = (IUSBRequest*)CurrentQH->Request; // // let IUSBRequest free the queue head // Request->FreeQueueHead(CurrentQH); // // check if we should release request when done // ShouldReleaseWhenDone = Request->ShouldReleaseRequestAfterCompletion(); // // release reference when the request was added // Request->Release(); // // check if the operation was asynchronous // if (ShouldReleaseWhenDone) { // // release outstanding reference count // Request->Release(); } // // request is now released // } NTSTATUS CreateUSBQueue( PUSBQUEUE *OutUsbQueue) { PUSBQUEUE This; // // allocate controller // This = new(NonPagedPool, TAG_USBEHCI) CUSBQueue(0); if (!This) { // // failed to allocate // return STATUS_INSUFFICIENT_RESOURCES; } // // add reference count // This->AddRef(); // // return result // *OutUsbQueue = (PUSBQUEUE)This; // // done // return STATUS_SUCCESS; }