2 * PROJECT: ReactOS Universal Serial Bus Bulk Enhanced Host Controller Interface
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: drivers/usb/usbehci/usb_queue.cpp
5 * PURPOSE: USB EHCI device driver.
7 * Michael Martin (michael.martin@reactos.org)
8 * Johannes Anderwald (johannes.anderwald@reactos.org)
14 class CUSBQueue
: public IUSBQueue
17 STDMETHODIMP
QueryInterface( REFIID InterfaceId
, PVOID
* Interface
);
19 STDMETHODIMP_(ULONG
) AddRef()
21 InterlockedIncrement(&m_Ref
);
24 STDMETHODIMP_(ULONG
) Release()
26 InterlockedDecrement(&m_Ref
);
36 virtual NTSTATUS
Initialize(IN PUSBHARDWAREDEVICE Hardware
, PDMA_ADAPTER AdapterObject
, IN PDMAMEMORYMANAGER MemManager
, IN OPTIONAL PKSPIN_LOCK Lock
);
37 virtual ULONG
GetPendingRequestCount();
38 virtual NTSTATUS
AddUSBRequest(PURB Urb
);
39 virtual NTSTATUS
AddUSBRequest(IUSBRequest
* Request
);
40 virtual NTSTATUS
CancelRequests();
41 virtual NTSTATUS
CreateUSBRequest(IUSBRequest
**OutRequest
);
42 virtual VOID
InterruptCallback(IN NTSTATUS Status
, OUT PULONG ShouldRingDoorBell
);
43 virtual VOID
CompleteAsyncRequests();
45 // constructor / destructor
46 CUSBQueue(IUnknown
*OuterUnknown
){}
47 virtual ~CUSBQueue(){}
50 LONG m_Ref
; // reference count
51 KSPIN_LOCK m_Lock
; // list lock
52 PDMA_ADAPTER m_Adapter
; // dma adapter
53 PUSBHARDWAREDEVICE m_Hardware
; // stores hardware object
54 PQUEUE_HEAD AsyncListQueueHead
; // async queue head
55 LIST_ENTRY m_CompletedRequestAsyncList
; // completed async request list
56 LIST_ENTRY m_PendingRequestAsyncList
; // pending async request list
57 ULONG m_MaxPeriodicListEntries
; // max perdiodic list entries
58 ULONG m_MaxPollingInterval
; // max polling interval
59 PHYSICAL_ADDRESS m_SyncFrameListAddr
; // physical address of sync frame list
60 PULONG m_SyncFrameList
; // virtual address of sync frame list
61 PQUEUE_HEAD
* m_SyncFrameListQueueHeads
; // stores the frame list of queue head
63 // queue head manipulation functions
64 VOID
LinkQueueHead(PQUEUE_HEAD HeadQueueHead
, PQUEUE_HEAD NewQueueHead
);
65 VOID
UnlinkQueueHead(PQUEUE_HEAD QueueHead
);
66 VOID
LinkQueueHeadChain(PQUEUE_HEAD HeadQueueHead
, PQUEUE_HEAD NewQueueHead
);
67 PQUEUE_HEAD
UnlinkQueueHeadChain(PQUEUE_HEAD HeadQueueHead
, ULONG Count
);
69 // processes the async list
70 VOID
ProcessAsyncList(IN NTSTATUS Status
, OUT PULONG ShouldRingDoorBell
);
72 // called for each completed queue head
73 VOID
QueueHeadCompletion(PQUEUE_HEAD QueueHead
, NTSTATUS Status
);
75 // called when the completion queue is cleaned up
76 VOID
QueueHeadCleanup(PQUEUE_HEAD QueueHead
);
78 // intializes the sync schedule
79 NTSTATUS
InitializeSyncSchedule(IN PUSBHARDWAREDEVICE Hardware
, IN PDMAMEMORYMANAGER MemManager
);
82 //=================================================================================================
87 CUSBQueue::QueryInterface(
91 if (IsEqualGUIDAligned(refiid
, IID_IUnknown
))
93 *Output
= PVOID(PUNKNOWN(this));
94 PUNKNOWN(*Output
)->AddRef();
95 return STATUS_SUCCESS
;
98 return STATUS_UNSUCCESSFUL
;
102 CUSBQueue::Initialize(
103 IN PUSBHARDWAREDEVICE Hardware
,
104 IN PDMA_ADAPTER AdapterObject
,
105 IN PDMAMEMORYMANAGER MemManager
,
106 IN OPTIONAL PKSPIN_LOCK Lock
)
108 NTSTATUS Status
= STATUS_SUCCESS
;
110 DPRINT("CUSBQueue::Initialize()\n");
115 // initialize device lock
117 KeInitializeSpinLock(&m_Lock
);
120 // Get the AsyncQueueHead
122 AsyncListQueueHead
= (PQUEUE_HEAD
)Hardware
->GetAsyncListQueueHead();
125 // Initialize the List Head
127 InitializeListHead(&AsyncListQueueHead
->LinkedQueueHeads
);
130 // Initialize completed async list head
132 InitializeListHead(&m_CompletedRequestAsyncList
);
135 // Initialize pending async list head
137 InitializeListHead(&m_PendingRequestAsyncList
);
140 // now initialize sync schedule
142 Status
= InitializeSyncSchedule(Hardware
, MemManager
);
148 CUSBQueue::InitializeSyncSchedule(
149 IN PUSBHARDWAREDEVICE Hardware
,
150 IN PDMAMEMORYMANAGER MemManager
)
152 PHYSICAL_ADDRESS QueueHeadPhysAddr
;
155 PQUEUE_HEAD QueueHead
;
158 // FIXME: check if smaller list sizes are supported
160 m_MaxPeriodicListEntries
= 1024;
163 // use polling scheme of 32ms
165 m_MaxPollingInterval
= 32;
168 // allocate dummy frame list array
170 m_SyncFrameListQueueHeads
= (PQUEUE_HEAD
*)ExAllocatePool(NonPagedPool
, m_MaxPollingInterval
* sizeof(PQUEUE_HEAD
));
171 if (!m_SyncFrameListQueueHeads
)
176 return STATUS_INSUFFICIENT_RESOURCES
;
181 // first allocate a page to hold the queue array
183 Status
= MemManager
->Allocate(m_MaxPeriodicListEntries
* sizeof(PVOID
), (PVOID
*)&m_SyncFrameList
, &m_SyncFrameListAddr
);
184 if (!NT_SUCCESS(Status
))
187 // failed to allocate sync frame list array
189 DPRINT1("Failed to allocate sync frame list\n");
190 ExFreePool(m_SyncFrameListQueueHeads
);
192 return STATUS_INSUFFICIENT_RESOURCES
;
196 // now allocate queue head descriptors for the polling interval
198 for(Index
= 0; Index
< m_MaxPeriodicListEntries
; Index
++)
201 // check if is inside our polling interrupt frequency window
203 if (Index
< m_MaxPollingInterval
)
206 // allocate queue head
208 Status
= MemManager
->Allocate(sizeof(QUEUE_HEAD
), (PVOID
*)&QueueHead
, &QueueHeadPhysAddr
);
211 // initialize queue head
213 QueueHead
->HorizontalLinkPointer
= TERMINATE_POINTER
;
214 QueueHead
->AlternateNextPointer
= TERMINATE_POINTER
;
215 QueueHead
->NextPointer
= TERMINATE_POINTER
;
218 // 1 for non high speed, 0 for high speed device
220 QueueHead
->EndPointCharacteristics
.ControlEndPointFlag
= 0;
221 QueueHead
->EndPointCharacteristics
.HeadOfReclamation
= FALSE
;
222 QueueHead
->EndPointCharacteristics
.MaximumPacketLength
= 64;
225 // Set NakCountReload to max value possible
227 QueueHead
->EndPointCharacteristics
.NakCountReload
= 0xF;
230 // Get the Initial Data Toggle from the QEDT
232 QueueHead
->EndPointCharacteristics
.QEDTDataToggleControl
= FALSE
;
235 // FIXME: check if High Speed Device
237 QueueHead
->EndPointCharacteristics
.EndPointSpeed
= QH_ENDPOINT_HIGHSPEED
;
238 QueueHead
->EndPointCapabilities
.NumberOfTransactionPerFrame
= 0x03;
239 QueueHead
->Token
.DWord
= 0;
240 QueueHead
->Token
.Bits
.InterruptOnComplete
= FALSE
;
241 QueueHead
->PhysicalAddr
= QueueHeadPhysAddr
.LowPart
;
245 // store in queue head array
247 m_SyncFrameListQueueHeads
[Index
] = QueueHead
;
254 QueueHead
= m_SyncFrameListQueueHeads
[m_MaxPeriodicListEntries
% m_MaxPollingInterval
];
260 m_SyncFrameList
[Index
] = (QueueHead
->PhysicalAddr
| 0x2);
264 // now set the sync base
266 Hardware
->SetPeriodicListRegister(m_SyncFrameListAddr
.LowPart
);
269 // sync frame list initialized
271 return STATUS_SUCCESS
;
275 CUSBQueue::GetPendingRequestCount()
278 // Loop through the pending list and iterrate one for each QueueHead that
279 // has a IRP to complete.
286 CUSBQueue::AddUSBRequest(
287 IUSBRequest
* Request
)
289 PQUEUE_HEAD QueueHead
;
297 ASSERT(Request
!= NULL
);
302 Type
= Request
->GetTransferType();
305 // check if supported
309 case USB_ENDPOINT_TYPE_ISOCHRONOUS
:
310 case USB_ENDPOINT_TYPE_INTERRUPT
:
311 /* NOT IMPLEMENTED IN QUEUE */
312 Status
= STATUS_NOT_SUPPORTED
;
314 case USB_ENDPOINT_TYPE_BULK
:
315 case USB_ENDPOINT_TYPE_CONTROL
:
316 Status
= STATUS_SUCCESS
;
321 Status
= STATUS_NOT_SUPPORTED
;
327 if (!NT_SUCCESS(Status
))
330 // request not supported, please try later
335 if (Type
== USB_ENDPOINT_TYPE_BULK
|| Type
== USB_ENDPOINT_TYPE_CONTROL
)
340 Status
= Request
->GetQueueHead(&QueueHead
);
345 if (!NT_SUCCESS(Status
))
348 // failed to get queue head
353 DPRINT("Request %p QueueHead %p inserted into AsyncQueue\n", Request
, QueueHead
);
356 // Add it to the pending list
358 KeAcquireSpinLock(&m_Lock
, &OldLevel
);
359 LinkQueueHead(AsyncListQueueHead
, QueueHead
);
360 KeReleaseSpinLock(&m_Lock
, OldLevel
);
365 // add extra reference which is released when the request is completed
370 return STATUS_SUCCESS
;
374 CUSBQueue::AddUSBRequest(
378 return STATUS_NOT_IMPLEMENTED
;
382 CUSBQueue::CancelRequests()
385 return STATUS_NOT_IMPLEMENTED
;
389 CUSBQueue::CreateUSBRequest(
390 IUSBRequest
**OutRequest
)
392 PUSBREQUEST UsbRequest
;
396 Status
= InternalCreateUSBRequest(&UsbRequest
);
398 if (NT_SUCCESS(Status
))
400 *OutRequest
= UsbRequest
;
407 // LinkQueueHead - Links one QueueHead to the end of HeadQueueHead list, updating HorizontalLinkPointer.
410 CUSBQueue::LinkQueueHead(
411 PQUEUE_HEAD HeadQueueHead
,
412 PQUEUE_HEAD NewQueueHead
)
414 PQUEUE_HEAD LastQueueHead
, NextQueueHead
;
416 ASSERT(HeadQueueHead
);
417 ASSERT(NewQueueHead
);
420 // Link the LIST_ENTRYs
422 InsertTailList(&HeadQueueHead
->LinkedQueueHeads
, &NewQueueHead
->LinkedQueueHeads
);
425 // Update HLP for Previous QueueHead, which should be the last in list.
427 Entry
= NewQueueHead
->LinkedQueueHeads
.Blink
;
428 LastQueueHead
= CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
429 LastQueueHead
->HorizontalLinkPointer
= (NewQueueHead
->PhysicalAddr
| QH_TYPE_QH
);
432 // Update HLP for NewQueueHead to point to next, which should be the HeadQueueHead
434 Entry
= NewQueueHead
->LinkedQueueHeads
.Flink
;
435 NextQueueHead
= CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
436 ASSERT(NextQueueHead
== HeadQueueHead
);
437 NewQueueHead
->HorizontalLinkPointer
= (NextQueueHead
->PhysicalAddr
| QH_TYPE_QH
);
441 // UnlinkQueueHead - Unlinks one QueueHead, updating HorizontalLinkPointer.
444 CUSBQueue::UnlinkQueueHead(
445 PQUEUE_HEAD QueueHead
)
447 PQUEUE_HEAD PreviousQH
, NextQH
;
451 // sanity check: there must be at least one queue head with halted bit set
453 PC_ASSERT(QueueHead
->Token
.Bits
.Halted
== 0);
458 Entry
= QueueHead
->LinkedQueueHeads
.Blink
;
461 // get queue head structure
463 PreviousQH
= CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
468 Entry
= QueueHead
->LinkedQueueHeads
.Flink
;
471 // get queue head structure
473 NextQH
= CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
478 ASSERT(QueueHead
->HorizontalLinkPointer
== (NextQH
->PhysicalAddr
| QH_TYPE_QH
));
481 // remove queue head from linked list
483 PreviousQH
->HorizontalLinkPointer
= NextQH
->PhysicalAddr
| QH_TYPE_QH
;
486 // remove software link
488 RemoveEntryList(&QueueHead
->LinkedQueueHeads
);
492 // LinkQueueHeadChain - Links a list of QueueHeads to the HeadQueueHead list, updating HorizontalLinkPointer.
495 CUSBQueue::LinkQueueHeadChain(
496 PQUEUE_HEAD HeadQueueHead
,
497 PQUEUE_HEAD NewQueueHead
)
499 PQUEUE_HEAD LastQueueHead
;
501 ASSERT(HeadQueueHead
);
502 ASSERT(NewQueueHead
);
505 // Find the last QueueHead in NewQueueHead
507 Entry
= NewQueueHead
->LinkedQueueHeads
.Blink
;
508 ASSERT(Entry
!= NewQueueHead
->LinkedQueueHeads
.Flink
);
509 LastQueueHead
= CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
512 // Set the LinkPointer and Flink
514 LastQueueHead
->HorizontalLinkPointer
= HeadQueueHead
->PhysicalAddr
| QH_TYPE_QH
;
515 LastQueueHead
->LinkedQueueHeads
.Flink
= &HeadQueueHead
->LinkedQueueHeads
;
518 // Fine the last QueueHead in HeadQueueHead
520 Entry
= HeadQueueHead
->LinkedQueueHeads
.Blink
;
521 HeadQueueHead
->LinkedQueueHeads
.Blink
= &LastQueueHead
->LinkedQueueHeads
;
522 LastQueueHead
= CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
523 LastQueueHead
->LinkedQueueHeads
.Flink
= &NewQueueHead
->LinkedQueueHeads
;
524 LastQueueHead
->HorizontalLinkPointer
= NewQueueHead
->PhysicalAddr
| QH_TYPE_QH
;
528 // UnlinkQueueHeadChain - Unlinks a list number of QueueHeads from HeadQueueHead list, updating HorizontalLinkPointer.
529 // returns the chain of QueueHeads removed from HeadQueueHead.
532 CUSBQueue::UnlinkQueueHeadChain(
533 PQUEUE_HEAD HeadQueueHead
,
536 PQUEUE_HEAD LastQueueHead
, FirstQueueHead
;
541 // Find the last QueueHead in NewQueueHead
543 Entry
= &HeadQueueHead
->LinkedQueueHeads
;
544 FirstQueueHead
= CONTAINING_RECORD(Entry
->Flink
, QUEUE_HEAD
, LinkedQueueHeads
);
546 for (Index
= 0; Index
< Count
; Index
++)
548 Entry
= Entry
->Flink
;
550 if (Entry
== &HeadQueueHead
->LinkedQueueHeads
)
552 DPRINT1("Warnnig; Only %d QueueHeads in HeadQueueHead\n", Index
);
558 LastQueueHead
= CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
559 HeadQueueHead
->LinkedQueueHeads
.Flink
= LastQueueHead
->LinkedQueueHeads
.Flink
;
560 if (Count
+ 1 == Index
)
562 HeadQueueHead
->LinkedQueueHeads
.Blink
= &HeadQueueHead
->LinkedQueueHeads
;
565 HeadQueueHead
->LinkedQueueHeads
.Blink
= LastQueueHead
->LinkedQueueHeads
.Flink
;
567 FirstQueueHead
->LinkedQueueHeads
.Blink
= &LastQueueHead
->LinkedQueueHeads
;
568 LastQueueHead
->LinkedQueueHeads
.Flink
= &FirstQueueHead
->LinkedQueueHeads
;
569 LastQueueHead
->HorizontalLinkPointer
= TERMINATE_POINTER
;
570 return FirstQueueHead
;
574 CUSBQueue::QueueHeadCompletion(
575 PQUEUE_HEAD CurrentQH
,
581 // now unlink the queue head
582 // FIXME: implement chained queue heads
583 // no need to acquire locks, as it is called with locks held
589 UnlinkQueueHead(CurrentQH
);
592 // insert into completed list
594 InsertTailList(&m_CompletedRequestAsyncList
, &CurrentQH
->LinkedQueueHeads
);
598 CUSBQueue::ProcessAsyncList(
600 OUT PULONG ShouldRingDoorBell
)
604 PQUEUE_HEAD QueueHead
;
605 IUSBRequest
* Request
;
606 BOOLEAN IsQueueHeadComplete
;
609 // lock completed async list
611 KeAcquireSpinLock(&m_Lock
, &OldLevel
);
616 Entry
= AsyncListQueueHead
->LinkedQueueHeads
.Flink
;
618 while(Entry
!= &AsyncListQueueHead
->LinkedQueueHeads
)
621 // get queue head structure
623 QueueHead
= (PQUEUE_HEAD
)CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
628 PC_ASSERT(QueueHead
->Request
);
631 // get IUSBRequest interface
633 Request
= (IUSBRequest
*)QueueHead
->Request
;
636 // move to next entry
638 Entry
= Entry
->Flink
;
641 // check if queue head is complete
643 IsQueueHeadComplete
= Request
->IsQueueHeadComplete(QueueHead
);
645 DPRINT("Request %p QueueHead %p Complete %d\n", Request
, QueueHead
, IsQueueHeadComplete
);
648 // check if queue head is complete
650 if (IsQueueHeadComplete
)
653 // current queue head is complete
655 QueueHeadCompletion(QueueHead
, Status
);
658 // ring door bell is going to be necessary
660 *ShouldRingDoorBell
= TRUE
;
667 KeReleaseSpinLock(&m_Lock
, OldLevel
);
672 CUSBQueue::InterruptCallback(
674 OUT PULONG ShouldRingDoorBell
)
677 DPRINT("CUSBQueue::InterruptCallback\n");
680 // iterate asynchronous list
682 *ShouldRingDoorBell
= FALSE
;
683 ProcessAsyncList(Status
, ShouldRingDoorBell
);
686 // TODO: implement periodic schedule processing
691 CUSBQueue::QueueHeadCleanup(
692 PQUEUE_HEAD CurrentQH
)
694 PQUEUE_HEAD NewQueueHead
;
695 IUSBRequest
* Request
;
696 BOOLEAN ShouldReleaseWhenDone
;
697 USBD_STATUS UrbStatus
;
702 PC_ASSERT(CurrentQH
->Token
.Bits
.Active
== 0);
703 PC_ASSERT(CurrentQH
->Request
);
709 Request
= (IUSBRequest
*)CurrentQH
->Request
;
717 // check if the queue head was completed with errors
719 if (CurrentQH
->Token
.Bits
.Halted
)
721 if (CurrentQH
->Token
.Bits
.DataBufferError
)
726 UrbStatus
= USBD_STATUS_DATA_BUFFER_ERROR
;
728 else if (CurrentQH
->Token
.Bits
.BabbleDetected
)
733 UrbStatus
= USBD_STATUS_BABBLE_DETECTED
;
740 UrbStatus
= USBD_STATUS_STALL_PID
;
748 UrbStatus
= USBD_STATUS_SUCCESS
;
752 // Check if the transfer was completed and if UrbStatus is ok
754 if ((Request
->IsRequestComplete() == FALSE
) && (UrbStatus
== USBD_STATUS_SUCCESS
))
757 // let IUSBRequest free the queue head
759 Request
->FreeQueueHead(CurrentQH
);
762 // request is incomplete, get new queue head
764 if (Request
->GetQueueHead(&NewQueueHead
) == STATUS_SUCCESS
)
767 // add to pending list
769 InsertTailList(&m_PendingRequestAsyncList
, &NewQueueHead
->LinkedQueueHeads
);
776 DPRINT1("Unable to create a new QueueHead\n");
780 // Else there was a problem
781 // FIXME: Find better return
782 UrbStatus
= USBD_STATUS_INSUFFICIENT_RESOURCES
;
785 if (UrbStatus
!= USBD_STATUS_SUCCESS
) PC_ASSERT(FALSE
);
788 // notify request that a transfer has completed
790 Request
->CompletionCallback(UrbStatus
!= USBD_STATUS_SUCCESS
? STATUS_UNSUCCESSFUL
: STATUS_SUCCESS
,
795 // let IUSBRequest free the queue head
797 Request
->FreeQueueHead(CurrentQH
);
800 // check if we should release request when done
802 ShouldReleaseWhenDone
= Request
->ShouldReleaseRequestAfterCompletion();
805 // release reference when the request was added
810 // check if the operation was asynchronous
812 if (ShouldReleaseWhenDone
)
815 // release outstanding reference count
821 // request is now released
826 CUSBQueue::CompleteAsyncRequests()
830 PQUEUE_HEAD CurrentQH
;
831 IUSBRequest
*Request
;
833 DPRINT("CUSBQueue::CompleteAsyncRequests\n");
836 // first acquire request lock
838 KeAcquireSpinLock(&m_Lock
, &OldLevel
);
841 // the list should not be empty
843 PC_ASSERT(!IsListEmpty(&m_CompletedRequestAsyncList
));
845 while(!IsListEmpty(&m_CompletedRequestAsyncList
))
848 // remove first entry
850 Entry
= RemoveHeadList(&m_CompletedRequestAsyncList
);
853 // get queue head structure
855 CurrentQH
= (PQUEUE_HEAD
)CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
858 // Get the Request for this QueueHead
860 Request
= (IUSBRequest
*) CurrentQH
->Request
;
863 // complete request now
865 QueueHeadCleanup(CurrentQH
);
869 // is there a pending async entry
871 if (!IsListEmpty(&m_PendingRequestAsyncList
))
874 // remove first entry
876 Entry
= RemoveHeadList(&m_PendingRequestAsyncList
);
879 // get queue head structure
881 CurrentQH
= (PQUEUE_HEAD
)CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
884 // Add it to the AsyncList list
886 LinkQueueHead(AsyncListQueueHead
, CurrentQH
);
892 KeReleaseSpinLock(&m_Lock
, OldLevel
);
897 PUSBQUEUE
*OutUsbQueue
)
902 // allocate controller
904 This
= new(NonPagedPool
, TAG_USBEHCI
) CUSBQueue(0);
908 // failed to allocate
910 return STATUS_INSUFFICIENT_RESOURCES
;
914 // add reference count
921 *OutUsbQueue
= (PUSBQUEUE
)This
;
926 return STATUS_SUCCESS
;